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.
2379 lines
57 KiB
2379 lines
57 KiB
4 years ago
|
//
|
||
|
// 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.
|
||
|
|
||
|
// Test Suite for Little cms
|
||
|
|
||
|
// #define ICM_COMPARATIVE 1
|
||
|
// #define CHECK_SPEED 1
|
||
|
|
||
|
#ifdef __BORLANDC__
|
||
|
# include <condefs.h>
|
||
|
#endif
|
||
|
|
||
|
#include "lcms.h"
|
||
|
|
||
|
|
||
|
|
||
|
#include <time.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
#ifndef NON_WINDOWS
|
||
|
#include <icm.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#define PREC 20
|
||
|
|
||
|
#define TYPE_XYZA_16 (COLORSPACE_SH(PT_XYZ)|CHANNELS_SH(3)|BYTES_SH(2)|EXTRA_SH(1))
|
||
|
#define TYPE_LABA_16 (COLORSPACE_SH(PT_Lab)|CHANNELS_SH(3)|BYTES_SH(2)|EXTRA_SH(1))
|
||
|
|
||
|
typedef struct {BYTE r, g, b, a;} Scanline_rgb1;
|
||
|
typedef struct {WORD r, g, b, a;} Scanline_rgb2;
|
||
|
typedef struct {BYTE r, g, b;} Scanline_rgb8;
|
||
|
typedef struct {WORD r, g, b;} Scanline_rgb0;
|
||
|
|
||
|
|
||
|
// Print a dot for gauging
|
||
|
|
||
|
static
|
||
|
void Dot(void)
|
||
|
{
|
||
|
fprintf(stdout, "."); fflush(stdout);
|
||
|
}
|
||
|
|
||
|
// #ifndef LCMS_DLL
|
||
|
|
||
|
// Are we little or big endian? From Harbison&Steele.
|
||
|
|
||
|
static
|
||
|
int CheckEndianess(void)
|
||
|
{
|
||
|
int BigEndian, IsOk;
|
||
|
union {
|
||
|
|
||
|
long l;
|
||
|
char c[sizeof (long)];
|
||
|
|
||
|
} u;
|
||
|
|
||
|
u.l = 1;
|
||
|
BigEndian = (u.c[sizeof (long) - 1] == 1);
|
||
|
|
||
|
#ifdef USE_BIG_ENDIAN
|
||
|
IsOk = BigEndian;
|
||
|
#else
|
||
|
IsOk = !BigEndian;
|
||
|
#endif
|
||
|
|
||
|
if (!IsOk) {
|
||
|
|
||
|
printf("\nOOOPPSS! You have USE_BIG_ENDIAN toggle misconfigured!\n\n");
|
||
|
printf("Please, edit lcms.h and %s the USE_BIG_ENDIAN toggle.\n", BigEndian? "uncomment" : "comment");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int CheckSwab(void)
|
||
|
{
|
||
|
unsigned char Test[] = { 1, 2, 3, 4, 5, 6};
|
||
|
|
||
|
#ifdef USE_CUSTOM_SWAB
|
||
|
return 1;
|
||
|
#endif
|
||
|
|
||
|
#ifdef USE_BIG_ENDIAN
|
||
|
return 1;
|
||
|
#endif
|
||
|
|
||
|
swab((char*) Test, (char*) Test, 6);
|
||
|
|
||
|
if (strncmp((char*) Test, "\x2\x1\x4\x3\x6\x5", 6) != 0)
|
||
|
{
|
||
|
printf("\nOOOPPSS! swab() does not work as expected in your machine!\n\n");
|
||
|
printf("Please, edit lcms.h and uncomment the USE_CUSTOM_SWAB toggle.\n");
|
||
|
return 0;
|
||
|
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int CheckQuickFloor(void)
|
||
|
{
|
||
|
|
||
|
if ((_cmsQuickFloor(1.234) != 1) ||
|
||
|
(_cmsQuickFloor(32767.234) != 32767) ||
|
||
|
(_cmsQuickFloor(-1.234) != -2) ||
|
||
|
(_cmsQuickFloor(-32767.1) != -32768)) {
|
||
|
|
||
|
printf("\nOOOPPSS! _cmsFloor() does not work as expected in your machine!\n\n");
|
||
|
printf("Please, edit lcms.h and uncomment the LCMS_DEFAULT_FLOOR_CONVERSION toggle.\n");
|
||
|
return 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
typedef struct _Stats {
|
||
|
double n, x, y, x2, y2, xy;
|
||
|
double Peak;
|
||
|
} STATS, FAR* LPSTATS;
|
||
|
|
||
|
static void ClearStats(LPSTATS p)
|
||
|
{
|
||
|
p -> n = p -> x = p -> y = p -> x2 = p -> y2 = p -> xy
|
||
|
= p -> Peak = 0.0;
|
||
|
}
|
||
|
|
||
|
static double Std(LPSTATS p)
|
||
|
{
|
||
|
return sqrt((p->n*p->x2 - p->x * p->x) / (p->n*(p->n-1)));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
void PrintStatistics(clock_t atime, LPSTATS Stats)
|
||
|
{
|
||
|
|
||
|
clock_t diff;
|
||
|
double a;
|
||
|
|
||
|
diff = clock() - atime;
|
||
|
a = (double) diff / CLOCKS_PER_SEC;
|
||
|
|
||
|
// These are statistics of 16 bit, so divide
|
||
|
// by 257 to get dE relative to 8 bits
|
||
|
|
||
|
printf("\n");
|
||
|
|
||
|
if (Stats)
|
||
|
printf("dE: mean=%g, SD=%g, max=%g ",
|
||
|
(Stats->x / Stats -> n) / 257.,
|
||
|
(Std(Stats)) / 257.,
|
||
|
Stats -> Peak / 257.);
|
||
|
|
||
|
|
||
|
if (atime > 0)
|
||
|
printf("[%d tics, %g sec.]", (int) diff, a);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// Simpler fixed-point math
|
||
|
|
||
|
static
|
||
|
void TestFixedPoint(void)
|
||
|
{
|
||
|
Fixed32 a, b, c, d;
|
||
|
double f;
|
||
|
|
||
|
a = DOUBLE_TO_FIXED(1.1234);
|
||
|
b = DOUBLE_TO_FIXED(2.5678);
|
||
|
|
||
|
c = FixedMul(a, b);
|
||
|
|
||
|
d = FIXED_REST_TO_INT(c);
|
||
|
f = ((double) d / 0xffff) * 1000000.0;
|
||
|
|
||
|
printf("Testing fixed point:\t%f = %d.%d\n", (1.1234 * 2.5678), FIXED_TO_INT(c), (int) f);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestFixedScaling(void)
|
||
|
{
|
||
|
int i, j, nfl, nfx;
|
||
|
double FloatFactor;
|
||
|
Fixed32 FixedFactor;
|
||
|
|
||
|
|
||
|
|
||
|
printf("Testing fixed scaling...");
|
||
|
|
||
|
for (j=5; j<100; j++)
|
||
|
{
|
||
|
FloatFactor = (double) j / 100.0 ;
|
||
|
FloatFactor = FIXED_TO_DOUBLE(DOUBLE_TO_FIXED(FloatFactor));
|
||
|
FixedFactor = DOUBLE_TO_FIXED(FloatFactor);
|
||
|
|
||
|
for (i=0; i < 0x10000L; i++)
|
||
|
{
|
||
|
nfl = (WORD) ((double) i * FloatFactor);
|
||
|
nfx = FixedScale((WORD) i, FixedFactor);
|
||
|
|
||
|
if (nfl != nfx) {
|
||
|
printf("Failed!\ni=%x (%d), float=%x, fixed=%x", i, i, nfl, nfx);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
printf ("pass.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// Curve joining test. Joining two high-gamma of 3.0 curves should
|
||
|
// give something like linear
|
||
|
|
||
|
static
|
||
|
int TestJointCurves(void)
|
||
|
{
|
||
|
LPGAMMATABLE Forward, Reverse, Result;
|
||
|
LCMSBOOL rc;
|
||
|
|
||
|
printf("Testing curves join ...");
|
||
|
|
||
|
Forward = cmsBuildGamma(256, 3.0);
|
||
|
Reverse = cmsBuildGamma(256, 3.0);
|
||
|
|
||
|
Result = cmsJoinGammaEx(Forward, Reverse, 256);
|
||
|
|
||
|
cmsFreeGamma(Forward); cmsFreeGamma(Reverse);
|
||
|
|
||
|
rc = cmsIsLinear(Result->GammaTable, Result ->nEntries);
|
||
|
cmsFreeGamma(Result);
|
||
|
|
||
|
if (!rc) {
|
||
|
printf("failed!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
else {
|
||
|
printf("pass.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// Check reversing of gamma curves
|
||
|
|
||
|
#define NPOINTS 1024
|
||
|
|
||
|
static
|
||
|
int TestReversingOfCurves(void)
|
||
|
{
|
||
|
LPGAMMATABLE Gamma, Reverse, Computed;
|
||
|
int i;
|
||
|
double dE;
|
||
|
STATS Stats;
|
||
|
|
||
|
printf("Testing reversing of curves ...");
|
||
|
ClearStats(&Stats);
|
||
|
|
||
|
|
||
|
Gamma = cmsBuildGamma(NPOINTS, 3.0);
|
||
|
Reverse = cmsBuildGamma(NPOINTS, 1.0/3.0);
|
||
|
|
||
|
Computed = cmsReverseGamma(NPOINTS, Gamma);
|
||
|
|
||
|
for (i=0; i < NPOINTS; i++) {
|
||
|
|
||
|
dE = fabs(Reverse->GammaTable[i] - Computed->GammaTable[i]);
|
||
|
|
||
|
Stats.x += dE;
|
||
|
Stats.x2 += (dE * dE);
|
||
|
Stats.n += 1.0;
|
||
|
if (dE > Stats.Peak) {
|
||
|
Stats.Peak = dE;
|
||
|
}
|
||
|
|
||
|
if (dE > 0x0010) {
|
||
|
printf("Coarse error! %x on entry %d: %X/%X", (int) dE, i, Reverse->GammaTable[i],
|
||
|
Computed->GammaTable[i]);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if (Stats.Peak > 0) PrintStatistics(0, &Stats);
|
||
|
printf(" pass.\n");
|
||
|
cmsFreeGamma(Gamma);
|
||
|
cmsFreeGamma(Reverse);
|
||
|
cmsFreeGamma(Computed);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// Linear interpolation test. Here I check the cmsLinearInterpLUT16
|
||
|
// Tables are supposed to be monotonic, but the algorithm works on
|
||
|
// non-monotonic as well.
|
||
|
|
||
|
static
|
||
|
int TestLinearInterpolation(int lExhaustive)
|
||
|
{
|
||
|
static WORD Tab[4098];
|
||
|
int j, i, k = 0;
|
||
|
L16PARAMS p;
|
||
|
int n;
|
||
|
clock_t time;
|
||
|
|
||
|
printf("Testing linear interpolation ...");
|
||
|
|
||
|
// First I will check exact values. Since prime factors of 65535 (FFFF) are,
|
||
|
//
|
||
|
// 0xFFFF = 1 * 3 * 5 * 17 * 257
|
||
|
//
|
||
|
// I test tables of 2, 4, 6, and 18 points, that will be exact.
|
||
|
// Then, a table of 3 elements are tested. Error must be < 1
|
||
|
// Since no floating point is involved, This will be a measure of speed.
|
||
|
|
||
|
|
||
|
// Perform 10 times, so i can measure average times
|
||
|
|
||
|
time = clock();
|
||
|
for (j=0; j < 10; j++)
|
||
|
{
|
||
|
|
||
|
// 2 points - exact
|
||
|
|
||
|
Tab[0] = 0;
|
||
|
Tab[1] = 0xffffU;
|
||
|
|
||
|
cmsCalcL16Params(2, &p);
|
||
|
|
||
|
for (i=0; i <= 0xffffL; i++)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if (n != i)
|
||
|
{
|
||
|
printf("Error in Linear interpolation (2p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// 3 points - Here the error must be <= 1, since
|
||
|
// 2 == (3 - 1) is not a factor of 0xffff
|
||
|
|
||
|
Tab[0] = 0;
|
||
|
Tab[1] = 0x7FFF;
|
||
|
Tab[2] = 0xffffU;
|
||
|
|
||
|
cmsCalcL16Params(3, &p);
|
||
|
|
||
|
for (i=0; i <= 0xffffL; i++)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if (abs(n - i) > 1)
|
||
|
{
|
||
|
printf("Error in Linear interpolation (3p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// 4 points - exact
|
||
|
|
||
|
Tab[0] = 0;
|
||
|
Tab[1] = 0x5555U;
|
||
|
Tab[2] = 0xAAAAU;
|
||
|
Tab[3] = 0xffffU;
|
||
|
|
||
|
cmsCalcL16Params(4, &p);
|
||
|
|
||
|
for (i=0; i <= 0xffffL; i++)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if (n != i) {
|
||
|
printf("Error in Linear interpolation (4p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// 6 - points
|
||
|
|
||
|
Tab[0] = 0;
|
||
|
Tab[1] = 0x3333U;
|
||
|
Tab[2] = 0x6666U;
|
||
|
Tab[3] = 0x9999U;
|
||
|
Tab[4] = 0xCCCCU;
|
||
|
Tab[5] = 0xFFFFU;
|
||
|
|
||
|
cmsCalcL16Params(6, &p);
|
||
|
|
||
|
for (i=0; i <= 0xffffL; i++)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if (n != i) {
|
||
|
printf("Error in Linear interpolation (6p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// 18 points
|
||
|
|
||
|
for (i=0; i < 18; i++)
|
||
|
Tab[i] = (WORD) (0x0f0fU*i);
|
||
|
|
||
|
cmsCalcL16Params(18, &p);
|
||
|
|
||
|
for (i=0; i <= 0xffffL; i++)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if (n != i) {
|
||
|
printf("Error in Linear interpolation (18p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
printf("pass. (%d tics)\n", (int) (clock() - time));
|
||
|
|
||
|
// Now test descending tables
|
||
|
printf("Testing descending tables (linear interpolation)...");
|
||
|
|
||
|
// 2 points - exact
|
||
|
|
||
|
Tab[1] = 0;
|
||
|
Tab[0] = 0xffffU;
|
||
|
|
||
|
cmsCalcL16Params(2, &p);
|
||
|
|
||
|
for (i=0xffffL; i > 0; --i)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if ((0xffffL - n) != i) {
|
||
|
|
||
|
printf("Error in Linear interpolation (descending) (2p): Must be i=%x, But is n=%x\n", i, 0xffff - n);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// 3 points - Here the error must be <= 1, since
|
||
|
// 2 = (3 - 1) is not a factor of 0xffff
|
||
|
|
||
|
Tab[2] = 0;
|
||
|
Tab[1] = 0x7FFF;
|
||
|
Tab[0] = 0xffffU;
|
||
|
|
||
|
cmsCalcL16Params(3, &p);
|
||
|
|
||
|
for (i=0xffffL; i > 0; --i)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if (abs((0xffffL - n) - i) > 1) {
|
||
|
|
||
|
printf("Error in Linear interpolation (descending) (3p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// 4 points - exact
|
||
|
|
||
|
Tab[3] = 0;
|
||
|
Tab[2] = 0x5555U;
|
||
|
Tab[1] = 0xAAAAU;
|
||
|
Tab[0] = 0xffffU;
|
||
|
|
||
|
cmsCalcL16Params(4, &p);
|
||
|
|
||
|
for (i=0xffffL; i > 0; --i)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if ((0xffffL - n) != i) {
|
||
|
|
||
|
printf("Error in Linear interpolation (descending) (4p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// 6 - points
|
||
|
|
||
|
Tab[5] = 0;
|
||
|
Tab[4] = 0x3333U;
|
||
|
Tab[3] = 0x6666U;
|
||
|
Tab[2] = 0x9999U;
|
||
|
Tab[1] = 0xCCCCU;
|
||
|
Tab[0] = 0xFFFFU;
|
||
|
|
||
|
cmsCalcL16Params(6, &p);
|
||
|
|
||
|
for (i=0xffffL; i > 0; --i)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if ((0xffffL - n) != i) {
|
||
|
printf("Error in Linear interpolation (descending) (6p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// 18 points
|
||
|
|
||
|
for (i=0; i < 18; i++)
|
||
|
Tab[17-i] = (WORD) (0x0f0fU*i);
|
||
|
|
||
|
cmsCalcL16Params(18, &p);
|
||
|
|
||
|
for (i=0xffffL; i > 0; --i)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if ((0xffffL - n) != i) {
|
||
|
|
||
|
printf("Error in Linear interpolation (descending) (18p): Must be i=%x, But is n=%x\n", i, n);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
printf("pass.\n");
|
||
|
|
||
|
if (!lExhaustive) return 1;
|
||
|
|
||
|
printf("Now, testing interpolation errors for tables of n elements ...\n");
|
||
|
|
||
|
for (j=10; j < 4096; j ++)
|
||
|
{
|
||
|
if ((j % 10) == 0) printf("%d\r", j);
|
||
|
|
||
|
for (i=0; i <= j; i++)
|
||
|
{
|
||
|
Tab[i] = (WORD) floor((((double) i / ((double) j-1)) * 65535.0) + .5);
|
||
|
}
|
||
|
|
||
|
k =0;
|
||
|
cmsCalcL16Params(j, &p);
|
||
|
for (i=0; i <= 0xffffL; i++)
|
||
|
{
|
||
|
n = cmsLinearInterpLUT16((WORD) i, Tab, &p);
|
||
|
if (n != i) k++;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
printf("\n%d: %d errors\n\n", j, k);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
int IsGood(const char *frm, WORD in, WORD out)
|
||
|
{
|
||
|
|
||
|
// 1 for rounding
|
||
|
if ((abs(in - out) > 1)) {
|
||
|
|
||
|
printf("error %s %x - %x\n", frm, in, out);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static
|
||
|
LCMSBOOL TestReverseLinearInterpolation(void)
|
||
|
{
|
||
|
WORD Tab[20];
|
||
|
L16PARAMS p;
|
||
|
int i, n, v;
|
||
|
|
||
|
printf("Testing reverse linear interpolation\n");
|
||
|
|
||
|
|
||
|
cmsCalcL16Params(16, &p);
|
||
|
|
||
|
for (i=0; i < 16; i++) Tab[i] = (WORD) i * 0x1111;
|
||
|
|
||
|
printf("\ton normal monotonic curve...");
|
||
|
for (i=0; i < 16; i++)
|
||
|
{
|
||
|
v = (i * 0x1111);
|
||
|
n = cmsReverseLinearInterpLUT16((WORD) v, Tab, &p);
|
||
|
if (!IsGood("unexpected result", (WORD) v, (WORD) n))
|
||
|
return FALSE;
|
||
|
}
|
||
|
printf("pass.\n");
|
||
|
|
||
|
|
||
|
Tab[0] = 0;
|
||
|
Tab[1] = 0;
|
||
|
Tab[2] = 0;
|
||
|
Tab[3] = 0;
|
||
|
Tab[4] = 0;
|
||
|
Tab[5] = 0x5555;
|
||
|
Tab[6] = 0x6666;
|
||
|
Tab[7] = 0x7777;
|
||
|
Tab[8] = 0x8888;
|
||
|
Tab[9] = 0x9999;
|
||
|
Tab[10]= 0xffff;
|
||
|
Tab[11]= 0xffff;
|
||
|
Tab[12]= 0xffff;
|
||
|
Tab[13]= 0xffff;
|
||
|
Tab[14]= 0xffff;
|
||
|
Tab[15]= 0xffff;
|
||
|
|
||
|
|
||
|
printf("\ton degenerated curve ...");
|
||
|
|
||
|
for (i=0; i < 16; i++)
|
||
|
{
|
||
|
v = (i * 0x1111);
|
||
|
n = cmsReverseLinearInterpLUT16((WORD) v, Tab, &p);
|
||
|
|
||
|
if (i > 5 && i <= 9) {
|
||
|
|
||
|
if (!IsGood("unexpected result", (WORD) v, (WORD) n))
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
printf("pass.\n");
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// 3D LUT test
|
||
|
|
||
|
static
|
||
|
int Test3D(void)
|
||
|
{
|
||
|
LPLUT MyLut;
|
||
|
LPWORD Table;
|
||
|
WORD In[3], Out[3];
|
||
|
int r, g, b, i;
|
||
|
double *SampleTablePtr, SampleTable[] = { //R G B
|
||
|
|
||
|
0, 0, 0, // B=0,G=0,R=0
|
||
|
0, 0, .25, // B=1,G=0,R=0
|
||
|
|
||
|
0, .5, 0, // B=0,G=1,R=0
|
||
|
0, .5, .25, // B=1,G=1,R=0
|
||
|
|
||
|
1, 0, 0, // B=0,G=0,R=1
|
||
|
1, 0, .25, // B=1,G=0,R=1
|
||
|
|
||
|
1, .5, 0, // B=0,G=1,R=1
|
||
|
1, .5, .25 // B=1,G=1,R=1
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
printf("Testing 3D interpolation on LUT...");
|
||
|
|
||
|
// 1.- Allocate an empty LUT
|
||
|
|
||
|
MyLut = cmsAllocLUT();
|
||
|
|
||
|
// 2.- In this LUT, allocate a 3D grid of 2 points, from 3 components (RGB)
|
||
|
// to 3 components. First 3 is input dimension, last 3 is output one.
|
||
|
// 2 is number of grid points.
|
||
|
|
||
|
MyLut = cmsAlloc3DGrid(MyLut, 2, 3, 3);
|
||
|
|
||
|
// 3.- Fill the LUT table with values.
|
||
|
|
||
|
Table = MyLut -> T;
|
||
|
|
||
|
SampleTablePtr = SampleTable;
|
||
|
|
||
|
for (i= 0; i < 3; i++)
|
||
|
for (r = 0; r < 2; r++)
|
||
|
for (g = 0; g < 2; g++)
|
||
|
for (b = 0; b < 2; b++) {
|
||
|
|
||
|
WORD a = (WORD) floor(*SampleTablePtr++ * 65535. + .5);
|
||
|
|
||
|
*Table++ = a;
|
||
|
}
|
||
|
|
||
|
|
||
|
// The sample table gives
|
||
|
//
|
||
|
// r = input,
|
||
|
// g = input divided by 2
|
||
|
// b = input divided by 4
|
||
|
//
|
||
|
// So, I should obtain on output r, g/2, g/4
|
||
|
|
||
|
|
||
|
for (i=0; i < 0xffff; i++) {
|
||
|
|
||
|
In[0] = In[1] = In[2] = (WORD) i;
|
||
|
|
||
|
cmsEvalLUT(MyLut, In, Out);
|
||
|
|
||
|
|
||
|
|
||
|
// Check results, I will tolerate error <= 1 for rounding
|
||
|
|
||
|
|
||
|
|
||
|
if (!IsGood("Channel 1", Out[0], In[0])) return 0;
|
||
|
if (!IsGood("Channel 2", Out[1], (WORD) ((double) In[1] / 2))) return 0;
|
||
|
if (!IsGood("Channel 3", Out[2], (WORD) ((double) In[2] / 4))) return 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// Last, remember free stuff
|
||
|
|
||
|
cmsFreeLUT(MyLut);
|
||
|
|
||
|
printf("pass.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
void PrintMatrix(LPMAT3 lpM)
|
||
|
{
|
||
|
int i, j;
|
||
|
|
||
|
for (i=0; i < 3; i++) {
|
||
|
printf ("[ ");
|
||
|
for (j=0; j < 3; j++)
|
||
|
{
|
||
|
printf("%1.6f ", (*lpM).v[i].n[j]);
|
||
|
}
|
||
|
printf("]\n");
|
||
|
}
|
||
|
|
||
|
printf("\n");
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
LCMSBOOL CmpMatrix(LPMAT3 lpM1, LPMAT3 lpM2, double tolerance)
|
||
|
{
|
||
|
int i, j;
|
||
|
|
||
|
for (i=0; i < 3; i++) {
|
||
|
for (j=0; j < 3; j++) {
|
||
|
if (fabs(lpM1 -> v[i].n[j] - lpM2 -> v[i].n[j]) > tolerance)
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
LCMSBOOL TestMatrixCreation(void)
|
||
|
{
|
||
|
MAT3 Mat;
|
||
|
int rc;
|
||
|
|
||
|
cmsCIExyY WhitePt = {0.3127, 0.3290, 1.0};
|
||
|
cmsCIExyYTRIPLE Primaries = {
|
||
|
{0.6400, 0.3300, 1.0},
|
||
|
{0.3000, 0.6000, 1.0},
|
||
|
{0.1500, 0.0600, 1.0}
|
||
|
};
|
||
|
MAT3 sRGB = {{
|
||
|
{{ 0.436066, 0.385147, 0.143066 }},
|
||
|
{{ 0.222488, 0.716873, 0.060608 }},
|
||
|
{{ 0.013916, 0.097076, 0.714096 }}
|
||
|
}};
|
||
|
|
||
|
|
||
|
printf("Testing virtual profiles (Emulating sRGB)...");
|
||
|
|
||
|
rc = cmsBuildRGB2XYZtransferMatrix(&Mat,
|
||
|
&WhitePt,
|
||
|
&Primaries);
|
||
|
cmsAdaptMatrixToD50(&Mat, &WhitePt);
|
||
|
|
||
|
if (rc < 0)
|
||
|
{
|
||
|
printf("TestMatrixCreation failed, rc = %d\n", rc);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!CmpMatrix(&Mat, &sRGB, 0.001)) {
|
||
|
printf("FAILED!\n");
|
||
|
printf("sRGB final matrix is:\n");
|
||
|
PrintMatrix(&sRGB);
|
||
|
printf("\nlcms calculated matrix is:\n");
|
||
|
PrintMatrix(&Mat);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
printf("pass.\n");
|
||
|
return TRUE;
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
|
||
|
Used for debug purposes
|
||
|
*/
|
||
|
|
||
|
#if 0
|
||
|
static
|
||
|
void AdaptationMatrixTest(void)
|
||
|
{
|
||
|
cmsCIExyY D65 = {0.3127, 0.329001, 1.0}; // D65
|
||
|
MAT3 sRGB, TosRGB;
|
||
|
|
||
|
|
||
|
VEC3init(&sRGB.v[0], 0.4124, 0.3576, 0.1805);
|
||
|
VEC3init(&sRGB.v[1], 0.2126, 0.7152, 0.0722);
|
||
|
VEC3init(&sRGB.v[2], 0.0193, 0.1192, 0.9505);
|
||
|
|
||
|
cmsAdaptMatrixToD50(&sRGB, &D65);
|
||
|
printf("Adaptation matrix D65 -> D50 (to PCS)\n");
|
||
|
PrintMatrix(&sRGB);
|
||
|
|
||
|
MAT3inverse(&sRGB, &TosRGB);
|
||
|
printf("inverse\n");
|
||
|
PrintMatrix(&TosRGB);
|
||
|
|
||
|
cmsAdaptMatrixFromD50(&TosRGB, &D65);
|
||
|
printf("adaptated to D65\n");
|
||
|
PrintMatrix(&TosRGB);
|
||
|
}
|
||
|
#endif
|
||
|
// #endif
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
double VecDist(Scanline_rgb2 *bin, Scanline_rgb2 *bout)
|
||
|
{
|
||
|
double rdist, gdist, bdist;
|
||
|
|
||
|
rdist = fabs(bout -> r - bin -> r);
|
||
|
gdist = fabs(bout -> g - bin -> g);
|
||
|
bdist = fabs(bout -> b - bin -> b);
|
||
|
|
||
|
return (sqrt((rdist*rdist + gdist*gdist + bdist*bdist)));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// Perform sampling in the full spectrum & acotate error.
|
||
|
// I choose red for the lowest incidence in eye.
|
||
|
// Green is most lightful, eye is most accurate on blue.
|
||
|
|
||
|
static
|
||
|
int TestFullSpectrum(cmsHTRANSFORM xform, int nRedInterv, int MaxErr)
|
||
|
{
|
||
|
int r, g, b;
|
||
|
double err;
|
||
|
Scanline_rgb2 *bin, *bout;
|
||
|
STATS Stats;
|
||
|
clock_t t;
|
||
|
|
||
|
|
||
|
bin = (Scanline_rgb2 *) _cmsMalloc(256*sizeof(Scanline_rgb2));
|
||
|
bout = (Scanline_rgb2 *) _cmsMalloc(256*sizeof(Scanline_rgb2));
|
||
|
|
||
|
|
||
|
ClearStats(&Stats);
|
||
|
|
||
|
Stats.x = 0.0; Stats.n = 0.0; // GCC BUG HERE!!!!
|
||
|
|
||
|
t = clock();
|
||
|
|
||
|
for (r=0; r < 256; r+= nRedInterv)
|
||
|
{
|
||
|
// printf("\r%02x:", r);
|
||
|
|
||
|
Dot();
|
||
|
for (g=0; g < 256; g++)
|
||
|
{
|
||
|
|
||
|
for (b=0; b < 256; b++)
|
||
|
{
|
||
|
bin[b].r = (WORD) r << 8; // For L 0nly to 0xFF00
|
||
|
bin[b].g = RGB_8_TO_16(g);
|
||
|
bin[b].b = RGB_8_TO_16(b);
|
||
|
bin[b].a = 0;
|
||
|
}
|
||
|
|
||
|
cmsDoTransform(xform, bin, bout, 256);
|
||
|
|
||
|
// I'm using b as index
|
||
|
|
||
|
for (b=0; b < 256; b ++)
|
||
|
{
|
||
|
// I measure the error using vector distance
|
||
|
|
||
|
err = VecDist(bin+b, bout+b);
|
||
|
Stats.x += (double) err;
|
||
|
Stats.x2 += (double) err * err;
|
||
|
Stats.n += 1.0;
|
||
|
if (err > Stats.Peak)
|
||
|
Stats.Peak = err;
|
||
|
|
||
|
|
||
|
if (err > MaxErr)
|
||
|
{
|
||
|
printf("Coarse error! : In=(%x,%x,%x) Out=(%x,%x,%x)\n",
|
||
|
bin[b].r, bin[b].g, bin[b].b,
|
||
|
bout[b].r, bout[b].g, bout[b].b);
|
||
|
_cmsFree(bin);
|
||
|
_cmsFree(bout);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
PrintStatistics(t, &Stats);
|
||
|
_cmsFree(bin);
|
||
|
_cmsFree(bout);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestInducedError(DWORD Type)
|
||
|
{
|
||
|
cmsHPROFILE In, Out;
|
||
|
cmsHTRANSFORM xform;
|
||
|
int nMaxError;
|
||
|
|
||
|
In = cmsCreateLabProfile(NULL);
|
||
|
Out = cmsCreateLabProfile(NULL);
|
||
|
|
||
|
printf("Error Induced by the CMM due to roundoff (dE) ");
|
||
|
|
||
|
xform = cmsCreateTransform(In, Type,
|
||
|
Out, Type,
|
||
|
INTENT_RELATIVE_COLORIMETRIC, 0);
|
||
|
|
||
|
|
||
|
nMaxError = TestFullSpectrum(xform, 31, 0x800);
|
||
|
|
||
|
printf("\n");
|
||
|
|
||
|
cmsDeleteTransform(xform);
|
||
|
cmsCloseProfile(In);
|
||
|
cmsCloseProfile(Out);
|
||
|
|
||
|
return nMaxError;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
double ConvertL(WORD v)
|
||
|
{
|
||
|
int fix32;
|
||
|
|
||
|
fix32 = v;
|
||
|
return (double)fix32/652.800; /* 0xff00/100.0 */
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
double Convertab(WORD v)
|
||
|
{
|
||
|
int fix32;
|
||
|
|
||
|
|
||
|
fix32 = v;
|
||
|
|
||
|
return ((double)fix32/256.0)-128.0;
|
||
|
}
|
||
|
|
||
|
|
||
|
#define BASE 255
|
||
|
|
||
|
static
|
||
|
int CompareTransforms(cmsHTRANSFORM xform1, cmsHTRANSFORM xform2,
|
||
|
int nRedInterv, int lWithStats, LCMSBOOL lIsLab)
|
||
|
{
|
||
|
int r, g, b;
|
||
|
double err;
|
||
|
Scanline_rgb2 *bin, *bout1, *bout2;
|
||
|
STATS Stats;
|
||
|
int OutOfGamut = 0;
|
||
|
|
||
|
|
||
|
bin = (Scanline_rgb2 *) _cmsMalloc(256*sizeof(Scanline_rgb2));
|
||
|
bout1 = (Scanline_rgb2 *) _cmsMalloc(256*sizeof(Scanline_rgb2));
|
||
|
bout2 = (Scanline_rgb2 *) _cmsMalloc(256*sizeof(Scanline_rgb2));
|
||
|
|
||
|
|
||
|
ClearStats(&Stats);
|
||
|
Stats.x = 0.0; Stats.n = 0.0; // GCC BUG HERE!!!!
|
||
|
|
||
|
|
||
|
for (r=0; r <= BASE; r+= nRedInterv)
|
||
|
{
|
||
|
// printf("\r%02x:", r);
|
||
|
|
||
|
Dot();
|
||
|
for (g=0; g <= BASE; g++)
|
||
|
{
|
||
|
// I will test random LSB
|
||
|
|
||
|
for (b=0; b <= BASE; b++) // 256
|
||
|
{
|
||
|
|
||
|
bin[b].r = RGB_8_TO_16(r);
|
||
|
bin[b].g = RGB_8_TO_16(g);
|
||
|
bin[b].b = RGB_8_TO_16(b);
|
||
|
bin[b].a = 0;
|
||
|
}
|
||
|
|
||
|
cmsDoTransform(xform1, bin, bout1, 256);
|
||
|
cmsDoTransform(xform2, bin, bout2, 256);
|
||
|
|
||
|
// I'm using b as index
|
||
|
|
||
|
for (b=0; b <= BASE; b ++) {
|
||
|
|
||
|
// I measure the error using vector distance
|
||
|
// Only if encodable values
|
||
|
|
||
|
if (bout1[b].r != 0xffff && bout1[b].g != 0xffff && bout1[b].b != 0xffff)
|
||
|
{
|
||
|
|
||
|
err = VecDist(bout1+b, bout2+b);
|
||
|
|
||
|
|
||
|
if (err > 0x1000L)
|
||
|
{
|
||
|
|
||
|
if (lIsLab) {
|
||
|
printf("Coarse error: In=(%x,%x,%x) Out1=(%g,%g,%g) Out2=(%g,%g,%g)\n",
|
||
|
bin[b].r, bin[b].g, bin[b].b,
|
||
|
ConvertL(bout1[b].r), Convertab(bout1[b].g), Convertab(bout1[b].b),
|
||
|
ConvertL(bout2[b].r), Convertab(bout2[b].g), Convertab(bout2[b].b));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
printf("Coarse error: In=(%x,%x,%x) Out1=(%x,%x,%x) Out2=(%x,%x,%x)\n",
|
||
|
bin[b].r, bin[b].g, bin[b].b,
|
||
|
bout1[b].r, bout1[b].g, bout1[b].b,
|
||
|
bout2[b].r, bout2[b].g, bout2[b].b);
|
||
|
}
|
||
|
return 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
Stats.x += (double) err;
|
||
|
Stats.x2 += (double) err * err;
|
||
|
Stats.n += 1.0;
|
||
|
if (err > Stats.Peak)
|
||
|
Stats.Peak = err;
|
||
|
}
|
||
|
} else
|
||
|
OutOfGamut++;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
if (lWithStats) {
|
||
|
|
||
|
PrintStatistics(0, &Stats);
|
||
|
printf(" pass.\n");
|
||
|
}
|
||
|
|
||
|
if (OutOfGamut > 0)
|
||
|
printf("Out of encodeable representation=%d\n\n", OutOfGamut);
|
||
|
|
||
|
|
||
|
_cmsFree(bin);
|
||
|
_cmsFree(bout1);
|
||
|
_cmsFree(bout2);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
LCMSBOOL CheckXYZ(LPcmsCIEXYZ Check, double X, double Y, double Z)
|
||
|
{
|
||
|
return ((fabs(Check->X - X) < 0.001) &&
|
||
|
(fabs(Check->Y - Y) < 0.001) &&
|
||
|
(fabs(Check->Z - Z) < 0.001));
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
LPGAMMATABLE Build_sRGBGamma(void)
|
||
|
{
|
||
|
double Parameters[5];
|
||
|
|
||
|
Parameters[0] = 2.4;
|
||
|
Parameters[1] = 1. / 1.055;
|
||
|
Parameters[2] = 0.055 / 1.055;
|
||
|
Parameters[3] = 1. / 12.92;
|
||
|
Parameters[4] = 0.04045; // d
|
||
|
|
||
|
return cmsBuildParametricGamma(1024, 4, Parameters);
|
||
|
}
|
||
|
|
||
|
static
|
||
|
LCMSBOOL Check_sRGBGamma(LPGAMMATABLE Shape)
|
||
|
{
|
||
|
LPGAMMATABLE sRGB = Build_sRGBGamma();
|
||
|
int i;
|
||
|
|
||
|
if (Shape ->nEntries != 1024) {
|
||
|
printf("because wrong sizes (%d != 1024), ", Shape -> nEntries);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
for (i=0; i < Shape -> nEntries; i++) {
|
||
|
double nErr = Shape ->GammaTable[i] - sRGB ->GammaTable[i];
|
||
|
|
||
|
if (fabs(nErr) > 1.0) {
|
||
|
int j;
|
||
|
printf("because %x != %x on index %d\n", Shape ->GammaTable[i], sRGB ->GammaTable[i], i);
|
||
|
printf("table dump follows:\n");
|
||
|
for (j=0; j < 10; j++)
|
||
|
printf("%d) %X\n", j, Shape ->GammaTable[j]);
|
||
|
printf("\nso, ");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cmsFreeGamma(sRGB);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int GetInfoTest(void)
|
||
|
{
|
||
|
cmsHPROFILE hProfile;
|
||
|
cmsCIEXYZ WhitePoint;
|
||
|
cmsCIEXYZTRIPLE Primaries;
|
||
|
const char* Product;
|
||
|
LPGAMMATABLE Shapes[3];
|
||
|
|
||
|
|
||
|
printf("Testing profile decoding (sRGB)");
|
||
|
hProfile = cmsOpenProfileFromFile("sRGB Color Space Profile.icm", "rb");
|
||
|
cmsTakeMediaWhitePoint(&WhitePoint, hProfile);
|
||
|
|
||
|
Dot();
|
||
|
if (!CheckXYZ(&WhitePoint, 0.95045, 1.0, 1.08905)) {
|
||
|
printf("White point read failed!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
Dot();
|
||
|
cmsTakeColorants(&Primaries, hProfile);
|
||
|
|
||
|
if (!CheckXYZ(&Primaries.Red, 0.43607, 0.22249, 0.01392)) {
|
||
|
printf("Red colorant failed!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!CheckXYZ(&Primaries.Green, 0.38515,0.71617, 0.09708)) {
|
||
|
printf("Green colorant failed!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!CheckXYZ(&Primaries.Blue, 0.14307, 0.06061, 0.71410)) {
|
||
|
printf("Blue colorant failed!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
Dot();
|
||
|
Product = cmsTakeProductName(hProfile);
|
||
|
if (strcmp(Product, "IEC 61966-2.1 Default RGB colour space - sRGB") != 0) {
|
||
|
printf("Product name mismatch!\n");
|
||
|
}
|
||
|
|
||
|
Dot();
|
||
|
Shapes[0] = cmsReadICCGamma(hProfile, icSigRedTRCTag);
|
||
|
Shapes[1] = cmsReadICCGamma(hProfile, icSigGreenTRCTag);
|
||
|
Shapes[2] = cmsReadICCGamma(hProfile, icSigBlueTRCTag);
|
||
|
|
||
|
if (!Check_sRGBGamma(Shapes[0]) ||
|
||
|
!Check_sRGBGamma(Shapes[1]) ||
|
||
|
!Check_sRGBGamma(Shapes[2])) {
|
||
|
printf("Gamma curves mismatch!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
cmsFreeGammaTriple(Shapes);
|
||
|
|
||
|
|
||
|
Dot();
|
||
|
cmsCloseProfile(hProfile);
|
||
|
printf("pass.\n");
|
||
|
return 1;
|
||
|
|
||
|
}
|
||
|
static
|
||
|
int Test_sRGB(void)
|
||
|
{
|
||
|
cmsHPROFILE In1, In2, Out1, Out2;
|
||
|
cmsHTRANSFORM xform1, xform2;
|
||
|
int nMaxErr;
|
||
|
|
||
|
printf("Testing sRGB built-in space");
|
||
|
|
||
|
In1 = cmsOpenProfileFromFile("sRGB Color Space Profile.icm", "rb");
|
||
|
Out1 = cmsCreateXYZProfile();
|
||
|
|
||
|
In2 = cmsCreate_sRGBProfile();
|
||
|
Out2 = cmsCreateXYZProfile();
|
||
|
|
||
|
xform1 = cmsCreateTransform(In1, TYPE_RGBA_16, Out1, TYPE_XYZA_16, 0, cmsFLAGS_NOTPRECALC);
|
||
|
xform2 = cmsCreateTransform(In2, TYPE_RGBA_16, Out2, TYPE_XYZA_16, 0, cmsFLAGS_NOTPRECALC);
|
||
|
|
||
|
nMaxErr = CompareTransforms(xform1, xform2, 31, TRUE, FALSE);
|
||
|
|
||
|
cmsDeleteTransform(xform1);
|
||
|
cmsCloseProfile(In1);
|
||
|
cmsCloseProfile(Out1);
|
||
|
|
||
|
cmsDeleteTransform(xform2);
|
||
|
cmsCloseProfile(In2);
|
||
|
cmsCloseProfile(Out2);
|
||
|
|
||
|
return nMaxErr;
|
||
|
|
||
|
}
|
||
|
|
||
|
static
|
||
|
int RealProfilesTest(void)
|
||
|
{
|
||
|
cmsHPROFILE In1, In2, Out1, Out2;
|
||
|
cmsHTRANSFORM xform1, xform2;
|
||
|
int nMaxErr;
|
||
|
|
||
|
printf("Using two real profiles");
|
||
|
|
||
|
// sRGB is a simpler, public domain XYZ PCS profile
|
||
|
// sRGBSpac comes with Win95 Platform SDK, in the public domain.
|
||
|
// (not latest revisions)
|
||
|
|
||
|
// Using LAB identity as output profile, I'm forcing an
|
||
|
// implicit XYZ => L*a*b conversion in xform1.
|
||
|
// xform2 is 8 bits - LUT based, and PCS is L*a*b
|
||
|
|
||
|
In1 = cmsOpenProfileFromFile("sRGB Color Space Profile.icm", "rb");
|
||
|
Out1 = cmsCreateXYZProfile();
|
||
|
|
||
|
In2 = cmsOpenProfileFromFile("sRGBSpac.icm", "rb");
|
||
|
Out2 = cmsCreateXYZProfile();
|
||
|
|
||
|
|
||
|
// Since LUT is 8-bits width,
|
||
|
xform1 = cmsCreateTransform(In1, TYPE_RGBA_16, Out1, TYPE_XYZA_16, 0, cmsFLAGS_NOTPRECALC|cmsFLAGS_MATRIXINPUT);
|
||
|
xform2 = cmsCreateTransform(In2, TYPE_RGBA_16, Out2, TYPE_XYZA_16, 0, cmsFLAGS_NOTPRECALC);
|
||
|
|
||
|
nMaxErr = CompareTransforms(xform1, xform2, 31, FALSE, FALSE);
|
||
|
|
||
|
printf("pass\n");
|
||
|
|
||
|
cmsDeleteTransform(xform1);
|
||
|
cmsCloseProfile(In1);
|
||
|
cmsCloseProfile(Out1);
|
||
|
|
||
|
cmsDeleteTransform(xform2);
|
||
|
cmsCloseProfile(In2);
|
||
|
cmsCloseProfile(Out2);
|
||
|
|
||
|
return nMaxErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// ---------------------------------------------------------
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestPreview(void)
|
||
|
{
|
||
|
cmsHPROFILE In, Out, Proof;
|
||
|
cmsHTRANSFORM xform;
|
||
|
int nMaxErr;
|
||
|
|
||
|
printf("Testing preview");
|
||
|
|
||
|
In = cmsCreateLabProfile(NULL);
|
||
|
Out = cmsCreateLabProfile(NULL);
|
||
|
Proof = cmsCreateLabProfile(NULL);
|
||
|
|
||
|
xform = cmsCreateProofingTransform(In, TYPE_LABA_16, Out, TYPE_LABA_16, Proof, 0, 0, cmsFLAGS_SOFTPROOFING);
|
||
|
|
||
|
nMaxErr = TestFullSpectrum(xform, 31, 0x1000L);
|
||
|
|
||
|
cmsDeleteTransform(xform);
|
||
|
cmsCloseProfile(In);
|
||
|
cmsCloseProfile(Out);
|
||
|
cmsCloseProfile(Proof);
|
||
|
printf("\n");
|
||
|
|
||
|
return nMaxErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Check induced error on multiprofile transforms
|
||
|
|
||
|
static
|
||
|
int TestMultiprofile(void)
|
||
|
{
|
||
|
|
||
|
cmsHPROFILE hsRGB, hXYZ, hLab;
|
||
|
cmsHTRANSFORM hXForm;
|
||
|
cmsHPROFILE Profiles[10];
|
||
|
int nMaxErr;
|
||
|
|
||
|
hsRGB = cmsCreate_sRGBProfile();
|
||
|
hLab = cmsCreateLabProfile(NULL);
|
||
|
hXYZ = cmsCreateXYZProfile();
|
||
|
|
||
|
Profiles[0] = hsRGB;
|
||
|
Profiles[1] = hLab;
|
||
|
Profiles[2] = hsRGB;
|
||
|
Profiles[3] = hsRGB;
|
||
|
|
||
|
Profiles[4] = hLab;
|
||
|
Profiles[5] = hXYZ;
|
||
|
Profiles[6] = hsRGB;
|
||
|
|
||
|
hXForm = cmsCreateMultiprofileTransform(Profiles, 7, TYPE_RGBA_16, TYPE_RGBA_16, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_HIGHRESPRECALC);
|
||
|
|
||
|
printf("Testing multiprofile transforms (6 profiles)");
|
||
|
|
||
|
nMaxErr = TestFullSpectrum(hXForm, 31, 0x1000L);
|
||
|
|
||
|
cmsDeleteTransform(hXForm);
|
||
|
cmsCloseProfile(hsRGB);
|
||
|
cmsCloseProfile(hXYZ);
|
||
|
cmsCloseProfile(hLab);
|
||
|
|
||
|
printf("\n");
|
||
|
|
||
|
return nMaxErr;
|
||
|
}
|
||
|
|
||
|
// Check linearization and other goodies
|
||
|
|
||
|
static
|
||
|
int TestLinearizationDevicelink()
|
||
|
{
|
||
|
LPGAMMATABLE Transfer[3];
|
||
|
cmsHPROFILE hLin1, hLin2;
|
||
|
cmsHTRANSFORM hXForm;
|
||
|
cmsHPROFILE Profiles[10];
|
||
|
int nMaxErr;
|
||
|
|
||
|
printf("Testing linearization devicelink");
|
||
|
|
||
|
Transfer[0] = cmsBuildGamma(256, 1./2.2);
|
||
|
Transfer[1] = cmsBuildGamma(256, 1./2.2);
|
||
|
Transfer[2] = cmsBuildGamma(256, 1./2.2);
|
||
|
|
||
|
hLin1 = cmsCreateLinearizationDeviceLink(icSigRgbData, Transfer);
|
||
|
|
||
|
cmsFreeGammaTriple(Transfer);
|
||
|
|
||
|
|
||
|
|
||
|
Transfer[0] = cmsBuildGamma(256, 2.2);
|
||
|
Transfer[1] = cmsBuildGamma(256, 2.2);
|
||
|
Transfer[2] = cmsBuildGamma(256, 2.2);
|
||
|
|
||
|
hLin2 = cmsCreateLinearizationDeviceLink(icSigRgbData, Transfer);
|
||
|
|
||
|
cmsFreeGammaTriple(Transfer);
|
||
|
|
||
|
Profiles[0] = hLin1;
|
||
|
Profiles[1] = hLin2;
|
||
|
|
||
|
hXForm = cmsCreateMultiprofileTransform(Profiles, 2, TYPE_RGBA_16, TYPE_RGBA_16, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_HIGHRESPRECALC);
|
||
|
if (!hXForm) {
|
||
|
|
||
|
printf("Error!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
nMaxErr = TestFullSpectrum(hXForm, 31, 0x1000L);
|
||
|
|
||
|
cmsDeleteTransform(hXForm);
|
||
|
cmsCloseProfile(hLin1);
|
||
|
cmsCloseProfile(hLin2);
|
||
|
|
||
|
printf("\n");
|
||
|
|
||
|
return nMaxErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestLinearizationDevicelink2()
|
||
|
{
|
||
|
LPGAMMATABLE Transfer[3];
|
||
|
cmsHPROFILE hLin1;
|
||
|
cmsHTRANSFORM hXForm;
|
||
|
int nMaxErr;
|
||
|
|
||
|
printf("Testing saved linearization devicelink");
|
||
|
|
||
|
Transfer[0] = cmsBuildGamma(256, 1);
|
||
|
Transfer[1] = cmsBuildGamma(256, 1);
|
||
|
Transfer[2] = cmsBuildGamma(256, 1);
|
||
|
|
||
|
hLin1 = cmsCreateLinearizationDeviceLink(icSigRgbData, Transfer);
|
||
|
|
||
|
_cmsSaveProfile(hLin1, "lin1.icc");
|
||
|
cmsFreeGammaTriple(Transfer);
|
||
|
cmsCloseProfile(hLin1);
|
||
|
|
||
|
hLin1 = cmsOpenProfileFromFile("lin1.icc", "r");
|
||
|
|
||
|
hXForm = cmsCreateTransform(hLin1, TYPE_RGBA_16, NULL, TYPE_RGBA_16, INTENT_ABSOLUTE_COLORIMETRIC, 0);
|
||
|
|
||
|
if (!hXForm) {
|
||
|
|
||
|
printf("Error!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
nMaxErr = TestFullSpectrum(hXForm, 31, 1);
|
||
|
|
||
|
cmsDeleteTransform(hXForm);
|
||
|
cmsCloseProfile(hLin1);
|
||
|
unlink("lin1.icc");
|
||
|
|
||
|
printf("\n");
|
||
|
|
||
|
return nMaxErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestDeviceLinkGeneration()
|
||
|
{
|
||
|
cmsHTRANSFORM hXForm, hIdentity;
|
||
|
cmsHPROFILE hDevLink, hsRGB;
|
||
|
int nMaxErr;
|
||
|
|
||
|
|
||
|
printf("Testing devicelink generation");
|
||
|
|
||
|
hsRGB = cmsOpenProfileFromFile("sRGB Color Space Profile.icm", "r");
|
||
|
hIdentity = cmsCreateTransform(hsRGB, TYPE_RGBA_16, hsRGB, TYPE_RGBA_16, INTENT_RELATIVE_COLORIMETRIC, 0);
|
||
|
hDevLink = cmsTransform2DeviceLink(hIdentity, 0);
|
||
|
_cmsSaveProfile(hDevLink, "devicelink.icm");
|
||
|
|
||
|
cmsCloseProfile(hDevLink);
|
||
|
cmsCloseProfile(hsRGB);
|
||
|
cmsDeleteTransform(hIdentity);
|
||
|
|
||
|
|
||
|
hDevLink = cmsOpenProfileFromFile("devicelink.icm", "r");
|
||
|
hXForm = cmsCreateTransform(hDevLink, TYPE_RGBA_16, NULL, TYPE_RGBA_16, INTENT_RELATIVE_COLORIMETRIC, 0);
|
||
|
nMaxErr = TestFullSpectrum(hXForm, 31, 0x1000L);
|
||
|
|
||
|
cmsDeleteTransform(hXForm);
|
||
|
cmsCloseProfile(hDevLink);
|
||
|
|
||
|
printf("\n");
|
||
|
unlink("devicelink.icm");
|
||
|
|
||
|
return nMaxErr;
|
||
|
}
|
||
|
|
||
|
static
|
||
|
int TestInkLimiting()
|
||
|
{
|
||
|
cmsHPROFILE hIL;
|
||
|
cmsHTRANSFORM hXForm;
|
||
|
BYTE In[4], Out[4];
|
||
|
int i, j, k, l, res;
|
||
|
|
||
|
|
||
|
|
||
|
printf("Testing ink limiting ");
|
||
|
|
||
|
hIL = cmsCreateInkLimitingDeviceLink(icSigCmykData, 100);
|
||
|
|
||
|
|
||
|
hXForm = cmsCreateTransform(hIL, TYPE_CMYK_8, NULL, TYPE_CMYK_8, INTENT_RELATIVE_COLORIMETRIC, 0);
|
||
|
if (!hXForm) {
|
||
|
|
||
|
printf("Error!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for (l=0; l < 255; l += 8) {
|
||
|
Dot();
|
||
|
for (k=0; k < 255; k += 8)
|
||
|
for (j=0; j < 255; j += 8)
|
||
|
for (i=0; i < 255; i += 8) {
|
||
|
|
||
|
In[0] = (BYTE) i; In[1] = (BYTE) j; In[2] = (BYTE) k; In[3] = (BYTE) l;
|
||
|
|
||
|
cmsDoTransform(hXForm, In, Out, 1);
|
||
|
|
||
|
res = Out[0] + Out[1] + Out[2] + Out[3];
|
||
|
|
||
|
if (res > 0x100) {
|
||
|
|
||
|
printf("Failed! (%d) \n", res);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cmsDeleteTransform(hXForm);
|
||
|
cmsCloseProfile(hIL);
|
||
|
printf(" pass.\n");
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
void CheckPlanar(void)
|
||
|
{
|
||
|
cmsHTRANSFORM xform;
|
||
|
cmsHPROFILE hsRGB;
|
||
|
int i;
|
||
|
BYTE Out[12];
|
||
|
BYTE Bmp[] = { 0x00, 0x10, 0x20, 0x30, // R Plane
|
||
|
0x00, 0x10, 0x20, 0x30, // G Plane
|
||
|
0x00, 0x10, 0x20, 0x30 }; // B Plane
|
||
|
|
||
|
|
||
|
|
||
|
hsRGB = cmsCreate_sRGBProfile();
|
||
|
xform = cmsCreateTransform(hsRGB, TYPE_RGB_8_PLANAR,
|
||
|
hsRGB, TYPE_RGB_8_PLANAR,
|
||
|
INTENT_PERCEPTUAL, cmsFLAGS_NOTPRECALC);
|
||
|
|
||
|
cmsDoTransform(xform, Bmp, Out, 4);
|
||
|
|
||
|
for (i=0; i < 12; i += 3) {
|
||
|
printf("RGB=(%x, %x, %x)\n", Out[i+0], Out[i+1], Out[i+2]);
|
||
|
}
|
||
|
|
||
|
cmsDeleteTransform(xform);
|
||
|
cmsCloseProfile(hsRGB);
|
||
|
}
|
||
|
|
||
|
#ifdef ICM_COMPARATIVE
|
||
|
#ifndef NON_WINDOWS
|
||
|
|
||
|
static
|
||
|
void CompareWithICM_16bit(void)
|
||
|
{
|
||
|
|
||
|
HTRANSFORM hICMxform;
|
||
|
HPROFILE hICMProfileFrom, hICMProfileTo;
|
||
|
LOGCOLORSPACE LogColorSpace;
|
||
|
COLOR In, Out;
|
||
|
COLOR *InBlk, *OutBlk, *InPtr;
|
||
|
size_t size;
|
||
|
int r, g, b;
|
||
|
PROFILE Profile;
|
||
|
clock_t atime;
|
||
|
double seconds, diff;
|
||
|
cmsHPROFILE hlcmsProfileIn, hlcmsProfileOut;
|
||
|
cmsHTRANSFORM hlcmsxform;
|
||
|
|
||
|
|
||
|
printf("\n\nComparative with MS-Windows ICM (16 bits per sample):\n");
|
||
|
|
||
|
|
||
|
Profile.dwType = PROFILE_FILENAME;
|
||
|
Profile.pProfileData = "sRGBSpac.icm";
|
||
|
Profile.cbDataSize = strlen("sRGBSpac.icm");
|
||
|
|
||
|
hICMProfileFrom = OpenColorProfile(&Profile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
|
||
|
|
||
|
Profile.pProfileData = "sRGBSpac.icm";
|
||
|
Profile.cbDataSize = strlen("sRGBSpac.icm");
|
||
|
hICMProfileTo = OpenColorProfile(&Profile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
|
||
|
|
||
|
ZeroMemory(&LogColorSpace, sizeof(LOGCOLORSPACE));
|
||
|
|
||
|
LogColorSpace.lcsSignature = LCS_SIGNATURE;
|
||
|
LogColorSpace.lcsVersion = 0x400;
|
||
|
LogColorSpace.lcsCSType = LCS_CALIBRATED_RGB;
|
||
|
strcpy(LogColorSpace.lcsFilename, "sRGBSpac.icm");
|
||
|
|
||
|
hICMxform = CreateColorTransform(&LogColorSpace, hICMProfileTo, NULL, BEST_MODE);
|
||
|
|
||
|
|
||
|
|
||
|
size = 256 * 256 * 256;
|
||
|
InBlk = _cmsMalloc((size_t) size * sizeof(COLOR));
|
||
|
OutBlk = _cmsMalloc((size_t) size * sizeof(COLOR));
|
||
|
|
||
|
if (InBlk == NULL || OutBlk == NULL) {
|
||
|
printf("Out of memory\n"); exit(2);
|
||
|
}
|
||
|
|
||
|
printf("Windows ICM is transforming full spectrum...");
|
||
|
|
||
|
InPtr = InBlk;
|
||
|
for (r=0; r < 255; r++)
|
||
|
for (g=0; g < 255; g++)
|
||
|
for (b=0; b < 255; b++) {
|
||
|
|
||
|
InPtr->rgb.red = (r << 8) | r;
|
||
|
InPtr->rgb.green = (g << 8) | g;
|
||
|
InPtr->rgb.blue = (b << 8) | b;
|
||
|
|
||
|
InPtr++;
|
||
|
}
|
||
|
|
||
|
atime = clock();
|
||
|
|
||
|
TranslateColors( hICMxform, InBlk, size, COLOR_RGB, OutBlk, COLOR_RGB);
|
||
|
|
||
|
diff = clock() - atime;
|
||
|
seconds = (double) diff / CLOCKS_PER_SEC;
|
||
|
|
||
|
|
||
|
printf("done. [%d tics, %g sec.]\n", (int) diff, seconds);
|
||
|
|
||
|
CloseColorProfile(hICMProfileFrom);
|
||
|
CloseColorProfile(hICMProfileTo);
|
||
|
DeleteColorTransform(hICMxform);
|
||
|
|
||
|
hlcmsProfileIn = cmsOpenProfileFromFile("sRGBSpac.icm", "r");
|
||
|
hlcmsProfileOut = cmsOpenProfileFromFile("sRGBSpac.icm", "r");
|
||
|
|
||
|
hlcmsxform = cmsCreateTransform(hlcmsProfileIn, TYPE_RGB_16, hlcmsProfileOut, TYPE_RGB_16, INTENT_PERCEPTUAL, 0);
|
||
|
|
||
|
printf("lcms is transforming full spectrum...");
|
||
|
|
||
|
atime = clock();
|
||
|
|
||
|
cmsDoTransform(hlcmsxform, InBlk, OutBlk, size);
|
||
|
|
||
|
diff = clock() - atime;
|
||
|
seconds = (double) diff / CLOCKS_PER_SEC;
|
||
|
|
||
|
printf("done. [%d tics, %g sec.]\n", (int) diff, seconds);
|
||
|
|
||
|
cmsDeleteTransform(hlcmsxform);
|
||
|
cmsCloseProfile(hlcmsProfileIn);
|
||
|
cmsCloseProfile(hlcmsProfileOut);
|
||
|
|
||
|
_cmsFree(InBlk);
|
||
|
_cmsFree(OutBlk);
|
||
|
}
|
||
|
|
||
|
static
|
||
|
void CompareWithICM_8bit(void)
|
||
|
{
|
||
|
|
||
|
HTRANSFORM hICMxform;
|
||
|
HPROFILE hICMProfileFrom, hICMProfileTo;
|
||
|
LOGCOLORSPACE LogColorSpace;
|
||
|
RGBQUAD In, Out;
|
||
|
int r, g, b;
|
||
|
PROFILE Profile;
|
||
|
clock_t atime;
|
||
|
double seconds, diff;
|
||
|
cmsHPROFILE hlcmsProfileIn, hlcmsProfileOut;
|
||
|
cmsHTRANSFORM hlcmsxform;
|
||
|
|
||
|
|
||
|
printf("\n\nComparative with MS-Windows ICM (8 bits per sample):\n");
|
||
|
|
||
|
|
||
|
Profile.dwType = PROFILE_FILENAME;
|
||
|
Profile.pProfileData = "sRGBSpac.icm";
|
||
|
Profile.cbDataSize = strlen("sRGBSpac.icm");
|
||
|
|
||
|
hICMProfileFrom = OpenColorProfile(&Profile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
|
||
|
|
||
|
Profile.pProfileData = "sRGBSpac.icm";
|
||
|
Profile.cbDataSize = strlen("sRGBSpac.icm");
|
||
|
hICMProfileTo = OpenColorProfile(&Profile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
|
||
|
|
||
|
ZeroMemory(&LogColorSpace, sizeof(LOGCOLORSPACE));
|
||
|
|
||
|
LogColorSpace.lcsSignature = LCS_SIGNATURE;
|
||
|
LogColorSpace.lcsVersion = 0x400;
|
||
|
LogColorSpace.lcsCSType = LCS_CALIBRATED_RGB;
|
||
|
strcpy(LogColorSpace.lcsFilename, "sRGBSpac.icm");
|
||
|
|
||
|
hICMxform = CreateColorTransform(&LogColorSpace, hICMProfileTo, NULL, BEST_MODE);
|
||
|
|
||
|
printf("Windows ICM is transforming full spectrum...");
|
||
|
|
||
|
atime = clock();
|
||
|
|
||
|
for (r=0; r < 255; r++)
|
||
|
for (g=0; g < 255; g++)
|
||
|
for (b=0; b < 255; b++) {
|
||
|
|
||
|
In.rgbRed = r;
|
||
|
In.rgbGreen = g;
|
||
|
In.rgbBlue = b;
|
||
|
|
||
|
if (!TranslateBitmapBits(hICMxform, &In, BM_RGBTRIPLETS, 1, 1, 0, &Out, BM_RGBTRIPLETS, 0, NULL, 0))
|
||
|
exit(2);
|
||
|
|
||
|
}
|
||
|
|
||
|
diff = clock() - atime;
|
||
|
seconds = (double) diff / CLOCKS_PER_SEC;
|
||
|
|
||
|
|
||
|
printf("done. [%d tics, %g sec.]\n", (int) diff, seconds);
|
||
|
|
||
|
CloseColorProfile(hICMProfileFrom);
|
||
|
CloseColorProfile(hICMProfileTo);
|
||
|
DeleteColorTransform(hICMxform);
|
||
|
|
||
|
hlcmsProfileIn = cmsOpenProfileFromFile("sRGBSpac.icm", "r");
|
||
|
hlcmsProfileOut = cmsOpenProfileFromFile("sRGBSpac.icm", "r");
|
||
|
|
||
|
hlcmsxform = cmsCreateTransform(hlcmsProfileIn, TYPE_BGRA_8, hlcmsProfileOut, TYPE_BGRA_8, INTENT_PERCEPTUAL, 0);
|
||
|
|
||
|
printf("lcms is transforming full spectrum...");
|
||
|
|
||
|
atime = clock();
|
||
|
|
||
|
for (r=0; r < 255; r++)
|
||
|
for (g=0; g < 255; g++)
|
||
|
for (b=0; b < 255; b++) {
|
||
|
|
||
|
In.rgbRed = r;
|
||
|
In.rgbGreen = g;
|
||
|
In.rgbBlue = b;
|
||
|
|
||
|
cmsDoTransform(hlcmsxform, &In, &Out, 1);
|
||
|
}
|
||
|
|
||
|
diff = clock() - atime;
|
||
|
seconds = (double) diff / CLOCKS_PER_SEC;
|
||
|
|
||
|
printf("done. [%d tics, %g sec.]\n", (int) diff, seconds);
|
||
|
|
||
|
cmsDeleteTransform(hlcmsxform);
|
||
|
cmsCloseProfile(hlcmsProfileIn);
|
||
|
cmsCloseProfile(hlcmsProfileOut);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#ifdef CHECK_SPEED
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
void SpeedTest(void)
|
||
|
{
|
||
|
|
||
|
int r, g, b, j;
|
||
|
clock_t atime;
|
||
|
double seconds, diff;
|
||
|
cmsHPROFILE hlcmsProfileIn, hlcmsProfileOut;
|
||
|
cmsHTRANSFORM hlcmsxform;
|
||
|
Scanline_rgb0 *In;
|
||
|
size_t Mb;
|
||
|
|
||
|
hlcmsProfileIn = cmsOpenProfileFromFile("sRGB Color Space Profile.icm", "r");
|
||
|
hlcmsProfileOut = cmsOpenProfileFromFile("sRGBSpac.icm", "r");
|
||
|
|
||
|
hlcmsxform = cmsCreateTransform(hlcmsProfileIn, TYPE_RGB_16, hlcmsProfileOut, TYPE_RGB_16, INTENT_PERCEPTUAL, cmsFLAGS_NOTCACHE);
|
||
|
|
||
|
Mb = 256*256*256*sizeof(Scanline_rgb0);
|
||
|
|
||
|
In = (Scanline_rgb0*) _cmsMalloc(Mb);
|
||
|
|
||
|
j = 0;
|
||
|
for (r=0; r < 256; r++)
|
||
|
for (g=0; g < 256; g++)
|
||
|
for (b=0; b < 256; b++) {
|
||
|
|
||
|
In[j].r = (WORD) ((r << 8) | r);
|
||
|
In[j].g = (WORD) ((g << 8) | g);
|
||
|
In[j].b = (WORD) ((b << 8) | b);
|
||
|
|
||
|
j++;
|
||
|
}
|
||
|
|
||
|
|
||
|
printf("lcms is transforming full spectrum...");
|
||
|
|
||
|
atime = clock();
|
||
|
|
||
|
cmsDoTransform(hlcmsxform, In, In, 256*256*256);
|
||
|
|
||
|
diff = clock() - atime;
|
||
|
seconds = (double) diff / CLOCKS_PER_SEC;
|
||
|
_cmsFree(In);
|
||
|
|
||
|
|
||
|
printf("done.\n[%d tics, %g sec, %g Mpixel/sec.]\n", (int) diff, seconds, Mb / (1024*1024*seconds*3*2) );
|
||
|
|
||
|
cmsDeleteTransform(hlcmsxform);
|
||
|
cmsCloseProfile(hlcmsProfileIn);
|
||
|
cmsCloseProfile(hlcmsProfileOut);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
void SpeedTest2(void)
|
||
|
{
|
||
|
|
||
|
int r, g, b, j;
|
||
|
clock_t atime;
|
||
|
double seconds, diff;
|
||
|
cmsHPROFILE hlcmsProfileIn, hlcmsProfileOut;
|
||
|
cmsHTRANSFORM hlcmsxform;
|
||
|
Scanline_rgb8 *In;
|
||
|
size_t Mb;
|
||
|
|
||
|
|
||
|
hlcmsProfileIn = cmsOpenProfileFromFile("sRGB Color Space Profile.icm", "r");
|
||
|
hlcmsProfileOut = cmsOpenProfileFromFile("sRGBSpac.icm", "r");
|
||
|
|
||
|
hlcmsxform = cmsCreateTransform(hlcmsProfileIn, TYPE_RGB_8, hlcmsProfileOut, TYPE_RGB_8, INTENT_PERCEPTUAL, cmsFLAGS_NOTCACHE);
|
||
|
|
||
|
Mb = 256*256*256*sizeof(Scanline_rgb8);
|
||
|
|
||
|
In = (Scanline_rgb8*) _cmsMalloc(Mb);
|
||
|
|
||
|
j = 0;
|
||
|
for (r=0; r < 256; r++)
|
||
|
for (g=0; g < 256; g++)
|
||
|
for (b=0; b < 256; b++) {
|
||
|
|
||
|
In[j].r = (BYTE) r;
|
||
|
In[j].g = (BYTE) g;
|
||
|
In[j].b = (BYTE) b;
|
||
|
|
||
|
j++;
|
||
|
}
|
||
|
|
||
|
|
||
|
printf("lcms is transforming full spectrum...");
|
||
|
|
||
|
atime = clock();
|
||
|
|
||
|
cmsDoTransform(hlcmsxform, In, In, 256*256*256);
|
||
|
|
||
|
diff = clock() - atime;
|
||
|
seconds = (double) diff / CLOCKS_PER_SEC;
|
||
|
_cmsFree(In);
|
||
|
|
||
|
|
||
|
printf("done.\n[%d tics, %g sec, %g Mpixels/sec.]\n", (int) diff, seconds, Mb / (1024*1024*seconds*3) );
|
||
|
|
||
|
cmsDeleteTransform(hlcmsxform);
|
||
|
cmsCloseProfile(hlcmsProfileIn);
|
||
|
cmsCloseProfile(hlcmsProfileOut);
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestSaveToMem(void)
|
||
|
{
|
||
|
void *memPtr=0;
|
||
|
size_t bytesNeeded=0;
|
||
|
int rc = FALSE;
|
||
|
cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
|
||
|
|
||
|
printf("Testing save to memory: ");
|
||
|
|
||
|
// pass 1 - compute length
|
||
|
if (!_cmsSaveProfileToMem(hProfile, memPtr, &bytesNeeded)) {
|
||
|
printf("Failed!\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
// pass 2 - generate profile
|
||
|
if(!bytesNeeded) {
|
||
|
printf("Failed!\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
memPtr = _cmsMalloc(bytesNeeded);
|
||
|
if (_cmsSaveProfileToMem(hProfile, memPtr, &bytesNeeded)) {
|
||
|
|
||
|
|
||
|
cmsHPROFILE newProfile = cmsOpenProfileFromMem(memPtr, (DWORD) bytesNeeded);
|
||
|
const char* s = cmsTakeProductName(newProfile);
|
||
|
|
||
|
if (strncmp(s, "sRGB", 4) == 0) rc = TRUE;
|
||
|
|
||
|
cmsCloseProfile(newProfile);
|
||
|
_cmsFree(memPtr);
|
||
|
|
||
|
}
|
||
|
|
||
|
cmsCloseProfile(hProfile);
|
||
|
|
||
|
printf (rc ? "pass.\n" : "failed!\n");
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestNamedColor(void)
|
||
|
{
|
||
|
LPcmsNAMEDCOLORLIST nc2;
|
||
|
cmsHPROFILE hProfile, hDevicelink, hsRGB, hLab;
|
||
|
cmsHTRANSFORM xform, rgb2lab;
|
||
|
int i;
|
||
|
|
||
|
|
||
|
printf("Testing Named color profiles: ");
|
||
|
|
||
|
|
||
|
hsRGB = cmsCreate_sRGBProfile();
|
||
|
hLab = cmsCreateLabProfile(NULL);
|
||
|
|
||
|
rgb2lab = cmsCreateTransform(hsRGB, TYPE_RGB_16, hLab, TYPE_Lab_16, INTENT_PERCEPTUAL, cmsFLAGS_NOTPRECALC);
|
||
|
|
||
|
nc2 = cmsAllocNamedColorList(64);
|
||
|
|
||
|
nc2 ->ColorantCount = 3;
|
||
|
strcpy(nc2 ->Prefix, "prefix");
|
||
|
strcpy(nc2 ->Suffix, "suffix");
|
||
|
|
||
|
for (i=0; i < 64; i++) {
|
||
|
|
||
|
WORD vv = RGB_8_TO_16((i*4));
|
||
|
|
||
|
nc2 ->List[i].DeviceColorant[0] = vv;
|
||
|
nc2 ->List[i].DeviceColorant[1] = vv;
|
||
|
nc2 ->List[i].DeviceColorant[2] = vv;
|
||
|
|
||
|
cmsDoTransform(rgb2lab, nc2 ->List[i].DeviceColorant, nc2 ->List[i].PCS, 1);
|
||
|
|
||
|
sprintf(nc2 ->List[i].Name, "Color #%d", i);
|
||
|
|
||
|
}
|
||
|
|
||
|
hProfile = cmsOpenProfileFromFile("named.icc", "w");
|
||
|
|
||
|
cmsSetDeviceClass(hProfile, icSigNamedColorClass);
|
||
|
cmsSetPCS(hProfile, icSigLabData);
|
||
|
cmsSetColorSpace(hProfile, icSigRgbData);
|
||
|
|
||
|
cmsAddTag(hProfile, icSigNamedColor2Tag, (void*) nc2);
|
||
|
cmsAddTag(hProfile, icSigMediaWhitePointTag, cmsD50_XYZ());
|
||
|
cmsCloseProfile(hProfile);
|
||
|
|
||
|
cmsFreeNamedColorList(nc2);
|
||
|
|
||
|
|
||
|
hProfile = cmsOpenProfileFromFile("named.icc", "r");
|
||
|
|
||
|
xform = cmsCreateTransform(hProfile, TYPE_NAMED_COLOR_INDEX, NULL, TYPE_RGB_16, INTENT_PERCEPTUAL, 0);
|
||
|
|
||
|
for (i=0; i < 64; i++) {
|
||
|
|
||
|
WORD index;
|
||
|
WORD Color[3];
|
||
|
|
||
|
index = (WORD) i;
|
||
|
|
||
|
cmsDoTransform(xform, &index, Color, 1);
|
||
|
|
||
|
if (Color[0] != RGB_8_TO_16((i*4)) ||
|
||
|
Color[1] != RGB_8_TO_16((i*4)) ||
|
||
|
Color[2] != RGB_8_TO_16((i*4))) {
|
||
|
|
||
|
printf(" fail on spot color #%d\n", i);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
cmsDeleteTransform(xform);
|
||
|
cmsCloseProfile(hProfile);
|
||
|
cmsDeleteTransform(rgb2lab);
|
||
|
cmsCloseProfile(hLab);
|
||
|
|
||
|
|
||
|
hProfile = cmsOpenProfileFromFile("named.icc", "r");
|
||
|
|
||
|
xform = cmsCreateTransform(hProfile, TYPE_NAMED_COLOR_INDEX, hsRGB, TYPE_RGB_16, INTENT_PERCEPTUAL, 0);
|
||
|
|
||
|
hDevicelink = cmsTransform2DeviceLink(xform, 0);
|
||
|
|
||
|
_cmsSaveProfile(hDevicelink, "named2.icc");
|
||
|
cmsCloseProfile(hDevicelink);
|
||
|
|
||
|
cmsDeleteTransform(xform);
|
||
|
cmsCloseProfile(hProfile);
|
||
|
|
||
|
cmsCloseProfile(hsRGB);
|
||
|
|
||
|
unlink("named.icc");
|
||
|
unlink("named2.icc");
|
||
|
|
||
|
printf(" pass.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int TestColorantTableTag()
|
||
|
{
|
||
|
LPcmsNAMEDCOLORLIST nc2;
|
||
|
cmsHPROFILE hProfile = cmsOpenProfileFromFile("colTable.icc", "w");
|
||
|
|
||
|
nc2 = cmsAllocNamedColorList(3);
|
||
|
|
||
|
strcpy(nc2 ->List[0].Name, "Red");
|
||
|
strcpy(nc2 ->List[1].Name, "Green");
|
||
|
strcpy(nc2 ->List[2].Name, "Blue");
|
||
|
|
||
|
|
||
|
cmsSetDeviceClass(hProfile, icSigOutputClass);
|
||
|
cmsSetPCS(hProfile, icSigLabData);
|
||
|
cmsSetColorSpace(hProfile, icSigRgbData);
|
||
|
|
||
|
cmsAddTag(hProfile, icSigColorantTableTag, (void*) nc2);
|
||
|
cmsAddTag(hProfile, icSigMediaWhitePointTag, cmsD50_XYZ());
|
||
|
cmsCloseProfile(hProfile);
|
||
|
cmsFreeNamedColorList(nc2);
|
||
|
|
||
|
|
||
|
hProfile = cmsOpenProfileFromFile("colTable.icc", "r");
|
||
|
|
||
|
nc2 = cmsReadColorantTable(hProfile, icSigColorantTableTag);
|
||
|
|
||
|
cmsFreeNamedColorList(nc2);
|
||
|
cmsCloseProfile(hProfile);
|
||
|
|
||
|
unlink("colTable.icc");
|
||
|
|
||
|
return 1;
|
||
|
|
||
|
}
|
||
|
|
||
|
// New to 1.13 -- CGATS/IT8.7
|
||
|
|
||
|
|
||
|
#define NPOINTS_IT8 10 // (17*17*17*17)
|
||
|
|
||
|
static
|
||
|
int TestIT8(void)
|
||
|
{
|
||
|
LCMSHANDLE it8;
|
||
|
int i;
|
||
|
|
||
|
printf("Testing CGATS parser: ");
|
||
|
|
||
|
it8 = cmsIT8Alloc();
|
||
|
|
||
|
cmsIT8SetSheetType(it8, "LCMS/TESTING");
|
||
|
cmsIT8SetPropertyStr(it8, "ORIGINATOR", "1 2 3 4");
|
||
|
cmsIT8SetPropertyUncooked(it8, "DESCRIPTOR", "1234");
|
||
|
cmsIT8SetPropertyStr(it8, "MANUFACTURER", "3");
|
||
|
cmsIT8SetPropertyDbl(it8, "CREATED", 4);
|
||
|
cmsIT8SetPropertyDbl(it8, "SERIAL", 5);
|
||
|
cmsIT8SetPropertyHex(it8, "MATERIAL", 0x123);
|
||
|
|
||
|
cmsIT8SetPropertyDbl(it8, "NUMBER_OF_SETS", NPOINTS_IT8);
|
||
|
cmsIT8SetPropertyDbl(it8, "NUMBER_OF_FIELDS", 4);
|
||
|
|
||
|
cmsIT8SetDataFormat(it8, 0, "SAMPLE_ID");
|
||
|
cmsIT8SetDataFormat(it8, 1, "RGB_R");
|
||
|
cmsIT8SetDataFormat(it8, 2, "RGB_G");
|
||
|
cmsIT8SetDataFormat(it8, 3, "RGB_B");
|
||
|
|
||
|
for (i=0; i < NPOINTS_IT8; i++) {
|
||
|
|
||
|
char Patch[20];
|
||
|
|
||
|
sprintf(Patch, "P%d", i);
|
||
|
|
||
|
cmsIT8SetDataRowCol(it8, i, 0, Patch);
|
||
|
cmsIT8SetDataRowColDbl(it8, i, 1, i);
|
||
|
cmsIT8SetDataRowColDbl(it8, i, 2, i);
|
||
|
cmsIT8SetDataRowColDbl(it8, i, 3, i);
|
||
|
}
|
||
|
|
||
|
cmsIT8SaveToFile(it8, "TEST.IT8");
|
||
|
cmsIT8Free(it8);
|
||
|
|
||
|
|
||
|
it8 = cmsIT8LoadFromFile("TEST.IT8");
|
||
|
cmsIT8SaveToFile(it8, "TEST.IT8");
|
||
|
cmsIT8Free(it8);
|
||
|
|
||
|
|
||
|
|
||
|
it8 = cmsIT8LoadFromFile("TEST.IT8");
|
||
|
|
||
|
if (cmsIT8GetPropertyDbl(it8, "DESCRIPTOR") != 1234) {
|
||
|
|
||
|
printf("fail!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
cmsIT8SetPropertyDbl(it8, "DESCRIPTOR", 5678);
|
||
|
|
||
|
if (cmsIT8GetPropertyDbl(it8, "DESCRIPTOR") != 5678) {
|
||
|
|
||
|
printf("fail!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (cmsIT8GetDataDbl(it8, "P3", "RGB_G") != 3) {
|
||
|
printf("fail!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
cmsIT8Free(it8);
|
||
|
|
||
|
unlink("TEST.IT8");
|
||
|
printf("pass.\n");
|
||
|
return 1;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// Create CSA/CRD
|
||
|
|
||
|
static
|
||
|
void GenerateCSA(const char* cInProf)
|
||
|
{
|
||
|
cmsHPROFILE hProfile;
|
||
|
|
||
|
|
||
|
DWORD n;
|
||
|
char* Buffer;
|
||
|
|
||
|
|
||
|
if (cInProf == NULL)
|
||
|
hProfile = cmsCreateLabProfile(NULL);
|
||
|
else
|
||
|
hProfile = cmsOpenProfileFromFile(cInProf, "r");
|
||
|
|
||
|
n = cmsGetPostScriptCSA(hProfile, 0, NULL, 0);
|
||
|
if (n == 0) return;
|
||
|
|
||
|
Buffer = (char*) _cmsMalloc(n + 1);
|
||
|
cmsGetPostScriptCSA(hProfile, 0, Buffer, n);
|
||
|
Buffer[n] = 0;
|
||
|
|
||
|
_cmsFree(Buffer);
|
||
|
cmsCloseProfile(hProfile);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void GenerateCRD(const char* cOutProf)
|
||
|
{
|
||
|
cmsHPROFILE hProfile;
|
||
|
DWORD n;
|
||
|
char* Buffer;
|
||
|
DWORD dwFlags = 0;
|
||
|
|
||
|
|
||
|
if (cOutProf == NULL)
|
||
|
hProfile = cmsCreateLabProfile(NULL);
|
||
|
else
|
||
|
hProfile = cmsOpenProfileFromFile(cOutProf, "r");
|
||
|
|
||
|
n = cmsGetPostScriptCRDEx(hProfile, 0, dwFlags, NULL, 0);
|
||
|
if (n == 0) return;
|
||
|
|
||
|
Buffer = (char*) _cmsMalloc(n + 1);
|
||
|
cmsGetPostScriptCRDEx(hProfile, 0, dwFlags, Buffer, n);
|
||
|
Buffer[n] = 0;
|
||
|
|
||
|
_cmsFree(Buffer);
|
||
|
cmsCloseProfile(hProfile);
|
||
|
}
|
||
|
|
||
|
static
|
||
|
int TestPostScript()
|
||
|
{
|
||
|
GenerateCSA("sRGB Color Space Profile.icm");
|
||
|
GenerateCRD("sRGB Color Space Profile.icm");
|
||
|
GenerateCSA(NULL);
|
||
|
GenerateCRD(NULL);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void TestLabFloat()
|
||
|
{
|
||
|
#define TYPE_LabA_DBL (COLORSPACE_SH(PT_Lab)|CHANNELS_SH(3)|BYTES_SH(0)|EXTRA_SH(1)|DOSWAP_SH(1))
|
||
|
|
||
|
struct {
|
||
|
double L, a, b;
|
||
|
double A;
|
||
|
} a;
|
||
|
cmsCIELab b;
|
||
|
cmsHPROFILE hLab = cmsCreateLabProfile(NULL);
|
||
|
cmsHTRANSFORM xform = cmsCreateTransform(hLab, TYPE_LabA_DBL, hLab, TYPE_Lab_DBL, 0, 0);
|
||
|
|
||
|
a.L = 100; a.a = 0; a.b= 0;
|
||
|
cmsDoTransform(xform, &a, &b, 1);
|
||
|
|
||
|
cmsDeleteTransform(xform);
|
||
|
cmsCloseProfile(hLab);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
int lExhaustive = 0;
|
||
|
|
||
|
// #include "crtdbg.h"
|
||
|
// _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
|
||
|
|
||
|
|
||
|
|
||
|
printf("little cms testbed. Ver %1.2f [build %s %s]\n\n", LCMS_VERSION / 100., __DATE__, __TIME__);
|
||
|
|
||
|
#ifndef LCMS_DLL
|
||
|
|
||
|
if (!CheckEndianess()) return 1;
|
||
|
if (!CheckSwab()) return 1;
|
||
|
if (!CheckQuickFloor()) return 1;
|
||
|
|
||
|
TestFixedPoint();
|
||
|
|
||
|
if (!TestFixedScaling()) return 1;
|
||
|
if (!TestJointCurves()) return 1;
|
||
|
if (!TestReversingOfCurves()) return 1;
|
||
|
if (!TestLinearInterpolation(lExhaustive)) return 1;
|
||
|
if (!TestReverseLinearInterpolation()) return 1;
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
if (!Test3D()) return 1;
|
||
|
if (!TestMatrixCreation()) return 1;
|
||
|
if (!GetInfoTest()) return 1;
|
||
|
|
||
|
|
||
|
#endif
|
||
|
|
||
|
if (!Test_sRGB()) return 1;
|
||
|
|
||
|
if (!RealProfilesTest()) return 1;
|
||
|
if (!TestInducedError(TYPE_LABA_16)) return 1;
|
||
|
if (!TestPreview()) return 1;
|
||
|
if (!TestMultiprofile()) return 1;
|
||
|
if (!TestLinearizationDevicelink()) return 1;
|
||
|
|
||
|
if (!TestDeviceLinkGeneration()) return 1;
|
||
|
|
||
|
|
||
|
if (!TestLinearizationDevicelink2()) return 1;
|
||
|
|
||
|
|
||
|
if (!TestInkLimiting()) return 1;
|
||
|
if (!TestSaveToMem()) return 1;
|
||
|
if (!TestNamedColor()) return 1;
|
||
|
if (!TestIT8()) return 1;
|
||
|
if (!TestPostScript()) return 1;
|
||
|
if (!TestColorantTableTag()) return 1;
|
||
|
|
||
|
#ifdef ICM_COMPARATIVE
|
||
|
#ifndef NON_WINDOWS
|
||
|
CompareWithICM_8bit();
|
||
|
CompareWithICM_16bit();
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#ifdef CHECK_SPEED
|
||
|
SpeedTest();
|
||
|
SpeedTest2();
|
||
|
#endif
|
||
|
|
||
|
printf("\nSuccess.\n");
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|