|
|
//
|
|
|
// Little cms
|
|
|
// Copyright (C) 1998-2007 Marti Maria
|
|
|
//
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
|
// a copy of this software and associated documentation files (the "Software"),
|
|
|
// to deal in the Software without restriction, including without limitation
|
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
// and/or sell copies of the Software, and to permit persons to whom the Software
|
|
|
// is furnished to do so, subject to the following conditions:
|
|
|
//
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
// all copies or substantial portions of the Software.
|
|
|
//
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
|
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
|
#include "lcms.h"
|
|
|
|
|
|
|
|
|
// Virtual (built-in) profiles
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
// This function creates a profile based on White point, primaries and
|
|
|
// transfer functions.
|
|
|
|
|
|
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateRGBProfile(LPcmsCIExyY WhitePoint,
|
|
|
LPcmsCIExyYTRIPLE Primaries,
|
|
|
LPGAMMATABLE TransferFunction[3])
|
|
|
{
|
|
|
cmsHPROFILE hICC;
|
|
|
cmsCIEXYZ tmp;
|
|
|
MAT3 MColorants;
|
|
|
cmsCIEXYZTRIPLE Colorants;
|
|
|
cmsCIExyY MaxWhite;
|
|
|
|
|
|
|
|
|
hICC = _cmsCreateProfilePlaceholder();
|
|
|
if (!hICC) // can't allocate
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
cmsSetDeviceClass(hICC, icSigDisplayClass);
|
|
|
cmsSetColorSpace(hICC, icSigRgbData);
|
|
|
cmsSetPCS(hICC, icSigXYZData);
|
|
|
cmsSetRenderingIntent(hICC, INTENT_PERCEPTUAL);
|
|
|
|
|
|
|
|
|
// Implement profile using following tags:
|
|
|
//
|
|
|
// 1 icSigProfileDescriptionTag
|
|
|
// 2 icSigMediaWhitePointTag
|
|
|
// 3 icSigRedColorantTag
|
|
|
// 4 icSigGreenColorantTag
|
|
|
// 5 icSigBlueColorantTag
|
|
|
// 6 icSigRedTRCTag
|
|
|
// 7 icSigGreenTRCTag
|
|
|
// 8 icSigBlueTRCTag
|
|
|
|
|
|
// This conforms a standard RGB DisplayProfile as says ICC, and then I add
|
|
|
|
|
|
// 9 icSigChromaticityTag
|
|
|
|
|
|
// As addendum II
|
|
|
|
|
|
|
|
|
// Fill-in the tags
|
|
|
|
|
|
cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "lcms RGB virtual profile");
|
|
|
cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "rgb built-in");
|
|
|
|
|
|
|
|
|
if (WhitePoint) {
|
|
|
|
|
|
cmsxyY2XYZ(&tmp, WhitePoint);
|
|
|
cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) &tmp);
|
|
|
}
|
|
|
|
|
|
if (WhitePoint && Primaries) {
|
|
|
|
|
|
MaxWhite.x = WhitePoint -> x;
|
|
|
MaxWhite.y = WhitePoint -> y;
|
|
|
MaxWhite.Y = 1.0;
|
|
|
|
|
|
if (!cmsBuildRGB2XYZtransferMatrix(&MColorants, &MaxWhite, Primaries))
|
|
|
{
|
|
|
cmsCloseProfile(hICC);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
cmsAdaptMatrixToD50(&MColorants, &MaxWhite);
|
|
|
|
|
|
Colorants.Red.X = MColorants.v[0].n[0];
|
|
|
Colorants.Red.Y = MColorants.v[1].n[0];
|
|
|
Colorants.Red.Z = MColorants.v[2].n[0];
|
|
|
|
|
|
Colorants.Green.X = MColorants.v[0].n[1];
|
|
|
Colorants.Green.Y = MColorants.v[1].n[1];
|
|
|
Colorants.Green.Z = MColorants.v[2].n[1];
|
|
|
|
|
|
Colorants.Blue.X = MColorants.v[0].n[2];
|
|
|
Colorants.Blue.Y = MColorants.v[1].n[2];
|
|
|
Colorants.Blue.Z = MColorants.v[2].n[2];
|
|
|
|
|
|
cmsAddTag(hICC, icSigRedColorantTag, (LPVOID) &Colorants.Red);
|
|
|
cmsAddTag(hICC, icSigBlueColorantTag, (LPVOID) &Colorants.Blue);
|
|
|
cmsAddTag(hICC, icSigGreenColorantTag, (LPVOID) &Colorants.Green);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (TransferFunction) {
|
|
|
|
|
|
// In case of gamma, we must dup' the table pointer
|
|
|
|
|
|
cmsAddTag(hICC, icSigRedTRCTag, (LPVOID) TransferFunction[0]);
|
|
|
cmsAddTag(hICC, icSigGreenTRCTag, (LPVOID) TransferFunction[1]);
|
|
|
cmsAddTag(hICC, icSigBlueTRCTag, (LPVOID) TransferFunction[2]);
|
|
|
}
|
|
|
|
|
|
if (Primaries) {
|
|
|
cmsAddTag(hICC, icSigChromaticityTag, (LPVOID) Primaries);
|
|
|
}
|
|
|
|
|
|
return hICC;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This function creates a profile based on White point and transfer function.
|
|
|
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateGrayProfile(LPcmsCIExyY WhitePoint,
|
|
|
LPGAMMATABLE TransferFunction)
|
|
|
{
|
|
|
cmsHPROFILE hICC;
|
|
|
cmsCIEXYZ tmp;
|
|
|
|
|
|
|
|
|
hICC = _cmsCreateProfilePlaceholder();
|
|
|
if (!hICC) // can't allocate
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
cmsSetDeviceClass(hICC, icSigDisplayClass);
|
|
|
cmsSetColorSpace(hICC, icSigGrayData);
|
|
|
cmsSetPCS(hICC, icSigXYZData);
|
|
|
cmsSetRenderingIntent(hICC, INTENT_PERCEPTUAL);
|
|
|
|
|
|
|
|
|
|
|
|
// Implement profile using following tags:
|
|
|
//
|
|
|
// 1 icSigProfileDescriptionTag
|
|
|
// 2 icSigMediaWhitePointTag
|
|
|
// 6 icSigGrayTRCTag
|
|
|
|
|
|
// This conforms a standard Gray DisplayProfile
|
|
|
|
|
|
// Fill-in the tags
|
|
|
|
|
|
|
|
|
cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "lcms gray virtual profile");
|
|
|
cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "gray built-in");
|
|
|
|
|
|
|
|
|
if (WhitePoint) {
|
|
|
|
|
|
cmsxyY2XYZ(&tmp, WhitePoint);
|
|
|
cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) &tmp);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (TransferFunction) {
|
|
|
|
|
|
// In case of gamma, we must dup' the table pointer
|
|
|
|
|
|
cmsAddTag(hICC, icSigGrayTRCTag, (LPVOID) TransferFunction);
|
|
|
}
|
|
|
|
|
|
return hICC;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
static
|
|
|
int IsPCS(icColorSpaceSignature ColorSpace)
|
|
|
{
|
|
|
return (ColorSpace == icSigXYZData ||
|
|
|
ColorSpace == icSigLabData);
|
|
|
}
|
|
|
|
|
|
static
|
|
|
void FixColorSpaces(cmsHPROFILE hProfile,
|
|
|
icColorSpaceSignature ColorSpace,
|
|
|
icColorSpaceSignature PCS,
|
|
|
DWORD dwFlags)
|
|
|
{
|
|
|
|
|
|
if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) {
|
|
|
|
|
|
if (IsPCS(ColorSpace) && IsPCS(PCS)) {
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigAbstractClass);
|
|
|
cmsSetColorSpace(hProfile, ColorSpace);
|
|
|
cmsSetPCS(hProfile, PCS);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (IsPCS(ColorSpace) && !IsPCS(PCS)) {
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigOutputClass);
|
|
|
cmsSetPCS(hProfile, ColorSpace);
|
|
|
cmsSetColorSpace(hProfile, PCS);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (IsPCS(PCS) && !IsPCS(ColorSpace)) {
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigInputClass);
|
|
|
cmsSetColorSpace(hProfile, ColorSpace);
|
|
|
cmsSetPCS(hProfile, PCS);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigLinkClass);
|
|
|
cmsSetColorSpace(hProfile, ColorSpace);
|
|
|
cmsSetPCS(hProfile, PCS);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
static
|
|
|
cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform)
|
|
|
{
|
|
|
_LPcmsTRANSFORM v = (_LPcmsTRANSFORM) xform;
|
|
|
cmsHPROFILE hICC;
|
|
|
cmsCIEXYZ WhitePoint;
|
|
|
int i, nColors;
|
|
|
size_t Size;
|
|
|
LPcmsNAMEDCOLORLIST nc2;
|
|
|
|
|
|
|
|
|
hICC = _cmsCreateProfilePlaceholder();
|
|
|
if (hICC == NULL) return NULL;
|
|
|
|
|
|
cmsSetRenderingIntent(hICC, v -> Intent);
|
|
|
cmsSetDeviceClass(hICC, icSigNamedColorClass);
|
|
|
cmsSetColorSpace(hICC, v ->ExitColorSpace);
|
|
|
cmsSetPCS(hICC, cmsGetPCS(v ->InputProfile));
|
|
|
cmsTakeMediaWhitePoint(&WhitePoint, v ->InputProfile);
|
|
|
|
|
|
cmsAddTag(hICC, icSigMediaWhitePointTag, &WhitePoint);
|
|
|
cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "LittleCMS");
|
|
|
cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "Named color Device link");
|
|
|
cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "Named color Device link");
|
|
|
|
|
|
|
|
|
nColors = cmsNamedColorCount(xform);
|
|
|
nc2 = cmsAllocNamedColorList(nColors);
|
|
|
|
|
|
Size = sizeof(cmsNAMEDCOLORLIST) + (sizeof(cmsNAMEDCOLOR) * (nColors-1));
|
|
|
|
|
|
CopyMemory(nc2, v->NamedColorList, Size);
|
|
|
nc2 ->ColorantCount = _cmsChannelsOf(v ->ExitColorSpace);
|
|
|
|
|
|
for (i=0; i < nColors; i++) {
|
|
|
cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
|
|
|
}
|
|
|
|
|
|
cmsAddTag(hICC, icSigNamedColor2Tag, (void*) nc2);
|
|
|
cmsFreeNamedColorList(nc2);
|
|
|
|
|
|
return hICC;
|
|
|
}
|
|
|
|
|
|
|
|
|
// Does convert a transform into a device link profile
|
|
|
|
|
|
cmsHPROFILE LCMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, DWORD dwFlags)
|
|
|
{
|
|
|
cmsHPROFILE hICC;
|
|
|
_LPcmsTRANSFORM v = (_LPcmsTRANSFORM) hTransform;
|
|
|
LPLUT Lut;
|
|
|
LCMSBOOL MustFreeLUT;
|
|
|
LPcmsNAMEDCOLORLIST InputColorant = NULL;
|
|
|
LPcmsNAMEDCOLORLIST OutputColorant = NULL;
|
|
|
|
|
|
|
|
|
// Check if is a named color transform
|
|
|
|
|
|
if (cmsGetDeviceClass(v ->InputProfile) == icSigNamedColorClass) {
|
|
|
|
|
|
return CreateNamedColorDevicelink(hTransform);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (v ->DeviceLink) {
|
|
|
|
|
|
Lut = v -> DeviceLink;
|
|
|
MustFreeLUT = FALSE;
|
|
|
}
|
|
|
else {
|
|
|
|
|
|
Lut = _cmsPrecalculateDeviceLink(hTransform, dwFlags);
|
|
|
if (!Lut) return NULL;
|
|
|
MustFreeLUT = TRUE;
|
|
|
}
|
|
|
|
|
|
hICC = _cmsCreateProfilePlaceholder();
|
|
|
if (!hICC) { // can't allocate
|
|
|
|
|
|
if (MustFreeLUT) cmsFreeLUT(Lut);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
|
|
|
FixColorSpaces(hICC, v -> EntryColorSpace, v -> ExitColorSpace, dwFlags);
|
|
|
|
|
|
cmsSetRenderingIntent(hICC, v -> Intent);
|
|
|
|
|
|
// Implement devicelink profile using following tags:
|
|
|
//
|
|
|
// 1 icSigProfileDescriptionTag
|
|
|
// 2 icSigMediaWhitePointTag
|
|
|
// 3 icSigAToB0Tag
|
|
|
|
|
|
|
|
|
cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "LittleCMS");
|
|
|
cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "Device link");
|
|
|
cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "Device link");
|
|
|
|
|
|
|
|
|
cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ());
|
|
|
|
|
|
if (cmsGetDeviceClass(hICC) == icSigOutputClass) {
|
|
|
|
|
|
cmsAddTag(hICC, icSigBToA0Tag, (LPVOID) Lut);
|
|
|
}
|
|
|
else
|
|
|
cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
|
|
|
|
|
|
|
|
|
|
|
|
// Try to read input and output colorant table
|
|
|
if (cmsIsTag(v ->InputProfile, icSigColorantTableTag)) {
|
|
|
|
|
|
// Input table can only come in this way.
|
|
|
InputColorant = cmsReadColorantTable(v ->InputProfile, icSigColorantTableTag);
|
|
|
}
|
|
|
|
|
|
// Output is a little bit more complex.
|
|
|
if (cmsGetDeviceClass(v ->OutputProfile) == icSigLinkClass) {
|
|
|
|
|
|
// This tag may exist only on devicelink profiles.
|
|
|
if (cmsIsTag(v ->OutputProfile, icSigColorantTableOutTag)) {
|
|
|
|
|
|
OutputColorant = cmsReadColorantTable(v ->OutputProfile, icSigColorantTableOutTag);
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (cmsIsTag(v ->OutputProfile, icSigColorantTableTag)) {
|
|
|
|
|
|
OutputColorant = cmsReadColorantTable(v ->OutputProfile, icSigColorantTableTag);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (InputColorant)
|
|
|
cmsAddTag(hICC, icSigColorantTableTag, InputColorant);
|
|
|
|
|
|
if (OutputColorant)
|
|
|
cmsAddTag(hICC, icSigColorantTableOutTag, OutputColorant);
|
|
|
|
|
|
|
|
|
|
|
|
if (MustFreeLUT) cmsFreeLUT(Lut);
|
|
|
if (InputColorant) cmsFreeNamedColorList(InputColorant);
|
|
|
if (OutputColorant) cmsFreeNamedColorList(OutputColorant);
|
|
|
|
|
|
return hICC;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
// This is a devicelink operating in the target colorspace with as many transfer
|
|
|
// functions as components
|
|
|
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateLinearizationDeviceLink(icColorSpaceSignature ColorSpace,
|
|
|
LPGAMMATABLE TransferFunctions[])
|
|
|
{
|
|
|
cmsHPROFILE hICC;
|
|
|
LPLUT Lut;
|
|
|
|
|
|
|
|
|
hICC = _cmsCreateProfilePlaceholder();
|
|
|
if (!hICC) // can't allocate
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
cmsSetDeviceClass(hICC, icSigLinkClass);
|
|
|
cmsSetColorSpace(hICC, ColorSpace);
|
|
|
cmsSetPCS(hICC, ColorSpace);
|
|
|
cmsSetRenderingIntent(hICC, INTENT_PERCEPTUAL);
|
|
|
|
|
|
|
|
|
// Creates a LUT with prelinearization step only
|
|
|
Lut = cmsAllocLUT();
|
|
|
if (Lut == NULL) return NULL;
|
|
|
|
|
|
// Set up channels
|
|
|
Lut ->InputChan = Lut ->OutputChan = _cmsChannelsOf(ColorSpace);
|
|
|
|
|
|
// Copy tables to LUT
|
|
|
cmsAllocLinearTable(Lut, TransferFunctions, 1);
|
|
|
|
|
|
// Create tags
|
|
|
cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "lcms linearization device link");
|
|
|
cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "linearization built-in");
|
|
|
|
|
|
cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ());
|
|
|
cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
|
|
|
|
|
|
// LUT is already on virtual profile
|
|
|
cmsFreeLUT(Lut);
|
|
|
|
|
|
// Ok, done
|
|
|
return hICC;
|
|
|
}
|
|
|
|
|
|
|
|
|
// Ink-limiting algorithm
|
|
|
//
|
|
|
// Sum = C + M + Y + K
|
|
|
// If Sum > InkLimit
|
|
|
// Ratio= 1 - (Sum - InkLimit) / (C + M + Y)
|
|
|
// if Ratio <0
|
|
|
// Ratio=0
|
|
|
// endif
|
|
|
// Else
|
|
|
// Ratio=1
|
|
|
// endif
|
|
|
//
|
|
|
// C = Ratio * C
|
|
|
// M = Ratio * M
|
|
|
// Y = Ratio * Y
|
|
|
// K: Does not change
|
|
|
|
|
|
static
|
|
|
int InkLimitingSampler(register WORD In[], register WORD Out[], register LPVOID Cargo)
|
|
|
{
|
|
|
double InkLimit = *(double *) Cargo;
|
|
|
double SumCMY, SumCMYK, Ratio;
|
|
|
|
|
|
InkLimit = (InkLimit * 655.35);
|
|
|
|
|
|
SumCMY = In[0] + In[1] + In[2];
|
|
|
SumCMYK = SumCMY + In[3];
|
|
|
|
|
|
if (SumCMYK > InkLimit) {
|
|
|
|
|
|
Ratio = 1 - ((SumCMYK - InkLimit) / SumCMY);
|
|
|
if (Ratio < 0)
|
|
|
Ratio = 0;
|
|
|
}
|
|
|
else Ratio = 1;
|
|
|
|
|
|
Out[0] = (WORD) floor(In[0] * Ratio + 0.5); // C
|
|
|
Out[1] = (WORD) floor(In[1] * Ratio + 0.5); // M
|
|
|
Out[2] = (WORD) floor(In[2] * Ratio + 0.5); // Y
|
|
|
|
|
|
Out[3] = In[3]; // K (untouched)
|
|
|
|
|
|
return TRUE;
|
|
|
}
|
|
|
|
|
|
// This is a devicelink operating in CMYK for ink-limiting
|
|
|
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateInkLimitingDeviceLink(icColorSpaceSignature ColorSpace,
|
|
|
double Limit)
|
|
|
{
|
|
|
cmsHPROFILE hICC;
|
|
|
LPLUT Lut;
|
|
|
|
|
|
if (ColorSpace != icSigCmykData) {
|
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "InkLimiting: Only CMYK currently supported");
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
if (Limit < 0.0 || Limit > 400) {
|
|
|
|
|
|
cmsSignalError(LCMS_ERRC_WARNING, "InkLimiting: Limit should be between 0..400");
|
|
|
if (Limit < 0) Limit = 0;
|
|
|
if (Limit > 400) Limit = 400;
|
|
|
|
|
|
}
|
|
|
|
|
|
hICC = _cmsCreateProfilePlaceholder();
|
|
|
if (!hICC) // can't allocate
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
cmsSetDeviceClass(hICC, icSigLinkClass);
|
|
|
cmsSetColorSpace(hICC, ColorSpace);
|
|
|
cmsSetPCS(hICC, ColorSpace);
|
|
|
cmsSetRenderingIntent(hICC, INTENT_PERCEPTUAL);
|
|
|
|
|
|
|
|
|
// Creates a LUT with 3D grid only
|
|
|
Lut = cmsAllocLUT();
|
|
|
if (Lut == NULL) {
|
|
|
cmsCloseProfile(hICC);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
|
|
|
cmsAlloc3DGrid(Lut, 17, _cmsChannelsOf(ColorSpace),
|
|
|
_cmsChannelsOf(ColorSpace));
|
|
|
|
|
|
if (!cmsSample3DGrid(Lut, InkLimitingSampler, (LPVOID) &Limit, 0)) {
|
|
|
|
|
|
// Shouldn't reach here
|
|
|
cmsFreeLUT(Lut);
|
|
|
cmsCloseProfile(hICC);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
// Create tags
|
|
|
|
|
|
cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "lcms ink limiting device link");
|
|
|
cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "ink limiting built-in");
|
|
|
|
|
|
cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ());
|
|
|
|
|
|
cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
|
|
|
|
|
|
// LUT is already on virtual profile
|
|
|
cmsFreeLUT(Lut);
|
|
|
|
|
|
// Ok, done
|
|
|
return hICC;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static
|
|
|
LPLUT Create3x3EmptyLUT(void)
|
|
|
{
|
|
|
LPLUT AToB0 = cmsAllocLUT();
|
|
|
if (AToB0 == NULL) return NULL;
|
|
|
|
|
|
AToB0 -> InputChan = AToB0 -> OutputChan = 3;
|
|
|
return AToB0;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Creates a fake Lab identity.
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateLabProfile(LPcmsCIExyY WhitePoint)
|
|
|
{
|
|
|
cmsHPROFILE hProfile;
|
|
|
LPLUT Lut;
|
|
|
|
|
|
hProfile = cmsCreateRGBProfile(WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
|
|
|
if (hProfile == NULL) return NULL;
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigAbstractClass);
|
|
|
cmsSetColorSpace(hProfile, icSigLabData);
|
|
|
cmsSetPCS(hProfile, icSigLabData);
|
|
|
|
|
|
cmsAddTag(hProfile, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hProfile, icSigProfileDescriptionTag, (LPVOID) "lcms Lab identity");
|
|
|
cmsAddTag(hProfile, icSigDeviceModelDescTag, (LPVOID) "Lab built-in");
|
|
|
|
|
|
|
|
|
// An empty LUTs is all we need
|
|
|
Lut = Create3x3EmptyLUT();
|
|
|
if (Lut == NULL) {
|
|
|
cmsCloseProfile(hProfile);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
cmsAddTag(hProfile, icSigAToB0Tag, (LPVOID) Lut);
|
|
|
cmsAddTag(hProfile, icSigBToA0Tag, (LPVOID) Lut);
|
|
|
|
|
|
cmsFreeLUT(Lut);
|
|
|
|
|
|
return hProfile;
|
|
|
}
|
|
|
|
|
|
|
|
|
// Creates a fake Lab identity.
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateLab4Profile(LPcmsCIExyY WhitePoint)
|
|
|
{
|
|
|
cmsHPROFILE hProfile;
|
|
|
LPLUT Lut;
|
|
|
|
|
|
hProfile = cmsCreateRGBProfile(WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
|
|
|
if (hProfile == NULL) return NULL;
|
|
|
|
|
|
cmsSetProfileICCversion(hProfile, 0x4000000);
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigAbstractClass);
|
|
|
cmsSetColorSpace(hProfile, icSigLabData);
|
|
|
cmsSetPCS(hProfile, icSigLabData);
|
|
|
|
|
|
cmsAddTag(hProfile, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hProfile, icSigProfileDescriptionTag, (LPVOID) "lcms Lab identity v4");
|
|
|
cmsAddTag(hProfile, icSigDeviceModelDescTag, (LPVOID) "Lab v4 built-in");
|
|
|
|
|
|
|
|
|
// An empty LUTs is all we need
|
|
|
Lut = Create3x3EmptyLUT();
|
|
|
if (Lut == NULL) {
|
|
|
cmsCloseProfile(hProfile);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
Lut -> wFlags |= LUT_V4_INPUT_EMULATE_V2;
|
|
|
cmsAddTag(hProfile, icSigAToB0Tag, (LPVOID) Lut);
|
|
|
|
|
|
Lut -> wFlags |= LUT_V4_OUTPUT_EMULATE_V2;
|
|
|
cmsAddTag(hProfile, icSigBToA0Tag, (LPVOID) Lut);
|
|
|
|
|
|
cmsFreeLUT(Lut);
|
|
|
|
|
|
return hProfile;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Creates a fake XYZ identity
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateXYZProfile(void)
|
|
|
{
|
|
|
cmsHPROFILE hProfile;
|
|
|
LPLUT Lut;
|
|
|
|
|
|
hProfile = cmsCreateRGBProfile(cmsD50_xyY(), NULL, NULL);
|
|
|
if (hProfile == NULL) return NULL;
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigAbstractClass);
|
|
|
cmsSetColorSpace(hProfile, icSigXYZData);
|
|
|
cmsSetPCS(hProfile, icSigXYZData);
|
|
|
|
|
|
cmsAddTag(hProfile, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hProfile, icSigProfileDescriptionTag, (LPVOID) "lcms XYZ identity");
|
|
|
cmsAddTag(hProfile, icSigDeviceModelDescTag, (LPVOID) "XYZ built-in");
|
|
|
|
|
|
// An empty LUTs is all we need
|
|
|
Lut = Create3x3EmptyLUT();
|
|
|
if (Lut == NULL) {
|
|
|
cmsCloseProfile(hProfile);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
cmsAddTag(hProfile, icSigAToB0Tag, (LPVOID) Lut);
|
|
|
cmsAddTag(hProfile, icSigBToA0Tag, (LPVOID) Lut);
|
|
|
cmsAddTag(hProfile, icSigPreview0Tag, (LPVOID) Lut);
|
|
|
|
|
|
cmsFreeLUT(Lut);
|
|
|
return hProfile;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
If R<>sRGB,G<>sRGB, B<>sRGB < 0.04045
|
|
|
|
|
|
R = R<>sRGB / 12.92
|
|
|
G = G<>sRGB / 12.92
|
|
|
B = B<>sRGB / 12.92
|
|
|
|
|
|
|
|
|
|
|
|
else if R<>sRGB,G<>sRGB, B<>sRGB >= 0.04045
|
|
|
|
|
|
R = ((R<>sRGB + 0.055) / 1.055)^2.4
|
|
|
G = ((G<>sRGB + 0.055) / 1.055)^2.4
|
|
|
B = ((B<>sRGB + 0.055) / 1.055)^2.4
|
|
|
|
|
|
*/
|
|
|
|
|
|
static
|
|
|
LPGAMMATABLE Build_sRGBGamma(void)
|
|
|
{
|
|
|
double Parameters[5];
|
|
|
|
|
|
Parameters[0] = 2.4;
|
|
|
Parameters[1] = 1. / 1.055;
|
|
|
Parameters[2] = 0.055 / 1.055;
|
|
|
Parameters[3] = 1. / 12.92;
|
|
|
Parameters[4] = 0.04045; // d
|
|
|
|
|
|
return cmsBuildParametricGamma(1024, 4, Parameters);
|
|
|
}
|
|
|
|
|
|
// Create the ICC virtual profile for sRGB space
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreate_sRGBProfile(void)
|
|
|
{
|
|
|
cmsCIExyY D65;
|
|
|
cmsCIExyYTRIPLE Rec709Primaries = {
|
|
|
{0.6400, 0.3300, 1.0},
|
|
|
{0.3000, 0.6000, 1.0},
|
|
|
{0.1500, 0.0600, 1.0}
|
|
|
};
|
|
|
LPGAMMATABLE Gamma22[3];
|
|
|
cmsHPROFILE hsRGB;
|
|
|
|
|
|
cmsWhitePointFromTemp(6504, &D65);
|
|
|
Gamma22[0] = Gamma22[1] = Gamma22[2] = Build_sRGBGamma();
|
|
|
|
|
|
hsRGB = cmsCreateRGBProfile(&D65, &Rec709Primaries, Gamma22);
|
|
|
cmsFreeGamma(Gamma22[0]);
|
|
|
if (hsRGB == NULL) return NULL;
|
|
|
|
|
|
|
|
|
cmsAddTag(hsRGB, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hsRGB, icSigDeviceModelDescTag, (LPVOID) "sRGB built-in");
|
|
|
cmsAddTag(hsRGB, icSigProfileDescriptionTag, (LPVOID) "sRGB built-in");
|
|
|
|
|
|
return hsRGB;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
double Brightness;
|
|
|
double Contrast;
|
|
|
double Hue;
|
|
|
double Saturation;
|
|
|
cmsCIEXYZ WPsrc, WPdest;
|
|
|
|
|
|
} BCHSWADJUSTS, *LPBCHSWADJUSTS;
|
|
|
|
|
|
|
|
|
static
|
|
|
int bchswSampler(register WORD In[], register WORD Out[], register LPVOID Cargo)
|
|
|
{
|
|
|
cmsCIELab LabIn, LabOut;
|
|
|
cmsCIELCh LChIn, LChOut;
|
|
|
cmsCIEXYZ XYZ;
|
|
|
LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo;
|
|
|
|
|
|
|
|
|
cmsLabEncoded2Float(&LabIn, In);
|
|
|
|
|
|
|
|
|
cmsLab2LCh(&LChIn, &LabIn);
|
|
|
|
|
|
// Do some adjusts on LCh
|
|
|
|
|
|
LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness;
|
|
|
LChOut.C = LChIn.C + bchsw -> Saturation;
|
|
|
LChOut.h = LChIn.h + bchsw -> Hue;
|
|
|
|
|
|
|
|
|
cmsLCh2Lab(&LabOut, &LChOut);
|
|
|
|
|
|
// Move white point in Lab
|
|
|
|
|
|
cmsLab2XYZ(&bchsw ->WPsrc, &XYZ, &LabOut);
|
|
|
cmsXYZ2Lab(&bchsw ->WPdest, &LabOut, &XYZ);
|
|
|
|
|
|
// Back to encoded
|
|
|
|
|
|
cmsFloat2LabEncoded(Out, &LabOut);
|
|
|
|
|
|
return TRUE;
|
|
|
}
|
|
|
|
|
|
|
|
|
// Creates an abstract profile operating in Lab space for Brightness,
|
|
|
// contrast, Saturation and white point displacement
|
|
|
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateBCHSWabstractProfile(int nLUTPoints,
|
|
|
double Bright,
|
|
|
double Contrast,
|
|
|
double Hue,
|
|
|
double Saturation,
|
|
|
int TempSrc,
|
|
|
int TempDest)
|
|
|
{
|
|
|
cmsHPROFILE hICC;
|
|
|
LPLUT Lut;
|
|
|
BCHSWADJUSTS bchsw;
|
|
|
cmsCIExyY WhitePnt;
|
|
|
|
|
|
bchsw.Brightness = Bright;
|
|
|
bchsw.Contrast = Contrast;
|
|
|
bchsw.Hue = Hue;
|
|
|
bchsw.Saturation = Saturation;
|
|
|
|
|
|
cmsWhitePointFromTemp(TempSrc, &WhitePnt);
|
|
|
cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt);
|
|
|
|
|
|
cmsWhitePointFromTemp(TempDest, &WhitePnt);
|
|
|
cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt);
|
|
|
|
|
|
hICC = _cmsCreateProfilePlaceholder();
|
|
|
if (!hICC) // can't allocate
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
cmsSetDeviceClass(hICC, icSigAbstractClass);
|
|
|
cmsSetColorSpace(hICC, icSigLabData);
|
|
|
cmsSetPCS(hICC, icSigLabData);
|
|
|
|
|
|
cmsSetRenderingIntent(hICC, INTENT_PERCEPTUAL);
|
|
|
|
|
|
|
|
|
// Creates a LUT with 3D grid only
|
|
|
Lut = cmsAllocLUT();
|
|
|
if (Lut == NULL) {
|
|
|
cmsCloseProfile(hICC);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
cmsAlloc3DGrid(Lut, nLUTPoints, 3, 3);
|
|
|
|
|
|
if (!cmsSample3DGrid(Lut, bchswSampler, (LPVOID) &bchsw, 0)) {
|
|
|
|
|
|
// Shouldn't reach here
|
|
|
cmsFreeLUT(Lut);
|
|
|
cmsCloseProfile(hICC);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
// Create tags
|
|
|
|
|
|
cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "(lcms internal)");
|
|
|
cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "lcms BCHSW abstract profile");
|
|
|
cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "BCHSW built-in");
|
|
|
|
|
|
cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ());
|
|
|
|
|
|
cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
|
|
|
|
|
|
// LUT is already on virtual profile
|
|
|
cmsFreeLUT(Lut);
|
|
|
|
|
|
// Ok, done
|
|
|
return hICC;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
// Creates a fake NULL profile. This profile return 1 channel as always 0.
|
|
|
// Is useful only for gamut checking tricks
|
|
|
|
|
|
cmsHPROFILE LCMSEXPORT cmsCreateNULLProfile(void)
|
|
|
{
|
|
|
cmsHPROFILE hProfile;
|
|
|
LPLUT Lut;
|
|
|
LPGAMMATABLE EmptyTab;
|
|
|
|
|
|
hProfile = _cmsCreateProfilePlaceholder();
|
|
|
if (!hProfile) // can't allocate
|
|
|
return NULL;
|
|
|
|
|
|
cmsSetDeviceClass(hProfile, icSigOutputClass);
|
|
|
cmsSetColorSpace(hProfile, icSigGrayData);
|
|
|
cmsSetPCS(hProfile, icSigLabData);
|
|
|
|
|
|
|
|
|
// An empty LUTs is all we need
|
|
|
Lut = cmsAllocLUT();
|
|
|
if (Lut == NULL) {
|
|
|
cmsCloseProfile(hProfile);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
Lut -> InputChan = 3;
|
|
|
Lut -> OutputChan = 1;
|
|
|
|
|
|
EmptyTab = cmsAllocGamma(2);
|
|
|
EmptyTab ->GammaTable[0] = 0;
|
|
|
EmptyTab ->GammaTable[1] = 0;
|
|
|
|
|
|
cmsAllocLinearTable(Lut, &EmptyTab, 2);
|
|
|
|
|
|
cmsAddTag(hProfile, icSigBToA0Tag, (LPVOID) Lut);
|
|
|
|
|
|
cmsFreeLUT(Lut);
|
|
|
cmsFreeGamma(EmptyTab);
|
|
|
|
|
|
return hProfile;
|
|
|
}
|