blob: 2d7a01476ec02cfb15551e966a92d48a982929d4 [file] [log] [blame]
/*
* 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 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"
/*
This module provides conversion stages for handling intents.
The chain of evaluation in a transform is:
PCS1 PCS2 PCS3 PCS4
|From | |From | |Conversion | |Preview | |Gamut | |Conversion | |To | |To |
|Input|->|Device|->|Stage 1 |->|handling|->|Checking|->|Stage 2 |->|Device|->|output |
-------- ------- ------------- --------- ---------- ------------- ------- ---------
AToB0 prew0 gamut BToA0
Formatting LUT Adjusting LUT LUT Adjusting LUT Formatting
Intent Intent 1 intent intent Intent 2 Intent
Some of these LUT may be missing
There are two intents involved here, the intent of the transform itself, and the
intent the proof is being done, if is the case. Since the first intent is to be
applied to preview, is the proofing intent. The second intent identifies the
transform intent. Input data of any stage is taked as relative colorimetric
always.
NOTES: V4 states than perceptual & saturation intents between mixed v2 & v4 profiles should
scale PCS from a black point equal to ZERO in v2 profiles to the reference media black of
perceptual v4 PCS. Since I found many v2 profiles to be using a perceptual intent with black
point not zero at all, I'm implementing that as a black point compensation from whatever
black from perceptal intent to the reference media black for v4 profiles.
*/
int cdecl cmsChooseCnvrt(int Absolute,
int Phase1, LPcmsCIEXYZ BlackPointIn,
LPcmsCIEXYZ WhitePointIn,
LPcmsCIEXYZ IlluminantIn,
LPMAT3 ChromaticAdaptationMatrixIn,
int Phase2, LPcmsCIEXYZ BlackPointOut,
LPcmsCIEXYZ WhitePointOut,
LPcmsCIEXYZ IlluminantOut,
LPMAT3 ChromaticAdaptationMatrixOut,
int DoBlackPointCompensation,
double AdaptationState,
_cmsADJFN *fn1,
LPWMAT3 wm, LPWVEC3 wof);
// -------------------------------------------------------------------------
// D50 - Widely used
LCMSAPI LPcmsCIEXYZ LCMSEXPORT cmsD50_XYZ(void)
{
static cmsCIEXYZ D50XYZ = {D50X, D50Y, D50Z};
return &D50XYZ;
}
LCMSAPI LPcmsCIExyY LCMSEXPORT cmsD50_xyY(void)
{
static cmsCIExyY D50xyY;
cmsXYZ2xyY(&D50xyY, cmsD50_XYZ());
return &D50xyY;
}
// ---------------- From LUT to LUT --------------------------
// Calculate m, offset Relativ -> Absolute undoing any chromatic
// adaptation done by the profile.
#ifdef _MSC_VER
#pragma warning(disable : 4100 4505)
#endif
// join scalings to obtain:
// relative input to absolute and then to relative output
static
void Rel2RelStepAbsCoefs(double AdaptationState,
LPcmsCIEXYZ BlackPointIn,
LPcmsCIEXYZ WhitePointIn,
LPcmsCIEXYZ IlluminantIn,
LPMAT3 ChromaticAdaptationMatrixIn,
LPcmsCIEXYZ BlackPointOut,
LPcmsCIEXYZ WhitePointOut,
LPcmsCIEXYZ IlluminantOut,
LPMAT3 ChromaticAdaptationMatrixOut,
LPMAT3 m, LPVEC3 of)
{
VEC3 WtPtIn, WtPtInAdapted;
VEC3 WtPtOut, WtPtOutAdapted;
MAT3 Scale, m1, m2, m3;
VEC3init(&WtPtIn, WhitePointIn->X, WhitePointIn->Y, WhitePointIn->Z);
MAT3eval(&WtPtInAdapted, ChromaticAdaptationMatrixIn, &WtPtIn);
VEC3init(&WtPtOut, WhitePointOut->X, WhitePointOut->Y, WhitePointOut->Z);
MAT3eval(&WtPtOutAdapted, ChromaticAdaptationMatrixOut, &WtPtOut);
VEC3init(&Scale.v[0], WtPtInAdapted.n[0] / WtPtOutAdapted.n[0], 0, 0);
VEC3init(&Scale.v[1], 0, WtPtInAdapted.n[1] / WtPtOutAdapted.n[1], 0);
VEC3init(&Scale.v[2], 0, 0, WtPtInAdapted.n[2] / WtPtOutAdapted.n[2]);
// Adaptation state
if (AdaptationState == 1.0) {
// Observer is fully adapted. Keep chromatic adaptation
CopyMemory(m, &Scale, sizeof(MAT3));
}
else {
// Observer is not adapted, undo the chromatic adaptation
m1 = *ChromaticAdaptationMatrixIn;
MAT3inverse(&m1, &m2);
MAT3per(&m3, &m2, &Scale);
MAT3per(m, &m3, ChromaticAdaptationMatrixOut);
}
VEC3init(of, 0.0, 0.0, 0.0);
}
// The (in)famous black point compensation. Right now implemented as
// a linear scaling in XYZ
static
void ComputeBlackPointCompensationFactors(LPcmsCIEXYZ BlackPointIn,
LPcmsCIEXYZ WhitePointIn,
LPcmsCIEXYZ IlluminantIn,
LPcmsCIEXYZ BlackPointOut,
LPcmsCIEXYZ WhitePointOut,
LPcmsCIEXYZ IlluminantOut,
LPMAT3 m, LPVEC3 of)
{
cmsCIEXYZ RelativeBlackPointIn, RelativeBlackPointOut;
double ax, ay, az, bx, by, bz, tx, ty, tz;
// At first, convert both black points to relative.
cmsAdaptToIlluminant(&RelativeBlackPointIn, WhitePointIn, IlluminantIn, BlackPointIn);
cmsAdaptToIlluminant(&RelativeBlackPointOut, WhitePointOut, IlluminantOut, BlackPointOut);
// Now we need to compute a matrix plus an offset m and of such of
// [m]*bpin + off = bpout
// [m]*D50 + off = D50
//
// This is a linear scaling in the form ax+b, where
// a = (bpout - D50) / (bpin - D50)
// b = - D50* (bpout - bpin) / (bpin - D50)
tx = RelativeBlackPointIn.X - IlluminantIn ->X;
ty = RelativeBlackPointIn.Y - IlluminantIn ->Y;
tz = RelativeBlackPointIn.Z - IlluminantIn ->Z;
ax = (RelativeBlackPointOut.X - IlluminantOut ->X) / tx;
ay = (RelativeBlackPointOut.Y - IlluminantOut ->Y) / ty;
az = (RelativeBlackPointOut.Z - IlluminantOut ->Z) / tz;
bx = - IlluminantOut -> X * (RelativeBlackPointOut.X - RelativeBlackPointIn.X) / tx;
by = - IlluminantOut -> Y * (RelativeBlackPointOut.Y - RelativeBlackPointIn.Y) / ty;
bz = - IlluminantOut -> Z * (RelativeBlackPointOut.Z - RelativeBlackPointIn.Z) / tz;
MAT3identity(m);
m->v[VX].n[0] = ax;
m->v[VY].n[1] = ay;
m->v[VZ].n[2] = az;
VEC3init(of, bx, by, bz);
}
// Return TRUE if both m and of are empy -- "m" being identity and "of" being 0
static
LCMSBOOL IdentityParameters(LPWMAT3 m, LPWVEC3 of)
{
WVEC3 wv0;
VEC3initF(&wv0, 0, 0, 0);
if (!MAT3isIdentity(m, 0.00001)) return FALSE;
if (!VEC3equal(of, &wv0, 0.00001)) return FALSE;
return TRUE;
}
// ----------------------------------------- Inter PCS conversions
// XYZ to XYZ linear scaling. Aso used on Black point compensation
static
void XYZ2XYZ(WORD In[], WORD Out[], LPWMAT3 m, LPWVEC3 of)
{
WVEC3 a, r;
a.n[0] = In[0] << 1;
a.n[1] = In[1] << 1;
a.n[2] = In[2] << 1;
MAT3evalW(&r, m, &a);
Out[0] = _cmsClampWord((r.n[VX] + of->n[VX]) >> 1);
Out[1] = _cmsClampWord((r.n[VY] + of->n[VY]) >> 1);
Out[2] = _cmsClampWord((r.n[VZ] + of->n[VZ]) >> 1);
}
// XYZ to Lab, scaling first
static
void XYZ2Lab(WORD In[], WORD Out[], LPWMAT3 m, LPWVEC3 of)
{
WORD XYZ[3];
XYZ2XYZ(In, XYZ, m, of);
cmsXYZ2LabEncoded(XYZ, Out);
}
// Lab to XYZ, then scalling
static
void Lab2XYZ(WORD In[], WORD Out[], LPWMAT3 m, LPWVEC3 of)
{
WORD XYZ[3];
cmsLab2XYZEncoded(In, XYZ);
XYZ2XYZ(XYZ, Out, m, of);
}
// Lab to XYZ, scalling and then, back to Lab
static
void Lab2XYZ2Lab(WORD In[], WORD Out[], LPWMAT3 m, LPWVEC3 of)
{
WORD XYZ[3], XYZ2[3];
cmsLab2XYZEncoded(In, XYZ);
XYZ2XYZ(XYZ, XYZ2, m, of);
cmsXYZ2LabEncoded(XYZ2, Out);
}
// ------------------------------------------------------------------
// Dispatcher for XYZ Relative LUT
static
int FromXYZRelLUT(int Absolute,
LPcmsCIEXYZ BlackPointIn,
LPcmsCIEXYZ WhitePointIn,
LPcmsCIEXYZ IlluminantIn,
LPMAT3 ChromaticAdaptationMatrixIn,
int Phase2, LPcmsCIEXYZ BlackPointOut,
LPcmsCIEXYZ WhitePointOut,
LPcmsCIEXYZ IlluminantOut,
LPMAT3 ChromaticAdaptationMatrixOut,
int DoBlackPointCompensation,
double AdaptationState,
_cmsADJFN *fn1,
LPMAT3 m, LPVEC3 of)
{
switch (Phase2) {
// From relative XYZ to Relative XYZ.
case XYZRel:
if (Absolute)
{
// From input relative to absolute, and then
// back to output relative
Rel2RelStepAbsCoefs(AdaptationState,
BlackPointIn,
WhitePointIn,
IlluminantIn,
ChromaticAdaptationMatrixIn,
BlackPointOut,
WhitePointOut,
IlluminantOut,
ChromaticAdaptationMatrixOut,
m, of);
*fn1 = XYZ2XYZ;
}
else
{
// XYZ Relative to XYZ relative, no op required
*fn1 = NULL;
if (DoBlackPointCompensation) {
*fn1 = XYZ2XYZ;
ComputeBlackPointCompensationFactors(BlackPointIn,
WhitePointIn,
IlluminantIn,
BlackPointOut,
WhitePointOut,
IlluminantOut,
m, of);
}
}
break;
// From relative XYZ to Relative Lab
case LabRel:
// First pass XYZ to absolute, then to relative and
// finally to Lab. I use here D50 for output in order
// to prepare the "to Lab" conversion.
if (Absolute)
{
Rel2RelStepAbsCoefs(AdaptationState,
BlackPointIn,
WhitePointIn,
IlluminantIn,
ChromaticAdaptationMatrixIn,
BlackPointOut,
WhitePointOut,
IlluminantOut,
ChromaticAdaptationMatrixOut,
m, of);
*fn1 = XYZ2Lab;
}
else
{
// Just Convert to Lab
MAT3identity(m);
VEC3init(of, 0, 0, 0);
*fn1 = XYZ2Lab;
if (DoBlackPointCompensation) {
ComputeBlackPointCompensationFactors(BlackPointIn,
WhitePointIn,
IlluminantIn,
BlackPointOut,
WhitePointOut,
IlluminantOut,
m, of);
}
}
break;
default: return FALSE;
}
return TRUE;
}
// From Lab Relative type LUT
static
int FromLabRelLUT(int Absolute,
LPcmsCIEXYZ BlackPointIn,
LPcmsCIEXYZ WhitePointIn,
LPcmsCIEXYZ IlluminantIn,
LPMAT3 ChromaticAdaptationMatrixIn,
int Phase2, LPcmsCIEXYZ BlackPointOut,
LPcmsCIEXYZ WhitePointOut,
LPcmsCIEXYZ IlluminantOut,
LPMAT3 ChromaticAdaptationMatrixOut,
int DoBlackPointCompensation,
double AdaptationState,
_cmsADJFN *fn1,
LPMAT3 m, LPVEC3 of)
{
switch (Phase2) {
// From Lab Relative to XYZ Relative, very usual case
case XYZRel:
if (Absolute) { // Absolute intent
// From lab relative, to XYZ absolute, and then,
// back to XYZ relative
Rel2RelStepAbsCoefs(AdaptationState,
BlackPointIn,
WhitePointIn,
cmsD50_XYZ(),
ChromaticAdaptationMatrixIn,
BlackPointOut,
WhitePointOut,
IlluminantOut,
ChromaticAdaptationMatrixOut,
m, of);
*fn1 = Lab2XYZ;
}
else
{
// From Lab relative, to XYZ relative.
*fn1 = Lab2XYZ;
if (DoBlackPointCompensation) {
ComputeBlackPointCompensationFactors(BlackPointIn,
WhitePointIn,
IlluminantIn,
BlackPointOut,
WhitePointOut,
IlluminantOut,
m, of);
}
}
break;
case LabRel:
if (Absolute) {
// First pass to XYZ using the input illuminant
// * InIlluminant / D50, then to absolute. Then
// to relative, but for input
Rel2RelStepAbsCoefs(AdaptationState,
BlackPointIn,
WhitePointIn, IlluminantIn,
ChromaticAdaptationMatrixIn,
BlackPointOut,
WhitePointOut, cmsD50_XYZ(),
ChromaticAdaptationMatrixOut,
m, of);
*fn1 = Lab2XYZ2Lab;
}
else
{ // Lab -> Lab relative don't need any adjust unless
// black point compensation
*fn1 = NULL;
if (DoBlackPointCompensation) {
*fn1 = Lab2XYZ2Lab;
ComputeBlackPointCompensationFactors(BlackPointIn,
WhitePointIn,
IlluminantIn,
BlackPointOut,
WhitePointOut,
IlluminantOut,
m, of);
}
}
break;
default: return FALSE;
}
return TRUE;
}
// This function does calculate the necessary conversion operations
// needed from transpassing data from a LUT to a LUT. The conversion
// is modeled as a pointer of function and two coefficients, a and b
// The function is actually called only if not null pointer is provided,
// and the two paramaters are passed in. There are several types of
// conversions, but basically they do a linear scalling and a interchange
// Main dispatcher
int cmsChooseCnvrt(int Absolute,
int Phase1, LPcmsCIEXYZ BlackPointIn,
LPcmsCIEXYZ WhitePointIn,
LPcmsCIEXYZ IlluminantIn,
LPMAT3 ChromaticAdaptationMatrixIn,
int Phase2, LPcmsCIEXYZ BlackPointOut,
LPcmsCIEXYZ WhitePointOut,
LPcmsCIEXYZ IlluminantOut,
LPMAT3 ChromaticAdaptationMatrixOut,
int DoBlackPointCompensation,
double AdaptationState,
_cmsADJFN *fn1,
LPWMAT3 wm, LPWVEC3 wof)
{
int rc;
MAT3 m;
VEC3 of;
MAT3identity(&m);
VEC3init(&of, 0, 0, 0);
switch (Phase1) {
// Input LUT is giving XYZ relative values.
case XYZRel: rc = FromXYZRelLUT(Absolute,
BlackPointIn,
WhitePointIn,
IlluminantIn,
ChromaticAdaptationMatrixIn,
Phase2,
BlackPointOut,
WhitePointOut,
IlluminantOut,
ChromaticAdaptationMatrixOut,
DoBlackPointCompensation,
AdaptationState,
fn1, &m, &of);
break;
// Input LUT is giving Lab relative values
case LabRel: rc = FromLabRelLUT(Absolute,
BlackPointIn,
WhitePointIn,
IlluminantIn,
ChromaticAdaptationMatrixIn,
Phase2,
BlackPointOut,
WhitePointOut,
IlluminantOut,
ChromaticAdaptationMatrixOut,
DoBlackPointCompensation,
AdaptationState,
fn1, &m, &of);
break;
// Unrecognized combination
default: cmsSignalError(LCMS_ERRC_ABORTED, "(internal) Phase error");
return FALSE;
}
MAT3toFix(wm, &m);
VEC3toFix(wof, &of);
// Do some optimization -- discard conversion if identity parameters.
if (*fn1 == XYZ2XYZ || *fn1 == Lab2XYZ2Lab) {
if (IdentityParameters(wm, wof))
*fn1 = NULL;
}
return rc;
}