diff --git a/Pdf4QtLib/sources/pdfblendfunction.cpp b/Pdf4QtLib/sources/pdfblendfunction.cpp index 660f650..18bf83f 100644 --- a/Pdf4QtLib/sources/pdfblendfunction.cpp +++ b/Pdf4QtLib/sources/pdfblendfunction.cpp @@ -84,25 +84,27 @@ bool PDFBlendModeInfo::isSeparable(BlendMode mode) { switch (mode) { - case BlendMode::Normal: - case BlendMode::Multiply: - case BlendMode::Screen: - case BlendMode::Overlay: - case BlendMode::Darken: - case BlendMode::Lighten: - case BlendMode::ColorDodge: - case BlendMode::ColorBurn: - case BlendMode::HardLight: - case BlendMode::SoftLight: - case BlendMode::Difference: - case BlendMode::Exclusion: - case BlendMode::Compatible: + case BlendMode::Normal: + case BlendMode::Multiply: + case BlendMode::Screen: + case BlendMode::Overlay: + case BlendMode::Darken: + case BlendMode::Lighten: + case BlendMode::ColorDodge: + case BlendMode::ColorBurn: + case BlendMode::HardLight: + case BlendMode::SoftLight: + case BlendMode::Difference: + case BlendMode::Exclusion: + case BlendMode::Compatible: + case BlendMode::Overprint_SelectBackdrop: + case BlendMode::Overprint_SelectNonZeroSourceOrBackdrop: return true; - case BlendMode::Hue: - case BlendMode::Saturation: - case BlendMode::Color: - case BlendMode::Luminosity: + case BlendMode::Hue: + case BlendMode::Saturation: + case BlendMode::Color: + case BlendMode::Luminosity: return false; default: @@ -339,6 +341,19 @@ PDFColorComponent PDFBlendFunction::blend(BlendMode mode, PDFColorComponent Cb, case BlendMode::Exclusion: return Cb + Cs - 2.0f * Cb * Cs; + case BlendMode::Overprint_SelectBackdrop: + return Cb; + + case BlendMode::Overprint_SelectNonZeroSourceOrBackdrop: + { + if (qFuzzyIsNull(Cs)) + { + return Cb; + } + + return Cs; + } + default: { Q_ASSERT(false); diff --git a/Pdf4QtLib/sources/pdfblendfunction.h b/Pdf4QtLib/sources/pdfblendfunction.h index 6f99f6c..dbf9270 100644 --- a/Pdf4QtLib/sources/pdfblendfunction.h +++ b/Pdf4QtLib/sources/pdfblendfunction.h @@ -51,6 +51,10 @@ enum class BlendMode // to normal. It should be recognized for sake of compatibility. Compatible, ///< Equals to normal + // Special blend modes for handling overprint. Used only internally. + Overprint_SelectBackdrop, + Overprint_SelectNonZeroSourceOrBackdrop, + // Invalid blending mode - for internal purposes only Invalid }; diff --git a/Pdf4QtLib/sources/pdfcolorspaces.h b/Pdf4QtLib/sources/pdfcolorspaces.h index 10514c0..e5691ba 100644 --- a/Pdf4QtLib/sources/pdfcolorspaces.h +++ b/Pdf4QtLib/sources/pdfcolorspaces.h @@ -44,6 +44,7 @@ class PDFRenderErrorReporter; using PDFColor = PDFFlatArray; using PDFColorSpacePointer = QSharedPointer; using PDFColorBuffer = PDFBuffer; +using PDFConstColorBuffer = PDFBuffer; static constexpr const int COLOR_SPACE_MAX_LEVEL_OF_RECURSION = 12; diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp index b4d19b1..02a6896 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp @@ -47,6 +47,12 @@ PDFColorBuffer PDFFloatBitmap::getPixel(size_t x, size_t y) return PDFColorBuffer(m_data.data() + index, m_pixelSize); } +PDFConstColorBuffer PDFFloatBitmap::getPixel(size_t x, size_t y) const +{ + const size_t index = getPixelIndex(x, y); + return PDFConstColorBuffer(m_data.data() + index, m_pixelSize); +} + PDFColorBuffer PDFFloatBitmap::getPixels() { return PDFColorBuffer(m_data.data(), m_data.size()); @@ -130,7 +136,9 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source, PDFFloatBitmap& softMask, bool alphaIsShape, PDFColorComponent constantAlpha, - BlendMode mode) + BlendMode mode, + uint32_t activeColorChannels, + OverprintMode overprintMode) { Q_ASSERT(source.getWidth() == target.getWidth()); Q_ASSERT(source.getHeight() == target.getHeight()); @@ -146,16 +154,104 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source, const uint8_t opacityChannel = pixelFormat.getOpacityChannelIndex(); const uint8_t colorChannelStart = pixelFormat.getColorChannelIndexStart(); const uint8_t colorChannelEnd = pixelFormat.getColorChannelIndexEnd(); + const uint8_t processColorChannelStart = pixelFormat.getProcessColorChannelIndexStart(); + const uint8_t processColorChannelEnd = pixelFormat.getProcessColorChannelIndexEnd(); + const uint8_t spotColorChannelStart = pixelFormat.getSpotColorChannelIndexStart(); + const uint8_t spotColorChannelEnd = pixelFormat.getSpotColorChannelIndexEnd(); std::vector B_i(source.getPixelSize(), 0.0f); + std::vector channelBlendModes(source.getPixelSize(), mode); + + // For blending spot colors, only white preserving blend modes are possible. + // If this is not the case, revert spot color blend mode to normal blending. + // See 11.7.4.2 of PDF 2.0 specification. + if (pixelFormat.hasSpotColors() && !PDFBlendModeInfo::isWhitePreserving(mode)) + { + auto itBegin = std::next(channelBlendModes.begin(), spotColorChannelStart); + auto itEnd = std::next(channelBlendModes.begin(), spotColorChannelEnd); + std::fill(itBegin, itEnd, BlendMode::Normal); + } + + // Handle overprint mode for normal blend mode. We do not support + // oveprinting for other blend modes, than normal. + + switch (overprintMode) + { + case OverprintMode::NoOveprint: + break; + + case OverprintMode::Overprint_Mode_0: + { + // Select source color, if channel is active, + // otherwise select backdrop color. + for (uint8_t colorChannelIndex = colorChannelStart; colorChannelIndex < colorChannelEnd; ++colorChannelIndex) + { + uint32_t flag = (static_cast(1)) << colorChannelIndex; + if (channelBlendModes[colorChannelIndex] == BlendMode::Normal && !(activeColorChannels & flag)) + { + // Color channel is inactive + channelBlendModes[colorChannelIndex] = BlendMode::Overprint_SelectBackdrop; + } + } + + break; + } + + case OverprintMode::Overprint_Mode_1: + { + // For process colors, select source color, if it is nonzero, + // otherwise select backdrop. If process color channel is inactive, + // select backdrop. + if (pixelFormat.hasProcessColors() && mode == BlendMode::Normal) + { + for (uint8_t colorChannelIndex = processColorChannelStart; colorChannelIndex < processColorChannelEnd; ++colorChannelIndex) + { + uint32_t flag = (static_cast(1)) << colorChannelIndex; + if (!(activeColorChannels & flag)) + { + // Color channel is inactive + channelBlendModes[colorChannelIndex] = BlendMode::Overprint_SelectBackdrop; + } + else + { + // Color channel is active, but select source color only, if it is nonzero + channelBlendModes[colorChannelIndex] = BlendMode::Overprint_SelectNonZeroSourceOrBackdrop; + } + } + } + + if (pixelFormat.hasSpotColors()) + { + // For spot colors, select backdrop, if channel is inactive, + // otherwise select source color. + for (uint8_t colorChannelIndex = spotColorChannelStart; colorChannelIndex < spotColorChannelEnd; ++colorChannelIndex) + { + uint32_t flag = (static_cast(1)) << colorChannelIndex; + if (channelBlendModes[colorChannelIndex] == BlendMode::Normal && !(activeColorChannels & flag)) + { + // Color channel is inactive + channelBlendModes[colorChannelIndex] = BlendMode::Overprint_SelectBackdrop; + } + } + } + + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } for (size_t x = 0; x < width; ++x) { for (size_t y = 0; y < height; ++y) { - PDFColorBuffer sourceColor = source.getPixel(x, y); + PDFConstColorBuffer sourceColor = source.getPixel(x, y); PDFColorBuffer targetColor = target.getPixel(x, y); - PDFColorBuffer backdropColor = backdrop.getPixel(x, y); - PDFColorBuffer initialBackdropColor = initialBackdrop.getPixel(x, y); + PDFConstColorBuffer backdropColor = backdrop.getPixel(x, y); + PDFConstColorBuffer initialBackdropColor = initialBackdrop.getPixel(x, y); PDFColorBuffer alphaColorBuffer = softMask.getPixel(x, y); const PDFColorComponent softMaskValue = alphaColorBuffer[0]; @@ -199,40 +295,33 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source, { for (uint8_t i = pixelFormat.getProcessColorChannelIndexStart(); i < pixelFormat.getProcessColorChannelIndexEnd(); ++i) { - B_i[i] = PDFBlendFunction::blend(mode, backdropColor[i], sourceColor[i]); + B_i[i] = PDFBlendFunction::blend(channelBlendModes[i], backdropColor[i], sourceColor[i]); } } else { for (uint8_t i = pixelFormat.getProcessColorChannelIndexStart(); i < pixelFormat.getProcessColorChannelIndexEnd(); ++i) { - B_i[i] = 1.0f - PDFBlendFunction::blend(mode, 1.0f - backdropColor[i], 1.0f - sourceColor[i]); + B_i[i] = 1.0f - PDFBlendFunction::blend(channelBlendModes[i], 1.0f - backdropColor[i], 1.0f - sourceColor[i]); } } } if (pixelFormat.hasSpotColors()) { - // Blend mode for spot colors must be white-preserving, - // see 11.7.4.2 of PDF 2.0 specification - BlendMode spotBlendMode = mode; - if (!PDFBlendModeInfo::isWhitePreserving(mode)) - { - spotBlendMode = BlendMode::Normal; - } if (!isSpotColorSubtractive) { for (uint8_t i = pixelFormat.getSpotColorChannelIndexStart(); i < pixelFormat.getSpotColorChannelIndexEnd(); ++i) { - B_i[i] = PDFBlendFunction::blend(spotBlendMode, backdropColor[i], sourceColor[i]); + B_i[i] = PDFBlendFunction::blend(channelBlendModes[i], backdropColor[i], sourceColor[i]); } } else { for (uint8_t i = pixelFormat.getSpotColorChannelIndexStart(); i < pixelFormat.getSpotColorChannelIndexEnd(); ++i) { - B_i[i] = 1.0f - PDFBlendFunction::blend(spotBlendMode, 1.0f - backdropColor[i], 1.0f - sourceColor[i]); + B_i[i] = 1.0f - PDFBlendFunction::blend(channelBlendModes[i], 1.0f - backdropColor[i], 1.0f - sourceColor[i]); } } } @@ -305,14 +394,14 @@ void PDFFloatBitmap::blend(const PDFFloatBitmap& source, { for (uint8_t i = pixelFormat.getSpotColorChannelIndexStart(); i < pixelFormat.getSpotColorChannelIndexEnd(); ++i) { - B_i[i] = PDFBlendFunction::blend(BlendMode::Normal, backdropColor[i], sourceColor[i]); + B_i[i] = PDFBlendFunction::blend(channelBlendModes[i], backdropColor[i], sourceColor[i]); } } else { for (uint8_t i = pixelFormat.getSpotColorChannelIndexStart(); i < pixelFormat.getSpotColorChannelIndexEnd(); ++i) { - B_i[i] = 1.0f - PDFBlendFunction::blend(BlendMode::Normal, 1.0f - backdropColor[i], 1.0f - sourceColor[i]); + B_i[i] = 1.0f - PDFBlendFunction::blend(channelBlendModes[i], 1.0f - backdropColor[i], 1.0f - sourceColor[i]); } } } @@ -532,6 +621,8 @@ void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order, void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) { + Q_UNUSED(transparencyGroup); + if (order == ProcessOrder::AfterOperation) { // "Unblend" the initial backdrop from immediate backdrop, according to 11.4.8 @@ -543,7 +634,8 @@ void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, co PDFTransparencyGroupPainterData& targetData = m_transparencyGroupDataStack.back(); sourceData.immediateBackdrop.convertToColorSpace(getCMS(), targetData.renderingIntent, targetData.blendColorSpace, this); - PDFFloatBitmap::blend(sourceData, targetData, *getBackdrop(), *getInitialBackdrop(), sourceData.softMask, sourceData.alphaIsShape, sourceData.alphaFill, BlendMode::Normal); + PDFFloatBitmap::blend(sourceData.immediateBackdrop, targetData.immediateBackdrop, *getBackdrop(), *getInitialBackdrop(), sourceData.softMask, + sourceData.alphaIsShape, sourceData.alphaFill, BlendMode::Normal, 0xFFFF, PDFFloatBitmap::OverprintMode::NoOveprint); } } @@ -573,7 +665,7 @@ void PDFTransparencyRenderer::removeInitialBackdrop() if (!qFuzzyIsNull(alpha_g_n)) { - for (const uint8_t i = colorChannelIndexStart; i < colorChannelIndexEnd; ++i) + for (uint8_t i = colorChannelIndexStart; i < colorChannelIndexEnd; ++i) { const PDFColorComponent C_0 = initialBackdropColorBuffer[i]; const PDFColorComponent C_n = immediateBackdropColorBuffer[i]; diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.h b/Pdf4QtLib/sources/pdftransparencyrenderer.h index b964534..1dfa0f9 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.h +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.h @@ -134,6 +134,9 @@ public: /// Returns buffer with pixel channels PDFColorBuffer getPixel(size_t x, size_t y); + /// Returns constant buffer with pixel channels + PDFConstColorBuffer getPixel(size_t x, size_t y) const; + /// Returns buffer with all pixels PDFColorBuffer getPixels(); @@ -164,9 +167,19 @@ public: /// Extract process colors into another bitmap PDFFloatBitmap extractProcessColors(); + enum class OverprintMode + { + NoOveprint, ///< No oveprint performed + Overprint_Mode_0, ///< Overprint performed (either backdrop or source color is selected) + Overprint_Mode_1, ///< Overprint performed (only nonzero source color is selected, otherwise backdrop) + }; + /// Performs bitmap blending, pixel format of source and target must be the same. /// Blending algorithm uses the one from chapter 11.4.8 in the PDF 2.0 specification. /// Bitmap size must be equal for all three bitmaps (source, target and soft mask). + /// Oveprinting is also handled. You can specify a mask with active color channels. + /// If n-th bit in \p activeColorChannels variable is 1, then color channel is active; + /// otherwise backdrop color is selected (if overprint is active). /// \param source Source bitmap /// \param target Target bitmap /// \param backdrop Backdrop @@ -175,14 +188,18 @@ public: /// \param alphaIsShape Both soft mask and constant alpha are shapes and not opacity? /// \param constantAlpha Constant alpha, can mean shape or opacity /// \param mode Blend mode - void blend(const PDFFloatBitmap& source, - PDFFloatBitmap& target, - const PDFFloatBitmap& backdrop, - const PDFFloatBitmap& initialBackdrop, - PDFFloatBitmap& softMask, - bool alphaIsShape, - PDFColorComponent constantAlpha, - BlendMode mode); + /// \param activeColorChannels Active color channels + /// \param overprintMode Overprint mode + static void blend(const PDFFloatBitmap& source, + PDFFloatBitmap& target, + const PDFFloatBitmap& backdrop, + const PDFFloatBitmap& initialBackdrop, + PDFFloatBitmap& softMask, + bool alphaIsShape, + PDFColorComponent constantAlpha, + BlendMode mode, + uint32_t activeColorChannels, + OverprintMode overprintMode); private: void fillChannel(size_t channel, PDFColorComponent value);