From 3e345a768f1d574cf5cb6e7ae034f94a8e5d2235 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 1 Sep 2019 14:42:32 +0200 Subject: [PATCH] DeviceN color space --- PdfForQtLib/sources/pdfcolorspaces.cpp | 187 ++++++++++++++++++++++++- PdfForQtLib/sources/pdfcolorspaces.h | 56 +++++++- PdfForQtLib/sources/pdfdocument.cpp | 10 ++ PdfForQtLib/sources/pdfdocument.h | 6 + PdfForQtLib/sources/pdffunction.cpp | 24 +--- 5 files changed, 260 insertions(+), 23 deletions(-) diff --git a/PdfForQtLib/sources/pdfcolorspaces.cpp b/PdfForQtLib/sources/pdfcolorspaces.cpp index d75afee..7d74752 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.cpp +++ b/PdfForQtLib/sources/pdfcolorspaces.cpp @@ -243,7 +243,7 @@ QColor PDFAbstractColorSpace::getCheckedColor(const PDFColor& color) const { if (getColorComponentCount() != color.size()) { - throw PDFParserException(PDFTranslationContext::tr("Invalid number of color components. Expected number is %1, actual number is %2.").arg(getColorComponentCount(), color.size())); + throw PDFParserException(PDFTranslationContext::tr("Invalid number of color components. Expected number is %1, actual number is %2.").arg(static_cast(getColorComponentCount()), static_cast(color.size()))); } return getColor(color); @@ -389,6 +389,11 @@ PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictio return PDFSeparationColorSpace::createSeparationColorSpace(colorSpaceDictionary, document, array, recursion); } + if (name == COLOR_SPACE_NAME_DEVICE_N && count >= 4) + { + return PDFDeviceNColorSpace::createDeviceNColorSpace(colorSpaceDictionary, document, array, recursion); + } + // Try to just load by standard way - we can have "standard" color space stored in array return createColorSpaceImpl(colorSpaceDictionary, document, colorSpaceIdentifier, recursion); } @@ -985,6 +990,7 @@ QColor PDFPatternColorSpace::getDefaultColor() const QColor PDFPatternColorSpace::getColor(const PDFColor& color) const { + Q_UNUSED(color); throw PDFParserException(PDFTranslationContext::tr("Pattern doesn't have defined uniform color.")); } @@ -993,4 +999,183 @@ size_t PDFPatternColorSpace::getColorComponentCount() const return 0; } +PDFDeviceNColorSpace::PDFDeviceNColorSpace(PDFDeviceNColorSpace::Type type, + PDFDeviceNColorSpace::Colorants&& colorants, + PDFColorSpacePointer alternateColorSpace, + PDFColorSpacePointer processColorSpace, + PDFFunctionPtr tintTransform, + std::vector&& colorantsPrintingOrder, + std::vector processColorSpaceComponents) : + m_type(type), + m_colorants(qMove(colorants)), + m_alternateColorSpace(qMove(alternateColorSpace)), + m_processColorSpace(qMove(processColorSpace)), + m_tintTransform(qMove(tintTransform)), + m_colorantsPrintingOrder(qMove(colorantsPrintingOrder)), + m_processColorSpaceComponents(qMove(processColorSpaceComponents)) +{ + +} + +QColor PDFDeviceNColorSpace::getDefaultColor() const +{ + PDFColor color; + color.resize(getColorComponentCount()); + return getColor(color); +} + +QColor PDFDeviceNColorSpace::getColor(const PDFColor& color) const +{ + // Input values + std::vector inputColor(color.size(), 0.0); + for (size_t i = 0, count = inputColor.size(); i < count; ++i) + { + inputColor[i] = color[i]; + } + + // Output values + std::vector outputColor; + outputColor.resize(m_alternateColorSpace->getColorComponentCount(), 0.0); + PDFFunction::FunctionResult result = m_tintTransform->apply(inputColor.data(), inputColor.data() + inputColor.size(), outputColor.data(), outputColor.data() + outputColor.size()); + + if (result) + { + PDFColor color; + std::for_each(outputColor.cbegin(), outputColor.cend(), [&color](double value) { color.push_back(static_cast(value)); }); + return m_alternateColorSpace->getColor(color); + } + else + { + // Return invalid color + return QColor(); + } +} + +size_t PDFDeviceNColorSpace::getColorComponentCount() const +{ + return m_colorants.size(); +} + +PDFColorSpacePointer PDFDeviceNColorSpace::createDeviceNColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion) +{ + Q_ASSERT(array->getCount() >= 4); + + PDFDocumentDataLoaderDecorator loader(document); + std::vector colorantNames = loader.readNameArray(array->getItem(1)); + + if (colorantNames.empty()) + { + throw PDFParserException(PDFTranslationContext::tr("Invalid colorants for DeviceN color space.")); + } + + std::vector colorants; + colorants.resize(colorantNames.size()); + for (size_t i = 0; i < colorantNames.size(); ++i) + { + colorants[i].name = qMove(colorantNames[i]); + } + + // Read alternate color space + PDFColorSpacePointer alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(2)), recursion); + if (!alternateColorSpace) + { + throw PDFParserException(PDFTranslationContext::tr("Can't determine alternate color space for DeviceN color space.")); + } + + PDFFunctionPtr tintTransform = PDFFunction::createFunction(document, array->getItem(3)); + if (!tintTransform) + { + throw PDFParserException(PDFTranslationContext::tr("Can't determine tint transform for DeviceN color space.")); + } + + Type type = Type::DeviceN; + std::vector colorantsPrintingOrder; + PDFColorSpacePointer processColorSpace; + std::vector processColorSpaceComponents; + + // Now, check, if we have attributes, and if yes, then read them + if (array->getCount() == 5) + { + const PDFObject& object = document->getObject(array->getItem(4)); + if (object.isDictionary()) + { + const PDFDictionary* attributesDictionary = object.getDictionary(); + QByteArray subtype = loader.readNameFromDictionary(attributesDictionary, "Subtype"); + if (subtype == "NChannel") + { + type = Type::NChannel; + } + + const PDFObject& colorantsObject = document->getObject(attributesDictionary->get("Colorants")); + if (colorantsObject.isDictionary()) + { + const PDFDictionary* colorantsDictionary = colorantsObject.getDictionary(); + + // Separation color spaces for each colorant + for (ColorantInfo& colorantInfo : colorants) + { + if (colorantsDictionary->hasKey(colorantInfo.name)) + { + colorantInfo.separationColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorantsDictionary->get(colorantInfo.name)), recursion); + } + } + } + + const PDFObject& mixingHints = document->getObject(attributesDictionary->get("MixingHints")); + if (mixingHints.isDictionary()) + { + const PDFDictionary* mixingHintsDictionary = mixingHints.getDictionary(); + + // Printing order + colorantsPrintingOrder = loader.readNameArray(mixingHintsDictionary->get("PrintingOrder")); + + // Solidities + const PDFObject& solidityObject = document->getObject(mixingHintsDictionary->get("Solidites")); + if (solidityObject.isDictionary()) + { + const PDFDictionary* solidityDictionary = solidityObject.getDictionary(); + const PDFReal defaultSolidity = loader.readNumberFromDictionary(solidityDictionary, "Default", 0.0); + for (ColorantInfo& colorantInfo : colorants) + { + colorantInfo.solidity = loader.readNumberFromDictionary(solidityDictionary, colorantInfo.name, defaultSolidity); + } + } + + // Dot gain + const PDFObject& dotGainObject = document->getObject(mixingHintsDictionary->get("DotGain")); + if (dotGainObject.isDictionary()) + { + const PDFDictionary* dotGainDictionary = dotGainObject.getDictionary(); + for (ColorantInfo& colorantInfo : colorants) + { + const PDFObject& dotGainFunctionObject = document->getObject(dotGainDictionary->get(colorantInfo.name)); + if (!dotGainFunctionObject.isNull()) + { + colorantInfo.dotGain = PDFFunction::createFunction(document, dotGainFunctionObject); + } + } + } + } + + // Process + const PDFObject& processObject = document->getObject(attributesDictionary->get("Process")); + if (processObject.isDictionary()) + { + const PDFDictionary* processDictionary = processObject.getDictionary(); + const PDFObject& processColorSpaceObject = document->getObject(processDictionary->get("ColorSpace")); + if (!processColorSpaceObject.isNull()) + { + processColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, processColorSpaceObject, recursion); + processColorSpaceComponents = loader.readNameArrayFromDictionary(processDictionary, "Components"); + } + } + } + } + + return PDFColorSpacePointer(new PDFDeviceNColorSpace(type, qMove(colorants), qMove(alternateColorSpace), qMove(processColorSpace), qMove(tintTransform), qMove(colorantsPrintingOrder), qMove(processColorSpaceComponents))); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfcolorspaces.h b/PdfForQtLib/sources/pdfcolorspaces.h index 1c7ad85..bb4b7ba 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.h +++ b/PdfForQtLib/sources/pdfcolorspaces.h @@ -61,6 +61,7 @@ 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* COLOR_SPACE_NAME_SEPARATION = "Separation"; +static constexpr const char* COLOR_SPACE_NAME_DEVICE_N = "DeviceN"; static constexpr const char* COLOR_SPACE_NAME_PATTERN = "Pattern"; static constexpr const char* CAL_WHITE_POINT = "WhitePoint"; @@ -535,6 +536,59 @@ private: PDFFunctionPtr m_tintTransform; }; +class PDFDeviceNColorSpace : public PDFAbstractColorSpace +{ +public: + + enum class Type + { + DeviceN, + NChannel + }; + + struct ColorantInfo + { + QByteArray name; + PDFColorSpacePointer separationColorSpace; + PDFReal solidity = 0.0; + PDFFunctionPtr dotGain; + }; + + using Colorants = std::vector; + + explicit PDFDeviceNColorSpace(Type type, + Colorants&& colorants, + PDFColorSpacePointer alternateColorSpace, + PDFColorSpacePointer processColorSpace, + PDFFunctionPtr tintTransform, + std::vector&& colorantsPrintingOrder, + std::vector processColorSpaceComponents); + virtual ~PDFDeviceNColorSpace() = default; + + virtual QColor getDefaultColor() const override; + virtual QColor getColor(const PDFColor& color) const override; + virtual size_t getColorComponentCount() const override; + + /// Returns type of DeviceN color space + Type getType() const { return m_type; } + + /// Creates DeviceN color space from provided values. + /// \param colorSpaceDictionary Color space dictionary + /// \param document Document + /// \param array Array with DeviceN color space definition + /// \param recursion Recursion guard + static PDFColorSpacePointer createDeviceNColorSpace(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFArray* array, int recursion); + +private: + Type m_type; + Colorants m_colorants; + PDFColorSpacePointer m_alternateColorSpace; + PDFColorSpacePointer m_processColorSpace; + PDFFunctionPtr m_tintTransform; + std::vector m_colorantsPrintingOrder; + std::vector m_processColorSpaceComponents; +}; + class PDFPatternColorSpace : public PDFAbstractColorSpace { public: @@ -551,8 +605,6 @@ private: std::shared_ptr m_pattern; }; -// TODO: Implement DeviceN color space - } // namespace pdf #endif // PDFCOLORSPACES_H diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index ec9a676..17476be 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -342,6 +342,16 @@ PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictio return defaultValue; } +PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const QByteArray& 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)) diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index 1ebddba..05ab365 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -214,6 +214,12 @@ public: /// \param defaultValue Default value PDFReal readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const; + /// 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 QByteArray& 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 diff --git a/PdfForQtLib/sources/pdffunction.cpp b/PdfForQtLib/sources/pdffunction.cpp index 3921625..1fae8b1 100644 --- a/PdfForQtLib/sources/pdffunction.cpp +++ b/PdfForQtLib/sources/pdffunction.cpp @@ -598,28 +598,12 @@ PDFFunction::FunctionResult PDFStitchingFunction::apply(const_iterator x_1, // First search for partial function, which defines our range. Use algorithm // similar to the std::lower_bound. - size_t count = m_partialFunctions.size(); - size_t functionIndex = 0; - while (count > 0) + auto it = std::lower_bound(m_partialFunctions.cbegin(), m_partialFunctions.cend(), x, [](const auto& partialFunction, PDFReal value) { return partialFunction.bound1 < value; }); + if (it == m_partialFunctions.cend()) { - const size_t step = count / 2; - const size_t current = functionIndex + step; - - if (m_partialFunctions[current].bound1 < x) - { - functionIndex = current + 1; - count = count - functionIndex; - } - else - { - count = current; - } + --it; } - if (functionIndex == m_partialFunctions.size()) - { - --functionIndex; - } - const PartialFunction& function = m_partialFunctions[functionIndex]; + const PartialFunction& function = *it; // Encode the value into the input range of the function const PDFReal xEncoded = interpolate(x, function.bound0, function.bound1, function.encode0, function.encode1);