You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
423 lines
12 KiB
423 lines
12 KiB
/* */
|
|
/* Little cms - profiler construction set */
|
|
/* Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
|
|
/* */
|
|
/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
|
|
/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
|
|
/* */
|
|
/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
|
|
/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
|
|
/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
|
|
/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
|
|
/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
|
|
/* OF THIS SOFTWARE. */
|
|
/* */
|
|
/* This file is free software; you can redistribute it and/or modify it */
|
|
/* under the terms of the GNU General Public License as published by */
|
|
/* the Free Software Foundation; either version 2 of the License, or */
|
|
/* (at your option) any later version. */
|
|
/* */
|
|
/* This program 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 for more details. */
|
|
/* */
|
|
/* You should have received a copy of the GNU General Public License */
|
|
/* along with this program; if not, write to the Free Software */
|
|
/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
|
|
/* */
|
|
/* As a special exception to the GNU General Public License, if you */
|
|
/* distribute this file as part of a program that contains a */
|
|
/* configuration script generated by Autoconf, you may include it under */
|
|
/* the same distribution terms that you use for the rest of that program. */
|
|
/* */
|
|
/* Version 1.08a */
|
|
|
|
|
|
#include "lcmsprf.h"
|
|
#include <stdio.h>
|
|
|
|
/* The scanner profiler */
|
|
|
|
|
|
BOOL cdecl cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys);
|
|
BOOL cdecl cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys);
|
|
|
|
/* ------------------------------------------------------------ Implementation */
|
|
|
|
|
|
|
|
/* Does create regression matrix */
|
|
|
|
static
|
|
void ComputeGlobalRegression(LPSCANNERPROFILERDATA sys)
|
|
{
|
|
BOOL lAllOk;
|
|
int nTerms;
|
|
MLRSTATISTICS Stat;
|
|
|
|
nTerms = cmsxFindOptimumNumOfTerms(&sys ->hdr, 55, &lAllOk);
|
|
|
|
|
|
if (!lAllOk) {
|
|
if (sys -> hdr.printf)
|
|
sys -> hdr.printf("*** WARNING: Inconsistence found, profile may be wrong. Check the target!");
|
|
nTerms = 4;
|
|
}
|
|
|
|
/* Create high terms matrix used by interpolation */
|
|
cmsxRegressionCreateMatrix(&sys -> hdr.m,
|
|
sys -> hdr.m.Allowed,
|
|
nTerms,
|
|
sys -> hdr.PCSType,
|
|
&sys -> HiTerms,
|
|
&Stat);
|
|
|
|
if (sys -> hdr.printf)
|
|
sys -> hdr.printf("Global regression: %d terms, R2Adj = %g", nTerms, Stat.R2adj);
|
|
|
|
/* Create low terms matrix used by extrapolation */
|
|
cmsxRegressionCreateMatrix(&sys -> hdr.m,
|
|
sys -> hdr.m.Allowed,
|
|
(nTerms > 10 ? 10 : nTerms),
|
|
sys -> hdr.PCSType,
|
|
&sys -> LoTerms,
|
|
&Stat);
|
|
if (sys -> hdr.printf)
|
|
sys -> hdr.printf("Extrapolation: R2Adj = %g", Stat.R2adj);
|
|
|
|
}
|
|
|
|
|
|
/* Fill struct with default values */
|
|
|
|
BOOL cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys)
|
|
{
|
|
|
|
|
|
if (sys == NULL) return false;
|
|
ZeroMemory(sys, sizeof(SCANNERPROFILERDATA));
|
|
|
|
sys->hdr.DeviceClass = icSigInputClass;
|
|
sys->hdr.ColorSpace = icSigRgbData;
|
|
sys->hdr.PCSType = PT_Lab;
|
|
sys->hdr.Medium = MEDIUM_REFLECTIVE_D50;
|
|
|
|
/* Default values for generation */
|
|
|
|
sys -> hdr.lUseCIECAM97s = false;
|
|
sys -> hdr.CLUTPoints = 16;
|
|
|
|
|
|
/* Default viewing conditions for scanner */
|
|
|
|
sys -> hdr.device.Yb = 20;
|
|
sys -> hdr.device.La = 20;
|
|
sys -> hdr.device.surround = AVG_SURROUND;
|
|
sys -> hdr.device.D_value = 1.0; /* Complete adaptation */
|
|
|
|
|
|
/* Viewing conditions of PCS */
|
|
cmsxInitPCSViewingConditions(&sys -> hdr);
|
|
|
|
|
|
sys -> HiTerms = NULL;
|
|
sys -> LoTerms = NULL;
|
|
|
|
strcpy(sys -> hdr.Description, "no description");
|
|
strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set");
|
|
strcpy(sys -> hdr.Copyright, "No copyright, use freely");
|
|
strcpy(sys -> hdr.Model, "(unknown)");
|
|
|
|
sys ->lLocalConvergenceExtrapolation = false;
|
|
sys ->hdr.ProfileVerbosityLevel = 0;
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Auxiliar: take RGB and update gauge */
|
|
static
|
|
void GetRGB(LPPROFILERCOMMONDATA hdr, WORD In[], double* r, double* g, double* b)
|
|
{
|
|
static int Count = 0, n_old = 0;
|
|
double R, G, B;
|
|
int n;
|
|
|
|
|
|
R = _cmsxSaturate65535To255(In[0]); /* Convert from the sheet notation */
|
|
G = _cmsxSaturate65535To255(In[1]); /* 0..255.0, to our notation */
|
|
B = _cmsxSaturate65535To255(In[2]); /* of 0..0xffff, 0xffff/255 = 257 */
|
|
|
|
if (R == 0 && G == 0 && B == 0) {
|
|
Count = 0; n_old = -1;
|
|
}
|
|
|
|
n = (int) (double) (100. * Count) / (hdr->CLUTPoints * hdr->CLUTPoints * hdr->CLUTPoints);
|
|
Count++;
|
|
|
|
if (n > n_old) {
|
|
if (hdr->Gauger) hdr->Gauger("", 0, 100, (int) n);
|
|
}
|
|
|
|
n_old = n;
|
|
*r = R; *g = G; *b = B;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The sampler for Lab */
|
|
static
|
|
int RegressionSamplerLab(WORD In[], WORD Out[], LPVOID Cargo)
|
|
{
|
|
cmsCIEXYZ xyz;
|
|
cmsCIELab Lab;
|
|
double r, g, b;
|
|
LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo;
|
|
char code;
|
|
|
|
|
|
GetRGB(&sys->hdr, In, &r, &g, &b);
|
|
|
|
|
|
code = cmsxHullCheckpoint(sys->hdr.hRGBHull,
|
|
(int) floor(r + .5),
|
|
(int) floor(g + .5),
|
|
(int) floor(b + .5));
|
|
|
|
|
|
if (code == 'i') { /* Inside gamut */
|
|
|
|
if (!cmsxRegressionRGB2Lab(r, g, b, sys -> HiTerms, &Lab)) return false;
|
|
}
|
|
else
|
|
if (!sys -> lLocalConvergenceExtrapolation && code == 'o') { /* outside gamut */
|
|
|
|
if (!cmsxRegressionRGB2Lab(r, g, b, sys -> LoTerms, &Lab)) return false;
|
|
}
|
|
else { /* At gamut hull boundaries */
|
|
|
|
if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m,
|
|
PT_Lab,
|
|
10,
|
|
true,
|
|
30,
|
|
r, g, b,
|
|
&Lab)) return false;
|
|
}
|
|
|
|
|
|
/* Regression CAN deliver wrong values. Clamp these. */
|
|
cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128);
|
|
|
|
/* Normalize */
|
|
cmsLab2XYZ(cmsD50_XYZ(), &xyz, &Lab);
|
|
cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false);
|
|
cmsXYZ2Lab(cmsD50_XYZ(), &Lab, &xyz);
|
|
|
|
/* Clamping again, adaptation could move slightly values */
|
|
cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128);
|
|
|
|
/* To PCS encoding */
|
|
cmsFloat2LabEncoded(Out, &Lab);
|
|
|
|
|
|
return true; /* And done with success */
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The sampler for XYZ */
|
|
static
|
|
int RegressionSamplerXYZ(WORD In[], WORD Out[], LPVOID Cargo)
|
|
{
|
|
cmsCIEXYZ xyz;
|
|
double r, g, b;
|
|
LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo;
|
|
char code;
|
|
|
|
GetRGB(&sys -> hdr, In, &r, &g, &b);
|
|
|
|
code = cmsxHullCheckpoint(sys ->hdr.hRGBHull,
|
|
(int) floor(r + .5),
|
|
(int) floor(g + .5),
|
|
(int) floor(b + .5));
|
|
|
|
if (code == 'i') { /* Inside gamut */
|
|
|
|
if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> HiTerms, &xyz)) return false;
|
|
}
|
|
else
|
|
if (!sys -> lLocalConvergenceExtrapolation && code == 'o') { /* outside gamut */
|
|
|
|
if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> LoTerms, &xyz)) return false;
|
|
}
|
|
|
|
else { /* At gamut hull boundaries */
|
|
|
|
if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m,
|
|
PT_XYZ,
|
|
10,
|
|
true,
|
|
30,
|
|
r, g, b,
|
|
&xyz)) return false;
|
|
}
|
|
|
|
|
|
xyz.X /= 100.;
|
|
xyz.Y /= 100.;
|
|
xyz.Z /= 100.;
|
|
|
|
cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false);
|
|
|
|
/* To PCS encoding. It also claps bad values */
|
|
cmsFloat2XYZEncoded(Out, &xyz);
|
|
|
|
return true; /* And done witch success */
|
|
}
|
|
|
|
|
|
|
|
/* The main scanner profiler */
|
|
BOOL cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys)
|
|
{
|
|
|
|
LPLUT AToB0;
|
|
DWORD dwNeedSamples;
|
|
|
|
|
|
if (!*sys -> hdr.OutputProfileFile)
|
|
return false;
|
|
|
|
|
|
if (!cmsxChoosePCS(&sys->hdr))
|
|
return false;
|
|
|
|
dwNeedSamples = PATCH_HAS_RGB;
|
|
if (sys ->hdr.PCSType == PT_Lab)
|
|
dwNeedSamples |= PATCH_HAS_Lab;
|
|
else
|
|
dwNeedSamples |= PATCH_HAS_XYZ;
|
|
|
|
|
|
if (sys->hdr.printf) {
|
|
|
|
sys->hdr.printf("Loading sheets...");
|
|
|
|
if (sys->hdr.ReferenceSheet[0])
|
|
sys->hdr.printf("Reference sheet: %s", sys->hdr.ReferenceSheet);
|
|
if (sys->hdr.MeasurementSheet[0])
|
|
sys->hdr.printf("Measurement sheet: %s", sys->hdr.MeasurementSheet);
|
|
}
|
|
|
|
|
|
if (!cmsxPCollBuildMeasurement(&sys->hdr.m,
|
|
sys->hdr.ReferenceSheet,
|
|
sys->hdr.MeasurementSheet,
|
|
dwNeedSamples)) return false;
|
|
|
|
|
|
|
|
sys->hdr.hProfile = cmsCreateRGBProfile(NULL, NULL, NULL);
|
|
|
|
|
|
cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass);
|
|
cmsSetColorSpace(sys->hdr.hProfile, sys->hdr.ColorSpace);
|
|
cmsSetPCS(sys->hdr. hProfile, _cmsICCcolorSpace(sys->hdr.PCSType));
|
|
|
|
/* Save char target tag */
|
|
if (sys->hdr.ProfileVerbosityLevel >= 2) {
|
|
|
|
cmsxEmbedCharTarget(&sys ->hdr);
|
|
}
|
|
|
|
AToB0 = cmsAllocLUT();
|
|
|
|
cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3);
|
|
|
|
cmsxComputeLinearizationTables(&sys-> hdr.m,
|
|
sys -> hdr.PCSType,
|
|
sys -> Prelinearization,
|
|
1024,
|
|
MEDIUM_REFLECTIVE_D50);
|
|
|
|
/* Refresh RGB of all patches. This converts all regression into */
|
|
/* near linear RGB->Lab or XYZ */
|
|
|
|
cmsxPCollLinearizePatches(&sys->hdr.m, sys -> hdr.m.Allowed, sys -> Prelinearization);
|
|
|
|
cmsxComputeGamutHull(&sys->hdr);
|
|
ComputeGlobalRegression(sys);
|
|
|
|
cmsAllocLinearTable(AToB0, sys -> Prelinearization, 1);
|
|
|
|
/* Set CIECAM97s parameters */
|
|
|
|
sys -> hdr.device.whitePoint.X = sys -> hdr.WhitePoint.X * 100.;
|
|
sys -> hdr.device.whitePoint.Y = sys -> hdr.WhitePoint.Y * 100.;
|
|
sys -> hdr.device.whitePoint.Z = sys -> hdr.WhitePoint.Z * 100.;
|
|
|
|
|
|
sys->hdr.hDevice = cmsCIECAM97sInit(&sys->hdr.device);
|
|
sys->hdr.hPCS = cmsCIECAM97sInit(&sys->hdr.PCS);
|
|
|
|
|
|
if (sys -> hdr.PCSType == PT_Lab)
|
|
cmsSample3DGrid(AToB0, RegressionSamplerLab, sys, 0);
|
|
else
|
|
cmsSample3DGrid(AToB0, RegressionSamplerXYZ, sys, 0);
|
|
|
|
cmsCIECAM97sDone(sys->hdr.hDevice);
|
|
cmsCIECAM97sDone(sys->hdr.hPCS);
|
|
|
|
cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0);
|
|
|
|
|
|
cmsxEmbedTextualInfo(&sys -> hdr);
|
|
|
|
cmsAddTag(sys->hdr.hProfile, icSigMediaWhitePointTag, &sys->hdr.WhitePoint);
|
|
cmsAddTag(sys->hdr.hProfile, icSigMediaBlackPointTag, &sys->hdr.BlackPoint);
|
|
|
|
|
|
/* Save primaries & gamma curves */
|
|
if (sys->hdr.ProfileVerbosityLevel >= 1) {
|
|
|
|
cmsxEmbedMatrixShaper(&sys ->hdr);
|
|
}
|
|
|
|
_cmsSaveProfile(sys->hdr.hProfile, sys->hdr.OutputProfileFile);
|
|
|
|
cmsCloseProfile(sys->hdr.hProfile);
|
|
sys->hdr.hProfile = NULL;
|
|
|
|
cmsxPCollFreeMeasurements(&sys->hdr.m);
|
|
|
|
cmsFreeLUT(AToB0);
|
|
|
|
if (sys -> HiTerms)
|
|
MATNfree(sys -> HiTerms);
|
|
sys -> HiTerms = NULL;
|
|
|
|
|
|
if (sys -> LoTerms)
|
|
MATNfree(sys -> LoTerms);
|
|
sys -> LoTerms = NULL;
|
|
|
|
|
|
|
|
if (sys ->Prelinearization[0])
|
|
cmsFreeGammaTriple(sys -> Prelinearization);
|
|
|
|
if (sys ->hdr.Gamma)
|
|
cmsFreeGammaTriple(sys->hdr.Gamma);
|
|
|
|
return true;
|
|
}
|