mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	DeviceN color space
This commit is contained in:
		@@ -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<int>(getColorComponentCount()), static_cast<int>(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<QByteArray>&& colorantsPrintingOrder,
 | 
			
		||||
                                           std::vector<QByteArray> 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<double> inputColor(color.size(), 0.0);
 | 
			
		||||
    for (size_t i = 0, count = inputColor.size(); i < count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        inputColor[i] = color[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Output values
 | 
			
		||||
    std::vector<double> 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<float>(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<QByteArray> colorantNames = loader.readNameArray(array->getItem(1));
 | 
			
		||||
 | 
			
		||||
    if (colorantNames.empty())
 | 
			
		||||
    {
 | 
			
		||||
        throw PDFParserException(PDFTranslationContext::tr("Invalid colorants for DeviceN color space."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<ColorantInfo> 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<QByteArray> colorantsPrintingOrder;
 | 
			
		||||
    PDFColorSpacePointer processColorSpace;
 | 
			
		||||
    std::vector<QByteArray> 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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ColorantInfo>;
 | 
			
		||||
 | 
			
		||||
    explicit PDFDeviceNColorSpace(Type type,
 | 
			
		||||
                                  Colorants&& colorants,
 | 
			
		||||
                                  PDFColorSpacePointer alternateColorSpace,
 | 
			
		||||
                                  PDFColorSpacePointer processColorSpace,
 | 
			
		||||
                                  PDFFunctionPtr tintTransform,
 | 
			
		||||
                                  std::vector<QByteArray>&& colorantsPrintingOrder,
 | 
			
		||||
                                  std::vector<QByteArray> 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<QByteArray> m_colorantsPrintingOrder;
 | 
			
		||||
    std::vector<QByteArray> m_processColorSpaceComponents;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PDFPatternColorSpace : public PDFAbstractColorSpace
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
@@ -551,8 +605,6 @@ private:
 | 
			
		||||
     std::shared_ptr<PDFPattern> m_pattern;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: Implement DeviceN color space
 | 
			
		||||
 | 
			
		||||
}   // namespace pdf
 | 
			
		||||
 | 
			
		||||
#endif // PDFCOLORSPACES_H
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user