Soft mask implementation

This commit is contained in:
Jakub Melka 2021-03-24 19:55:31 +01:00
parent 67872a532f
commit 2e9459dfa9
6 changed files with 426 additions and 65 deletions

View File

@ -507,6 +507,21 @@ PDFCMYK PDFBlendFunction::blend_Nonseparable(BlendMode mode, PDFCMYK Cb, PDFCMYK
return Cs; return Cs;
} }
PDFColorComponent PDFBlendFunction::getLuminosity(PDFGray gray)
{
return nonseparable_Lum(nonseparable_gray2rgb(gray));
}
PDFColorComponent PDFBlendFunction::getLuminosity(PDFRGB rgb)
{
return nonseparable_Lum(rgb);
}
PDFColorComponent PDFBlendFunction::getLuminosity(PDFCMYK cmyk)
{
return nonseparable_Lum(nonseparable_cmyk2rgb(cmyk));
}
PDFRGB PDFBlendFunction::nonseparable_gray2rgb(PDFGray gray) PDFRGB PDFBlendFunction::nonseparable_gray2rgb(PDFGray gray)
{ {
return nonseparable_SetLum(PDFRGB{ 0.0f, 0.0f, 0.0f }, gray); return nonseparable_SetLum(PDFRGB{ 0.0f, 0.0f, 0.0f }, gray);

View File

@ -192,6 +192,18 @@ public:
/// \param Cs Source color /// \param Cs Source color
static PDFCMYK blend_Nonseparable(BlendMode mode, PDFCMYK Cb, PDFCMYK Cs); static PDFCMYK blend_Nonseparable(BlendMode mode, PDFCMYK Cb, PDFCMYK Cs);
/// Get luminosity from color value
/// \param gray Color value
static PDFColorComponent getLuminosity(PDFGray gray);
/// Get luminosity from color value
/// \param rgb Color value
static PDFColorComponent getLuminosity(PDFRGB rgb);
/// Get luminosity from color value
/// \param cmyk Color value
static PDFColorComponent getLuminosity(PDFCMYK cmyk);
/// Union function /// Union function
static constexpr PDFColorComponent blend_Union(PDFColorComponent b, PDFColorComponent s) { return b + s - b * s; } static constexpr PDFColorComponent blend_Union(PDFColorComponent b, PDFColorComponent s) { return b + s - b * s; }

View File

@ -481,6 +481,12 @@ bool PDFPageContentProcessor::isContentKindSuppressed(ContentKind kind) const
return false; return false;
} }
void PDFPageContentProcessor::setGraphicsState(const PDFPageContentProcessorState& state)
{
m_graphicState = state;
updateGraphicState();
}
bool PDFPageContentProcessor::isContentSuppressed() const bool PDFPageContentProcessor::isContentSuppressed() const
{ {
return std::any_of(m_markedContentStack.cbegin(), m_markedContentStack.cend(), [](const MarkedContentState& state) { return state.contentSuppressed; }); return std::any_of(m_markedContentStack.cbegin(), m_markedContentStack.cend(), [](const MarkedContentState& state) { return state.contentSuppressed; });
@ -2970,7 +2976,33 @@ void PDFPageContentProcessor::reportWarningAboutColorOperatorsInUTP()
reportRenderErrorOnce(RenderErrorType::Warning, PDFTranslationContext::tr("Color operators are not allowed in uncolored tilling pattern.")); reportRenderErrorOnce(RenderErrorType::Warning, PDFTranslationContext::tr("Color operators are not allowed in uncolored tilling pattern."));
} }
void PDFPageContentProcessor::operatorPaintXObject(PDFPageContentProcessor::PDFOperandName name) void PDFPageContentProcessor::processForm(const PDFStream* stream)
{
PDFDocumentDataLoaderDecorator loader(getDocument());
const PDFDictionary* streamDictionary = stream->getDictionary();
// Read the bounding rectangle, if it is present
QRectF boundingBox = loader.readRectangle(streamDictionary->get("BBox"), QRectF());
// Read the transformation matrix, if it is present
QMatrix transformationMatrix = loader.readMatrixFromDictionary(streamDictionary, "Matrix", QMatrix());
// Read the dictionary content
QByteArray content = m_document->getDecodedStream(stream);
// Read resources
PDFObject resources = m_document->getObject(streamDictionary->get("Resources"));
// Transparency group
PDFObject transparencyGroup = m_document->getObject(streamDictionary->get("Group"));
// Form structural parent key
const PDFInteger formStructuralParentKey = loader.readIntegerFromDictionary(streamDictionary, "StructParent", m_structuralParentKey);
processForm(transformationMatrix, boundingBox, resources, transparencyGroup, content, formStructuralParentKey);
}
void PDFPageContentProcessor::operatorPaintXObject(PDFOperandName name)
{ {
// We want to have empty operands, when we are invoking forms // We want to have empty operands, when we are invoking forms
m_operands.clear(); m_operands.clear();
@ -3014,25 +3046,7 @@ void PDFPageContentProcessor::operatorPaintXObject(PDFPageContentProcessor::PDFO
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Form of type %1 not supported.").arg(formType)); throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Form of type %1 not supported.").arg(formType));
} }
// Read the bounding rectangle, if it is present processForm(stream);
QRectF boundingBox = loader.readRectangle(streamDictionary->get("BBox"), QRectF());
// Read the transformation matrix, if it is present
QMatrix transformationMatrix = loader.readMatrixFromDictionary(streamDictionary, "Matrix", QMatrix());
// Read the dictionary content
QByteArray content = m_document->getDecodedStream(stream);
// Read resources
PDFObject resources = m_document->getObject(streamDictionary->get("Resources"));
// Transparency group
PDFObject transparencyGroup = m_document->getObject(streamDictionary->get("Group"));
// Form structural parent key
const PDFInteger formStructuralParentKey = loader.readIntegerFromDictionary(streamDictionary, "StructParent", m_structuralParentKey);
processForm(transformationMatrix, boundingBox, resources, transparencyGroup, content, formStructuralParentKey);
} }
else else
{ {
@ -3930,4 +3944,46 @@ void PDFLineDashPattern::fix()
} }
} }
PDFPageContentProcessor::PDFSoftMaskDefinition PDFPageContentProcessor::PDFSoftMaskDefinition::parse(const PDFDictionary* softMask, PDFPageContentProcessor* processor)
{
PDFSoftMaskDefinition result;
PDFDocumentDataLoaderDecorator loader(processor->getDocument());
constexpr const std::array type = {
std::pair<const char*, Type>{ "Alpha", Type::Alpha },
std::pair<const char*, Type>{ "Luminosity", Type::Luminosity }
};
result.m_type = loader.readEnumByName(softMask->get("S"), type.begin(), type.end(), Type::Invalid);
PDFObject streamObject = processor->getDocument()->getObject(softMask->get("G"));
result.m_formStream = streamObject.isStream() ? streamObject.getStream() : nullptr;
if (result.m_formStream)
{
result.m_transparencyGroup = processor->parseTransparencyGroup(result.m_formStream->getDictionary()->get("Group"));
}
std::vector<PDFReal> backdropColor = loader.readNumberArrayFromDictionary(softMask, "BC");
result.m_backdropColor = PDFAbstractColorSpace::convertToColor(backdropColor);
if (result.m_backdropColor.empty() && result.m_transparencyGroup.colorSpacePointer)
{
result.m_transparencyGroup.colorSpacePointer->getDefaultColorOriginal();
}
if (softMask->hasKey("TR"))
{
try
{
result.m_transferFunction = PDFFunction::createFunction(processor->getDocument(), softMask->get("TR"));
}
catch (PDFException)
{
processor->reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid soft mask transfer function."));
}
}
return result;
}
} // namespace pdf } // namespace pdf

