2020-01-18 11:38:54 +01:00
// Copyright (C) 2019-2020 Jakub Melka
2019-12-24 17:29:40 +01:00
//
// 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>
2019-12-26 16:28:46 +01:00
# include <QReadWriteLock>
2019-12-25 14:34:37 +01:00
2019-12-24 17:29:40 +01:00
# pragma warning(push)
# pragma warning(disable:5033)
# include <lcms2.h>
# pragma warning(pop)
2019-12-26 16:28:46 +01:00
# include <unordered_map>
2019-12-24 17:29:40 +01:00
namespace pdf
{
2019-12-26 16:28:46 +01:00
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 ;
2019-12-26 16:28:46 +01:00
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 ;
2019-12-26 16:28:46 +01:00
/// 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 ) ; }
2019-12-26 16:28:46 +01:00
/// 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-26 16:28:46 +01:00
} ;
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 ;
}
2019-12-26 16:28:46 +01:00
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 ) ;
}
}
2019-12-26 16:28:46 +01:00
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 ) ;
2019-12-26 16:28:46 +01:00
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 ) ;
2019-12-26 16:28:46 +01:00
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 ) ;
2019-12-26 16:28:46 +01:00
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 ) ;
2019-12-26 16:28:46 +01:00
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-26 16:28:46 +01:00
{
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 = { } ;
2019-12-27 14:17:33 +01:00
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 )
{
2019-12-27 14:17:33 +01:00
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. " ) ) ;
}
2019-12-26 16:28:46 +01:00
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-26 16:28:46 +01:00
{
2019-12-30 16:08:48 +01:00
const int key = getCacheKey ( profile , intent , isRGB888Buffer ) ;
2019-12-26 16:28:46 +01:00
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 ( ) ) ;
2019-12-26 16:28:46 +01:00
}
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 ( ) ;
}
2019-12-24 17:29:40 +01:00
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 ) ;
}
2019-12-24 17:29:40 +01:00
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
2019-12-24 17:29:40 +01:00
{
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
2019-12-25 18:42:54 +01:00
{
Q_UNUSED ( color ) ;
2019-12-26 17:57:45 +01:00
Q_UNUSED ( renderingIntent ) ;
2019-12-25 18:42:54 +01:00
Q_UNUSED ( iccID ) ;
Q_UNUSED ( iccData ) ;
2019-12-26 17:57:45 +01:00
Q_UNUSED ( reporter ) ;
2019-12-25 18:42:54 +01:00
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 ;
}
2019-12-24 17:29:40 +01:00
PDFCMSManager : : PDFCMSManager ( QObject * parent ) :
2019-12-26 16:28:46 +01:00
BaseClass ( parent ) ,
m_mutex ( QMutex : : Recursive )
2019-12-24 17:29:40 +01:00
{
}
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
2019-12-24 17:29:40 +01:00
{
QMutexLocker lock ( & m_mutex ) ;
return m_outputProfiles . get ( this , & PDFCMSManager : : getOutputProfilesImpl ) ;
}
2019-12-25 14:34:37 +01:00
const PDFColorProfileIdentifiers & PDFCMSManager : : getGrayProfiles ( ) const
2019-12-24 17:29:40 +01:00
{
QMutexLocker lock ( & m_mutex ) ;
return m_grayProfiles . get ( this , & PDFCMSManager : : getGrayProfilesImpl ) ;
}
2019-12-25 14:34:37 +01:00
const PDFColorProfileIdentifiers & PDFCMSManager : : getRGBProfiles ( ) const
2019-12-24 17:29:40 +01:00
{
QMutexLocker lock ( & m_mutex ) ;
return m_RGBProfiles . get ( this , & PDFCMSManager : : getRGBProfilesImpl ) ;
}
2019-12-25 14:34:37 +01:00
const PDFColorProfileIdentifiers & PDFCMSManager : : getCMYKProfiles ( ) const
2019-12-24 17:29:40 +01:00
{
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 ) ;
}
2019-12-24 17:29:40 +01:00
PDFCMSSettings PDFCMSManager : : getDefaultSettings ( ) const
{
PDFCMSSettings settings ;
2019-12-25 14:34:37 +01:00
auto getFirstProfileId = [ ] ( const PDFColorProfileIdentifiers & identifiers )
2019-12-24 17:29:40 +01:00
{
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
{
2019-12-26 16:28:46 +01:00
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
2019-12-24 17:29:40 +01:00
{
// Currently, we only support sRGB output color profile.
2019-12-25 14:34:37 +01:00
return { PDFColorProfileIdentifier : : createSRGB ( ) } ;
2019-12-24 17:29:40 +01:00
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager : : getGrayProfilesImpl ( ) const
2019-12-24 17:29:40 +01:00
{
// 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-24 17:29:40 +01:00
} ;
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-24 17:29:40 +01:00
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager : : getRGBProfilesImpl ( ) const
2019-12-24 17:29:40 +01:00
{
// 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-24 17:29:40 +01:00
} ;
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-24 17:29:40 +01:00
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager : : getCMYKProfilesImpl ( ) const
2019-12-24 17:29:40 +01:00
{
2019-12-25 14:34:37 +01:00
return getFilteredExternalProfiles ( PDFColorProfileIdentifier : : Type : : FileCMYK ) ;
}
2019-12-24 17:29:40 +01:00
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifiers PDFCMSManager : : getExternalColorProfiles ( QString profileDirectory ) const
{
PDFColorProfileIdentifiers result ;
QDir directory ( profileDirectory ) ;
2020-05-03 19:14:53 +02:00
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 )
{
2020-05-03 19:14:53 +02:00
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-24 17:29:40 +01:00
}
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier PDFColorProfileIdentifier : : createGray ( QString name , QString id , PDFReal temperature , PDFReal gamma )
2019-12-24 17:29:40 +01:00
{
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier result ;
2019-12-24 17:29:40 +01:00
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-24 17:29:40 +01:00
{
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier result ;
2019-12-24 17:29:40 +01:00
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-24 17:29:40 +01:00
{
2019-12-25 14:34:37 +01:00
PDFColorProfileIdentifier result ;
2019-12-24 17:29:40 +01:00
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 ;
}
2019-12-24 17:29:40 +01:00
} // namespace pdf