//--------------------------------------------------------------------------------- // // Little Color Management System, multithreaded extensions // Copyright (c) 1998-2023 Marti Maria Saguer, all rights reserved // // This program 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 3 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, see . // //--------------------------------------------------------------------------------- #include "threaded_internal.h" #include #include #include // On Visual Studio, use debug CRT #ifdef _MSC_VER # include "crtdbg.h" #endif #ifndef PROFILES_DIR #define PROFILES_DIR "../../test_profiles/" #endif // A fast way to convert from/to 16 <-> 8 bits #define FROM_8_TO_16(rgb) (cmsUInt16Number) ((((cmsUInt16Number) (rgb)) << 8)|(rgb)) #define FROM_16_TO_8(rgb) (cmsUInt8Number) ((((rgb) * 65281 + 8388608) >> 24) & 0xFF) // Some pixel representations typedef struct { cmsUInt8Number r, g, b; } Scanline_rgb8bits; typedef struct { cmsUInt8Number r, g, b, a; } Scanline_rgba8bits; typedef struct { cmsUInt8Number c, m, y, k; } Scanline_cmyk8bits; typedef struct { cmsUInt16Number r, g, b; } Scanline_rgb16bits; typedef struct { cmsUInt16Number r, g, b, a; } Scanline_rgba16bits; typedef struct { cmsUInt16Number c, m, y, k; } Scanline_cmyk16bits; static struct timespec start, finish; cmsINLINE void MeasureTimeStart(void) { #ifdef CMS_IS_WINDOWS_ timespec_get(&start, TIME_UTC); #else clock_gettime(CLOCK_MONOTONIC, &start); #endif } cmsINLINE double MeasureTimeStop(void) { double elapsed; #ifdef CMS_IS_WINDOWS_ timespec_get(&finish, TIME_UTC); #else clock_gettime(CLOCK_MONOTONIC, &finish); #endif elapsed = ((double) finish.tv_sec - start.tv_sec); elapsed += ((double) finish.tv_nsec - start.tv_nsec) / 1000000000.0; return elapsed; } // A flushed printf static void trace(const char* frm, ...) { va_list args; va_start(args, frm); vfprintf(stderr, frm, args); fflush(stderr); va_end(args); } // The callback function used by cmsSetLogErrorHandler() static void FatalErrorQuit(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text) { UNUSED_PARAMETER(ContextID); UNUSED_PARAMETER(ErrorCode); trace("** Fatal error: %s\n", Text); exit(1); } // Rise an error and exit static void Fail(const char* frm, ...) { char ReasonToFailBuffer[1024]; va_list args; va_start(args, frm); vsprintf(ReasonToFailBuffer, frm, args); FatalErrorQuit(0, 0, ReasonToFailBuffer); // unreachable va_end(args); } // Creates a fake profile that only has a curve. Used in several places static cmsHPROFILE CreateCurves(void) { cmsToneCurve* Gamma = cmsBuildGamma(0, 1.1); cmsToneCurve* Transfer[3]; cmsHPROFILE h; Transfer[0] = Transfer[1] = Transfer[2] = Gamma; h = cmsCreateLinearizationDeviceLink(cmsSigRgbData, Transfer); cmsFreeToneCurve(Gamma); return h; } // -------------------------------------------------------------------------------------------------- // A C C U R A C Y C H E C K S // -------------------------------------------------------------------------------------------------- // Check change format feature static void CheckChangeFormat(void) { cmsHPROFILE hsRGB, hLab; cmsHTRANSFORM xform; cmsUInt8Number rgb8[3] = { 10, 120, 40 }; cmsUInt16Number rgb16[3] = { 10* 257, 120*257, 40*257 }; cmsUInt16Number lab16_1[3], lab16_2[3]; trace("Checking change format feature..."); hsRGB = cmsCreate_sRGBProfile(); hLab = cmsCreateLab4Profile(NULL); xform = cmsCreateTransform(hsRGB, TYPE_RGB_16, hLab, TYPE_Lab_16, INTENT_PERCEPTUAL, 0); cmsCloseProfile(hsRGB); cmsCloseProfile(hLab); cmsDoTransform(xform, rgb16, lab16_1, 1); cmsChangeBuffersFormat(xform, TYPE_RGB_8, TYPE_Lab_16); cmsDoTransform(xform, rgb8, lab16_2, 1); cmsDeleteTransform(xform); if (memcmp(lab16_1, lab16_2, sizeof(lab16_1)) != 0) Fail("Change format failed!"); trace("Ok\n"); } // Next test checks results of optimized 8 bits versus raw 8 bits. static void TryAllValues8bits(cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent) { cmsContext Raw = cmsCreateContext(NULL, NULL); cmsContext Plugin = cmsCreateContext(cmsThreadedExtensions(CMS_THREADED_GUESS_MAX_THREADS, 0), NULL); Scanline_rgba8bits* bufferIn; Scanline_rgba8bits* bufferRawOut; Scanline_rgba8bits* bufferPluginOut; int r, g, b; int j; cmsUInt32Number npixels = 256 * 256 * 256; cmsHTRANSFORM xformRaw = cmsCreateTransformTHR(Raw, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, Intent, cmsFLAGS_NOCACHE | cmsFLAGS_COPY_ALPHA); cmsHTRANSFORM xformPlugin = cmsCreateTransformTHR(Plugin, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, Intent, cmsFLAGS_NOCACHE | cmsFLAGS_COPY_ALPHA); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); if (xformRaw == NULL || xformPlugin == NULL) { Fail("NULL transforms on check float conversions"); } // Again, no checking on mem alloc because this is just a test bufferIn = (Scanline_rgba8bits*)malloc(npixels * sizeof(Scanline_rgba8bits)); bufferRawOut = (Scanline_rgba8bits*)malloc(npixels * sizeof(Scanline_rgba8bits)); bufferPluginOut = (Scanline_rgba8bits*)malloc(npixels * sizeof(Scanline_rgba8bits)); // Same input to both transforms j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { bufferIn[j].r = (cmsUInt8Number) r; bufferIn[j].g = (cmsUInt8Number) g; bufferIn[j].b = (cmsUInt8Number) b; bufferIn[j].a = 0xff; j++; } // Different transforms, different output buffers cmsDoTransform(xformRaw, bufferIn, bufferRawOut, npixels); cmsDoTransform(xformPlugin, bufferIn, bufferPluginOut, npixels); // Lets compare results j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { if (bufferRawOut[j].r != bufferPluginOut[j].r || bufferRawOut[j].g != bufferPluginOut[j].g || bufferRawOut[j].b != bufferPluginOut[j].b || bufferRawOut[j].a != bufferPluginOut[j].a) Fail( "Conversion failed at [%x %x %x %x] (%x %x %x %x) != (%x %x %x %x)", bufferIn[j].r, bufferIn[j].g, bufferIn[j].b, bufferIn[j].a, bufferRawOut[j].r, bufferRawOut[j].g, bufferRawOut[j].b, bufferRawOut[j].a, bufferPluginOut[j].r, bufferPluginOut[j].g, bufferPluginOut[j].b, bufferPluginOut[j].a); j++; } free(bufferIn); free(bufferRawOut); free(bufferPluginOut); cmsDeleteTransform(xformRaw); cmsDeleteTransform(xformPlugin); cmsDeleteContext(Plugin); cmsDeleteContext(Raw); } static void CheckAccuracy8Bits(void) { trace("Checking accuracy of 8 bits CLUT..."); TryAllValues8bits(cmsOpenProfileFromFile(PROFILES_DIR "test5.icc", "r"), cmsOpenProfileFromFile(PROFILES_DIR "test3.icc", "r"), INTENT_PERCEPTUAL); trace("OK\n"); } // Next test checks results of optimized 16 bits versus raw 16 bits. static void TryAllValues16bits(cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent) { cmsContext Raw = cmsCreateContext(NULL, NULL); cmsContext Plugin = cmsCreateContext(cmsThreadedExtensions(CMS_THREADED_GUESS_MAX_THREADS, 0), NULL); Scanline_rgba16bits* bufferIn; Scanline_rgba16bits* bufferRawOut; Scanline_rgba16bits* bufferPluginOut; int r, g, b; int j; cmsUInt32Number npixels = 256 * 256 * 256; cmsHTRANSFORM xformRaw = cmsCreateTransformTHR(Raw, hlcmsProfileIn, TYPE_RGBA_16, hlcmsProfileOut, TYPE_RGBA_16, Intent, cmsFLAGS_NOCACHE | cmsFLAGS_COPY_ALPHA); cmsHTRANSFORM xformPlugin = cmsCreateTransformTHR(Plugin, hlcmsProfileIn, TYPE_RGBA_16, hlcmsProfileOut, TYPE_RGBA_16, Intent, cmsFLAGS_NOCACHE | cmsFLAGS_COPY_ALPHA); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); if (xformRaw == NULL || xformPlugin == NULL) { Fail("NULL transforms on check float conversions"); } // Again, no checking on mem alloc because this is just a test bufferIn = (Scanline_rgba16bits*)malloc(npixels * sizeof(Scanline_rgba16bits)); bufferRawOut = (Scanline_rgba16bits*)malloc(npixels * sizeof(Scanline_rgba16bits)); bufferPluginOut = (Scanline_rgba16bits*)malloc(npixels * sizeof(Scanline_rgba16bits)); // Same input to both transforms j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { bufferIn[j].r = FROM_8_TO_16(r); bufferIn[j].g = FROM_8_TO_16(g); bufferIn[j].b = FROM_8_TO_16(b); bufferIn[j].a = 0xffff; j++; } // Different transforms, different output buffers cmsDoTransform(xformRaw, bufferIn, bufferRawOut, npixels); cmsDoTransform(xformPlugin, bufferIn, bufferPluginOut, npixels); // Lets compare results j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { if (bufferRawOut[j].r != bufferPluginOut[j].r || bufferRawOut[j].g != bufferPluginOut[j].g || bufferRawOut[j].b != bufferPluginOut[j].b || bufferRawOut[j].a != bufferPluginOut[j].a) Fail( "Conversion failed at [%x %x %x %x] (%x %x %x %x) != (%x %x %x %x)", bufferIn[j].r, bufferIn[j].g, bufferIn[j].b, bufferIn[j].a, bufferRawOut[j].r, bufferRawOut[j].g, bufferRawOut[j].b, bufferRawOut[j].a, bufferPluginOut[j].r, bufferPluginOut[j].g, bufferPluginOut[j].b, bufferPluginOut[j].a); j++; } free(bufferIn); free(bufferRawOut); free(bufferPluginOut); cmsDeleteTransform(xformRaw); cmsDeleteTransform(xformPlugin); cmsDeleteContext(Plugin); cmsDeleteContext(Raw); } static void CheckAccuracy16Bits(void) { // CLUT should be as 16 bits or better trace("Checking accuracy of 16 bits CLUT..."); TryAllValues16bits(cmsOpenProfileFromFile(PROFILES_DIR "test5.icc", "r"), cmsOpenProfileFromFile(PROFILES_DIR "test3.icc", "r"), INTENT_PERCEPTUAL); trace("OK\n"); } // -------------------------------------------------------------------------------------------------- // P E R F O R M A N C E C H E C K S // -------------------------------------------------------------------------------------------------- static cmsFloat64Number MPixSec(cmsFloat64Number seconds) { return (256.0 * 256.0 * 256.0) / (1024.0*1024.0*seconds); } typedef cmsFloat64Number(*perf_fn)(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut); static void PerformanceHeader(void) { trace(" MPixel/sec. MByte/sec.\n"); } static cmsHPROFILE loadProfile(const char* name) { if (*name == '*') { if (strcmp(name, "*lab") == 0) { return cmsCreateLab4Profile(NULL); } else if (strcmp(name, "*xyz") == 0) { return cmsCreateXYZProfile(); } else if (strcmp(name, "*curves") == 0) { return CreateCurves(); } else Fail("Unknown builtin '%s'", name); } return cmsOpenProfileFromFile(name, "r"); } static cmsFloat64Number Performance(const char* Title, perf_fn fn, cmsContext ct, const char* inICC, const char* outICC, size_t sz, cmsFloat64Number prev) { cmsHPROFILE hlcmsProfileIn = loadProfile(inICC); cmsHPROFILE hlcmsProfileOut = loadProfile(outICC); cmsFloat64Number n = fn(ct, hlcmsProfileIn, hlcmsProfileOut); trace("%-30s: ", Title); fflush(stdout); trace("%-12.2f %-12.2f", n, n * sz); if (prev > 0.0) { cmsFloat64Number imp = n / prev; if (imp > 1) trace(" (x %-2.1f)", imp); } trace("\n"); fflush(stdout); return n; } static void ComparativeCt(cmsContext ct1, cmsContext ct2, const char* Title, perf_fn fn1, perf_fn fn2, const char* inICC, const char* outICC) { cmsHPROFILE hlcmsProfileIn; cmsHPROFILE hlcmsProfileOut; if (inICC == NULL) hlcmsProfileIn = CreateCurves(); else hlcmsProfileIn = cmsOpenProfileFromFile(inICC, "r"); if (outICC == NULL) hlcmsProfileOut = CreateCurves(); else hlcmsProfileOut = cmsOpenProfileFromFile(outICC, "r"); cmsFloat64Number n1 = fn1(ct1, hlcmsProfileIn, hlcmsProfileOut); if (inICC == NULL) hlcmsProfileIn = CreateCurves(); else hlcmsProfileIn = cmsOpenProfileFromFile(inICC, "r"); if (outICC == NULL) hlcmsProfileOut = CreateCurves(); else hlcmsProfileOut = cmsOpenProfileFromFile(outICC, "r"); cmsFloat64Number n2 = fn2(ct2, hlcmsProfileIn, hlcmsProfileOut); trace("%-30s: ", Title); fflush(stdout); trace("%-12.2f %-12.2f\n", n1, n2); } static void Comparative(const char* Title, perf_fn fn1, perf_fn fn2, const char* inICC, const char* outICC) { ComparativeCt(0, 0, Title, fn1, fn2, inICC, outICC); } // The worst case is used, no cache and all rgb combinations static cmsFloat64Number SpeedTest8bitsRGB(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) { cmsInt32Number r, g, b, j; cmsFloat64Number diff; cmsHTRANSFORM hlcmsxform; Scanline_rgb8bits* In; cmsUInt32Number Mb; if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) Fail("Unable to open profiles"); hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGB_8, hlcmsProfileOut, TYPE_RGB_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); Mb = 256 * 256 * 256 * sizeof(Scanline_rgb8bits); In = (Scanline_rgb8bits*)malloc(Mb); j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { In[j].r = (cmsUInt8Number)r; In[j].g = (cmsUInt8Number)g; In[j].b = (cmsUInt8Number)b; j++; } MeasureTimeStart(); cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256); diff = MeasureTimeStop(); free(In); cmsDeleteTransform(hlcmsxform); return MPixSec(diff); } static cmsFloat64Number SpeedTest8bitsRGBA(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) { cmsInt32Number r, g, b, j; cmsFloat64Number diff; cmsHTRANSFORM hlcmsxform; Scanline_rgba8bits* In; cmsUInt32Number Mb; if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) Fail("Unable to open profiles"); hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); Mb = 256 * 256 * 256 * sizeof(Scanline_rgba8bits); In = (Scanline_rgba8bits*)malloc(Mb); j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { In[j].r = (cmsUInt8Number)r; In[j].g = (cmsUInt8Number)g; In[j].b = (cmsUInt8Number)b; In[j].a = 0; j++; } MeasureTimeStart(); cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256); diff = MeasureTimeStop(); free(In); cmsDeleteTransform(hlcmsxform); return MPixSec(diff); } // The worst case is used, no cache and all rgb combinations static cmsFloat64Number SpeedTest16bitsRGB(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) { cmsInt32Number r, g, b, j; cmsFloat64Number diff; cmsHTRANSFORM hlcmsxform; Scanline_rgb16bits *In; cmsUInt32Number Mb; if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) Fail("Unable to open profiles"); hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGB_16, hlcmsProfileOut, TYPE_RGB_16, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); Mb = 256 * 256 * 256 * sizeof(Scanline_rgb16bits); In = (Scanline_rgb16bits*)malloc(Mb); j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { In[j].r = (cmsUInt16Number)FROM_8_TO_16(r); In[j].g = (cmsUInt16Number)FROM_8_TO_16(g); In[j].b = (cmsUInt16Number)FROM_8_TO_16(b); j++; } MeasureTimeStart(); cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256); diff = MeasureTimeStop(); free(In); cmsDeleteTransform(hlcmsxform); return MPixSec(diff); } static cmsFloat64Number SpeedTest16bitsCMYK(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) { cmsInt32Number r, g, b, j; cmsFloat64Number diff; cmsHTRANSFORM hlcmsxform; Scanline_cmyk16bits* In; cmsUInt32Number Mb; if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) Fail("Unable to open profiles"); hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_CMYK_16, hlcmsProfileOut, TYPE_CMYK_16, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); Mb = 256 * 256 * 256 * sizeof(Scanline_cmyk16bits); In = (Scanline_cmyk16bits*)malloc(Mb); j = 0; for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { In[j].c = (cmsUInt16Number)r; In[j].m = (cmsUInt16Number)g; In[j].y = (cmsUInt16Number)b; In[j].k = (cmsUInt16Number)r; j++; } MeasureTimeStart(); cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256); diff = MeasureTimeStop(); free(In); cmsDeleteTransform(hlcmsxform); return MPixSec(diff); } static void SpeedTest8(void) { cmsContext noPlugin = cmsCreateContext(0, 0); cmsFloat64Number t[10]; trace("\n\n"); trace("P E R F O R M A N C E T E S T S 8 B I T S (D E F A U L T)\n"); trace("==============================================================\n\n"); fflush(stdout); PerformanceHeader(); t[0] = Performance("8 bits on CLUT profiles ", SpeedTest8bitsRGB, noPlugin, PROFILES_DIR "test5.icc", PROFILES_DIR "test3.icc", sizeof(Scanline_rgb8bits), 0); t[1] = Performance("8 bits on Matrix-Shaper ", SpeedTest8bitsRGB, noPlugin, PROFILES_DIR "test5.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb8bits), 0); t[2] = Performance("8 bits on same MatrixSh ", SpeedTest8bitsRGB, noPlugin, PROFILES_DIR "test0.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb8bits), 0); t[3] = Performance("8 bits on curves ", SpeedTest8bitsRGB, noPlugin, "*curves", "*curves", sizeof(Scanline_rgb8bits), 0); // Note that context 0 has the plug-in installed trace("\n\n"); trace("P E R F O R M A N C E T E S T S 8 B I T S (P L U G I N)\n"); trace("===========================================================\n\n"); fflush(stdout); PerformanceHeader(); Performance("8 bits on CLUT profiles ", SpeedTest8bitsRGB, 0, PROFILES_DIR "test5.icc", PROFILES_DIR "test3.icc", sizeof(Scanline_rgb8bits), t[0]); Performance("8 bits on Matrix-Shaper ", SpeedTest8bitsRGB, 0, PROFILES_DIR "test5.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb8bits), t[1]); Performance("8 bits on same MatrixSh ", SpeedTest8bitsRGB, 0, PROFILES_DIR "test0.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb8bits), t[2]); Performance("8 bits on curves ", SpeedTest8bitsRGB, 0, "*curves", "*curves", sizeof(Scanline_rgb8bits), t[3]); cmsDeleteContext(noPlugin); } static void SpeedTest16(void) { cmsContext noPlugin = cmsCreateContext(0, 0); cmsFloat64Number t[10]; trace("\n\n"); trace("P E R F O R M A N C E T E S T S 1 6 B I T S (D E F A U L T)\n"); trace("=================================================================\n\n"); PerformanceHeader(); t[0] = Performance("16 bits on CLUT profiles ", SpeedTest16bitsRGB, noPlugin, PROFILES_DIR "test5.icc", PROFILES_DIR "test3.icc", sizeof(Scanline_rgb16bits), 0); t[1] = Performance("16 bits on Matrix-Shaper profiles", SpeedTest16bitsRGB, noPlugin, PROFILES_DIR "test5.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb16bits), 0); t[2] = Performance("16 bits on same Matrix-Shaper ", SpeedTest16bitsRGB, noPlugin, PROFILES_DIR "test0.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb16bits), 0); t[3] = Performance("16 bits on curves ", SpeedTest16bitsRGB, noPlugin, "*curves", "*curves", sizeof(Scanline_rgb16bits), 0); t[4] = Performance("16 bits on CMYK CLUT profiles ", SpeedTest16bitsCMYK, noPlugin, PROFILES_DIR "test1.icc", PROFILES_DIR "test2.icc", sizeof(Scanline_cmyk16bits), 0); trace("\n\n"); trace("P E R F O R M A N C E T E S T S 1 6 B I T S (P L U G I N)\n"); trace("===============================================================\n\n"); PerformanceHeader(); Performance("16 bits on CLUT profiles ", SpeedTest16bitsRGB, 0, PROFILES_DIR "test5.icc", PROFILES_DIR "test3.icc", sizeof(Scanline_rgb16bits), t[0]); Performance("16 bits on Matrix-Shaper profiles", SpeedTest16bitsRGB, 0, PROFILES_DIR "test5.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb16bits), t[1]); Performance("16 bits on same Matrix-Shaper ", SpeedTest16bitsRGB, 0, PROFILES_DIR "test0.icc", PROFILES_DIR "test0.icc", sizeof(Scanline_rgb16bits), t[2]); Performance("16 bits on curves ", SpeedTest16bitsRGB, 0, "*curves", "*curves", sizeof(Scanline_rgb16bits), t[3]); Performance("16 bits on CMYK CLUT profiles ", SpeedTest16bitsCMYK, 0, PROFILES_DIR "test1.icc", PROFILES_DIR "test2.icc", sizeof(Scanline_cmyk16bits), t[4]); cmsDeleteContext(noPlugin); } typedef struct { Scanline_rgba8bits pixels[256][256]; cmsUInt8Number padding[4]; } padded_line; typedef struct { padded_line line[256]; } big_bitmap; static cmsFloat64Number SpeedTest8bitDoTransform(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) { cmsInt32Number r, g, b, j; cmsFloat64Number diff; cmsHTRANSFORM hlcmsxform; big_bitmap* In; big_bitmap* Out; cmsUInt32Number Mb; if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) Fail("Unable to open profiles"); hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); // Our test bitmap is 256 x 256 padded lines Mb = sizeof(big_bitmap); In = (big_bitmap*)malloc(Mb); Out = (big_bitmap*)malloc(Mb); for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { In->line[r].pixels[g][b].r = (cmsUInt8Number)r; In->line[r].pixels[g][b].g = (cmsUInt8Number)g; In->line[r].pixels[g][b].b = (cmsUInt8Number)b; In->line[r].pixels[g][b].a = 0; } MeasureTimeStart(); for (j = 0; j < 256; j++) { cmsDoTransform(hlcmsxform, In->line[j].pixels, Out->line[j].pixels, 256 * 256); } diff = MeasureTimeStop(); free(In); free(Out); cmsDeleteTransform(hlcmsxform); return MPixSec(diff); } static cmsFloat64Number SpeedTest8bitLineStride(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) { cmsInt32Number r, g, b; cmsFloat64Number diff; cmsHTRANSFORM hlcmsxform; big_bitmap* In; big_bitmap* Out; cmsUInt32Number Mb; if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) Fail("Unable to open profiles"); hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); cmsCloseProfile(hlcmsProfileIn); cmsCloseProfile(hlcmsProfileOut); // Our test bitmap is 256 x 256 padded lines Mb = sizeof(big_bitmap); In = (big_bitmap*)malloc(Mb); Out = (big_bitmap*)malloc(Mb); for (r = 0; r < 256; r++) for (g = 0; g < 256; g++) for (b = 0; b < 256; b++) { In->line[r].pixels[g][b].r = (cmsUInt8Number)r; In->line[r].pixels[g][b].g = (cmsUInt8Number)g; In->line[r].pixels[g][b].b = (cmsUInt8Number)b; In->line[r].pixels[g][b].a = 0; } MeasureTimeStart(); cmsDoTransformLineStride(hlcmsxform, In, Out, 256 * 256, 256, sizeof(padded_line), sizeof(padded_line), 0, 0); diff = MeasureTimeStop(); free(In); free(Out); cmsDeleteTransform(hlcmsxform); return MPixSec(diff); } static void ComparativeLineStride8bits(void) { cmsContext NoPlugin, Plugin; trace("\n\n"); trace("C O M P A R A T I V E cmsDoTransform() vs. cmsDoTransformLineStride()\n"); trace(" values given in MegaPixels per second.\n"); trace("====================================================================\n"); fflush(stdout); NoPlugin = cmsCreateContext(NULL, NULL); Plugin = cmsCreateContext(cmsThreadedExtensions(CMS_THREADED_GUESS_MAX_THREADS, 0), NULL); ComparativeCt(NoPlugin, Plugin, "CLUT profiles ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, PROFILES_DIR "test5.icc", PROFILES_DIR "test3.icc"); ComparativeCt(NoPlugin, Plugin, "CLUT 16 bits ", SpeedTest16bitsRGB, SpeedTest16bitsRGB, PROFILES_DIR "test5.icc", PROFILES_DIR "test3.icc"); ComparativeCt(NoPlugin, Plugin, "Matrix-Shaper ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, PROFILES_DIR "test5.icc", PROFILES_DIR "test0.icc"); ComparativeCt(NoPlugin, Plugin, "same MatrixSh ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, PROFILES_DIR "test0.icc", PROFILES_DIR "test0.icc"); ComparativeCt(NoPlugin, Plugin, "curves ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, NULL, NULL); cmsDeleteContext(Plugin); cmsDeleteContext(NoPlugin); } // The harness test int main() { #ifdef _MSC_VER _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif trace("Multithreaded extensions testbed - 1.1\n"); trace("Copyright (c) 1998-2023 Marti Maria Saguer, all rights reserved\n"); trace("\nInstalling error logger ... "); cmsSetLogErrorHandler(FatalErrorQuit); trace("done.\n"); trace("Installing plug-in ... "); cmsPlugin(cmsThreadedExtensions(CMS_THREADED_GUESS_MAX_THREADS, 0)); trace("done.\n\n"); // Change format CheckChangeFormat(); // Accuracy CheckAccuracy8Bits(); CheckAccuracy16Bits(); // Check speed SpeedTest8(); SpeedTest16(); ComparativeLineStride8bits(); cmsUnregisterPlugins(); trace("\nAll tests passed OK\n"); return 0; }