PDF4QT/PdfForQtLib/sources/pdfcms.cpp

1188 lines
45 KiB
C++
Raw Normal View History

2020-01-18 11:38:54 +01:00
// Copyright (C) 2019-2020 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 <https://www.gnu.org/licenses/>.
#include "pdfcms.h"
2019-12-25 14:34:37 +01:00
#include <QApplication>
#include <QReadWriteLock>
2019-12-25 14:34:37 +01:00
#pragma warning(push)
#pragma warning(disable:5033)
#include <lcms2.h>
#pragma warning(pop)
#include <unordered_map>
namespace pdf
{
class PDFLittleCMS : public PDFCMS
{
public:
explicit PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings);
virtual ~PDFLittleCMS() override;
virtual bool isCompatible(const PDFCMSSettings& settings) const override;
virtual QColor getPaperColor() const override;
virtual QColor getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override;
virtual QColor getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override;
virtual QColor getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override;
virtual QColor getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override;
2019-12-26 17:57:45 +01:00
virtual QColor getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override;
2019-12-30 16:08:48 +01:00
virtual bool fillRGBBufferFromDeviceGray(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override;
virtual bool fillRGBBufferFromDeviceRGB(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override;
virtual bool fillRGBBufferFromDeviceCMYK(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override;
virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override;
virtual bool fillRGBBufferFromICC(const std::vector<float>& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override;
private:
void init();
enum Profile
{
Output,
Gray,
RGB,
CMYK,
XYZ,
ProfileCount
};
/// Creates a profile using provided id and a list of profile descriptors.
/// If profile can't be created, then null handle is returned.
/// \param id Id of color profile
/// \param profileDescriptors Profile descriptor list
cmsHPROFILE createProfile(const QString& id, const PDFColorProfileIdentifiers& profileDescriptors) const;
/// Gets transform from cache. If transform doesn't exist, then it is created.
/// \param profile Color profile
/// \param intent Rendering intent
2019-12-30 16:08:48 +01:00
/// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used
cmsHTRANSFORM getTransform(Profile profile, RenderingIntent intent, bool isRGB888Buffer) const;
/// Gets transform for ICC profile from cache. If transform doesn't exist, then it is created.
/// \param iccData Data of icc profile
/// \param iccID Icc profile id
/// \param renderingIntent Rendering intent
/// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used
cmsHTRANSFORM getTransformFromICCProfile(const QByteArray& iccData, const QByteArray& iccID, RenderingIntent renderingIntent, bool isRGB888Buffer) const;
/// Returns transformation flags accordint to the current settings
cmsUInt32Number getTransformationFlags() const;
/// Calculates effective rendering intent. If rendering intent is auto,
/// then \p intent is used, otherwise intent is overriden.
RenderingIntent getEffectiveRenderingIntent(RenderingIntent intent) const;
/// Gets transform from cache key.
/// \param profile Color profile
/// \param intent Rendering intent
2019-12-30 16:08:48 +01:00
/// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used
static constexpr int getCacheKey(Profile profile, RenderingIntent intent, bool isRGB888Buffer) { return ((int(intent) * ProfileCount + profile) << 1) + (isRGB888Buffer ? 1 : 0); }
/// Returns little CMS rendering intent
/// \param intent Rendering intent
static cmsUInt32Number getLittleCMSRenderingIntent(RenderingIntent intent);
/// Returns little CMS data format for profile
/// \param profile Color profile handle
static cmsUInt32Number getProfileDataFormat(cmsHPROFILE profile);
/// Returns color from output color. Clamps invalid rgb output values to range [0.0, 1.0].
/// \param color01 Rgb color (range 0-1 is assumed).
static QColor getColorFromOutputColor(std::array<float, 3> color01);
const PDFCMSManager* m_manager;
PDFCMSSettings m_settings;
QColor m_paperColor;
std::array<cmsHPROFILE, ProfileCount> m_profiles;
mutable QReadWriteLock m_transformationCacheLock;
mutable std::unordered_map<int, cmsHTRANSFORM> m_transformationCache;
2019-12-26 17:57:45 +01:00
mutable QReadWriteLock m_customIccProfileCacheLock;
mutable std::map<std::pair<QByteArray, RenderingIntent>, cmsHTRANSFORM> m_customIccProfileCache;
};
2019-12-30 16:08:48 +01:00
bool PDFLittleCMS::fillRGBBufferFromDeviceGray(const std::vector<float>& colors,
RenderingIntent intent,
unsigned char* outputBuffer,
PDFRenderErrorReporter* reporter) const
{
cmsHTRANSFORM transform = getTransform(Gray, getEffectiveRenderingIntent(intent), true);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed."));
return false;
}
if (cmsGetTransformInputFormat(transform) == TYPE_GRAY_FLT)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8);
cmsDoTransform(transform, colors.data(), outputBuffer, static_cast<cmsUInt32Number>(colors.size()));
return true;
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed - invalid data format."));
}
return false;
}
bool PDFLittleCMS::fillRGBBufferFromDeviceRGB(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const
{
cmsHTRANSFORM transform = getTransform(RGB, getEffectiveRenderingIntent(intent), true);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed."));
return false;
}
const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform);
if (inputFormat == TYPE_RGB_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8);
cmsDoTransform(transform, colors.data(), outputBuffer, static_cast<cmsUInt32Number>(colors.size()) / T_CHANNELS(inputFormat));
return true;
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed - invalid data format."));
}
return false;
}
bool PDFLittleCMS::fillRGBBufferFromDeviceCMYK(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const
{
cmsHTRANSFORM transform = getTransform(CMYK, getEffectiveRenderingIntent(intent), true);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed."));
return false;
}
const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform);
if (inputFormat == TYPE_CMYK_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8);
std::vector<float> fixedColors = colors;
for (size_t i = 0, count = fixedColors.size(); i < count; ++i)
{
fixedColors[i] = fixedColors[i] * 100.0f;
}
cmsDoTransform(transform, fixedColors.data(), outputBuffer, static_cast<cmsUInt32Number>(colors.size()) / T_CHANNELS(inputFormat));
return true;
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed - invalid data format."));
}
return false;
}
bool PDFLittleCMS::fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const
{
cmsHTRANSFORM transform = getTransform(XYZ, getEffectiveRenderingIntent(intent), true);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed."));
return false;
}
const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform);
if (inputFormat == TYPE_XYZ_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8);
const cmsCIEXYZ* d50WhitePoint = cmsD50_XYZ();
std::array<float, 3> correctionCoefficients = { float(d50WhitePoint->X) / whitePoint[0], float(d50WhitePoint->Y) / whitePoint[1], float(d50WhitePoint->Z) / whitePoint[2] };
std::vector<float> fixedColors = colors;
for (size_t i = 0, count = fixedColors.size(); i < count; ++i)
{
fixedColors[i] = fixedColors[i] * correctionCoefficients[i % correctionCoefficients.size()];
}
cmsDoTransform(transform, fixedColors.data(), outputBuffer, static_cast<cmsUInt32Number>(colors.size()) / T_CHANNELS(inputFormat));
return true;
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed - invalid data format."));
}
return false;
}
bool PDFLittleCMS::fillRGBBufferFromICC(const std::vector<float>& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const
{
cmsHTRANSFORM transform = getTransformFromICCProfile(iccData, iccID, renderingIntent, true);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed."));
return false;
}
const cmsUInt32Number format = cmsGetTransformInputFormat(transform);
const cmsUInt32Number channels = T_CHANNELS(format);
const cmsUInt32Number colorSpace = T_COLORSPACE(format);
const bool isCMYK = colorSpace == PT_CMYK;
const float* inputColors = colors.data();
std::vector<float> cmykColors;
if (isCMYK)
{
cmykColors = colors;
for (size_t i = 0; i < cmykColors.size(); ++i)
{
cmykColors[i] = cmykColors[i] * 100.0;
}
inputColors = cmykColors.data();
}
if ((colors.size()) % T_CHANNELS(format) == 0)
{
const cmsUInt32Number pixels = static_cast<cmsUInt32Number>(colors.size()) / channels;
cmsDoTransform(transform, inputColors, outputBuffer, pixels);
return true;
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed - invalid data format."));
}
return false;
}
PDFLittleCMS::PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings) :
m_manager(manager),
m_settings(settings),
m_paperColor(Qt::white),
m_profiles()
{
init();
}
PDFLittleCMS::~PDFLittleCMS()
{
for (const auto& transformItem : m_transformationCache)
{
cmsHTRANSFORM transform = transformItem.second;
if (transform)
{
cmsDeleteTransform(transform);
}
}
2019-12-26 17:57:45 +01:00
for (const auto& transformItem : m_customIccProfileCache)
{
cmsHTRANSFORM transform = transformItem.second;
if (transform)
{
cmsDeleteTransform(transform);
}
}
for (cmsHPROFILE profile : m_profiles)
{
if (profile)
{
cmsCloseProfile(profile);
}
}
}
bool PDFLittleCMS::isCompatible(const PDFCMSSettings& settings) const
{
return m_settings == settings;
}
QColor PDFLittleCMS::getPaperColor() const
{
return m_paperColor;
}
QColor PDFLittleCMS::getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
2019-12-30 16:08:48 +01:00
cmsHTRANSFORM transform = getTransform(Gray, getEffectiveRenderingIntent(intent), false);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed."));
return QColor();
}
if (cmsGetTransformInputFormat(transform) == TYPE_GRAY_FLT && color.size() == 1)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT);
const float grayColor = color[0];
std::array<float, 3> rgbOutputColor = { };
cmsDoTransform(transform, &grayColor, rgbOutputColor.data(), 1);
return getColorFromOutputColor(rgbOutputColor);
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed - invalid data format."));
}
return QColor();
}
QColor PDFLittleCMS::getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
2019-12-30 16:08:48 +01:00
cmsHTRANSFORM transform = getTransform(RGB, getEffectiveRenderingIntent(intent), false);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed."));
return QColor();
}
if (cmsGetTransformInputFormat(transform) == TYPE_RGB_FLT && color.size() == 3)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT);
std::array<float, 3> rgbInputColor = { color[0], color[1], color[2] };
std::array<float, 3> rgbOutputColor = { };
cmsDoTransform(transform, rgbInputColor.data(), rgbOutputColor.data(), 1);
return getColorFromOutputColor(rgbOutputColor);
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed - invalid data format."));
}
return QColor();
}
QColor PDFLittleCMS::getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
2019-12-30 16:08:48 +01:00
cmsHTRANSFORM transform = getTransform(CMYK, getEffectiveRenderingIntent(intent), false);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed."));
return QColor();
}
if (cmsGetTransformInputFormat(transform) == TYPE_CMYK_FLT && color.size() == 4)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT);
std::array<float, 4> cmykInputColor = { color[0] * 100.0f, color[1] * 100.0f, color[2] * 100.0f, color[3] * 100.0f };
std::array<float, 3> rgbOutputColor = { };
cmsDoTransform(transform, cmykInputColor.data(), rgbOutputColor.data(), 1);
return getColorFromOutputColor(rgbOutputColor);
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed - invalid data format."));
}
return QColor();
}
QColor PDFLittleCMS::getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
2019-12-30 16:08:48 +01:00
cmsHTRANSFORM transform = getTransform(XYZ, getEffectiveRenderingIntent(intent), false);
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed."));
return QColor();
}
if (cmsGetTransformInputFormat(transform) == TYPE_XYZ_FLT && color.size() == 3)
{
Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT);
const cmsCIEXYZ* d50WhitePoint = cmsD50_XYZ();
std::array<float, 3> xyzInputColor = { color[0] / whitePoint[0] * float(d50WhitePoint->X), color[1] / whitePoint[1] * float(d50WhitePoint->Y), color[2] / whitePoint[2] * float(d50WhitePoint->Z) };
std::array<float, 3> rgbOutputColor = { };
cmsDoTransform(transform, xyzInputColor.data(), rgbOutputColor.data(), 1);
return getColorFromOutputColor(rgbOutputColor);
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed - invalid data format."));
}
return QColor();
}
2019-12-30 16:08:48 +01:00
cmsHTRANSFORM PDFLittleCMS::getTransformFromICCProfile(const QByteArray& iccData, const QByteArray& iccID, RenderingIntent renderingIntent, bool isRGB888Buffer) const
{
2019-12-30 16:08:48 +01:00
RenderingIntent effectiveRenderingIntent = getEffectiveRenderingIntent(renderingIntent);
const auto key = std::make_pair(iccID + (isRGB888Buffer ? "RGB_888" : "FLT"), effectiveRenderingIntent);
QReadLocker lock(&m_customIccProfileCacheLock);
auto it = m_customIccProfileCache.find(key);
if (it == m_customIccProfileCache.cend())
2019-12-26 17:57:45 +01:00
{
2019-12-30 16:08:48 +01:00
lock.unlock();
QWriteLocker writeLock(&m_customIccProfileCacheLock);
// Now, we have locked cache for writing. We must find out,
// if some other thread doesn't created the transformation already.
it = m_customIccProfileCache.find(key);
2019-12-26 17:57:45 +01:00
if (it == m_customIccProfileCache.cend())
{
2019-12-30 16:08:48 +01:00
cmsHTRANSFORM transform = cmsHTRANSFORM();
cmsHPROFILE profile = cmsOpenProfileFromMem(iccData.data(), iccData.size());
if (profile)
2019-12-26 17:57:45 +01:00
{
2019-12-30 16:08:48 +01:00
if (const cmsUInt32Number inputDataFormat = getProfileDataFormat(profile))
2019-12-26 17:57:45 +01:00
{
2019-12-30 16:08:48 +01:00
cmsUInt32Number lcmsIntent = getLittleCMSRenderingIntent(effectiveRenderingIntent);
transform = cmsCreateTransform(profile, inputDataFormat, m_profiles[Output], isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, lcmsIntent, getTransformationFlags());
2019-12-26 17:57:45 +01:00
}
2019-12-30 16:08:48 +01:00
cmsCloseProfile(profile);
2019-12-26 17:57:45 +01:00
}
2019-12-30 16:08:48 +01:00
it = m_customIccProfileCache.insert(std::make_pair(key, transform)).first;
2019-12-26 17:57:45 +01:00
}
2019-12-30 16:08:48 +01:00
return it->second;
2019-12-26 17:57:45 +01:00
}
2019-12-30 16:08:48 +01:00
else
{
return it->second;
}
return cmsHTRANSFORM();
}
QColor PDFLittleCMS::getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const
{
cmsHTRANSFORM transform = getTransformFromICCProfile(iccData, iccID, renderingIntent, false);
2019-12-26 17:57:45 +01:00
if (!transform)
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed."));
return QColor();
}
std::array<float, 4> inputBuffer = { };
const cmsUInt32Number format = cmsGetTransformInputFormat(transform);
const cmsUInt32Number channels = T_CHANNELS(format);
const cmsUInt32Number colorSpace = T_COLORSPACE(format);
const bool isCMYK = colorSpace == PT_CMYK;
2019-12-26 17:57:45 +01:00
if (channels == color.size() && channels <= inputBuffer.size())
{
for (size_t i = 0; i < color.size(); ++i)
{
inputBuffer[i] = isCMYK ? color[i] * 100.0 : color[i];
2019-12-26 17:57:45 +01:00
}
std::array<float, 3> rgbOutputColor = { };
cmsDoTransform(transform, inputBuffer.data(), rgbOutputColor.data(), 1);
return getColorFromOutputColor(rgbOutputColor);
}
else
{
reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed - invalid data format."));
}
return QColor();
}
void PDFLittleCMS::init()
{
// Jakub Melka: initialize all color profiles
m_profiles[Output] = createProfile(m_settings.outputCS, m_manager->getOutputProfiles());
m_profiles[Gray] = createProfile(m_settings.deviceGray, m_manager->getGrayProfiles());
m_profiles[RGB] = createProfile(m_settings.deviceRGB, m_manager->getRGBProfiles());
m_profiles[CMYK] = createProfile(m_settings.deviceCMYK, m_manager->getCMYKProfiles());
m_profiles[XYZ] = cmsCreateXYZProfile();
if (m_settings.isWhitePaperColorTransformed)
{
m_paperColor = getColorFromDeviceRGB(PDFColor(1.0f, 1.0f, 1.0f), RenderingIntent::AbsoluteColorimetric, nullptr);
// We must check color of the paper, it can be invalid, if error occurs...
if (!m_paperColor.isValid())
{
m_paperColor = QColor(Qt::white);
}
}
// 64 should be enough, because we can have 4 input color spaces (gray, RGB, CMYK and XYZ),
// and 4 rendering intents. We have 4 * 4 = 16 input tables, so 64 will suffice enough
// (because we then have 25% load factor).
m_transformationCache.reserve(64);
}
cmsHPROFILE PDFLittleCMS::createProfile(const QString& id, const PDFColorProfileIdentifiers& profileDescriptors) const
{
auto it = std::find_if(profileDescriptors.cbegin(), profileDescriptors.cend(), [&id](const PDFColorProfileIdentifier& identifier) { return identifier.id == id; });
if (it != profileDescriptors.cend())
{
const PDFColorProfileIdentifier& identifier = *it;
switch (identifier.type)
{
case PDFColorProfileIdentifier::Type::Gray:
{
cmsCIExyY whitePoint{ };
if (cmsWhitePointFromTemp(&whitePoint, identifier.temperature))
{
cmsToneCurve* gammaCurve = cmsBuildGamma(cmsContext(), identifier.gamma);
cmsHPROFILE profile = cmsCreateGrayProfile(&whitePoint, gammaCurve);
cmsFreeToneCurve(gammaCurve);
return profile;
}
break;
}
case PDFColorProfileIdentifier::Type::sRGB:
return cmsCreate_sRGBProfile();
case PDFColorProfileIdentifier::Type::RGB:
{
cmsCIExyY whitePoint{ };
if (cmsWhitePointFromTemp(&whitePoint, identifier.temperature))
{
cmsCIExyYTRIPLE primaries;
primaries.Red = { identifier.primaryR.x(), identifier.primaryR.y(), 1.0 };
primaries.Green = { identifier.primaryG.x(), identifier.primaryG.y(), 1.0 };
primaries.Blue = { identifier.primaryB.x(), identifier.primaryB.y(), 1.0 };
cmsToneCurve* gammaCurve = cmsBuildGamma(cmsContext(), identifier.gamma);
cmsToneCurve* toneCurves[3] = { gammaCurve, cmsDupToneCurve(gammaCurve), cmsDupToneCurve(gammaCurve) };
cmsHPROFILE profile = cmsCreateRGBProfile(&whitePoint, &primaries, toneCurves);
cmsFreeToneCurveTriple(toneCurves);
return profile;
}
break;
}
case PDFColorProfileIdentifier::Type::FileGray:
case PDFColorProfileIdentifier::Type::FileRGB:
case PDFColorProfileIdentifier::Type::FileCMYK:
{
QFile file(identifier.id);
if (file.open(QFile::ReadOnly))
{
QByteArray fileContent = file.readAll();
file.close();
return cmsOpenProfileFromMem(fileContent.data(), fileContent.size());
}
break;
}
default:
Q_ASSERT(false);
break;
}
}
return cmsHPROFILE();
}
2019-12-30 16:08:48 +01:00
cmsHTRANSFORM PDFLittleCMS::getTransform(Profile profile, RenderingIntent intent, bool isRGB888Buffer) const
{
2019-12-30 16:08:48 +01:00
const int key = getCacheKey(profile, intent, isRGB888Buffer);
QReadLocker lock(&m_transformationCacheLock);
auto it = m_transformationCache.find(key);
if (it == m_transformationCache.cend())
{
lock.unlock();
QWriteLocker writeLock(&m_transformationCacheLock);
// Now, we have locked cache for writing. We must find out,
// if some other thread doesn't created the transformation already.
it = m_transformationCache.find(key);
if (it == m_transformationCache.cend())
{
cmsHTRANSFORM transform = cmsHTRANSFORM();
cmsHPROFILE input = m_profiles[profile];
cmsHPROFILE output = m_profiles[Output];
if (input && output)
{
2019-12-30 16:08:48 +01:00
transform = cmsCreateTransform(input, getProfileDataFormat(input), output, isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, getLittleCMSRenderingIntent(intent), getTransformationFlags());
}
it = m_transformationCache.insert(std::make_pair(key, transform)).first;
}
// We must return it here to avoid race condition (after current block,
// lock is not locked, because we unlocked lock for reading).
return it->second;
}
return it->second;
}
cmsUInt32Number PDFLittleCMS::getTransformationFlags() const
{
cmsUInt32Number flags = cmsFLAGS_NOCACHE;
if (m_settings.isBlackPointCompensationActive)
{
flags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
}
switch (m_settings.accuracy)
{
case PDFCMSSettings::Accuracy::Low:
flags |= cmsFLAGS_LOWRESPRECALC;
break;
case PDFCMSSettings::Accuracy::Medium:
break;
case PDFCMSSettings::Accuracy::High:
flags |= cmsFLAGS_HIGHRESPRECALC;
break;
default:
Q_ASSERT(false);
break;
}
return flags;
}
RenderingIntent PDFLittleCMS::getEffectiveRenderingIntent(RenderingIntent intent) const
{
if (m_settings.intent != RenderingIntent::Auto)
{
return m_settings.intent;
}
return intent;
}
cmsUInt32Number PDFLittleCMS::getLittleCMSRenderingIntent(RenderingIntent intent)
{
switch (intent)
{
case RenderingIntent::Perceptual:
return INTENT_PERCEPTUAL;
case RenderingIntent::AbsoluteColorimetric:
return INTENT_ABSOLUTE_COLORIMETRIC;
case RenderingIntent::RelativeColorimetric:
return INTENT_RELATIVE_COLORIMETRIC;
case RenderingIntent::Saturation:
return INTENT_SATURATION;
default:
Q_ASSERT(false);
break;
}
return INTENT_PERCEPTUAL;
}
cmsUInt32Number PDFLittleCMS::getProfileDataFormat(cmsHPROFILE profile)
{
cmsColorSpaceSignature signature = cmsGetColorSpace(profile);
switch (signature)
{
case cmsSigGrayData:
return TYPE_GRAY_FLT;
case cmsSigRgbData:
return TYPE_RGB_FLT;
case cmsSigCmykData:
return TYPE_CMYK_FLT;
case cmsSigXYZData:
return TYPE_XYZ_FLT;
default:
break;
}
return 0;
}
QColor PDFLittleCMS::getColorFromOutputColor(std::array<float, 3> color01)
{
QColor color(QColor::Rgb);
color.setRgbF(qBound(0.0f, color01[0], 1.0f), qBound(0.0f, color01[1], 1.0f), qBound(0.0f, color01[2], 1.0f));
return color;
}
2019-12-25 14:34:37 +01:00
QString getInfoFromProfile(cmsHPROFILE profile, cmsInfoType infoType)
{
QLocale locale;
QString country = QLocale::countryToString(locale.country());
QString language = QLocale::languageToString(locale.language());
char countryCode[3] = { };
char languageCode[3] = { };
if (country.size() == 2)
{
countryCode[0] = country[0].toLatin1();
countryCode[1] = country[1].toLatin1();
}
if (language.size() == 2)
{
languageCode[0] = language[0].toLatin1();
languageCode[1] = language[1].toLatin1();
}
// Jakub Melka: try to get profile info from current language/country.
// If it fails, then pick any language/any country.
cmsUInt32Number bufferSize = cmsGetProfileInfo(profile, infoType, languageCode, countryCode, nullptr, 0);
if (bufferSize)
{
std::vector<wchar_t> buffer(bufferSize, 0);
cmsGetProfileInfo(profile, infoType, languageCode, countryCode, buffer.data(), static_cast<cmsUInt32Number>(buffer.size()));
return QString::fromWCharArray(buffer.data());
}
bufferSize = cmsGetProfileInfo(profile, infoType, cmsNoLanguage, cmsNoCountry, nullptr, 0);
if (bufferSize)
{
std::vector<wchar_t> buffer(bufferSize, 0);
cmsGetProfileInfo(profile, infoType, cmsNoLanguage, cmsNoCountry, buffer.data(), static_cast<cmsUInt32Number>(buffer.size()));
return QString::fromWCharArray(buffer.data());
}
return QString();
}
bool PDFCMSGeneric::isCompatible(const PDFCMSSettings& settings) const
{
return settings.system == PDFCMSSettings::System::Generic;
}
2019-12-25 17:56:17 +01:00
QColor PDFCMSGeneric::getPaperColor() const
{
return QColor(Qt::white);
}
QColor PDFCMSGeneric::getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(color);
Q_UNUSED(intent);
Q_UNUSED(reporter);
return QColor();
}
QColor PDFCMSGeneric::getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(color);
Q_UNUSED(intent);
Q_UNUSED(reporter);
return QColor();
}
QColor PDFCMSGeneric::getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(color);
Q_UNUSED(intent);
Q_UNUSED(reporter);
return QColor();
}
2019-12-25 17:56:17 +01:00
QColor PDFCMSGeneric::getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(color);
Q_UNUSED(intent);
Q_UNUSED(reporter);
Q_UNUSED(whitePoint);
return QColor();
}
2019-12-26 17:57:45 +01:00
QColor PDFCMSGeneric::getColorFromICC(const PDFColor& color,
RenderingIntent renderingIntent,
const QByteArray& iccID,
const QByteArray& iccData,
PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(color);
2019-12-26 17:57:45 +01:00
Q_UNUSED(renderingIntent);
Q_UNUSED(iccID);
Q_UNUSED(iccData);
2019-12-26 17:57:45 +01:00
Q_UNUSED(reporter);
return QColor();
}
2019-12-30 16:08:48 +01:00
bool PDFCMSGeneric::fillRGBBufferFromDeviceGray(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(colors);
Q_UNUSED(intent);
Q_UNUSED(outputBuffer);
Q_UNUSED(reporter);
return false;
}
bool PDFCMSGeneric::fillRGBBufferFromDeviceRGB(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(colors);
Q_UNUSED(intent);
Q_UNUSED(outputBuffer);
Q_UNUSED(reporter);
return false;
}
bool PDFCMSGeneric::fillRGBBufferFromDeviceCMYK(const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(colors);
Q_UNUSED(intent);
Q_UNUSED(outputBuffer);
Q_UNUSED(reporter);
return false;
}
bool PDFCMSGeneric::fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(whitePoint);
Q_UNUSED(colors);
Q_UNUSED(intent);
Q_UNUSED(outputBuffer);
Q_UNUSED(reporter);
return false;
}
bool PDFCMSGeneric::fillRGBBufferFromICC(const std::vector<float>& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const
{
Q_UNUSED(colors);
Q_UNUSED(renderingIntent);
Q_UNUSED(outputBuffer);
Q_UNUSED(iccID);
Q_UNUSED(iccData);
Q_UNUSED(reporter);
return false;
}
PDFCMSManager::PDFCMSManager(QObject* parent) :
BaseClass(parent),
m_mutex(QMutex::Recursive)
{
}
2019-12-25 17:56:17 +01:00
PDFCMSPointer PDFCMSManager::getCurrentCMS() const
{
QMutexLocker lock(&m_mutex);
return m_CMS.get(this, &PDFCMSManager::getCurrentCMSImpl);
}
2019-12-25 14:34:37 +01:00
void PDFCMSManager::setSettings(const PDFCMSSettings& settings)
{
if (m_settings != settings)
{
// We must ensure, that mutex is not locked, while we are
// sending signal about CMS change.
{
QMutexLocker lock(&m_mutex);
m_settings = settings;
2019-12-25 17:56:17 +01:00
m_CMS.dirty();
2019-12-25 14:34:37 +01:00
m_outputProfiles.dirty();
m_grayProfiles.dirty();
m_RGBProfiles.dirty();
m_CMYKProfiles.dirty();
m_externalProfiles.dirty();
}
emit colorManagementSystemChanged();
}
}
const PDFColorProfileIdentifiers& PDFCMSManager::getOutputProfiles() const
{
QMutexLocker lock(&m_mutex);
return m_outputProfiles.get(this, &PDFCMSManager::getOutputProfilesImpl);
}
2019-12-25 14:34:37 +01:00
const PDFColorProfileIdentifiers& PDFCMSManager::getGrayProfiles() const
{
QMutexLocker lock(&m_mutex);
return m_grayProfiles.get(this, &PDFCMSManager::getGrayProfilesImpl);
}
2019-12-25 14:34:37 +01:00
const PDFColorProfileIdentifiers& PDFCMSManager::getRGBProfiles() const
{
QMutexLocker lock(&m_mutex);
return m_RGBProfiles.get(this, &PDFCMSManager::getRGBProfilesImpl);
}
2019-12-25 14:34:37 +01:00
const PDFColorProfileIdentifiers& PDFCMSManager::getCMYKProfiles() const
{
QMutexLocker lock(&m_mutex);
return m_CMYKProfiles.get(this, &PDFCMSManager::getCMYKProfilesImpl);
}
2019-12-25 14:34:37 +01:00
const PDFColorProfileIdentifiers& PDFCMSManager::getExternalProfiles() const
{
// Jakub Melka: do not protect this by mutex, this function is private
// and must be called only from mutex-protected code.
return m_externalProfiles.get(this, &PDFCMSManager::getExternalProfilesImpl);
}
PDFCMSSettings PDFCMSManager::getDefaultSettings() const
{
PDFCMSSettings settings;
2019-12-25 14:34:37 +01:00
auto getFirstProfileId = [](const PDFColorProfileIdentifiers& identifiers)
{
if (!identifiers.empty())
{
return identifiers.front().id;
}
return QString();
};
settings.system = PDFCMSSettings::System::LittleCMS2;
settings.outputCS = getFirstProfileId(getOutputProfiles());
settings.deviceGray = getFirstProfileId(getGrayProfiles());
settings.deviceRGB = getFirstProfileId(getRGBProfiles());
settings.deviceCMYK = getFirstProfileId(getCMYKProfiles());
return settings;
}
QString PDFCMSManager::getSystemName(PDFCMSSettings::System system)
{
switch (system)
{
case PDFCMSSettings::System::Generic:
return tr("Generic");
case PDFCMSSettings::System::LittleCMS2:
{
const int major = LCMS_VERSION / 1000;
const int minor = (LCMS_VERSION % 1000) / 10;
return tr("Little CMS %1.%2").arg(major).arg(minor);
}
default:
{
Q_ASSERT(false);
break;
}
}
return QString();
}
2019-12-25 17:56:17 +01:00
PDFCMSPointer PDFCMSManager::getCurrentCMSImpl() const
{
switch (m_settings.system)
{
case PDFCMSSettings::System::Generic:
return PDFCMSPointer(new PDFCMSGeneric());
case PDFCMSSettings::System::LittleCMS2:
return PDFCMSPointer(new PDFLittleCMS(this, m_settings));
default:
Q_ASSERT(false);
break;
}
2019-12-25 17:56:17 +01:00
return PDFCMSPointer(new PDFCMSGeneric());
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager::getOutputProfilesImpl() const
{
// Currently, we only support sRGB output color profile.
2019-12-25 14:34:37 +01:00
return { PDFColorProfileIdentifier::createSRGB() };
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager::getGrayProfilesImpl() const
{
// Jakub Melka: We create gray profiles for temperature 5000K, 6500K and 9300K.
// We also use linear gamma and gamma value 2.2.
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers result =
{
PDFColorProfileIdentifier::createGray(tr("Gray D65, γ = 2.2"), "@GENERIC_Gray_D65_g22", 6500.0, 2.2),
PDFColorProfileIdentifier::createGray(tr("Gray D50, γ = 2.2"), "@GENERIC_Gray_D50_g22", 5000.0, 2.2),
PDFColorProfileIdentifier::createGray(tr("Gray D93, γ = 2.2"), "@GENERIC_Gray_D93_g22", 9300.0, 2.2),
PDFColorProfileIdentifier::createGray(tr("Gray D65, γ = 1.0 (linear)"), "@GENERIC_Gray_D65_g10", 6500.0, 1.0),
PDFColorProfileIdentifier::createGray(tr("Gray D50, γ = 1.0 (linear)"), "@GENERIC_Gray_D50_g10", 5000.0, 1.0),
PDFColorProfileIdentifier::createGray(tr("Gray D93, γ = 1.0 (linear)"), "@GENERIC_Gray_D93_g10", 9300.0, 1.0)
};
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers externalRGBProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileGray);
result.insert(result.end(), externalRGBProfiles.begin(), externalRGBProfiles.end());
return result;
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager::getRGBProfilesImpl() const
{
// Jakub Melka: We create RGB profiles for common standars and also for
// default standard sRGB. See https://en.wikipedia.org/wiki/Color_spaces_with_RGB_primaries.
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers result =
{
PDFColorProfileIdentifier::createSRGB(),
PDFColorProfileIdentifier::createRGB(tr("HDTV (ITU-R BT.709)"), "@GENERIC_RGB_HDTV", 6500, QPointF(0.64, 0.33), QPointF(0.30, 0.60), QPointF(0.15, 0.06), 20.0 / 9.0),
PDFColorProfileIdentifier::createRGB(tr("Adobe RGB 1998"), "@GENERIC_RGB_Adobe1998", 6500, QPointF(0.64, 0.33), QPointF(0.30, 0.60), QPointF(0.15, 0.06), 563.0 / 256.0),
PDFColorProfileIdentifier::createRGB(tr("PAL / SECAM"), "@GENERIC_RGB_PalSecam", 6500, QPointF(0.64, 0.33), QPointF(0.29, 0.60), QPointF(0.15, 0.06), 14.0 / 5.0),
PDFColorProfileIdentifier::createRGB(tr("NTSC"), "@GENERIC_RGB_NTSC", 6500, QPointF(0.64, 0.34), QPointF(0.31, 0.595), QPointF(0.155, 0.07), 20.0 / 9.0),
PDFColorProfileIdentifier::createRGB(tr("Adobe Wide Gamut RGB"), "@GENERIC_RGB_AdobeWideGamut", 5000, QPointF(0.735, 0.265), QPointF(0.115, 0.826), QPointF(0.157, 0.018), 563.0 / 256.0),
PDFColorProfileIdentifier::createRGB(tr("ProPhoto RGB"), "@GENERIC_RGB_ProPhoto", 5000, QPointF(0.7347, 0.2653), QPointF(0.1596, 0.8404), QPointF(0.0366, 0.0001), 9.0 / 5.0)
};
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers externalRGBProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileRGB);
result.insert(result.end(), externalRGBProfiles.begin(), externalRGBProfiles.end());
return result;
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager::getCMYKProfilesImpl() const
{
2019-12-25 14:34:37 +01:00
return getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileCMYK);
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager::getExternalColorProfiles(QString profileDirectory) const
{
PDFColorProfileIdentifiers result;
QDir directory(profileDirectory);
QDir applicationDirectory(QApplication::applicationDirPath());
2019-12-25 14:34:37 +01:00
if (!profileDirectory.isEmpty() && directory.exists())
{
QStringList iccProfiles = directory.entryList({ "*.icc" }, QDir::Files | QDir::Readable | QDir::NoDotAndDotDot, QDir::NoSort);
for (const QString& fileName : iccProfiles)
{
QString filePath = QDir::cleanPath(applicationDirectory.relativeFilePath(directory.absoluteFilePath(fileName)));
2019-12-25 14:34:37 +01:00
// Try to read the profile from the file. If it fails, then do nothing.
QFile file(filePath);
if (file.open(QFile::ReadOnly))
{
QByteArray content = file.readAll();
file.close();
cmsHPROFILE profile = cmsOpenProfileFromMem(content.data(), content.size());
if (profile)
{
PDFColorProfileIdentifier::Type csiType = PDFColorProfileIdentifier::Type::Invalid;
const cmsColorSpaceSignature colorSpace = cmsGetColorSpace(profile);
switch (colorSpace)
{
case cmsSigGrayData:
csiType = PDFColorProfileIdentifier::Type::FileGray;
break;
case cmsSigRgbData:
csiType = PDFColorProfileIdentifier::Type::FileRGB;
break;
case cmsSigCmykData:
csiType = PDFColorProfileIdentifier::Type::FileCMYK;
break;
default:
break;
}
QString description = getInfoFromProfile(profile, cmsInfoDescription);
cmsCloseProfile(profile);
// If we have a valid profile, then add it
if (csiType != PDFColorProfileIdentifier::Type::Invalid)
{
result.emplace_back(PDFColorProfileIdentifier::createFile(csiType, qMove(description), filePath));
}
}
}
}
}
return result;
}
PDFColorProfileIdentifiers PDFCMSManager::getExternalProfilesImpl() const
{
PDFColorProfileIdentifiers result;
QStringList directories(m_settings.profileDirectory);
QDir applicationDirectory(QApplication::applicationDirPath());
if (applicationDirectory.cd("colorprofiles"))
{
directories << applicationDirectory.absolutePath();
}
for (const QString& directory : directories)
{
PDFColorProfileIdentifiers externalProfiles = getExternalColorProfiles(directory);
result.insert(result.end(), externalProfiles.begin(), externalProfiles.end());
}
return result;
}
PDFColorProfileIdentifiers PDFCMSManager::getFilteredExternalProfiles(PDFColorProfileIdentifier::Type type) const
{
PDFColorProfileIdentifiers result;
const PDFColorProfileIdentifiers& externalProfiles = getExternalProfiles();
std::copy_if(externalProfiles.cbegin(), externalProfiles.cend(), std::back_inserter(result), [type](const PDFColorProfileIdentifier& identifier) { return identifier.type == type; });
return result;
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier PDFColorProfileIdentifier::createGray(QString name, QString id, PDFReal temperature, PDFReal gamma)
{
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier result;
result.type = Type::Gray;
result.name = qMove(name);
result.id = qMove(id);
result.temperature = temperature;
result.gamma = gamma;
return result;
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier PDFColorProfileIdentifier::createSRGB()
{
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier result;
result.type = Type::sRGB;
result.name = PDFCMSManager::tr("sRGB");
result.id = "@GENERIC_sRGB";
return result;
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier PDFColorProfileIdentifier::createRGB(QString name, QString id, PDFReal temperature, QPointF primaryR, QPointF primaryG, QPointF primaryB, PDFReal gamma)
{
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier result;
result.type = Type::RGB;
result.name = qMove(name);
result.id = qMove(id);
result.temperature = temperature;
result.primaryR = primaryR;
result.primaryG = primaryG;
result.primaryB = primaryB;
result.gamma = gamma;
return result;
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier PDFColorProfileIdentifier::createFile(Type type, QString name, QString id)
{
PDFColorProfileIdentifier result;
result.type = type;
result.name = qMove(name);
result.id = qMove(id);
return result;
}
} // namespace pdf