/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER .
*
* This code is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 only , as
* published by the Free Software Foundation . Oracle designates this
* particular file as subject to the " Classpath " exception as provided
* by Oracle in the LICENSE file that accompanied this code .
*
* This code 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 General Public License
* version 2 for more details ( a copy is included in the LICENSE file that
* accompanied this code ) .
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work ; if not , write to the Free Software Foundation ,
* Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA .
*
* Please contact Oracle , 500 Oracle Parkway , Redwood Shores , CA 94065 USA
* or visit www . oracle . com if you need additional information or have any
* questions .
*/
// This file is available under and governed by the GNU General Public
// License version 2 only, as published by the Free Software Foundation.
// However, the following notice accompanied the original version of this
// file:
//
//---------------------------------------------------------------------------------
//
// Little Color Management System
// Copyright (c) 1998-2022 Marti Maria Saguer
//
// 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 "lcms2_internal.h"
// PostScript ColorRenderingDictionary and ColorSpaceArray
#define MAXPSCOLS 60 // Columns on tables
/*
Implementation
- - - - - - - - - - - - - -
PostScript does use XYZ as its internal PCS . But since PostScript
interpolation tables are limited to 8 bits , I use Lab as a way to
improve the accuracy , favoring perceptual results . So , for the creation
of each CRD , CSA the profiles are converted to Lab via a device
link between profile - > Lab or Lab - > profile . The PS code necessary to
convert Lab < - > XYZ is also included .
Color Space Arrays ( CSA )
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
In order to obtain precision , code chooses between three ways to implement
the device - > XYZ transform . These cases identifies monochrome profiles ( often
implemented as a set of curves ) , matrix - shaper and Pipeline - based .
Monochrome
- - - - - - - - - - -
This is implemented as / CIEBasedA CSA . The prelinearization curve is
placed into / DecodeA section , and matrix equals to D50 . Since here is
no interpolation tables , I do the conversion directly to XYZ
NOTE : CLUT - based monochrome profiles are NOT supported . So , cmsFLAGS_MATRIXINPUT
flag is forced on such profiles .
[ / CIEBasedA
< <
/ DecodeA { transfer function } bind
/ MatrixA [ D50 ]
/ RangeLMN [ 0 . 0 cmsD50X 0 . 0 cmsD50Y 0 . 0 cmsD50Z ]
/ WhitePoint [ D50 ]
/ BlackPoint [ BP ]
/ RenderingIntent ( intent )
> >
]
On simpler profiles , the PCS is already XYZ , so no conversion is required .
Matrix - shaper based
- - - - - - - - - - - - - - - - - - -
This is implemented both with / CIEBasedABC or / CIEBasedDEF depending on the
profile implementation . Since here there are no interpolation tables , I do
the conversion directly to XYZ
[ / CIEBasedABC
< <
/ DecodeABC [ { transfer1 } { transfer2 } { transfer3 } ]
/ MatrixABC [ Matrix ]
/ RangeLMN [ 0 . 0 cmsD50X 0 . 0 cmsD50Y 0 . 0 cmsD50Z ]
/ DecodeLMN [ { / 2 } dup dup ]
/ WhitePoint [ D50 ]
/ BlackPoint [ BP ]
/ RenderingIntent ( intent )
> >
]
CLUT based
- - - - - - - - - -
Lab is used in such cases .
[ / CIEBasedDEF
< <
/ DecodeDEF [ < prelinearization > ]
/ Table [ p p p [ < . . . > ] ]
/ RangeABC [ 0 1 0 1 0 1 ]
/ DecodeABC [ < postlinearization > ]
/ RangeLMN [ - 0 . 236 1 . 254 0 1 - 0 . 635 1 . 640 ]
% - 128 / 500 1 + 127 / 500 0 1 - 127 / 200 1 + 128 / 200
/ MatrixABC [ 1 1 1 1 0 0 0 0 - 1 ]
/ WhitePoint [ D50 ]
/ BlackPoint [ BP ]
/ RenderingIntent ( intent )
]
Color Rendering Dictionaries ( CRD )
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
These are always implemented as CLUT , and always are using Lab . Since CRD are expected to
be used as resources , the code adds the definition as well .
< <
/ ColorRenderingType 1
/ WhitePoint [ D50 ]
/ BlackPoint [ BP ]
/ MatrixPQR [ Bradford ]
/ RangePQR [ - 0 . 125 1 . 375 - 0 . 125 1 . 375 - 0 . 125 1 . 375 ]
/ TransformPQR [
{ 4 index 3 get div 2 index 3 get mul exch pop exch pop exch pop exch pop } bind
{ 4 index 4 get div 2 index 4 get mul exch pop exch pop exch pop exch pop } bind
{ 4 index 5 get div 2 index 5 get mul exch pop exch pop exch pop exch pop } bind
]
/ MatrixABC < . . . >
/ EncodeABC < . . . >
/ RangeABC < . . used for XYZ - > Lab >
/ EncodeLMN
/ RenderTable [ p p p [ < . . . > ] ]
/ RenderingIntent ( Perceptual )
> >
/ Current exch / ColorRendering defineresource pop
The following stages are used to convert from XYZ to Lab
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Input is given at LMN stage on X , Y , Z
Encode LMN gives us f ( X / Xn ) , f ( Y / Yn ) , f ( Z / Zn )
/ EncodeLMN [
{ 0 . 964200 div dup 0 . 008856 le { 7 . 787 mul 16 116 div add } { 1 3 div exp } ifelse } bind
{ 1 . 000000 div dup 0 . 008856 le { 7 . 787 mul 16 116 div add } { 1 3 div exp } ifelse } bind
{ 0 . 824900 div dup 0 . 008856 le { 7 . 787 mul 16 116 div add } { 1 3 div exp } ifelse } bind
]
MatrixABC is used to compute f ( Y / Yn ) , f ( X / Xn ) - f ( Y / Yn ) , f ( Y / Yn ) - f ( Z / Zn )
| 0 1 0 |
| 1 - 1 0 |
| 0 1 - 1 |
/ MatrixABC [ 0 1 0 1 - 1 1 0 0 - 1 ]
EncodeABC finally gives Lab values .
/ EncodeABC [
{ 116 mul 16 sub 100 div } bind
{ 500 mul 128 add 255 div } bind
{ 200 mul 128 add 255 div } bind
]
The following stages are used to convert Lab to XYZ
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/ RangeABC [ 0 1 0 1 0 1 ]
/ DecodeABC [ { 100 mul 16 add 116 div } bind
{ 255 mul 128 sub 500 div } bind
{ 255 mul 128 sub 200 div } bind
]
/ MatrixABC [ 1 1 1 1 0 0 0 0 - 1 ]
/ DecodeLMN [
{ dup 6 29 div ge { dup dup mul mul } { 4 29 div sub 108 841 div mul } ifelse 0 . 964200 mul } bind
{ dup 6 29 div ge { dup dup mul mul } { 4 29 div sub 108 841 div mul } ifelse } bind
{ dup 6 29 div ge { dup dup mul mul } { 4 29 div sub 108 841 div mul } ifelse 0 . 824900 mul } bind
]
*/
/*
PostScript algorithms discussion .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
1 D interpolation algorithm
1 D interpolation ( float )
- - - - - - - - - - - - - - - - - - - - - - - -
val2 = Domain * Value ;
cell0 = ( int ) floor ( val2 ) ;
cell1 = ( int ) ceil ( val2 ) ;
rest = val2 - cell0 ;
y0 = LutTable [ cell0 ] ;
y1 = LutTable [ cell1 ] ;
y = y0 + ( y1 - y0 ) * rest ;
PostScript code Stack
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
{ % v
< check 0 . . 1 . 0 >
[ array ] % v tab
dup % v tab tab
length 1 sub % v tab dom
3 - 1 roll % tab dom v
mul % tab val2
dup % tab val2 val2
dup % tab val2 val2 val2
floor cvi % tab val2 val2 cell0
exch % tab val2 cell0 val2
ceiling cvi % tab val2 cell0 cell1
3 index % tab val2 cell0 cell1 tab
exch % tab val2 cell0 tab cell1
get % tab val2 cell0 y1
4 - 1 roll % val2 cell0 y1 tab
3 - 1 roll % val2 y1 tab cell0
get % val2 y1 y0
dup % val2 y1 y0 y0
3 1 roll % val2 y0 y1 y0
sub % val2 y0 ( y1 - y0 )
3 - 1 roll % y0 ( y1 - y0 ) val2
dup % y0 ( y1 - y0 ) val2 val2
floor cvi % y0 ( y1 - y0 ) val2 floor ( val2 )
sub % y0 ( y1 - y0 ) rest
mul % y0 t1
add % y
65535 div % result
} bind
*/
// This struct holds the memory block currently being write
typedef struct {
_cmsStageCLutData* Pipeline;
cmsIOHANDLER* m;
int FirstComponent;
int SecondComponent;
const char * PreMaj;
const char * PostMaj;
const char * PreMin;
const char * PostMin;
int FixWhite; // Force mapping of pure white
cmsColorSpaceSignature ColorSpace; // ColorSpace of profile
} cmsPsSamplerCargo;
static int _cmsPSActualColumn = 0 ;
// Convert to byte
static
cmsUInt8Number Word2Byte(cmsUInt16Number w)
{
return (cmsUInt8Number) floor((cmsFloat64Number) w / 257 .0 + 0 .5 );
}
// Write a cooked byte
static
void WriteByte(cmsIOHANDLER* m, cmsUInt8Number b)
{
_cmsIOPrintf(m, "%02x" , b);
_cmsPSActualColumn += 2 ;
if (_cmsPSActualColumn > MAXPSCOLS) {
_cmsIOPrintf(m, "\n" );
_cmsPSActualColumn = 0 ;
}
}
// ----------------------------------------------------------------- PostScript generation
// Removes offending carriage returns
static
char * RemoveCR(const char * txt)
{
static char Buffer[2048 ];
char * pt;
strncpy(Buffer, txt, 2047 );
Buffer[2047 ] = 0 ;
for (pt = Buffer; *pt; pt++)
if (*pt == '\n' || *pt == '\r' ) *pt = ' ' ;
return Buffer;
}
static
void EmitHeader(cmsIOHANDLER* m, const char * Title, cmsHPROFILE hProfile)
{
time_t timer;
cmsMLU *Description, *Copyright;
char DescASCII[256 ], CopyrightASCII[256 ];
time(&timer);
Description = (cmsMLU*) cmsReadTag(hProfile, cmsSigProfileDescriptionTag);
Copyright = (cmsMLU*) cmsReadTag(hProfile, cmsSigCopyrightTag);
DescASCII[0 ] = DescASCII[255 ] = 0 ;
CopyrightASCII[0 ] = CopyrightASCII[255 ] = 0 ;
if (Description != NULL) cmsMLUgetASCII(Description, cmsNoLanguage, cmsNoCountry, DescASCII, 255 );
if (Copyright != NULL) cmsMLUgetASCII(Copyright, cmsNoLanguage, cmsNoCountry, CopyrightASCII, 255 );
_cmsIOPrintf(m, "%%!PS-Adobe-3.0\n" );
_cmsIOPrintf(m, "%%\n" );
_cmsIOPrintf(m, "%% %s\n" , Title);
_cmsIOPrintf(m, "%% Source: %s\n" , RemoveCR(DescASCII));
_cmsIOPrintf(m, "%% %s\n" , RemoveCR(CopyrightASCII));
_cmsIOPrintf(m, "%% Created: %s" , ctime(&timer)); // ctime appends a \n!!!
_cmsIOPrintf(m, "%%\n" );
_cmsIOPrintf(m, "%%%%BeginResource\n" );
}
// Emits White & Black point. White point is always D50, Black point is the device
// Black point adapted to D50.
static
void EmitWhiteBlackD50(cmsIOHANDLER* m, cmsCIEXYZ* BlackPoint)
{
_cmsIOPrintf(m, "/BlackPoint [%f %f %f]\n" , BlackPoint -> X,
BlackPoint -> Y,
BlackPoint -> Z);
_cmsIOPrintf(m, "/WhitePoint [%f %f %f]\n" , cmsD50_XYZ()->X,
cmsD50_XYZ()->Y,
cmsD50_XYZ()->Z);
}
static
void EmitRangeCheck(cmsIOHANDLER* m)
{
_cmsIOPrintf(m, "dup 0.0 lt { pop 0.0 } if "
"dup 1.0 gt { pop 1.0 } if " );
}
// Does write the intent
static
void EmitIntent(cmsIOHANDLER* m, cmsUInt32Number RenderingIntent)
{
const char *intent;
switch (RenderingIntent) {
case INTENT_PERCEPTUAL: intent = "Perceptual" ; break ;
case INTENT_RELATIVE_COLORIMETRIC: intent = "RelativeColorimetric" ; break ;
case INTENT_ABSOLUTE_COLORIMETRIC: intent = "AbsoluteColorimetric" ; break ;
case INTENT_SATURATION: intent = "Saturation" ; break ;
default : intent = "Undefined" ; break ;
}
_cmsIOPrintf(m, "/RenderingIntent (%s)\n" , intent );
}
//
// Convert L* to Y
//
// Y = Yn*[ (L* + 16) / 116] ^ 3 if (L*) >= 6 / 29
// = Yn*( L* / 116) / 7.787 if (L*) < 6 / 29
//
// Lab -> XYZ, see the discussion above
static
void EmitLab2XYZ(cmsIOHANDLER* m)
{
_cmsIOPrintf(m, "/RangeABC [ 0 1 0 1 0 1]\n" );
_cmsIOPrintf(m, "/DecodeABC [\n" );
_cmsIOPrintf(m, "{100 mul 16 add 116 div } bind\n" );
_cmsIOPrintf(m, "{255 mul 128 sub 500 div } bind\n" );
_cmsIOPrintf(m, "{255 mul 128 sub 200 div } bind\n" );
_cmsIOPrintf(m, "]\n" );
_cmsIOPrintf(m, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n" );
_cmsIOPrintf(m, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n" );
_cmsIOPrintf(m, "/DecodeLMN [\n" );
_cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind\n" );
_cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n" );
_cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind\n" );
_cmsIOPrintf(m, "]\n" );
}
static
void EmitSafeGuardBegin(cmsIOHANDLER* m, const char * name)
{
_cmsIOPrintf(m, "%%LCMS2: Save previous definition of %s on the operand stack\n" , name);
_cmsIOPrintf(m, "currentdict /%s known { /%s load } { null } ifelse\n" , name, name);
}
static
void EmitSafeGuardEnd(cmsIOHANDLER* m, const char * name, int depth)
{
_cmsIOPrintf(m, "%%LCMS2: Restore previous definition of %s\n" , name);
if (depth > 1 ) {
// cycle topmost items on the stack to bring the previous definition to the front
_cmsIOPrintf(m, "%d -1 roll " , depth);
}
_cmsIOPrintf(m, "dup null eq { pop currentdict /%s undef } { /%s exch def } ifelse\n" , name, name);
}
// Outputs a table of words. It does use 16 bits
static
void Emit1Gamma(cmsIOHANDLER* m, cmsToneCurve* Table, const char * name)
{
cmsUInt32Number i;
cmsFloat64Number gamma;
if (Table == NULL) return ; // Error
if (Table ->nEntries <= 0 ) return ; // Empty table
// Suppress whole if identity
if (cmsIsToneCurveLinear(Table)) return ;
// Check if is really an exponential. If so, emit "exp"
gamma = cmsEstimateGamma(Table, 0 .001 );
if (gamma > 0 ) {
_cmsIOPrintf(m, "/%s { %g exp } bind def\n" , name, gamma);
return ;
}
EmitSafeGuardBegin(m, "lcms2gammatable" );
_cmsIOPrintf(m, "/lcms2gammatable [" );
for (i=0 ; i < Table->nEntries; i++) {
if (i % 10 == 0 )
_cmsIOPrintf(m, "\n " );
_cmsIOPrintf(m, "%d " , Table->Table16[i]);
}
_cmsIOPrintf(m, "] def\n" );
// Emit interpolation code
// PostScript code Stack
// =============== ========================
// v
_cmsIOPrintf(m, "/%s {\n " , name);
// Bounds check
EmitRangeCheck(m);
_cmsIOPrintf(m, "\n //lcms2gammatable "); // v tab
_cmsIOPrintf(m, "dup " ); // v tab tab
_cmsIOPrintf(m, "length 1 sub " ); // v tab dom
_cmsIOPrintf(m, "3 -1 roll " ); // tab dom v
_cmsIOPrintf(m, "mul " ); // tab val2
_cmsIOPrintf(m, "dup " ); // tab val2 val2
_cmsIOPrintf(m, "dup " ); // tab val2 val2 val2
_cmsIOPrintf(m, "floor cvi " ); // tab val2 val2 cell0
_cmsIOPrintf(m, "exch " ); // tab val2 cell0 val2
_cmsIOPrintf(m, "ceiling cvi " ); // tab val2 cell0 cell1
_cmsIOPrintf(m, "3 index " ); // tab val2 cell0 cell1 tab
_cmsIOPrintf(m, "exch " ); // tab val2 cell0 tab cell1
_cmsIOPrintf(m, "get\n " ); // tab val2 cell0 y1
_cmsIOPrintf(m, "4 -1 roll " ); // val2 cell0 y1 tab
_cmsIOPrintf(m, "3 -1 roll " ); // val2 y1 tab cell0
_cmsIOPrintf(m, "get " ); // val2 y1 y0
_cmsIOPrintf(m, "dup " ); // val2 y1 y0 y0
_cmsIOPrintf(m, "3 1 roll " ); // val2 y0 y1 y0
_cmsIOPrintf(m, "sub " ); // val2 y0 (y1-y0)
_cmsIOPrintf(m, "3 -1 roll " ); // y0 (y1-y0) val2
_cmsIOPrintf(m, "dup " ); // y0 (y1-y0) val2 val2
_cmsIOPrintf(m, "floor cvi " ); // y0 (y1-y0) val2 floor(val2)
_cmsIOPrintf(m, "sub " ); // y0 (y1-y0) rest
_cmsIOPrintf(m, "mul " ); // y0 t1
_cmsIOPrintf(m, "add " ); // y
_cmsIOPrintf(m, "65535 div\n" ); // result
_cmsIOPrintf(m, "} bind def\n" );
EmitSafeGuardEnd(m, "lcms2gammatable" , 1 );
}
// Compare gamma table
static
cmsBool GammaTableEquals(cmsUInt16Number* g1, cmsUInt16Number* g2, cmsUInt32Number nG1, cmsUInt32Number nG2)
{
if (nG1 != nG2) return FALSE ;
return memcmp(g1, g2, nG1 * sizeof (cmsUInt16Number)) == 0 ;
}
// Does write a set of gamma curves
static
void EmitNGamma(cmsIOHANDLER* m, cmsUInt32Number n, cmsToneCurve* g[], const char * nameprefix)
{
cmsUInt32Number i;
static char buffer[2048 ];
for ( i=0 ; i < n; i++ )
{
if (g[i] == NULL) return ; // Error
if (i > 0 && GammaTableEquals(g[i-1 ]->Table16, g[i]->Table16, g[i-1 ]->nEntries, g[i]->nEntries)) {
_cmsIOPrintf(m, "/%s%d /%s%d load def\n" , nameprefix, i, nameprefix, i-1 );
}
else {
snprintf(buffer, sizeof (buffer), "%s%d" , nameprefix, (int ) i);
buffer[sizeof (buffer)-1 ] = '\0' ;
Emit1Gamma(m, g[i], buffer);
}
}
}
// Following code dumps a LUT onto memory stream
// This is the sampler. Intended to work in SAMPLER_INSPECT mode,
// that is, the callback will be called for each knot with
//
// In[] The grid location coordinates, normalized to 0..ffff
// Out[] The Pipeline values, normalized to 0..ffff
//
// Returning a value other than 0 does terminate the sampling process
//
// Each row contains Pipeline values for all but first component. So, I
// detect row changing by keeping a copy of last value of first
// component. -1 is used to mark beginning of whole block.
static
int OutputValueSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void * Cargo)
{
cmsPsSamplerCargo* sc = (cmsPsSamplerCargo*) Cargo;
cmsUInt32Number i;
if (sc -> FixWhite) {
if (In[0 ] == 0 xFFFF) { // Only in L* = 100, ab = [-8..8]
if ((In[1 ] >= 0 x7800 && In[1 ] <= 0 x8800) &&
(In[2 ] >= 0 x7800 && In[2 ] <= 0 x8800)) {
cmsUInt16Number* Black;
cmsUInt16Number* White;
cmsUInt32Number nOutputs;
if (!_cmsEndPointsBySpace(sc ->ColorSpace, &White, &Black, &nOutputs))
return 0 ;
for (i=0 ; i < nOutputs; i++)
Out[i] = White[i];
}
}
}
// Hadle the parenthesis on rows
if (In[0 ] != sc ->FirstComponent) {
if (sc ->FirstComponent != -1 ) {
_cmsIOPrintf(sc ->m, sc ->PostMin);
sc ->SecondComponent = -1 ;
_cmsIOPrintf(sc ->m, sc ->PostMaj);
}
// Begin block
_cmsPSActualColumn = 0 ;
_cmsIOPrintf(sc ->m, sc ->PreMaj);
sc ->FirstComponent = In[0 ];
}
if (In[1 ] != sc ->SecondComponent) {
if (sc ->SecondComponent != -1 ) {
_cmsIOPrintf(sc ->m, sc ->PostMin);
}
_cmsIOPrintf(sc ->m, sc ->PreMin);
sc ->SecondComponent = In[1 ];
}
// Dump table.
for (i=0 ; i < sc -> Pipeline ->Params->nOutputs; i++) {
cmsUInt16Number wWordOut = Out[i];
cmsUInt8Number wByteOut; // Value as byte
// We always deal with Lab4
wByteOut = Word2Byte(wWordOut);
WriteByte(sc -> m, wByteOut);
}
return 1 ;
}
// Writes a Pipeline on memstream. Could be 8 or 16 bits based
static
void WriteCLUT(cmsIOHANDLER* m, cmsStage* mpe, const char * PreMaj,
const char * PostMaj,
const char * PreMin,
const char * PostMin,
int FixWhite,
cmsColorSpaceSignature ColorSpace)
{
cmsUInt32Number i;
cmsPsSamplerCargo sc;
sc.FirstComponent = -1 ;
sc.SecondComponent = -1 ;
sc.Pipeline = (_cmsStageCLutData *) mpe ->Data;
sc.m = m;
sc.PreMaj = PreMaj;
sc.PostMaj= PostMaj;
sc.PreMin = PreMin;
sc.PostMin = PostMin;
sc.FixWhite = FixWhite;
sc.ColorSpace = ColorSpace;
_cmsIOPrintf(m, "[" );
for (i=0 ; i < sc.Pipeline->Params->nInputs; i++)
_cmsIOPrintf(m, " %d " , sc.Pipeline->Params->nSamples[i]);
_cmsIOPrintf(m, " [\n" );
cmsStageSampleCLut16bit(mpe, OutputValueSampler, (void *) &sc, SAMPLER_INSPECT);
_cmsIOPrintf(m, PostMin);
_cmsIOPrintf(m, PostMaj);
_cmsIOPrintf(m, "] " );
}
// Dumps CIEBasedA Color Space Array
static
int EmitCIEBasedA(cmsIOHANDLER* m, cmsToneCurve* Curve, cmsCIEXYZ* BlackPoint)
{
_cmsIOPrintf(m, "[ /CIEBasedA\n" );
_cmsIOPrintf(m, " <<\n" );
EmitSafeGuardBegin(m, "lcms2gammaproc" );
Emit1Gamma(m, Curve, "lcms2gammaproc" );
_cmsIOPrintf(m, "/DecodeA /lcms2gammaproc load\n" );
EmitSafeGuardEnd(m, "lcms2gammaproc" , 3 );
_cmsIOPrintf(m, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n" );
_cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n" );
EmitWhiteBlackD50(m, BlackPoint);
EmitIntent(m, INTENT_PERCEPTUAL);
_cmsIOPrintf(m, ">>\n" );
_cmsIOPrintf(m, "]\n" );
return 1 ;
}
// Dumps CIEBasedABC Color Space Array
static
int EmitCIEBasedABC(cmsIOHANDLER* m, cmsFloat64Number* Matrix, cmsToneCurve** CurveSet, cmsCIEXYZ* BlackPoint)
{
int i;
_cmsIOPrintf(m, "[ /CIEBasedABC\n" );
_cmsIOPrintf(m, "<<\n" );
EmitSafeGuardBegin(m, "lcms2gammaproc0" );
EmitSafeGuardBegin(m, "lcms2gammaproc1" );
EmitSafeGuardBegin(m, "lcms2gammaproc2" );
EmitNGamma(m, 3 , CurveSet, "lcms2gammaproc" );
_cmsIOPrintf(m, "/DecodeABC [\n" );
_cmsIOPrintf(m, " /lcms2gammaproc0 load\n" );
_cmsIOPrintf(m, " /lcms2gammaproc1 load\n" );
_cmsIOPrintf(m, " /lcms2gammaproc2 load\n" );
_cmsIOPrintf(m, "]\n" );
EmitSafeGuardEnd(m, "lcms2gammaproc2" , 3 );
EmitSafeGuardEnd(m, "lcms2gammaproc1" , 3 );
EmitSafeGuardEnd(m, "lcms2gammaproc0" , 3 );
_cmsIOPrintf(m, "/MatrixABC [ " );
for ( i=0 ; i < 3 ; i++ ) {
_cmsIOPrintf(m, "%.6f %.6f %.6f " , Matrix[i + 3 *0 ],
Matrix[i + 3 *1 ],
Matrix[i + 3 *2 ]);
}
_cmsIOPrintf(m, "]\n" );
_cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n" );
EmitWhiteBlackD50(m, BlackPoint);
EmitIntent(m, INTENT_PERCEPTUAL);
_cmsIOPrintf(m, ">>\n" );
_cmsIOPrintf(m, "]\n" );
return 1 ;
}
static
int EmitCIEBasedDEF(cmsIOHANDLER* m, cmsPipeline* Pipeline, cmsUInt32Number Intent, cmsCIEXYZ* BlackPoint)
{
const char * PreMaj;
const char * PostMaj;
const char * PreMin, * PostMin;
cmsStage* mpe;
int i, numchans;
static char buffer[2048 ];
mpe = Pipeline->Elements;
switch (cmsStageInputChannels(mpe)) {
case 3 :
_cmsIOPrintf(m, "[ /CIEBasedDEF\n" );
PreMaj = "<" ;
PostMaj = ">\n" ;
PreMin = PostMin = "" ;
break ;
case 4 :
_cmsIOPrintf(m, "[ /CIEBasedDEFG\n" );
PreMaj = "[" ;
PostMaj = "]\n" ;
PreMin = "<" ;
PostMin = ">\n" ;
break ;
default :
return 0 ;
}
_cmsIOPrintf(m, "<<\n" );
if (cmsStageType(mpe) == cmsSigCurveSetElemType) {
numchans = (int ) cmsStageOutputChannels(mpe);
for (i = 0 ; i < numchans; ++i) {
snprintf(buffer, sizeof (buffer), "lcms2gammaproc%d" , i);
buffer[sizeof (buffer) - 1 ] = '\0' ;
EmitSafeGuardBegin(m, buffer);
}
EmitNGamma(m, cmsStageOutputChannels(mpe), _cmsStageGetPtrToCurveSet(mpe), "lcms2gammaproc" );
_cmsIOPrintf(m, "/DecodeDEF [\n" );
for (i = 0 ; i < numchans; ++i) {
snprintf(buffer, sizeof (buffer), " /lcms2gammaproc%d load\n" , i);
buffer[sizeof (buffer) - 1 ] = '\0' ;
_cmsIOPrintf(m, buffer);
}
_cmsIOPrintf(m, "]\n" );
for (i = numchans - 1 ; i >= 0 ; --i) {
snprintf(buffer, sizeof (buffer), "lcms2gammaproc%d" , i);
buffer[sizeof (buffer) - 1 ] = '\0' ;
EmitSafeGuardEnd(m, buffer, 3 );
}
mpe = mpe->Next;
}
if (cmsStageType(mpe) == cmsSigCLutElemType) {
_cmsIOPrintf(m, "/Table " );
WriteCLUT(m, mpe, PreMaj, PostMaj, PreMin, PostMin, FALSE , (cmsColorSpaceSignature)0 );
_cmsIOPrintf(m, "]\n" );
}
EmitLab2XYZ(m);
EmitWhiteBlackD50(m, BlackPoint);
EmitIntent(m, Intent);
_cmsIOPrintf(m, " >>\n" );
_cmsIOPrintf(m, "]\n" );
return 1 ;
}
// Generates a curve from a gray profile
static
cmsToneCurve* ExtractGray2Y(cmsContext ContextID, cmsHPROFILE hProfile, cmsUInt32Number Intent)
{
cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256 , NULL);
cmsHPROFILE hXYZ = cmsCreateXYZProfile();
cmsHTRANSFORM xform = cmsCreateTransformTHR(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE);
int i;
if (Out != NULL && xform != NULL) {
for (i=0 ; i < 256 ; i++) {
cmsUInt8Number Gray = (cmsUInt8Number) i;
cmsCIEXYZ XYZ;
cmsDoTransform(xform, &Gray, &XYZ, 1 );
Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535 .0 );
}
}
if (xform) cmsDeleteTransform(xform);
if (hXYZ) cmsCloseProfile(hXYZ);
return Out;
}
// Because PostScript has only 8 bits in /Table, we should use
// a more perceptually uniform space... I do choose Lab.
static
int WriteInputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
{
cmsHPROFILE hLab;
cmsHTRANSFORM xform;
cmsUInt32Number nChannels;
cmsUInt32Number InputFormat;
int rc;
cmsHPROFILE Profiles[2 ];
cmsCIEXYZ BlackPointAdaptedToD50;
// Does create a device-link based transform.
// The DeviceLink is next dumped as working CSA.
InputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2 , FALSE );
nChannels = T_CHANNELS(InputFormat);
cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0 );
// Adjust output to Lab4
hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
Profiles[0 ] = hProfile;
Profiles[1 ] = hLab;
xform = cmsCreateMultiprofileTransform(Profiles, 2 , InputFormat, TYPE_Lab_DBL, Intent, 0 );
cmsCloseProfile(hLab);
if (xform == NULL) {
cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Profile -> Lab" );
return 0 ;
}
// Only 1, 3 and 4 channels are allowed
switch (nChannels) {
case 1 : {
cmsToneCurve* Gray2Y = ExtractGray2Y(m ->ContextID, hProfile, Intent);
EmitCIEBasedA(m, Gray2Y, &BlackPointAdaptedToD50);
cmsFreeToneCurve(Gray2Y);
}
break ;
case 3 :
case 4 : {
cmsUInt32Number OutFrm = TYPE_Lab_16;
cmsPipeline* DeviceLink;
_cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
DeviceLink = cmsPipelineDup(v ->Lut);
if (DeviceLink == NULL) return 0 ;
dwFlags |= cmsFLAGS_FORCE_CLUT;
_cmsOptimizePipeline(m->ContextID, &DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags);
rc = EmitCIEBasedDEF(m, DeviceLink, Intent, &BlackPointAdaptedToD50);
cmsPipelineFree(DeviceLink);
if (rc == 0 ) return 0 ;
}
break ;
default :
cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Only 3, 4 channels are supported for CSA. This profile has %d channels." , nChannels);
return 0 ;
}
cmsDeleteTransform(xform);
return 1 ;
}
static
cmsFloat64Number* GetPtrToMatrix(const cmsStage* mpe)
{
_cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data;
return Data -> Double ;
}
// Does create CSA based on matrix-shaper. Allowed types are gray and RGB based
static
int WriteInputMatrixShaper(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper)
{
cmsColorSpaceSignature ColorSpace;
int rc;
cmsCIEXYZ BlackPointAdaptedToD50;
ColorSpace = cmsGetColorSpace(hProfile);
cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0 );
if (ColorSpace == cmsSigGrayData) {
cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper);
rc = EmitCIEBasedA(m, ShaperCurve[0 ], &BlackPointAdaptedToD50);
}
else
if (ColorSpace == cmsSigRgbData) {
cmsMAT3 Mat;
int i, j;
memmove(&Mat, GetPtrToMatrix(Matrix), sizeof (Mat));
for (i = 0 ; i < 3 ; i++)
for (j = 0 ; j < 3 ; j++)
Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ;
rc = EmitCIEBasedABC(m, (cmsFloat64Number *)&Mat,
_cmsStageGetPtrToCurveSet(Shaper),
&BlackPointAdaptedToD50);
}
else {
cmsSignalError(m->ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace." );
return 0 ;
}
return rc;
}
// Creates a PostScript color list from a named profile data.
// This is a HP extension, and it works in Lab instead of XYZ
static
int WriteNamedColorCSA(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent)
{
cmsHTRANSFORM xform;
cmsHPROFILE hLab;
cmsUInt32Number i, nColors;
char ColorName[cmsMAX_PATH];
cmsNAMEDCOLORLIST* NamedColorList;
hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0 );
if (xform == NULL) return 0 ;
NamedColorList = cmsGetNamedColorList(xform);
if (NamedColorList == NULL) return 0 ;
_cmsIOPrintf(m, "<<\n" );
_cmsIOPrintf(m, "(colorlistcomment) (%s)\n" , "Named color CSA" );
_cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n" );
_cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n" );
nColors = cmsNamedColorCount(NamedColorList);
for (i=0 ; i < nColors; i++) {
cmsUInt16Number In[1 ];
cmsCIELab Lab;
In[0 ] = (cmsUInt16Number) i;
if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
continue ;
cmsDoTransform(xform, In, &Lab, 1 );
_cmsIOPrintf(m, " (%s) [ %.3f %.3f %.3f ]\n" , ColorName, Lab.L, Lab.a, Lab.b);
}
_cmsIOPrintf(m, ">>\n" );
cmsDeleteTransform(xform);
cmsCloseProfile(hLab);
return 1 ;
}
// Does create a Color Space Array on XYZ colorspace for PostScript usage
static
cmsUInt32Number GenerateCSA(cmsContext ContextID,
cmsHPROFILE hProfile,
cmsUInt32Number Intent,
cmsUInt32Number dwFlags,
cmsIOHANDLER* mem)
{
cmsUInt32Number dwBytesUsed;
cmsPipeline* lut = NULL;
cmsStage* Matrix, *Shaper;
// Is a named color profile?
if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
if (!WriteNamedColorCSA(mem, hProfile, Intent)) goto Error;
}
else {
// Any profile class are allowed (including devicelink), but
// output (PCS) colorspace must be XYZ or Lab
cmsColorSpaceSignature ColorSpace = cmsGetPCS(hProfile);
if (ColorSpace != cmsSigXYZData &&
ColorSpace != cmsSigLabData) {
cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space" );
goto Error;
}
// Read the lut with all necessary conversion stages
lut = _cmsReadInputLUT(hProfile, Intent);
if (lut == NULL) goto Error;
// Tone curves + matrix can be implemented without any LUT
if (cmsPipelineCheckAndRetreiveStages(lut, 2 , cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) {
if (!WriteInputMatrixShaper(mem, hProfile, Matrix, Shaper)) goto Error;
}
else {
// We need a LUT for the rest
if (!WriteInputLUT(mem, hProfile, Intent, dwFlags)) goto Error;
}
}
// Done, keep memory usage
dwBytesUsed = mem ->UsedSpace;
// Get rid of LUT
if (lut != NULL) cmsPipelineFree(lut);
// Finally, return used byte count
return dwBytesUsed;
Error:
if (lut != NULL) cmsPipelineFree(lut);
return 0 ;
}
// ------------------------------------------------------ Color Rendering Dictionary (CRD)
/*
Black point compensation plus chromatic adaptation :
Step 1 - Chromatic adaptation
= = = = = = = = = = = = = = = = = = = = = = = = = = = = =
WPout
X = - - - - - - - PQR
Wpin
Step 2 - Black point compensation
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
( WPout - BPout ) * X - WPout * ( BPin - BPout )
out = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
WPout - BPin
Algorithm discussion
= = = = = = = = = = = = = = = = = = = =
TransformPQR ( WPin , BPin , WPout , BPout , PQR )
Wpin , etc = { Xws Yws Zws Pws Qws Rws }
Algorithm Stack 0 . . . n
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
PQR BPout WPout BPin WPin
4 index 3 get WPin PQR BPout WPout BPin WPin
div ( PQR / WPin ) BPout WPout BPin WPin
2 index 3 get WPout ( PQR / WPin ) BPout WPout BPin WPin
mult WPout * ( PQR / WPin ) BPout WPout BPin WPin
2 index 3 get WPout WPout * ( PQR / WPin ) BPout WPout BPin WPin
2 index 3 get BPout WPout WPout * ( PQR / WPin ) BPout WPout BPin WPin
sub ( WPout - BPout ) WPout * ( PQR / WPin ) BPout WPout BPin WPin
mult ( WPout - BPout ) * WPout * ( PQR / WPin ) BPout WPout BPin WPin
2 index 3 get WPout ( BPout - WPout ) * WPout * ( PQR / WPin ) BPout WPout BPin WPin
4 index 3 get BPin WPout ( BPout - WPout ) * WPout * ( PQR / WPin ) BPout WPout BPin WPin
3 index 3 get BPout BPin WPout ( BPout - WPout ) * WPout * ( PQR / WPin ) BPout WPout BPin WPin
sub ( BPin - BPout ) WPout ( BPout - WPout ) * WPout * ( PQR / WPin ) BPout WPout BPin WPin
mult ( BPin - BPout ) * WPout ( BPout - WPout ) * WPout * ( PQR / WPin ) BPout WPout BPin WPin
sub ( BPout - WPout ) * WPout * ( PQR / WPin ) - ( BPin - BPout ) * WPout BPout WPout BPin WPin
3 index 3 get BPin ( BPout - WPout ) * WPout * ( PQR / WPin ) - ( BPin - BPout ) * WPout BPout WPout BPin WPin
3 index 3 get WPout BPin ( BPout - WPout ) * WPout * ( PQR / WPin ) - ( BPin - BPout ) * WPout BPout WPout BPin WPin
exch
sub ( WPout - BPin ) ( BPout - WPout ) * WPout * ( PQR / WPin ) - ( BPin - BPout ) * WPout BPout WPout BPin WPin
div
exch pop
exch pop
exch pop
exch pop
*/
static
void EmitPQRStage(cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute)
{
if (lIsAbsolute) {
// For absolute colorimetric intent, encode back to relative
// and generate a relative Pipeline
// Relative encoding is obtained across XYZpcs*(D50/WhitePoint)
cmsCIEXYZ White;
_cmsReadMediaWhitePoint(&White, hProfile);
_cmsIOPrintf(m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n" );
_cmsIOPrintf(m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n" );
_cmsIOPrintf(m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n"
"/TransformPQR [\n"
"{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n"
"{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n"
"{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n" ,
White.X, White.Y, White.Z);
return ;
}
_cmsIOPrintf(m,"%% Bradford Cone Space\n"
"/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n" );
_cmsIOPrintf(m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n" );
// No BPC
if (!DoBPC) {
_cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space\n"
"/TransformPQR [\n"
"{exch pop exch 3 get mul exch pop exch 3 get div} bind\n"
"{exch pop exch 4 get mul exch pop exch 4 get div} bind\n"
"{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n" );
} else {
// BPC
_cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n"
"/TransformPQR [\n" );
_cmsIOPrintf(m, "{4 index 3 get div 2 index 3 get mul "
"2 index 3 get 2 index 3 get sub mul "
"2 index 3 get 4 index 3 get 3 index 3 get sub mul sub "
"3 index 3 get 3 index 3 get exch sub div "
"exch pop exch pop exch pop exch pop } bind\n" );
_cmsIOPrintf(m, "{4 index 4 get div 2 index 4 get mul "
"2 index 4 get 2 index 4 get sub mul "
"2 index 4 get 4 index 4 get 3 index 4 get sub mul sub "
"3 index 4 get 3 index 4 get exch sub div "
"exch pop exch pop exch pop exch pop } bind\n" );
_cmsIOPrintf(m, "{4 index 5 get div 2 index 5 get mul "
"2 index 5 get 2 index 5 get sub mul "
"2 index 5 get 4 index 5 get 3 index 5 get sub mul sub "
"3 index 5 get 3 index 5 get exch sub div "
"exch pop exch pop exch pop exch pop } bind\n]\n" );
}
}
static
void EmitXYZ2Lab(cmsIOHANDLER* m)
{
_cmsIOPrintf(m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n" );
_cmsIOPrintf(m, "/EncodeLMN [\n" );
_cmsIOPrintf(m, "{ 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n" );
_cmsIOPrintf(m, "{ 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n" );
_cmsIOPrintf(m, "{ 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n" );
_cmsIOPrintf(m, "]\n" );
_cmsIOPrintf(m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n" );
_cmsIOPrintf(m, "/EncodeABC [\n" );
_cmsIOPrintf(m, "{ 116 mul 16 sub 100 div } bind\n" );
_cmsIOPrintf(m, "{ 500 mul 128 add 256 div } bind\n" );
_cmsIOPrintf(m, "{ 200 mul 128 add 256 div } bind\n" );
_cmsIOPrintf(m, "]\n" );
}
// Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces
// I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted
// space on 3D CLUT, but since space seems not to be a problem here, 33 points
// would give a reasonable accuracy. Note also that CRD tables must operate in
// 8 bits.
static
int WriteOutputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
{
cmsHPROFILE hLab;
cmsHTRANSFORM xform;
cmsUInt32Number i, nChannels;
cmsUInt32Number OutputFormat;
_cmsTRANSFORM* v;
cmsPipeline* DeviceLink;
cmsHPROFILE Profiles[3 ];
cmsCIEXYZ BlackPointAdaptedToD50;
cmsBool lDoBPC = (cmsBool) (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);
cmsBool lFixWhite = (cmsBool) !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP);
cmsUInt32Number InFrm = TYPE_Lab_16;
cmsUInt32Number RelativeEncodingIntent;
cmsColorSpaceSignature ColorSpace;
hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
if (hLab == NULL) return 0 ;
OutputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2 , FALSE );
nChannels = T_CHANNELS(OutputFormat);
ColorSpace = cmsGetColorSpace(hProfile);
// For absolute colorimetric, the LUT is encoded as relative in order to preserve precision.
RelativeEncodingIntent = Intent;
if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC)
RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC;
// Use V4 Lab always
Profiles[0 ] = hLab;
Profiles[1 ] = hProfile;
xform = cmsCreateMultiprofileTransformTHR(m ->ContextID,
Profiles, 2 , TYPE_Lab_DBL,
OutputFormat, RelativeEncodingIntent, 0 );
cmsCloseProfile(hLab);
if (xform == NULL) {
cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation" );
return 0 ;
}
// Get a copy of the internal devicelink
v = (_cmsTRANSFORM*) xform;
DeviceLink = cmsPipelineDup(v ->Lut);
if (DeviceLink == NULL) return 0 ;
// We need a CLUT
dwFlags |= cmsFLAGS_FORCE_CLUT;
_cmsOptimizePipeline(m->ContextID, &DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags);
_cmsIOPrintf(m, "<<\n" );
_cmsIOPrintf(m, "/ColorRenderingType 1\n" );
cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0 );
// Emit headers, etc.
EmitWhiteBlackD50(m, &BlackPointAdaptedToD50);
EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC);
EmitXYZ2Lab(m);
// FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab
// does map a=b=0 not falling into any specific node. Since range a,b goes -128..127,
// zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to
// zero. This would sacrifice a bit of highlights, but failure to do so would cause
// scum dot. Ouch.
if (Intent == INTENT_ABSOLUTE_COLORIMETRIC)
lFixWhite = FALSE ;
_cmsIOPrintf(m, "/RenderTable " );
WriteCLUT(m, cmsPipelineGetPtrToFirstStage(DeviceLink), "<" , ">\n" , "" , "" , lFixWhite, ColorSpace);
_cmsIOPrintf(m, " %d {} bind " , nChannels);
for (i=1 ; i < nChannels; i++)
_cmsIOPrintf(m, "dup " );
_cmsIOPrintf(m, "]\n" );
EmitIntent(m, Intent);
_cmsIOPrintf(m, ">>\n" );
if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
_cmsIOPrintf(m, "/Current exch /ColorRendering defineresource pop\n" );
}
cmsPipelineFree(DeviceLink);
cmsDeleteTransform(xform);
return 1 ;
}
// Builds a ASCII string containing colorant list in 0..1.0 range
static
void BuildColorantList(char *Colorant, cmsUInt32Number nColorant, cmsUInt16Number Out[])
{
char Buff[32 ];
cmsUInt32Number j;
Colorant[0 ] = 0 ;
if (nColorant > cmsMAXCHANNELS)
nColorant = cmsMAXCHANNELS;
for (j = 0 ; j < nColorant; j++) {
snprintf(Buff, 31 , "%.3f" , Out[j] / 65535 .0 );
Buff[31 ] = 0 ;
strcat(Colorant, Buff);
if (j < nColorant - 1 )
strcat(Colorant, " " );
}
}
// Creates a PostScript color list from a named profile data.
// This is a HP extension.
static
int WriteNamedColorCRD(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
{
cmsHTRANSFORM xform;
cmsUInt32Number i, nColors, nColorant;
cmsUInt32Number OutputFormat;
char ColorName[cmsMAX_PATH];
char Colorant[512 ];
cmsNAMEDCOLORLIST* NamedColorList;
OutputFormat = cmsFormatterForColorspaceOfProfile(hNamedColor, 2 , FALSE );
nColorant = T_CHANNELS(OutputFormat);
xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags);
if (xform == NULL) return 0 ;
NamedColorList = cmsGetNamedColorList(xform);
if (NamedColorList == NULL) return 0 ;
_cmsIOPrintf(m, "<<\n" );
_cmsIOPrintf(m, "(colorlistcomment) (%s) \n" , "Named profile" );
_cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n" );
_cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n" );
nColors = cmsNamedColorCount(NamedColorList);
for (i=0 ; i < nColors; i++) {
cmsUInt16Number In[1 ];
cmsUInt16Number Out[cmsMAXCHANNELS];
In[0 ] = (cmsUInt16Number) i;
if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
continue ;
cmsDoTransform(xform, In, Out, 1 );
BuildColorantList(Colorant, nColorant, Out);
_cmsIOPrintf(m, " (%s) [ %s ]\n" , ColorName, Colorant);
}
_cmsIOPrintf(m, " >>" );
if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
_cmsIOPrintf(m, " /Current exch /HPSpotTable defineresource pop\n" );
}
cmsDeleteTransform(xform);
return 1 ;
}
// This one does create a Color Rendering Dictionary.
// CRD are always LUT-Based, no matter if profile is
// implemented as matrix-shaper.
static
cmsUInt32Number GenerateCRD(cmsContext ContextID,
cmsHPROFILE hProfile,
cmsUInt32Number Intent, cmsUInt32Number dwFlags,
cmsIOHANDLER* mem)
{
cmsUInt32Number dwBytesUsed;
if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
EmitHeader(mem, "Color Rendering Dictionary (CRD)" , hProfile);
}
// Is a named color profile?
if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
if (!WriteNamedColorCRD(mem, hProfile, Intent, dwFlags)) {
return 0 ;
}
}
else {
// CRD are always implemented as LUT
if (!WriteOutputLUT(mem, hProfile, Intent, dwFlags)) {
return 0 ;
}
}
if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
_cmsIOPrintf(mem, "%%%%EndResource\n" );
_cmsIOPrintf(mem, "\n%% CRD End\n" );
}
// Done, keep memory usage
dwBytesUsed = mem ->UsedSpace;
// Finally, return used byte count
return dwBytesUsed;
cmsUNUSED_PARAMETER(ContextID);
}
cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID,
cmsPSResourceType Type,
cmsHPROFILE hProfile,
cmsUInt32Number Intent,
cmsUInt32Number dwFlags,
cmsIOHANDLER* io)
{
cmsUInt32Number rc;
switch (Type) {
case cmsPS_RESOURCE_CSA:
rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io);
break ;
default :
case cmsPS_RESOURCE_CRD:
rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io);
break ;
}
return rc;
}
cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID,
cmsHPROFILE hProfile,
cmsUInt32Number Intent, cmsUInt32Number dwFlags,
void * Buffer, cmsUInt32Number dwBufferLen)
{
cmsIOHANDLER* mem;
cmsUInt32Number dwBytesUsed;
// Set up the serialization engine
if (Buffer == NULL)
mem = cmsOpenIOhandlerFromNULL(ContextID);
else
mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w" );
if (!mem) return 0 ;
dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem);
// Get rid of memory stream
cmsCloseIOhandler(mem);
return dwBytesUsed;
}
// Does create a Color Space Array on XYZ colorspace for PostScript usage
cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID,
cmsHPROFILE hProfile,
cmsUInt32Number Intent,
cmsUInt32Number dwFlags,
void * Buffer,
cmsUInt32Number dwBufferLen)
{
cmsIOHANDLER* mem;
cmsUInt32Number dwBytesUsed;
if (Buffer == NULL)
mem = cmsOpenIOhandlerFromNULL(ContextID);
else
mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w" );
if (!mem) return 0 ;
dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem);
// Get rid of memory stream
cmsCloseIOhandler(mem);
return dwBytesUsed;
}
Messung V0.5 in Prozent C=91 H=88 G=89
¤ Dauer der Verarbeitung: 0.66 Sekunden
¤
*© Formatika GbR, Deutschland