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;
}
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)
{
return nonseparable_SetLum(PDFRGB{ 0.0f, 0.0f, 0.0f }, gray);

View File

@ -192,6 +192,18 @@ public:
/// \param Cs Source color
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
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;
}
void PDFPageContentProcessor::setGraphicsState(const PDFPageContentProcessorState& state)
{
m_graphicState = state;
updateGraphicState();
}
bool PDFPageContentProcessor::isContentSuppressed() const
{
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."));
}
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
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));
}
// 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);
processForm(stream);
}
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

View File

@ -246,6 +246,40 @@ protected:
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
{
bool overprintStroking = false;
@ -608,6 +642,10 @@ protected:
/// shading, images, ...)
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
PDFInteger getStructuralParentKey() const { return m_structuralParentKey; }
@ -646,8 +684,11 @@ protected:
/// Returns color management system
const PDFCMS* getCMS() const { return m_CMS; }
/// Parses transparency group
PDFTransparencyGroup parseTransparencyGroup(const PDFObject& object);
/// Returns font cache
const PDFFontCache* getFontCache() const { return m_fontCache; }
/// Returns optional content activity
const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; }
class PDFTransparencyGroupGuard
{
@ -659,6 +700,9 @@ protected:
PDFPageContentProcessor* m_processor;
};
/// Process form using form stream
void processForm(const PDFStream* stream);
private:
/// Initializes the resources dictionaries
void initDictionaries(const PDFObject& resourcesObject);

View File

