From ea62e6ee32e16a6f4c8c0bbbf8d4b0b449d4df63 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Tue, 19 Jan 2021 20:14:50 +0100 Subject: [PATCH] Bitmap processing --- Pdf4QtLib/sources/pdfblendfunction.cpp | 42 +++- Pdf4QtLib/sources/pdfblendfunction.h | 43 ++++ Pdf4QtLib/sources/pdfpagecontentprocessor.h | 3 + Pdf4QtLib/sources/pdftransparencyrenderer.cpp | 221 +++++++++++++++++- Pdf4QtLib/sources/pdftransparencyrenderer.h | 70 +++++- 5 files changed, 376 insertions(+), 3 deletions(-) diff --git a/Pdf4QtLib/sources/pdfblendfunction.cpp b/Pdf4QtLib/sources/pdfblendfunction.cpp index 70521d3..2241cc1 100644 --- a/Pdf4QtLib/sources/pdfblendfunction.cpp +++ b/Pdf4QtLib/sources/pdfblendfunction.cpp @@ -323,9 +323,49 @@ PDFRGB PDFBlendFunction::blend_Luminosity(PDFRGB Cb, PDFRGB Cs) return nonseparable_SetLum(Cb, nonseparable_Lum(Cs)); } +PDFGray PDFBlendFunction::blend_Hue(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Hue(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFGray PDFBlendFunction::blend_Saturation(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Saturation(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFGray PDFBlendFunction::blend_Color(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Color(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFGray PDFBlendFunction::blend_Luminosity(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Luminosity(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFCMYK PDFBlendFunction::blend_Hue(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Hue(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); +} + +PDFCMYK PDFBlendFunction::blend_Saturation(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Saturation(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); +} + +PDFCMYK PDFBlendFunction::blend_Color(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Color(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); +} + +PDFCMYK PDFBlendFunction::blend_Luminosity(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Luminosity(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cs[3]); +} + PDFRGB PDFBlendFunction::nonseparable_gray2rgb(PDFGray gray) { - return PDFRGB{ gray, gray, gray }; + return nonseparable_SetLum(PDFRGB{ 0.0f, 0.0f, 0.0f }, gray); } PDFGray PDFBlendFunction::nonseparable_rgb2gray(PDFRGB rgb) diff --git a/Pdf4QtLib/sources/pdfblendfunction.h b/Pdf4QtLib/sources/pdfblendfunction.h index 85912a8..bd5dcb4 100644 --- a/Pdf4QtLib/sources/pdfblendfunction.h +++ b/Pdf4QtLib/sources/pdfblendfunction.h @@ -120,6 +120,49 @@ public: /// \param Cs Source color static PDFRGB blend_Luminosity(PDFRGB Cb, PDFRGB Cs); + /// Blend non-separable hue function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Hue(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable saturation function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Saturation(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable color function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Color(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable luminosity function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Luminosity(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable hue function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Hue(PDFCMYK Cb, PDFCMYK Cs); + + /// Blend non-separable saturation function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Saturation(PDFCMYK Cb, PDFCMYK Cs); + + /// Blend non-separable color function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Color(PDFCMYK Cb, PDFCMYK Cs); + + /// Blend non-separable luminosity function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Luminosity(PDFCMYK Cb, PDFCMYK Cs); + + /// Union function + static constexpr PDFColorComponent blend_Union(PDFColorComponent b, PDFColorComponent s) { return b + s - b * s; } + private: static PDFRGB nonseparable_gray2rgb(PDFGray gray); static PDFGray nonseparable_rgb2gray(PDFRGB rgb); diff --git a/Pdf4QtLib/sources/pdfpagecontentprocessor.h b/Pdf4QtLib/sources/pdfpagecontentprocessor.h index 89c693c..1397bcb 100644 --- a/Pdf4QtLib/sources/pdfpagecontentprocessor.h +++ b/Pdf4QtLib/sources/pdfpagecontentprocessor.h @@ -613,6 +613,9 @@ protected: /// Returns document const PDFDocument* getDocument() const { return m_document; } + /// Returns color management system + const PDFCMS* getCMS() const { return m_CMS; } + /// Parses transparency group PDFTransparencyGroup parseTransparencyGroup(const PDFObject& object); diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp index 9a613b1..173b5ad 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp @@ -17,6 +17,7 @@ #include "pdftransparencyrenderer.h" #include "pdfdocument.h" +#include "pdfcms.h" namespace pdf { @@ -37,7 +38,7 @@ PDFFloatBitmap::PDFFloatBitmap(size_t width, size_t height, PDFPixelFormat forma { Q_ASSERT(format.isValid()); - m_data.resize(format.calculateBitmapDataLength(width, height), static_cast(0.0)); + m_data.resize(format.calculateBitmapDataLength(width, height), static_cast(0.0f)); } PDFColorBuffer PDFFloatBitmap::getPixel(size_t x, size_t y) @@ -46,6 +47,11 @@ PDFColorBuffer PDFFloatBitmap::getPixel(size_t x, size_t y) return PDFColorBuffer(m_data.data() + index, m_pixelSize); } +PDFColorBuffer PDFFloatBitmap::getPixels() +{ + return PDFColorBuffer(m_data.data(), m_data.size()); +} + const PDFColorComponent* PDFFloatBitmap::begin() const { return m_data.data(); @@ -66,11 +72,146 @@ PDFColorComponent* PDFFloatBitmap::end() return m_data.data() + m_data.size(); } +void PDFFloatBitmap::makeTransparent() +{ + if (m_format.hasShapeChannel()) + { + fillChannel(m_format.getShapeChannelIndex(), 0.0f); + } + + if (m_format.hasOpacityChannel()) + { + fillChannel(m_format.getOpacityChannelIndex(), 0.0f); + } +} + +void PDFFloatBitmap::makeOpaque() +{ + if (m_format.hasShapeChannel()) + { + fillChannel(m_format.getShapeChannelIndex(), 1.0f); + } + + if (m_format.hasOpacityChannel()) + { + fillChannel(m_format.getOpacityChannelIndex(), 1.0f); + } +} + size_t PDFFloatBitmap::getPixelIndex(size_t x, size_t y) const { return (y * m_width + x) * m_pixelSize; } +PDFFloatBitmap PDFFloatBitmap::extractProcessColors() +{ + PDFPixelFormat format = PDFPixelFormat::createFormat(m_format.getProcessColorChannelCount(), 0, false, m_format.hasProcessColorsSubtractive()); + PDFFloatBitmap result(getWidth(), getHeight(), format); + + for (size_t x = 0; x < getWidth(); ++x) + { + for (size_t y = 0; y < getHeight(); ++y) + { + PDFColorBuffer sourceProcessColorBuffer = getPixel(x, y); + PDFColorBuffer targetProcessColorBuffer = result.getPixel(x, y); + + Q_ASSERT(sourceProcessColorBuffer.size() >= targetProcessColorBuffer.size()); + std::copy(sourceProcessColorBuffer.cbegin(), std::next(sourceProcessColorBuffer.cbegin(), targetProcessColorBuffer.size()), targetProcessColorBuffer.begin()); + } + } + + return result; +} + +void PDFFloatBitmap::fillChannel(size_t channel, PDFColorComponent value) +{ + // Do we have just one channel? + if (m_format.getChannelCount() == 1) + { + Q_ASSERT(channel == 0); + std::fill(m_data.begin(), m_data.end(), value); + return; + } + + for (PDFColorComponent* pixel = begin(); pixel != end(); pixel += m_pixelSize) + { + pixel[channel] = value; + } +} + +PDFFloatBitmapWithColorSpace::PDFFloatBitmapWithColorSpace() +{ + +} + +PDFFloatBitmapWithColorSpace::PDFFloatBitmapWithColorSpace(size_t width, size_t height, PDFPixelFormat format, PDFColorSpacePointer blendColorSpace) : + PDFFloatBitmap(width, height, format), + m_colorSpace(blendColorSpace) +{ + Q_ASSERT(!blendColorSpace || blendColorSpace->isBlendColorSpace()); +} + +PDFColorSpacePointer PDFFloatBitmapWithColorSpace::getColorSpace() const +{ + return m_colorSpace; +} + +void PDFFloatBitmapWithColorSpace::setColorSpace(const PDFColorSpacePointer& colorSpace) +{ + m_colorSpace = colorSpace; +} + +void PDFFloatBitmapWithColorSpace::convertToColorSpace(const PDFCMS* cms, + RenderingIntent intent, + const PDFColorSpacePointer& targetColorSpace, + PDFRenderErrorReporter* reporter) +{ + Q_ASSERT(m_colorSpace); + if (m_colorSpace->equals(targetColorSpace.get())) + { + return; + } + + const uint8_t targetDeviceColors = static_cast(targetColorSpace->getColorComponentCount()); + PDFPixelFormat newFormat = getPixelFormat(); + newFormat.setProcessColors(targetDeviceColors); + newFormat.setProcessColorsSubtractive(targetDeviceColors == 4); + + PDFFloatBitmap sourceProcessColors = extractProcessColors(); + PDFFloatBitmap targetProcessColors(sourceProcessColors.getWidth(), sourceProcessColors.getHeight(), PDFPixelFormat::createFormat(targetDeviceColors, 0, false, newFormat.hasProcessColorsSubtractive())); + + 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.")); + } + + PDFFloatBitmapWithColorSpace temporary(getWidth(), getHeight(), newFormat, targetColorSpace); + for (size_t x = 0; x < getWidth(); ++x) + { + for (size_t y = 0; y < getHeight(); ++y) + { + PDFColorBuffer sourceProcessColorBuffer = targetProcessColors.getPixel(x, y); + PDFColorBuffer sourceSpotColorAndOpacityBuffer = getPixel(x, y); + PDFColorBuffer targetBuffer = temporary.getPixel(x, y); + + Q_ASSERT(sourceProcessColorBuffer.size() <= targetBuffer.size()); + + // Copy process colors + auto targetIt = targetBuffer.begin(); + targetIt = std::copy(sourceProcessColorBuffer.cbegin(), sourceProcessColorBuffer.cend(), targetIt); + + Q_ASSERT(std::distance(targetIt, targetBuffer.end()) == temporary.getPixelFormat().getSpotColorChannelCount() + temporary.getPixelFormat().getAuxiliaryChannelCount()); + + auto sourceIt = std::next(sourceSpotColorAndOpacityBuffer.cbegin(), temporary.getPixelFormat().getProcessColorChannelCount()); + targetIt = std::copy(sourceIt, sourceSpotColorAndOpacityBuffer.cend(), targetIt); + + Q_ASSERT(targetIt == targetBuffer.cend()); + } + } + + *this = qMove(temporary); +} + PDFTransparencyRenderer::PDFTransparencyRenderer(const PDFPage* page, const PDFDocument* document, const PDFFontCache* fontCache, @@ -110,6 +251,8 @@ const PDFFloatBitmap& PDFTransparencyRenderer::endPaint() Q_ASSERT(m_active); m_pageTransparencyGroupGuard.reset(); m_active = false; + + return *getImmediateBackdrop(); } void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) @@ -134,10 +277,86 @@ void PDFTransparencyRenderer::performRestoreGraphicState(ProcessOrder order) void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) { + if (order == ProcessOrder::BeforeOperation) + { + PDFTransparencyGroupPainterData data; + data.group = transparencyGroup; + data.alphaIsShape = getGraphicState()->getAlphaIsShape(); + data.alphaFill = getGraphicState()->getAlphaFilling(); + data.alphaStroke = getGraphicState()->getAlphaStroking(); + data.blendMode = getGraphicState()->getBlendMode(); + data.blackPointCompensationMode = getGraphicState()->getBlackPointCompensationMode(); + data.renderingIntent = getGraphicState()->getRenderingIntent(); + data.blendColorSpace = transparencyGroup.colorSpacePointer; + + if (!data.blendColorSpace) + { + data.blendColorSpace = getBlendColorSpace(); + } + + // 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()) + { + // Make initial backdrop transparent + data.initialBackdrop.makeTransparent(); + } + + // Prepare soft mask + data.softMask = PDFFloatBitmap(oldBackdrop->getWidth(), oldBackdrop->getHeight(), PDFPixelFormat::createOpacityMask()); + // TODO: Create soft mask + data.softMask.makeOpaque(); + + data.initialBackdrop.convertToColorSpace(getCMS(), data.renderingIntent, data.blendColorSpace, this); + data.immediateBackdrop = data.initialBackdrop; + + m_transparencyGroupDataStack.emplace_back(qMove(data)); + } } void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) { + +} + +PDFFloatBitmapWithColorSpace* PDFTransparencyRenderer::getInitialBackdrop() +{ + return &m_transparencyGroupDataStack.back().initialBackdrop; +} + +PDFFloatBitmapWithColorSpace* PDFTransparencyRenderer::getImmediateBackdrop() +{ + return &m_transparencyGroupDataStack.back().immediateBackdrop; +} + +PDFFloatBitmapWithColorSpace* PDFTransparencyRenderer::getBackdrop() +{ + if (isTransparencyGroupKnockout()) + { + return getInitialBackdrop(); + } + else + { + return getImmediateBackdrop(); + } +} + +const PDFColorSpacePointer& PDFTransparencyRenderer::getBlendColorSpace() const +{ + return m_transparencyGroupDataStack.back().blendColorSpace; +} + +bool PDFTransparencyRenderer::isTransparencyGroupIsolated() const +{ + return m_transparencyGroupDataStack.back().group.isolated; +} + +bool PDFTransparencyRenderer::isTransparencyGroupKnockout() const +{ + return m_transparencyGroupDataStack.back().group.knockout; } } // namespace pdf diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.h b/Pdf4QtLib/sources/pdftransparencyrenderer.h index 8539004..7181fe0 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.h +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.h @@ -69,7 +69,19 @@ public: inline void setProcessColors(const uint8_t& processColors) { m_processColors = processColors; } inline void setSpotColors(const uint8_t& spotColors) { m_spotColors = spotColors; } + inline void setProcessColorsSubtractive(bool subtractive) + { + if (subtractive) + { + m_flags |= FLAG_PROCESS_COLORS_SUBTRACTIVE; + } + else + { + m_flags &= ~FLAG_PROCESS_COLORS_SUBTRACTIVE; + } + } + static constexpr PDFPixelFormat createOpacityMask() { return PDFPixelFormat(0, 0, FLAG_HAS_OPACITY_CHANNEL); } static constexpr PDFPixelFormat createFormatDefaultGray(uint8_t spotColors) { return createFormat(1, spotColors, true, false); } static constexpr PDFPixelFormat createFormatDefaultRGB(uint8_t spotColors) { return createFormat(3, spotColors, true, false); } static constexpr PDFPixelFormat createFormatDefaultCMYK(uint8_t spotColors) { return createFormat(4, spotColors, true, true); } @@ -117,18 +129,39 @@ public: /// Returns buffer with pixel channels PDFColorBuffer getPixel(size_t x, size_t y); + /// Returns buffer with all pixels + PDFColorBuffer getPixels(); + const PDFColorComponent* begin() const; const PDFColorComponent* end() const; PDFColorComponent* begin(); PDFColorComponent* end(); + size_t getWidth() const { return m_width; } + size_t getHeight() const { return m_height; } + size_t getPixelSize() const { return m_pixelSize; } + PDFPixelFormat getPixelFormat() const { return m_format; } + + /// Fills both shape and opacity channel with zero value. + /// If bitmap doesn't have shape/opacity channel, nothing happens. + void makeTransparent(); + + /// Fills both shape and opacity channel with 1.0 value. + /// If bitmap doesn't have shape/opacity channel, nothing happens. + void makeOpaque(); + /// Returns index where given pixel starts in the data block /// \param x Horizontal coordinate of the pixel /// \param y Vertical coordinate of the pixel size_t getPixelIndex(size_t x, size_t y) const; + /// Extract process colors into another bitmap + PDFFloatBitmap extractProcessColors(); + private: + void fillChannel(size_t channel, PDFColorComponent value); + PDFPixelFormat m_format; std::size_t m_width; std::size_t m_height; @@ -136,6 +169,29 @@ private: std::vector m_data; }; +/// Float bitmap with color space +class PDFFloatBitmapWithColorSpace : public PDFFloatBitmap +{ +public: + explicit PDFFloatBitmapWithColorSpace(); + explicit PDFFloatBitmapWithColorSpace(size_t width, size_t height, PDFPixelFormat format); + explicit PDFFloatBitmapWithColorSpace(size_t width, size_t height, PDFPixelFormat format, PDFColorSpacePointer blendColorSpace); + + PDFColorSpacePointer getColorSpace() const; + void setColorSpace(const PDFColorSpacePointer& colorSpace); + + /// Converts bitmap to target color space + /// \param cms Color management system + /// \param targetColorSpace Target color space + void convertToColorSpace(const PDFCMS* cms, + RenderingIntent intent, + const PDFColorSpacePointer& targetColorSpace, + PDFRenderErrorReporter* reporter); + +private: + PDFColorSpacePointer m_colorSpace; +}; + /// Renders PDF pages with transparency, using 32-bit floating point precision. /// Both device color space and blending color space can be defined. It implements /// page blending space and device blending space. So, painted graphics is being @@ -184,9 +240,21 @@ private: PDFReal alphaFill = 1.0; BlendMode blendMode = BlendMode::Normal; BlackPointCompensationMode blackPointCompensationMode = BlackPointCompensationMode::Default; - RenderingIntent RenderingIntent = RenderingIntent::RelativeColorimetric; + RenderingIntent renderingIntent = RenderingIntent::RelativeColorimetric; + PDFFloatBitmapWithColorSpace initialBackdrop; ///< Initial backdrop + PDFFloatBitmapWithColorSpace immediateBackdrop; ///< Immediate backdrop + PDFFloatBitmap softMask; ///< Soft mask for this group + PDFColorSpacePointer blendColorSpace; }; + PDFFloatBitmapWithColorSpace* getInitialBackdrop(); + PDFFloatBitmapWithColorSpace* getImmediateBackdrop(); + PDFFloatBitmapWithColorSpace* getBackdrop(); + const PDFColorSpacePointer& getBlendColorSpace() const; + + bool isTransparencyGroupIsolated() const; + bool isTransparencyGroupKnockout() const; + 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;