// Copyright (C) 2019 Jakub Melka // // This file is part of PdfForQt. // // PdfForQt is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // PdfForQt is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with PDFForQt. If not, see . #ifndef PDFCOLORSPACES_H #define PDFCOLORSPACES_H #include "pdfflatarray.h" #include #include namespace pdf { class PDFArray; class PDFObject; class PDFStream; class PDFDocument; class PDFDictionary; class PDFAbstractColorSpace; using PDFColorComponent = float; using PDFColor = PDFFlatArray; using PDFColorSpacePointer = QSharedPointer; static constexpr const int COLOR_SPACE_MAX_LEVEL_OF_RECURSION = 12; static constexpr const char* COLOR_SPACE_DICTIONARY = "ColorSpace"; 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* COLOR_SPACE_NAME_INDEXED = "Indexed"; 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: explicit PDFAbstractColorSpace() = default; virtual ~PDFAbstractColorSpace() = default; virtual QColor getDefaultColor() const = 0; 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); /// Creates device color space by name. Color space can be created by this function only, if /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). /// \param colorSpaceDictionary Dictionary containing color spaces of the page /// \param document Document (for loading objects) /// \param name Name of the color space static PDFColorSpacePointer createDeviceColorSpaceByName(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const QByteArray& name); 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); /// Creates device color space by name. Color space can be created by this function only, if /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). /// \param colorSpaceDictionary Dictionary containing color spaces of the page /// \param document Document (for loading objects) /// \param name Name of the color space static PDFColorSpacePointer createDeviceColorSpaceByNameImpl(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const QByteArray& name, 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 { public: explicit PDFDeviceGrayColorSpace() = default; virtual ~PDFDeviceGrayColorSpace() = default; virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; }; class PDFDeviceRGBColorSpace : public PDFAbstractColorSpace { public: explicit PDFDeviceRGBColorSpace() = default; virtual ~PDFDeviceRGBColorSpace() = default; virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; }; class PDFDeviceCMYKColorSpace : public PDFAbstractColorSpace { public: explicit PDFDeviceCMYKColorSpace() = default; virtual ~PDFDeviceCMYKColorSpace() = default; virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; }; class PDFXYZColorSpace : public PDFAbstractColorSpace { public: virtual QColor getDefaultColor() const override; protected: explicit PDFXYZColorSpace(PDFColor3 whitePoint); virtual ~PDFXYZColorSpace() = default; PDFColor3 m_whitePoint; /// 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 PDFCalGrayColorSpace : public PDFXYZColorSpace { 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_blackPoint; PDFColorComponent m_gamma; }; class PDFCalRGBColorSpace : public PDFXYZColorSpace { 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_blackPoint; PDFColor3 m_gamma; PDFColorComponentMatrix_3x3 m_matrix; }; class PDFLabColorSpace : public PDFXYZColorSpace { 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_blackPoint; PDFColorComponent m_aMin; PDFColorComponent m_aMax; PDFColorComponent m_bMin; PDFColorComponent m_bMax; }; 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 getDefaultColor() const override; 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; }; class PDFIndexedColorSpace : public PDFAbstractColorSpace { public: explicit inline PDFIndexedColorSpace(PDFColorSpacePointer baseColorSpace, QByteArray&& colors, int maxValue); virtual ~PDFIndexedColorSpace() = default; virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; /// Creates indexed color space from provided values. /// \param colorSpaceDictionary Color space dictionary /// \param document Document /// \param array Array with indexed color space definition /// \param recursion Recursion guard static PDFColorSpacePointer createIndexedColorSpace(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFArray* array, int recursion); private: static constexpr const int MIN_VALUE = 0; static constexpr const int MAX_VALUE = 255; PDFColorSpacePointer m_baseColorSpace; QByteArray m_colors; int m_maxValue; }; // TODO: Implement Separation color space // TODO: Implement DeviceN color space // TODO: Implement Pattern color space } // namespace pdf #endif // PDFCOLORSPACES_H