View File

@ -246,6 +246,40 @@ protected:
bool knockout = false; bool knockout = false;
}; };
/// Parses transparency group
PDFTransparencyGroup parseTransparencyGroup(const PDFObject& object);
/// Soft mask definition
class PDFSoftMaskDefinition
{
public:
enum class Type
{
Invalid,
Alpha,
Luminosity
};
Type getType() const { return m_type; }
const PDFStream* getFormStream() const { return m_formStream; }
const PDFDictionary* getFormDictionary() const { return m_formStream ? m_formStream->getDictionary() : nullptr; }
const PDFTransparencyGroup& getTransparencyGroup() const { return m_transparencyGroup; }
const PDFColor& getBackdropColor() const { return m_backdropColor; }
const PDFFunction* getTransferFunction() const { return m_transferFunction.get(); }
static PDFSoftMaskDefinition parse(const PDFDictionary* softMask, PDFPageContentProcessor* processor);
private:
Type m_type = Type::Invalid;
const PDFStream* m_formStream = nullptr;
PDFTransparencyGroup m_transparencyGroup;
PDFColor m_backdropColor;
PDFFunctionPtr m_transferFunction;
friend class PDFPageContentProcessor;
};
struct PDFOverprintMode struct PDFOverprintMode
{ {
bool overprintStroking = false; bool overprintStroking = false;
@ -608,6 +642,10 @@ protected:
/// shading, images, ...) /// shading, images, ...)
virtual bool isContentKindSuppressed(ContentKind kind) const; virtual bool isContentKindSuppressed(ContentKind kind) const;
/// Sets current graphic state and updates data
/// \param state New graphic state
void setGraphicsState(const PDFPageContentProcessorState& state);
/// Returns current structural parent key /// Returns current structural parent key
PDFInteger getStructuralParentKey() const { return m_structuralParentKey; } PDFInteger getStructuralParentKey() const { return m_structuralParentKey; }
@ -646,8 +684,11 @@ protected:
/// Returns color management system /// Returns color management system
const PDFCMS* getCMS() const { return m_CMS; } const PDFCMS* getCMS() const { return m_CMS; }
/// Parses transparency group /// Returns font cache
PDFTransparencyGroup parseTransparencyGroup(const PDFObject& object); const PDFFontCache* getFontCache() const { return m_fontCache; }
/// Returns optional content activity
const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; }
class PDFTransparencyGroupGuard class PDFTransparencyGroupGuard
{ {
@ -659,6 +700,9 @@ protected:
PDFPageContentProcessor* m_processor; PDFPageContentProcessor* m_processor;
}; };
/// Process form using form stream
void processForm(const PDFStream* stream);
private: private:
/// Initializes the resources dictionaries /// Initializes the resources dictionaries
void initDictionaries(const PDFObject& resourcesObject); void initDictionaries(const PDFObject& resourcesObject);

View File

@ -211,6 +211,120 @@ PDFFloatBitmap PDFFloatBitmap::extractSpotChannel(uint8_t channel) const
return result; return result;
} }
PDFFloatBitmap PDFFloatBitmap::extractOpacityChannel() const
{
PDFFloatBitmap result(getWidth(), getHeight(), PDFPixelFormat::createOpacityMask());
if (m_format.hasOpacityChannel())
{
const uint8_t opacityChannel = m_format.getOpacityChannelIndex();
for (size_t x = 0; x < getWidth(); ++x)
{
for (size_t y = 0; y < getHeight(); ++y)
{
PDFConstColorBuffer sourceProcessColorBuffer = getPixel(x, y);
PDFColorBuffer targetProcessColorBuffer = result.getPixel(x, y);
targetProcessColorBuffer[0] = sourceProcessColorBuffer[opacityChannel];
}
}
}
else
{
result.makeOpaque();
}
return result;
}
PDFFloatBitmap PDFFloatBitmap::extractLuminosityChannel() const
{
PDFFloatBitmap result(getWidth(), getHeight(), PDFPixelFormat::createOpacityMask());
const uint8_t sourceChannelIndex = m_format.getProcessColorChannelIndexStart();
switch (m_format.getProcessColorChannelCount())
{
case 1:
{
for (size_t x = 0; x < getWidth(); ++x)
{
for (size_t y = 0; y < getHeight(); ++y)
{
PDFConstColorBuffer sourceProcessColorBuffer = getPixel(x, y);
PDFColorBuffer targetProcessColorBuffer = result.getPixel(x, y);
targetProcessColorBuffer[0] = PDFBlendFunction::getLuminosity(PDFGray(sourceProcessColorBuffer[sourceChannelIndex]));
}
}
break;
}
case 3:
{
for (size_t x = 0; x < getWidth(); ++x)
{
for (size_t y = 0; y < getHeight(); ++y)
{
PDFConstColorBuffer sourceProcessColorBuffer = getPixel(x, y);
PDFColorBuffer targetProcessColorBuffer = result.getPixel(x, y);
PDFColorComponent r = sourceProcessColorBuffer[sourceChannelIndex + 0];
PDFColorComponent g = sourceProcessColorBuffer[sourceChannelIndex + 1];
PDFColorComponent b = sourceProcessColorBuffer[sourceChannelIndex + 2];
targetProcessColorBuffer[0] = PDFBlendFunction::getLuminosity(PDFRGB{ r, g, b });
}
}
break;
}
case 4:
{
for (size_t x = 0; x < getWidth(); ++x)
{
for (size_t y = 0; y < getHeight(); ++y)
{
PDFConstColorBuffer sourceProcessColorBuffer = getPixel(x, y);
PDFColorBuffer targetProcessColorBuffer = result.getPixel(x, y);
PDFColorComponent _c = sourceProcessColorBuffer[sourceChannelIndex + 0];
PDFColorComponent _m = sourceProcessColorBuffer[sourceChannelIndex + 1];
PDFColorComponent _y = sourceProcessColorBuffer[sourceChannelIndex + 2];
PDFColorComponent _k = sourceProcessColorBuffer[sourceChannelIndex + 3];
targetProcessColorBuffer[0] = PDFBlendFunction::getLuminosity(PDFCMYK{ _c, _m, _y, _k });
}
}
break;
}
default:
{
result.makeOpaque();
break;
}
}
if (m_format.hasOpacityChannel())
{
const uint8_t opacityChannel = m_format.getOpacityChannelIndex();
for (size_t x = 0; x < getWidth(); ++x)
{
for (size_t y = 0; y < getHeight(); ++y)
{
PDFConstColorBuffer sourceProcessColorBuffer = getPixel(x, y);
PDFColorBuffer targetProcessColorBuffer = result.getPixel(x, y);
targetProcessColorBuffer[0] = sourceProcessColorBuffer[opacityChannel];
}
}
}
else
{
result.makeOpaque();
}
return result;
}
void PDFFloatBitmap::copyChannel(const PDFFloatBitmap& sourceBitmap, uint8_t channelFrom, uint8_t channelTo) void PDFFloatBitmap::copyChannel(const PDFFloatBitmap& sourceBitmap, uint8_t channelFrom, uint8_t channelTo)
{ {
Q_ASSERT(getWidth() == sourceBitmap.getWidth()); Q_ASSERT(getWidth() == sourceBitmap.getWidth());
@ -339,7 +453,7 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source,
PDFFloatBitmap& target, PDFFloatBitmap& target,
const PDFFloatBitmap& backdrop, const PDFFloatBitmap& backdrop,
const PDFFloatBitmap& initialBackdrop, const PDFFloatBitmap& initialBackdrop,
PDFFloatBitmap& softMask, const PDFFloatBitmap& blendSoftMask,
bool alphaIsShape, bool alphaIsShape,
PDFColorComponent constantAlpha, PDFColorComponent constantAlpha,
BlendMode mode, BlendMode mode,
@ -350,9 +464,9 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source,
Q_ASSERT(source.getWidth() == target.getWidth()); Q_ASSERT(source.getWidth() == target.getWidth());
Q_ASSERT(source.getHeight() == target.getHeight()); Q_ASSERT(source.getHeight() == target.getHeight());
Q_ASSERT(source.getPixelFormat() == target.getPixelFormat()); Q_ASSERT(source.getPixelFormat() == target.getPixelFormat());
Q_ASSERT(source.getWidth() == softMask.getWidth()); Q_ASSERT(source.getWidth() == blendSoftMask.getWidth());
Q_ASSERT(source.getHeight() == softMask.getHeight()); Q_ASSERT(source.getHeight() == blendSoftMask.getHeight());
Q_ASSERT(softMask.getPixelFormat() == PDFPixelFormat::createOpacityMask()); Q_ASSERT(blendSoftMask.getPixelFormat() == PDFPixelFormat::createOpacityMask());
Q_ASSERT(blendRegion.left() >= 0); Q_ASSERT(blendRegion.left() >= 0);
Q_ASSERT(blendRegion.top() >= 0); Q_ASSERT(blendRegion.top() >= 0);
@ -466,7 +580,7 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source,
PDFColorBuffer targetColor = target.getPixel(x, y); PDFColorBuffer targetColor = target.getPixel(x, y);
PDFConstColorBuffer backdropColor = backdrop.getPixel(x, y); PDFConstColorBuffer backdropColor = backdrop.getPixel(x, y);
PDFConstColorBuffer initialBackdropColor = initialBackdrop.getPixel(x, y); PDFConstColorBuffer initialBackdropColor = initialBackdrop.getPixel(x, y);
PDFColorBuffer alphaColorBuffer = softMask.getPixel(x, y); PDFConstColorBuffer alphaColorBuffer = blendSoftMask.getPixel(x, y);
const PDFColorComponent softMaskValue = alphaColorBuffer[0]; const PDFColorComponent softMaskValue = alphaColorBuffer[0];
const PDFColorComponent f_j_i = sourceColor[shapeChannel]; const PDFColorComponent f_j_i = sourceColor[shapeChannel];
@ -855,8 +969,11 @@ void PDFTransparencyRenderer::beginPaint(QSize pixelSize)
m_transparencyGroupDataStack.clear(); m_transparencyGroupDataStack.clear();
m_painterStateStack.push(PDFTransparencyPainterState()); m_painterStateStack.push(PDFTransparencyPainterState());
m_painterStateStack.top().softMask = PDFFloatBitmap(pixelSize.width(), pixelSize.height(), PDFPixelFormat::createOpacityMask());
m_painterStateStack.top().softMask.makeOpaque(); // Initialize initial opaque soft mask
PDFFloatBitmap initialSoftMaskBitmap(pixelSize.width(), pixelSize.height(), PDFPixelFormat::createOpacityMask());
initialSoftMaskBitmap.makeOpaque();
m_painterStateStack.top().softMask = PDFTransparencySoftMask(true, qMove(initialSoftMaskBitmap));
PDFPixelFormat pixelFormat = PDFPixelFormat::createFormat(uint8_t(m_deviceColorSpace->getColorComponentCount()), PDFPixelFormat pixelFormat = PDFPixelFormat::createFormat(uint8_t(m_deviceColorSpace->getColorComponentCount()),
uint8_t(m_inkMapper->getActiveSpotColorCount()), uint8_t(m_inkMapper->getActiveSpotColorCount()),
@ -993,11 +1110,11 @@ QImage PDFTransparencyRenderer::toImage(bool use16Bit, bool usePaper, const PDFR
PDFFloatBitmapWithColorSpace paperImage(floatImage.getWidth(), floatImage.getHeight(), floatImage.getPixelFormat(), floatImage.getColorSpace()); PDFFloatBitmapWithColorSpace paperImage(floatImage.getWidth(), floatImage.getHeight(), floatImage.getPixelFormat(), floatImage.getColorSpace());
createPaperBitmap(paperImage, paperColor); createPaperBitmap(paperImage, paperColor);
PDFFloatBitmap softMask; PDFFloatBitmap imageSoftMask;
createOpaqueSoftMask(softMask, paperImage.getWidth(), paperImage.getHeight()); createOpaqueSoftMask(imageSoftMask, paperImage.getWidth(), paperImage.getHeight());
QRect blendRegion(0, 0, int(floatImage.getWidth()), int(floatImage.getHeight())); QRect blendRegion(0, 0, int(floatImage.getWidth()), int(floatImage.getHeight()));
PDFFloatBitmapWithColorSpace::blend(floatImage, paperImage, paperImage, paperImage, softMask, false, 1.0f, BlendMode::Normal, false, PDFFloatBitmap::OverprintMode::NoOveprint, blendRegion); PDFFloatBitmapWithColorSpace::blend(floatImage, paperImage, paperImage, paperImage, imageSoftMask, false, 1.0f, BlendMode::Normal, false, PDFFloatBitmap::OverprintMode::NoOveprint, blendRegion);
return toImageImpl(paperImage, use16Bit); return toImageImpl(paperImage, use16Bit);
} }
@ -1291,7 +1408,7 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
PDFFloatBitmapWithColorSpace result; PDFFloatBitmapWithColorSpace result;
const PDFImageData& imageData = sourceImage.getImageData(); const PDFImageData& imageData = sourceImage.getImageData();
const PDFImageData& softMask = sourceImage.getSoftMaskData(); const PDFImageData& imageSoftMask = sourceImage.getSoftMaskData();
PDFColorSpacePointer imageColorSpace = sourceImage.getColorSpace(); PDFColorSpacePointer imageColorSpace = sourceImage.getColorSpace();
size_t colorComponentCount = imageColorSpace->getColorComponentCount(); size_t colorComponentCount = imageColorSpace->getColorComponentCount();
@ -1381,7 +1498,7 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
case PDFImageData::MaskingType::SoftMask: case PDFImageData::MaskingType::SoftMask:
{ {
PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(softMask); PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(imageSoftMask);
if (alphaMask.getWidth() != result.getWidth() || alphaMask.getHeight() != result.getHeight()) if (alphaMask.getWidth() != result.getWidth() || alphaMask.getHeight() != result.getHeight())
{ {
// Scale the alpha mask, if it is masked // Scale the alpha mask, if it is masked
@ -1479,8 +1596,8 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
result = PDFFloatBitmapWithColorSpace(imageData.getWidth(), imageData.getHeight(), PDFPixelFormat::createFormat(uint8_t(colorComponentCount), 0, true, isCMYK, false)); result = PDFFloatBitmapWithColorSpace(imageData.getWidth(), imageData.getHeight(), PDFPixelFormat::createFormat(uint8_t(colorComponentCount), 0, true, isCMYK, false));
result.setColorSpace(imageColorSpace); result.setColorSpace(imageColorSpace);
const bool hasMatte = !softMask.getMatte().empty(); const bool hasMatte = !imageSoftMask.getMatte().empty();
std::vector<PDFReal> matte = softMask.getMatte(); std::vector<PDFReal> matte = imageSoftMask.getMatte();
if (hasMatte && matte.size() != colorComponentCount) if (hasMatte && matte.size() != colorComponentCount)
{ {
@ -1504,7 +1621,7 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
const unsigned int imageWidth = imageData.getWidth(); const unsigned int imageWidth = imageData.getWidth();
const unsigned int imageHeight = imageData.getHeight(); const unsigned int imageHeight = imageData.getHeight();
PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(softMask); PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(imageSoftMask);
if (alphaMask.getWidth() != result.getWidth() || alphaMask.getHeight() != result.getHeight()) if (alphaMask.getWidth() != result.getWidth() || alphaMask.getHeight() != result.getHeight())
{ {
// Scale the alpha mask, if it is masked // Scale the alpha mask, if it is masked
@ -1668,33 +1785,33 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
return convertImageToBlendSpace(result); return convertImageToBlendSpace(result);
} }
PDFFloatBitmap PDFTransparencyRenderer::getAlphaMaskFromSoftMask(const PDFImageData& softMask) PDFFloatBitmap PDFTransparencyRenderer::getAlphaMaskFromSoftMask(const PDFImageData& imageSoftMask)
{ {
if (softMask.getMaskingType() != PDFImageData::MaskingType::None) if (imageSoftMask.getMaskingType() != PDFImageData::MaskingType::None)
{ {
throw PDFException(PDFTranslationContext::tr("Soft mask can't have masking.")); throw PDFException(PDFTranslationContext::tr("Soft mask can't have masking."));
} }
if (softMask.getWidth() < 1 || softMask.getHeight() < 1) if (imageSoftMask.getWidth() < 1 || imageSoftMask.getHeight() < 1)
{ {
throw PDFException(PDFTranslationContext::tr("Invalid size of soft mask.")); throw PDFException(PDFTranslationContext::tr("Invalid size of soft mask."));
} }
PDFFloatBitmap result(softMask.getWidth(), softMask.getHeight(), PDFPixelFormat::createFormat(0, 0, true, false, false)); PDFFloatBitmap result(imageSoftMask.getWidth(), imageSoftMask.getHeight(), PDFPixelFormat::createFormat(0, 0, true, false, false));
unsigned int componentCount = softMask.getComponents(); unsigned int componentCount = imageSoftMask.getComponents();
if (componentCount != 1) if (componentCount != 1)
{ {
throw PDFException(PDFTranslationContext::tr("Soft mask should have only 1 color component (alpha) instead of %1.").arg(componentCount)); throw PDFException(PDFTranslationContext::tr("Soft mask should have only 1 color component (alpha) instead of %1.").arg(componentCount));
} }
const std::vector<PDFReal>& decode = softMask.getDecode(); const std::vector<PDFReal>& decode = imageSoftMask.getDecode();
if (!decode.empty() && decode.size() != componentCount * 2) if (!decode.empty() && decode.size() != componentCount * 2)
{ {
throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size()));
} }
PDFBitReader reader(&softMask.getData(), softMask.getBitsPerComponent()); PDFBitReader reader(&imageSoftMask.getData(), imageSoftMask.getBitsPerComponent());
PDFColor color; PDFColor color;
color.resize(componentCount); color.resize(componentCount);
@ -1705,11 +1822,11 @@ PDFFloatBitmap PDFTransparencyRenderer::getAlphaMaskFromSoftMask(const PDFImageD
const uint8_t targetOpacityChannelIndex = result.getPixelFormat().getOpacityChannelIndex(); const uint8_t targetOpacityChannelIndex = result.getPixelFormat().getOpacityChannelIndex();
const bool alphaIsShape = getGraphicState()->getAlphaIsShape(); const bool alphaIsShape = getGraphicState()->getAlphaIsShape();
for (unsigned int i = 0, rowCount = softMask.getHeight(); i < rowCount; ++i) for (unsigned int i = 0, rowCount = imageSoftMask.getHeight(); i < rowCount; ++i)
{ {
reader.seek(i * softMask.getStride()); reader.seek(i * imageSoftMask.getStride());
for (unsigned int j = 0, colCount = softMask.getWidth(); j < colCount; ++j) for (unsigned int j = 0, colCount = imageSoftMask.getWidth(); j < colCount; ++j)
{ {
PDFColorComponent alpha = 0.0; PDFColorComponent alpha = 0.0;
PDFReal value = reader.read(); PDFReal value = reader.read();
@ -1736,6 +1853,84 @@ PDFFloatBitmap PDFTransparencyRenderer::getAlphaMaskFromSoftMask(const PDFImageD
return result; return result;
} }
void PDFTransparencyRenderer::processSoftMask(const PDFDictionary* softMask)
{
if (m_painterStateStack.empty())
{
// Jakub Melka: This occurs only in initialization phase.
// Just quit, opaque soft mask is initialized when beginPaint is called.
return;
}
if (!softMask)
{
// Make soft mask opaque
getPainterState()->softMask.makeOpaque();
}
else
{
PDFSoftMaskDefinition softMaskDefinition = PDFSoftMaskDefinition::parse(softMask, this);
if (!softMaskDefinition.getFormStream())
{
reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalind soft mask."));
getPainterState()->softMask.makeOpaque();
return;
}
// Jakub Melka: Define blend color space
PDFColorSpacePointer blendColorSpace = softMaskDefinition.getTransparencyGroup().colorSpacePointer;
if (!blendColorSpace)
{
blendColorSpace.reset(new PDFDeviceRGBColorSpace());
}
if (!blendColorSpace->isBlendColorSpace())
{
reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalind blend color space of soft mask definition."));
getPainterState()->softMask.makeOpaque();
return;
}
PDFInkMapper inkMapper(getDocument());
PDFTransparencyRenderer softMaskRenderer(getPage(), getDocument(), getFontCache(), getCMS(), getOptionalContentActivity(), &inkMapper, m_settings, getPagePointToDevicePointMatrix());
softMaskRenderer.initializeProcessor();
PDFPageContentProcessorState graphicState = *getGraphicState();
graphicState.setSoftMask(nullptr);
// TODO: soft mask background color
softMaskRenderer.setDeviceColorSpace(blendColorSpace);
softMaskRenderer.setProcessColorSpace(blendColorSpace);
softMaskRenderer.beginPaint(QSize(int(m_drawBuffer.getWidth()), int(m_drawBuffer.getHeight())));
softMaskRenderer.setGraphicsState(graphicState);
softMaskRenderer.processForm(softMaskDefinition.getFormStream());
const PDFFloatBitmap& renderedSoftMask = softMaskRenderer.endPaint();
PDFFloatBitmap softMask;
switch (softMaskDefinition.getType())
{
case pdf::PDFPageContentProcessor::PDFSoftMaskDefinition::Type::Alpha:
softMask = renderedSoftMask.extractOpacityChannel();
break;
case pdf::PDFPageContentProcessor::PDFSoftMaskDefinition::Type::Luminosity:
softMask = renderedSoftMask.extractLuminosityChannel();
break;
default:
case pdf::PDFPageContentProcessor::PDFSoftMaskDefinition::Type::Invalid:
reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid soft mask type."));
softMask = renderedSoftMask.extractOpacityChannel();
break;
}
getPainterState()->softMask = PDFTransparencySoftMask(false, qMove(softMask));
// TODO: use transfer function
}
}
void PDFTransparencyRenderer::createOpaqueBitmap(PDFFloatBitmap& bitmap) void PDFTransparencyRenderer::createOpaqueBitmap(PDFFloatBitmap& bitmap)
{ {
bitmap.makeOpaque(); bitmap.makeOpaque();
@ -2084,15 +2279,12 @@ void PDFTransparencyRenderer::performUpdateGraphicsState(const PDFPageContentPro
m_mappedFillColor.dirty(); m_mappedFillColor.dirty();
} }
BaseClass::performUpdateGraphicsState(state);
if (stateFlags.testFlag(PDFPageContentProcessorState::StateSoftMask)) if (stateFlags.testFlag(PDFPageContentProcessorState::StateSoftMask))
{ {
if (getGraphicState()->getSoftMask()) processSoftMask(state.getSoftMask());
{
reportRenderErrorOnce(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Soft mask not implemented."));
}
} }
BaseClass::performUpdateGraphicsState(state);
} }
void PDFTransparencyRenderer::performSaveGraphicState(ProcessOrder order) void PDFTransparencyRenderer::performSaveGraphicState(ProcessOrder order)
@ -2136,7 +2328,6 @@ void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order,
// Create initial backdrop, according to 11.4.8 of PDF 2.0 specification. // Create initial backdrop, according to 11.4.8 of PDF 2.0 specification.
// If group is knockout, use initial backdrop. // If group is knockout, use initial backdrop.
PDFFloatBitmapWithColorSpace* oldBackdrop = getBackdrop();
data.initialBackdrop = *getBackdrop(); data.initialBackdrop = *getBackdrop();
if (isTransparencyGroupIsolated()) if (isTransparencyGroupIsolated())
@ -2164,9 +2355,7 @@ void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order,
} }
// Prepare soft mask // Prepare soft mask
data.softMask = PDFFloatBitmap(oldBackdrop->getWidth(), oldBackdrop->getHeight(), PDFPixelFormat::createOpacityMask()); data.softMask = getPainterState()->softMask;
// TODO: Create soft mask
data.makeSoftMaskOpaque();
data.initialBackdrop.convertToColorSpace(getCMS(), data.renderingIntent, data.blendColorSpace, this); data.initialBackdrop.convertToColorSpace(getCMS(), data.renderingIntent, data.blendColorSpace, this);
data.immediateBackdrop = data.initialBackdrop; data.immediateBackdrop = data.initialBackdrop;
@ -2237,7 +2426,7 @@ void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, co
: PDFFloatBitmap::OverprintMode::Overprint_Mode_1; : PDFFloatBitmap::OverprintMode::Overprint_Mode_1;
} }
PDFFloatBitmap::blend(sourceData.immediateBackdrop, targetData.immediateBackdrop, *getBackdrop(), *getInitialBackdrop(), sourceData.softMask, PDFFloatBitmap::blend(sourceData.immediateBackdrop, targetData.immediateBackdrop, *getBackdrop(), *getInitialBackdrop(), *sourceData.softMask.getSoftMask(),
sourceData.alphaIsShape, sourceData.alphaFill, sourceData.blendMode, targetData.group.knockout, selectedOverprintMode, getPaintRect()); sourceData.alphaIsShape, sourceData.alphaFill, sourceData.blendMode, targetData.group.knockout, selectedOverprintMode, getPaintRect());
// Create draw buffer // Create draw buffer
@ -2673,7 +2862,7 @@ void PDFTransparencyRenderer::flushDrawBuffer()
: PDFFloatBitmap::OverprintMode::Overprint_Mode_1; : PDFFloatBitmap::OverprintMode::Overprint_Mode_1;
} }
PDFFloatBitmap::blend(m_drawBuffer, *getImmediateBackdrop(), *getBackdrop(), *getInitialBackdrop(), m_painterStateStack.top().softMask, PDFFloatBitmap::blend(m_drawBuffer, *getImmediateBackdrop(), *getBackdrop(), *getInitialBackdrop(), *getPainterState()->softMask.getSoftMask(),
getGraphicState()->getAlphaIsShape(), 1.0f, getGraphicState()->getBlendMode(), isTransparencyGroupKnockout(), getGraphicState()->getAlphaIsShape(), 1.0f, getGraphicState()->getBlendMode(), isTransparencyGroupKnockout(),
selectedOverprintMode, m_drawBuffer.getModifiedRect()); selectedOverprintMode, m_drawBuffer.getModifiedRect());
@ -3466,9 +3655,13 @@ void PDFTransparencyRenderer::PDFTransparencyGroupPainterData::makeImmediateBack
immediateBackdrop.setAllColorInactive(); immediateBackdrop.setAllColorInactive();
} }
void PDFTransparencyRenderer::PDFTransparencyGroupPainterData::makeSoftMaskOpaque() void PDFTransparencyRenderer::PDFTransparencySoftMask::makeOpaque()
{ {
softMask.makeOpaque(); if (!isOpaque())
{
m_data->isOpaque = true;
m_data->softMask.makeOpaque();
}
} }
} // namespace pdf } // namespace pdf

View File

@ -227,6 +227,15 @@ public:
/// \param channel Channel /// \param channel Channel
PDFFloatBitmap extractSpotChannel(uint8_t channel) const; PDFFloatBitmap extractSpotChannel(uint8_t channel) const;
/// Extract opacity channel. If bitmap doesn't have opacity channel,
/// opaque opacity bitmap of same size is returned.
PDFFloatBitmap extractOpacityChannel() const;
/// Extract luminosity channel as opacity channel. Bitmap should have
/// 1 (gray), 3 (red, green, blue) or 4 (cyan, magenta, yellow, black)
/// process colors. Otherwise opaque bitmap of same size is returned.
PDFFloatBitmap extractLuminosityChannel() const;
/// Copies channel from source bitmap to target channel in this bitmap /// Copies channel from source bitmap to target channel in this bitmap
/// \param sourceBitmap Source bitmap /// \param sourceBitmap Source bitmap
/// \param channelFrom Source channel /// \param channelFrom Source channel
@ -266,7 +275,7 @@ public:
PDFFloatBitmap& target, PDFFloatBitmap& target,
const PDFFloatBitmap& backdrop, const PDFFloatBitmap& backdrop,
const PDFFloatBitmap& initialBackdrop, const PDFFloatBitmap& initialBackdrop,
PDFFloatBitmap& softMask, const PDFFloatBitmap& softMask,
bool alphaIsShape, bool alphaIsShape,
PDFColorComponent constantAlpha, PDFColorComponent constantAlpha,
BlendMode mode, BlendMode mode,
@ -657,11 +666,39 @@ private:
PDFReal getShapeFilling() const; PDFReal getShapeFilling() const;
PDFReal getOpacityFilling() const; PDFReal getOpacityFilling() const;
struct PDFTransparencySoftMaskImpl : public QSharedData
{
PDFTransparencySoftMaskImpl() = default;
PDFTransparencySoftMaskImpl(bool isOpaque, PDFFloatBitmap softMask) :
isOpaque(isOpaque),
softMask(qMove(softMask))
{
}
bool isOpaque = false;
PDFFloatBitmap softMask;
};
class PDFTransparencySoftMask
{
public:
PDFTransparencySoftMask() { m_data = new PDFTransparencySoftMaskImpl; }
PDFTransparencySoftMask(bool isOpaque, PDFFloatBitmap softMask) { m_data = new PDFTransparencySoftMaskImpl(isOpaque, qMove(softMask)); }
bool isOpaque() const { return m_data->isOpaque; }
const PDFFloatBitmap* getSoftMask() const { return &m_data->softMask; }
void makeOpaque();
private:
QSharedDataPointer<PDFTransparencySoftMaskImpl> m_data;
};
struct PDFTransparencyGroupPainterData struct PDFTransparencyGroupPainterData
{ {
void makeInitialBackdropTransparent(); void makeInitialBackdropTransparent();
void makeImmediateBackdropTransparent(); void makeImmediateBackdropTransparent();
void makeSoftMaskOpaque();
PDFTransparencyGroup group; PDFTransparencyGroup group;
bool alphaIsShape = false; bool alphaIsShape = false;
@ -672,7 +709,7 @@ private:
RenderingIntent renderingIntent = RenderingIntent::RelativeColorimetric; RenderingIntent renderingIntent = RenderingIntent::RelativeColorimetric;
PDFFloatBitmapWithColorSpace initialBackdrop; ///< Initial backdrop PDFFloatBitmapWithColorSpace initialBackdrop; ///< Initial backdrop
PDFFloatBitmapWithColorSpace immediateBackdrop; ///< Immediate backdrop PDFFloatBitmapWithColorSpace immediateBackdrop; ///< Immediate backdrop
PDFFloatBitmap softMask; ///< Soft mask for this group PDFTransparencySoftMask softMask; ///< Soft mask for this group
PDFColorSpacePointer blendColorSpace; PDFColorSpacePointer blendColorSpace;
bool filterColorsUsingMask = false; bool filterColorsUsingMask = false;
uint32_t activeColorMask = PDFPixelFormat::getAllColorsMask(); uint32_t activeColorMask = PDFPixelFormat::getAllColorsMask();
@ -682,7 +719,7 @@ private:
struct PDFTransparencyPainterState struct PDFTransparencyPainterState
{ {
QPainterPath clipPath; ///< Clipping path in device state coordinates QPainterPath clipPath; ///< Clipping path in device state coordinates
PDFFloatBitmap softMask; PDFTransparencySoftMask softMask;
}; };
struct PDFMappedColor struct PDFMappedColor
@ -816,6 +853,10 @@ private:
/// \param imageData Soft mask data /// \param imageData Soft mask data
PDFFloatBitmap getAlphaMaskFromSoftMask(const PDFImageData& softMask); PDFFloatBitmap getAlphaMaskFromSoftMask(const PDFImageData& softMask);
/// Processes soft mask and sets it as active
/// \param softMask Soft mask
void processSoftMask(const PDFDictionary* softMask);
static void createOpaqueBitmap(PDFFloatBitmap& bitmap); static void createOpaqueBitmap(PDFFloatBitmap& bitmap);
static void createPaperBitmap(PDFFloatBitmap& bitmap, const PDFRGB& paperColor); static void createPaperBitmap(PDFFloatBitmap& bitmap, const PDFRGB& paperColor);
static void createOpaqueSoftMask(PDFFloatBitmap& softMask, size_t width, size_t height) { softMask = PDFFloatBitmap::createOpaqueSoftMask(width, height); } static void createOpaqueSoftMask(PDFFloatBitmap& softMask, size_t width, size_t height) { softMask = PDFFloatBitmap::createOpaqueSoftMask(width, height); }