diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp index a088535..4fb5c83 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp @@ -499,7 +499,7 @@ void PDFFloatBitmapWithColorSpace::convertToColorSpace(const PDFCMS* cms, if (!PDFAbstractColorSpace::transform(m_colorSpace.data(), targetColorSpace.data(), cms, intent, sourceProcessColors.getPixels(), targetProcessColors.getPixels(), reporter)) { - reporter->reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Transformation between blending color spaces failed.")); + reporter->reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Transformation between blending color space failed.")); } PDFFloatBitmapWithColorSpace temporary(getWidth(), getHeight(), newFormat, targetColorSpace); @@ -615,6 +615,8 @@ const PDFFloatBitmap& PDFTransparencyRenderer::endPaint() void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) { + auto mappedStrokeColor = getMappedStrokeColor(); + auto mappedFillColor = getMappedFillColor(); } void PDFTransparencyRenderer::performClipping(const QPainterPath& path, Qt::FillRule fillRule) @@ -635,7 +637,24 @@ void PDFTransparencyRenderer::performClipping(const QPainterPath& path, Qt::Fill void PDFTransparencyRenderer::performUpdateGraphicsState(const PDFPageContentProcessorState& state) { - Q_UNUSED(state); + PDFPageContentProcessorState::StateFlags stateFlags = state.getStateFlags(); + + const bool colorTransformAffected = stateFlags.testFlag(PDFPageContentProcessorState::StateRenderingIntent) || + stateFlags.testFlag(PDFPageContentProcessorState::StateBlackPointCompensation); + + if (colorTransformAffected || + stateFlags.testFlag(PDFPageContentProcessorState::StateStrokeColor) || + stateFlags.testFlag(PDFPageContentProcessorState::StateStrokeColorSpace)) + { + m_mappedStrokeColor.dirty(); + } + + if (colorTransformAffected || + stateFlags.testFlag(PDFPageContentProcessorState::StateFillColor) || + stateFlags.testFlag(PDFPageContentProcessorState::StateFillColorSpace)) + { + m_mappedFillColor.dirty(); + } } void PDFTransparencyRenderer::performSaveGraphicState(ProcessOrder order) @@ -716,6 +735,7 @@ void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order, data.immediateBackdrop.makeTransparent(); m_transparencyGroupDataStack.emplace_back(qMove(data)); + invalidateCachedItems(); } } @@ -736,6 +756,8 @@ void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, co PDFFloatBitmap::blend(sourceData.immediateBackdrop, targetData.immediateBackdrop, *getBackdrop(), *getInitialBackdrop(), sourceData.softMask, sourceData.alphaIsShape, sourceData.alphaFill, BlendMode::Normal, targetData.group.knockout, 0xFFFF, PDFFloatBitmap::OverprintMode::NoOveprint); + + invalidateCachedItems(); } } @@ -759,6 +781,12 @@ PDFReal PDFTransparencyRenderer::getOpacityFilling() const return !getGraphicState()->getAlphaIsShape() ? getGraphicState()->getAlphaFilling() : 1.0; } +void PDFTransparencyRenderer::invalidateCachedItems() +{ + m_mappedStrokeColor.dirty(); + m_mappedFillColor.dirty(); +} + void PDFTransparencyRenderer::removeInitialBackdrop() { PDFFloatBitmapWithColorSpace* immediateBackdrop = getImmediateBackdrop(); @@ -835,6 +863,125 @@ bool PDFTransparencyRenderer::isTransparencyGroupKnockout() const return m_transparencyGroupDataStack.back().group.knockout; } +const PDFTransparencyRenderer::PDFMappedColor& PDFTransparencyRenderer::getMappedStrokeColor() +{ + return m_mappedStrokeColor.get(this, &PDFTransparencyRenderer::getMappedStrokeColorImpl); +} + +const PDFTransparencyRenderer::PDFMappedColor& PDFTransparencyRenderer::getMappedFillColor() +{ + return m_mappedFillColor.get(this, &PDFTransparencyRenderer::getMappedFillColorImpl); +} + +void PDFTransparencyRenderer::fillMappedColorUsingMapping(const PDFPixelFormat pixelFormat, + PDFMappedColor& result, + const PDFInkMapping& inkMapping, + const PDFColor& sourceColor) +{ + result.mappedColor.resize(pixelFormat.getColorChannelCount()); + + // Zero the color + for (size_t i = 0; i < pixelFormat.getColorChannelCount(); ++i) + { + result.mappedColor[i] = 0.0f; + } + + for (const PDFInkMapping::Mapping& ink : inkMapping.mapping) + { + // Sanity check of source color + if (ink.source >= sourceColor.size()) + { + reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid source ink index %1.").arg(ink.source)); + continue; + } + + // Sanity check of target color + if (ink.target >= result.mappedColor.size()) + { + reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid target ink index %1.").arg(ink.target)); + continue; + } + + switch (ink.type) + { + case pdf::PDFInkMapping::Pass: + result.mappedColor[ink.target] = sourceColor[ink.source]; + break; + + default: + Q_ASSERT(false); + break; + } + } + + result.activeChannels = inkMapping.activeChannels; +} + +PDFTransparencyRenderer::PDFMappedColor PDFTransparencyRenderer::createMappedColor(const PDFColor& sourceColor, const PDFAbstractColorSpace* sourceColorSpace) +{ + PDFMappedColor result; + + const PDFAbstractColorSpace* targetColorSpace = getBlendColorSpace().data(); + const PDFPixelFormat pixelFormat = getImmediateBackdrop()->getPixelFormat(); + + Q_ASSERT(targetColorSpace->equals(getImmediateBackdrop()->getColorSpace().data())); + + PDFInkMapping inkMapping = m_inkMapper->createMapping(sourceColorSpace, targetColorSpace, pixelFormat); + if (inkMapping.isValid()) + { + fillMappedColorUsingMapping(pixelFormat, result, inkMapping, sourceColor); + } + else + { + // Jakub Melka: We must convert color form source color space to target color space + std::vector sourceColorVector(sourceColor.size(), 0.0f); + for (size_t i = 0; i < sourceColorVector.size(); ++i) + { + sourceColorVector[i] = sourceColor[i]; + } + PDFColorBuffer sourceColorBuffer(sourceColorVector.data(), sourceColorVector.size()); + + std::vector targetColorVector(pixelFormat.getProcessColorChannelCount(), 0.0f); + PDFColorBuffer targetColorBuffer(targetColorVector.data(), targetColorVector.size()); + + if (!PDFAbstractColorSpace::transform(sourceColorSpace, targetColorSpace, getCMS(), getGraphicState()->getRenderingIntent(), sourceColorBuffer, targetColorBuffer, this)) + { + reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Transformation from source color space to target blending color space failed.")); + } + + PDFColor adjustedSourceColor; + adjustedSourceColor.resize(targetColorBuffer.size()); + + for (size_t i = 0; i < targetColorBuffer.size(); ++i) + { + adjustedSourceColor[i] = targetColorBuffer[i]; + } + + inkMapping = m_inkMapper->createMapping(targetColorSpace, targetColorSpace, pixelFormat); + + Q_ASSERT(inkMapping.isValid()); + fillMappedColorUsingMapping(pixelFormat, result, inkMapping, adjustedSourceColor); + } + + return result; +} + +PDFTransparencyRenderer::PDFMappedColor PDFTransparencyRenderer::getMappedStrokeColorImpl() +{ + const PDFAbstractColorSpace* sourceColorSpace = getGraphicState()->getStrokeColorSpace(); + const PDFColor& sourceColor = getGraphicState()->getStrokeColorOriginal(); + + return createMappedColor(sourceColor, sourceColorSpace); +} + +PDFTransparencyRenderer::PDFMappedColor PDFTransparencyRenderer::getMappedFillColorImpl() +{ + const PDFAbstractColorSpace* sourceColorSpace = getGraphicState()->getFillColorSpace(); + const PDFColor& sourceColor = getGraphicState()->getFillColorOriginal(); + + return createMappedColor(sourceColor, sourceColorSpace); +} + PDFInkMapper::PDFInkMapper(const PDFDocument* document) : m_document(document) { @@ -892,6 +1039,7 @@ void PDFInkMapper::createSpotColors(bool activate) SpotColorInfo info; info.name = colorName; info.colorSpace = colorSpacePointer; + info.spotColorIndex = uint32_t(m_spotColors.size()); m_spotColors.emplace_back(qMove(info)); } } @@ -913,8 +1061,9 @@ void PDFInkMapper::createSpotColors(bool activate) { SpotColorInfo info; info.name = colorantInfo.name; - info.index = uint32_t(i); + info.colorSpaceIndex = uint32_t(i); info.colorSpace = colorSpacePointer; + info.spotColorIndex = uint32_t(m_spotColors.size()); m_spotColors.emplace_back(qMove(info)); } } @@ -958,4 +1107,83 @@ const PDFInkMapper::SpotColorInfo* PDFInkMapper::getSpotColor(const QByteArray& return nullptr; } +PDFInkMapping PDFInkMapper::createMapping(const PDFAbstractColorSpace* sourceColorSpace, + const PDFAbstractColorSpace* targetColorSpace, + PDFPixelFormat targetPixelFormat) const +{ + PDFInkMapping mapping; + + Q_ASSERT(targetColorSpace->getColorComponentCount() == targetPixelFormat.getProcessColorChannelCount()); + + if (sourceColorSpace->equals(targetColorSpace)) + { + Q_ASSERT(sourceColorSpace->getColorComponentCount() == targetColorSpace->getColorComponentCount()); + + uint8_t colorCount = uint8_t(targetColorSpace->getColorComponentCount()); + mapping.mapping.reserve(colorCount); + + for (uint8_t i = 0; i < colorCount; ++i) + { + mapping.map(i, i); + } + } + else + { + switch (sourceColorSpace->getColorSpace()) + { + case PDFAbstractColorSpace::ColorSpace::Separation: + { + const PDFSeparationColorSpace* separationColorSpace = dynamic_cast(sourceColorSpace); + + if (separationColorSpace->isAll()) + { + // Map this color to all device colors + uint32_t colorCount = static_cast(targetColorSpace->getColorComponentCount()); + mapping.mapping.reserve(colorCount); + + for (size_t i = 0; i < colorCount; ++i) + { + mapping.map(0, uint8_t(i)); + } + } + else if (!separationColorSpace->isNone() && !separationColorSpace->getColorName().isEmpty()) + { + const QByteArray& colorName = separationColorSpace->getColorName(); + const SpotColorInfo* info = getSpotColor(colorName); + if (info && info->active && targetPixelFormat.hasSpotColors() && info->spotColorIndex < targetPixelFormat.getSpotColorChannelCount()) + { + mapping.map(0, uint8_t(targetPixelFormat.getSpotColorChannelIndexStart() + info->spotColorIndex)); + } + } + + break; + } + + case PDFAbstractColorSpace::ColorSpace::DeviceN: + { + const PDFDeviceNColorSpace* deviceNColorSpace = dynamic_cast(sourceColorSpace); + + if (!deviceNColorSpace->isNone()) + { + const PDFDeviceNColorSpace::Colorants& colorants = deviceNColorSpace->getColorants(); + for (size_t i = 0; i < colorants.size(); ++i) + { + const PDFDeviceNColorSpace::ColorantInfo& colorantInfo = colorants[i]; + const SpotColorInfo* info = getSpotColor(colorantInfo.name); + + if (info && info->active && targetPixelFormat.hasSpotColors() && info->spotColorIndex < targetPixelFormat.getSpotColorChannelCount()) + { + mapping.map(uint8_t(i), uint8_t(targetColorSpace->getColorComponentCount() + info->spotColorIndex)); + } + } + } + + break; + } + } + } + + return mapping; +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.h b/Pdf4QtLib/sources/pdftransparencyrenderer.h index 6337ac7..6f1c82d 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.h +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.h @@ -22,6 +22,7 @@ #include "pdfcolorspaces.h" #include "pdfpagecontentprocessor.h" #include "pdfconstants.h" +#include "pdfutils.h" namespace pdf { @@ -236,6 +237,29 @@ private: PDFColorSpacePointer m_colorSpace; }; +/// Ink mapping +struct PDFInkMapping +{ + inline bool isValid() const { return !mapping.empty(); } + inline void reserve(size_t size) { mapping.reserve(size); } + inline void map(uint8_t source, uint8_t target) { mapping.emplace_back(Mapping{ source, target, Pass}); activeChannels |= 1 << target; } + + enum Type + { + Pass + }; + + struct Mapping + { + uint8_t source = 0; + uint8_t target = 0; + Type type = Pass; + }; + + std::vector mapping; + uint32_t activeChannels = 0; +}; + /// Ink mapper for mapping device inks (device colors) and spot inks (spot colors). class Pdf4QtLIBSHARED_EXPORT PDFInkMapper { @@ -245,7 +269,8 @@ public: struct SpotColorInfo { QByteArray name; - uint32_t index = 0; ///< Index into DeviceN color space (index of colorant) + uint32_t spotColorIndex = 0; ///< Index of this spot color + uint32_t colorSpaceIndex = 0; ///< Index into DeviceN color space (index of colorant) PDFColorSpacePointer colorSpace; bool active = false; ///< Is spot color active? }; @@ -269,6 +294,17 @@ public: /// \param colorName Color name const SpotColorInfo* getSpotColor(const QByteArray& colorName) const; + /// Creates color mapping from source color space to the target color space. + /// If mapping cannot be created, then invalid mapping is returned. Target + /// color space must be blending color space and must correspond to active + /// blending space, if used when painting. + /// \param sourceColorSpace Source color space + /// \param targetColorSpace Target color space + /// \param targetPixelFormat + PDFInkMapping createMapping(const PDFAbstractColorSpace* sourceColorSpace, + const PDFAbstractColorSpace* targetColorSpace, + PDFPixelFormat targetPixelFormat) const; + private: const PDFDocument* m_document; @@ -352,8 +388,23 @@ private: QPainterPath clipPath; ///< Clipping path in device state coordinates }; + struct PDFMappedColor + { + PDFColor mappedColor; + uint32_t activeChannels = 0; + }; + + void invalidateCachedItems(); void removeInitialBackdrop(); + void fillMappedColorUsingMapping(const PDFPixelFormat pixelFormat, + PDFMappedColor& result, + const PDFInkMapping& inkMapping, + const PDFColor& sourceColor); + + PDFMappedColor createMappedColor(const PDFColor& sourceColor, + const PDFAbstractColorSpace* sourceColorSpace); + PDFFloatBitmapWithColorSpace* getInitialBackdrop(); PDFFloatBitmapWithColorSpace* getImmediateBackdrop(); PDFFloatBitmapWithColorSpace* getBackdrop(); @@ -364,6 +415,12 @@ private: bool isTransparencyGroupIsolated() const; bool isTransparencyGroupKnockout() const; + const PDFMappedColor& getMappedStrokeColor(); + const PDFMappedColor& getMappedFillColor(); + + PDFMappedColor getMappedStrokeColorImpl(); + PDFMappedColor getMappedFillColorImpl(); + PDFColorSpacePointer m_deviceColorSpace; ///< Device color space (color space for final result) PDFColorSpacePointer m_processColorSpace; ///< Process color space (color space, in which is page graphic's blended) std::unique_ptr m_pageTransparencyGroupGuard; @@ -371,6 +428,8 @@ private: std::stack m_painterStateStack; const PDFInkMapper* m_inkMapper; bool m_active; + PDFCachedItem m_mappedStrokeColor; + PDFCachedItem m_mappedFillColor; }; } // namespace pdf