mirror of https://github.com/JakubMelka/PDF4QT.git
Soft mask implementation
This commit is contained in:
parent
67872a532f
commit
2e9459dfa9
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); }
|
||||
|
|
Loading…
Reference in New Issue