From 8e766376bc1c3bc840925b7edf747eeba92d6cc4 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 16 Feb 2019 18:26:16 +0100 Subject: [PATCH] Advanced color spaces --- PdfForQtLib/sources/pdfcolorspaces.cpp | 418 ++++++++++++++++++++++++- PdfForQtLib/sources/pdfcolorspaces.h | 272 +++++++++++++++- PdfForQtLib/sources/pdfdocument.cpp | 35 +++ PdfForQtLib/sources/pdfdocument.h | 59 ++++ PdfForQtLib/sources/pdfflatarray.h | 14 + PdfForQtLib/sources/pdfrenderer.cpp | 173 +++++++++- PdfForQtLib/sources/pdfrenderer_impl.h | 40 ++- 7 files changed, 982 insertions(+), 29 deletions(-) diff --git a/PdfForQtLib/sources/pdfcolorspaces.cpp b/PdfForQtLib/sources/pdfcolorspaces.cpp index 2515d4b..82eeaf8 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.cpp +++ b/PdfForQtLib/sources/pdfcolorspaces.cpp @@ -16,6 +16,9 @@ // along with PDFForQt. If not, see . #include "pdfcolorspaces.h" +#include "pdfobject.h" +#include "pdfdocument.h" +#include "pdfparser.h" namespace pdf { @@ -40,13 +43,7 @@ QColor PDFDeviceRGBColorSpace::getColor(const PDFColor& color) const { Q_ASSERT(color.size() == getColorComponentCount()); - PDFColorComponent r = clip01(color[0]); - PDFColorComponent g = clip01(color[1]); - PDFColorComponent b = clip01(color[2]); - - QColor result(QColor::Rgb); - result.setRgbF(r, g, b, 1.0); - return result; + return fromRGB01({ color[0], color[1], color[2] }); } size_t PDFDeviceRGBColorSpace::getColorComponentCount() const @@ -73,4 +70,411 @@ size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const return 4; } +PDFColorSpacePointer PDFAbstractColorSpace::createColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace) +{ + return createColorSpaceImpl(colorSpaceDictionary, document, colorSpace, COLOR_SPACE_MAX_LEVEL_OF_RECURSION); +} + +PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace, + int recursion) +{ + if (--recursion <= 0) + { + throw PDFParserException(PDFTranslationContext::tr("Can't load color space, because color space structure is too complex.")); + } + + if (colorSpace.isName()) + { + QByteArray name = colorSpace.getString(); + + if (name == COLOR_SPACE_NAME_DEVICE_GRAY || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY) + { + if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY)) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_GRAY)), recursion); + } + else + { + return PDFColorSpacePointer(new PDFDeviceGrayColorSpace()); + } + } + else if (name == COLOR_SPACE_NAME_DEVICE_RGB || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB) + { + if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB)) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_RGB)), recursion); + } + else + { + return PDFColorSpacePointer(new PDFDeviceRGBColorSpace()); + } + } + else if (name == COLOR_SPACE_NAME_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK) + { + if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK)) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_CMYK)), recursion); + } + else + { + return PDFColorSpacePointer(new PDFDeviceCMYKColorSpace()); + } + } + } + else if (colorSpace.isArray()) + { + // First value of the array should be identification name, second value dictionary with parameters + const PDFArray* array = colorSpace.getArray(); + size_t count = array->getCount(); + + if (count > 0) + { + // Name of the color space + const PDFObject& colorSpaceIdentifier = document->getObject(array->getItem(0)); + if (colorSpaceIdentifier.isName()) + { + QByteArray name = colorSpaceIdentifier.getString(); + + const PDFDictionary* dictionary = nullptr; + const PDFStream* stream = nullptr; + if (count > 1) + { + const PDFObject& colorSpaceSettings = document->getObject(array->getItem(1)); + if (colorSpaceSettings.isDictionary()) + { + dictionary = colorSpaceSettings.getDictionary(); + } + if (colorSpaceSettings.isStream()) + { + stream = colorSpaceSettings.getStream(); + } + } + + if (dictionary) + { + if (name == COLOR_SPACE_NAME_CAL_GRAY) + { + return PDFCalGrayColorSpace::createCalGrayColorSpace(document, dictionary); + } + else if (name == COLOR_SPACE_NAME_CAL_RGB) + { + return PDFCalRGBColorSpace::createCalRGBColorSpace(document, dictionary); + } + else if (name == COLOR_SPACE_NAME_LAB) + { + return PDFLabColorSpace::createLabColorSpace(document, dictionary); + } + } + + if (stream && name == COLOR_SPACE_NAME_ICCBASED) + { + return PDFICCBasedColorSpace::createICCBasedColorSpace(colorSpaceDictionary, document, stream, recursion); + } + + // Try to just load by standard way - we can have "standard" color space stored in array + return createColorSpaceImpl(colorSpaceDictionary, document, colorSpaceIdentifier, recursion); + } + } + } + + throw PDFParserException(PDFTranslationContext::tr("Invalid color space.")); + return PDFColorSpacePointer(); +} + +/// Conversion matrix from XYZ space to RGB space. Values are taken from this article: +/// https://en.wikipedia.org/wiki/SRGB#The_sRGB_transfer_function_.28.22gamma.22.29 +static constexpr const PDFColorComponentMatrix<3, 3> matrixXYZtoRGB( + 3.2406f, -1.5372f, -0.4986f, + -0.9689f, 1.8758f, 0.0415f, + 0.0557f, -0.2040f, 1.0570f +); + +PDFColor3 PDFAbstractColorSpace::convertXYZtoRGB(const PDFColor3& xyzColor) +{ + return matrixXYZtoRGB * xyzColor; +} + +PDFCalGrayColorSpace::PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma) : + m_whitePoint(whitePoint), + m_blackPoint(blackPoint), + m_gamma(gamma), + m_correctionCoefficients() +{ + PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); + + m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0]; + m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1]; + m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; +} + +QColor PDFCalGrayColorSpace::getColor(const PDFColor& color) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + + const PDFColorComponent A = clip01(color[0]); + const PDFColorComponent xyzColor = std::powf(A, m_gamma); + const PDFColor3 xyzColorMultipliedByWhitePoint = colorMultiplyByFactor(m_whitePoint, xyzColor); + const PDFColor3 rgb = convertXYZtoRGB(xyzColorMultipliedByWhitePoint); + const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); + return fromRGB01(calibratedRGB); +} + +size_t PDFCalGrayColorSpace::getColorComponentCount() const +{ + return 1; +} + +PDFColorSpacePointer PDFCalGrayColorSpace::createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) +{ + // Standard D65 white point + PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; + PDFColor3 blackPoint = { 0, 0, 0 }; + PDFColorComponent gamma = 1.0f; + + PDFDocumentDataLoaderDecorator loader(document); + loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); + gamma = loader.readNumberFromDictionary(dictionary, CAL_GAMMA, gamma); + + return PDFColorSpacePointer(new PDFCalGrayColorSpace(whitePoint, blackPoint, gamma)); +} + +PDFCalRGBColorSpace::PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix) : + m_whitePoint(whitePoint), + m_blackPoint(blackPoint), + m_gamma(gamma), + m_matrix(matrix), + m_correctionCoefficients() +{ + PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); + + m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0]; + m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1]; + m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; +} + +QColor PDFCalRGBColorSpace::getColor(const PDFColor& color) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + + const PDFColor3 ABC = clip01(PDFColor3{ color[0], color[1], color[2] }); + const PDFColor3 ABCwithGamma = colorPowerByFactors(ABC, m_gamma); + const PDFColor3 XYZ = m_matrix * ABCwithGamma; + const PDFColor3 rgb = convertXYZtoRGB(XYZ); + const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); + return fromRGB01(calibratedRGB); +} + +size_t PDFCalRGBColorSpace::getColorComponentCount() const +{ + return 3; +} + +PDFColorSpacePointer PDFCalRGBColorSpace::createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) +{ + // Standard D65 white point + PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; + PDFColor3 blackPoint = { 0, 0, 0 }; + PDFColor3 gamma = { 1.0f, 1.0f, 1.0f }; + PDFColorComponentMatrix_3x3 matrix( 1, 0, 0, + 0, 1, 0, + 0, 0, 1 ); + + PDFDocumentDataLoaderDecorator loader(document); + loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_GAMMA, gamma.begin(), gamma.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_MATRIX, matrix.begin(), matrix.end()); + + return PDFColorSpacePointer(new PDFCalRGBColorSpace(whitePoint, blackPoint, gamma, matrix)); +} + +PDFLabColorSpace::PDFLabColorSpace(PDFColor3 whitePoint, + PDFColor3 blackPoint, + PDFColorComponent aMin, + PDFColorComponent aMax, + PDFColorComponent bMin, + PDFColorComponent bMax) : + m_whitePoint(whitePoint), + m_blackPoint(blackPoint), + m_aMin(aMin), + m_aMax(aMax), + m_bMin(bMin), + m_bMax(bMax), + m_correctionCoefficients() +{ + PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); + + m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0]; + m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1]; + m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; +} + +QColor PDFLabColorSpace::getColor(const PDFColor& color) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + + const PDFColorComponent LStar = qBound(0.0f, color[0], 100.0f); + const PDFColorComponent aStar = qBound(m_aMin, color[1], m_aMax); + const PDFColorComponent bStar = qBound(m_bMin, color[2], m_bMax); + + const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f; + const PDFColorComponent param2 = aStar / 500.0f; + const PDFColorComponent param3 = bStar / 200.0f; + + const PDFColorComponent L = param1 + param2; + const PDFColorComponent M = param1; + const PDFColorComponent N = param1 - param3; + + auto g = [](PDFColorComponent x) -> PDFColorComponent + { + if (x >= 6.0f / 29.0f) + { + return x * x * x; + } + else + { + return (108.0f / 841.0f) * (x - 4.0f / 29.0f); + } + }; + + const PDFColorComponent gL = g(L); + const PDFColorComponent gM = g(M); + const PDFColorComponent gN = g(N); + const PDFColor3 gLMN = { gL, gM, gN }; + + const PDFColor3 XYZ = colorMultiplyByFactors(m_whitePoint, gLMN); + const PDFColor3 rgb = convertXYZtoRGB(XYZ); + const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); + return fromRGB01(calibratedRGB); +} + +size_t PDFLabColorSpace::getColorComponentCount() const +{ + return 3; +} + +PDFColorSpacePointer PDFLabColorSpace::createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) +{ + // Standard D65 white point + PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; + PDFColor3 blackPoint = { 0, 0, 0 }; + + static_assert(std::numeric_limits::has_infinity, "Fix this code!"); + const PDFColorComponent infPos = std::numeric_limits::infinity(); + const PDFColorComponent infNeg = -std::numeric_limits::infinity(); + std::array minMax = { infNeg, infPos, infNeg, infPos }; + + PDFDocumentDataLoaderDecorator loader(document); + loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_RANGE, minMax.begin(), minMax.end()); + + return PDFColorSpacePointer(new PDFLabColorSpace(whitePoint, blackPoint, minMax[0], minMax[1], minMax[2], minMax[3])); +} + +PDFICCBasedColorSpace::PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range) : + m_alternateColorSpace(qMove(alternateColorSpace)), + m_range(range) +{ + +} + +QColor PDFICCBasedColorSpace::getColor(const PDFColor& color) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + + size_t colorComponentCount = getColorComponentCount(); + + // Clip color values by range + PDFColor clippedColor = color; + for (size_t i = 0; i < colorComponentCount; ++i) + { + const size_t imin = 2 * i + 0; + const size_t imax = 2 * i + 1; + clippedColor[i] = qBound(m_range[imin], clippedColor[i], m_range[imax]); + } + + return m_alternateColorSpace->getColor(clippedColor); +} + +size_t PDFICCBasedColorSpace::getColorComponentCount() const +{ + return m_alternateColorSpace->getColorComponentCount(); +} + +PDFColorSpacePointer PDFICCBasedColorSpace::createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFStream* stream, + int recursion) +{ + // First, try to load alternate color space, if it is present + const PDFDictionary* dictionary = stream->getDictionary(); + + PDFDocumentDataLoaderDecorator loader(document); + PDFColorSpacePointer alternateColorSpace; + if (dictionary->hasKey(ICCBASED_ALTERNATE)) + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(dictionary->get(ICCBASED_ALTERNATE)), recursion); + } + else + { + // Determine color space from parameter N, which determines number of components + const PDFInteger N = loader.readIntegerFromDictionary(dictionary, ICCBASED_N, 0); + + switch (N) + { + case 1: + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(std::make_shared(std::move(QByteArray(COLOR_SPACE_NAME_DEVICE_GRAY)))), recursion); + break; + } + + case 3: + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(std::make_shared(std::move(QByteArray(COLOR_SPACE_NAME_DEVICE_RGB)))), recursion); + break; + } + + case 4: + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(std::make_shared(std::move(QByteArray(COLOR_SPACE_NAME_DEVICE_CMYK)))), recursion); + break; + } + + default: + { + throw PDFParserException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile. Number of components is %1.").arg(N)); + break; + } + } + } + + if (!alternateColorSpace) + { + throw PDFParserException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile.")); + } + + Ranges ranges = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; + static_assert(ranges.size() == 8, "Fix initialization above!"); + + const size_t components = alternateColorSpace->getColorComponentCount(); + const size_t rangeSize = 2 * components; + + if (rangeSize > ranges.size()) + { + throw PDFParserException(PDFTranslationContext::tr("Too much color components for ICC based profile.")); + } + + auto itStart = ranges.begin(); + auto itEnd = std::next(itStart, rangeSize); + loader.readNumberArrayFromDictionary(dictionary, ICCBASED_RANGE, itStart, itEnd); + + return PDFColorSpacePointer(new PDFICCBasedColorSpace(qMove(alternateColorSpace), ranges)); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfcolorspaces.h b/PdfForQtLib/sources/pdfcolorspaces.h index 607860a..2762f8a 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.h +++ b/PdfForQtLib/sources/pdfcolorspaces.h @@ -21,14 +21,87 @@ #include "pdfflatarray.h" #include +#include namespace pdf { +class PDFObject; +class PDFStream; +class PDFDocument; +class PDFDictionary; +class PDFAbstractColorSpace; using PDFColorComponent = float; using PDFColor = PDFFlatArray; +using PDFColorSpacePointer = QSharedPointer; -/// Represents PDF's color space +static constexpr const int COLOR_SPACE_MAX_LEVEL_OF_RECURSION = 12; + +static constexpr const char* COLOR_SPACE_NAME_DEVICE_GRAY = "DeviceGray"; +static constexpr const char* COLOR_SPACE_NAME_DEVICE_RGB = "DeviceRGB"; +static constexpr const char* COLOR_SPACE_NAME_DEVICE_CMYK = "DeviceCMYK"; + +static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY = "G"; +static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB = "RGB"; +static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK = "CMYK"; + +static constexpr const char* COLOR_SPACE_NAME_DEFAULT_GRAY = "DefaultGray"; +static constexpr const char* COLOR_SPACE_NAME_DEFAULT_RGB = "DefaultRGB"; +static constexpr const char* COLOR_SPACE_NAME_DEFAULT_CMYK = "DefaultCMYK"; + +static constexpr const char* COLOR_SPACE_NAME_CAL_GRAY = "CalGray"; +static constexpr const char* COLOR_SPACE_NAME_CAL_RGB = "CalRGB"; +static constexpr const char* COLOR_SPACE_NAME_LAB = "Lab"; +static constexpr const char* COLOR_SPACE_NAME_ICCBASED = "ICCBased"; + +static constexpr const char* CAL_WHITE_POINT = "WhitePoint"; +static constexpr const char* CAL_BLACK_POINT = "BlackPoint"; +static constexpr const char* CAL_GAMMA = "Gamma"; +static constexpr const char* CAL_MATRIX = "Matrix"; +static constexpr const char* CAL_RANGE = "Range"; + +static constexpr const char* ICCBASED_ALTERNATE = "Alternate"; +static constexpr const char* ICCBASED_N = "N"; +static constexpr const char* ICCBASED_RANGE = "Range"; + +using PDFColor3 = std::array; + +/// Matrix for color component multiplication (for example, conversion between some color spaces) +template +class PDFColorComponentMatrix +{ +public: + explicit constexpr inline PDFColorComponentMatrix() : m_values() { } + + template + explicit constexpr inline PDFColorComponentMatrix(Components... components) : m_values({ static_cast(components)... }) { } + + std::array operator*(const std::array& color) const + { + std::array result = { }; + + for (size_t row = 0; row < Rows; ++row) + { + for (size_t column = 0; column < Cols; ++column) + { + result[row] += m_values[row * Cols + column] * color[column]; + } + } + + return result; + } + + inline typename std::array::iterator begin() { return m_values.begin(); } + inline typename std::array::iterator end() { return m_values.end(); } + +private: + std::array m_values; +}; + +using PDFColorComponentMatrix_3x3 = PDFColorComponentMatrix<3, 3>; + +/// Represents PDF's color space (abstract class). Contains functions for parsing +/// color spaces. class PDFAbstractColorSpace { public: @@ -38,9 +111,99 @@ public: virtual QColor getColor(const PDFColor& color) const = 0; virtual size_t getColorComponentCount() const = 0; + /// Parses the desired color space. If desired color space is not found, then exception is thrown. + /// If everything is OK, then shared pointer to the new color space is returned. + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param colorSpace Identification of color space (either name or array), must be a direct object + static PDFColorSpacePointer createColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace); + protected: /// Clips the color component to range [0, 1] static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound(0.0, component, 1.0); } + + /// Clips the color to range [0 1] in all components + static constexpr PDFColor3 clip01(const PDFColor3& color) + { + PDFColor3 result = color; + + for (PDFColorComponent& component : result) + { + component = clip01(component); + } + + return result; + } + + /// Parses the desired color space. If desired color space is not found, then exception is thrown. + /// If everything is OK, then shared pointer to the new color space is returned. + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param colorSpace Identification of color space (either name or array), must be a direct object + /// \param recursion Recursion guard + static PDFColorSpacePointer createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace, + int recursion); + + /// Converts XYZ value to the standard RGB value (linear). No gamma correction is applied. + /// Default transformation matrix is applied. + /// \param xyzColor Color in XYZ space + static PDFColor3 convertXYZtoRGB(const PDFColor3& xyzColor); + + /// Multiplies color by factor + /// \param color Color to be multiplied + /// \param factor Multiplication factor + static constexpr PDFColor3 colorMultiplyByFactor(const PDFColor3& color, PDFColorComponent factor) + { + PDFColor3 result = color; + for (PDFColorComponent& component : result) + { + component *= factor; + } + return result; + } + + /// Multiplies color by factors (stored in components of the color) + /// \param color Color to be multiplied + /// \param factor Multiplication factors + static constexpr PDFColor3 colorMultiplyByFactors(const PDFColor3& color, const PDFColor3& factors) + { + PDFColor3 result = { }; + for (size_t i = 0; i < color.size(); ++i) + { + result[i] = color[i] * factors[i]; + } + return result; + } + + /// Powers color by factors (stored in components of the color) + /// \param color Color to be multiplied + /// \param factor Power factors + static constexpr PDFColor3 colorPowerByFactors(const PDFColor3& color, const PDFColor3& factors) + { + PDFColor3 result = { }; + for (size_t i = 0; i < color.size(); ++i) + { + result[i] = std::powf(color[i], factors[i]); + } + return result; + } + + /// Converts RGB values of range [0.0, 1.0] to standard QColor + /// \param color Color to be converted + static inline QColor fromRGB01(const PDFColor3& color) + { + PDFColorComponent r = clip01(color[0]); + PDFColorComponent g = clip01(color[1]); + PDFColorComponent b = clip01(color[2]); + + QColor result(QColor::Rgb); + result.setRgbF(r, g, b, 1.0); + return result; + } }; class PDFDeviceGrayColorSpace : public PDFAbstractColorSpace @@ -73,6 +236,113 @@ public: virtual size_t getColorComponentCount() const override; }; +class PDFCalGrayColorSpace : public PDFAbstractColorSpace +{ +public: + explicit inline PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma); + virtual ~PDFCalGrayColorSpace() = default; + + virtual QColor getColor(const PDFColor& color) const override; + virtual size_t getColorComponentCount() const override; + + /// Creates CalGray color space from provided values. + /// \param document Document + /// \param dictionary Dictionary + static PDFColorSpacePointer createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); + +private: + PDFColor3 m_whitePoint; + PDFColor3 m_blackPoint; + PDFColorComponent m_gamma; + + /// What are these coefficients? We want to map white point from XYZ space to white point + /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white + /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) + /// after multiplication by these coefficients. + PDFColor3 m_correctionCoefficients; +}; + +class PDFCalRGBColorSpace : public PDFAbstractColorSpace +{ +public: + explicit inline PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix); + virtual ~PDFCalRGBColorSpace() = default; + + virtual QColor getColor(const PDFColor& color) const override; + virtual size_t getColorComponentCount() const override; + + /// Creates CalRGB color space from provided values. + /// \param document Document + /// \param dictionary Dictionary + static PDFColorSpacePointer createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); + +private: + PDFColor3 m_whitePoint; + PDFColor3 m_blackPoint; + PDFColor3 m_gamma; + PDFColorComponentMatrix_3x3 m_matrix; + + /// What are these coefficients? We want to map white point from XYZ space to white point + /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white + /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) + /// after multiplication by these coefficients. + PDFColor3 m_correctionCoefficients; +}; + +class PDFLabColorSpace : public PDFAbstractColorSpace +{ +public: + explicit inline PDFLabColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent aMin, PDFColorComponent aMax, PDFColorComponent bMin, PDFColorComponent bMax); + virtual ~PDFLabColorSpace() = default; + + virtual QColor getColor(const PDFColor& color) const override; + virtual size_t getColorComponentCount() const override; + + /// Creates Lab color space from provided values. + /// \param document Document + /// \param dictionary Dictionary + static PDFColorSpacePointer createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); + +private: + PDFColor3 m_whitePoint; + PDFColor3 m_blackPoint; + PDFColorComponent m_aMin; + PDFColorComponent m_aMax; + PDFColorComponent m_bMin; + PDFColorComponent m_bMax; + + /// What are these coefficients? We want to map white point from XYZ space to white point + /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white + /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) + /// after multiplication by these coefficients. + PDFColor3 m_correctionCoefficients; +}; + +class PDFICCBasedColorSpace : public PDFAbstractColorSpace +{ +private: + static constexpr const size_t MAX_COLOR_COMPONENTS = 4; + using Ranges = std::array; + +public: + explicit inline PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range); + virtual ~PDFICCBasedColorSpace() = default; + + virtual QColor getColor(const PDFColor& color) const override; + virtual size_t getColorComponentCount() const override; + + /// Creates ICC based color space from provided values. + /// \param colorSpaceDictionary Color space dictionary + /// \param document Document + /// \param stream Stream with ICC profile + /// \param recursion Recursion guard + static PDFColorSpacePointer createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFStream* stream, int recursion); + +private: + PDFColorSpacePointer m_alternateColorSpace; + Ranges m_range; +}; + } // namespace pdf #endif // PDFCOLORSPACES_H diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index ea55df0..82773ca 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -273,6 +273,21 @@ PDFInteger PDFDocumentDataLoaderDecorator::readInteger(const PDFObject& object, return defaultValue; } +PDFReal PDFDocumentDataLoaderDecorator::readNumber(const PDFObject& object, PDFInteger defaultValue) const +{ + const PDFObject& dereferencedObject = m_document->getObject(object); + + if (dereferencedObject.isReal()) + { + return dereferencedObject.getReal(); + } else if (dereferencedObject.isInt()) + { + return dereferencedObject.getInteger(); + } + + return defaultValue; +} + QString PDFDocumentDataLoaderDecorator::readTextString(const PDFObject& object, const QString& defaultValue) const { const PDFObject& dereferencedObject = m_document->getObject(object); @@ -322,4 +337,24 @@ QRectF PDFDocumentDataLoaderDecorator::readRectangle(const PDFObject& object, co return defaultValue; } +PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readNumber(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + +PDFInteger PDFDocumentDataLoaderDecorator::readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readInteger(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index f992081..7ef2eab 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -92,6 +92,12 @@ public: /// \param defaultValue Default value PDFInteger readInteger(const PDFObject& object, PDFInteger defaultValue) const; + /// Reads a real number from the object, if it is possible. If integer appears as object, + /// then it is converted to real number. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + /// \param defaultValue Default value + PDFReal readNumber(const PDFObject& object, PDFInteger defaultValue) const; + /// Reads a text string from the object, if it is possible. /// \param object Object, can be an indirect reference to object (it is dereferenced) /// \param defaultValue Default value @@ -127,6 +133,59 @@ public: return defaultValue; } + /// Tries to read array of real values. Reads as much values as possible. + /// If array size differs, then nothing happens. + /// \param object Array of integers + /// \param first First iterator + /// \param second Second iterator + template + void readNumberArray(const PDFObject& object, T first, T last) + { + const PDFObject& dereferencedObject = m_document->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + + size_t distance = std::distance(first, last); + if (array->getCount() == distance) + { + T it = first; + for (size_t i = 0; i < distance; ++i) + { + *it = readNumber(array->getItem(i), *it); + ++it; + } + } + } + } + + /// Tries to read array of real values from dictionary. Reads as much values as possible. + /// If array size differs, or entry dictionary doesn't exist, then nothing happens. + /// \param dictionary Dictionary with desired values + /// \param key Entry key + /// \param first First iterator + /// \param second Second iterator + template + void readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, T first, T last) + { + if (dictionary->hasKey(key)) + { + readNumberArray(dictionary->get(key), first, last); + } + } + + /// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + /// \param defaultValue Default value + PDFReal readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const; + + /// Reads integer from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + /// \param defaultValue Default value + PDFInteger readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const; + private: const PDFDocument* m_document; }; diff --git a/PdfForQtLib/sources/pdfflatarray.h b/PdfForQtLib/sources/pdfflatarray.h index f3ed76c..5713097 100644 --- a/PdfForQtLib/sources/pdfflatarray.h +++ b/PdfForQtLib/sources/pdfflatarray.h @@ -104,6 +104,20 @@ public: } } + T& operator[] (size_t index) + { + Q_ASSERT(index < size()); + + if (index < FlatSize) + { + return m_flatBlock[index]; + } + else + { + return m_variableBlock[index - FlatSize]; + } + } + void clear() { m_flatBlockEndIterator = m_flatBlock.begin(); diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index 038ee62..6b05f76 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -69,16 +69,16 @@ static constexpr const std::pair { "re", PDFPageContentProcessor::Operator::Rectangle }, // Path painting: S, s, f, F, f*, B, B*, b, b*, n - { "S", PDFPageContentProcessor::Operator::StrokePath }, - { "s", PDFPageContentProcessor::Operator::CloseAndStrokePath }, - { "f", PDFPageContentProcessor::Operator::FillPathWinding }, - { "F", PDFPageContentProcessor::Operator::FillPathWinding2 }, - { "f*", PDFPageContentProcessor::Operator::FillPathEvenOdd }, - { "B", PDFPageContentProcessor::Operator::StrokeAndFillWinding }, - { "B*", PDFPageContentProcessor::Operator::StrokeAndFillEvenOdd }, - { "b", PDFPageContentProcessor::Operator::CloseAndStrokeAndFillWinding }, - { "b*", PDFPageContentProcessor::Operator::CloseAndStrokeAndFillEvenOdd }, - { "n", PDFPageContentProcessor::Operator::ClearPath }, + { "S", PDFPageContentProcessor::Operator::PathStroke }, + { "s", PDFPageContentProcessor::Operator::PathCloseStroke }, + { "f", PDFPageContentProcessor::Operator::PathFillWinding }, + { "F", PDFPageContentProcessor::Operator::PathFillWinding2 }, + { "f*", PDFPageContentProcessor::Operator::PathFillEvenOdd }, + { "B", PDFPageContentProcessor::Operator::PathFillStrokeWinding }, + { "B*", PDFPageContentProcessor::Operator::PathFillStrokeEvenOdd }, + { "b", PDFPageContentProcessor::Operator::PathCloseFillStrokeWinding }, + { "b*", PDFPageContentProcessor::Operator::PathCloseFillStrokeEvenOdd }, + { "n", PDFPageContentProcessor::Operator::PathClear }, // Clipping paths: W, W* { "W", PDFPageContentProcessor::Operator::ClipWinding }, @@ -221,6 +221,14 @@ QList PDFPageContentProcessor::processContents() return m_errorList; } +void PDFPageContentProcessor::performPathPainting(const QPainterPath& path, bool stroke, bool fill, Qt::FillRule fillRule) +{ + Q_UNUSED(path); + Q_UNUSED(stroke); + Q_UNUSED(fill); + Q_UNUSED(fillRule); +} + void PDFPageContentProcessor::processContentStream(const PDFStream* stream) { QByteArray content = m_document->getDecodedStream(stream); @@ -325,6 +333,61 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command) break; } + case Operator::PathStroke: + { + operatorPathStroke(); + break; + } + + case Operator::PathCloseStroke: + { + operatorPathCloseStroke(); + break; + } + + case Operator::PathFillWinding: + case Operator::PathFillWinding2: + { + operatorPathFillWinding(); + break; + } + + case Operator::PathFillEvenOdd: + { + operatorPathFillEvenOdd(); + break; + } + + case Operator::PathFillStrokeWinding: + { + operatorPathFillStrokeWinding(); + break; + } + + case Operator::PathFillStrokeEvenOdd: + { + operatorPathFillStrokeEvenOdd(); + break; + } + + case Operator::PathCloseFillStrokeWinding: + { + operatorPathCloseFillStrokeWinding(); + break; + } + + case Operator::PathCloseFillStrokeEvenOdd: + { + operatorPathCloseFillStrokeEvenOdd(); + break; + } + + case Operator::PathClear: + { + operatorPathClear(); + break; + } + case Operator::Invalid: { m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Unknown operator '%1'.").arg(QString::fromLatin1(command)))); @@ -358,7 +421,7 @@ QPointF PDFPageContentProcessor::getCurrentPoint() const template<> PDFReal PDFPageContentProcessor::readOperand(size_t index) const { - if (m_operands.size() < index) + if (index < m_operands.size()) { const PDFLexicalAnalyzer::Token& token = m_operands[index]; @@ -421,6 +484,94 @@ void PDFPageContentProcessor::operatorRectangle(PDFReal x, PDFReal y, PDFReal wi m_currentPath.addRect(QRectF(x, y, width, height)); } +void PDFPageContentProcessor::operatorPathStroke() +{ + // Do not close the path + if (!m_currentPath.isEmpty()) + { + performPathPainting(m_currentPath, true, false, Qt::WindingFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathCloseStroke() +{ + // Close the path + if (!m_currentPath.isEmpty()) + { + m_currentPath.closeSubpath(); + performPathPainting(m_currentPath, true, false, Qt::WindingFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathFillWinding() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.setFillRule(Qt::WindingFill); + performPathPainting(m_currentPath, false, true, Qt::WindingFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathFillEvenOdd() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.setFillRule(Qt::OddEvenFill); + performPathPainting(m_currentPath, false, true, Qt::OddEvenFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathFillStrokeWinding() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.setFillRule(Qt::WindingFill); + performPathPainting(m_currentPath, true, true, Qt::WindingFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathFillStrokeEvenOdd() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.setFillRule(Qt::OddEvenFill); + performPathPainting(m_currentPath, true, true, Qt::OddEvenFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathCloseFillStrokeWinding() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.closeSubpath(); + m_currentPath.setFillRule(Qt::WindingFill); + performPathPainting(m_currentPath, true, true, Qt::WindingFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathCloseFillStrokeEvenOdd() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.closeSubpath(); + m_currentPath.setFillRule(Qt::OddEvenFill); + performPathPainting(m_currentPath, true, true, Qt::OddEvenFill); + m_currentPath = QPainterPath(); + } +} + +void PDFPageContentProcessor::operatorPathClear() +{ + m_currentPath = QPainterPath(); +} + PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() : m_currentTransformationMatrix(), m_fillColorSpace(), diff --git a/PdfForQtLib/sources/pdfrenderer_impl.h b/PdfForQtLib/sources/pdfrenderer_impl.h index f88edfd..b7e1637 100644 --- a/PdfForQtLib/sources/pdfrenderer_impl.h +++ b/PdfForQtLib/sources/pdfrenderer_impl.h @@ -81,16 +81,16 @@ public: Rectangle, ///< re, adds rectangle // Path painting: S, s, f, F, f*, B, B*, b, b*, n - StrokePath, ///< S, stroke the path - CloseAndStrokePath, ///< s, close the path and then stroke (equivalent of operators h S) - FillPathWinding, ///< f, close the path, and then fill the path using "Non zero winding number rule" - FillPathWinding2, ///< F, same as previous, see PDF Reference 1.7, Table 4.10 - FillPathEvenOdd, ///< f*, fill the path using "Even-odd rule" - StrokeAndFillWinding, ///< B, stroke and fill path, using "Non zero winding number rule" - StrokeAndFillEvenOdd, ///< B*, stroke and fill path, using "Even-odd rule" - CloseAndStrokeAndFillWinding, ///< b, close, stroke and fill path, using "Non zero winding number rule", equivalent of operators h B - CloseAndStrokeAndFillEvenOdd, ///< b*, close, stroke and fill path, using "Even-odd rule", equivalent of operators h B* - ClearPath, ///< n, clear parh (close current) path, "no-operation", used with clipping + PathStroke, ///< S, Stroke + PathCloseStroke, ///< s, Close, Stroke (equivalent of operators h S) + PathFillWinding, ///< f, Fill, Winding + PathFillWinding2, ///< F, same as previous, see PDF Reference 1.7, Table 4.10 + PathFillEvenOdd, ///< f*, Fill, Even-Odd + PathFillStrokeWinding, ///< B, Fill, Stroke, Winding + PathFillStrokeEvenOdd, ///< B*, Fill, Stroke, Even-Odd + PathCloseFillStrokeWinding, ///< b, Close, Fill, Stroke, Winding (equivalent of operators h B) + PathCloseFillStrokeEvenOdd, ///< b*, Close, Fill, Stroke, Even-Odd (equivalent of operators h B*) + PathClear, ///< n, clear path (close current) path, "no-operation", used with clipping // Clipping paths: W, W* ClipWinding, ///< W, modify current clipping path by intersecting it with current path using "Non zero winding number rule" @@ -166,6 +166,15 @@ public: /// Process the contents of the page QList processContents(); +protected: + /// This function has to be implemented in the client drawing implementation, it should + /// draw the path according to the parameters. + /// \param path Path, which should be drawn (can be emtpy - in that case nothing happens) + /// \param stroke Stroke the path + /// \param fill Fill the path using given rule + /// \param fillRule Fill rule used in the fill mode + virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, Qt::FillRule fillRule); + private: /// Process the content stream void processContentStream(const PDFStream* stream); @@ -211,6 +220,17 @@ private: void operatorEndSubpath(); void operatorRectangle(PDFReal x, PDFReal y, PDFReal width, PDFReal height); + // Path painting operators + void operatorPathStroke(); + void operatorPathCloseStroke(); + void operatorPathFillWinding(); + void operatorPathFillEvenOdd(); + void operatorPathFillStrokeWinding(); + void operatorPathFillStrokeEvenOdd(); + void operatorPathCloseFillStrokeWinding(); + void operatorPathCloseFillStrokeEvenOdd(); + void operatorPathClear(); + /// Represents graphic state of the PDF (holding current graphic state parameters). /// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State" class PDFPageContentProcessorState