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.
1046 lines
26 KiB
1046 lines
26 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"
|
|
|
|
|
|
/* ----------------------------------------------------------------- Patch collections */
|
|
|
|
BOOL cdecl cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet);
|
|
|
|
BOOL cdecl cmsxPCollBuildMeasurement(LPMEASUREMENT m,
|
|
const char *ReferenceSheet,
|
|
const char *MeasurementSheet,
|
|
DWORD dwNeededSamplesType);
|
|
|
|
LPPATCH cdecl cmsxPCollGetPatch(LPMEASUREMENT m, int n);
|
|
|
|
LPPATCH cdecl cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* Name, int* lpPos);
|
|
LPPATCH cdecl cmsxPCollGetPatchByPos(LPMEASUREMENT m, int row, int col);
|
|
LPPATCH cdecl cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name,
|
|
double r, double g, double b,
|
|
LPcmsCIEXYZ XYZ, LPcmsCIELab Lab);
|
|
|
|
/* Sets of patches */
|
|
|
|
SETOFPATCHES cdecl cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault);
|
|
int cdecl cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set);
|
|
BOOL cdecl cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags);
|
|
|
|
|
|
/* Collect "need" patches of the specific kind, return the number of collected (that */
|
|
/* could be less if set of patches is exhausted) */
|
|
|
|
|
|
void cdecl cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result);
|
|
|
|
int cdecl cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double r, double g, double b, int need, SETOFPATCHES Result);
|
|
|
|
int cdecl cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
int need, SETOFPATCHES Result);
|
|
|
|
int cdecl cmsxPCollPatchesNearPrimary(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
int nChannel, int need, SETOFPATCHES Result);
|
|
|
|
int cdecl cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double Lmin, double LMax, double a, double b, SETOFPATCHES Result);
|
|
|
|
int cdecl cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
LPLUT Gamut, SETOFPATCHES Result);
|
|
|
|
LPPATCH cdecl cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
|
|
LPPATCH cdecl cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
|
|
LPPATCH cdecl cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* Distance);
|
|
|
|
|
|
/* ------------------------------------------------------------- Implementation */
|
|
|
|
#define IS(x) EqualsTo(c, x)
|
|
|
|
/* A wrapper on stricmp() */
|
|
|
|
static
|
|
BOOL EqualsTo(const char* a, const char *b)
|
|
{
|
|
return (stricmp(a, b) == 0);
|
|
}
|
|
|
|
|
|
/* Does return a bitwise mask holding the measurements contained in a Sheet */
|
|
|
|
static
|
|
DWORD MaskOfDataSet(LCMSHANDLE hSheet)
|
|
{
|
|
char** Names;
|
|
int i, n;
|
|
DWORD dwMask = 0;
|
|
|
|
n = cmsxIT8EnumDataFormat(hSheet, &Names);
|
|
|
|
for (i=0; i < n; i++) {
|
|
|
|
char *c = Names[i];
|
|
|
|
if (IS("RGB_R") || IS("RGB_G") || IS("RGB_B"))
|
|
dwMask |= PATCH_HAS_RGB;
|
|
else
|
|
if (IS("XYZ_X") || IS("XYZ_Y") || IS("XYZ_Z"))
|
|
dwMask |= PATCH_HAS_XYZ;
|
|
else
|
|
if (IS("LAB_L") || IS("LAB_A") ||IS("LAB_B"))
|
|
dwMask |= PATCH_HAS_Lab;
|
|
else
|
|
if (IS("STDEV_DE"))
|
|
dwMask |= PATCH_HAS_STD_DE;
|
|
}
|
|
|
|
return dwMask;
|
|
}
|
|
|
|
|
|
/* Addition of a patch programatically */
|
|
|
|
LPPATCH cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name,
|
|
double r, double g, double b,
|
|
LPcmsCIEXYZ XYZ, LPcmsCIELab Lab)
|
|
{
|
|
LPPATCH p;
|
|
|
|
p = m->Patches + m->nPatches++;
|
|
|
|
strcpy(p -> Name, Name);
|
|
|
|
p -> Colorant.RGB[0] = r;
|
|
p -> Colorant.RGB[1] = g;
|
|
p -> Colorant.RGB[2] = b;
|
|
p -> dwFlags = PATCH_HAS_RGB;
|
|
|
|
if (XYZ) {
|
|
|
|
p -> XYZ = *XYZ;
|
|
p -> dwFlags |= PATCH_HAS_XYZ;
|
|
}
|
|
|
|
if (Lab) {
|
|
p -> Lab = *Lab;
|
|
p -> dwFlags |= PATCH_HAS_Lab;
|
|
}
|
|
|
|
|
|
return p;
|
|
}
|
|
|
|
/* Some vendors does store colorant data in a non-standard way, */
|
|
/* i.e, from 0.0..1.0 or from 0.0..100.0 This routine tries to */
|
|
/* detect such situations */
|
|
|
|
static
|
|
void NormalizeColorant(LPMEASUREMENT m)
|
|
{
|
|
int i, j;
|
|
double MaxColorant=0;
|
|
double Normalize;
|
|
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
for (j=0; j < MAXCHANNELS; j++) {
|
|
if (p ->Colorant.Hexa[j] > MaxColorant)
|
|
MaxColorant = p ->Colorant.Hexa[j];
|
|
}
|
|
}
|
|
|
|
/* Ok, some heuristics */
|
|
|
|
if (MaxColorant < 2)
|
|
Normalize = 255.0; /* goes 0..1 */
|
|
else
|
|
if (MaxColorant < 102)
|
|
Normalize = 2.55; /* goes 0..100 */
|
|
else
|
|
if (MaxColorant > 300)
|
|
Normalize = (255.0 / 65535.0); /* Goes 0..65535.0 */
|
|
else
|
|
return; /* Is ok */
|
|
|
|
|
|
/* Rearrange patches */
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
for (j=0; j < MAXCHANNELS; j++)
|
|
p ->Colorant.Hexa[j] *= Normalize;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Load a collection from a Sheet */
|
|
|
|
BOOL cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet)
|
|
{
|
|
int i;
|
|
DWORD dwMask;
|
|
|
|
|
|
if (m -> nPatches == 0) {
|
|
|
|
m -> nPatches = (int) cmsxIT8GetPropertyDbl(hSheet, "NUMBER_OF_SETS");
|
|
m -> Patches = (PATCH*)calloc(m -> nPatches, sizeof(PATCH)); // C->C++ : cast
|
|
|
|
if (m -> Patches == NULL) {
|
|
cmsxIT8Free(hSheet);
|
|
return false;
|
|
}
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
p -> dwFlags = 0;
|
|
cmsxIT8GetPatchName(hSheet, i, p ->Name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Build mask according to data format */
|
|
|
|
dwMask = MaskOfDataSet(hSheet);
|
|
|
|
|
|
/* Read items. Set flags accordly. */
|
|
for (i = 0; i < m->nPatches; i++) {
|
|
|
|
LPPATCH Patch = m -> Patches + i;
|
|
|
|
/* Fill in data according to mask */
|
|
|
|
if (dwMask & PATCH_HAS_Lab) {
|
|
|
|
if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_L", &Patch -> Lab.L) &&
|
|
cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_A", &Patch -> Lab.a) &&
|
|
cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_B", &Patch -> Lab.b))
|
|
|
|
Patch -> dwFlags |= PATCH_HAS_Lab;
|
|
}
|
|
|
|
if (dwMask & PATCH_HAS_XYZ) {
|
|
|
|
if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_X", &Patch -> XYZ.X) &&
|
|
cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Y", &Patch -> XYZ.Y) &&
|
|
cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Z", &Patch -> XYZ.Z))
|
|
|
|
Patch -> dwFlags |= PATCH_HAS_XYZ;
|
|
|
|
}
|
|
|
|
if (dwMask & PATCH_HAS_RGB) {
|
|
|
|
if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_R", &Patch -> Colorant.RGB[0]) &&
|
|
cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_G", &Patch -> Colorant.RGB[1]) &&
|
|
cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_B", &Patch -> Colorant.RGB[2]))
|
|
|
|
Patch -> dwFlags |= PATCH_HAS_RGB;
|
|
}
|
|
|
|
if (dwMask & PATCH_HAS_STD_DE) {
|
|
|
|
if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "STDEV_DE", &Patch -> dEStd))
|
|
|
|
Patch -> dwFlags |= PATCH_HAS_STD_DE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NormalizeColorant(m);
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Does save parameters to a empty sheet */
|
|
|
|
BOOL cmsxPCollSaveToSheet(LPMEASUREMENT m, LCMSHANDLE it8)
|
|
{
|
|
int nNumberOfSets = cmsxPCollCountSet(m, m->Allowed);
|
|
int nNumberOfFields = 0;
|
|
DWORD dwMask = 0;
|
|
int i;
|
|
|
|
/* Find mask of fields */
|
|
for (i=0; i < m ->nPatches; i++) {
|
|
if (m ->Allowed[i]) {
|
|
|
|
LPPATCH p = m ->Patches + i;
|
|
dwMask |= p ->dwFlags;
|
|
}
|
|
}
|
|
|
|
nNumberOfFields = 1; /* SampleID */
|
|
|
|
if (dwMask & PATCH_HAS_RGB)
|
|
nNumberOfFields += 3;
|
|
|
|
if (dwMask & PATCH_HAS_XYZ)
|
|
nNumberOfFields += 3;
|
|
|
|
if (dwMask & PATCH_HAS_Lab)
|
|
nNumberOfFields += 3;
|
|
|
|
|
|
cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_SETS", nNumberOfSets);
|
|
cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_FIELDS", nNumberOfFields);
|
|
|
|
nNumberOfFields = 0;
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "SAMPLE_ID");
|
|
|
|
if (dwMask & PATCH_HAS_RGB) {
|
|
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_R");
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_G");
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_B");
|
|
}
|
|
|
|
if (dwMask & PATCH_HAS_XYZ) {
|
|
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_X");
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Y");
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Z");
|
|
|
|
}
|
|
|
|
|
|
if (dwMask & PATCH_HAS_XYZ) {
|
|
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_L");
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_A");
|
|
cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_B");
|
|
|
|
}
|
|
|
|
for (i=0; i < m ->nPatches; i++) {
|
|
if (m ->Allowed[i]) {
|
|
|
|
LPPATCH Patch = m ->Patches + i;
|
|
|
|
cmsxIT8SetDataSet(it8, Patch->Name, "SAMPLE_ID", Patch->Name);
|
|
|
|
if (dwMask & PATCH_HAS_RGB) {
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_R", Patch ->Colorant.RGB[0]);
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_G", Patch ->Colorant.RGB[1]);
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_B", Patch ->Colorant.RGB[2]);
|
|
}
|
|
|
|
if (dwMask & PATCH_HAS_XYZ) {
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_X", Patch ->XYZ.X);
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Y", Patch ->XYZ.Y);
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Z", Patch ->XYZ.Z);
|
|
}
|
|
|
|
if (dwMask & PATCH_HAS_Lab) {
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_L", Patch ->Lab.L);
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_A", Patch ->Lab.a);
|
|
cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_B", Patch ->Lab.b);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static
|
|
void FixLabOnly(LPMEASUREMENT m)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i < m ->nPatches; i++) {
|
|
|
|
LPPATCH p = m ->Patches + i;
|
|
if ((p ->dwFlags & PATCH_HAS_Lab) &&
|
|
!(p ->dwFlags & PATCH_HAS_XYZ))
|
|
{
|
|
cmsLab2XYZ(cmsD50_XYZ(), &p->XYZ, &p ->Lab);
|
|
|
|
p ->XYZ.X *= 100.;
|
|
p ->XYZ.Y *= 100.;
|
|
p ->XYZ.Z *= 100.;
|
|
|
|
p ->dwFlags |= PATCH_HAS_XYZ;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Higher level function. Does merge reference and measurement sheet into */
|
|
/* a MEASUREMENT struct. Data to keep is described in dwNeededSamplesType */
|
|
/* mask as follows: */
|
|
/* */
|
|
/* PATCH_HAS_Lab 0x00000001 */
|
|
/* PATCH_HAS_XYZ 0x00000002 */
|
|
/* PATCH_HAS_RGB 0x00000004 */
|
|
/* PATCH_HAS_CMY 0x00000008 */
|
|
/* PATCH_HAS_CMYK 0x00000010 */
|
|
/* PATCH_HAS_HEXACRM 0x00000020 */
|
|
/* PATCH_HAS_STD_Lab 0x00010000 */
|
|
/* PATCH_HAS_STD_XYZ 0x00020000 */
|
|
/* PATCH_HAS_STD_RGB 0x00040000 */
|
|
/* PATCH_HAS_STD_CMY 0x00080000 */
|
|
/* PATCH_HAS_STD_CMYK 0x00100000 */
|
|
/* PATCH_HAS_STD_HEXACRM 0x00100000 */
|
|
/* PATCH_HAS_MEAN_DE 0x01000000 */
|
|
/* PATCH_HAS_STD_DE 0x02000000 */
|
|
/* PATCH_HAS_CHISQ 0x04000000 */
|
|
/* */
|
|
/* See lprof.h for further info */
|
|
|
|
|
|
BOOL cmsxPCollBuildMeasurement(LPMEASUREMENT m,
|
|
const char *ReferenceSheet,
|
|
const char *MeasurementSheet,
|
|
DWORD dwNeededSamplesType)
|
|
{
|
|
LCMSHANDLE hSheet;
|
|
BOOL rc = true;
|
|
|
|
ZeroMemory(m, sizeof(MEASUREMENT));
|
|
|
|
|
|
if (ReferenceSheet != NULL && *ReferenceSheet) {
|
|
|
|
hSheet = cmsxIT8LoadFromFile(ReferenceSheet);
|
|
if (hSheet == NULL) return false;
|
|
|
|
rc = cmsxPCollLoadFromSheet(m, hSheet);
|
|
cmsxIT8Free(hSheet);
|
|
}
|
|
|
|
if (!rc) return false;
|
|
|
|
if (MeasurementSheet != NULL && *MeasurementSheet) {
|
|
|
|
hSheet = cmsxIT8LoadFromFile(MeasurementSheet);
|
|
if (hSheet == NULL) return false;
|
|
|
|
rc = cmsxPCollLoadFromSheet(m, hSheet);
|
|
cmsxIT8Free(hSheet);
|
|
}
|
|
|
|
if (!rc) return false;
|
|
|
|
|
|
/* Fix up -- If only Lab is present, then compute */
|
|
/* XYZ based on D50 */
|
|
|
|
FixLabOnly(m);
|
|
|
|
cmsxPCollValidatePatches(m, dwNeededSamplesType);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void cmsxPCollFreeMeasurements(LPMEASUREMENT m)
|
|
{
|
|
if (m->Patches)
|
|
free(m->Patches);
|
|
|
|
m->Patches = NULL;
|
|
m->nPatches = 0;
|
|
|
|
if (m -> Allowed)
|
|
free(m -> Allowed);
|
|
|
|
}
|
|
|
|
/* Retrieval functions */
|
|
|
|
LPPATCH cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* name, int* lpPos)
|
|
{
|
|
int i;
|
|
for (i=0; i < m->nPatches; i++)
|
|
{
|
|
if (m -> Allowed)
|
|
if (!m -> Allowed[i])
|
|
continue;
|
|
|
|
if (EqualsTo(m->Patches[i].Name, name)) {
|
|
if (lpPos) *lpPos = i;
|
|
return m->Patches + i;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- Sets */
|
|
|
|
|
|
SETOFPATCHES cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault)
|
|
{
|
|
SETOFPATCHES Full = (SETOFPATCHES) malloc(m -> nPatches * sizeof(BOOL));
|
|
int i;
|
|
|
|
for (i=0; i < m -> nPatches; i++)
|
|
Full[i] = lDefault;
|
|
|
|
return Full;
|
|
}
|
|
|
|
int cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set)
|
|
{
|
|
int i, Count = 0;
|
|
|
|
for (i = 0; i < m -> nPatches; i++) {
|
|
|
|
if (Set[i])
|
|
Count++;
|
|
}
|
|
|
|
return Count;
|
|
}
|
|
|
|
|
|
/* Validate patches */
|
|
|
|
BOOL cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags)
|
|
{
|
|
int i, n;
|
|
|
|
if (m->Allowed)
|
|
free(m->Allowed);
|
|
|
|
m -> Allowed = cmsxPCollBuildSet(m, true);
|
|
|
|
/* Check for flags */
|
|
for (i=n=0; i < m -> nPatches; i++) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
m -> Allowed[i] = ((p -> dwFlags & dwFlags) == dwFlags);
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Several filters */
|
|
|
|
|
|
/* This filter does validate patches placed on 'radius' distance of a */
|
|
/* device-color space. Currently only RGB is supported */
|
|
|
|
static
|
|
void PatchesByRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double R, double G, double B, double radius, SETOFPATCHES Result)
|
|
{
|
|
int i;
|
|
double ra, rmax = sqrt(radius / 255.);
|
|
double dR, dG, dB;
|
|
LPPATCH p;
|
|
|
|
for (i=0; i < m->nPatches; i++) {
|
|
|
|
|
|
if (Valids[i]) {
|
|
|
|
p = m->Patches + i;
|
|
|
|
dR = fabs(R - p -> Colorant.RGB[0]) / 255.;
|
|
dG = fabs(G - p -> Colorant.RGB[1]) / 255.;
|
|
dB = fabs(B - p -> Colorant.RGB[2]) / 255.;
|
|
|
|
ra = sqrt(dR*dR + dG*dG + dB*dB);
|
|
|
|
if (ra <= rmax)
|
|
Result[i] = true;
|
|
else
|
|
Result[i] = false;
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* This filter does validate patches placed at dEmax radius */
|
|
/* in the device-independent side. */
|
|
|
|
static
|
|
void PatchesByLab(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double L, double a, double b, double dEmax, SETOFPATCHES Result)
|
|
{
|
|
int i;
|
|
double dE, dEMaxSQR = sqrt(dEmax);
|
|
double dL, da, db;
|
|
LPPATCH p;
|
|
|
|
|
|
for (i=0; i < m->nPatches; i++) {
|
|
|
|
|
|
if (Valids[i]) {
|
|
|
|
p = m->Patches + i;
|
|
|
|
dL = fabs(L - p -> Lab.L);
|
|
da = fabs(a - p -> Lab.a);
|
|
db = fabs(b - p -> Lab.b);
|
|
|
|
dE = sqrt(dL*dL + da*da + db*db);
|
|
|
|
if (dE <= dEMaxSQR)
|
|
Result[i] = true;
|
|
else
|
|
Result[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Restrict Lab in a cube of variable sides. Quick and dirty out-of-gamut */
|
|
/* stripper used in estimations. */
|
|
|
|
static
|
|
void PatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double Lmin, double Lmax, double da, double db, SETOFPATCHES Result)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
if ((p->Lab.L >= Lmin && p->Lab.L <= Lmax) &&
|
|
(fabs(p -> Lab.a) < da) &&
|
|
(fabs(p -> Lab.b) < db))
|
|
|
|
Result[i] = true;
|
|
else
|
|
Result[i] = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Restrict to low colorfullness */
|
|
|
|
static
|
|
void PatchesOfLowC(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double Cmax, SETOFPATCHES Result)
|
|
{
|
|
int i;
|
|
cmsCIELCh LCh;
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
cmsLab2LCh(&LCh, &p->Lab);
|
|
|
|
|
|
if (LCh.C < Cmax)
|
|
Result[i] = true;
|
|
else
|
|
Result[i] = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Primary can be -1 for specifying device gray. Does return patches */
|
|
/* on device-space Colorants. dEMax is the maximum allowed ratio */
|
|
|
|
static
|
|
void PatchesPrimary(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
int nColorant, double dEMax, SETOFPATCHES Result)
|
|
{
|
|
int i, j;
|
|
double n, dE;
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
|
|
if (nColorant < 0) /* device-grey? */
|
|
{
|
|
/* cross. */
|
|
|
|
double drg = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[1]) / 255.;
|
|
double drb = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[2]) / 255.;
|
|
double dbg = fabs(p -> Colorant.RGB[1] - p -> Colorant.RGB[2]) / 255.;
|
|
|
|
dE = (drg*drg + drb*drb + dbg*dbg);
|
|
|
|
|
|
}
|
|
else {
|
|
dE = 0.;
|
|
for (j=0; j < 3; j++) {
|
|
|
|
if (j != nColorant) {
|
|
|
|
n = p -> Colorant.RGB[j] / 255.;
|
|
dE += (n * n);
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (sqrt(dE) < dEMax)
|
|
Result[i] = true;
|
|
else
|
|
Result[i] = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* The high level extractors ----------------------------------------------------- */
|
|
|
|
int cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double r, double g, double b,
|
|
int need, SETOFPATCHES Result)
|
|
{
|
|
double radius;
|
|
int nCollected;
|
|
|
|
/* Collect points inside of a sphere or radius 'radius' by RGB */
|
|
|
|
radius = 1;
|
|
do {
|
|
PatchesByRGB(m, Valids, r, g, b, radius, Result);
|
|
|
|
nCollected = cmsxPCollCountSet(m, Result);
|
|
if (nCollected <= need) {
|
|
|
|
radius += 1.0;
|
|
}
|
|
|
|
} while (nCollected <= need && radius < 256.);
|
|
|
|
return nCollected; /* Can be less than needed! */
|
|
}
|
|
|
|
|
|
int cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
int need, SETOFPATCHES Result)
|
|
{
|
|
int nGrays;
|
|
double Cmax;
|
|
|
|
Cmax = 1.;
|
|
do {
|
|
|
|
|
|
PatchesOfLowC(m, Valids, Cmax, Result);
|
|
|
|
nGrays = cmsxPCollCountSet(m, Result);
|
|
if (nGrays <= need) {
|
|
|
|
Cmax += .2;
|
|
}
|
|
|
|
} while (nGrays <= need && Cmax < 10.);
|
|
|
|
return nGrays;
|
|
}
|
|
|
|
|
|
int cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
double Lmin, double Lmax, double a, double b,
|
|
SETOFPATCHES Result)
|
|
|
|
|
|
{
|
|
PatchesInLabCube(m, Valids, Lmin, Lmax, a, b, Result);
|
|
return cmsxPCollCountSet(m, Result);
|
|
}
|
|
|
|
|
|
|
|
|
|
int cmsxPCollPatchesNearPrimary(LPMEASUREMENT m,
|
|
SETOFPATCHES Valids,
|
|
int nChannel,
|
|
int need,
|
|
SETOFPATCHES Result)
|
|
{
|
|
double radius;
|
|
int nCollected;
|
|
|
|
/* Collect points inside of a sphere or radius 'radius' by RGB */
|
|
|
|
radius = 0.05;
|
|
do {
|
|
PatchesPrimary(m, Valids, nChannel, radius, Result);
|
|
|
|
nCollected = cmsxPCollCountSet(m, Result);
|
|
if (nCollected <= need) {
|
|
|
|
radius += 0.01;
|
|
}
|
|
|
|
} while (nCollected <= need && radius < 256.);
|
|
|
|
return nCollected;
|
|
|
|
}
|
|
|
|
|
|
static
|
|
void AddOneGray(LPMEASUREMENT m, int n, SETOFPATCHES Grays)
|
|
{
|
|
LPPATCH p;
|
|
char Buffer[cmsxIT8_GRAYCOLS];
|
|
int pos;
|
|
|
|
if (n == 0) strcpy(Buffer, "DMIN");
|
|
else
|
|
if (n == cmsxIT8_GRAYCOLS - 1) strcpy(Buffer, "DMAX");
|
|
else
|
|
sprintf(Buffer, "GS%d", n);
|
|
|
|
p = cmsxPCollGetPatchByName(m, Buffer, &pos);
|
|
|
|
if (p)
|
|
Grays[pos] = true;
|
|
}
|
|
|
|
|
|
|
|
void cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result)
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i=0; i < cmsxIT8_GRAYCOLS; i++)
|
|
AddOneGray(m, i, Result);
|
|
}
|
|
|
|
|
|
|
|
/* Refresh RGB of all patches after prelinearization */
|
|
|
|
void cmsxPCollLinearizePatches(LPMEASUREMENT m, SETOFPATCHES Valids, LPGAMMATABLE Gamma[3])
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
cmsxApplyLinearizationTable(p -> Colorant.RGB, Gamma, p -> Colorant.RGB);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
int cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids,
|
|
LPLUT Gamut, SETOFPATCHES Result)
|
|
{
|
|
int i;
|
|
int nCollected = 0;
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
WORD EncodedLab[3];
|
|
WORD dE;
|
|
|
|
cmsFloat2LabEncoded(EncodedLab, &p->Lab);
|
|
cmsEvalLUT(Gamut, EncodedLab, &dE);
|
|
Result[i] = (dE < 2) ? true : false;
|
|
if (Result[i]) nCollected++;
|
|
}
|
|
}
|
|
|
|
return nCollected;
|
|
}
|
|
|
|
LPPATCH cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance)
|
|
{
|
|
int i;
|
|
LPPATCH Candidate = NULL;
|
|
double Distance, CandidateDistance = 255;
|
|
double dR, dG, dB;
|
|
|
|
Candidate = cmsxPCollGetPatchByName(m, "DMIN", NULL);
|
|
if (Candidate) {
|
|
|
|
if (TheDistance) *TheDistance = 0.0;
|
|
return Candidate;
|
|
}
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
dR = fabs(255.0 - p -> Colorant.RGB[0]) / 255.0;
|
|
dG = fabs(255.0 - p -> Colorant.RGB[1]) / 255.0;
|
|
dB = fabs(255.0 - p -> Colorant.RGB[2]) / 255.0;
|
|
|
|
Distance = sqrt(dR*dR + dG*dG + dB*dB);
|
|
|
|
if (Distance < CandidateDistance) {
|
|
Candidate = p;
|
|
CandidateDistance = Distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TheDistance)
|
|
*TheDistance = floor(CandidateDistance * 255.0 + .5);
|
|
|
|
return Candidate;
|
|
}
|
|
|
|
LPPATCH cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance)
|
|
{
|
|
int i;
|
|
LPPATCH Candidate = NULL;
|
|
double Distance, CandidateDistance = 255;
|
|
double dR, dG, dB;
|
|
|
|
|
|
Candidate = cmsxPCollGetPatchByName(m, "DMAX", NULL);
|
|
if (Candidate) {
|
|
|
|
if (TheDistance) *TheDistance = 0.0;
|
|
return Candidate;
|
|
}
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
dR = (p -> Colorant.RGB[0]) / 255.0;
|
|
dG = (p -> Colorant.RGB[1]) / 255.0;
|
|
dB = (p -> Colorant.RGB[2]) / 255.0;
|
|
|
|
Distance = sqrt(dR*dR + dG*dG + dB*dB);
|
|
|
|
if (Distance < CandidateDistance) {
|
|
Candidate = p;
|
|
CandidateDistance = Distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TheDistance)
|
|
*TheDistance = floor(CandidateDistance * 255.0 + .5);
|
|
|
|
return Candidate;
|
|
}
|
|
|
|
|
|
LPPATCH cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* TheDistance)
|
|
{
|
|
int i;
|
|
LPPATCH Candidate = NULL;
|
|
double Distance, CandidateDistance = 255;
|
|
double dR, dG, dB;
|
|
const struct {
|
|
double r, g, b;
|
|
|
|
} RGBPrimaries[3] = {
|
|
{ 255.0, 0, 0},
|
|
{ 0, 255.0, 0},
|
|
{ 0, 0, 255 }};
|
|
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
if (Valids[i]) {
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
dR = fabs(RGBPrimaries[Channel].r - p -> Colorant.RGB[0]) / 255.0;
|
|
dG = fabs(RGBPrimaries[Channel].g - p -> Colorant.RGB[1]) / 255.0;
|
|
dB = fabs(RGBPrimaries[Channel].b - p -> Colorant.RGB[2]) / 255.0;
|
|
|
|
Distance = sqrt(dR*dR + dG*dG + dB*dB);
|
|
|
|
if (Distance < CandidateDistance) {
|
|
Candidate = p;
|
|
CandidateDistance = Distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TheDistance)
|
|
*TheDistance = floor(CandidateDistance * 255.0 + .5);
|
|
|
|
return Candidate;
|
|
}
|