@ -211,6 +211,120 @@ PDFFloatBitmap PDFFloatBitmap::extractSpotChannel(uint8_t channel) const
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)
{
Q_ASSERT(getWidth() == sourceBitmap.getWidth());
@ -339,7 +453,7 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source,
PDFFloatBitmap& target,
const PDFFloatBitmap& backdrop,
const PDFFloatBitmap& initialBackdrop,
PDFFloatBitmap& softMask,
const PDFFloatBitmap& blendSoftMask,
bool alphaIsShape,
PDFColorComponent constantAlpha,
BlendMode mode,
@ -350,9 +464,9 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source,
Q_ASSERT(source.getWidth() == target.getWidth());
Q_ASSERT(source.getHeight() == target.getHeight());
Q_ASSERT(source.getPixelFormat() == target.getPixelFormat());
Q_ASSERT(source.getWidth() == softMask.getWidth());
Q_ASSERT(source.getHeight() == softMask.getHeight());
Q_ASSERT(softMask.getPixelFormat() == PDFPixelFormat::createOpacityMask());
Q_ASSERT(source.getWidth() == blendSoftMask.getWidth());
Q_ASSERT(source.getHeight() == blendSoftMask.getHeight());
Q_ASSERT(blendSoftMask.getPixelFormat() == PDFPixelFormat::createOpacityMask());
Q_ASSERT(blendRegion.left() >= 0);
Q_ASSERT(blendRegion.top() >= 0);
@ -466,7 +580,7 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source,
PDFColorBuffer targetColor = target.getPixel(x, y);
PDFConstColorBuffer backdropColor = backdrop.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 f_j_i = sourceColor[shapeChannel];
@ -855,8 +969,11 @@ void PDFTransparencyRenderer::beginPaint(QSize pixelSize)
m_transparencyGroupDataStack.clear();
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()),
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());
createPaperBitmap(paperImage, paperColor);
PDFFloatBitmap softMask;
createOpaqueSoftMask(softMask, paperImage.getWidth(), paperImage.getHeight());
PDFFloatBitmap imageSoftMask;
createOpaqueSoftMask(imageSoftMask, paperImage.getWidth(), paperImage.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);
}
@ -1291,7 +1408,7 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
PDFFloatBitmapWithColorSpace result;
const PDFImageData& imageData = sourceImage.getImageData();
const PDFImageData& softMask = sourceImage.getSoftMaskData();
const PDFImageData& imageSoftMask = sourceImage.getSoftMaskData();
PDFColorSpacePointer imageColorSpace = sourceImage.getColorSpace();
size_t colorComponentCount = imageColorSpace->getColorComponentCount();
@ -1381,7 +1498,7 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
case PDFImageData::MaskingType::SoftMask:
{
PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(softMask);
PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(imageSoftMask);
if (alphaMask.getWidth() != result.getWidth() || alphaMask.getHeight() != result.getHeight())
{
// 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.setColorSpace(imageColorSpace);
const bool hasMatte = !softMask.getMatte().empty();
std::vector<PDFReal> matte = softMask.getMatte();
const bool hasMatte = !imageSoftMask.getMatte().empty();
std::vector<PDFReal> matte = imageSoftMask.getMatte();
if (hasMatte && matte.size() != colorComponentCount)
{
@ -1504,7 +1621,7 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
const unsigned int imageWidth = imageData.getWidth();
const unsigned int imageHeight = imageData.getHeight();
PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(softMask);
PDFFloatBitmap alphaMask = getAlphaMaskFromSoftMask(imageSoftMask);
if (alphaMask.getWidth() != result.getWidth() || alphaMask.getHeight() != result.getHeight())
{
// Scale the alpha mask, if it is masked
@ -1668,33 +1785,33 @@ PDFFloatBitmapWithColorSpace PDFTransparencyRenderer::getColoredImage(const PDFI
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."));
}
if (softMask.getWidth() < 1 || softMask.getHeight() < 1)
if (imageSoftMask.getWidth() < 1 || imageSoftMask.getHeight() < 1)
{
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)
{
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)
{
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;
color.resize(componentCount);
@ -1705,11 +1822,11 @@ PDFFloatBitmap PDFTransparencyRenderer::getAlphaMaskFromSoftMask(const PDFImageD
const uint8_t targetOpacityChannelIndex = result.getPixelFormat().getOpacityChannelIndex();
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;
PDFReal value = reader.read();
@ -1736,6 +1853,84 @@ PDFFloatBitmap PDFTransparencyRenderer::getAlphaMaskFromSoftMask(const PDFImageD
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)
{
bitmap.makeOpaque();
@ -2084,15 +2279,12 @@ void PDFTransparencyRenderer::performUpdateGraphicsState(const PDFPageContentPro
m_mappedFillColor.dirty();
}
BaseClass::performUpdateGraphicsState(state);
if (stateFlags.testFlag(PDFPageContentProcessorState::StateSoftMask))
{
if (getGraphicState()->getSoftMask())
{
reportRenderErrorOnce(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Soft mask not implemented."));
}
processSoftMask(state.getSoftMask());
}
BaseClass::performUpdateGraphicsState(state);
}
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.
// If group is knockout, use initial backdrop.
PDFFloatBitmapWithColorSpace* oldBackdrop = getBackdrop();
data.initialBackdrop = *getBackdrop();
if (isTransparencyGroupIsolated())
@ -2164,9 +2355,7 @@ void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order,
}
// Prepare soft mask
data.softMask = PDFFloatBitmap(oldBackdrop->getWidth(), oldBackdrop->getHeight(), PDFPixelFormat::createOpacityMask());
// TODO: Create soft mask
data.makeSoftMaskOpaque();
data.softMask = getPainterState()->softMask;
data.initialBackdrop.convertToColorSpace(getCMS(), data.renderingIntent, data.blendColorSpace, this);
data.immediateBackdrop = data.initialBackdrop;
@ -2237,7 +2426,7 @@ void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, co
: 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());
// Create draw buffer
@ -2673,7 +2862,7 @@ void PDFTransparencyRenderer::flushDrawBuffer()
: 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(),
selectedOverprintMode, m_drawBuffer.getModifiedRect());
@ -3466,9 +3655,13 @@ void PDFTransparencyRenderer::PDFTransparencyGroupPainterData::makeImmediateBack
immediateBackdrop.setAllColorInactive();
}
void PDFTransparencyRenderer::PDFTransparencyGroupPainterData::makeSoftMaskOpaque()
void PDFTransparencyRenderer::PDFTransparencySoftMask::makeOpaque()
{
softMask.makeOpaque();
if (!isOpaque())
{
m_data->isOpaque = true;
m_data->softMask.makeOpaque();
}
}
} // namespace pdf

View File

@ -227,6 +227,15 @@ public:
/// \param channel Channel
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
/// \param sourceBitmap Source bitmap
/// \param channelFrom Source channel
@ -266,7 +275,7 @@ public:
PDFFloatBitmap& target,
const PDFFloatBitmap& backdrop,
const PDFFloatBitmap& initialBackdrop,
PDFFloatBitmap& softMask,
const PDFFloatBitmap& softMask,
bool alphaIsShape,
PDFColorComponent constantAlpha,
BlendMode mode,
@ -657,11 +666,39 @@ private:
PDFReal getShapeFilling() 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
{
void makeInitialBackdropTransparent();
void makeImmediateBackdropTransparent();
void makeSoftMaskOpaque();
PDFTransparencyGroup group;
bool alphaIsShape = false;
@ -672,7 +709,7 @@ private:
RenderingIntent renderingIntent = RenderingIntent::RelativeColorimetric;
PDFFloatBitmapWithColorSpace initialBackdrop; ///< Initial backdrop
PDFFloatBitmapWithColorSpace immediateBackdrop; ///< Immediate backdrop
PDFFloatBitmap softMask; ///< Soft mask for this group
PDFTransparencySoftMask softMask; ///< Soft mask for this group
PDFColorSpacePointer blendColorSpace;
bool filterColorsUsingMask = false;
uint32_t activeColorMask = PDFPixelFormat::getAllColorsMask();
@ -682,7 +719,7 @@ private:
struct PDFTransparencyPainterState
{
QPainterPath clipPath; ///< Clipping path in device state coordinates
PDFFloatBitmap softMask;
PDFTransparencySoftMask softMask;
};
struct PDFMappedColor
@ -816,6 +853,10 @@ private:
/// \param imageData Soft mask data
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 createPaperBitmap(PDFFloatBitmap& bitmap, const PDFRGB& paperColor);
static void createOpaqueSoftMask(PDFFloatBitmap& softMask, size_t width, size_t height) { softMask = PDFFloatBitmap::createOpaqueSoftMask(width, height); }