diff options
Diffstat (limited to 'third_party')
30 files changed, 8211 insertions, 0 deletions
diff --git a/third_party/qcms/BUILD.gn b/third_party/qcms/BUILD.gn new file mode 100644 index 0000000000..04db5df64a --- /dev/null +++ b/third_party/qcms/BUILD.gn @@ -0,0 +1,77 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +config("qcms_config") { + include_dirs = [ "src" ] +} + +# Do not build QCMS on Android or iOS. (See http://crbug.com/577155) +disable_qcms = is_android || is_ios + +source_set("qcms") { + if (disable_qcms) { + sources = [ + "src/empty.c", + ] + } else { + sources = [ + "src/chain.c", + "src/chain.h", + "src/iccread.c", + "src/matrix.c", + "src/matrix.h", + "src/qcms.h", + "src/qcms_util.c", + "src/qcmsint.h", + "src/qcmstypes.h", + "src/transform.c", + "src/transform_util.c", + "src/transform_util.h", + ] + + if (current_cpu == "x86" || current_cpu == "x64") { + defines = [ "SSE2_ENABLE" ] + sources += [ "src/transform-sse2.c" ] + } + } + + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + public_configs = [ ":qcms_config" ] + + if (is_win) { + cflags = [ + "/wd4018", # Signed/unsigned mismatch in comparison. + "/wd4056", # Overflow in floating-point constant arithmetic (INFINITY). + "/wd4756", # Overflow in constant arithmetic (INFINITY). + ] + } +} + +if (!disable_qcms) { + executable("qcms_tests") { + if (current_cpu == "x86" || current_cpu == "x64") { + defines = [ "SSE2_ENABLE" ] + } + sources = [ + "src/tests/qcms_test_internal_srgb.c", + "src/tests/qcms_test_main.c", + "src/tests/qcms_test_munsell.c", + "src/tests/qcms_test_ntsc_gamut.c", + "src/tests/qcms_test_output_trc.c", + "src/tests/qcms_test_tetra_clut_rgba.c", + "src/tests/qcms_test_util.c", + ] + + deps = [ + "//build/config/sanitizers:deps", + "//build/win:default_exe_manifest", + "//third_party/qcms", + ] + + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + public_configs = [ ":qcms_config" ] + } +} diff --git a/third_party/qcms/README.chromium b/third_party/qcms/README.chromium new file mode 100644 index 0000000000..b19c005820 --- /dev/null +++ b/third_party/qcms/README.chromium @@ -0,0 +1,162 @@ +Name: Quick Color Management System +Short Name: qcms +URL: https://github.com/jrmuizel/qcms/tree/v4 +Version: v4 +Date: 2012-03-13 +License: MIT +License File: src/COPYING +Security Critical: yes + +Description: +Contains support for applying an ICC color profile to an image. The code is +originally based on tinycms, re-written by Mozilla for better security and +performance. This copy is a source-drop from Mozilla on March 13, 2012. + +Local Modifications: +Some files only have license headers in the master branch. + - Added the same license headers to the versions brought down from the 'v4' + branch src URL qcms/tree/v4 +The following changes have been made since qcms was imported: + - Add bgra output support. + - Use HAVE_POSIX_MEMALIGN instead of HAS_POSIX_MEMALIG + (https://bugzilla.mozilla.org/show_bug.cgi?id=692922) + - Applied upstream patch: + - https://bug752254.bugzilla.mozilla.org/attachment.cgi?id=626102 + - Applied upstream patch for sanitizing gamma table: + - Expanded gamma clamp range to allow 1.0. + - Do not short-circuit bogus profile check for A2B0 or B2A0 unless v4 enabled. + - Only reference code from transform-sse1.c and transform-sse2.c when SSE is + enabled. + - Limit the use an x86-only attribute to 32-bit x86 builds. + - https://code.google.com/p/chromium/issues/detail?id=490016 + - Fix integer truncation warning/errors on Win64 build. + - Apply upstream thread safety (fix) patch from + - https://bugzilla.mozilla.org/show_bug.cgi?id=853169 + - Apply upstream fix for qcms_profile_from_memory from + - https://bugzilla.mozilla.org/show_bug.cgi?id=969226 + - Apply upstream simplification of qcmstypes.h + - https://hg.mozilla.org/mozilla-central/rev/b5be94737a83 + - Check for unused tag_len in read_nested_curveType() + - Apply qcms_transform_precacheLUT_float does not properly free dest buffer + - https://bugzilla.mozilla.org/show_bug.cgi?id=701348 + - Add qcms_profile_match api + - https://code.google.com/p/chromium/issues/detail?id=401971 + - Add qcms_profile_get_description api + - https://code.google.com/p/chromium/issues/detail?id=401971 + - Convert MLUC profile description embedded nulls to '.' + - https://code.google.com/p/chromium/issues/detail?id=401971 + - [APPLE port] Use en-localized profile description names + - https://code.google.com/p/chromium/issues/detail?id=401971 + - [APPLE port] Use mmod data for profile descriptions + - https://code.google.com/p/chromium/issues/detail?id=401971 + - Minor variable name change: description -> description_offset + - https://code.google.com/p/chromium/issues/detail?id=401971 + - Avoid divisions creating sample points in the float cube LUT builder + - https://code.google.com/p/chromium/issues/detail?id=443863 + - Add bgra (z,y,x) sampled transform lookup table api + - https://code.google.com/p/chromium/issues/detail?id=443863 + - Apply upstream fix reject invalid sizes from + - https://bugzilla.mozilla.org/show_bug.cgi?id=1132468 + - lut_inverse_interp16: remove odd whitespace formatting + - https://code.google.com/p/chromium/issues/detail?id=458024 + - lut_inverse_interp16: better handle degenerate TRC curves + - https://code.google.com/p/chromium/issues/detail?id=458024 + - Add vcgt tag reader and API to read its RGB gamma data + - https://code.google.com/p/chromium/issues/detail?id=471749 + - Consolidate loop over channels and elements in VCGT parser + - https://code.google.com/p/chromium/issues/detail?id=471749 + - Limit vcgt table to a maximum of 1024 entries + - https://code.google.com/p/chromium/issues/detail?id=471749 + - Add float to half float conversion routine + - https://code.google.com/p/chromium/issues/detail?id=491784 + - Use half float conversion tables (not functions) + - https://code.google.com/p/chromium/issues/detail?id=491784 + - Add matrix transform flag and matrix extraction api + - https://code.google.com/p/chromium/issues/detail?id=491826 + - Add tone reproduction curve (TRC) extraction api + - https://code.google.com/p/chromium/issues/detail?id=491826 + - lut_inverse_interp16: interpolate degenerate zeros in TRC curves + - https://code.google.com/p/chromium/issues/detail?id=458024 + - Make half float code self-contained: add halffloat.h + - https://code.google.com/p/chromium/issues/detail?id=491784 + - lut_inverse_interp16: fix black level inversion error + - https://code.google.com/p/chromium/issues/detail?id=504681 + - Apply upstream fix of uninitialized value + - https://github.com/jrmuizel/qcms/commit/0edb697 + - Apply Keep the output of the TRC between 0 and 1 + - https://bugzilla.mozilla.org/show_bug.cgi?id=764181 + - Drop SSE1 code as Chromium mandates SSE2 as a minimum requirement + - https://code.google.com/p/chromium/issues/detail?id=506856 + - Apply Fix potential leaks in qcms_transform_create + - https://bugzilla.mozilla.org/show_bug.cgi?id=1168480 + - Apply fix for crash in qcms_transform_data_rgb_out_lut_sse1 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1163740 + - Add SSE2 code for qcms_transform_data_rgba_out_lut_sse2 + - https://code.google.com/p/chromium/issues/detail?id=506607 + - Turbo charge SSE2 qcms_transform_data_rgba_out_lut_sse2 + - https://code.google.com/p/chromium/issues/detail?id=506607 + - Check matrix transforms have the same PCS + - https://code.google.com/p/chromium/issues/detail?id=510682 + - Apply Don't reject valid profiles with no CLUT offset + - https://bugzilla.mozilla.org/show_bug.cgi?id=1176551 + - Runtime detect qcms_transform_data_tetra_clut_rgba_sse2 use + - https://code.google.com/p/chromium/issues/detail?id=506607 + - LUT8/16 profiles with an empty CLUT are invalid + - https://code.google.com/p/chromium/issues/detail?id=487284 + - Caching transform prelude to save computation in V4 profiles + - https://code.google.com/p/chromium/issues/detail?id=506607 + - Use the caching transform prelude for non-SSE code in transform.c + - https://code.google.com/p/chromium/issues/detail?id=506607 + - Expand QCMS tests. Add Munsell test for transform accuracy + - https://code.google.com/p/chromium/issues/detail?id=532910 + - Compute RMS error from color error dE in the Munsell test + - https://code.google.com/p/chromium/issues/detail?id=532910 + - Store color profile version and add a version read API + - https://code.google.com/p/chromium/issues/detail?id=532258 + - Add support for VCGT tag formula gamma + - https://code.google.com/p/chromium/issues/detail?id=551568 + - Minimize transform RMS in qcms_transform_data_rgb(a)_out_lut + - https://code.google.com/p/chromium/issues/detail?id=532910 + - Allow negative XYZ for display profiles on the APPLE port + - https://code.google.com/p/chromium/issues/detail?id=562951 + - Add a color profile white point transform api + - https://code.google.com/p/chromium/issues/detail?id=564355 + - Compute the pole and zero values needed by lut_inverse_interp16 once + - https://code.google.com/p/chromium/issues/detail?id=505793 + - Store the profile media white point during iccread.c + - https://code.google.com/p/chromium/issues/detail?id=565222 + - Add an API to check for profile media white point + - https://code.google.com/p/chromium/issues/detail?id=565222 + - Add a qcms_profile_get_white_point() api + - https://code.google.com/p/chromium/issues/detail?id=565222 + - Set the media white point of the sRGB profile + - https://code.google.com/p/chromium/issues/detail?id=565222 + - Don't build QCMS on Android and iOS + - https://code.google.com/p/chromium/issues/detail?id=577155 + - Fix para curve tag interval paramater use + - https://code.google.com/p/chromium/issues/detail?id=580920 + - Verify internal sRGB profile tristimulus match the standard sRGB profile + - https://code.google.com/p/chromium/issues/detail?id=580917 + - Expose unsigned short version of TRC curve + - https://code.google.com/p/chromium/issues/detail?id=495196 + - Add qcms_profile_ntsc_relative_gamut_size api + - https://code.google.com/p/chromium/issues/detail?id=582870 + - Fix build issue of qcms_tests for MIPS Linux + - https://code.google.com/p/chromium/issues/detail?id=590227 + - Update primaries used to build internal sRGB profile + - https://code.google.com/p/chromium/issues/detail?id=580917 + - Update internal sRGB profile test report output + - https://code.google.com/p/chromium/issues/detail?id=580917 + - Add more internal sRGB profile tests + - https://code.google.com/p/chromium/issues/detail?id=580917 + - Fix build_output_lut to return correct data for parametric curves + - https://bugs.chromium.org/p/chromium/issues/detail?id=600338 + - Make build_output_lut output 4096 points for parametric curves + - https://bugs.chromium.org/p/chromium/issues/detail?id=600338 + - Use a static table in build_output_lut to invert para curves + - https://bugs.chromium.org/p/chromium/issues/detail?id=600338 + - Make qcms_test_output_trc handle normal gamma curves + - https://bugs.chromium.org/p/chromium/issues/detail?id=600338 + +For the Chromium changes, since the import, in a patch format run: + git diff b8456f38 src diff --git a/third_party/qcms/qcms.gyp b/third_party/qcms/qcms.gyp new file mode 100644 index 0000000000..fd78e5e8d8 --- /dev/null +++ b/third_party/qcms/qcms.gyp @@ -0,0 +1,113 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'conditions': [ + # Do not build QCMS on Android or iOS. (See http://crbug.com/577155) + ['OS == "android" or OS == "ios"', { + 'disable_qcms%': 1, + }, { + 'disable_qcms%': 0, + }], + ], + }, + 'targets': [ + { + 'target_name': 'qcms', + 'product_name': 'qcms', + 'type': 'static_library', + + # Warning (sign-conversion) fixed upstream by large refactoring. Can be + # removed on next roll. + 'msvs_disabled_warnings': [ 4018 ], + + 'direct_dependent_settings': { + 'include_dirs': [ + './src', + ], + }, + + 'conditions': [ + ['disable_qcms == 1', { + 'sources': [ + 'src/empty.c', + ], + }, { # disable_qcms == 0 + 'sources': [ + 'src/chain.c', + 'src/chain.h', + 'src/iccread.c', + 'src/matrix.c', + 'src/matrix.h', + 'src/qcms.h', + 'src/qcmsint.h', + 'src/qcmstypes.h', + 'src/qcms_util.c', + 'src/transform.c', + 'src/transform_util.c', + 'src/transform_util.h', + ], + 'conditions': [ + ['target_arch=="ia32" or target_arch=="x64"', { + 'defines': [ + 'SSE2_ENABLE', + ], + 'sources': [ + 'src/transform-sse2.c', + ], + }], + ], + }], + ['OS == "win"', { + 'msvs_disabled_warnings': [ + 4056, # overflow in floating-point constant arithmetic (INFINITY) + 4756, # overflow in constant arithmetic (INFINITY) + ], + }], + ], + }, + ], + 'conditions': [ + ['disable_qcms == 0', { + 'targets': [ + { + 'target_name': 'qcms_tests', + 'product_name': 'qcms_tests', + 'type': 'executable', + 'dependencies': [ + 'qcms', + ], + 'conditions': [ + ['OS != "win"', { + 'libraries': [ + '-lm', + ], + }], + ['target_arch=="ia32" or target_arch=="x64"', { + 'defines': [ + 'SSE2_ENABLE', + ], + }], + ], + 'sources': [ + 'src/tests/qcms_test_main.c', + 'src/tests/qcms_test_internal_srgb.c', + 'src/tests/qcms_test_munsell.c', + 'src/tests/qcms_test_ntsc_gamut.c', + 'src/tests/qcms_test_output_trc.c', + 'src/tests/qcms_test_tetra_clut_rgba.c', + 'src/tests/qcms_test_util.c', + ], + }, + ], + }], + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/third_party/qcms/src/COPYING b/third_party/qcms/src/COPYING new file mode 100644 index 0000000000..76b303b00a --- /dev/null +++ b/third_party/qcms/src/COPYING @@ -0,0 +1,21 @@ +qcms +Copyright (C) 2009 Mozilla Corporation +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. diff --git a/third_party/qcms/src/README b/third_party/qcms/src/README new file mode 100644 index 0000000000..e3d95a37a3 --- /dev/null +++ b/third_party/qcms/src/README @@ -0,0 +1,9 @@ +qcms +==== + +Quick color management. + +Current limitations +----------- +- Only supports RGB and Gray colorspaces. e.g. no support for CMYK yet. +- Only packed RGB and RGBA, K, and KA. diff --git a/third_party/qcms/src/chain.c b/third_party/qcms/src/chain.c new file mode 100644 index 0000000000..525507084b --- /dev/null +++ b/third_party/qcms/src/chain.c @@ -0,0 +1,1026 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Corporation +// Copyright (C) 1998-2007 Marti Maria +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include <string.h> //memcpy +#include "qcmsint.h" +#include "transform_util.h" +#include "matrix.h" + +static struct matrix build_lut_matrix(struct lutType *lut) +{ + struct matrix result; + if (lut) { + result.m[0][0] = s15Fixed16Number_to_float(lut->e00); + result.m[0][1] = s15Fixed16Number_to_float(lut->e01); + result.m[0][2] = s15Fixed16Number_to_float(lut->e02); + result.m[1][0] = s15Fixed16Number_to_float(lut->e10); + result.m[1][1] = s15Fixed16Number_to_float(lut->e11); + result.m[1][2] = s15Fixed16Number_to_float(lut->e12); + result.m[2][0] = s15Fixed16Number_to_float(lut->e20); + result.m[2][1] = s15Fixed16Number_to_float(lut->e21); + result.m[2][2] = s15Fixed16Number_to_float(lut->e22); + result.invalid = false; + } else { + memset(&result, 0, sizeof(struct matrix)); + result.invalid = true; + } + return result; +} + +static struct matrix build_mAB_matrix(struct lutmABType *lut) +{ + struct matrix result; + if (lut) { + result.m[0][0] = s15Fixed16Number_to_float(lut->e00); + result.m[0][1] = s15Fixed16Number_to_float(lut->e01); + result.m[0][2] = s15Fixed16Number_to_float(lut->e02); + result.m[1][0] = s15Fixed16Number_to_float(lut->e10); + result.m[1][1] = s15Fixed16Number_to_float(lut->e11); + result.m[1][2] = s15Fixed16Number_to_float(lut->e12); + result.m[2][0] = s15Fixed16Number_to_float(lut->e20); + result.m[2][1] = s15Fixed16Number_to_float(lut->e21); + result.m[2][2] = s15Fixed16Number_to_float(lut->e22); + result.invalid = false; + } else { + memset(&result, 0, sizeof(struct matrix)); + result.invalid = true; + } + return result; +} + +//Based on lcms cmsLab2XYZ +#define f(t) (t <= (24.0f/116.0f)*(24.0f/116.0f)*(24.0f/116.0f)) ? ((841.0/108.0) * t + (16.0/116.0)) : pow(t,1.0/3.0) +#define f_1(t) (t <= (24.0f/116.0f)) ? ((108.0/841.0) * (t - (16.0/116.0))) : (t * t * t) +static void qcms_transform_module_LAB_to_XYZ(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + // lcms: D50 XYZ values + float WhitePointX = 0.9642f; + float WhitePointY = 1.0f; + float WhitePointZ = 0.8249f; + for (i = 0; i < length; i++) { + float device_L = *src++ * 100.0f; + float device_a = *src++ * 255.0f - 128.0f; + float device_b = *src++ * 255.0f - 128.0f; + float y = (device_L + 16.0f) / 116.0f; + + float X = f_1((y + 0.002f * device_a)) * WhitePointX; + float Y = f_1(y) * WhitePointY; + float Z = f_1((y - 0.005f * device_b)) * WhitePointZ; + *dest++ = X / (1.0 + 32767.0/32768.0); + *dest++ = Y / (1.0 + 32767.0/32768.0); + *dest++ = Z / (1.0 + 32767.0/32768.0); + } +} + +//Based on lcms cmsXYZ2Lab +static void qcms_transform_module_XYZ_to_LAB(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + // lcms: D50 XYZ values + float WhitePointX = 0.9642f; + float WhitePointY = 1.0f; + float WhitePointZ = 0.8249f; + for (i = 0; i < length; i++) { + float device_x = *src++ * (1.0 + 32767.0/32768.0) / WhitePointX; + float device_y = *src++ * (1.0 + 32767.0/32768.0) / WhitePointY; + float device_z = *src++ * (1.0 + 32767.0/32768.0) / WhitePointZ; + + float fx = f(device_x); + float fy = f(device_y); + float fz = f(device_z); + + float L = 116.0f*fy - 16.0f; + float a = 500.0f*(fx - fy); + float b = 200.0f*(fy - fz); + *dest++ = L / 100.0f; + *dest++ = (a+128.0f) / 255.0f; + *dest++ = (b+128.0f) / 255.0f; + } + +} + +static void qcms_transform_module_clut_only(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + int xy_len = 1; + int x_len = transform->grid_size; + int len = x_len * x_len; + float* r_table = transform->r_clut; + float* g_table = transform->g_clut; + float* b_table = transform->b_clut; + + assert(transform->grid_size >= 1); + + for (i = 0; i < length; i++) { + float linear_r = *src++; + float linear_g = *src++; + float linear_b = *src++; + + int x = floor(linear_r * (transform->grid_size-1)); + int y = floor(linear_g * (transform->grid_size-1)); + int z = floor(linear_b * (transform->grid_size-1)); + int x_n = ceil(linear_r * (transform->grid_size-1)); + int y_n = ceil(linear_g * (transform->grid_size-1)); + int z_n = ceil(linear_b * (transform->grid_size-1)); + float x_d = linear_r * (transform->grid_size-1) - x; + float y_d = linear_g * (transform->grid_size-1) - y; + float z_d = linear_b * (transform->grid_size-1) - z; + + float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d); + float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d); + float r_y1 = lerp(r_x1, r_x2, y_d); + float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d); + float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d); + float r_y2 = lerp(r_x3, r_x4, y_d); + float clut_r = lerp(r_y1, r_y2, z_d); + + float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d); + float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d); + float g_y1 = lerp(g_x1, g_x2, y_d); + float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d); + float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d); + float g_y2 = lerp(g_x3, g_x4, y_d); + float clut_g = lerp(g_y1, g_y2, z_d); + + float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d); + float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d); + float b_y1 = lerp(b_x1, b_x2, y_d); + float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d); + float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d); + float b_y2 = lerp(b_x3, b_x4, y_d); + float clut_b = lerp(b_y1, b_y2, z_d); + + *dest++ = clamp_float(clut_r); + *dest++ = clamp_float(clut_g); + *dest++ = clamp_float(clut_b); + } +} + +static void qcms_transform_module_clut(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + int xy_len = 1; + int x_len = transform->grid_size; + int len = x_len * x_len; + float* r_table = transform->r_clut; + float* g_table = transform->g_clut; + float* b_table = transform->b_clut; + + assert(transform->grid_size >= 1); + + for (i = 0; i < length; i++) { + float device_r = *src++; + float device_g = *src++; + float device_b = *src++; + float linear_r = lut_interp_linear_float(device_r, + transform->input_clut_table_r, transform->input_clut_table_length); + float linear_g = lut_interp_linear_float(device_g, + transform->input_clut_table_g, transform->input_clut_table_length); + float linear_b = lut_interp_linear_float(device_b, + transform->input_clut_table_b, transform->input_clut_table_length); + + int x = floor(linear_r * (transform->grid_size-1)); + int y = floor(linear_g * (transform->grid_size-1)); + int z = floor(linear_b * (transform->grid_size-1)); + int x_n = ceil(linear_r * (transform->grid_size-1)); + int y_n = ceil(linear_g * (transform->grid_size-1)); + int z_n = ceil(linear_b * (transform->grid_size-1)); + float x_d = linear_r * (transform->grid_size-1) - x; + float y_d = linear_g * (transform->grid_size-1) - y; + float z_d = linear_b * (transform->grid_size-1) - z; + + float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d); + float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d); + float r_y1 = lerp(r_x1, r_x2, y_d); + float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d); + float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d); + float r_y2 = lerp(r_x3, r_x4, y_d); + float clut_r = lerp(r_y1, r_y2, z_d); + + float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d); + float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d); + float g_y1 = lerp(g_x1, g_x2, y_d); + float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d); + float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d); + float g_y2 = lerp(g_x3, g_x4, y_d); + float clut_g = lerp(g_y1, g_y2, z_d); + + float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d); + float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d); + float b_y1 = lerp(b_x1, b_x2, y_d); + float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d); + float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d); + float b_y2 = lerp(b_x3, b_x4, y_d); + float clut_b = lerp(b_y1, b_y2, z_d); + + float pcs_r = lut_interp_linear_float(clut_r, + transform->output_clut_table_r, transform->output_clut_table_length); + float pcs_g = lut_interp_linear_float(clut_g, + transform->output_clut_table_g, transform->output_clut_table_length); + float pcs_b = lut_interp_linear_float(clut_b, + transform->output_clut_table_b, transform->output_clut_table_length); + + *dest++ = clamp_float(pcs_r); + *dest++ = clamp_float(pcs_g); + *dest++ = clamp_float(pcs_b); + } +} + +/* NOT USED +static void qcms_transform_module_tetra_clut(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + int xy_len = 1; + int x_len = transform->grid_size; + int len = x_len * x_len; + float* r_table = transform->r_clut; + float* g_table = transform->g_clut; + float* b_table = transform->b_clut; + float c0_r, c1_r, c2_r, c3_r; + float c0_g, c1_g, c2_g, c3_g; + float c0_b, c1_b, c2_b, c3_b; + float clut_r, clut_g, clut_b; + float pcs_r, pcs_g, pcs_b; + for (i = 0; i < length; i++) { + float device_r = *src++; + float device_g = *src++; + float device_b = *src++; + float linear_r = lut_interp_linear_float(device_r, + transform->input_clut_table_r, transform->input_clut_table_length); + float linear_g = lut_interp_linear_float(device_g, + transform->input_clut_table_g, transform->input_clut_table_length); + float linear_b = lut_interp_linear_float(device_b, + transform->input_clut_table_b, transform->input_clut_table_length); + + int x = floor(linear_r * (transform->grid_size-1)); + int y = floor(linear_g * (transform->grid_size-1)); + int z = floor(linear_b * (transform->grid_size-1)); + int x_n = ceil(linear_r * (transform->grid_size-1)); + int y_n = ceil(linear_g * (transform->grid_size-1)); + int z_n = ceil(linear_b * (transform->grid_size-1)); + float rx = linear_r * (transform->grid_size-1) - x; + float ry = linear_g * (transform->grid_size-1) - y; + float rz = linear_b * (transform->grid_size-1) - z; + + c0_r = CLU(r_table, x, y, z); + c0_g = CLU(g_table, x, y, z); + c0_b = CLU(b_table, x, y, z); + if( rx >= ry ) { + if (ry >= rz) { //rx >= ry && ry >= rz + c1_r = CLU(r_table, x_n, y, z) - c0_r; + c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z); + c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); + c1_g = CLU(g_table, x_n, y, z) - c0_g; + c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z); + c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); + c1_b = CLU(b_table, x_n, y, z) - c0_b; + c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z); + c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); + } else { + if (rx >= rz) { //rx >= rz && rz >= ry + c1_r = CLU(r_table, x_n, y, z) - c0_r; + c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); + c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z); + c1_g = CLU(g_table, x_n, y, z) - c0_g; + c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); + c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z); + c1_b = CLU(b_table, x_n, y, z) - c0_b; + c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); + c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z); + } else { //rz > rx && rx >= ry + c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n); + c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); + c3_r = CLU(r_table, x, y, z_n) - c0_r; + c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n); + c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); + c3_g = CLU(g_table, x, y, z_n) - c0_g; + c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n); + c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); + c3_b = CLU(b_table, x, y, z_n) - c0_b; + } + } + } else { + if (rx >= rz) { //ry > rx && rx >= rz + c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z); + c2_r = CLU(r_table, x_n, y_n, z) - c0_r; + c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); + c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z); + c2_g = CLU(g_table, x_n, y_n, z) - c0_g; + c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); + c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z); + c2_b = CLU(b_table, x_n, y_n, z) - c0_b; + c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); + } else { + if (ry >= rz) { //ry >= rz && rz > rx + c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); + c2_r = CLU(r_table, x, y_n, z) - c0_r; + c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z); + c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); + c2_g = CLU(g_table, x, y_n, z) - c0_g; + c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z); + c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); + c2_b = CLU(b_table, x, y_n, z) - c0_b; + c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z); + } else { //rz > ry && ry > rx + c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); + c2_r = CLU(r_table, x, y_n, z) - c0_r; + c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); + c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); + c2_g = CLU(g_table, x, y_n, z) - c0_g; + c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); + c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); + c2_b = CLU(b_table, x, y_n, z) - c0_b; + c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); + } + } + } + + clut_r = c0_r + c1_r*rx + c2_r*ry + c3_r*rz; + clut_g = c0_g + c1_g*rx + c2_g*ry + c3_g*rz; + clut_b = c0_b + c1_b*rx + c2_b*ry + c3_b*rz; + + pcs_r = lut_interp_linear_float(clut_r, + transform->output_clut_table_r, transform->output_clut_table_length); + pcs_g = lut_interp_linear_float(clut_g, + transform->output_clut_table_g, transform->output_clut_table_length); + pcs_b = lut_interp_linear_float(clut_b, + transform->output_clut_table_b, transform->output_clut_table_length); + *dest++ = clamp_float(pcs_r); + *dest++ = clamp_float(pcs_g); + *dest++ = clamp_float(pcs_b); + } +} +*/ + +static void qcms_transform_module_gamma_table(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + float out_r, out_g, out_b; + for (i = 0; i < length; i++) { + float in_r = *src++; + float in_g = *src++; + float in_b = *src++; + + out_r = lut_interp_linear_float(in_r, transform->input_clut_table_r, 256); + out_g = lut_interp_linear_float(in_g, transform->input_clut_table_g, 256); + out_b = lut_interp_linear_float(in_b, transform->input_clut_table_b, 256); + + *dest++ = clamp_float(out_r); + *dest++ = clamp_float(out_g); + *dest++ = clamp_float(out_b); + } +} + +static void qcms_transform_module_gamma_lut(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + float out_r, out_g, out_b; + for (i = 0; i < length; i++) { + float in_r = *src++; + float in_g = *src++; + float in_b = *src++; + + out_r = lut_interp_linear(in_r, + transform->output_gamma_lut_r, transform->output_gamma_lut_r_length); + out_g = lut_interp_linear(in_g, + transform->output_gamma_lut_g, transform->output_gamma_lut_g_length); + out_b = lut_interp_linear(in_b, + transform->output_gamma_lut_b, transform->output_gamma_lut_b_length); + + *dest++ = clamp_float(out_r); + *dest++ = clamp_float(out_g); + *dest++ = clamp_float(out_b); + } +} + +static void qcms_transform_module_matrix_translate(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + struct matrix mat; + + /* store the results in column major mode + * this makes doing the multiplication with sse easier */ + mat.m[0][0] = transform->matrix.m[0][0]; + mat.m[1][0] = transform->matrix.m[0][1]; + mat.m[2][0] = transform->matrix.m[0][2]; + mat.m[0][1] = transform->matrix.m[1][0]; + mat.m[1][1] = transform->matrix.m[1][1]; + mat.m[2][1] = transform->matrix.m[1][2]; + mat.m[0][2] = transform->matrix.m[2][0]; + mat.m[1][2] = transform->matrix.m[2][1]; + mat.m[2][2] = transform->matrix.m[2][2]; + + for (i = 0; i < length; i++) { + float in_r = *src++; + float in_g = *src++; + float in_b = *src++; + + float out_r = mat.m[0][0]*in_r + mat.m[1][0]*in_g + mat.m[2][0]*in_b + transform->tx; + float out_g = mat.m[0][1]*in_r + mat.m[1][1]*in_g + mat.m[2][1]*in_b + transform->ty; + float out_b = mat.m[0][2]*in_r + mat.m[1][2]*in_g + mat.m[2][2]*in_b + transform->tz; + + *dest++ = clamp_float(out_r); + *dest++ = clamp_float(out_g); + *dest++ = clamp_float(out_b); + } +} + +static void qcms_transform_module_matrix(struct qcms_modular_transform *transform, float *src, float *dest, size_t length) +{ + size_t i; + struct matrix mat; + + /* store the results in column major mode + * this makes doing the multiplication with sse easier */ + mat.m[0][0] = transform->matrix.m[0][0]; + mat.m[1][0] = transform->matrix.m[0][1]; + mat.m[2][0] = transform->matrix.m[0][2]; + mat.m[0][1] = transform->matrix.m[1][0]; + mat.m[1][1] = transform->matrix.m[1][1]; + mat.m[2][1] = transform->matrix.m[1][2]; + mat.m[0][2] = transform->matrix.m[2][0]; + mat.m[1][2] = transform->matrix.m[2][1]; + mat.m[2][2] = transform->matrix.m[2][2]; + + for (i = 0; i < length; i++) { + float in_r = *src++; + float in_g = *src++; + float in_b = *src++; + + float out_r = mat.m[0][0]*in_r + mat.m[1][0]*in_g + mat.m[2][0]*in_b; + float out_g = mat.m[0][1]*in_r + mat.m[1][1]*in_g + mat.m[2][1]*in_b; + float out_b = mat.m[0][2]*in_r + mat.m[1][2]*in_g + mat.m[2][2]*in_b; + + *dest++ = clamp_float(out_r); + *dest++ = clamp_float(out_g); + *dest++ = clamp_float(out_b); + } +} + +static struct qcms_modular_transform* qcms_modular_transform_alloc() { + return calloc(1, sizeof(struct qcms_modular_transform)); +} + +static void qcms_modular_transform_release(struct qcms_modular_transform *transform) +{ + struct qcms_modular_transform *next_transform; + while (transform != NULL) { + next_transform = transform->next_transform; + // clut may use a single block of memory. + // Perhaps we should remove this to simply the code. + if (transform->input_clut_table_r + transform->input_clut_table_length == transform->input_clut_table_g && transform->input_clut_table_g + transform->input_clut_table_length == transform->input_clut_table_b) { + if (transform->input_clut_table_r) free(transform->input_clut_table_r); + } else { + if (transform->input_clut_table_r) free(transform->input_clut_table_r); + if (transform->input_clut_table_g) free(transform->input_clut_table_g); + if (transform->input_clut_table_b) free(transform->input_clut_table_b); + } + if (transform->r_clut + 1 == transform->g_clut && transform->g_clut + 1 == transform->b_clut) { + if (transform->r_clut) free(transform->r_clut); + } else { + if (transform->r_clut) free(transform->r_clut); + if (transform->g_clut) free(transform->g_clut); + if (transform->b_clut) free(transform->b_clut); + } + if (transform->output_clut_table_r + transform->output_clut_table_length == transform->output_clut_table_g && transform->output_clut_table_g+ transform->output_clut_table_length == transform->output_clut_table_b) { + if (transform->output_clut_table_r) free(transform->output_clut_table_r); + } else { + if (transform->output_clut_table_r) free(transform->output_clut_table_r); + if (transform->output_clut_table_g) free(transform->output_clut_table_g); + if (transform->output_clut_table_b) free(transform->output_clut_table_b); + } + if (transform->output_gamma_lut_r) free(transform->output_gamma_lut_r); + if (transform->output_gamma_lut_g) free(transform->output_gamma_lut_g); + if (transform->output_gamma_lut_b) free(transform->output_gamma_lut_b); + free(transform); + transform = next_transform; + } +} + +/* Set transform to be the next element in the linked list. */ +static void append_transform(struct qcms_modular_transform *transform, struct qcms_modular_transform ***next_transform) +{ + **next_transform = transform; + while (transform) { + *next_transform = &(transform->next_transform); + transform = transform->next_transform; + } +} + +/* reverse the transformation list (used by mBA) */ +static struct qcms_modular_transform* reverse_transform(struct qcms_modular_transform *transform) +{ + struct qcms_modular_transform *prev_transform = NULL; + while (transform != NULL) { + struct qcms_modular_transform *next_transform = transform->next_transform; + transform->next_transform = prev_transform; + prev_transform = transform; + transform = next_transform; + } + + return prev_transform; +} + +#define EMPTY_TRANSFORM_LIST NULL +static struct qcms_modular_transform* qcms_modular_transform_create_mAB(struct lutmABType *lut) +{ + struct qcms_modular_transform *first_transform = NULL; + struct qcms_modular_transform **next_transform = &first_transform; + struct qcms_modular_transform *transform = NULL; + + if (lut->a_curves[0] != NULL) { + size_t clut_length; + float *clut; + + // If the A curve is present this also implies the + // presence of a CLUT. + if (!lut->clut_table) + goto fail; + + // Prepare A curve. + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->input_clut_table_r = build_input_gamma_table(lut->a_curves[0]); + transform->input_clut_table_g = build_input_gamma_table(lut->a_curves[1]); + transform->input_clut_table_b = build_input_gamma_table(lut->a_curves[2]); + transform->transform_module_fn = qcms_transform_module_gamma_table; + if (lut->num_grid_points[0] != lut->num_grid_points[1] || + lut->num_grid_points[1] != lut->num_grid_points[2] ) { + //XXX: We don't currently support clut that are not squared! + goto fail; + } + + // Prepare CLUT + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + clut_length = sizeof(float)*pow(lut->num_grid_points[0], 3)*3; + clut = malloc(clut_length); + if (!clut) + goto fail; + memcpy(clut, lut->clut_table, clut_length); + transform->r_clut = clut + 0; + transform->g_clut = clut + 1; + transform->b_clut = clut + 2; + transform->grid_size = lut->num_grid_points[0]; + transform->transform_module_fn = qcms_transform_module_clut_only; + } + if (lut->m_curves[0] != NULL) { + // M curve imples the presence of a Matrix + + // Prepare M curve + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->input_clut_table_r = build_input_gamma_table(lut->m_curves[0]); + transform->input_clut_table_g = build_input_gamma_table(lut->m_curves[1]); + transform->input_clut_table_b = build_input_gamma_table(lut->m_curves[2]); + transform->transform_module_fn = qcms_transform_module_gamma_table; + + // Prepare Matrix + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->matrix = build_mAB_matrix(lut); + if (transform->matrix.invalid) + goto fail; + transform->tx = s15Fixed16Number_to_float(lut->e03); + transform->ty = s15Fixed16Number_to_float(lut->e13); + transform->tz = s15Fixed16Number_to_float(lut->e23); + transform->transform_module_fn = qcms_transform_module_matrix_translate; + } + if (lut->b_curves[0] != NULL) { + // Prepare B curve + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->input_clut_table_r = build_input_gamma_table(lut->b_curves[0]); + transform->input_clut_table_g = build_input_gamma_table(lut->b_curves[1]); + transform->input_clut_table_b = build_input_gamma_table(lut->b_curves[2]); + transform->transform_module_fn = qcms_transform_module_gamma_table; + } else { + // B curve is mandatory + goto fail; + } + + if (lut->reversed) { + // mBA are identical to mAB except that the transformation order + // is reversed + first_transform = reverse_transform(first_transform); + } + + return first_transform; +fail: + qcms_modular_transform_release(first_transform); + return NULL; +} + +static struct qcms_modular_transform* qcms_modular_transform_create_lut(struct lutType *lut) +{ + struct qcms_modular_transform *first_transform = NULL; + struct qcms_modular_transform **next_transform = &first_transform; + struct qcms_modular_transform *transform = NULL; + + size_t in_curve_len, clut_length, out_curve_len; + float *in_curves, *clut, *out_curves; + + // Prepare Matrix + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->matrix = build_lut_matrix(lut); + if (transform->matrix.invalid) + goto fail; + transform->transform_module_fn = qcms_transform_module_matrix; + + // Prepare input curves + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + in_curve_len = sizeof(float)*lut->num_input_table_entries * 3; + in_curves = malloc(in_curve_len); + if (!in_curves) + goto fail; + memcpy(in_curves, lut->input_table, in_curve_len); + transform->input_clut_table_r = in_curves + lut->num_input_table_entries * 0; + transform->input_clut_table_g = in_curves + lut->num_input_table_entries * 1; + transform->input_clut_table_b = in_curves + lut->num_input_table_entries * 2; + transform->input_clut_table_length = lut->num_input_table_entries; + + // Prepare table + clut_length = sizeof(float)*pow(lut->num_clut_grid_points, 3)*3; + clut = malloc(clut_length); + if (!clut) + goto fail; + memcpy(clut, lut->clut_table, clut_length); + transform->r_clut = clut + 0; + transform->g_clut = clut + 1; + transform->b_clut = clut + 2; + transform->grid_size = lut->num_clut_grid_points; + + // Prepare output curves + out_curve_len = sizeof(float) * lut->num_output_table_entries * 3; + out_curves = malloc(out_curve_len); + if (!out_curves) + goto fail; + memcpy(out_curves, lut->output_table, out_curve_len); + transform->output_clut_table_r = out_curves + lut->num_output_table_entries * 0; + transform->output_clut_table_g = out_curves + lut->num_output_table_entries * 1; + transform->output_clut_table_b = out_curves + lut->num_output_table_entries * 2; + transform->output_clut_table_length = lut->num_output_table_entries; + transform->transform_module_fn = qcms_transform_module_clut; + + return first_transform; +fail: + qcms_modular_transform_release(first_transform); + return NULL; +} + +struct qcms_modular_transform* qcms_modular_transform_create_input(qcms_profile *in) +{ + struct qcms_modular_transform *first_transform = NULL; + struct qcms_modular_transform **next_transform = &first_transform; + + if (in->A2B0) { + struct qcms_modular_transform *lut_transform; + lut_transform = qcms_modular_transform_create_lut(in->A2B0); + if (!lut_transform) + goto fail; + append_transform(lut_transform, &next_transform); + } else if (in->mAB && in->mAB->num_in_channels == 3 && in->mAB->num_out_channels == 3) { + struct qcms_modular_transform *mAB_transform; + mAB_transform = qcms_modular_transform_create_mAB(in->mAB); + if (!mAB_transform) + goto fail; + append_transform(mAB_transform, &next_transform); + + } else { + struct qcms_modular_transform *transform; + + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->input_clut_table_r = build_input_gamma_table(in->redTRC); + transform->input_clut_table_g = build_input_gamma_table(in->greenTRC); + transform->input_clut_table_b = build_input_gamma_table(in->blueTRC); + transform->transform_module_fn = qcms_transform_module_gamma_table; + if (!transform->input_clut_table_r || !transform->input_clut_table_g || + !transform->input_clut_table_b) { + goto fail; + } + + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->matrix.m[0][0] = 1/1.999969482421875f; + transform->matrix.m[0][1] = 0.f; + transform->matrix.m[0][2] = 0.f; + transform->matrix.m[1][0] = 0.f; + transform->matrix.m[1][1] = 1/1.999969482421875f; + transform->matrix.m[1][2] = 0.f; + transform->matrix.m[2][0] = 0.f; + transform->matrix.m[2][1] = 0.f; + transform->matrix.m[2][2] = 1/1.999969482421875f; + transform->matrix.invalid = false; + transform->transform_module_fn = qcms_transform_module_matrix; + + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->matrix = build_colorant_matrix(in); + transform->transform_module_fn = qcms_transform_module_matrix; + } + + return first_transform; +fail: + qcms_modular_transform_release(first_transform); + return EMPTY_TRANSFORM_LIST; +} +static struct qcms_modular_transform* qcms_modular_transform_create_output(qcms_profile *out) +{ + struct qcms_modular_transform *first_transform = NULL; + struct qcms_modular_transform **next_transform = &first_transform; + + if (out->B2A0) { + struct qcms_modular_transform *lut_transform; + lut_transform = qcms_modular_transform_create_lut(out->B2A0); + if (!lut_transform) + goto fail; + append_transform(lut_transform, &next_transform); + } else if (out->mBA && out->mBA->num_in_channels == 3 && out->mBA->num_out_channels == 3) { + struct qcms_modular_transform *lut_transform; + lut_transform = qcms_modular_transform_create_mAB(out->mBA); + if (!lut_transform) + goto fail; + append_transform(lut_transform, &next_transform); + } else if (out->redTRC && out->greenTRC && out->blueTRC) { + struct qcms_modular_transform *transform; + + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->matrix = matrix_invert(build_colorant_matrix(out)); + transform->transform_module_fn = qcms_transform_module_matrix; + + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + transform->matrix.m[0][0] = 1.999969482421875f; + transform->matrix.m[0][1] = 0.f; + transform->matrix.m[0][2] = 0.f; + transform->matrix.m[1][0] = 0.f; + transform->matrix.m[1][1] = 1.999969482421875f; + transform->matrix.m[1][2] = 0.f; + transform->matrix.m[2][0] = 0.f; + transform->matrix.m[2][1] = 0.f; + transform->matrix.m[2][2] = 1.999969482421875f; + transform->matrix.invalid = false; + transform->transform_module_fn = qcms_transform_module_matrix; + + transform = qcms_modular_transform_alloc(); + if (!transform) + goto fail; + append_transform(transform, &next_transform); + build_output_lut(out->redTRC, &transform->output_gamma_lut_r, + &transform->output_gamma_lut_r_length); + build_output_lut(out->greenTRC, &transform->output_gamma_lut_g, + &transform->output_gamma_lut_g_length); + build_output_lut(out->blueTRC, &transform->output_gamma_lut_b, + &transform->output_gamma_lut_b_length); + transform->transform_module_fn = qcms_transform_module_gamma_lut; + + if (!transform->output_gamma_lut_r || !transform->output_gamma_lut_g || + !transform->output_gamma_lut_b) { + goto fail; + } + } else { + assert(0 && "Unsupported output profile workflow."); + return NULL; + } + + return first_transform; +fail: + qcms_modular_transform_release(first_transform); + return EMPTY_TRANSFORM_LIST; +} + +/* Not Completed +// Simplify the transformation chain to an equivalent transformation chain +static struct qcms_modular_transform* qcms_modular_transform_reduce(struct qcms_modular_transform *transform) +{ + struct qcms_modular_transform *first_transform = NULL; + struct qcms_modular_transform *curr_trans = transform; + struct qcms_modular_transform *prev_trans = NULL; + while (curr_trans) { + struct qcms_modular_transform *next_trans = curr_trans->next_transform; + if (curr_trans->transform_module_fn == qcms_transform_module_matrix) { + if (next_trans && next_trans->transform_module_fn == qcms_transform_module_matrix) { + curr_trans->matrix = matrix_multiply(curr_trans->matrix, next_trans->matrix); + goto remove_next; + } + } + if (curr_trans->transform_module_fn == qcms_transform_module_gamma_table) { + bool isLinear = true; + uint16_t i; + for (i = 0; isLinear && i < 256; i++) { + isLinear &= (int)(curr_trans->input_clut_table_r[i] * 255) == i; + isLinear &= (int)(curr_trans->input_clut_table_g[i] * 255) == i; + isLinear &= (int)(curr_trans->input_clut_table_b[i] * 255) == i; + } + goto remove_current; + } + +next_transform: + if (!next_trans) break; + prev_trans = curr_trans; + curr_trans = next_trans; + continue; +remove_current: + if (curr_trans == transform) { + //Update head + transform = next_trans; + } else { + prev_trans->next_transform = next_trans; + } + curr_trans->next_transform = NULL; + qcms_modular_transform_release(curr_trans); + //return transform; + return qcms_modular_transform_reduce(transform); +remove_next: + curr_trans->next_transform = next_trans->next_transform; + next_trans->next_transform = NULL; + qcms_modular_transform_release(next_trans); + continue; + } + return transform; +} +*/ + +static struct qcms_modular_transform* qcms_modular_transform_create(qcms_profile *in, qcms_profile *out) +{ + struct qcms_modular_transform *first_transform = NULL; + struct qcms_modular_transform **next_transform = &first_transform; + qcms_bool transform_to_pcs_xyz_only = (out == NULL); + + if (in->color_space == RGB_SIGNATURE) { + struct qcms_modular_transform* rgb_to_pcs; + rgb_to_pcs = qcms_modular_transform_create_input(in); + if (!rgb_to_pcs) + goto fail; + append_transform(rgb_to_pcs, &next_transform); + } else { + assert(0 && "input color space not supported"); + goto fail; + } + + if (in->pcs == LAB_SIGNATURE && (transform_to_pcs_xyz_only || out->pcs == XYZ_SIGNATURE)) { + struct qcms_modular_transform* lab_to_pcs; + lab_to_pcs = qcms_modular_transform_alloc(); + if (!lab_to_pcs) + goto fail; + append_transform(lab_to_pcs, &next_transform); + lab_to_pcs->transform_module_fn = qcms_transform_module_LAB_to_XYZ; + } + + if (transform_to_pcs_xyz_only) + return first_transform; + + // This does not improve accuracy in practice, something is wrong here. + //if (in->chromaticAdaption.invalid == false) { + // struct qcms_modular_transform* chromaticAdaption; + // chromaticAdaption = qcms_modular_transform_alloc(); + // if (!chromaticAdaption) + // goto fail; + // append_transform(chromaticAdaption, &next_transform); + // chromaticAdaption->matrix = matrix_invert(in->chromaticAdaption); + // chromaticAdaption->transform_module_fn = qcms_transform_module_matrix; + //} + + if (in->pcs == XYZ_SIGNATURE && out->pcs == LAB_SIGNATURE) { + struct qcms_modular_transform* pcs_to_lab; + pcs_to_lab = qcms_modular_transform_alloc(); + if (!pcs_to_lab) + goto fail; + append_transform(pcs_to_lab, &next_transform); + pcs_to_lab->transform_module_fn = qcms_transform_module_XYZ_to_LAB; + } + + if (out->color_space == RGB_SIGNATURE) { + struct qcms_modular_transform* pcs_to_rgb; + pcs_to_rgb = qcms_modular_transform_create_output(out); + if (!pcs_to_rgb) + goto fail; + append_transform(pcs_to_rgb, &next_transform); + } else { + assert(0 && "output color space not supported"); + goto fail; + } + // Not Completed + //return qcms_modular_transform_reduce(first_transform); + return first_transform; +fail: + qcms_modular_transform_release(first_transform); + return EMPTY_TRANSFORM_LIST; +} + +static float* qcms_modular_transform_data(struct qcms_modular_transform *transform, float *src, float *dest, size_t len) +{ + while (transform != NULL) { + // Keep swaping src/dest when performing a transform to use less memory. + float *new_src = dest; + const transform_module_fn_t transform_fn = transform->transform_module_fn; + if (transform_fn != qcms_transform_module_gamma_table && + transform_fn != qcms_transform_module_gamma_lut && + transform_fn != qcms_transform_module_clut && + transform_fn != qcms_transform_module_clut_only && + transform_fn != qcms_transform_module_matrix && + transform_fn != qcms_transform_module_matrix_translate && + transform_fn != qcms_transform_module_LAB_to_XYZ && + transform_fn != qcms_transform_module_XYZ_to_LAB) { + assert(0 && "Unsupported transform module"); + return NULL; + } + transform->transform_module_fn(transform,src,dest,len); + dest = src; + src = new_src; + transform = transform->next_transform; + } + // The results end up in the src buffer because of the switching + return src; +} + +float* qcms_chain_transform(qcms_profile *in, qcms_profile *out, float *src, float *dest, size_t lutSize) +{ + struct qcms_modular_transform *transform_list = qcms_modular_transform_create(in, out); + if (transform_list != NULL) { + float *lut = qcms_modular_transform_data(transform_list, src, dest, lutSize/3); + qcms_modular_transform_release(transform_list); + return lut; + } + return NULL; +} + +qcms_bool qcms_profile_white_transform(qcms_profile *profile, float XYZ[3]) +{ + const float inverse_internal_scale = 1.999969482421875f; + + // Set the output profile to NULL to request a color transform to PCS XYZ only. + struct qcms_modular_transform *transform_list = qcms_modular_transform_create(profile, NULL); + + // Now calculate how the profile transforms white input color to PCS XYZ space. + if (transform_list != NULL) { + XYZ[0] = XYZ[1] = XYZ[2] = 1.0f; // white input + qcms_modular_transform_data(transform_list, XYZ, XYZ, 1); + // qcms_modular_transform_create internally scales input by 1/1.999969482421875f + // but no qcms changelog describes why / how that number was choosen. junov@ "it + // might be related to the epsilon of the fixed-point type 2*(1-1/(2^16)), but + // there is no explanation, which is disconcerting." Meanwhile, undo the internal + // scaling so we return a normalized CIEXYZ value viz., where Y is scaled to 1.0. + // A properly created color profile should produce Y=~1.0 in PCS XYZ with white + // input (the D50 test). If it does not, then the profile is likely bogus. + XYZ[0] *= inverse_internal_scale; + XYZ[1] *= inverse_internal_scale; + XYZ[2] *= inverse_internal_scale; + qcms_modular_transform_release(transform_list); + return true; + } + + return false; +} diff --git a/third_party/qcms/src/chain.h b/third_party/qcms/src/chain.h new file mode 100644 index 0000000000..bdc6c8872a --- /dev/null +++ b/third_party/qcms/src/chain.h @@ -0,0 +1,30 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// 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. + +#ifndef _QCMS_CHAIN_H +#define _QCMS_CHAIN_H + +// Generates and returns a 3D LUT with lutSize^3 samples using the provided src/dest. +float* qcms_chain_transform(qcms_profile *in, qcms_profile *out, float *src, float *dest, size_t lutSize); + +#endif diff --git a/third_party/qcms/src/empty.c b/third_party/qcms/src/empty.c new file mode 100644 index 0000000000..6fd68b8f64 --- /dev/null +++ b/third_party/qcms/src/empty.c @@ -0,0 +1,6 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. +/* + * qcms is not built on this port: see http://crbug.com/577155 + */ diff --git a/third_party/qcms/src/halffloat.h b/third_party/qcms/src/halffloat.h new file mode 100644 index 0000000000..35c725066b --- /dev/null +++ b/third_party/qcms/src/halffloat.h @@ -0,0 +1,106 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// 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. + +#ifndef _QCMS_HALFFLOAT_H +#define _QCMS_HALFFLOAT_H + +const unsigned short qcms_half_float_base_table[512] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, + 512, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192, 9216, 10240, 11264, 12288, 13312, 14336, 15360, + 16384, 17408, 18432, 19456, 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672, 29696, 30720, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32769, 32770, 32772, 32776, 32784, 32800, 32832, 32896, 33024, + 33280, 33792, 34816, 35840, 36864, 37888, 38912, 39936, 40960, 41984, 43008, 44032, 45056, 46080, 47104, 48128, + 49152, 50176, 51200, 52224, 53248, 54272, 55296, 56320, 57344, 58368, 59392, 60416, 61440, 62464, 63488, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512 +}; + +const unsigned char qcms_half_float_shift_table[512] = { + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, + 14, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 13, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, + 14, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 13 +}; + +static inline unsigned short float_to_half_float(float f) +{ + // See Blink::Source/platform/graphics/gpu/WebGLImageConversion.cpp::convertFloatToHalfFloat() and http://crbug.com/491784 + + unsigned temp = *((unsigned *)(&f)); + unsigned signexp = (temp >> 23) & 0x1ff; + return qcms_half_float_base_table[signexp] + ((temp & 0x007fffff) >> qcms_half_float_shift_table[signexp]); +} + +#endif diff --git a/third_party/qcms/src/iccread.c b/third_party/qcms/src/iccread.c new file mode 100644 index 0000000000..b0a9a0296f --- /dev/null +++ b/third_party/qcms/src/iccread.c @@ -0,0 +1,1645 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// Copyright (C) 1998-2007 Marti Maria +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include <math.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> //memset +#include "qcmsint.h" + +/* It might be worth having a unified limit on content controlled + * allocation per profile. This would remove the need for many + * of the arbitrary limits that we used */ + +typedef uint32_t be32; +typedef uint16_t be16; + +#if 0 +not used yet +/* __builtin_bswap isn't available in older gccs + * so open code it for now */ +static be32 cpu_to_be32(int32_t v) +{ +#ifdef IS_LITTLE_ENDIAN + return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) | ((v & 0xff000000) >> 24); + //return __builtin_bswap32(v); + return v; +#endif +} +#endif + +static uint32_t be32_to_cpu(be32 v) +{ +#ifdef IS_LITTLE_ENDIAN + return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) | ((v & 0xff000000) >> 24); + //return __builtin_bswap32(v); +#else + return v; +#endif +} + +static uint16_t be16_to_cpu(be16 v) +{ +#ifdef IS_LITTLE_ENDIAN + return ((v & 0xff) << 8) | ((v & 0xff00) >> 8); +#else + return v; +#endif +} + +/* a wrapper around the memory that we are going to parse + * into a qcms_profile */ +struct mem_source +{ + const unsigned char *buf; + size_t size; + qcms_bool valid; + const char *invalid_reason; +}; + +static void invalid_source(struct mem_source *mem, const char *reason) +{ + mem->valid = false; + mem->invalid_reason = reason; +} + +static uint32_t read_u32(struct mem_source *mem, size_t offset) +{ + /* Subtract from mem->size instead of the more intuitive adding to offset. + * This avoids overflowing offset. The subtraction is safe because + * mem->size is guaranteed to be > 4 */ + if (offset > mem->size - 4) { + invalid_source(mem, "Invalid offset"); + return 0; + } else { + be32 k; + memcpy(&k, mem->buf + offset, sizeof(k)); + return be32_to_cpu(k); + } +} + +static uint16_t read_u16(struct mem_source *mem, size_t offset) +{ + if (offset > mem->size - 2) { + invalid_source(mem, "Invalid offset"); + return 0; + } else { + be16 k; + memcpy(&k, mem->buf + offset, sizeof(k)); + return be16_to_cpu(k); + } +} + +static uint8_t read_u8(struct mem_source *mem, size_t offset) +{ + if (offset > mem->size - 1) { + invalid_source(mem, "Invalid offset"); + return 0; + } else { + return *(uint8_t*)(mem->buf + offset); + } +} + +static s15Fixed16Number read_s15Fixed16Number(struct mem_source *mem, size_t offset) +{ + return read_u32(mem, offset); +} + +static uInt8Number read_uInt8Number(struct mem_source *mem, size_t offset) +{ + return read_u8(mem, offset); +} + +static uInt16Number read_uInt16Number(struct mem_source *mem, size_t offset) +{ + return read_u16(mem, offset); +} + +#define BAD_VALUE_PROFILE NULL +#define INVALID_PROFILE NULL +#define NO_MEM_PROFILE NULL + +/* An arbitrary 4MB limit on profile size */ +#define MAX_PROFILE_SIZE 1024*1024*4 +#define MAX_TAG_COUNT 1024 + +static void check_CMM_type_signature(struct mem_source *src) +{ + //uint32_t CMM_type_signature = read_u32(src, 4); +} + +static void read_profile_version(qcms_profile *profile, struct mem_source *src) +{ + uint8_t major_revision = read_u8(src, 8 + 0); + uint8_t minor_revision = read_u8(src, 8 + 1); + uint8_t reserved_byte1 = read_u8(src, 8 + 2); + uint8_t reserved_byte2 = read_u8(src, 8 + 3); + + profile->icc_version = major_revision << 8 | minor_revision; + + if (reserved_byte1 || reserved_byte2) { + invalid_source(src, "Invalid reserved bytes"); + return; + } + + if (major_revision == 2) + return; // ICC V2.X color profile + if (major_revision == 4 && qcms_supports_iccv4) + return; // ICC V4.X color profile + + /* Checking the version doesn't buy us anything: permit any + version without failure for now */ + // invalid_source(src, "Unsupported ICC revision"); + return; +} + +#define INPUT_DEVICE_PROFILE 0x73636e72 // 'scnr' +#define DISPLAY_DEVICE_PROFILE 0x6d6e7472 // 'mntr' +#define OUTPUT_DEVICE_PROFILE 0x70727472 // 'prtr' +#define DEVICE_LINK_PROFILE 0x6c696e6b // 'link' +#define COLOR_SPACE_PROFILE 0x73706163 // 'spac' +#define ABSTRACT_PROFILE 0x61627374 // 'abst' +#define NAMED_COLOR_PROFILE 0x6e6d636c // 'nmcl' + +static void read_class_signature(qcms_profile *profile, struct mem_source *mem) +{ + profile->class = read_u32(mem, 12); + switch (profile->class) { + case DISPLAY_DEVICE_PROFILE: + case INPUT_DEVICE_PROFILE: + case OUTPUT_DEVICE_PROFILE: + case COLOR_SPACE_PROFILE: + break; + default: + invalid_source(mem, "Invalid Profile/Device Class signature"); + } +} + +static void read_color_space(qcms_profile *profile, struct mem_source *mem) +{ + profile->color_space = read_u32(mem, 16); + switch (profile->color_space) { + case RGB_SIGNATURE: + case GRAY_SIGNATURE: + break; + default: + invalid_source(mem, "Unsupported colorspace"); + } +} + +static void read_pcs(qcms_profile *profile, struct mem_source *mem) +{ + profile->pcs = read_u32(mem, 20); + switch (profile->pcs) { + case XYZ_SIGNATURE: + case LAB_SIGNATURE: + break; + default: + invalid_source(mem, "Unsupported pcs"); + } +} + +struct tag { + uint32_t signature; + uint32_t offset; + uint32_t size; +}; + +struct tag_index { + uint32_t count; + struct tag *tags; +}; + +static struct tag_index read_tag_table(qcms_profile *profile, struct mem_source *mem) +{ + struct tag_index index = {0, NULL}; + unsigned int i; + + index.count = read_u32(mem, 128); + if (index.count > MAX_TAG_COUNT) { + invalid_source(mem, "max number of tags exceeded"); + return index; + } + + index.tags = malloc(sizeof(struct tag)*index.count); + if (index.tags) { + for (i = 0; i < index.count; i++) { + index.tags[i].signature = read_u32(mem, 128 + 4 + 4*i*3); + index.tags[i].offset = read_u32(mem, 128 + 4 + 4*i*3 + 4); + index.tags[i].size = read_u32(mem, 128 + 4 + 4*i*3 + 8); + } + } + + return index; +} + +/* Checks a profile for obvious inconsistencies and return true if the + * profile looks bogus and should probably be ignored. + */ +qcms_bool qcms_profile_is_bogus(qcms_profile *profile) +{ + float rX, rY, rZ, gX, gY, gZ, bX, bY, bZ; + float target[3], tolerance[3], sum[3]; + unsigned i; + + // We currently only check the bogosity of RGB profiles. + if (profile->color_space != RGB_SIGNATURE) + return false; + + if (qcms_supports_iccv4 && (profile->A2B0 || profile->B2A0)) + return false; + + rX = s15Fixed16Number_to_float(profile->redColorant.X); + rY = s15Fixed16Number_to_float(profile->redColorant.Y); + rZ = s15Fixed16Number_to_float(profile->redColorant.Z); + + gX = s15Fixed16Number_to_float(profile->greenColorant.X); + gY = s15Fixed16Number_to_float(profile->greenColorant.Y); + gZ = s15Fixed16Number_to_float(profile->greenColorant.Z); + + bX = s15Fixed16Number_to_float(profile->blueColorant.X); + bY = s15Fixed16Number_to_float(profile->blueColorant.Y); + bZ = s15Fixed16Number_to_float(profile->blueColorant.Z); + + // Build our target vector: CIE D50 white. See also mozilla bug 460629, + // and http://www.color.org/whyd50.xalter "Why is the media white point + // of a display profile always D50?" + + target[0] = (float) 0.96420; + target[1] = (float) 1.00000; + target[2] = (float) 0.82491; + + // Our tolerance vector - Recommended by Chris Murphy [1] based on + // conversion from the L*a*b space criterion of no more than 3 in any + // one channel. This is similar to, but slightly more tolerant than + // Adobe's criterion. [1] https://bugzil.la/460629#c10 + + tolerance[0] = (float) 0.02; + tolerance[1] = (float) 0.02; + tolerance[2] = (float) 0.04; + + // Sum the XYZ values: they should add to D50 white, within tolerance. + + // FIXME: this test assumes the TRC RGB curves equal 1.0 for the white + // input (255,255,255) RGB test color. For user display profiles, that + // is the normal case. Profiles with abnormal TRC exist. A better test + // would transform 255,255,255 white through the profile to either XYZ + // or L*a*b color and compare the result to D50 in XYZ or L*a*b color. + + sum[0] = rX + gX + bX; + sum[1] = rY + gY + bY; + sum[2] = rZ + gZ + bZ; + + for (i = 0; i < 3; ++i) { + if (!(((sum[i] - tolerance[i]) <= target[i]) && + ((sum[i] + tolerance[i]) >= target[i]))) { + return true; // out of tolerance: bogus + } + } + +#ifndef __APPLE__ + // Check if any of the XYZ values are negative (see mozilla bug 498245) + // CIEXYZ tristimulus values cannot be negative according to the spec. + + bool negative = + (rX < 0) || (rY < 0) || (rZ < 0) || + (gX < 0) || (gY < 0) || (gZ < 0) || + (bX < 0) || (bY < 0) || (bZ < 0); + + if (negative) + return true; // bogus +#else + // Chromatic adaption to D50 can result in negative XYZ, but the white + // point D50 tolerance test has passed. Accept negative values herein. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=498245#c18 onwards + // for discussion about whether profile XYZ can or cannot be negative, + // per the spec. Also the https://bugzil.la/450923 user report. + + // FIXME: allow this relaxation on all ports? +#endif + // All good. + return false; +} + +qcms_bool qcms_profile_has_white_point(qcms_profile *profile) +{ + struct XYZNumber wp = profile->mediaWhitePoint; + + return (wp.X != 0) && (wp.Y != 0) && (wp.Z != 0); +} + +qcms_xyz_float qcms_profile_get_white_point(qcms_profile *profile) +{ + qcms_xyz_float wp = { 0.0f, 0.0f, 0.0f }; + + if (qcms_profile_has_white_point(profile)) { + wp.X = s15Fixed16Number_to_float(profile->mediaWhitePoint.X); + wp.Y = s15Fixed16Number_to_float(profile->mediaWhitePoint.Y); + wp.Z = s15Fixed16Number_to_float(profile->mediaWhitePoint.Z); + } + + return wp; +} + +#define TAG_bXYZ 0x6258595a +#define TAG_gXYZ 0x6758595a +#define TAG_rXYZ 0x7258595a +#define TAG_rTRC 0x72545243 +#define TAG_bTRC 0x62545243 +#define TAG_gTRC 0x67545243 +#define TAG_kTRC 0x6b545243 +#define TAG_A2B0 0x41324230 +#define TAG_B2A0 0x42324130 +#define TAG_CHAD 0x63686164 +#define TAG_desc 0x64657363 +#define TAG_vcgt 0x76636774 +#define TAG_wtpt 0x77747074 + +static struct tag *find_tag(struct tag_index index, uint32_t tag_id) +{ + unsigned int i; + + for (i = 0; i < index.count; i++) { + if (index.tags[i].signature == tag_id) + return &index.tags[i]; + } + + return NULL; +} + +#define DESC_TYPE 0x64657363 // 'desc' +#define MLUC_TYPE 0x6d6c7563 // 'mluc' +#define MMOD_TYPE 0x6D6D6F64 // 'mmod' +#define VCGT_TYPE 0x76636774 // 'vcgt' + +enum { + VCGT_TYPE_TABLE, + VCGT_TYPE_FORMULA, + VCGT_TYPE_LAST = VCGT_TYPE_FORMULA +}; + +static qcms_bool read_tag_vcgtType(qcms_profile *profile, struct mem_source *src, struct tag_index index) +{ + size_t tag_offset = find_tag(index, TAG_vcgt)->offset; + uint32_t tag_type = read_u32(src, tag_offset); + uint32_t vcgt_type = read_u32(src, tag_offset + 8); + + if (!src->valid || tag_type != VCGT_TYPE) + goto invalid_vcgt_tag; + + // Only support table and equation types. + if (vcgt_type > VCGT_TYPE_LAST) + return true; + + if (vcgt_type == VCGT_TYPE_TABLE) { + uint16_t channels = read_u16(src, tag_offset + 12); + uint16_t elements = read_u16(src, tag_offset + 14); + uint16_t byte_depth = read_u16(src, tag_offset + 16); + size_t table_offset = tag_offset + 18; + uint32_t i; + uint16_t *dest; + + if (!src->valid) + goto invalid_vcgt_tag; + + // Only support 3 channels. + if (channels != 3) + return true; + // Only support single or double byte values. + if (byte_depth != 1 && byte_depth != 2) + return true; + // Limit the table to a sensible size; 10-bit gamma is a reasonable + // maximum for hardware correction. + if (elements > 1024) + return true; + + // Empty table is invalid. + if (!elements) + goto invalid_vcgt_tag; + + profile->vcgt.length = elements; + profile->vcgt.data = malloc(3 * elements * sizeof(uint16_t)); + if (!profile->vcgt.data) + return false; + + dest = profile->vcgt.data; + + for (i = 0; i < 3 * elements; ++i) { + if (byte_depth == 1) { + *dest++ = read_u8(src, table_offset) * 256; + } else { + *dest++ = read_u16(src, table_offset); + } + + table_offset += byte_depth; + + if (!src->valid) + goto invalid_vcgt_tag; + } + } else { + size_t formula_offset = tag_offset + 12; + int i, j; + uint16_t *dest; + + // For formula always provide an 8-bit lut. + profile->vcgt.length = 256; + profile->vcgt.data = malloc(3 * profile->vcgt.length * sizeof(uint16_t)); + if (!profile->vcgt.data) + return false; + + dest = profile->vcgt.data; + for (i = 0; i < 3; ++i) { + float gamma = s15Fixed16Number_to_float( + read_s15Fixed16Number(src, formula_offset + 12 * i)); + float min = s15Fixed16Number_to_float( + read_s15Fixed16Number(src, formula_offset + 4 + 12 * i)); + float max = s15Fixed16Number_to_float( + read_s15Fixed16Number(src, formula_offset + 8 + 12 * i)); + float range = max - min; + + if (!src->valid) + goto invalid_vcgt_tag; + + for (j = 0; j < profile->vcgt.length; ++j) { + *dest++ = 65535.f * + (min + range * pow((float)j / (profile->vcgt.length - 1), gamma)); + } + } + } + + return true; + +invalid_vcgt_tag: + invalid_source(src, "invalid vcgt tag"); + return false; +} + +static bool read_tag_descType(qcms_profile *profile, struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + struct tag *tag = find_tag(index, tag_id); + if (tag) { + const uint32_t limit = sizeof profile->description; + uint32_t offset = tag->offset; + uint32_t type = read_u32(src, offset); + uint32_t length = read_u32(src, offset+8); + uint32_t i, description_offset; + bool mluc = false; + if (length && type == MLUC_TYPE) { + length = read_u32(src, offset+20); + if (!length || (length & 1) || (read_u32(src, offset+12) != 12)) + goto invalid_desc_tag; + description_offset = offset + read_u32(src, offset+24); + if (!src->valid) + goto invalid_desc_tag; + mluc = true; + } else if (length && type == DESC_TYPE) { + description_offset = offset + 12; + } else { + goto invalid_desc_tag; + } + if (length >= limit) + length = limit - 1; + for (i = 0; i < length; ++i) { + uint8_t value = read_u8(src, description_offset + i); + if (!src->valid) + goto invalid_desc_tag; + if (mluc && !value) + value = '.'; + profile->description[i] = value; + } + profile->description[length] = 0; + } else { + goto invalid_desc_tag; + } + + if (src->valid) + return true; + +invalid_desc_tag: + invalid_source(src, "invalid description"); + return false; +} + +#if defined(__APPLE__) + +// Use the dscm tag to change profile description "Display" to its more specific en-localized monitor name, if any. + +#define TAG_dscm 0x6473636D // 'dscm' + +static bool read_tag_dscmType(qcms_profile *profile, struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + if (strcmp(profile->description, "Display") != 0) + return true; + + struct tag *tag = find_tag(index, tag_id); + if (tag) { + uint32_t offset = tag->offset; + uint32_t type = read_u32(src, offset); + uint32_t records = read_u32(src, offset+8); + + if (!src->valid || !records || type != MLUC_TYPE) + goto invalid_dscm_tag; + if (read_u32(src, offset+12) != 12) // MLUC record size: bytes + goto invalid_dscm_tag; + + for (uint32_t i = 0; i < records; ++i) { + const uint32_t limit = sizeof profile->description; + const uint16_t isoen = 0x656E; // ISO-3166-1 language 'en' + + uint16_t language = read_u16(src, offset + 16 + (i * 12) + 0); + uint32_t length = read_u32(src, offset + 16 + (i * 12) + 4); + uint32_t description_offset = read_u32(src, offset + 16 + (i * 12) + 8); + + if (!src->valid || !length || (length & 1)) + goto invalid_dscm_tag; + if (language != isoen) + continue; + + // Use a prefix to identify the display description source + strcpy(profile->description, "dscm:"); + length += 5; + + if (length >= limit) + length = limit - 1; + for (uint32_t j = 5; j < length; ++j) { + uint8_t value = read_u8(src, offset + description_offset + j - 5); + if (!src->valid) + goto invalid_dscm_tag; + profile->description[j] = value ? value : '.'; + } + profile->description[length] = 0; + break; + } + } + + if (src->valid) + return true; + +invalid_dscm_tag: + invalid_source(src, "invalid dscm tag"); + return false; +} + +// Use the mmod tag to change profile description "Display" to its specific mmod maker model data, if any. + +#define TAG_mmod 0x6D6D6F64 // 'mmod' + +static bool read_tag_mmodType(qcms_profile *profile, struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + if (strcmp(profile->description, "Display") != 0) + return true; + + struct tag *tag = find_tag(index, tag_id); + if (tag) { + const uint8_t length = 4 * 4; // Four 4-byte fields: 'mmod', 0, maker, model. + + uint32_t offset = tag->offset; + if (tag->size < 40 || read_u32(src, offset) != MMOD_TYPE) + goto invalid_mmod_tag; + + for (uint8_t i = 0; i < length; ++i) { + uint8_t value = read_u8(src, offset + i); + if (!src->valid) + goto invalid_mmod_tag; + profile->description[i] = value ? value : '.'; + } + profile->description[length] = 0; + } + + if (src->valid) + return true; + +invalid_mmod_tag: + invalid_source(src, "invalid mmod tag"); + return false; +} + +#endif // __APPLE__ + +#define XYZ_TYPE 0x58595a20 // 'XYZ ' +#define CURVE_TYPE 0x63757276 // 'curv' +#define PARAMETRIC_CURVE_TYPE 0x70617261 // 'para' +#define LUT16_TYPE 0x6d667432 // 'mft2' +#define LUT8_TYPE 0x6d667431 // 'mft1' +#define LUT_MAB_TYPE 0x6d414220 // 'mAB ' +#define LUT_MBA_TYPE 0x6d424120 // 'mBA ' +#define CHROMATIC_TYPE 0x73663332 // 'sf32' + +static struct matrix read_tag_s15Fixed16ArrayType(struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + struct tag *tag = find_tag(index, tag_id); + struct matrix matrix; + if (tag) { + uint8_t i; + uint32_t offset = tag->offset; + uint32_t type = read_u32(src, offset); + + // Check mandatory type signature for s16Fixed16ArrayType + if (type != CHROMATIC_TYPE) { + invalid_source(src, "unexpected type, expected 'sf32'"); + } + + for (i = 0; i < 9; i++) { + matrix.m[i/3][i%3] = s15Fixed16Number_to_float(read_s15Fixed16Number(src, offset+8+i*4)); + } + matrix.invalid = false; + } else { + matrix.invalid = true; + invalid_source(src, "missing sf32tag"); + } + return matrix; +} + +static struct XYZNumber read_tag_XYZType(struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + struct XYZNumber num = {0, 0, 0}; + struct tag *tag = find_tag(index, tag_id); + if (tag) { + uint32_t offset = tag->offset; + + uint32_t type = read_u32(src, offset); + if (type != XYZ_TYPE) + invalid_source(src, "unexpected type, expected XYZ"); + num.X = read_s15Fixed16Number(src, offset+8); + num.Y = read_s15Fixed16Number(src, offset+12); + num.Z = read_s15Fixed16Number(src, offset+16); + } else { + invalid_source(src, "missing xyztag"); + } + return num; +} + +// Read the tag at a given offset rather then the tag_index. +// This method is used when reading mAB tags where nested curveType are +// present that are not part of the tag_index. +static struct curveType *read_curveType(struct mem_source *src, uint32_t offset, uint32_t *len) +{ + static const uint32_t COUNT_TO_LENGTH[5] = {1, 3, 4, 5, 7}; + struct curveType *curve = NULL; + uint32_t type = read_u32(src, offset); + uint32_t count; + int i; + + if (type != CURVE_TYPE && type != PARAMETRIC_CURVE_TYPE) { + invalid_source(src, "unexpected type, expected CURV or PARA"); + return NULL; + } + + if (type == CURVE_TYPE) { + count = read_u32(src, offset+8); + +#define MAX_CURVE_ENTRIES 40000 //arbitrary + if (count > MAX_CURVE_ENTRIES) { + invalid_source(src, "curve size too large"); + return NULL; + } + curve = malloc(sizeof(struct curveType) + sizeof(uInt16Number)*count); + if (!curve) + return NULL; + + curve->count = count; + curve->type = type; + + for (i=0; i<count; i++) { + curve->data[i] = read_u16(src, offset + 12 + i*2); + } + *len = 12 + count * 2; + } else { //PARAMETRIC_CURVE_TYPE + count = read_u16(src, offset+8); + + if (count > 4) { + invalid_source(src, "parametric function type not supported."); + return NULL; + } + + curve = malloc(sizeof(struct curveType)); + if (!curve) + return NULL; + + curve->count = count; + curve->type = type; + + for (i=0; i < COUNT_TO_LENGTH[count]; i++) { + curve->parameter[i] = s15Fixed16Number_to_float(read_s15Fixed16Number(src, offset + 12 + i*4)); + } + *len = 12 + COUNT_TO_LENGTH[count] * 4; + + if ((count == 1 || count == 2)) { + /* we have a type 1 or type 2 function that has a division by 'a' */ + float a = curve->parameter[1]; + if (a == 0.f) + invalid_source(src, "parametricCurve definition causes division by zero."); + } + } + + return curve; +} + +static struct curveType *read_tag_curveType(struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + struct tag *tag = find_tag(index, tag_id); + struct curveType *curve = NULL; + if (tag) { + uint32_t len; + return read_curveType(src, tag->offset, &len); + } else { + invalid_source(src, "missing curvetag"); + } + + return curve; +} + +#define MAX_CLUT_SIZE 500000 // arbitrary +#define MAX_CHANNELS 10 // arbitrary +static void read_nested_curveType(struct mem_source *src, struct curveType *(*curveArray)[MAX_CHANNELS], uint8_t num_channels, uint32_t curve_offset) +{ + uint32_t channel_offset = 0; + int i; + for (i = 0; i < num_channels; i++) { + uint32_t tag_len = ~0; + + (*curveArray)[i] = read_curveType(src, curve_offset + channel_offset, &tag_len); + if (!(*curveArray)[i]) { + invalid_source(src, "invalid nested curveType curve"); + } + + if (tag_len == ~0) { + invalid_source(src, "invalid nested curveType tag length"); + return; + } + + channel_offset += tag_len; + // 4 byte aligned + if ((tag_len % 4) != 0) + channel_offset += 4 - (tag_len % 4); + } +} + +static void mAB_release(struct lutmABType *lut) +{ + uint8_t i; + + for (i = 0; i < lut->num_in_channels; i++){ + free(lut->a_curves[i]); + } + for (i = 0; i < lut->num_out_channels; i++){ + free(lut->b_curves[i]); + free(lut->m_curves[i]); + } + free(lut); +} + +/* See section 10.10 for specs */ +static struct lutmABType *read_tag_lutmABType(struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + struct tag *tag = find_tag(index, tag_id); + uint32_t offset = tag->offset; + uint32_t a_curve_offset, b_curve_offset, m_curve_offset; + uint32_t matrix_offset; + uint32_t clut_offset; + uint32_t clut_size = 1; + uint8_t clut_precision; + uint32_t type = read_u32(src, offset); + uint8_t num_in_channels, num_out_channels; + struct lutmABType *lut; + int i; + + if (type != LUT_MAB_TYPE && type != LUT_MBA_TYPE) { + return NULL; + } + + num_in_channels = read_u8(src, offset + 8); + num_out_channels = read_u8(src, offset + 8); + if (num_in_channels > MAX_CHANNELS || num_out_channels > MAX_CHANNELS) + return NULL; + + // We require 3in/out channels since we only support RGB->XYZ (or RGB->LAB) + // XXX: If we remove this restriction make sure that the number of channels + // is less or equal to the maximum number of mAB curves in qcmsint.h + // also check for clut_size overflow. Also make sure it's != 0 + if (num_in_channels != 3 || num_out_channels != 3) + return NULL; + + // some of this data is optional and is denoted by a zero offset + // we also use this to track their existance + a_curve_offset = read_u32(src, offset + 28); + clut_offset = read_u32(src, offset + 24); + m_curve_offset = read_u32(src, offset + 20); + matrix_offset = read_u32(src, offset + 16); + b_curve_offset = read_u32(src, offset + 12); + + // Convert offsets relative to the tag to relative to the profile + // preserve zero for optional fields + if (a_curve_offset) + a_curve_offset += offset; + if (clut_offset) + clut_offset += offset; + if (m_curve_offset) + m_curve_offset += offset; + if (matrix_offset) + matrix_offset += offset; + if (b_curve_offset) + b_curve_offset += offset; + + if (clut_offset) { + assert (num_in_channels == 3); + // clut_size can not overflow since lg(256^num_in_channels) = 24 bits. + for (i = 0; i < num_in_channels; i++) { + clut_size *= read_u8(src, clut_offset + i); + if (clut_size == 0) { + invalid_source(src, "bad clut_size"); + } + } + } else { + clut_size = 0; + } + + // 24bits * 3 won't overflow either + clut_size = clut_size * num_out_channels; + + if (clut_size > MAX_CLUT_SIZE) + return NULL; + + lut = malloc(sizeof(struct lutmABType) + (clut_size) * sizeof(float)); + if (!lut) + return NULL; + // we'll fill in the rest below + memset(lut, 0, sizeof(struct lutmABType)); + lut->clut_table = &lut->clut_table_data[0]; + + if (clut_offset) { + for (i = 0; i < num_in_channels; i++) { + lut->num_grid_points[i] = read_u8(src, clut_offset + i); + if (lut->num_grid_points[i] == 0) { + invalid_source(src, "bad grid_points"); + } + } + } + + // Reverse the processing of transformation elements for mBA type. + lut->reversed = (type == LUT_MBA_TYPE); + + lut->num_in_channels = num_in_channels; + lut->num_out_channels = num_out_channels; + + if (matrix_offset) { + // read the matrix if we have it + lut->e00 = read_s15Fixed16Number(src, matrix_offset+4*0); + lut->e01 = read_s15Fixed16Number(src, matrix_offset+4*1); + lut->e02 = read_s15Fixed16Number(src, matrix_offset+4*2); + lut->e10 = read_s15Fixed16Number(src, matrix_offset+4*3); + lut->e11 = read_s15Fixed16Number(src, matrix_offset+4*4); + lut->e12 = read_s15Fixed16Number(src, matrix_offset+4*5); + lut->e20 = read_s15Fixed16Number(src, matrix_offset+4*6); + lut->e21 = read_s15Fixed16Number(src, matrix_offset+4*7); + lut->e22 = read_s15Fixed16Number(src, matrix_offset+4*8); + lut->e03 = read_s15Fixed16Number(src, matrix_offset+4*9); + lut->e13 = read_s15Fixed16Number(src, matrix_offset+4*10); + lut->e23 = read_s15Fixed16Number(src, matrix_offset+4*11); + } + + if (a_curve_offset) { + read_nested_curveType(src, &lut->a_curves, num_in_channels, a_curve_offset); + } + if (m_curve_offset) { + read_nested_curveType(src, &lut->m_curves, num_out_channels, m_curve_offset); + } + if (b_curve_offset) { + read_nested_curveType(src, &lut->b_curves, num_out_channels, b_curve_offset); + } else { + invalid_source(src, "B curves required"); + } + + if (clut_offset) { + clut_precision = read_u8(src, clut_offset + 16); + if (clut_precision == 1) { + for (i = 0; i < clut_size; i++) { + lut->clut_table[i] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + 20 + i*1)); + } + } else if (clut_precision == 2) { + for (i = 0; i < clut_size; i++) { + lut->clut_table[i] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + 20 + i*2)); + } + } else { + invalid_source(src, "Invalid clut precision"); + } + } + + if (!src->valid) { + mAB_release(lut); + return NULL; + } + + return lut; +} + +static struct lutType *read_tag_lutType(struct mem_source *src, struct tag_index index, uint32_t tag_id) +{ + struct tag *tag = find_tag(index, tag_id); + uint32_t offset = tag->offset; + uint32_t type = read_u32(src, offset); + uint16_t num_input_table_entries; + uint16_t num_output_table_entries; + uint8_t in_chan, grid_points, out_chan; + size_t clut_offset, output_offset; + uint32_t clut_size; + size_t entry_size; + struct lutType *lut; + int i; + + /* I'm not sure why the spec specifies a fixed number of entries for LUT8 tables even though + * they have room for the num_entries fields */ + if (type == LUT8_TYPE) { + num_input_table_entries = 256; + num_output_table_entries = 256; + entry_size = 1; + } else if (type == LUT16_TYPE) { + num_input_table_entries = read_u16(src, offset + 48); + num_output_table_entries = read_u16(src, offset + 50); + if (num_input_table_entries == 0 || num_output_table_entries == 0) { + invalid_source(src, "Bad channel count"); + return NULL; + } + entry_size = 2; + } else { + assert(0); // the caller checks that this doesn't happen + invalid_source(src, "Unexpected lut type"); + return NULL; + } + + in_chan = read_u8(src, offset + 8); + out_chan = read_u8(src, offset + 9); + grid_points = read_u8(src, offset + 10); + + clut_size = pow(grid_points, in_chan); + if (clut_size > MAX_CLUT_SIZE) { + invalid_source(src, "CLUT too large"); + return NULL; + } + + if (clut_size <= 0) { + invalid_source(src, "CLUT must not be empty."); + return NULL; + } + + if (in_chan != 3 || out_chan != 3) { + invalid_source(src, "CLUT only supports RGB"); + return NULL; + } + + lut = malloc(sizeof(struct lutType) + (num_input_table_entries * in_chan + clut_size*out_chan + num_output_table_entries * out_chan)*sizeof(float)); + if (!lut) { + invalid_source(src, "CLUT too large"); + return NULL; + } + + /* compute the offsets of tables */ + lut->input_table = &lut->table_data[0]; + lut->clut_table = &lut->table_data[in_chan*num_input_table_entries]; + lut->output_table = &lut->table_data[in_chan*num_input_table_entries + clut_size*out_chan]; + + lut->num_input_table_entries = num_input_table_entries; + lut->num_output_table_entries = num_output_table_entries; + lut->num_input_channels = in_chan; + lut->num_output_channels = out_chan; + lut->num_clut_grid_points = grid_points; + lut->e00 = read_s15Fixed16Number(src, offset+12); + lut->e01 = read_s15Fixed16Number(src, offset+16); + lut->e02 = read_s15Fixed16Number(src, offset+20); + lut->e10 = read_s15Fixed16Number(src, offset+24); + lut->e11 = read_s15Fixed16Number(src, offset+28); + lut->e12 = read_s15Fixed16Number(src, offset+32); + lut->e20 = read_s15Fixed16Number(src, offset+36); + lut->e21 = read_s15Fixed16Number(src, offset+40); + lut->e22 = read_s15Fixed16Number(src, offset+44); + + for (i = 0; i < lut->num_input_table_entries * in_chan; i++) { + if (type == LUT8_TYPE) { + lut->input_table[i] = uInt8Number_to_float(read_uInt8Number(src, offset + 52 + i * entry_size)); + } else { + lut->input_table[i] = uInt16Number_to_float(read_uInt16Number(src, offset + 52 + i * entry_size)); + } + } + + clut_offset = offset + 52 + lut->num_input_table_entries * in_chan * entry_size; + for (i = 0; i < clut_size * out_chan; i+=3) { + if (type == LUT8_TYPE) { + lut->clut_table[i+0] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + i*entry_size + 0)); + lut->clut_table[i+1] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + i*entry_size + 1)); + lut->clut_table[i+2] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + i*entry_size + 2)); + } else { + lut->clut_table[i+0] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + i*entry_size + 0)); + lut->clut_table[i+1] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + i*entry_size + 2)); + lut->clut_table[i+2] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + i*entry_size + 4)); + } + } + + output_offset = clut_offset + clut_size * out_chan * entry_size; + for (i = 0; i < lut->num_output_table_entries * out_chan; i++) { + if (type == LUT8_TYPE) { + lut->output_table[i] = uInt8Number_to_float(read_uInt8Number(src, output_offset + i*entry_size)); + } else { + lut->output_table[i] = uInt16Number_to_float(read_uInt16Number(src, output_offset + i*entry_size)); + } + } + + return lut; +} + +static void read_rendering_intent(qcms_profile *profile, struct mem_source *src) +{ + profile->rendering_intent = read_u32(src, 64); + switch (profile->rendering_intent) { + case QCMS_INTENT_PERCEPTUAL: + case QCMS_INTENT_SATURATION: + case QCMS_INTENT_RELATIVE_COLORIMETRIC: + case QCMS_INTENT_ABSOLUTE_COLORIMETRIC: + break; + default: + invalid_source(src, "unknown rendering intent"); + } +} + +qcms_profile *qcms_profile_create(void) +{ + return calloc(sizeof(qcms_profile), 1); +} + + + +/* build sRGB gamma table */ +/* based on cmsBuildParametricGamma() */ +static uint16_t *build_sRGB_gamma_table(int num_entries) +{ + int i; + /* taken from lcms: Build_sRGBGamma() */ + double gamma = 2.4; + double a = 1./1.055; + double b = 0.055/1.055; + double c = 1./12.92; + double d = 0.04045; + + uint16_t *table = malloc(sizeof(uint16_t) * num_entries); + if (!table) + return NULL; + + for (i=0; i<num_entries; i++) { + double x = (double)i / (num_entries-1); + double y, output; + // IEC 61966-2.1 (sRGB) + // Y = (aX + b)^Gamma | X >= d + // Y = cX | X < d + if (x >= d) { + double e = (a*x + b); + if (e > 0) + y = pow(e, gamma); + else + y = 0; + } else { + y = c*x; + } + + // Saturate -- this could likely move to a separate function + output = y * 65535. + .5; + if (output > 65535.) + output = 65535; + if (output < 0) + output = 0; + table[i] = (uint16_t)floor(output); + } + return table; +} + +static struct curveType *curve_from_table(uint16_t *table, int num_entries) +{ + struct curveType *curve; + int i; + curve = malloc(sizeof(struct curveType) + sizeof(uInt16Number)*num_entries); + if (!curve) + return NULL; + curve->type = CURVE_TYPE; + curve->count = num_entries; + for (i = 0; i < num_entries; i++) { + curve->data[i] = table[i]; + } + return curve; +} + +static uint16_t float_to_u8Fixed8Number(float a) +{ + if (a > (255.f + 255.f/256)) + return 0xffff; + else if (a < 0.f) + return 0; + else + return floor(a*256.f + .5f); +} + +static struct curveType *curve_from_gamma(float gamma) +{ + struct curveType *curve; + int num_entries = 1; + curve = malloc(sizeof(struct curveType) + sizeof(uInt16Number)*num_entries); + if (!curve) + return NULL; + curve->count = num_entries; + curve->data[0] = float_to_u8Fixed8Number(gamma); + return curve; +} + + +//XXX: it would be nice if we had a way of ensuring +// everything in a profile was initialized regardless of how it was created + +//XXX: should this also be taking a black_point? +/* similar to CGColorSpaceCreateCalibratedRGB */ +qcms_profile* qcms_profile_create_rgb_with_gamma( + qcms_CIE_xyY white_point, + qcms_CIE_xyYTRIPLE primaries, + float gamma) +{ + qcms_profile* profile = qcms_profile_create(); + if (!profile) + return NO_MEM_PROFILE; + + if (!set_rgb_colorants(profile, white_point, primaries)) { + qcms_profile_release(profile); + return INVALID_PROFILE; + } + + profile->redTRC = curve_from_gamma(gamma); + profile->blueTRC = curve_from_gamma(gamma); + profile->greenTRC = curve_from_gamma(gamma); + + if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) { + qcms_profile_release(profile); + return NO_MEM_PROFILE; + } + + profile->class = DISPLAY_DEVICE_PROFILE; + profile->rendering_intent = QCMS_INTENT_PERCEPTUAL; + profile->color_space = RGB_SIGNATURE; + profile->pcs = XYZ_SIGNATURE; + return profile; +} + +qcms_profile* qcms_profile_create_rgb_with_table( + qcms_CIE_xyY white_point, + qcms_CIE_xyYTRIPLE primaries, + uint16_t *table, int num_entries) +{ + qcms_profile* profile = qcms_profile_create(); + if (!profile) + return NO_MEM_PROFILE; + + if (!set_rgb_colorants(profile, white_point, primaries)) { + qcms_profile_release(profile); + return INVALID_PROFILE; + } + + profile->redTRC = curve_from_table(table, num_entries); + profile->blueTRC = curve_from_table(table, num_entries); + profile->greenTRC = curve_from_table(table, num_entries); + + if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) { + qcms_profile_release(profile); + return NO_MEM_PROFILE; + } + + profile->class = DISPLAY_DEVICE_PROFILE; + profile->rendering_intent = QCMS_INTENT_PERCEPTUAL; + profile->color_space = RGB_SIGNATURE; + profile->pcs = XYZ_SIGNATURE; + return profile; +} + +/* from lcms: cmsWhitePointFromTemp */ +/* tempK must be >= 4000. and <= 25000. + * Invalid values of tempK will return + * (x,y,Y) = (-1.0, -1.0, -1.0) + * similar to argyll: icx_DTEMP2XYZ() */ +qcms_CIE_xyY white_point_from_temp(int temp_K) +{ + qcms_CIE_xyY white_point; + double x, y; + double T, T2, T3; + // double M1, M2; + + // No optimization provided. + T = temp_K; + T2 = T*T; // Square + T3 = T2*T; // Cube + + // For correlated color temperature (T) between 4000K and 7000K: + if (T >= 4000. && T <= 7000.) { + x = -4.6070*(1E9/T3) + 2.9678*(1E6/T2) + 0.09911*(1E3/T) + 0.244063; + } else { + // or for correlated color temperature (T) between 7000K and 25000K: + if (T > 7000.0 && T <= 25000.0) { + x = -2.0064*(1E9/T3) + 1.9018*(1E6/T2) + 0.24748*(1E3/T) + 0.237040; + } else { + // Invalid tempK + white_point.x = -1.0; + white_point.y = -1.0; + white_point.Y = -1.0; + + assert(0 && "invalid temp"); + + return white_point; + } + } + + // Obtain y(x) + + y = -3.000*(x*x) + 2.870*x - 0.275; + + // wave factors (not used, but here for futures extensions) + + // M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y); + // M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y); + + // Fill white_point struct + white_point.x = x; + white_point.y = y; + white_point.Y = 1.0; + + return white_point; +} + +qcms_profile* qcms_profile_sRGB(void) +{ + qcms_profile *profile; + uint16_t *table; + + // Standard Illuminant D65 in XYZ coordinates, which is the standard + // sRGB IEC61966-2.1 / Rec.709 profile reference media white point. + struct XYZNumber D65 = { + 0xf351, 0x10000, 0x116cc // ( 0.950455, 1.000000, 1.089050 ) + }; + + // sRGB IEC61966-2.1 / Rec.709 color profile primaries, chromatically + // adapted (via Bradford procedures) to D50 white point. + // For details, refer to crbug/580917 +#if 0 + // lindbloom: ASTM E308-01 D50 White point. + s15Fixed16Number primaries[3][3] = { + { 0x06fa3, 0x06294, 0x024a1 }, // ( 0.436081, 0.385071, 0.143082 ) + { 0x038f6, 0x0b785, 0x00f85 }, // ( 0.222504, 0.716873, 0.060623 ) + { 0x00391, 0x018dc, 0x0b6d4 }, // ( 0.013931, 0.097107, 0.714172 ) + }; +#else + // ninedegreesbelow: ICC D50 White point. + s15Fixed16Number primaries[3][3] = { + { 0x06fa0, 0x06296, 0x024a0 }, // ( 0.436035, 0.385101, 0.143066 ) + { 0x038f2, 0x0b789, 0x00f85 }, // ( 0.222443, 0.716934, 0.060623 ) + { 0x0038f, 0x018da, 0x0b6c4 }, // ( 0.013901, 0.097076, 0.713928 ) + }; +#endif + + table = build_sRGB_gamma_table(1024); + + if (!table) + return NO_MEM_PROFILE; + + profile = qcms_profile_create(); + + if (!profile) { + free(table); + return NO_MEM_PROFILE; + } + + profile->redTRC = curve_from_table(table, 1024); + profile->blueTRC = curve_from_table(table, 1024); + profile->greenTRC = curve_from_table(table, 1024); + + if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) { + qcms_profile_release(profile); + free(table); + return NO_MEM_PROFILE; + } + + profile->redColorant.X = primaries[0][0]; + profile->redColorant.Y = primaries[1][0]; + profile->redColorant.Z = primaries[2][0]; + + profile->greenColorant.X = primaries[0][1]; + profile->greenColorant.Y = primaries[1][1]; + profile->greenColorant.Z = primaries[2][1]; + + profile->blueColorant.X = primaries[0][2]; + profile->blueColorant.Y = primaries[1][2]; + profile->blueColorant.Z = primaries[2][2]; + + profile->mediaWhitePoint.X = D65.X; + profile->mediaWhitePoint.Y = D65.Y; + profile->mediaWhitePoint.Z = D65.Z; + + profile->class = DISPLAY_DEVICE_PROFILE; + profile->rendering_intent = QCMS_INTENT_PERCEPTUAL; + profile->color_space = RGB_SIGNATURE; + profile->pcs = XYZ_SIGNATURE; + + strcpy(profile->description, "sRGB IEC61966-2.1"); + + free(table); + + return profile; +} + +/* qcms_profile_from_memory does not hold a reference to the memory passed in */ +qcms_profile* qcms_profile_from_memory(const void *mem, size_t size) +{ + uint32_t length; + struct mem_source source; + struct mem_source *src = &source; + struct tag_index index; + qcms_profile *profile; + + source.buf = mem; + source.size = size; + source.valid = true; + + if (size < 4) + return INVALID_PROFILE; + + length = read_u32(src, 0); + if (length <= size) { + // shrink the area that we can read if appropriate + source.size = length; + } else { + return INVALID_PROFILE; + } + + /* ensure that the profile size is sane so it's easier to reason about */ + if (source.size <= 64 || source.size >= MAX_PROFILE_SIZE) + return INVALID_PROFILE; + + profile = qcms_profile_create(); + if (!profile) + return NO_MEM_PROFILE; + + check_CMM_type_signature(src); + read_profile_version(profile, src); + read_class_signature(profile, src); + read_rendering_intent(profile, src); + read_color_space(profile, src); + read_pcs(profile, src); + //TODO read rest of profile stuff + + if (!src->valid) + goto invalid_profile; + + index = read_tag_table(profile, src); + if (!src->valid || !index.tags) + goto invalid_tag_table; + + if (!read_tag_descType(profile, src, index, TAG_desc)) + goto invalid_tag_table; +#if defined(__APPLE__) + if (!read_tag_dscmType(profile, src, index, TAG_dscm)) + goto invalid_tag_table; + if (!read_tag_mmodType(profile, src, index, TAG_mmod)) + goto invalid_tag_table; +#endif // __APPLE__ + + if (find_tag(index, TAG_CHAD)) { + profile->chromaticAdaption = read_tag_s15Fixed16ArrayType(src, index, TAG_CHAD); + } else { + profile->chromaticAdaption.invalid = true; //Signal the data is not present + } + + if (find_tag(index, TAG_vcgt)) { + if (!read_tag_vcgtType(profile, src, index)) + goto invalid_tag_table; + } + + if (profile->class == DISPLAY_DEVICE_PROFILE || profile->class == INPUT_DEVICE_PROFILE || + profile->class == OUTPUT_DEVICE_PROFILE || profile->class == COLOR_SPACE_PROFILE) { + if (profile->color_space == RGB_SIGNATURE) { + if (find_tag(index, TAG_A2B0)) { + if (read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT8_TYPE || + read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT16_TYPE) { + profile->A2B0 = read_tag_lutType(src, index, TAG_A2B0); + } else if (read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT_MAB_TYPE) { + profile->mAB = read_tag_lutmABType(src, index, TAG_A2B0); + } + } + if (find_tag(index, TAG_B2A0)) { + if (read_u32(src, find_tag(index, TAG_B2A0)->offset) == LUT8_TYPE || + read_u32(src, find_tag(index, TAG_B2A0)->offset) == LUT16_TYPE) { + profile->B2A0 = read_tag_lutType(src, index, TAG_B2A0); + } else if (read_u32(src, find_tag(index, TAG_B2A0)->offset) == LUT_MBA_TYPE) { + profile->mBA = read_tag_lutmABType(src, index, TAG_B2A0); + } + } + if (find_tag(index, TAG_rXYZ) || !qcms_supports_iccv4) { + profile->redColorant = read_tag_XYZType(src, index, TAG_rXYZ); + profile->greenColorant = read_tag_XYZType(src, index, TAG_gXYZ); + profile->blueColorant = read_tag_XYZType(src, index, TAG_bXYZ); + } + + if (!src->valid) + goto invalid_tag_table; + + if (find_tag(index, TAG_rTRC) || !qcms_supports_iccv4) { + profile->redTRC = read_tag_curveType(src, index, TAG_rTRC); + profile->greenTRC = read_tag_curveType(src, index, TAG_gTRC); + profile->blueTRC = read_tag_curveType(src, index, TAG_bTRC); + + if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) + goto invalid_tag_table; + } + } else if (profile->color_space == GRAY_SIGNATURE) { + + profile->grayTRC = read_tag_curveType(src, index, TAG_kTRC); + if (!profile->grayTRC) + goto invalid_tag_table; + + } else { + assert(0 && "read_color_space protects against entering here"); + goto invalid_tag_table; + } + } else { + goto invalid_tag_table; + } + + // Profiles other than DeviceLink should have a media white point. + // Here we read it if present. + if (find_tag(index, TAG_wtpt)) { + profile->mediaWhitePoint = read_tag_XYZType(src, index, TAG_wtpt); + } + + if (!src->valid) + goto invalid_tag_table; + + free(index.tags); + return profile; + +invalid_tag_table: + if (index.tags) + free(index.tags); +invalid_profile: + qcms_profile_release(profile); + return INVALID_PROFILE; +} + +qcms_bool qcms_profile_match(qcms_profile *p1, qcms_profile *p2) +{ + return memcmp(p1->description, p2->description, sizeof p1->description) == 0; +} + +const char* qcms_profile_get_description(qcms_profile *profile) +{ + return profile->description; +} + +qcms_intent qcms_profile_get_rendering_intent(qcms_profile *profile) +{ + return profile->rendering_intent; +} + +qcms_color_space qcms_profile_get_color_space(qcms_profile *profile) +{ + return profile->color_space; +} + +unsigned qcms_profile_get_version(qcms_profile *profile) +{ + return profile->icc_version & 0xffff; +} + +size_t qcms_profile_get_vcgt_channel_length(qcms_profile *profile) +{ + return profile->vcgt.length; +} + +// Check unsigned short is uint16_t. +typedef char assert_short_not_16b[(sizeof(unsigned short) == sizeof(uint16_t)) ? 1 : -1]; + +qcms_bool qcms_profile_get_vcgt_rgb_channels(qcms_profile *profile, unsigned short *data) +{ + size_t vcgt_channel_bytes = qcms_profile_get_vcgt_channel_length(profile) * sizeof(uint16_t); + + if (!vcgt_channel_bytes || !data) + return false; + + memcpy(data, profile->vcgt.data, 3 * vcgt_channel_bytes); + return true; +} + +static void lut_release(struct lutType *lut) +{ + free(lut); +} + +void qcms_profile_release(qcms_profile *profile) +{ + if (profile->output_table_r) + precache_release(profile->output_table_r); + if (profile->output_table_g) + precache_release(profile->output_table_g); + if (profile->output_table_b) + precache_release(profile->output_table_b); + + if (profile->A2B0) + lut_release(profile->A2B0); + if (profile->B2A0) + lut_release(profile->B2A0); + + if (profile->mAB) + mAB_release(profile->mAB); + if (profile->mBA) + mAB_release(profile->mBA); + + if (profile->vcgt.data) + free(profile->vcgt.data); + + free(profile->redTRC); + free(profile->blueTRC); + free(profile->greenTRC); + free(profile->grayTRC); + free(profile); +} + +#include <stdio.h> + +qcms_profile* qcms_profile_from_file(FILE *file) +{ + uint32_t length, remaining_length; + qcms_profile *profile; + size_t read_length; + be32 length_be; + void *data; + + if (fread(&length_be, 1, sizeof(length_be), file) != sizeof(length_be)) + return BAD_VALUE_PROFILE; + + length = be32_to_cpu(length_be); + if (length > MAX_PROFILE_SIZE || length < sizeof(length_be)) + return BAD_VALUE_PROFILE; + + /* allocate room for the entire profile */ + data = malloc(length); + if (!data) + return NO_MEM_PROFILE; + + /* copy in length to the front so that the buffer will contain the entire profile */ + *((be32*)data) = length_be; + remaining_length = length - sizeof(length_be); + + /* read the rest profile */ + read_length = fread((unsigned char*)data + sizeof(length_be), 1, remaining_length, file); + if (read_length != remaining_length) { + free(data); + return INVALID_PROFILE; + } + + profile = qcms_profile_from_memory(data, length); + free(data); + return profile; +} + +qcms_profile* qcms_profile_from_path(const char *path) +{ + qcms_profile *profile = NULL; + FILE *file = fopen(path, "rb"); + if (file) { + profile = qcms_profile_from_file(file); + fclose(file); + } + return profile; +} + +#ifdef _WIN32 +/* Unicode path version */ +qcms_profile* qcms_profile_from_unicode_path(const wchar_t *path) +{ + qcms_profile *profile = NULL; + FILE *file = _wfopen(path, L"rb"); + if (file) { + profile = qcms_profile_from_file(file); + fclose(file); + } + return profile; +} +#endif diff --git a/third_party/qcms/src/matrix.c b/third_party/qcms/src/matrix.c new file mode 100644 index 0000000000..0ce5bd66d1 --- /dev/null +++ b/third_party/qcms/src/matrix.c @@ -0,0 +1,136 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// Copyright (C) 1998-2007 Marti Maria +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include <stdlib.h> +#include "qcmsint.h" +#include "matrix.h" + +struct vector matrix_eval(struct matrix mat, struct vector v) +{ + struct vector result; + result.v[0] = mat.m[0][0]*v.v[0] + mat.m[0][1]*v.v[1] + mat.m[0][2]*v.v[2]; + result.v[1] = mat.m[1][0]*v.v[0] + mat.m[1][1]*v.v[1] + mat.m[1][2]*v.v[2]; + result.v[2] = mat.m[2][0]*v.v[0] + mat.m[2][1]*v.v[1] + mat.m[2][2]*v.v[2]; + return result; +} + +//XXX: should probably pass by reference and we could +//probably reuse this computation in matrix_invert +float matrix_det(struct matrix mat) +{ + float det; + det = mat.m[0][0]*mat.m[1][1]*mat.m[2][2] + + mat.m[0][1]*mat.m[1][2]*mat.m[2][0] + + mat.m[0][2]*mat.m[1][0]*mat.m[2][1] - + mat.m[0][0]*mat.m[1][2]*mat.m[2][1] - + mat.m[0][1]*mat.m[1][0]*mat.m[2][2] - + mat.m[0][2]*mat.m[1][1]*mat.m[2][0]; + return det; +} + +/* from pixman and cairo and Mathematics for Game Programmers */ +/* lcms uses gauss-jordan elimination with partial pivoting which is + * less efficient and not as numerically stable. See Mathematics for + * Game Programmers. */ +struct matrix matrix_invert(struct matrix mat) +{ + struct matrix dest_mat; + int i,j; + static int a[3] = { 2, 2, 1 }; + static int b[3] = { 1, 0, 0 }; + + /* inv (A) = 1/det (A) * adj (A) */ + float det = matrix_det(mat); + + if (det == 0) { + dest_mat.invalid = true; + } else { + dest_mat.invalid = false; + } + + det = 1/det; + + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) { + double p; + int ai = a[i]; + int aj = a[j]; + int bi = b[i]; + int bj = b[j]; + + p = mat.m[ai][aj] * mat.m[bi][bj] - + mat.m[ai][bj] * mat.m[bi][aj]; + if (((i + j) & 1) != 0) + p = -p; + + dest_mat.m[j][i] = det * p; + } + } + return dest_mat; +} + +struct matrix matrix_identity(void) +{ + struct matrix i; + i.m[0][0] = 1; + i.m[0][1] = 0; + i.m[0][2] = 0; + i.m[1][0] = 0; + i.m[1][1] = 1; + i.m[1][2] = 0; + i.m[2][0] = 0; + i.m[2][1] = 0; + i.m[2][2] = 1; + i.invalid = false; + return i; +} + +struct matrix matrix_invalid(void) +{ + struct matrix inv = matrix_identity(); + inv.invalid = true; + return inv; +} + + +/* from pixman */ +/* MAT3per... */ +struct matrix matrix_multiply(struct matrix a, struct matrix b) +{ + struct matrix result; + int dx, dy; + int o; + for (dy = 0; dy < 3; dy++) { + for (dx = 0; dx < 3; dx++) { + double v = 0; + for (o = 0; o < 3; o++) { + v += a.m[dy][o] * b.m[o][dx]; + } + result.m[dy][dx] = v; + } + } + result.invalid = a.invalid || b.invalid; + return result; +} + + diff --git a/third_party/qcms/src/matrix.h b/third_party/qcms/src/matrix.h new file mode 100644 index 0000000000..5011988a16 --- /dev/null +++ b/third_party/qcms/src/matrix.h @@ -0,0 +1,39 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// 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. + +#ifndef _QCMS_MATRIX_H +#define _QCMS_MATRIX_H + +struct vector { + float v[3]; +}; + +struct vector matrix_eval(struct matrix mat, struct vector v); +float matrix_det(struct matrix mat); +struct matrix matrix_identity(void); +struct matrix matrix_multiply(struct matrix a, struct matrix b); +struct matrix matrix_invert(struct matrix mat); + +struct matrix matrix_invalid(void); + +#endif diff --git a/third_party/qcms/src/qcms.h b/third_party/qcms/src/qcms.h new file mode 100644 index 0000000000..9efe21fa7c --- /dev/null +++ b/third_party/qcms/src/qcms.h @@ -0,0 +1,171 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// +// 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. + +#ifndef QCMS_H +#define QCMS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> + +struct _qcms_profile; +typedef struct _qcms_profile qcms_profile; + +struct _qcms_transform; +typedef struct _qcms_transform qcms_transform; + +typedef int qcms_bool; + +/* ICC Section 6.1.5 Color Space Signatures (abridged) */ +typedef enum { + XYZData /* ‘XYZ ’ */ = 0x58595A20, + labData /* ‘Lab ’ */ = 0x4C616220, + luvData /* ‘Luv ’ */ = 0x4C757620, + YCbCrData /* ‘YCbr' */ = 0x59436272, + YxyData /* ‘Yxy ’ */ = 0x59787920, + rgbData /* ‘RGB ’ */ = 0x52474220, + grayData /* ‘GRAY’ */ = 0x47524159, + hsvData /* ‘HSV ’ */ = 0x48535620, + hlsData /* ‘HLS ’ */ = 0x484C5320, + cmykData /* ‘CMYK’ */ = 0x434D594B, + cmyData /* ‘CMY ’ */ = 0x434D5920, +} qcms_color_space; + +/* ICC Section 6.1.11 Rendering Intents */ +typedef enum { + QCMS_INTENT_DEFAULT = 0, + QCMS_INTENT_PERCEPTUAL = 0, + QCMS_INTENT_RELATIVE_COLORIMETRIC = 1, + QCMS_INTENT_SATURATION = 2, + QCMS_INTENT_ABSOLUTE_COLORIMETRIC = 3 +} qcms_intent; + +/* Input data formats */ +typedef enum { + QCMS_DATA_RGB_8, + QCMS_DATA_RGBA_8, + QCMS_DATA_GRAY_8, + QCMS_DATA_GRAYA_8 +} qcms_data_type; + +/* Output data format for qcms_transform_data_type() */ +typedef enum { + QCMS_OUTPUT_RGBX, + QCMS_OUTPUT_BGRX +} qcms_output_type; + +/* Output data format for qcms_transform_get_input|output_trc_rgba() */ +typedef enum { + QCMS_TRC_PARAMETRIC, // Not implemented. + QCMS_TRC_FLOAT, // Not implemented. + QCMS_TRC_HALF_FLOAT, // IEE754: binary16. + QCMS_TRC_USHORT, // 0.16 fixed point. +} qcms_trc_type; + +typedef struct { + double x; + double y; + double Y; +} qcms_CIE_xyY; + +typedef struct { + qcms_CIE_xyY red; + qcms_CIE_xyY green; + qcms_CIE_xyY blue; +} qcms_CIE_xyYTRIPLE; + +typedef struct { + float X; + float Y; + float Z; +} qcms_xyz_float; + +qcms_profile* qcms_profile_create_rgb_with_gamma( + qcms_CIE_xyY white_point, + qcms_CIE_xyYTRIPLE primaries, + float gamma); + +qcms_profile* qcms_profile_from_memory(const void *mem, size_t size); + +qcms_profile* qcms_profile_from_file(FILE *file); +qcms_profile* qcms_profile_from_path(const char *path); +#ifdef _WIN32 +qcms_profile* qcms_profile_from_unicode_path(const wchar_t *path); +#endif +qcms_profile* qcms_profile_sRGB(void); +void qcms_profile_release(qcms_profile *profile); + +qcms_bool qcms_profile_is_bogus(qcms_profile *profile); +qcms_bool qcms_profile_has_white_point(qcms_profile *profile); +qcms_xyz_float qcms_profile_get_white_point(qcms_profile *profile); +qcms_intent qcms_profile_get_rendering_intent(qcms_profile *profile); +qcms_color_space qcms_profile_get_color_space(qcms_profile *profile); +unsigned qcms_profile_get_version(qcms_profile *profile); +qcms_bool qcms_profile_white_transform(qcms_profile *profile, float XYZ[3]); + +qcms_bool qcms_profile_match(qcms_profile *p1, qcms_profile *p2); +const char* qcms_profile_get_description(qcms_profile *profile); + +void qcms_profile_precache_output_transform(qcms_profile *profile); + +size_t qcms_profile_get_vcgt_channel_length(qcms_profile *profile); +qcms_bool qcms_profile_get_vcgt_rgb_channels(qcms_profile *profile, unsigned short *data); + +float qcms_profile_ntsc_relative_gamut_size(qcms_profile *profile); + +qcms_transform* qcms_transform_create( + qcms_profile *in, qcms_data_type in_type, + qcms_profile *out, qcms_data_type out_type, + qcms_intent intent); + +size_t qcms_transform_get_input_trc_rgba( + qcms_transform *transform, qcms_profile *in, qcms_trc_type type, unsigned short *data); +size_t qcms_transform_get_output_trc_rgba( + qcms_transform *transform, qcms_profile *out, qcms_trc_type type, unsigned short *data); + +qcms_bool qcms_transform_is_matrix(qcms_transform *transform); +float qcms_transform_get_matrix(qcms_transform *transform, unsigned i, unsigned j); + +qcms_bool qcms_transform_create_LUT_zyx_bgra( + qcms_profile *in, qcms_profile *out, qcms_intent intent, + int samples, unsigned char* lut); + +void qcms_transform_data(qcms_transform *transform, void *src, void *dest, size_t length); +void qcms_transform_data_type(qcms_transform *transform, void *src, void *dest, size_t length, qcms_output_type type); + +void qcms_transform_release(qcms_transform *); + +void qcms_enable_iccv4(); + +#ifdef __cplusplus +} +#endif + +/* + * In general, QCMS is not threadsafe. However, it should be safe to create + * profile and transformation objects on different threads, so long as you + * don't use the same objects on different threads at the same time. + */ + +#endif diff --git a/third_party/qcms/src/qcms_util.c b/third_party/qcms/src/qcms_util.c new file mode 100644 index 0000000000..9b2d9fa744 --- /dev/null +++ b/third_party/qcms/src/qcms_util.c @@ -0,0 +1,69 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcmsint.h" + +#include <math.h> + +typedef struct _qcms_coords { + float x; + float y; +} qcms_coords; + +typedef struct _qcms_triangle { + qcms_coords verticies[3]; +} qcms_triangle; + +#define NTSC_1953_GAMUT_SIZE 0.1582 + +static qcms_triangle get_profile_triangle(qcms_profile *profile) +{ + float sumRed = s15Fixed16Number_to_float(profile->redColorant.X) + + s15Fixed16Number_to_float(profile->redColorant.Y) + + s15Fixed16Number_to_float(profile->redColorant.Z); + float xRed = s15Fixed16Number_to_float(profile->redColorant.X) / sumRed; + float yRed = s15Fixed16Number_to_float(profile->redColorant.Y) / sumRed; + + float sumGreen = s15Fixed16Number_to_float(profile->greenColorant.X) + + s15Fixed16Number_to_float(profile->greenColorant.Y) + + s15Fixed16Number_to_float(profile->greenColorant.Z); + float xGreen = s15Fixed16Number_to_float(profile->greenColorant.X) / sumGreen; + float yGreen = s15Fixed16Number_to_float(profile->greenColorant.Y) / sumGreen; + + float sumBlue = s15Fixed16Number_to_float(profile->blueColorant.X) + + s15Fixed16Number_to_float(profile->blueColorant.Y) + + s15Fixed16Number_to_float(profile->blueColorant.Z); + float xBlue = s15Fixed16Number_to_float(profile->blueColorant.X) / sumBlue; + float yBlue = s15Fixed16Number_to_float(profile->blueColorant.Y) / sumBlue; + + qcms_triangle triangle = {{{xRed, yRed}, {xGreen, yGreen}, {xBlue, yBlue}}}; + return triangle; +} + +static float get_triangle_area(const qcms_triangle candidate) +{ + float xRed = candidate.verticies[0].x; + float yRed = candidate.verticies[0].y; + float xGreen = candidate.verticies[1].x; + float yGreen = candidate.verticies[1].y; + float xBlue = candidate.verticies[2].x; + float yBlue = candidate.verticies[2].y; + + float area = fabs((xRed - xBlue) * (yGreen - yBlue) - (xGreen - xBlue) * (yRed - yBlue)) / 2; + return area; +} + +static float get_ntsc_gamut_metric_area(const qcms_triangle candidate) +{ + float area = get_triangle_area(candidate); + return area * 100 / NTSC_1953_GAMUT_SIZE; +} + +float qcms_profile_ntsc_relative_gamut_size(qcms_profile *profile) +{ + qcms_triangle triangle = get_profile_triangle(profile); + return get_ntsc_gamut_metric_area(triangle); +} + + diff --git a/third_party/qcms/src/qcmsint.h b/third_party/qcms/src/qcmsint.h new file mode 100644 index 0000000000..005b089758 --- /dev/null +++ b/third_party/qcms/src/qcmsint.h @@ -0,0 +1,342 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "qcms.h" +#include "qcmstypes.h" + +/* used as a lookup table for the output transformation. + * we refcount them so we only need to have one around per output + * profile, instead of duplicating them per transform */ +struct precache_output +{ + int ref_count; + /* We previously used a count of 65536 here but that seems like more + * precision than we actually need. By reducing the size we can + * improve startup performance and reduce memory usage. ColorSync on + * 10.5 uses 4097 which is perhaps because they use a fixed point + * representation where 1. is represented by 0x1000. */ +#define PRECACHE_OUTPUT_SIZE 8192 +#define PRECACHE_OUTPUT_MAX (PRECACHE_OUTPUT_SIZE-1) + uint8_t data[PRECACHE_OUTPUT_SIZE]; +}; + +#ifdef _MSC_VER +#define ALIGN __declspec(align(16)) +#else +#define ALIGN __attribute__(( aligned (16) )) +#endif + +typedef struct _qcms_format_type { + int r; + int b; +} qcms_format_type; + +struct _qcms_transform { + float ALIGN matrix[3][4]; + float *input_gamma_table_r; + float *input_gamma_table_g; + float *input_gamma_table_b; + + float *input_clut_table_r; + float *input_clut_table_g; + float *input_clut_table_b; + uint16_t input_clut_table_length; + float *r_clut; + float *g_clut; + float *b_clut; + uint16_t grid_size; + float *output_clut_table_r; + float *output_clut_table_g; + float *output_clut_table_b; + uint16_t output_clut_table_length; + + float *input_gamma_table_gray; + + float out_gamma_r; + float out_gamma_g; + float out_gamma_b; + + float out_gamma_gray; + + uint16_t *output_gamma_lut_r; + uint16_t *output_gamma_lut_g; + uint16_t *output_gamma_lut_b; + + uint16_t *output_gamma_lut_gray; + + size_t output_gamma_lut_r_length; + size_t output_gamma_lut_g_length; + size_t output_gamma_lut_b_length; + + size_t output_gamma_lut_gray_length; + + struct precache_output *output_table_r; + struct precache_output *output_table_g; + struct precache_output *output_table_b; + + void (*transform_fn)(struct _qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, struct _qcms_format_type output_format); + + unsigned char floor_cache[256]; + unsigned char ceil_cache[256]; + float r_cache[256]; + +#define TRANSFORM_FLAG_MATRIX 0x0001 +#define TRANSFORM_FLAG_CLUT_CACHE 0x0002 + + uint16_t transform_flags; +}; + +struct matrix { + float m[3][3]; + bool invalid; +}; + +struct qcms_modular_transform; + +typedef void (*transform_module_fn_t)(struct qcms_modular_transform *transform, float *src, float *dest, size_t length); + +struct qcms_modular_transform { + struct matrix matrix; + float tx, ty, tz; + + float *input_clut_table_r; + float *input_clut_table_g; + float *input_clut_table_b; + uint16_t input_clut_table_length; + float *r_clut; + float *g_clut; + float *b_clut; + uint16_t grid_size; + float *output_clut_table_r; + float *output_clut_table_g; + float *output_clut_table_b; + uint16_t output_clut_table_length; + + uint16_t *output_gamma_lut_r; + uint16_t *output_gamma_lut_g; + uint16_t *output_gamma_lut_b; + + size_t output_gamma_lut_r_length; + size_t output_gamma_lut_g_length; + size_t output_gamma_lut_b_length; + + transform_module_fn_t transform_module_fn; + struct qcms_modular_transform *next_transform; +}; + +typedef int32_t s15Fixed16Number; +typedef uint16_t uInt16Number; +typedef uint8_t uInt8Number; + +struct XYZNumber { + s15Fixed16Number X; + s15Fixed16Number Y; + s15Fixed16Number Z; +}; + +struct curveType { + uint32_t type; + uint32_t count; + float parameter[7]; + uInt16Number data[]; +}; + +struct lutmABType { + uint8_t num_in_channels; + uint8_t num_out_channels; + // 16 is the upperbound, actual is 0..num_in_channels. + uint8_t num_grid_points[16]; + + s15Fixed16Number e00; + s15Fixed16Number e01; + s15Fixed16Number e02; + s15Fixed16Number e03; + s15Fixed16Number e10; + s15Fixed16Number e11; + s15Fixed16Number e12; + s15Fixed16Number e13; + s15Fixed16Number e20; + s15Fixed16Number e21; + s15Fixed16Number e22; + s15Fixed16Number e23; + + // reversed elements (for mBA) + bool reversed; + + float *clut_table; + struct curveType *a_curves[10]; + struct curveType *b_curves[10]; + struct curveType *m_curves[10]; + float clut_table_data[]; +}; + +/* should lut8Type and lut16Type be different types? */ +struct lutType { // used by lut8Type/lut16Type (mft2) only + uint8_t num_input_channels; + uint8_t num_output_channels; + uint8_t num_clut_grid_points; + + s15Fixed16Number e00; + s15Fixed16Number e01; + s15Fixed16Number e02; + s15Fixed16Number e10; + s15Fixed16Number e11; + s15Fixed16Number e12; + s15Fixed16Number e20; + s15Fixed16Number e21; + s15Fixed16Number e22; + + uint16_t num_input_table_entries; + uint16_t num_output_table_entries; + + float *input_table; + float *clut_table; + float *output_table; + + float table_data[]; +}; + +struct vcgtType { + /* data contains three gamma channels: R[length], then G[length], then + * B[length]. */ + uint16_t *data; + size_t length; +}; + +#if 0 +/* this is from an intial idea of having the struct correspond to the data in + * the file. I decided that it wasn't a good idea. + */ +struct tag_value { + uint32_t type; + union { + struct { + uint32_t reserved; + struct { + s15Fixed16Number X; + s15Fixed16Number Y; + s15Fixed16Number Z; + } XYZNumber; + } XYZType; + }; +}; // I guess we need to pack this? +#endif + +#define RGB_SIGNATURE 0x52474220 +#define GRAY_SIGNATURE 0x47524159 +#define XYZ_SIGNATURE 0x58595A20 +#define LAB_SIGNATURE 0x4C616220 + +struct _qcms_profile { + uint32_t icc_version; + char description[64]; + uint32_t class; + uint32_t color_space; + uint32_t pcs; + qcms_intent rendering_intent; + struct XYZNumber mediaWhitePoint; + struct XYZNumber redColorant; + struct XYZNumber blueColorant; + struct XYZNumber greenColorant; + struct curveType *redTRC; + struct curveType *blueTRC; + struct curveType *greenTRC; + struct curveType *grayTRC; + struct lutType *A2B0; + struct lutType *B2A0; + struct lutmABType *mAB; + struct lutmABType *mBA; + struct matrix chromaticAdaption; + struct vcgtType vcgt; + + struct precache_output *output_table_r; + struct precache_output *output_table_g; + struct precache_output *output_table_b; +}; + +#ifdef _MSC_VER +#define inline _inline +#endif + +/* produces the nearest float to 'a' with a maximum error + * of 1/1024 which happens for large values like 0x40000040 */ +static inline float s15Fixed16Number_to_float(s15Fixed16Number a) +{ + return ((int32_t)a)/65536.f; +} + +static inline s15Fixed16Number double_to_s15Fixed16Number(double v) +{ + return (int32_t)(v*65536); +} + +static inline float uInt8Number_to_float(uInt8Number a) +{ + return ((int32_t)a)/255.f; +} + +static inline float uInt16Number_to_float(uInt16Number a) +{ + return ((int32_t)a)/65535.f; +} + + +void precache_release(struct precache_output *p); +qcms_bool set_rgb_colorants(qcms_profile *profile, qcms_CIE_xyY white_point, qcms_CIE_xyYTRIPLE primaries); + +void qcms_transform_data_rgb_out_lut_sse2(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format); +void qcms_transform_data_rgba_out_lut_sse2(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format); +void qcms_transform_data_tetra_clut_rgba_sse2(qcms_transform* transform, + unsigned char* src, + unsigned char* dest, + size_t length, + qcms_format_type output_format); + +void qcms_transform_build_clut_cache(qcms_transform* transform); + +extern qcms_bool qcms_supports_iccv4; + + +#ifdef _MSC_VER + +long __cdecl _InterlockedIncrement(long volatile *); +long __cdecl _InterlockedDecrement(long volatile *); +#pragma intrinsic(_InterlockedIncrement) +#pragma intrinsic(_InterlockedDecrement) + +#define qcms_atomic_increment(x) _InterlockedIncrement((long volatile *)&x) +#define qcms_atomic_decrement(x) _InterlockedDecrement((long volatile*)&x) + +#else + +#define qcms_atomic_increment(x) __sync_add_and_fetch(&x, 1) +#define qcms_atomic_decrement(x) __sync_sub_and_fetch(&x, 1) + +#endif diff --git a/third_party/qcms/src/qcmstypes.h b/third_party/qcms/src/qcmstypes.h new file mode 100644 index 0000000000..d58f691535 --- /dev/null +++ b/third_party/qcms/src/qcmstypes.h @@ -0,0 +1,77 @@ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// +// 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. + +#ifndef QCMS_TYPES_H +#define QCMS_TYPES_H + +#if BYTE_ORDER == LITTLE_ENDIAN +#define IS_LITTLE_ENDIAN +#elif BYTE_ORDER == BIG_ENDIAN +#define IS_BIG_ENDIAN +#endif + +/* all of the platforms that we use _MSC_VER on are little endian + * so this is sufficient for now */ +#ifdef _MSC_VER +#define IS_LITTLE_ENDIAN +#endif + +#ifdef __OS2__ +#define IS_LITTLE_ENDIAN +#endif + +#if !defined(IS_LITTLE_ENDIAN) && !defined(IS_BIG_ENDIAN) +#error Unknown endianess +#endif + +#if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || defined (_sgi) || defined (__sun) || defined (sun) || defined (__digital__) +# include <inttypes.h> +#elif defined (_MSC_VER) && _MSC_VER < 1600 +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +#pragma warning(push) +/* Disable benign redefinition of type warning 4142 */ +#pragma warning(disable:4142) +typedef unsigned long uintptr_t; +/* Restore warnings */ +#pragma warning(pop) +#endif + +#elif defined (_AIX) +# include <sys/inttypes.h> +#else +# include <stdint.h> +#endif + +typedef qcms_bool bool; +#define true 1 +#define false 0 + +#endif diff --git a/third_party/qcms/src/tests/Makefile b/third_party/qcms/src/tests/Makefile new file mode 100644 index 0000000000..4adb7e217f --- /dev/null +++ b/third_party/qcms/src/tests/Makefile @@ -0,0 +1,16 @@ +CC=gcc +INCLUDE=-I../ +WALL=-Wall +CFLAGS=-O2 -msse2 $(WALL) -DSSE2_ENABLE +LDFLAGS=-lm + +QCMS=../transform.c ../transform-sse2.c ../transform_util.c ../matrix.c ../iccread.c ../chain.c ../qcms_util.c +OBJS=$(QCMS:.c=.o) + +all: qcms_tests + +qcms_tests: qcms_test_*.c $(OBJS) + $(CC) $(CFLAGS) $(INCLUDE) $^ -o $@ $(LDFLAGS) + +clean: + rm -rf qcms_tests *.o $(OBJS) diff --git a/third_party/qcms/src/tests/qcms_test_internal_srgb.c b/third_party/qcms/src/tests/qcms_test_internal_srgb.c new file mode 100644 index 0000000000..dac55f339f --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_internal_srgb.c @@ -0,0 +1,155 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcms.h" +#include "qcms_test_util.h" + +#include <assert.h> +#include <math.h> // sqrt +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> + +#ifndef DISPLAY_DEVICE_PROFILE +#define DISPLAY_DEVICE_PROFILE 0x6d6e7472 // 'mntr' +#endif + +// D50 adapted color primaries of the internal sRGB color profile. +static s15Fixed16Number sRGB_reference[3][3] = { + { 0x06fa0, 0x06296, 0x024a0 }, // ( 0.436035, 0.385101, 0.143066 ) + { 0x038f2, 0x0b789, 0x00f85 }, // ( 0.222443, 0.716934, 0.060623 ) + { 0x0038f, 0x018da, 0x0b6c4 }, // ( 0.013901, 0.097076, 0.713928 ) +}; + +// Reference media white point of the sRGB IEC61966-2.1 color profile. +static struct XYZNumber D65 = { + 0xf351, 0x10000, 0x116cc // ( 0.950455, 1.000000, 1.089050 ) +}; + +static void check_profile_description(qcms_profile *profile) +{ + printf("Test profile description:\n"); + + const char* description = qcms_profile_get_description(profile); + printf("description=[%s]\n\n", description); +} + +static void check_profile_pcs_white_point(const qcms_profile *profile) +{ + float rX = s15Fixed16Number_to_float(profile->redColorant.X); + float gX = s15Fixed16Number_to_float(profile->greenColorant.X); + float bX = s15Fixed16Number_to_float(profile->blueColorant.X); + float rY = s15Fixed16Number_to_float(profile->redColorant.Y); + float gY = s15Fixed16Number_to_float(profile->greenColorant.Y); + float bY = s15Fixed16Number_to_float(profile->blueColorant.Y); + float rZ = s15Fixed16Number_to_float(profile->redColorant.Z); + float gZ = s15Fixed16Number_to_float(profile->greenColorant.Z); + float bZ = s15Fixed16Number_to_float(profile->blueColorant.Z); + + printf("Test PCS white point against expected D50 XYZ values\n"); + + float X = rX + gX + bX; + float Y = rY + gY + bY; + float Z = rZ + gZ + bZ; + + float x = X / (X + Y + Z); + float y = Y / (X + Y + Z); + + printf("Computed profile D50 White point xyY = [%.6f %.6f %.6f]\n", x, y, Y); + + float xerr = x - 0.345702915; // Compute error to ICC spec D50 xyY. + float yerr = y - 0.358538597; + float Yerr = Y - 1.000000000; + + printf("D50 white point error = %.6f\n\n", (float) + sqrt((xerr * xerr) + (yerr * yerr) + (Yerr * Yerr))); +} + +static void check_profile_media_white_point(const qcms_profile *profile) +{ + int errX = profile->mediaWhitePoint.X - D65.X; + int errY = profile->mediaWhitePoint.Y - D65.Y; + int errZ = profile->mediaWhitePoint.Z - D65.Z; + + printf("Test media white point against expected D65 XYZ values\n"); + printf("Internal profile D65 values = [0x%X, 0x%X, 0x%X]\n", + profile->mediaWhitePoint.X, profile->mediaWhitePoint.Y, profile->mediaWhitePoint.Z); + printf("D65 media white point error = [%d, %d, %d]\n\n", errX, errY, errZ); +} + +static s15Fixed16Number check_profile_primaries(const qcms_profile *profile) +{ + s15Fixed16Number sRGB_internal[3][3]; + s15Fixed16Number primary_error; + int i, j; + + printf("Test qcms internal sRGB color primaries\n"); + + sRGB_internal[0][0] = profile->redColorant.X; + sRGB_internal[1][0] = profile->redColorant.Y; + sRGB_internal[2][0] = profile->redColorant.Z; + sRGB_internal[0][1] = profile->greenColorant.X; + sRGB_internal[1][1] = profile->greenColorant.Y; + sRGB_internal[2][1] = profile->greenColorant.Z; + sRGB_internal[0][2] = profile->blueColorant.X; + sRGB_internal[1][2] = profile->blueColorant.Y; + sRGB_internal[2][2] = profile->blueColorant.Z; + + primary_error = 0; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + s15Fixed16Number tmp = sRGB_internal[i][j] - sRGB_reference[i][j]; + printf(" %d", tmp); + primary_error += abs(tmp); + } + printf("\n"); + } + + return primary_error; +} + +static int qcms_test_internal_srgb(size_t width, + size_t height, + int iterations, + const char *in_path, + const char *out_path, + const int force_software) +{ + s15Fixed16Number primary_error; + + qcms_profile *profile = qcms_profile_sRGB(); + + assert(profile->class == DISPLAY_DEVICE_PROFILE); + assert(profile->rendering_intent == QCMS_INTENT_PERCEPTUAL); + assert(profile->color_space == RGB_SIGNATURE); + assert(profile->pcs == XYZ_SIGNATURE); + + if (qcms_profile_is_bogus(profile)) { + fprintf(stderr, "Failure: the internal sRGB profile failed the bogus profile check\n"); + qcms_profile_release(profile); + return -1; + } + + // Compute tristimulus matrix error. + primary_error = check_profile_primaries(profile); + printf("Total primary error = 0x%x [%.6f]\n\n", primary_error, primary_error / 65536.0); + + // Verify media white point correctness. + check_profile_media_white_point(profile); + + // Verify PCS white point correctness. + check_profile_pcs_white_point(profile); + + // Output profile description. + check_profile_description(profile); + + qcms_profile_release(profile); + return primary_error; +} + +struct qcms_test_case qcms_test_internal_srgb_info = { + "qcms_test_internal_srgb", + qcms_test_internal_srgb, + QCMS_TEST_DISABLED +}; diff --git a/third_party/qcms/src/tests/qcms_test_main.c b/third_party/qcms/src/tests/qcms_test_main.c new file mode 100644 index 0000000000..455d17b4ab --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_main.c @@ -0,0 +1,131 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcms.h" +#include "qcms_test_util.h" +#include "timing.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// Manually update the items below to add more tests. +extern struct qcms_test_case qcms_test_tetra_clut_rgba_info; +extern struct qcms_test_case qcms_test_munsell_info; +extern struct qcms_test_case qcms_test_internal_srgb_info; +extern struct qcms_test_case qcms_test_ntsc_gamut_info; +extern struct qcms_test_case qcms_test_output_trc_info; + +struct qcms_test_case qcms_test[5]; +#define TEST_CASES (sizeof(qcms_test) / sizeof(qcms_test[0])) + +static void initialize_tests() +{ + qcms_test[0] = qcms_test_tetra_clut_rgba_info; + qcms_test[1] = qcms_test_munsell_info; + qcms_test[2] = qcms_test_internal_srgb_info; + qcms_test[3] = qcms_test_ntsc_gamut_info; + qcms_test[4] = qcms_test_output_trc_info; +} + +static void list_tests() +{ + int i; + printf("Available qcms tests:\n"); + + for (i = 0; i < TEST_CASES; ++i) { + printf("\t%s\n", qcms_test[i].test_name); + } + + exit(EXIT_FAILURE); +} + +static void print_usage() +{ + printf("Usage:\n\tqcms_test -w WIDTH -h HEIGHT -n ITERATIONS -t TEST\n"); + printf("\t-w INT\t\ttest image width\n"); + printf("\t-h INT\t\ttest image height\n"); + printf("\t-n INT\t\tnumber of iterations for each test\n"); + printf("\t-a\t\trun all tests\n"); + printf("\t-l\t\tlist available tests\n"); + printf("\t-s \t\tforce software(non-sse) transform function, where available\n"); + printf("\t-i STRING\tspecify input icc color profile\n"); + printf("\t-o STRING\tspecify output icc color profile\n"); + printf("\t-t STRING\trun specific test - use \"-l\" to list possible values\n"); + printf("\n"); + exit(1); +} + +int enable_test(const char *args) +{ + int i; + + if (!args) + return 0; + + for (i = 0; i < TEST_CASES; ++i) { + if (strcmp(qcms_test[i].test_name, args) == 0) { + qcms_test[i].status = QCMS_TEST_ENABLED; + return 1; + } + } + + return 0; +} + +int main(int argc, const char **argv) +{ + int iterations = 1; + size_t height = 2000; + size_t width = 2000; + int run_all = 0; + const char *in = NULL, *out = NULL; + int force_software = 0; + int exit_status; + int enabled_tests = 0; + int i; + + initialize_tests(); + seconds(); + + if (argc == 1) { + print_usage(); + } + + while (argc > 1) { + if (strcmp(argv[1], "-n") == 0) + iterations = abs(atoi(argv[2])); + else if (strcmp(argv[1], "-w") == 0) + width = (size_t) abs(atoi(argv[2])); + else if (strcmp(argv[1], "-h") == 0) + height = (size_t) abs(atoi(argv[2])); + else if (strcmp(argv[1], "-l") == 0) + list_tests(); + else if (strcmp(argv[1], "-t") == 0) + enabled_tests += enable_test(argv[2]); + else if (strcmp(argv[1], "-a") == 0) + run_all = 1; + else if (strcmp(argv[1], "-i") == 0) + in = argv[2]; + else if (strcmp(argv[1], "-o") == 0) + out = argv[2]; + else if (strcmp(argv[1], "-s") == 0) + force_software = 1; + (--argc, ++argv); + } + + if (!run_all && !enabled_tests) { + print_usage(); + } + + exit_status = 0; + + for (i = 0; i < TEST_CASES; ++i) { + if (run_all || QCMS_TEST_ENABLED == qcms_test[i].status) + exit_status += qcms_test[i].test_fn(width, height, iterations, in, out, force_software); + } + + return exit_status; +} diff --git a/third_party/qcms/src/tests/qcms_test_munsell.c b/third_party/qcms/src/tests/qcms_test_munsell.c new file mode 100644 index 0000000000..8df3570eb0 --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_munsell.c @@ -0,0 +1,217 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcms.h" +#include "qcms_test_util.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +struct color_checker_chart { + char* name; + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; +}; + +struct color_checker_chart adobe_munsell[24] = { + { "Dark Skin", 106, 81, 67, 255 }, + { "Light Skin", 182, 149, 130, 255 }, + { "Blue Sky", 103, 122, 154, 255 }, + { "Foliage", 95, 108, 69, 255 }, + { "Blue Flower", 129, 128, 174, 255 }, + { "Bluish Green", 133, 189, 170, 255 }, + { "Orange", 194, 121, 48, 255 }, + { "Purplish Blue", 79, 91, 162, 255 }, + { "Moderate Red", 170, 85, 97, 255 }, + { "Purple", 84, 62, 105, 255 }, + { "Yellow Green", 167, 186, 73, 255 }, + { "Orange Yellow", 213, 162, 57, 255 }, + { "Blue", 54, 62, 149, 255 }, + { "Green", 101, 148, 76, 255 }, + { "Red", 152, 48, 58, 255 }, + { "Yellow", 228, 199, 55, 255 }, + { "Magenta", 164, 83, 144, 255 }, + { "Cyan", 63, 134, 163, 255 }, + { "White", 242, 241, 236, 255 }, + { "Neutral 8", 200, 200, 199, 255 }, + { "Neutral 6.5", 159, 160, 159, 255 }, + { "Neutral 5", 122, 121, 120, 255 }, + { "Neutral 3.5", 84, 84, 84, 255 }, + { "Black", 53, 53, 53, 255 }, +}; + +struct color_checker_chart srgb_munsell[24] = { + { "Dark Skin", 115, 80, 64, 255 }, + { "Light Skin", 195, 151, 130, 255 }, + { "Blue Sky", 94, 123, 156, 255 }, + { "Foliage", 88, 108, 65, 255 }, + { "Blue Flower", 130, 129, 177, 255 }, + { "Bluish Green", 100, 190, 171, 255 }, + { "Orange", 217, 122, 37, 255 }, + { "Purplish Blue", 72, 91, 165, 255 }, + { "Moderate Red", 194, 84, 98, 255 }, + { "Purple", 91, 59, 107, 255 }, + { "Yellow Green", 160, 188, 60, 255 }, + { "Orange Yellow", 230, 163, 42, 255 }, + { "Blue", 46, 60, 153, 255 }, + { "Green", 71, 150, 69, 255 }, + { "Red", 177, 44, 56, 255 }, + { "Yellow", 238, 200, 27, 255 }, + { "Magenta", 187, 82, 148, 255 }, + { "Cyan", /* -49 */ 0, 135, 166, 255 }, + { "White", 243, 242, 237, 255 }, + { "Neutral 8",201, 201, 201, 255 }, + { "Neutral 6.5", 161, 161, 161, 255 }, + { "Neutral 5",122, 122, 121, 255 }, + { "Neutral 3.5", 83, 83, 83, 255 }, + { "Black", 50, 49, 50, 255 }, +}; + +extern void qcms_transform_data_rgba_out_lut_precache(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format); + +static qcms_bool invalid_rgb_color_profile(qcms_profile *profile) +{ + return rgbData != qcms_profile_get_color_space(profile) || qcms_profile_is_bogus(profile); +} + +static int color_error(struct color_checker_chart cx, struct color_checker_chart cy) +{ + int dr = cx.r - cy.r; + int dg = cx.g - cy.g; + int db = cx.b - cy.b; + + return round(sqrt((dr * dr) + (dg * dg) + (db * db))); +} + +static qcms_profile* open_profile_from_path(const char *path) +{ + if (strcmp(path, "internal-srgb") != 0) + return qcms_profile_from_path(path); + return qcms_profile_sRGB(); +} + +static int qcms_test_munsell(size_t width, + size_t height, + int iterations, + const char *in_path, + const char *out_path, + const int force_software) +{ + qcms_profile *in_profile = NULL; + qcms_profile *out_profile = NULL; + qcms_format_type format = {0, 2}; // RGBA + qcms_transform *transform; + + struct color_checker_chart *source_munsell = NULL; + struct color_checker_chart *reference_munsell = NULL; + struct color_checker_chart destination_munsell[24]; + + char file_name[256]; + FILE *output; + int dE[24]; + float rmse; + + int i; + + printf("Test qcms data transform accuracy using Munsell colors\n"); + fflush(stdout); + + if (in_path == NULL || out_path == NULL) { + fprintf(stderr, "%s: please provide valid ICC profiles via -i/o options\n", __FUNCTION__); + return EXIT_FAILURE; + } + + in_profile = open_profile_from_path(in_path); + if (!in_profile || invalid_rgb_color_profile(in_profile)) { + fprintf(stderr, "Invalid input profile\n"); + return EXIT_FAILURE; + } + + source_munsell = srgb_munsell; + if (strstr(in_profile->description, "Adobe") != NULL) { + source_munsell = adobe_munsell; + } + + printf("Input profile %s\n", in_profile->description); + + out_profile = open_profile_from_path(out_path); + if (!out_profile || invalid_rgb_color_profile(out_profile)) { + fprintf(stderr, "Invalid output profile\n"); + return EXIT_FAILURE; + } + + reference_munsell = srgb_munsell; + if (strstr(out_profile->description, "Adobe") != NULL) { + reference_munsell = adobe_munsell; + } + + printf("Output profile %s (using qcms precache)\n", out_profile->description); + qcms_profile_precache_output_transform(out_profile); + + transform = qcms_transform_create(in_profile, QCMS_DATA_RGBA_8, out_profile, QCMS_DATA_RGBA_8, QCMS_INTENT_DEFAULT); + if (!transform) { + fprintf(stderr, "Failed to create color transform\n"); + return EXIT_FAILURE; + } else if (force_software) { + transform->transform_fn = qcms_transform_data_rgba_out_lut_precache; + } + + if (qcms_profile_match(in_profile, out_profile)) { + printf("Note: input / output profiles match\n"); + } + + rmse = 0.0f; + + for (i = 0; i < 24; i++) { + transform->transform_fn(transform, &source_munsell[i].r, &destination_munsell[i].r, 1, format); + dE[i] = color_error(reference_munsell[i], destination_munsell[i]); + rmse += dE[i] * dE[i]; + } + + rmse = sqrt(rmse / 24); + printf("RMS color error %.2f\n", rmse); + + // Name and open test result file. + sprintf(file_name, "qcms-test-%ld-munsell-%s-to-%s-rms-%.3f.csv", (long int)time(NULL), in_profile->description, out_profile->description, rmse); + // FIXME: remove spaces from the file name? + output = fopen(file_name, "w"); + + // Print headers. + if (force_software) + fprintf(output, "Report for: qcms_transform_data_rgba_out_lut_precache\n\n"); + else + fprintf(output, "Report for: qcms_transform_data_rgba_out_lut_sse2\n\n"); + + fprintf(output, "%14s,\t%s,\t%s,\t%s\n\n", "Color,", "Actual,,", "Expected,", "dE"); + + // Print results. + for (i = 0; i < 24; i++) { + fprintf(output, "%14s,\t%d,%d,%d,\t%d,%d,%d,\t%d\n", + source_munsell[i].name, + destination_munsell[i].r, destination_munsell[i].g, destination_munsell[i].b, + reference_munsell[i].r, reference_munsell[i].g, reference_munsell[i].b, + dE[i]); + } + + fprintf(output, "\nRMS color error = %.2f\n", rmse); + fclose(output); + + printf("Output written to %s\n", file_name); + return rmse > 0.000001f; +} + +struct qcms_test_case qcms_test_munsell_info = { + "qcms_test_munsell", + qcms_test_munsell, + QCMS_TEST_DISABLED +}; diff --git a/third_party/qcms/src/tests/qcms_test_ntsc_gamut.c b/third_party/qcms/src/tests/qcms_test_ntsc_gamut.c new file mode 100644 index 0000000000..22a702b04b --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_ntsc_gamut.c @@ -0,0 +1,74 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcms_test_util.h" + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +static qcms_bool invalid_rgb_color_profile(qcms_profile *profile) +{ + return rgbData != qcms_profile_get_color_space(profile) || qcms_profile_is_bogus(profile); +} + +static int qcms_test_ntsc_gamut(size_t width, + size_t height, + int iterations, + const char *input_path, + const char *referece_path, + const int force_software) +{ + qcms_profile *input_profile; + qcms_profile *reference_profile = qcms_profile_sRGB(); + qcms_transform *transform; + float input_gamut_metric, reference_gamut_metric; + + if (!input_path) { + fprintf(stderr, "%s: please provide valid ICC profiles via -i/o options\n", __FUNCTION__); + return EXIT_FAILURE; + } + + input_profile = qcms_profile_from_path(input_path); + if (!input_profile || invalid_rgb_color_profile(input_profile)) { + fprintf(stderr, "Invalid input profile\n"); + return EXIT_FAILURE; + } + + transform = qcms_transform_create(input_profile, QCMS_DATA_RGBA_8, reference_profile, QCMS_DATA_RGBA_8, QCMS_INTENT_DEFAULT); + if (!transform) { + fprintf(stderr, "Could not create transform\n"); + return EXIT_FAILURE; + } + + if (!(transform->transform_flags & TRANSFORM_FLAG_MATRIX)) { + fprintf(stderr, "Transform is not matrix\n"); + qcms_transform_release(transform); + qcms_profile_release(input_profile); + qcms_profile_release(reference_profile); + return EXIT_FAILURE; + } + + printf("NTSC 1953 relative gamut area test\n"); + + input_gamut_metric = qcms_profile_ntsc_relative_gamut_size(input_profile); + printf("Input profile\n\tDescription: %s\n\tNTSC relative gamut area: %.3f %%\n", + input_profile->description, input_gamut_metric); + + reference_gamut_metric = qcms_profile_ntsc_relative_gamut_size(reference_profile); + printf("Internal reference profile\n\tDescription: %s\n\tNTSC relative gamut area: %.3f %%\n", + reference_profile->description, reference_gamut_metric); + + qcms_transform_release(transform); + qcms_profile_release(input_profile); + qcms_profile_release(reference_profile); + + return 0; +} + +struct qcms_test_case qcms_test_ntsc_gamut_info = { + "qcms_test_ntsc_gamut", + qcms_test_ntsc_gamut, + QCMS_TEST_DISABLED +}; diff --git a/third_party/qcms/src/tests/qcms_test_output_trc.c b/third_party/qcms/src/tests/qcms_test_output_trc.c new file mode 100644 index 0000000000..049141b634 --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_output_trc.c @@ -0,0 +1,255 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcms.h" +#include "qcms_test_util.h" + +#include <assert.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#define PARAMETRIC_CURVE_TYPE 0x70617261 // 'para' + +static const float inverse65535 = (float) (1.0 / 65535.0); + +extern float clamp_float(float a); + +static int get_output_gamma_table(const char *profile_path, uint16_t **table, size_t *size) +{ + qcms_transform *transform; + qcms_profile *sRGB; + qcms_profile *target; + + target = qcms_profile_from_path(profile_path); + if (!target) { + fprintf(stderr, "Invalid input profile\n"); + return EXIT_FAILURE; + } + + sRGB = qcms_profile_sRGB(); + + transform = qcms_transform_create(sRGB, QCMS_DATA_RGBA_8, target, QCMS_DATA_RGBA_8, QCMS_INTENT_DEFAULT); + if (!transform) { + fprintf(stderr, "Failed to create colour transform\n"); + qcms_profile_release(sRGB); + qcms_profile_release(target); + return EXIT_FAILURE; + } + + *size = qcms_transform_get_output_trc_rgba(transform, target, QCMS_TRC_USHORT, NULL); + assert(*size >= 256); + + *table = malloc(*size * sizeof(uint16_t) * 4); + qcms_transform_get_output_trc_rgba(transform, target, QCMS_TRC_USHORT, *table); + + qcms_transform_release(transform); + qcms_profile_release(sRGB); + qcms_profile_release(target); + + return 0; +} + +static int get_input_gamma_table(const char *profile_path, uint16_t **table, size_t *size) +{ + qcms_transform *transform; + qcms_profile *source; + qcms_profile *sRGB; + + source = qcms_profile_from_path(profile_path); + if (!source) { + fprintf(stderr, "Invalid input profile\n"); + return EXIT_FAILURE; + } + + sRGB = qcms_profile_sRGB(); + + transform = qcms_transform_create(source, QCMS_DATA_RGBA_8, sRGB, QCMS_DATA_RGBA_8, QCMS_INTENT_DEFAULT); + if (!transform) { + fprintf(stderr, "Failed to create colour transform\n"); + qcms_profile_release(sRGB); + qcms_profile_release(source); + return EXIT_FAILURE; + } + + *size = qcms_transform_get_input_trc_rgba(transform, source, QCMS_TRC_USHORT, NULL); + assert(*size >= 256); + + *table = calloc(*size, sizeof(uint16_t) * 4); + qcms_transform_get_input_trc_rgba(transform, source, QCMS_TRC_USHORT, *table); + + qcms_transform_release(transform); + qcms_profile_release(sRGB); + qcms_profile_release(source); + + return 0; +} + +static int qcms_test_output_trc(size_t width, + size_t height, + int iterations, + const char *in_path, + const char *out_path, + const int force_software) +{ + uint16_t *gamma_table_out = NULL; + size_t output_size = 0; + qcms_profile *profile; + long time_stamp = (long)time(NULL); + char output_file_name[1024]; + float scale_factor; + size_t i; + + if (!in_path) { + fprintf(stderr, "%s: please provide valid ICC profiles via -i option\n", __FUNCTION__); + return EXIT_FAILURE; + } + + printf("Test color profile gamma curves\n"); + fflush(stdout); + + if (get_output_gamma_table(in_path, &gamma_table_out, &output_size) != 0) { + fprintf(stderr, "Unable to extract output gamma table\n"); + return EXIT_FAILURE; + } + + printf("Output gamma table size = %zu\n", output_size); + + profile = qcms_profile_from_path(in_path); + if (!profile) { + fprintf(stderr, "Invalid input profile\n"); + free(gamma_table_out); + return EXIT_FAILURE; + } + + if (profile->redTRC->type == PARAMETRIC_CURVE_TYPE) { + // Check the red TRC curve only for now. + int type = - (int)(profile->redTRC->count + 1); + uint16_t *gamma_table_in = NULL; + size_t input_size = 0; + FILE *output_file; + + printf("Detected parametric curve type = %d\n", profile->redTRC->count); + + if (get_input_gamma_table(in_path, &gamma_table_in, &input_size) != 0) { + fprintf(stderr, "Failed to compute input gamma table\n"); + qcms_profile_release(profile); + free(gamma_table_out); + return EXIT_FAILURE; + } + + // Write output to stdout and tables into a csv file. + sprintf(output_file_name, "qcms-test-%ld-parametric-gamma-output-%s.csv", + time_stamp, profile->description); + printf("Writing output gamma tables to %s\n", output_file_name); + output_file = fopen(output_file_name, "w"); + + printf("Parametric gamma values for profile %s description [%s]\n", + in_path, profile->description); + fprintf(output_file, "Parametric gamma values for profile %s description [%s]\n", + in_path, profile->description); + + printf("gamma = %.6f, a = %.6f, b = %.6f, c = %.6f, d = %.6f, e = %.6f, f = %.6f\n", + profile->redTRC->parameter[0], profile->redTRC->parameter[1], profile->redTRC->parameter[2], + profile->redTRC->parameter[3], profile->redTRC->parameter[4], profile->redTRC->parameter[5], + profile->redTRC->parameter[6]); + + fprintf(output_file, "gamma, a, b, c, d, e, f\n"); + fprintf(output_file, "%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f\n", + profile->redTRC->parameter[0], profile->redTRC->parameter[1], profile->redTRC->parameter[2], + profile->redTRC->parameter[3], profile->redTRC->parameter[4], profile->redTRC->parameter[5], + profile->redTRC->parameter[6]); + + fprintf(output_file, "\nInput curve size: %zu", input_size); + fprintf(output_file, "\nOutput curve size: %zu", output_size); + + fprintf(output_file, "\n\nInput gamma, Output gamma, LCMS Output gamma, Output gamma error\n"); + // Output gamma curve down-sample factor. + scale_factor = (float)(output_size - 1) / (input_size - 1); + + for (i = 0; i < input_size; ++i) { + float input = gamma_table_in[i * 4] * inverse65535; + size_t out_index = (size_t)floor(i * scale_factor + 0.5); + float output = gamma_table_out[out_index * 4] * inverse65535; + float x = out_index / (float)(output_size - 1); + float reference = clamp_float(evaluate_parametric_curve(type, profile->redTRC->parameter, x)); + float difference = fabs(output - reference); + + fprintf(output_file, "%.6f, %.6f, %6f, %6f\n", input, output, reference, difference); + } + + fprintf(output_file, "\nNote: the output gamma curves are down-sampled by a factor of %zu / %zu\n", + output_size, input_size); + + fclose(output_file); + free(gamma_table_in); + } else { + uint16_t *gamma_table_in = NULL; + size_t input_size = 0; + FILE *output_file; + + if (get_input_gamma_table(in_path, &gamma_table_in, &input_size) != 0) { + fprintf(stderr, "Failed to compute input gamma table\n"); + qcms_profile_release(profile); + free(gamma_table_out); + return EXIT_FAILURE; + } + + // Write output to stdout and tables into a csv file. + sprintf(output_file_name, "qcms-test-%ld-gamma-output-%s.csv", + time_stamp, profile->description); + printf("Writing gamma tables to %s\n", output_file_name); + output_file = fopen(output_file_name, "w"); + + printf("Gamma values for profile %s description [%s]\n", + in_path, profile->description); + fprintf(output_file, "Gamma values for profile %s description [%s]\n", + in_path, profile->description); + + if (profile->redTRC->count == 0) { + printf("Gamma LUT type 0: linear gamma\n"); + fprintf(output_file, "Gamma LUT type 0: linear gamma\n"); + } else if (profile->redTRC->count == 1) { + float gamma = profile->redTRC->data[0] / 256.0f; + printf("Gamma LUT type 1: gamma = %.6f\n", gamma); + fprintf(output_file, "Gamma LUT type 1: gamma = %.6f\n", gamma); + } else { + printf("Gamma LUT table size = %u\n", profile->redTRC->count); + fprintf(output_file, "Gamma LUT table size = %u\n", profile->redTRC->count); + } + + fprintf(output_file, "\nInput curve size: %zu", input_size); + fprintf(output_file, "\nOutput curve size: %zu", output_size); + + fprintf(output_file, "\n\nInput gamma, Output gamma\n"); + // Output gamma curve down-sample factor. + scale_factor = (float)(output_size - 1) / (input_size - 1); + + for (i = 0; i < input_size; ++i) { + float input = gamma_table_in[i * 4] * inverse65535; + size_t out_index = (size_t)floor(i * scale_factor + 0.5); + float output = gamma_table_out[out_index * 4] * inverse65535; + + fprintf(output_file, "%.6f, %.6f\n", input, output); + } + + fprintf(output_file, "\nNote: the output gamma curves are down-sampled by a factor of %zu / %zu\n", + output_size, input_size); + + fclose(output_file); + free(gamma_table_in); + } + + qcms_profile_release(profile); + free(gamma_table_out); + + return 0; +} + +struct qcms_test_case qcms_test_output_trc_info = { + "qcms_test_output_trc", + qcms_test_output_trc, + QCMS_TEST_DISABLED +}; diff --git a/third_party/qcms/src/tests/qcms_test_tetra_clut_rgba.c b/third_party/qcms/src/tests/qcms_test_tetra_clut_rgba.c new file mode 100644 index 0000000000..81b6ee3287 --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_tetra_clut_rgba.c @@ -0,0 +1,180 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcms.h" +#include "qcms_test_util.h" +#include "timing.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// External qcms tetra clut interpolators. + +extern void qcms_transform_data_tetra_clut_rgba(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format); + +#ifdef SSE2_ENABLE +extern void qcms_transform_data_tetra_clut_rgba_sse2(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format); +#else +void qcms_transform_data_tetra_clut_rgba_dummy(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format) +{ + (void)(transform); + (void)(src); + (void)(dest); + (void)(length); + (void)(output_format); +} +#endif + +static float *create_lut(size_t lutSize) +{ + float *lut = malloc(lutSize * sizeof(float)); + size_t i; + + for (i = 0; i < lutSize; ++i) { + lut[i] = (rand() & 255) * (1.0f / 255.0f); + } + + return lut; +} + +static int diffs; + +static int validate(unsigned char *dst0, unsigned char *dst1, size_t length, int limit, const size_t pixel_size) +{ + size_t bytes = length * pixel_size; + size_t i; + + // Compare dst0/dst0 byte-by-byte, allowing for minor differences due + // to SSE rounding modes (controlled by the limit argument). + + if (limit < 0) + limit = 255; // Ignore all differences. + + for (diffs = 0, i = 0; i < bytes; ++i) { + if (abs((int)dst0[i] - (int)dst1[i]) > limit) { + ++diffs; + } + } + + return !diffs; +} + +static int qcms_test_tetra_clut_rgba(size_t width, + size_t height, + int iterations, + const char *in_profile, + const char *out_profile, + const int force_software) +{ + qcms_transform transform0, transform1; + qcms_format_type format = {2, 0}; + uint16_t samples = 33; + size_t lutSize; + float *lut0, *lut1; + + const size_t length = width * height; + const size_t pixel_size = 4; + + double time0, time1; + int i; + + printf("Test qcms clut transforms for %d iterations\n", iterations); + printf("Test image size %u x %u pixels\n", (unsigned) width, (unsigned) height); + fflush(stdout); + + srand(0); + seconds(); + + memset(&transform0, 0, sizeof(transform0)); + memset(&transform1, 0, sizeof(transform1)); + + transform0.grid_size = samples; + transform1.grid_size = samples; + + transform0.transform_flags = 0; + transform1.transform_flags = 0; + + lutSize = 3 * samples * samples * samples; + lut0 = create_lut(lutSize); + lut1 = (float *)malloc(lutSize * sizeof(float)); + memcpy(lut1, lut0, lutSize * sizeof(float)); + + transform0.r_clut = &lut0[0]; + transform0.g_clut = &lut0[1]; + transform0.b_clut = &lut0[2]; + + transform1.r_clut = &lut1[0]; + transform1.g_clut = &lut1[1]; + transform1.b_clut = &lut1[2]; + + // Re-generate and use different data sources during the iteration loop + // to avoid compiler / cache optimizations that may affect performance. + + time0 = 0.0; + time1 = 0.0; + + for (i = 0; i < iterations; ++i) { + unsigned char *src0 = (unsigned char *)calloc(length, pixel_size); + unsigned char *src1 = (unsigned char *)calloc(length, pixel_size); + unsigned char *dst0 = (unsigned char *)calloc(length, pixel_size); + unsigned char *dst1 = (unsigned char *)calloc(length, pixel_size); + + generate_source_uint8_t(src0, length, pixel_size); + memcpy(src1, src0, length * pixel_size); + +#define TRANSFORM_TEST0 qcms_transform_data_tetra_clut_rgba +#ifdef SSE2_ENABLE +#define TRANSFORM_TEST1 qcms_transform_data_tetra_clut_rgba_sse2 +#else +#define TRANSFORM_TEST1 qcms_transform_data_tetra_clut_rgba_dummy +#endif + + TIME(TRANSFORM_TEST0(&transform0, src0, dst0, length, format), &time0); + TIME(TRANSFORM_TEST1(&transform1, src1, dst1, length, format), &time1); + + if (!validate(dst0, dst1, length, 0, pixel_size)) { + fprintf(stderr, "Invalid transform output: %d diffs\n", diffs); + } + + free(src0); + free(src1); + free(dst0); + free(dst1); + } + +#define STRINGIZE(s) #s +#define STRING(s) STRINGIZE(s) + + printf("%.6lf (avg %.6lf) seconds " STRING(TRANSFORM_TEST0) "\n", + time0, time0 / iterations); + printf("%.6lf (avg %.6lf) seconds " STRING(TRANSFORM_TEST1) "\n", + time1, time1 / iterations); + printf("%.6lf speedup after %d iterations\n\n", + time0 / time1, iterations); + + free(lut0); + free(lut1); + + return diffs; +} + +struct qcms_test_case qcms_test_tetra_clut_rgba_info = { + "qcms_test_tetra_clut_rgba", + qcms_test_tetra_clut_rgba, + QCMS_TEST_DISABLED +}; diff --git a/third_party/qcms/src/tests/qcms_test_util.c b/third_party/qcms/src/tests/qcms_test_util.c new file mode 100644 index 0000000000..e0ecff3af4 --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_util.c @@ -0,0 +1,280 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcms_test_util.h" + +#include <math.h> +#include <stdlib.h> + +#define MAX_FLOAT_ERROR 0.000001f + +// Store random pixel data in the source. +void generate_source_uint8_t(unsigned char *src, const size_t length, const size_t pixel_size) +{ + size_t bytes = length * pixel_size; + size_t i; + + for (i = 0; i < bytes; ++i) { + *src++ = rand() & 255; + } +} + +// Parametric Fn using floating point <from lcms/src/cmsgamma.c>: DefaultEvalParametricFn +float evaluate_parametric_curve(int type, const float params[], float r) +{ + float e, val, disc; + + switch (type) { + + // X = Y ^ Gamma + case 1: + if (r < 0) { + + if (fabs(params[0] - 1.0) < MAX_FLOAT_ERROR) + val = r; + else + val = 0; + } + else + val = pow(r, params[0]); + break; + + // Type 1 Reversed: X = Y ^1/gamma + case -1: + if (r < 0) { + + if (fabs(params[0] - 1.0) < MAX_FLOAT_ERROR) + val = r; + else + val = 0; + } + else + val = pow(r, 1/params[0]); + break; + + // CIE 122-1966 + // Y = (aX + b)^Gamma | X >= -b/a + // Y = 0 | else + case 2: + disc = -params[2] / params[1]; + + if (r >= disc ) { + + e = params[1]*r + params[2]; + + if (e > 0) + val = pow(e, params[0]); + else + val = 0; + } + else + val = 0; + break; + + // Type 2 Reversed + // X = (Y ^1/g - b) / a + case -2: + if (r < 0) + val = 0; + else + val = (pow(r, 1.0/params[0]) - params[2]) / params[1]; + + if (val < 0) + val = 0; + break; + + + // IEC 61966-3 + // Y = (aX + b)^Gamma | X <= -b/a + // Y = c | else + case 3: + disc = -params[2] / params[1]; + if (disc < 0) + disc = 0; + + if (r >= disc) { + + e = params[1]*r + params[2]; + + if (e > 0) + val = pow(e, params[0]) + params[3]; + else + val = 0; + } + else + val = params[3]; + break; + + + // Type 3 reversed + // X=((Y-c)^1/g - b)/a | (Y>=c) + // X=-b/a | (Y<c) + case -3: + if (r >= params[3]) { + + e = r - params[3]; + + if (e > 0) + val = (pow(e, 1/params[0]) - params[2]) / params[1]; + else + val = 0; + } + else { + val = -params[2] / params[1]; + } + break; + + + // IEC 61966-2.1 (sRGB) + // Y = (aX + b)^Gamma | X >= d + // Y = cX | X < d + case 4: + if (r >= params[4]) { + + e = params[1]*r + params[2]; + + if (e > 0) + val = pow(e, params[0]); + else + val = 0; + } + else + val = r * params[3]; + break; + + // Type 4 reversed + // X=((Y^1/g-b)/a) | Y >= (ad+b)^g + // X=Y/c | Y< (ad+b)^g + case -4: + e = params[1] * params[4] + params[2]; + if (e < 0) + disc = 0; + else + disc = pow(e, params[0]); + + if (r >= disc) { + + val = (pow(r, 1.0/params[0]) - params[2]) / params[1]; + } + else { + val = r / params[3]; + } + break; + + + // Y = (aX + b)^Gamma + e | X >= d + // Y = cX + f | X < d + case 5: + if (r >= params[4]) { + + e = params[1]*r + params[2]; + + if (e > 0) + val = pow(e, params[0]) + params[5]; + else + val = params[5]; + } + else + val = r*params[3] + params[6]; + break; + + + // Reversed type 5 + // X=((Y-e)1/g-b)/a | Y >=(ad+b)^g+e), cd+f + // X=(Y-f)/c | else + case -5: + + disc = params[3] * params[4] + params[6]; + if (r >= disc) { + + e = r - params[5]; + if (e < 0) + val = 0; + else + val = (pow(e, 1.0/params[0]) - params[2]) / params[1]; + } + else { + val = (r - params[6]) / params[3]; + } + break; + + + // Types 6,7,8 comes from segmented curves as described in ICCSpecRevision_02_11_06_Float.pdf + // Type 6 is basically identical to type 5 without d + + // Y = (a * X + b) ^ Gamma + c + case 6: + e = params[1]*r + params[2]; + + if (e < 0) + val = params[3]; + else + val = pow(e, params[0]) + params[3]; + break; + + // ((Y - c) ^1/Gamma - b) / a + case -6: + e = r - params[3]; + if (e < 0) + val = 0; + else + val = (pow(e, 1.0/params[0]) - params[2]) / params[1]; + break; + + + // Y = a * log (b * X^Gamma + c) + d + case 7: + + e = params[2] * pow(r, params[0]) + params[3]; + if (e <= 0) + val = params[4]; + else + val = params[1]*log10(e) + params[4]; + break; + + // (Y - d) / a = log(b * X ^Gamma + c) + // pow(10, (Y-d) / a) = b * X ^Gamma + c + // pow((pow(10, (Y-d) / a) - c) / b, 1/g) = X + case -7: + val = pow((pow(10.0, (r-params[4]) / params[1]) - params[3]) / params[2], 1.0 / params[0]); + break; + + + //Y = a * b^(c*X+d) + e + case 8: + val = (params[0] * pow(params[1], params[2] * r + params[3]) + params[4]); + break; + + + // Y = (log((y-e) / a) / log(b) - d ) / c + // a=0, b=1, c=2, d=3, e=4, + case -8: + + disc = r - params[4]; + if (disc < 0) val = 0; + else + val = (log(disc / params[0]) / log(params[1]) - params[3]) / params[2]; + break; + + // S-Shaped: (1 - (1-x)^1/g)^1/g + case 108: + val = pow(1.0 - pow(1 - r, 1/params[0]), 1/params[0]); + break; + + // y = (1 - (1-x)^1/g)^1/g + // y^g = (1 - (1-x)^1/g) + // 1 - y^g = (1-x)^1/g + // (1 - y^g)^g = 1 - x + // 1 - (1 - y^g)^g + case -108: + val = 1 - pow(1 - pow(r, params[0]), params[0]); + break; + + default: + // Unsupported parametric curve. Should never reach here + return 0; + } + + return val; +} diff --git a/third_party/qcms/src/tests/qcms_test_util.h b/third_party/qcms/src/tests/qcms_test_util.h new file mode 100644 index 0000000000..d8286e6028 --- /dev/null +++ b/third_party/qcms/src/tests/qcms_test_util.h @@ -0,0 +1,27 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#include "qcmsint.h" +#include "qcmstypes.h" + +typedef int (*qcms_test_function)(size_t width, + size_t height, + int iterations, + const char *in_profile, + const char *out_profile, + const int force_software); + +enum QCMS_TEST_STATUS { + QCMS_TEST_DISABLED = 0, + QCMS_TEST_ENABLED = 1, +}; + +struct qcms_test_case { + char test_name[256]; + qcms_test_function test_fn; + enum QCMS_TEST_STATUS status; +}; + +void generate_source_uint8_t(unsigned char *src, const size_t length, const size_t pixel_size); +float evaluate_parametric_curve(int type, const float params[], float r); diff --git a/third_party/qcms/src/tests/timing.h b/third_party/qcms/src/tests/timing.h new file mode 100644 index 0000000000..83f1bbdf0c --- /dev/null +++ b/third_party/qcms/src/tests/timing.h @@ -0,0 +1,51 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the Chromium LICENSE file. + +#ifndef TESTS_TIMING_H +#define TESTS_TIMING_H + +#include <assert.h> +#if defined(_WIN32) +#include <windows.h> +#else +#include <sys/time.h> +#endif +#include <time.h> + +#if defined(_WIN32) + +static double seconds() +{ + static double clock_frequency; + static bool have_frequency; + + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + if (have_frequency) + return qpc.QuadPart * clock_frequency; + + have_frequency = true; + QueryPerformanceFrequency(&qpc); + clock_frequency = 1.0 / (double) qpc.QuadPart; + return seconds(); +} + +#else + +static double seconds() +{ + struct timeval now; + gettimeofday(&now, 0); + return now.tv_sec + now.tv_usec * (1.0 / 1000000.0); +} + +#endif + +#define TIME(function, time) do { \ + double start = seconds(); \ + (function); \ + *time += seconds() - start; \ +} while (0) + +#endif // TESTS_TIMING_H diff --git a/third_party/qcms/src/transform-sse2.c b/third_party/qcms/src/transform-sse2.c new file mode 100644 index 0000000000..c06db69cd3 --- /dev/null +++ b/third_party/qcms/src/transform-sse2.c @@ -0,0 +1,458 @@ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// Copyright (C) 2015 Intel Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include <emmintrin.h> + +#include "qcmsint.h" + +/* pre-shuffled: just load these into XMM reg instead of load-scalar/shufps sequence */ +#define FLOATSCALE (float)(PRECACHE_OUTPUT_SIZE - 1) +#define CLAMPMAXVAL 1.0f + +static const ALIGN float floatScaleX4[4] = + { FLOATSCALE, FLOATSCALE, FLOATSCALE, FLOATSCALE}; +static const ALIGN float clampMaxValueX4[4] = + { CLAMPMAXVAL, CLAMPMAXVAL, CLAMPMAXVAL, CLAMPMAXVAL}; + +void qcms_transform_data_rgb_out_lut_sse2(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format) +{ + unsigned int i; + float (*mat)[4] = transform->matrix; + char input_back[32]; + /* Ensure we have a buffer that's 16 byte aligned regardless of the original + * stack alignment. We can't use __attribute__((aligned(16))) or __declspec(align(32)) + * because they don't work on stack variables. gcc 4.4 does do the right thing + * on x86 but that's too new for us right now. For more info: gcc bug #16660 */ + float const * input = (float*)(((uintptr_t)&input_back[16]) & ~0xf); + /* share input and output locations to save having to keep the + * locations in separate registers */ + uint32_t const * output = (uint32_t*)input; + + /* deref *transform now to avoid it in loop */ + const float *igtbl_r = transform->input_gamma_table_r; + const float *igtbl_g = transform->input_gamma_table_g; + const float *igtbl_b = transform->input_gamma_table_b; + + /* deref *transform now to avoid it in loop */ + const uint8_t *otdata_r = &transform->output_table_r->data[0]; + const uint8_t *otdata_g = &transform->output_table_g->data[0]; + const uint8_t *otdata_b = &transform->output_table_b->data[0]; + + /* input matrix values never change */ + const __m128 mat0 = _mm_load_ps(mat[0]); + const __m128 mat1 = _mm_load_ps(mat[1]); + const __m128 mat2 = _mm_load_ps(mat[2]); + + /* these values don't change, either */ + const __m128 max = _mm_load_ps(clampMaxValueX4); + const __m128 min = _mm_setzero_ps(); + const __m128 scale = _mm_load_ps(floatScaleX4); + + /* working variables */ + __m128 vec_r, vec_g, vec_b, result; + const int r_out = output_format.r; + const int b_out = output_format.b; + + /* CYA */ + if (!length) + return; + + /* one pixel is handled outside of the loop */ + length--; + + /* setup for transforming 1st pixel */ + vec_r = _mm_load_ss(&igtbl_r[src[0]]); + vec_g = _mm_load_ss(&igtbl_g[src[1]]); + vec_b = _mm_load_ss(&igtbl_b[src[2]]); + src += 3; + + /* transform all but final pixel */ + + for (i=0; i<length; i++) + { + /* position values from gamma tables */ + vec_r = _mm_shuffle_ps(vec_r, vec_r, 0); + vec_g = _mm_shuffle_ps(vec_g, vec_g, 0); + vec_b = _mm_shuffle_ps(vec_b, vec_b, 0); + + /* gamma * matrix */ + vec_r = _mm_mul_ps(vec_r, mat0); + vec_g = _mm_mul_ps(vec_g, mat1); + vec_b = _mm_mul_ps(vec_b, mat2); + + /* crunch, crunch, crunch */ + vec_r = _mm_add_ps(vec_g, _mm_add_ps(vec_r, vec_b)); + vec_r = _mm_max_ps(min, vec_r); + vec_r = _mm_min_ps(max, vec_r); + result = _mm_mul_ps(vec_r, scale); + + /* store calc'd output tables indices */ + _mm_store_si128((__m128i*)output, _mm_cvtps_epi32(result)); + + /* load for next loop while store completes */ + vec_r = _mm_load_ss(&igtbl_r[src[0]]); + vec_g = _mm_load_ss(&igtbl_g[src[1]]); + vec_b = _mm_load_ss(&igtbl_b[src[2]]); + src += 3; + + /* use calc'd indices to output RGB values */ + dest[r_out] = otdata_r[output[0]]; + dest[1] = otdata_g[output[1]]; + dest[b_out] = otdata_b[output[2]]; + dest += 3; + } + + /* handle final (maybe only) pixel */ + + vec_r = _mm_shuffle_ps(vec_r, vec_r, 0); + vec_g = _mm_shuffle_ps(vec_g, vec_g, 0); + vec_b = _mm_shuffle_ps(vec_b, vec_b, 0); + + vec_r = _mm_mul_ps(vec_r, mat0); + vec_g = _mm_mul_ps(vec_g, mat1); + vec_b = _mm_mul_ps(vec_b, mat2); + + vec_r = _mm_add_ps(vec_g, _mm_add_ps(vec_r, vec_b)); + vec_r = _mm_max_ps(min, vec_r); + vec_r = _mm_min_ps(max, vec_r); + result = _mm_mul_ps(vec_r, scale); + + _mm_store_si128((__m128i*)output, _mm_cvtps_epi32(result)); + + dest[r_out] = otdata_r[output[0]]; + dest[1] = otdata_g[output[1]]; + dest[b_out] = otdata_b[output[2]]; +} + +void qcms_transform_data_rgba_out_lut_sse2(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format) +{ + unsigned int i; + float (*mat)[4] = transform->matrix; + char input_back[32]; + /* Ensure we have a buffer that's 16 byte aligned regardless of the original + * stack alignment. We can't use __attribute__((aligned(16))) or __declspec(align(32)) + * because they don't work on stack variables. gcc 4.4 does do the right thing + * on x86 but that's too new for us right now. For more info: gcc bug #16660 */ + float const * input = (float*)(((uintptr_t)&input_back[16]) & ~0xf); + /* share input and output locations to save having to keep the + * locations in separate registers */ + uint32_t const * output = (uint32_t*)input; + + /* deref *transform now to avoid it in loop */ + const float *igtbl_r = transform->input_gamma_table_r; + const float *igtbl_g = transform->input_gamma_table_g; + const float *igtbl_b = transform->input_gamma_table_b; + + /* deref *transform now to avoid it in loop */ + const uint8_t *otdata_r = &transform->output_table_r->data[0]; + const uint8_t *otdata_g = &transform->output_table_g->data[0]; + const uint8_t *otdata_b = &transform->output_table_b->data[0]; + + /* input matrix values never change */ + const __m128 mat0 = _mm_load_ps(mat[0]); + const __m128 mat1 = _mm_load_ps(mat[1]); + const __m128 mat2 = _mm_load_ps(mat[2]); + + /* these values don't change, either */ + const __m128 max = _mm_load_ps(clampMaxValueX4); + const __m128 min = _mm_setzero_ps(); + const __m128 scale = _mm_load_ps(floatScaleX4); + + /* working variables */ + __m128 vec_r, vec_g, vec_b, result; + const int r_out = output_format.r; + const int b_out = output_format.b; + unsigned char alpha; + + /* CYA */ + if (!length) + return; + + /* one pixel is handled outside of the loop */ + length--; + + /* setup for transforming 1st pixel */ + vec_r = _mm_load_ss(&igtbl_r[src[0]]); + vec_g = _mm_load_ss(&igtbl_g[src[1]]); + vec_b = _mm_load_ss(&igtbl_b[src[2]]); + alpha = src[3]; + src += 4; + + /* transform all but final pixel */ + + for (i=0; i<length; i++) + { + /* position values from gamma tables */ + vec_r = _mm_shuffle_ps(vec_r, vec_r, 0); + vec_g = _mm_shuffle_ps(vec_g, vec_g, 0); + vec_b = _mm_shuffle_ps(vec_b, vec_b, 0); + + /* gamma * matrix */ + vec_r = _mm_mul_ps(vec_r, mat0); + vec_g = _mm_mul_ps(vec_g, mat1); + vec_b = _mm_mul_ps(vec_b, mat2); + + /* store alpha for this pixel; load alpha for next */ + dest[3] = alpha; + alpha = src[3]; + + /* crunch, crunch, crunch */ + vec_r = _mm_add_ps(vec_g, _mm_add_ps(vec_r, vec_b)); + vec_r = _mm_max_ps(min, vec_r); + vec_r = _mm_min_ps(max, vec_r); + result = _mm_mul_ps(vec_r, scale); + + /* store calc'd output tables indices */ + _mm_store_si128((__m128i*)output, _mm_cvtps_epi32(result)); + + /* load gamma values for next loop while store completes */ + vec_r = _mm_load_ss(&igtbl_r[src[0]]); + vec_g = _mm_load_ss(&igtbl_g[src[1]]); + vec_b = _mm_load_ss(&igtbl_b[src[2]]); + src += 4; + + /* use calc'd indices to output RGB values */ + dest[r_out] = otdata_r[output[0]]; + dest[1] = otdata_g[output[1]]; + dest[b_out] = otdata_b[output[2]]; + dest += 4; + } + + /* handle final (maybe only) pixel */ + + vec_r = _mm_shuffle_ps(vec_r, vec_r, 0); + vec_g = _mm_shuffle_ps(vec_g, vec_g, 0); + vec_b = _mm_shuffle_ps(vec_b, vec_b, 0); + + vec_r = _mm_mul_ps(vec_r, mat0); + vec_g = _mm_mul_ps(vec_g, mat1); + vec_b = _mm_mul_ps(vec_b, mat2); + + dest[3] = alpha; + + vec_r = _mm_add_ps(vec_g, _mm_add_ps(vec_r, vec_b)); + vec_r = _mm_max_ps(min, vec_r); + vec_r = _mm_min_ps(max, vec_r); + result = _mm_mul_ps(vec_r, scale); + + _mm_store_si128((__m128i*)output, _mm_cvtps_epi32(result)); + + dest[r_out] = otdata_r[output[0]]; + dest[1] = otdata_g[output[1]]; + dest[b_out] = otdata_b[output[2]]; +} + +static inline __m128i __mm_swizzle_epi32(__m128i value, int bgra) +{ + return bgra ? _mm_shuffle_epi32(value, _MM_SHUFFLE(0, 1, 2, 3)) : + _mm_shuffle_epi32(value, _MM_SHUFFLE(0, 3, 2, 1)) ; +} + +void qcms_transform_data_tetra_clut_rgba_sse2(qcms_transform *transform, + unsigned char *src, + unsigned char *dest, + size_t length, + qcms_format_type output_format) +{ + const int bgra = output_format.r; + + size_t i; + + const int xy_len_3 = 3 * 1; + const int x_len_3 = 3 * transform->grid_size; + const int len_3 = x_len_3 * transform->grid_size; + + const __m128 __255 = _mm_set1_ps(255.0f); + const __m128 __one = _mm_set1_ps(1.0f); + const __m128 __000 = _mm_setzero_ps(); + + const float* r_table = transform->r_clut; + const float* g_table = transform->g_clut; + const float* b_table = transform->b_clut; + + int i3, i2, i1, i0; + + __m128 c3; + __m128 c2; + __m128 c1; + __m128 c0; + + if (!(transform->transform_flags & TRANSFORM_FLAG_CLUT_CACHE)) + qcms_transform_build_clut_cache(transform); + + for (i = 0; i < length; ++i) { + unsigned char in_r = *src++; + unsigned char in_g = *src++; + unsigned char in_b = *src++; + + // initialize the output result with the alpha channel only + + __m128i result = _mm_setr_epi32(*src++, 0, 0, 0); + + // get the input point r.xyz relative to the subcube origin + + float rx = transform->r_cache[in_r]; + float ry = transform->r_cache[in_g]; + float rz = transform->r_cache[in_b]; + + // load and LUT scale the subcube maximum vertex + + int xn = transform->ceil_cache[in_r] * len_3; + int yn = transform->ceil_cache[in_g] * x_len_3; + int zn = transform->ceil_cache[in_b] * xy_len_3; + + // load and LUT scale the subcube origin vertex + + int x0 = transform->floor_cache[in_r] * len_3; + int y0 = transform->floor_cache[in_g] * x_len_3; + int z0 = transform->floor_cache[in_b] * xy_len_3; + + // tetrahedral interpolate the input color r.xyz + +#define TETRA_LOOKUP_CLUT(i3, i2, i1, i0) \ + c0 = _mm_set_ps(b_table[i0], g_table[i0], r_table[i0], 0.f), \ + c1 = _mm_set_ps(b_table[i1], g_table[i1], r_table[i1], 0.f), \ + c2 = _mm_set_ps(b_table[i2], g_table[i2], r_table[i2], 0.f), \ + c3 = _mm_set_ps(b_table[i3], g_table[i3], r_table[i3], 0.f) + + i0 = x0 + y0 + z0; + + if (rx >= ry) { + + if (ry >= rz) { // rx >= ry && ry >= rz + + i3 = yn + (i1 = xn); + i1 += i0 - x0; + i2 = i3 + z0; + i3 += zn; + + TETRA_LOOKUP_CLUT(i3, i2, i1, i0); + + c3 = _mm_sub_ps(c3, c2); + c2 = _mm_sub_ps(c2, c1); + c1 = _mm_sub_ps(c1, c0); + + } else if (rx >= rz) { // rx >= rz && rz >= ry + + i3 = zn + (i1 = xn); + i1 += i0 - x0; + i2 = i3 + yn; + i3 += y0; + + TETRA_LOOKUP_CLUT(i3, i2, i1, i0); + + c2 = _mm_sub_ps(c2, c3); + c3 = _mm_sub_ps(c3, c1); + c1 = _mm_sub_ps(c1, c0); + + } else { // rz > rx && rx >= ry + + i2 = xn + (i3 = zn); + i3 += i0 - z0; + i1 = i2 + y0; + i2 += yn; + + TETRA_LOOKUP_CLUT(i3, i2, i1, i0); + + c2 = _mm_sub_ps(c2, c1); + c1 = _mm_sub_ps(c1, c3); + c3 = _mm_sub_ps(c3, c0); + } + } else { + + if (rx >= rz) { // ry > rx && rx >= rz + + i3 = xn + (i2 = yn); + i2 += i0 - y0; + i1 = i3 + z0; + i3 += zn; + + TETRA_LOOKUP_CLUT(i3, i2, i1, i0); + + c3 = _mm_sub_ps(c3, c1); + c1 = _mm_sub_ps(c1, c2); + c2 = _mm_sub_ps(c2, c0); + + } else if (ry >= rz) { // ry >= rz && rz > rx + + i3 = zn + (i2 = yn); + i2 += i0 - y0; + i1 = i3 + xn; + i3 += x0; + + TETRA_LOOKUP_CLUT(i3, i2, i1, i0); + + c1 = _mm_sub_ps(c1, c3); + c3 = _mm_sub_ps(c3, c2); + c2 = _mm_sub_ps(c2, c0); + + } else { // rz > ry && ry > rx + + i2 = yn + (i3 = zn); + i3 += i0 - z0; + i1 = i2 + xn; + i2 += x0; + + TETRA_LOOKUP_CLUT(i3, i2, i1, i0); + + c1 = _mm_sub_ps(c1, c2); + c2 = _mm_sub_ps(c2, c3); + c3 = _mm_sub_ps(c3, c0); + } + } + + // output.xyz = column_matrix(c1, c2, c3) x r.xyz + c0.xyz + + c0 = _mm_add_ps(c0, _mm_mul_ps(c1, _mm_set1_ps(rx))); + c0 = _mm_add_ps(c0, _mm_mul_ps(c2, _mm_set1_ps(ry))); + c0 = _mm_add_ps(c0, _mm_mul_ps(c3, _mm_set1_ps(rz))); + + // clamp to [0.0..1.0], then scale by 255 + + c0 = _mm_max_ps(c0, __000); + c0 = _mm_min_ps(c0, __one); + c0 = _mm_mul_ps(c0, __255); + + // int(c0) with float rounding, add alpha + + result = _mm_add_epi32(result, _mm_cvtps_epi32(c0)); + + // swizzle and repack in result low bytes + + result = __mm_swizzle_epi32(result, bgra); + result = _mm_packus_epi16(result, result); + result = _mm_packus_epi16(result, result); + + // store into uint32_t* pixel destination + + *(uint32_t *)dest = _mm_cvtsi128_si32(result); + dest += 4; + } +} diff --git a/third_party/qcms/src/transform.c b/third_party/qcms/src/transform.c new file mode 100644 index 0000000000..dd4eee46ec --- /dev/null +++ b/third_party/qcms/src/transform.c @@ -0,0 +1,1641 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Corporation +// Copyright (C) 1998-2007 Marti Maria +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include <string.h> //memcpy +#include "qcmsint.h" +#include "chain.h" +#include "halffloat.h" +#include "matrix.h" +#include "transform_util.h" + +/* for MSVC, GCC, Intel, and Sun compilers */ +#if defined(_M_IX86) || defined(__i386__) || defined(__i386) || defined(_M_AMD64) || defined(__x86_64__) || defined(__x86_64) +#define X86 +#endif /* _M_IX86 || __i386__ || __i386 || _M_AMD64 || __x86_64__ || __x86_64 */ + +// Build a White point, primary chromas transfer matrix from RGB to CIE XYZ +// This is just an approximation, I am not handling all the non-linear +// aspects of the RGB to XYZ process, and assumming that the gamma correction +// has transitive property in the tranformation chain. +// +// the alghoritm: +// +// - First I build the absolute conversion matrix using +// primaries in XYZ. This matrix is next inverted +// - Then I eval the source white point across this matrix +// obtaining the coeficients of the transformation +// - Then, I apply these coeficients to the original matrix +static struct matrix build_RGB_to_XYZ_transfer_matrix(qcms_CIE_xyY white, qcms_CIE_xyYTRIPLE primrs) +{ + struct matrix primaries; + struct matrix primaries_invert; + struct matrix result; + struct vector white_point; + struct vector coefs; + + double xn, yn; + double xr, yr; + double xg, yg; + double xb, yb; + + xn = white.x; + yn = white.y; + + if (yn == 0.0) + return matrix_invalid(); + + xr = primrs.red.x; + yr = primrs.red.y; + xg = primrs.green.x; + yg = primrs.green.y; + xb = primrs.blue.x; + yb = primrs.blue.y; + + primaries.m[0][0] = xr; + primaries.m[0][1] = xg; + primaries.m[0][2] = xb; + + primaries.m[1][0] = yr; + primaries.m[1][1] = yg; + primaries.m[1][2] = yb; + + primaries.m[2][0] = 1 - xr - yr; + primaries.m[2][1] = 1 - xg - yg; + primaries.m[2][2] = 1 - xb - yb; + primaries.invalid = false; + + white_point.v[0] = xn/yn; + white_point.v[1] = 1.; + white_point.v[2] = (1.0-xn-yn)/yn; + + primaries_invert = matrix_invert(primaries); + + coefs = matrix_eval(primaries_invert, white_point); + + result.m[0][0] = coefs.v[0]*xr; + result.m[0][1] = coefs.v[1]*xg; + result.m[0][2] = coefs.v[2]*xb; + + result.m[1][0] = coefs.v[0]*yr; + result.m[1][1] = coefs.v[1]*yg; + result.m[1][2] = coefs.v[2]*yb; + + result.m[2][0] = coefs.v[0]*(1.-xr-yr); + result.m[2][1] = coefs.v[1]*(1.-xg-yg); + result.m[2][2] = coefs.v[2]*(1.-xb-yb); + result.invalid = primaries_invert.invalid; + + return result; +} + +struct CIE_XYZ { + double X; + double Y; + double Z; +}; + +/* CIE Illuminant D50 */ +static const struct CIE_XYZ D50_XYZ = { + 0.9642, + 1.0000, + 0.8249 +}; + +/* from lcms: xyY2XYZ() + * corresponds to argyll: icmYxy2XYZ() */ +static struct CIE_XYZ xyY2XYZ(qcms_CIE_xyY source) +{ + struct CIE_XYZ dest; + dest.X = (source.x / source.y) * source.Y; + dest.Y = source.Y; + dest.Z = ((1 - source.x - source.y) / source.y) * source.Y; + return dest; +} + +/* from lcms: ComputeChromaticAdaption */ +// Compute chromatic adaption matrix using chad as cone matrix +static struct matrix +compute_chromatic_adaption(struct CIE_XYZ source_white_point, + struct CIE_XYZ dest_white_point, + struct matrix chad) +{ + struct matrix chad_inv; + struct vector cone_source_XYZ, cone_source_rgb; + struct vector cone_dest_XYZ, cone_dest_rgb; + struct matrix cone, tmp; + + tmp = chad; + chad_inv = matrix_invert(tmp); + + cone_source_XYZ.v[0] = source_white_point.X; + cone_source_XYZ.v[1] = source_white_point.Y; + cone_source_XYZ.v[2] = source_white_point.Z; + + cone_dest_XYZ.v[0] = dest_white_point.X; + cone_dest_XYZ.v[1] = dest_white_point.Y; + cone_dest_XYZ.v[2] = dest_white_point.Z; + + cone_source_rgb = matrix_eval(chad, cone_source_XYZ); + cone_dest_rgb = matrix_eval(chad, cone_dest_XYZ); + + cone.m[0][0] = cone_dest_rgb.v[0]/cone_source_rgb.v[0]; + cone.m[0][1] = 0; + cone.m[0][2] = 0; + cone.m[1][0] = 0; + cone.m[1][1] = cone_dest_rgb.v[1]/cone_source_rgb.v[1]; + cone.m[1][2] = 0; + cone.m[2][0] = 0; + cone.m[2][1] = 0; + cone.m[2][2] = cone_dest_rgb.v[2]/cone_source_rgb.v[2]; + cone.invalid = false; + + // Normalize + return matrix_multiply(chad_inv, matrix_multiply(cone, chad)); +} + +/* from lcms: cmsAdaptionMatrix */ +// Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll +// Bradford is assumed +static struct matrix +adaption_matrix(struct CIE_XYZ source_illumination, struct CIE_XYZ target_illumination) +{ +#if defined (_MSC_VER) +#pragma warning(push) +/* Disable double to float truncation warning 4305 */ +#pragma warning(disable:4305) +#endif + struct matrix lam_rigg = {{ // Bradford matrix + { 0.8951, 0.2664, -0.1614 }, + { -0.7502, 1.7135, 0.0367 }, + { 0.0389, -0.0685, 1.0296 } + }}; +#if defined (_MSC_VER) +/* Restore warnings */ +#pragma warning(pop) +#endif + return compute_chromatic_adaption(source_illumination, target_illumination, lam_rigg); +} + +/* from lcms: cmsAdaptMatrixToD50 */ +static struct matrix adapt_matrix_to_D50(struct matrix r, qcms_CIE_xyY source_white_point) +{ + struct CIE_XYZ DNN_XYZ; + struct matrix Bradford; + + if (source_white_point.y == 0.0) + return matrix_invalid(); + + DNN_XYZ = xyY2XYZ(source_white_point); + + Bradford = adaption_matrix(DNN_XYZ, D50_XYZ); + + return matrix_multiply(Bradford, r); +} + +qcms_bool set_rgb_colorants(qcms_profile *profile, qcms_CIE_xyY white_point, qcms_CIE_xyYTRIPLE primaries) +{ + struct CIE_XYZ source_white; + struct matrix colorants; + + colorants = build_RGB_to_XYZ_transfer_matrix(white_point, primaries); + colorants = adapt_matrix_to_D50(colorants, white_point); + + if (colorants.invalid) + return false; + + /* note: there's a transpose type of operation going on here */ + profile->redColorant.X = double_to_s15Fixed16Number(colorants.m[0][0]); + profile->redColorant.Y = double_to_s15Fixed16Number(colorants.m[1][0]); + profile->redColorant.Z = double_to_s15Fixed16Number(colorants.m[2][0]); + + profile->greenColorant.X = double_to_s15Fixed16Number(colorants.m[0][1]); + profile->greenColorant.Y = double_to_s15Fixed16Number(colorants.m[1][1]); + profile->greenColorant.Z = double_to_s15Fixed16Number(colorants.m[2][1]); + + profile->blueColorant.X = double_to_s15Fixed16Number(colorants.m[0][2]); + profile->blueColorant.Y = double_to_s15Fixed16Number(colorants.m[1][2]); + profile->blueColorant.Z = double_to_s15Fixed16Number(colorants.m[2][2]); + + /* Store the media white point */ + source_white = xyY2XYZ(white_point); + profile->mediaWhitePoint.X = double_to_s15Fixed16Number(source_white.X); + profile->mediaWhitePoint.Y = double_to_s15Fixed16Number(source_white.Y); + profile->mediaWhitePoint.Z = double_to_s15Fixed16Number(source_white.Z); + + return true; +} + +#if 0 +static void qcms_transform_data_rgb_out_pow(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + int i; + float (*mat)[4] = transform->matrix; + for (i=0; i<length; i++) { + unsigned char device_r = *src++; + unsigned char device_g = *src++; + unsigned char device_b = *src++; + + float linear_r = transform->input_gamma_table_r[device_r]; + float linear_g = transform->input_gamma_table_g[device_g]; + float linear_b = transform->input_gamma_table_b[device_b]; + + float out_linear_r = mat[0][0]*linear_r + mat[1][0]*linear_g + mat[2][0]*linear_b; + float out_linear_g = mat[0][1]*linear_r + mat[1][1]*linear_g + mat[2][1]*linear_b; + float out_linear_b = mat[0][2]*linear_r + mat[1][2]*linear_g + mat[2][2]*linear_b; + + float out_device_r = pow(out_linear_r, transform->out_gamma_r); + float out_device_g = pow(out_linear_g, transform->out_gamma_g); + float out_device_b = pow(out_linear_b, transform->out_gamma_b); + + dest[r_out] = clamp_u8(out_device_r*255); + dest[1] = clamp_u8(out_device_g*255); + dest[b_out] = clamp_u8(out_device_b*255); + dest += 3; + } +} +#endif + +static void qcms_transform_data_gray_out_lut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + for (i = 0; i < length; i++) { + float out_device_r, out_device_g, out_device_b; + unsigned char device = *src++; + + float linear = transform->input_gamma_table_gray[device]; + + out_device_r = lut_interp_linear(linear, transform->output_gamma_lut_r, transform->output_gamma_lut_r_length); + out_device_g = lut_interp_linear(linear, transform->output_gamma_lut_g, transform->output_gamma_lut_g_length); + out_device_b = lut_interp_linear(linear, transform->output_gamma_lut_b, transform->output_gamma_lut_b_length); + + dest[r_out] = clamp_u8(out_device_r*255); + dest[1] = clamp_u8(out_device_g*255); + dest[b_out] = clamp_u8(out_device_b*255); + dest += 3; + } +} + +/* Alpha is not corrected. + A rationale for this is found in Alvy Ray's "Should Alpha Be Nonlinear If + RGB Is?" Tech Memo 17 (December 14, 1998). + See: ftp://ftp.alvyray.com/Acrobat/17_Nonln.pdf +*/ + +static void qcms_transform_data_graya_out_lut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + for (i = 0; i < length; i++) { + float out_device_r, out_device_g, out_device_b; + unsigned char device = *src++; + unsigned char alpha = *src++; + + float linear = transform->input_gamma_table_gray[device]; + + out_device_r = lut_interp_linear(linear, transform->output_gamma_lut_r, transform->output_gamma_lut_r_length); + out_device_g = lut_interp_linear(linear, transform->output_gamma_lut_g, transform->output_gamma_lut_g_length); + out_device_b = lut_interp_linear(linear, transform->output_gamma_lut_b, transform->output_gamma_lut_b_length); + + dest[r_out] = clamp_u8(out_device_r*255); + dest[1] = clamp_u8(out_device_g*255); + dest[b_out] = clamp_u8(out_device_b*255); + dest[3] = alpha; + dest += 4; + } +} + + +static void qcms_transform_data_gray_out_precache(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + for (i = 0; i < length; i++) { + unsigned char device = *src++; + uint16_t gray; + + float linear = transform->input_gamma_table_gray[device]; + + /* we could round here... */ + gray = linear * PRECACHE_OUTPUT_MAX; + + dest[r_out] = transform->output_table_r->data[gray]; + dest[1] = transform->output_table_g->data[gray]; + dest[b_out] = transform->output_table_b->data[gray]; + dest += 3; + } +} + + +static void qcms_transform_data_graya_out_precache(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + for (i = 0; i < length; i++) { + unsigned char device = *src++; + unsigned char alpha = *src++; + uint16_t gray; + + float linear = transform->input_gamma_table_gray[device]; + + /* we could round here... */ + gray = linear * PRECACHE_OUTPUT_MAX; + + dest[r_out] = transform->output_table_r->data[gray]; + dest[1] = transform->output_table_g->data[gray]; + dest[b_out] = transform->output_table_b->data[gray]; + dest[3] = alpha; + dest += 4; + } +} + +static void qcms_transform_data_rgb_out_lut_precache(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + float (*mat)[4] = transform->matrix; + for (i = 0; i < length; i++) { + unsigned char device_r = *src++; + unsigned char device_g = *src++; + unsigned char device_b = *src++; + uint16_t r, g, b; + + float linear_r = transform->input_gamma_table_r[device_r]; + float linear_g = transform->input_gamma_table_g[device_g]; + float linear_b = transform->input_gamma_table_b[device_b]; + + float out_linear_r = mat[0][0]*linear_r + mat[1][0]*linear_g + mat[2][0]*linear_b; + float out_linear_g = mat[0][1]*linear_r + mat[1][1]*linear_g + mat[2][1]*linear_b; + float out_linear_b = mat[0][2]*linear_r + mat[1][2]*linear_g + mat[2][2]*linear_b; + + out_linear_r = clamp_float(out_linear_r); + out_linear_g = clamp_float(out_linear_g); + out_linear_b = clamp_float(out_linear_b); + + /* we could round here... */ + r = out_linear_r * PRECACHE_OUTPUT_MAX; + g = out_linear_g * PRECACHE_OUTPUT_MAX; + b = out_linear_b * PRECACHE_OUTPUT_MAX; + + dest[r_out] = transform->output_table_r->data[r]; + dest[1] = transform->output_table_g->data[g]; + dest[b_out] = transform->output_table_b->data[b]; + dest += 3; + } +} + +void qcms_transform_data_rgba_out_lut_precache(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + float (*mat)[4] = transform->matrix; + for (i = 0; i < length; i++) { + unsigned char device_r = *src++; + unsigned char device_g = *src++; + unsigned char device_b = *src++; + unsigned char alpha = *src++; + uint16_t r, g, b; + + float linear_r = transform->input_gamma_table_r[device_r]; + float linear_g = transform->input_gamma_table_g[device_g]; + float linear_b = transform->input_gamma_table_b[device_b]; + + float out_linear_r = mat[0][0]*linear_r + mat[1][0]*linear_g + mat[2][0]*linear_b; + float out_linear_g = mat[0][1]*linear_r + mat[1][1]*linear_g + mat[2][1]*linear_b; + float out_linear_b = mat[0][2]*linear_r + mat[1][2]*linear_g + mat[2][2]*linear_b; + + out_linear_r = clamp_float(out_linear_r); + out_linear_g = clamp_float(out_linear_g); + out_linear_b = clamp_float(out_linear_b); + + /* we could round here... */ + r = out_linear_r * PRECACHE_OUTPUT_MAX; + g = out_linear_g * PRECACHE_OUTPUT_MAX; + b = out_linear_b * PRECACHE_OUTPUT_MAX; + + dest[r_out] = transform->output_table_r->data[r]; + dest[1] = transform->output_table_g->data[g]; + dest[b_out] = transform->output_table_b->data[b]; + dest[3] = alpha; + dest += 4; + } +} + +// Not used +/* +static void qcms_transform_data_clut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + int xy_len = 1; + int x_len = transform->grid_size; + int len = x_len * x_len; + float* r_table = transform->r_clut; + float* g_table = transform->g_clut; + float* b_table = transform->b_clut; + + for (i = 0; i < length; i++) { + unsigned char in_r = *src++; + unsigned char in_g = *src++; + unsigned char in_b = *src++; + float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f; + + int x = floor(linear_r * (transform->grid_size-1)); + int y = floor(linear_g * (transform->grid_size-1)); + int z = floor(linear_b * (transform->grid_size-1)); + int x_n = ceil(linear_r * (transform->grid_size-1)); + int y_n = ceil(linear_g * (transform->grid_size-1)); + int z_n = ceil(linear_b * (transform->grid_size-1)); + float x_d = linear_r * (transform->grid_size-1) - x; + float y_d = linear_g * (transform->grid_size-1) - y; + float z_d = linear_b * (transform->grid_size-1) - z; + + float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d); + float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d); + float r_y1 = lerp(r_x1, r_x2, y_d); + float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d); + float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d); + float r_y2 = lerp(r_x3, r_x4, y_d); + float clut_r = lerp(r_y1, r_y2, z_d); + + float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d); + float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d); + float g_y1 = lerp(g_x1, g_x2, y_d); + float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d); + float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d); + float g_y2 = lerp(g_x3, g_x4, y_d); + float clut_g = lerp(g_y1, g_y2, z_d); + + float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d); + float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d); + float b_y1 = lerp(b_x1, b_x2, y_d); + float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d); + float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d); + float b_y2 = lerp(b_x3, b_x4, y_d); + float clut_b = lerp(b_y1, b_y2, z_d); + + dest[r_out] = clamp_u8(clut_r*255.0f); + dest[1] = clamp_u8(clut_g*255.0f); + dest[b_out] = clamp_u8(clut_b*255.0f); + dest += 3; + } +} +*/ + +// Using lcms' tetra interpolation algorithm. +void qcms_transform_data_tetra_clut_rgba(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + int xy_len = 1; + int x_len = transform->grid_size; + int len = x_len * x_len; + float* r_table = transform->r_clut; + float* g_table = transform->g_clut; + float* b_table = transform->b_clut; + float c0_r, c1_r, c2_r, c3_r; + float c0_g, c1_g, c2_g, c3_g; + float c0_b, c1_b, c2_b, c3_b; + float clut_r, clut_g, clut_b; + + if (!(transform->transform_flags & TRANSFORM_FLAG_CLUT_CACHE)) + qcms_transform_build_clut_cache(transform); + + for (i = 0; i < length; i++) { + unsigned char in_r = *src++; + unsigned char in_g = *src++; + unsigned char in_b = *src++; + unsigned char in_a = *src++; + + int x = transform->floor_cache[in_r]; + int y = transform->floor_cache[in_g]; + int z = transform->floor_cache[in_b]; + + int x_n = transform->ceil_cache[in_r]; + int y_n = transform->ceil_cache[in_g]; + int z_n = transform->ceil_cache[in_b]; + + float rx = transform->r_cache[in_r]; + float ry = transform->r_cache[in_g]; + float rz = transform->r_cache[in_b]; + + c0_r = CLU(r_table, x, y, z); + c0_g = CLU(g_table, x, y, z); + c0_b = CLU(b_table, x, y, z); + + if( rx >= ry ) { + if (ry >= rz) { //rx >= ry && ry >= rz + c1_r = CLU(r_table, x_n, y, z) - c0_r; + c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z); + c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); + c1_g = CLU(g_table, x_n, y, z) - c0_g; + c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z); + c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); + c1_b = CLU(b_table, x_n, y, z) - c0_b; + c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z); + c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); + } else { + if (rx >= rz) { //rx >= rz && rz >= ry + c1_r = CLU(r_table, x_n, y, z) - c0_r; + c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); + c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z); + c1_g = CLU(g_table, x_n, y, z) - c0_g; + c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); + c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z); + c1_b = CLU(b_table, x_n, y, z) - c0_b; + c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); + c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z); + } else { //rz > rx && rx >= ry + c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n); + c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); + c3_r = CLU(r_table, x, y, z_n) - c0_r; + c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n); + c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); + c3_g = CLU(g_table, x, y, z_n) - c0_g; + c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n); + c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); + c3_b = CLU(b_table, x, y, z_n) - c0_b; + } + } + } else { + if (rx >= rz) { //ry > rx && rx >= rz + c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z); + c2_r = CLU(r_table, x, y_n, z) - c0_r; + c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); + c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z); + c2_g = CLU(g_table, x, y_n, z) - c0_g; + c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); + c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z); + c2_b = CLU(b_table, x, y_n, z) - c0_b; + c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); + } else { + if (ry >= rz) { //ry >= rz && rz > rx + c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); + c2_r = CLU(r_table, x, y_n, z) - c0_r; + c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z); + c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); + c2_g = CLU(g_table, x, y_n, z) - c0_g; + c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z); + c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); + c2_b = CLU(b_table, x, y_n, z) - c0_b; + c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z); + } else { //rz > ry && ry > rx + c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); + c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n); + c3_r = CLU(r_table, x, y, z_n) - c0_r; + c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); + c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n); + c3_g = CLU(g_table, x, y, z_n) - c0_g; + c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); + c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n); + c3_b = CLU(b_table, x, y, z_n) - c0_b; + } + } + } + + clut_r = c0_r + c1_r*rx + c2_r*ry + c3_r*rz; + clut_g = c0_g + c1_g*rx + c2_g*ry + c3_g*rz; + clut_b = c0_b + c1_b*rx + c2_b*ry + c3_b*rz; + + dest[r_out] = clamp_u8(clut_r*255.0f); + dest[1] = clamp_u8(clut_g*255.0f); + dest[b_out] = clamp_u8(clut_b*255.0f); + dest[3] = in_a; + dest += 4; + } +} + +// Using lcms' tetra interpolation code. +static void qcms_transform_data_tetra_clut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + int xy_len = 1; + int x_len = transform->grid_size; + int len = x_len * x_len; + float* r_table = transform->r_clut; + float* g_table = transform->g_clut; + float* b_table = transform->b_clut; + float c0_r, c1_r, c2_r, c3_r; + float c0_g, c1_g, c2_g, c3_g; + float c0_b, c1_b, c2_b, c3_b; + float clut_r, clut_g, clut_b; + + if (!(transform->transform_flags & TRANSFORM_FLAG_CLUT_CACHE)) + qcms_transform_build_clut_cache(transform); + + for (i = 0; i < length; i++) { + unsigned char in_r = *src++; + unsigned char in_g = *src++; + unsigned char in_b = *src++; + + int x = transform->floor_cache[in_r]; + int y = transform->floor_cache[in_g]; + int z = transform->floor_cache[in_b]; + + int x_n = transform->ceil_cache[in_r]; + int y_n = transform->ceil_cache[in_g]; + int z_n = transform->ceil_cache[in_b]; + + float rx = transform->r_cache[in_r]; + float ry = transform->r_cache[in_g]; + float rz = transform->r_cache[in_b]; + + c0_r = CLU(r_table, x, y, z); + c0_g = CLU(g_table, x, y, z); + c0_b = CLU(b_table, x, y, z); + + if( rx >= ry ) { + if (ry >= rz) { //rx >= ry && ry >= rz + c1_r = CLU(r_table, x_n, y, z) - c0_r; + c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z); + c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); + c1_g = CLU(g_table, x_n, y, z) - c0_g; + c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z); + c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); + c1_b = CLU(b_table, x_n, y, z) - c0_b; + c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z); + c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); + } else { + if (rx >= rz) { //rx >= rz && rz >= ry + c1_r = CLU(r_table, x_n, y, z) - c0_r; + c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); + c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z); + c1_g = CLU(g_table, x_n, y, z) - c0_g; + c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); + c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z); + c1_b = CLU(b_table, x_n, y, z) - c0_b; + c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); + c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z); + } else { //rz > rx && rx >= ry + c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n); + c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n); + c3_r = CLU(r_table, x, y, z_n) - c0_r; + c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n); + c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n); + c3_g = CLU(g_table, x, y, z_n) - c0_g; + c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n); + c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n); + c3_b = CLU(b_table, x, y, z_n) - c0_b; + } + } + } else { + if (rx >= rz) { //ry > rx && rx >= rz + c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z); + c2_r = CLU(r_table, x, y_n, z) - c0_r; + c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z); + c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z); + c2_g = CLU(g_table, x, y_n, z) - c0_g; + c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z); + c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z); + c2_b = CLU(b_table, x, y_n, z) - c0_b; + c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z); + } else { + if (ry >= rz) { //ry >= rz && rz > rx + c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); + c2_r = CLU(r_table, x, y_n, z) - c0_r; + c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z); + c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); + c2_g = CLU(g_table, x, y_n, z) - c0_g; + c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z); + c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); + c2_b = CLU(b_table, x, y_n, z) - c0_b; + c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z); + } else { //rz > ry && ry > rx + c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n); + c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n); + c3_r = CLU(r_table, x, y, z_n) - c0_r; + c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n); + c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n); + c3_g = CLU(g_table, x, y, z_n) - c0_g; + c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n); + c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n); + c3_b = CLU(b_table, x, y, z_n) - c0_b; + } + } + } + + clut_r = c0_r + c1_r*rx + c2_r*ry + c3_r*rz; + clut_g = c0_g + c1_g*rx + c2_g*ry + c3_g*rz; + clut_b = c0_b + c1_b*rx + c2_b*ry + c3_b*rz; + + dest[r_out] = clamp_u8(clut_r*255.0f); + dest[1] = clamp_u8(clut_g*255.0f); + dest[b_out] = clamp_u8(clut_b*255.0f); + dest += 3; + } +} + +static void qcms_transform_data_rgb_out_lut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + float (*mat)[4] = transform->matrix; + for (i = 0; i < length; i++) { + unsigned char device_r = *src++; + unsigned char device_g = *src++; + unsigned char device_b = *src++; + float out_device_r, out_device_g, out_device_b; + + float linear_r = transform->input_gamma_table_r[device_r]; + float linear_g = transform->input_gamma_table_g[device_g]; + float linear_b = transform->input_gamma_table_b[device_b]; + + float out_linear_r = mat[0][0]*linear_r + mat[1][0]*linear_g + mat[2][0]*linear_b; + float out_linear_g = mat[0][1]*linear_r + mat[1][1]*linear_g + mat[2][1]*linear_b; + float out_linear_b = mat[0][2]*linear_r + mat[1][2]*linear_g + mat[2][2]*linear_b; + + out_linear_r = clamp_float(out_linear_r); + out_linear_g = clamp_float(out_linear_g); + out_linear_b = clamp_float(out_linear_b); + + out_device_r = lut_interp_linear(out_linear_r, + transform->output_gamma_lut_r, transform->output_gamma_lut_r_length); + out_device_g = lut_interp_linear(out_linear_g, + transform->output_gamma_lut_g, transform->output_gamma_lut_g_length); + out_device_b = lut_interp_linear(out_linear_b, + transform->output_gamma_lut_b, transform->output_gamma_lut_b_length); + + dest[r_out] = clamp_u8(out_device_r*255); + dest[1] = clamp_u8(out_device_g*255); + dest[b_out] = clamp_u8(out_device_b*255); + dest += 3; + } +} + +static void qcms_transform_data_rgba_out_lut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + unsigned int i; + float (*mat)[4] = transform->matrix; + for (i = 0; i < length; i++) { + unsigned char device_r = *src++; + unsigned char device_g = *src++; + unsigned char device_b = *src++; + unsigned char alpha = *src++; + float out_device_r, out_device_g, out_device_b; + + float linear_r = transform->input_gamma_table_r[device_r]; + float linear_g = transform->input_gamma_table_g[device_g]; + float linear_b = transform->input_gamma_table_b[device_b]; + + float out_linear_r = mat[0][0]*linear_r + mat[1][0]*linear_g + mat[2][0]*linear_b; + float out_linear_g = mat[0][1]*linear_r + mat[1][1]*linear_g + mat[2][1]*linear_b; + float out_linear_b = mat[0][2]*linear_r + mat[1][2]*linear_g + mat[2][2]*linear_b; + + out_linear_r = clamp_float(out_linear_r); + out_linear_g = clamp_float(out_linear_g); + out_linear_b = clamp_float(out_linear_b); + + out_device_r = lut_interp_linear(out_linear_r, + transform->output_gamma_lut_r, transform->output_gamma_lut_r_length); + out_device_g = lut_interp_linear(out_linear_g, + transform->output_gamma_lut_g, transform->output_gamma_lut_g_length); + out_device_b = lut_interp_linear(out_linear_b, + transform->output_gamma_lut_b, transform->output_gamma_lut_b_length); + + dest[r_out] = clamp_u8(out_device_r*255); + dest[1] = clamp_u8(out_device_g*255); + dest[b_out] = clamp_u8(out_device_b*255); + dest[3] = alpha; + dest += 4; + } +} + +#if 0 +static void qcms_transform_data_rgb_out_linear(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length, qcms_format_type output_format) +{ + const int r_out = output_format.r; + const int b_out = output_format.b; + + int i; + float (*mat)[4] = transform->matrix; + for (i = 0; i < length; i++) { + unsigned char device_r = *src++; + unsigned char device_g = *src++; + unsigned char device_b = *src++; + + float linear_r = transform->input_gamma_table_r[device_r]; + float linear_g = transform->input_gamma_table_g[device_g]; + float linear_b = transform->input_gamma_table_b[device_b]; + + float out_linear_r = mat[0][0]*linear_r + mat[1][0]*linear_g + mat[2][0]*linear_b; + float out_linear_g = mat[0][1]*linear_r + mat[1][1]*linear_g + mat[2][1]*linear_b; + float out_linear_b = mat[0][2]*linear_r + mat[1][2]*linear_g + mat[2][2]*linear_b; + + dest[r_out] = clamp_u8(out_linear_r*255); + dest[1] = clamp_u8(out_linear_g*255); + dest[b_out] = clamp_u8(out_linear_b*255); + dest += 3; + } +} +#endif + +/* + * If users create and destroy objects on different threads, even if the same + * objects aren't used on different threads at the same time, we can still run + * in to trouble with refcounts if they aren't atomic. + * + * This can lead to us prematurely deleting the precache if threads get unlucky + * and write the wrong value to the ref count. + */ +static struct precache_output *precache_reference(struct precache_output *p) +{ + qcms_atomic_increment(p->ref_count); + return p; +} + +static struct precache_output *precache_create() +{ + struct precache_output *p = malloc(sizeof(struct precache_output)); + if (p) + p->ref_count = 1; + return p; +} + +void precache_release(struct precache_output *p) +{ + if (qcms_atomic_decrement(p->ref_count) == 0) { + free(p); + } +} + +#ifdef HAVE_POSIX_MEMALIGN +static qcms_transform *transform_alloc(void) +{ + qcms_transform *t; + if (!posix_memalign(&t, 16, sizeof(*t))) { + return t; + } else { + return NULL; + } +} +static void transform_free(qcms_transform *t) +{ + free(t); +} +#else +static qcms_transform *transform_alloc(void) +{ + /* transform needs to be aligned on a 16byte boundrary */ + char *original_block = calloc(sizeof(qcms_transform) + sizeof(void*) + 16, 1); + /* make room for a pointer to the block returned by calloc */ + void *transform_start = original_block + sizeof(void*); + /* align transform_start */ + qcms_transform *transform_aligned = (qcms_transform*)(((uintptr_t)transform_start + 15) & ~0xf); + + /* store a pointer to the block returned by calloc so that we can free it later */ + void **(original_block_ptr) = (void**)transform_aligned; + if (!original_block) + return NULL; + original_block_ptr--; + *original_block_ptr = original_block; + + return transform_aligned; +} +static void transform_free(qcms_transform *t) +{ + /* get at the pointer to the unaligned block returned by calloc */ + void **p = (void**)t; + p--; + free(*p); +} +#endif + +void qcms_transform_release(qcms_transform *t) +{ + /* ensure we only free the gamma tables once even if there are + * multiple references to the same data */ + + if (t->output_table_r) + precache_release(t->output_table_r); + if (t->output_table_g) + precache_release(t->output_table_g); + if (t->output_table_b) + precache_release(t->output_table_b); + + free(t->input_gamma_table_r); + if (t->input_gamma_table_g != t->input_gamma_table_r) + free(t->input_gamma_table_g); + if (t->input_gamma_table_g != t->input_gamma_table_r && + t->input_gamma_table_g != t->input_gamma_table_b) + free(t->input_gamma_table_b); + + free(t->input_gamma_table_gray); + + free(t->output_gamma_lut_r); + free(t->output_gamma_lut_g); + free(t->output_gamma_lut_b); + + transform_free(t); +} + +#ifdef X86 +// Determine if we can build with SSE2 (this was partly copied from jmorecfg.h in +// mozilla/jpeg) + // ------------------------------------------------------------------------- +#if defined(_M_IX86) && defined(_MSC_VER) +#define HAS_CPUID +/* Get us a CPUID function. Avoid clobbering EBX because sometimes it's the PIC + register - I'm not sure if that ever happens on windows, but cpuid isn't + on the critical path so we just preserve the register to be safe and to be + consistent with the non-windows version. */ +static void cpuid(uint32_t fxn, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d) { + uint32_t a_, b_, c_, d_; + __asm { + xchg ebx, esi + mov eax, fxn + cpuid + mov a_, eax + mov b_, ebx + mov c_, ecx + mov d_, edx + xchg ebx, esi + } + *a = a_; + *b = b_; + *c = c_; + *d = d_; +} +#elif (defined(__GNUC__) || defined(__SUNPRO_C)) && (defined(__i386__) || defined(__i386)) +#define HAS_CPUID +/* Get us a CPUID function. We can't use ebx because it's the PIC register on + some platforms, so we use ESI instead and save ebx to avoid clobbering it. */ +static void cpuid(uint32_t fxn, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d) { + + uint32_t a_, b_, c_, d_; + __asm__ __volatile__ ("xchgl %%ebx, %%esi; cpuid; xchgl %%ebx, %%esi;" + : "=a" (a_), "=S" (b_), "=c" (c_), "=d" (d_) : "a" (fxn)); + *a = a_; + *b = b_; + *c = c_; + *d = d_; +} +#endif + +// -------------------------Runtime SSEx Detection----------------------------- + +/* MMX is always supported per + * Gecko v1.9.1 minimum CPU requirements */ +#define SSE1_EDX_MASK (1UL << 25) +#define SSE2_EDX_MASK (1UL << 26) +#define SSE3_ECX_MASK (1UL << 0) + +static int sse_version_available(void) +{ +#if defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) + /* we know at build time that 64-bit CPUs always have SSE2 + * this tells the compiler that non-SSE2 branches will never be + * taken (i.e. OK to optimze away the SSE1 and non-SIMD code */ + return 2; +#elif defined(HAS_CPUID) + static int sse_version = -1; + uint32_t a, b, c, d; + uint32_t function = 0x00000001; + + if (sse_version == -1) { + sse_version = 0; + cpuid(function, &a, &b, &c, &d); + if (c & SSE3_ECX_MASK) + sse_version = 3; + else if (d & SSE2_EDX_MASK) + sse_version = 2; + else if (d & SSE1_EDX_MASK) + sse_version = 1; + } + + return sse_version; +#else + return 0; +#endif +} +#endif + +static const struct matrix bradford_matrix = {{ { 0.8951f, 0.2664f,-0.1614f}, + {-0.7502f, 1.7135f, 0.0367f}, + { 0.0389f,-0.0685f, 1.0296f}}, + false}; + +static const struct matrix bradford_matrix_inv = {{ { 0.9869929f,-0.1470543f, 0.1599627f}, + { 0.4323053f, 0.5183603f, 0.0492912f}, + {-0.0085287f, 0.0400428f, 0.9684867f}}, + false}; + +// See ICCv4 E.3 +struct matrix compute_whitepoint_adaption(float X, float Y, float Z) { + float p = (0.96422f*bradford_matrix.m[0][0] + 1.000f*bradford_matrix.m[1][0] + 0.82521f*bradford_matrix.m[2][0]) / + (X*bradford_matrix.m[0][0] + Y*bradford_matrix.m[1][0] + Z*bradford_matrix.m[2][0] ); + float y = (0.96422f*bradford_matrix.m[0][1] + 1.000f*bradford_matrix.m[1][1] + 0.82521f*bradford_matrix.m[2][1]) / + (X*bradford_matrix.m[0][1] + Y*bradford_matrix.m[1][1] + Z*bradford_matrix.m[2][1] ); + float b = (0.96422f*bradford_matrix.m[0][2] + 1.000f*bradford_matrix.m[1][2] + 0.82521f*bradford_matrix.m[2][2]) / + (X*bradford_matrix.m[0][2] + Y*bradford_matrix.m[1][2] + Z*bradford_matrix.m[2][2] ); + struct matrix white_adaption = {{ {p,0,0}, {0,y,0}, {0,0,b}}, false}; + return matrix_multiply( bradford_matrix_inv, matrix_multiply(white_adaption, bradford_matrix) ); +} + +void qcms_profile_precache_output_transform(qcms_profile *profile) +{ + /* we only support precaching on rgb profiles */ + if (profile->color_space != RGB_SIGNATURE) + return; + + if (qcms_supports_iccv4) { + /* don't precache since we will use the B2A LUT */ + if (profile->B2A0) + return; + + /* don't precache since we will use the mBA LUT */ + if (profile->mBA) + return; + } + + /* don't precache if we do not have the TRC curves */ + if (!profile->redTRC || !profile->greenTRC || !profile->blueTRC) + return; + + if (!profile->output_table_r) { + profile->output_table_r = precache_create(); + if (profile->output_table_r && + !compute_precache(profile->redTRC, profile->output_table_r->data)) { + precache_release(profile->output_table_r); + profile->output_table_r = NULL; + } + } + if (!profile->output_table_g) { + profile->output_table_g = precache_create(); + if (profile->output_table_g && + !compute_precache(profile->greenTRC, profile->output_table_g->data)) { + precache_release(profile->output_table_g); + profile->output_table_g = NULL; + } + } + if (!profile->output_table_b) { + profile->output_table_b = precache_create(); + if (profile->output_table_b && + !compute_precache(profile->blueTRC, profile->output_table_b->data)) { + precache_release(profile->output_table_b); + profile->output_table_b = NULL; + } + } +} + +/* Replace the current transformation with a LUT transformation using a given number of sample points */ +qcms_transform* qcms_transform_precacheLUT_float(qcms_transform *transform, qcms_profile *in, qcms_profile *out, + int samples, qcms_data_type in_type) +{ + /* The range between which 2 consecutive sample points can be used to interpolate */ + uint16_t x,y,z; + uint32_t l; + uint32_t lutSize = 3 * samples * samples * samples; + float* src = NULL; + float* dest = NULL; + float* lut = NULL; + float inverse; + + src = malloc(lutSize*sizeof(float)); + dest = malloc(lutSize*sizeof(float)); + + if (src && dest) { + /* Prepare a list of points we want to sample: x, y, z order */ + l = 0; + inverse = 1 / (float)(samples-1); + for (x = 0; x < samples; x++) { + for (y = 0; y < samples; y++) { + for (z = 0; z < samples; z++) { + src[l++] = x * inverse; // r + src[l++] = y * inverse; // g + src[l++] = z * inverse; // b + } + } + } + + lut = qcms_chain_transform(in, out, src, dest, lutSize); + + if (lut) { + transform->r_clut = &lut[0]; // r + transform->g_clut = &lut[1]; // g + transform->b_clut = &lut[2]; // b + transform->grid_size = samples; + + if (in_type == QCMS_DATA_RGBA_8) { +#if defined(SSE2_ENABLE) + if (sse_version_available() >= 2) { + transform->transform_fn = qcms_transform_data_tetra_clut_rgba_sse2; + } else { + transform->transform_fn = qcms_transform_data_tetra_clut_rgba; + } +#else + transform->transform_fn = qcms_transform_data_tetra_clut_rgba; +#endif + } else { + transform->transform_fn = qcms_transform_data_tetra_clut; + } + } + } + + // XXX: qcms_modular_transform_data may return the lut in either the src or the + // dest buffer. If so, it must not be free-ed. + if (src && lut != src) { + free(src); + } + if (dest && lut != dest) { + free(dest); + } + + if (lut == NULL) { + return NULL; + } + return transform; +} + +/* Create a transform LUT using the given number of sample points. The transform LUT data is stored + in the output (cube) in bgra format in zyx sample order. */ +qcms_bool qcms_transform_create_LUT_zyx_bgra(qcms_profile *in, qcms_profile *out, qcms_intent intent, + int samples, unsigned char* cube) +{ + uint16_t z,y,x; + uint32_t l,index; + uint32_t lutSize = 3 * samples * samples * samples; + + float* src = NULL; + float* dest = NULL; + float* lut = NULL; + float inverse; + + src = malloc(lutSize*sizeof(float)); + dest = malloc(lutSize*sizeof(float)); + + if (src && dest) { + /* Prepare a list of points we want to sample: z, y, x order */ + l = 0; + inverse = 1 / (float)(samples-1); + for (z = 0; z < samples; z++) { + for (y = 0; y < samples; y++) { + for (x = 0; x < samples; x++) { + src[l++] = x * inverse; // r + src[l++] = y * inverse; // g + src[l++] = z * inverse; // b + } + } + } + + lut = qcms_chain_transform(in, out, src, dest, lutSize); + + if (lut) { + index = l = 0; + for (z = 0; z < samples; z++) { + for (y = 0; y < samples; y++) { + for (x = 0; x < samples; x++) { + cube[index++] = (int)floorf(lut[l + 2] * 255.0f + 0.5f); // b + cube[index++] = (int)floorf(lut[l + 1] * 255.0f + 0.5f); // g + cube[index++] = (int)floorf(lut[l + 0] * 255.0f + 0.5f); // r + cube[index++] = 255; // a + l += 3; + } + } + } + } + } + + // XXX: qcms_modular_transform_data may return the lut data in either the src or + // dest buffer so free src, dest, and lut with care. + + if (src && lut != src) + free(src); + if (dest && lut != dest) + free(dest); + + if (lut) { + free(lut); + return true; + } + + return false; +} + +void qcms_transform_build_clut_cache(qcms_transform* transform) { + const int grid_factor = transform->grid_size - 1; + const float grid_scaled = (1.0f / 255.0f) * grid_factor; + int i; + +#define div_255_ceiling(value) (((value) + 254) / 255) + + for (i = 0; i < 256; i++) { + transform->ceil_cache[i] = div_255_ceiling(i * grid_factor); + transform->floor_cache[i] = i * grid_factor / 255; + transform->r_cache[i] = (i * grid_scaled) - transform->floor_cache[i]; + } + +#undef div_255_ceil + + transform->transform_flags |= TRANSFORM_FLAG_CLUT_CACHE; +} + +#define NO_MEM_TRANSFORM NULL + +qcms_transform* qcms_transform_create( + qcms_profile *in, qcms_data_type in_type, + qcms_profile *out, qcms_data_type out_type, + qcms_intent intent) +{ + qcms_transform *transform = NULL; + bool precache = false; + int i, j; + + transform = transform_alloc(); + if (!transform) { + return NULL; + } + + if (out_type != QCMS_DATA_RGB_8 && out_type != QCMS_DATA_RGBA_8) { + assert(0 && "output type"); + qcms_transform_release(transform); + return NULL; + } + + transform->transform_flags = 0; + + if (out->output_table_r && out->output_table_g && out->output_table_b) { + precache = true; + } + + if (qcms_supports_iccv4 && (in->A2B0 || out->B2A0 || in->mAB || out->mAB)) { + // Precache the transformation to a CLUT 33x33x33 in size. + // 33 is used by many profiles and works well in practice. + // This evenly divides 256 into blocks of 8x8x8. + // TODO For transforming small data sets of about 200x200 or less + // precaching should be avoided. + qcms_transform *result = qcms_transform_precacheLUT_float(transform, in, out, 33, in_type); + if (!result) { + assert(0 && "precacheLUT failed"); + qcms_transform_release(transform); + return NULL; + } + return result; + } + + /* A matrix-based transform will be selected: check that the PCS + of the input/output profiles are the same, crbug.com/5120682 */ + if (in->pcs != out->pcs) { + qcms_transform_release(transform); + return NULL; + } + + if (precache) { + transform->output_table_r = precache_reference(out->output_table_r); + transform->output_table_g = precache_reference(out->output_table_g); + transform->output_table_b = precache_reference(out->output_table_b); + } else { + if (!out->redTRC || !out->greenTRC || !out->blueTRC) { + qcms_transform_release(transform); + return NO_MEM_TRANSFORM; + } + + build_output_lut(out->redTRC, &transform->output_gamma_lut_r, &transform->output_gamma_lut_r_length); + build_output_lut(out->greenTRC, &transform->output_gamma_lut_g, &transform->output_gamma_lut_g_length); + build_output_lut(out->blueTRC, &transform->output_gamma_lut_b, &transform->output_gamma_lut_b_length); + + if (!transform->output_gamma_lut_r || !transform->output_gamma_lut_g || !transform->output_gamma_lut_b) { + qcms_transform_release(transform); + return NO_MEM_TRANSFORM; + } + } + + if (in->color_space == RGB_SIGNATURE) { + struct matrix in_matrix, out_matrix, result; + + if (in_type != QCMS_DATA_RGB_8 && in_type != QCMS_DATA_RGBA_8) { + assert(0 && "input type"); + qcms_transform_release(transform); + return NULL; + } + + if (precache) { +#if defined(SSE2_ENABLE) + if (sse_version_available() >= 2) { + if (in_type == QCMS_DATA_RGB_8) + transform->transform_fn = qcms_transform_data_rgb_out_lut_sse2; + else + transform->transform_fn = qcms_transform_data_rgba_out_lut_sse2; + } else +#endif + { + if (in_type == QCMS_DATA_RGB_8) + transform->transform_fn = qcms_transform_data_rgb_out_lut_precache; + else + transform->transform_fn = qcms_transform_data_rgba_out_lut_precache; + } + } else { + if (in_type == QCMS_DATA_RGB_8) + transform->transform_fn = qcms_transform_data_rgb_out_lut; + else + transform->transform_fn = qcms_transform_data_rgba_out_lut; + } + + //XXX: avoid duplicating tables if we can + transform->input_gamma_table_r = build_input_gamma_table(in->redTRC); + transform->input_gamma_table_g = build_input_gamma_table(in->greenTRC); + transform->input_gamma_table_b = build_input_gamma_table(in->blueTRC); + + if (!transform->input_gamma_table_r || !transform->input_gamma_table_g || !transform->input_gamma_table_b) { + qcms_transform_release(transform); + return NO_MEM_TRANSFORM; + } + + /* build combined colorant matrix */ + in_matrix = build_colorant_matrix(in); + out_matrix = build_colorant_matrix(out); + out_matrix = matrix_invert(out_matrix); + if (out_matrix.invalid) { + qcms_transform_release(transform); + return NULL; + } + result = matrix_multiply(out_matrix, in_matrix); + + /* check for NaN values in the matrix and bail if we find any + see also https://bugzilla.mozilla.org/show_bug.cgi?id=1170316 */ + for (i = 0 ; i < 3 ; ++i) { + for (j = 0 ; j < 3 ; ++j) { + if (result.m[i][j] != result.m[i][j]) { + qcms_transform_release(transform); + return NULL; + } + } + } + + /* store the results in column major mode + * this makes doing the multiplication with sse easier */ + transform->matrix[0][0] = result.m[0][0]; + transform->matrix[1][0] = result.m[0][1]; + transform->matrix[2][0] = result.m[0][2]; + transform->matrix[0][1] = result.m[1][0]; + transform->matrix[1][1] = result.m[1][1]; + transform->matrix[2][1] = result.m[1][2]; + transform->matrix[0][2] = result.m[2][0]; + transform->matrix[1][2] = result.m[2][1]; + transform->matrix[2][2] = result.m[2][2]; + + /* Flag transform as matrix. */ + transform->transform_flags |= TRANSFORM_FLAG_MATRIX; + + } else if (in->color_space == GRAY_SIGNATURE) { + if (in_type != QCMS_DATA_GRAY_8 && in_type != QCMS_DATA_GRAYA_8) { + assert(0 && "input type"); + qcms_transform_release(transform); + return NULL; + } + + transform->input_gamma_table_gray = build_input_gamma_table(in->grayTRC); + + if (!transform->input_gamma_table_gray) { + qcms_transform_release(transform); + return NO_MEM_TRANSFORM; + } + + if (precache) { + if (in_type == QCMS_DATA_GRAY_8) { + transform->transform_fn = qcms_transform_data_gray_out_precache; + } else { + transform->transform_fn = qcms_transform_data_graya_out_precache; + } + } else { + if (in_type == QCMS_DATA_GRAY_8) { + transform->transform_fn = qcms_transform_data_gray_out_lut; + } else { + transform->transform_fn = qcms_transform_data_graya_out_lut; + } + } + } else { + assert(0 && "unexpected colorspace"); + qcms_transform_release(transform); + return NULL; + } + + return transform; +} + +/* __force_align_arg_pointer__ is an x86-only attribute, and gcc/clang warns on unused + * attributes. Don't use this on ARM or AMD64. __has_attribute can detect the presence + * of the attribute but is currently only supported by clang */ +#if defined(__has_attribute) +#define HAS_FORCE_ALIGN_ARG_POINTER __has_attribute(__force_align_arg_pointer__) +#elif defined(__GNUC__) && defined(__i386__) +#define HAS_FORCE_ALIGN_ARG_POINTER 1 +#else +#define HAS_FORCE_ALIGN_ARG_POINTER 0 +#endif + +#if HAS_FORCE_ALIGN_ARG_POINTER +/* we need this to avoid crashes when gcc assumes the stack is 128bit aligned */ +__attribute__((__force_align_arg_pointer__)) +#endif +void qcms_transform_data(qcms_transform *transform, void *src, void *dest, size_t length) +{ + static const struct _qcms_format_type output_rgbx = { 0, 2 }; + + transform->transform_fn(transform, src, dest, length, output_rgbx); +} + +void qcms_transform_data_type(qcms_transform *transform, void *src, void *dest, size_t length, qcms_output_type type) +{ + static const struct _qcms_format_type output_rgbx = { 0, 2 }; + static const struct _qcms_format_type output_bgrx = { 2, 0 }; + + transform->transform_fn(transform, src, dest, length, type == QCMS_OUTPUT_BGRX ? output_bgrx : output_rgbx); +} + +#define ENABLE_ICC_V4_PROFILE_SUPPORT false + +qcms_bool qcms_supports_iccv4 = ENABLE_ICC_V4_PROFILE_SUPPORT; + +void qcms_enable_iccv4() +{ + qcms_supports_iccv4 = true; +} + +static inline qcms_bool transform_is_matrix(qcms_transform *t) +{ + return (t->transform_flags & TRANSFORM_FLAG_MATRIX) ? true : false; +} + +qcms_bool qcms_transform_is_matrix(qcms_transform *t) +{ + return transform_is_matrix(t); +} + +float qcms_transform_get_matrix(qcms_transform *t, unsigned i, unsigned j) +{ + assert(transform_is_matrix(t) && i < 3 && j < 3); + + // Return transform matrix element in row major order (permute i and j) + + return t->matrix[j][i]; +} + +static inline qcms_bool supported_trc_type(qcms_trc_type type) +{ + return (type == QCMS_TRC_HALF_FLOAT || type == QCMS_TRC_USHORT); +} + +const uint16_t half_float_one = 0x3c00; + +size_t qcms_transform_get_input_trc_rgba(qcms_transform *t, qcms_profile *in, qcms_trc_type type, unsigned short *data) +{ + const size_t size = 256; // The input gamma tables always have 256 entries. + + size_t i; + + if (in->color_space != RGB_SIGNATURE || !supported_trc_type(type)) + return 0; + + // qcms_profile *in is assumed to be the profile on the input-side of the color transform t. + // When a transform is created, the input gamma curve data is stored in the transform ... + + if (!t->input_gamma_table_r || !t->input_gamma_table_g || !t->input_gamma_table_b) + return 0; + + // Report the size if no output data is requested. This allows callers to first work out the + // the curve size, then provide allocated memory sufficient to store the curve rgba data. + + if (!data) + return size; + + switch(type) { + case QCMS_TRC_HALF_FLOAT: + for (i = 0; i < size; ++i) { + *data++ = float_to_half_float(t->input_gamma_table_r[i]); // r + *data++ = float_to_half_float(t->input_gamma_table_g[i]); // g + *data++ = float_to_half_float(t->input_gamma_table_b[i]); // b + *data++ = half_float_one; // a + } + break; + case QCMS_TRC_USHORT: + for (i = 0; i < size; ++i) { + *data++ = roundf(t->input_gamma_table_r[i] * 65535.0); // r + *data++ = roundf(t->input_gamma_table_g[i] * 65535.0); // g + *data++ = roundf(t->input_gamma_table_b[i] * 65535.0); // b + *data++ = 65535; // a + } + break; + default: + /* should not be reached */ + assert(0); + } + + return size; +} + +const float inverse65535 = (float) (1.0 / 65535.0); + +size_t qcms_transform_get_output_trc_rgba(qcms_transform *t, qcms_profile *out, qcms_trc_type type, unsigned short *data) +{ + size_t size, i; + + if (out->color_space != RGB_SIGNATURE || !supported_trc_type(type)) + return 0; + + // qcms_profile *out is assumed to be the profile on the output-side of the transform t. + // If the transform output gamma curves need building, do that. They're usually built when + // the transform was created, but sometimes not due to the output gamma precache ... + + if (!out->redTRC || !out->greenTRC || !out->blueTRC) + return 0; + if (!t->output_gamma_lut_r) + build_output_lut(out->redTRC, &t->output_gamma_lut_r, &t->output_gamma_lut_r_length); + if (!t->output_gamma_lut_g) + build_output_lut(out->greenTRC, &t->output_gamma_lut_g, &t->output_gamma_lut_g_length); + if (!t->output_gamma_lut_b) + build_output_lut(out->blueTRC, &t->output_gamma_lut_b, &t->output_gamma_lut_b_length); + + if (!t->output_gamma_lut_r || !t->output_gamma_lut_g || !t->output_gamma_lut_b) + return 0; + + // Output gamma tables should have the same size and should have 4096 entries at most (the + // minimum is 256). Larger tables are rare and ignored here: fail by returning 0. + + size = t->output_gamma_lut_r_length; + if (size != t->output_gamma_lut_g_length) + return 0; + if (size != t->output_gamma_lut_b_length) + return 0; + if (size < 256 || size > 4096) + return 0; + + // Report the size if no output data is requested. This allows callers to first work out the + // the curve size, then provide allocated memory sufficient to store the curve rgba data. + + if (!data) + return size; + + switch (type) { + case QCMS_TRC_HALF_FLOAT: + for (i = 0; i < size; ++i) { + *data++ = float_to_half_float(t->output_gamma_lut_r[i] * inverse65535); // r + *data++ = float_to_half_float(t->output_gamma_lut_g[i] * inverse65535); // g + *data++ = float_to_half_float(t->output_gamma_lut_b[i] * inverse65535); // b + *data++ = half_float_one; // a + } + break; + case QCMS_TRC_USHORT: + for (i = 0; i < size; ++i) { + *data++ = t->output_gamma_lut_r[i]; // r + *data++ = t->output_gamma_lut_g[i]; // g + *data++ = t->output_gamma_lut_b[i]; // b + *data++ = 65535; // a + } + break; + default: + /* should not be reached */ + assert(0); + } + + return size; +} diff --git a/third_party/qcms/src/transform_util.c b/third_party/qcms/src/transform_util.c new file mode 100644 index 0000000000..e60744742c --- /dev/null +++ b/third_party/qcms/src/transform_util.c @@ -0,0 +1,639 @@ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// +// 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. + +#define _ISOC99_SOURCE /* for INFINITY */ + +#include <math.h> +#include <assert.h> +#include <string.h> //memcpy +#include "qcmsint.h" +#include "transform_util.h" +#include "matrix.h" + +#if !defined(INFINITY) +#define INFINITY HUGE_VAL +#endif + +#define PARAMETRIC_CURVE_TYPE 0x70617261 //'para' + +/* value must be a value between 0 and 1 */ +//XXX: is the above a good restriction to have? +// the output range of this function is 0..1 +float lut_interp_linear(double input_value, uint16_t *table, size_t length) +{ + int upper, lower; + float value; + input_value = input_value * (length - 1); // scale to length of the array + upper = ceil(input_value); + lower = floor(input_value); + //XXX: can we be more performant here? + value = table[upper]*(1. - (upper - input_value)) + table[lower]*(upper - input_value); + /* scale the value */ + return value * (1.f/65535.f); +} + +/* same as above but takes and returns a uint16_t value representing a range from 0..1 */ +uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, size_t length) +{ + /* Start scaling input_value to the length of the array: 65535*(length-1). + * We'll divide out the 65535 next */ + uintptr_t value = (input_value * (length - 1)); + uint32_t upper = (value + 65534) / 65535; /* equivalent to ceil(value/65535) */ + uint32_t lower = value / 65535; /* equivalent to floor(value/65535) */ + /* interp is the distance from upper to value scaled to 0..65535 */ + uint32_t interp = value % 65535; + + value = (table[upper]*(interp) + table[lower]*(65535 - interp))/65535; // 0..65535*65535 + + return value; +} + +/* same as above but takes an input_value from 0..PRECACHE_OUTPUT_MAX + * and returns a uint8_t value representing a range from 0..1 */ +static +uint8_t lut_interp_linear_precache_output(uint32_t input_value, uint16_t *table, size_t length) +{ + /* Start scaling input_value to the length of the array: PRECACHE_OUTPUT_MAX*(length-1). + * We'll divide out the PRECACHE_OUTPUT_MAX next */ + uintptr_t value = (input_value * (length - 1)); + + /* equivalent to ceil(value/PRECACHE_OUTPUT_MAX) */ + uint32_t upper = (value + PRECACHE_OUTPUT_MAX-1) / PRECACHE_OUTPUT_MAX; + /* equivalent to floor(value/PRECACHE_OUTPUT_MAX) */ + uint32_t lower = value / PRECACHE_OUTPUT_MAX; + /* interp is the distance from upper to value scaled to 0..PRECACHE_OUTPUT_MAX */ + uint32_t interp = value % PRECACHE_OUTPUT_MAX; + + /* the table values range from 0..65535 */ + value = (table[upper]*(interp) + table[lower]*(PRECACHE_OUTPUT_MAX - interp)); // 0..(65535*PRECACHE_OUTPUT_MAX) + + /* round and scale */ + value += (PRECACHE_OUTPUT_MAX*65535/255)/2; + value /= (PRECACHE_OUTPUT_MAX*65535/255); // scale to 0..255 + return value; +} + +/* value must be a value between 0 and 1 */ +//XXX: is the above a good restriction to have? +float lut_interp_linear_float(float value, float *table, size_t length) +{ + int upper, lower; + value = value * (length - 1); + upper = ceil(value); + lower = floor(value); + //XXX: can we be more performant here? + value = table[upper]*(1. - (upper - value)) + table[lower]*(upper - value); + /* scale the value */ + return value; +} + +#if 0 +/* if we use a different representation i.e. one that goes from 0 to 0x1000 we can be more efficient + * because we can avoid the divisions and use a shifting instead */ +/* same as above but takes and returns a uint16_t value representing a range from 0..1 */ +uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, int length) +{ + uint32_t value = (input_value * (length - 1)); + uint32_t upper = (value + 4095) / 4096; /* equivalent to ceil(value/4096) */ + uint32_t lower = value / 4096; /* equivalent to floor(value/4096) */ + uint32_t interp = value % 4096; + + value = (table[upper]*(interp) + table[lower]*(4096 - interp))/4096; // 0..4096*4096 + + return value; +} +#endif + +void compute_curve_gamma_table_type1(float gamma_table[256], uint16_t gamma) +{ + unsigned int i; + float gamma_float = u8Fixed8Number_to_float(gamma); + for (i = 0; i < 256; i++) { + // 0..1^(0..255 + 255/256) will always be between 0 and 1 + gamma_table[i] = pow(i/255., gamma_float); + } +} + +void compute_curve_gamma_table_type2(float gamma_table[256], uint16_t *table, size_t length) +{ + unsigned int i; + for (i = 0; i < 256; i++) { + gamma_table[i] = lut_interp_linear(i/255., table, length); + } +} + +void compute_curve_gamma_table_type_parametric(float gamma_table[256], float parameter[7], int count) +{ + size_t X; + float interval; + float a, b, c, e, f; + float y = parameter[0]; + if (count == 0) { + a = 1; + b = 0; + c = 0; + e = 0; + f = 0; + interval = -INFINITY; + } else if(count == 1) { + a = parameter[1]; + b = parameter[2]; + c = 0; + e = 0; + f = 0; + interval = -1 * parameter[2] / parameter[1]; + } else if(count == 2) { + a = parameter[1]; + b = parameter[2]; + c = 0; + e = parameter[3]; + f = parameter[3]; + interval = -1 * parameter[2] / parameter[1]; + } else if(count == 3) { + a = parameter[1]; + b = parameter[2]; + c = parameter[3]; + e = -c; + f = 0; + interval = parameter[4]; + } else if(count == 4) { + a = parameter[1]; + b = parameter[2]; + c = parameter[3]; + e = parameter[5] - c; + f = parameter[6]; + interval = parameter[4]; + } else { + assert(0 && "invalid parametric function type."); + a = 1; + b = 0; + c = 0; + e = 0; + f = 0; + interval = -INFINITY; + } + for (X = 0; X < 256; X++) { + float x = X / 255.0; + if (x >= interval) { + // XXX The equations are not exactly as definied in the spec but are + // algebraic equivilent. + // TODO Should division by 255 be for the whole expression. + gamma_table[X] = clamp_float(pow(a * x + b, y) + c + e); + } else { + gamma_table[X] = clamp_float(c * x + f); + } + } +} + +void compute_curve_gamma_table_type0(float gamma_table[256]) +{ + unsigned int i; + for (i = 0; i < 256; i++) { + gamma_table[i] = i/255.; + } +} + +float clamp_float(float a) +{ + /* One would naturally write this function as the following: + if (a > 1.) + return 1.; + else if (a < 0) + return 0; + else + return a; + + However, that version will let NaNs pass through which is undesirable + for most consumers. + */ + + if (a > 1.) + return 1.; + else if (a >= 0) + return a; + else // a < 0 or a is NaN + return 0; +} + +unsigned char clamp_u8(float v) +{ + if (v > 255.) + return 255; + else if (v < 0) + return 0; + else + return floor(v+.5); +} + +float u8Fixed8Number_to_float(uint16_t x) +{ + // 0x0000 = 0. + // 0x0100 = 1. + // 0xffff = 255 + 255/256 + return x/256.; +} + +/* The SSE2 code uses min & max which let NaNs pass through. + We want to try to prevent that here by ensuring that + gamma table is within expected values. */ +void validate_gamma_table(float gamma_table[256]) +{ + int i; + for (i = 0; i < 256; i++) { + // Note: we check that the gamma is not in range + // instead of out of range so that we catch NaNs + if (!(gamma_table[i] >= 0.f && gamma_table[i] <= 1.f)) { + gamma_table[i] = 0.f; + } + } +} + +float *build_input_gamma_table(struct curveType *TRC) +{ + float *gamma_table; + + if (!TRC) return NULL; + gamma_table = malloc(sizeof(float)*256); + if (gamma_table) { + if (TRC->type == PARAMETRIC_CURVE_TYPE) { + compute_curve_gamma_table_type_parametric(gamma_table, TRC->parameter, TRC->count); + } else { + if (TRC->count == 0) { + compute_curve_gamma_table_type0(gamma_table); + } else if (TRC->count == 1) { + compute_curve_gamma_table_type1(gamma_table, TRC->data[0]); + } else { + compute_curve_gamma_table_type2(gamma_table, TRC->data, TRC->count); + } + } + } + + validate_gamma_table(gamma_table); + + return gamma_table; +} + +struct matrix build_colorant_matrix(qcms_profile *p) +{ + struct matrix result; + result.m[0][0] = s15Fixed16Number_to_float(p->redColorant.X); + result.m[0][1] = s15Fixed16Number_to_float(p->greenColorant.X); + result.m[0][2] = s15Fixed16Number_to_float(p->blueColorant.X); + result.m[1][0] = s15Fixed16Number_to_float(p->redColorant.Y); + result.m[1][1] = s15Fixed16Number_to_float(p->greenColorant.Y); + result.m[1][2] = s15Fixed16Number_to_float(p->blueColorant.Y); + result.m[2][0] = s15Fixed16Number_to_float(p->redColorant.Z); + result.m[2][1] = s15Fixed16Number_to_float(p->greenColorant.Z); + result.m[2][2] = s15Fixed16Number_to_float(p->blueColorant.Z); + result.invalid = false; + return result; +} + +/* The following code is copied nearly directly from lcms. + * I think it could be much better. For example, Argyll seems to have better code in + * icmTable_lookup_bwd and icmTable_setup_bwd. However, for now this is a quick way + * to a working solution and allows for easy comparing with lcms. */ +uint16_fract_t lut_inverse_interp16(uint16_t Value, uint16_t LutTable[], int length, int NumZeroes, int NumPoles) +{ + int l = 1; + int r = 0x10000; + int x = 0, res; // 'int' Give spacing for negative values + int cell0, cell1; + double val2; + double y0, y1, x0, x1; + double a, b, f; + + // July/27 2001 - Expanded to handle degenerated curves with an arbitrary + // number of elements containing 0 at the beginning of the table (Zeroes) + // and another arbitrary number of poles (FFFFh) at the end. + + // There are no zeros at the beginning and we are trying to find a zero, so + // return anything. It seems zero would be the less destructive choice + /* I'm not sure that this makes sense, but oh well... */ + if (NumZeroes == 0 && Value == 0) + return 0; + + // Does the curve belong to this case? + if (NumZeroes > 1 || NumPoles > 1) + { + int a, b, sample; + + // Identify if value fall downto 0 or FFFF zone + if (Value == 0) return 0; + // if (Value == 0xFFFF) return 0xFFFF; + sample = (length-1) * ((double) Value * (1./65535.)); + if (LutTable[sample] == 0xffff) + return 0xffff; + + // else restrict to valid zone + + a = ((NumZeroes-1) * 0xFFFF) / (length-1); + b = ((length-1 - NumPoles) * 0xFFFF) / (length-1); + + l = a - 1; + r = b + 1; + + // Ensure a valid binary search range + + if (l < 1) + l = 1; + if (r > 0x10000) + r = 0x10000; + + // If the search range is inverted due to degeneracy, + // deem LutTable non-invertible in this search range. + // Refer to https://bugzil.la/1132467 + + if (r <= l) + return 0; + } + + // For input 0, return that to maintain black level. Note the binary search + // does not. For example, it inverts the standard sRGB gamma curve to 7 at + // the origin, causing a black level error. + + if (Value == 0 && NumZeroes) { + return 0; + } + + // Seems not a degenerated case... apply binary search + + while (r > l) { + + x = (l + r) / 2; + + res = (int) lut_interp_linear16((uint16_fract_t) (x-1), LutTable, length); + + if (res == Value) { + + // Found exact match. + + return (uint16_fract_t) (x - 1); + } + + if (res > Value) r = x - 1; + else l = x + 1; + } + + // Not found, should we interpolate? + + // Get surrounding nodes + + assert(x >= 1); + + val2 = (length-1) * ((double) (x - 1) / 65535.0); + + cell0 = (int) floor(val2); + cell1 = (int) ceil(val2); + + assert(cell0 >= 0); + assert(cell1 >= 0); + assert(cell0 < length); + assert(cell1 < length); + + if (cell0 == cell1) return (uint16_fract_t) x; + + y0 = LutTable[cell0] ; + x0 = (65535.0 * cell0) / (length-1); + + y1 = LutTable[cell1] ; + x1 = (65535.0 * cell1) / (length-1); + + a = (y1 - y0) / (x1 - x0); + b = y0 - a * x0; + + if (fabs(a) < 0.01) return (uint16_fract_t) x; + + f = ((Value - b) / a); + + if (f < 0.0) return (uint16_fract_t) 0; + if (f >= 65535.0) return (uint16_fract_t) 0xFFFF; + + return (uint16_fract_t) floor(f + 0.5); +} + +// December/16 2015 - Moved this code out of lut_inverse_interp16 +// in order to save computation in invert_lut loop. +static void count_zeroes_and_poles(uint16_t *LutTable, int length, int *NumZeroes, int *NumPoles) +{ + int z = 0, p = 0; + + while (LutTable[z] == 0 && z < length - 1) + z++; + *NumZeroes = z; + + while (LutTable[length - 1 - p] == 0xFFFF && p < length - 1) + p++; + *NumPoles = p; +} + +/* + The number of entries needed to invert a lookup table should not + necessarily be the same as the original number of entries. This is + especially true of lookup tables that have a small number of entries. + + For example: + Using a table like: + {0, 3104, 14263, 34802, 65535} + invert_lut will produce an inverse of: + {3, 34459, 47529, 56801, 65535} + which has an maximum error of about 9855 (pixel difference of ~38.346) + + For now, we punt the decision of output size to the caller. */ +static uint16_t *invert_lut(uint16_t *table, int length, size_t out_length) +{ + int NumZeroes; + int NumPoles; + int i; + /* for now we invert the lut by creating a lut of size out_length + * and attempting to lookup a value for each entry using lut_inverse_interp16 */ + uint16_t *output = malloc(sizeof(uint16_t)*out_length); + if (!output) + return NULL; + + // December/16 2015 - Compute the input curve zero and pole extents outside + // the loop and pass them to lut_inverse_interp16. + count_zeroes_and_poles(table, length, &NumZeroes, &NumPoles); + + for (i = 0; i < out_length; i++) { + double x = ((double) i * 65535.) / (double) (out_length - 1); + uint16_fract_t input = floor(x + .5); + output[i] = lut_inverse_interp16(input, table, length, NumZeroes, NumPoles); + } + + return output; +} + +static void compute_precache_pow(uint8_t *output, float gamma) +{ + uint32_t v = 0; + for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) { + //XXX: don't do integer/float conversion... and round? + output[v] = 255. * pow(v/(double)PRECACHE_OUTPUT_MAX, gamma); + } +} + +void compute_precache_lut(uint8_t *output, uint16_t *table, int length) +{ + uint32_t v = 0; + for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) { + output[v] = lut_interp_linear_precache_output(v, table, length); + } +} + +void compute_precache_linear(uint8_t *output) +{ + uint32_t v = 0; + for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) { + //XXX: round? + output[v] = v / (PRECACHE_OUTPUT_SIZE/256); + } +} + +qcms_bool compute_precache(struct curveType *trc, uint8_t *output) +{ + + if (trc->type == PARAMETRIC_CURVE_TYPE) { + float gamma_table[256]; + uint16_t gamma_table_uint[256]; + uint16_t i; + uint16_t *inverted; + int inverted_size = 256; + + compute_curve_gamma_table_type_parametric(gamma_table, trc->parameter, trc->count); + for(i = 0; i < 256; i++) { + gamma_table_uint[i] = (uint16_t)(gamma_table[i] * 65535); + } + + //XXX: the choice of a minimum of 256 here is not backed by any theory, + // measurement or data, howeve r it is what lcms uses. + // the maximum number we would need is 65535 because that's the + // accuracy used for computing the pre cache table + if (inverted_size < 256) + inverted_size = 256; + + inverted = invert_lut(gamma_table_uint, 256, inverted_size); + if (!inverted) + return false; + compute_precache_lut(output, inverted, inverted_size); + free(inverted); + } else { + if (trc->count == 0) { + compute_precache_linear(output); + } else if (trc->count == 1) { + compute_precache_pow(output, 1./u8Fixed8Number_to_float(trc->data[0])); + } else { + uint16_t *inverted; + int inverted_size = trc->count; + //XXX: the choice of a minimum of 256 here is not backed by any theory, + // measurement or data, howeve r it is what lcms uses. + // the maximum number we would need is 65535 because that's the + // accuracy used for computing the pre cache table + if (inverted_size < 256) + inverted_size = 256; + + inverted = invert_lut(trc->data, trc->count, inverted_size); + if (!inverted) + return false; + compute_precache_lut(output, inverted, inverted_size); + free(inverted); + } + } + return true; +} + + +static uint16_t *build_linear_table(int length) +{ + int i; + uint16_t *output = malloc(sizeof(uint16_t)*length); + if (!output) + return NULL; + + for (i = 0; i < length; i++) { + double x = ((double) i * 65535.) / (double) (length - 1); + uint16_fract_t input = floor(x + .5); + output[i] = input; + } + return output; +} + +static uint16_t *build_pow_table(float gamma, int length) +{ + int i; + uint16_t *output = malloc(sizeof(uint16_t)*length); + if (!output) + return NULL; + + for (i = 0; i < length; i++) { + uint16_fract_t result; + double x = ((double) i) / (double) (length - 1); + x = pow(x, gamma); //XXX turn this conversion into a function + result = floor(x*65535. + .5); + output[i] = result; + } + return output; +} + +void build_output_lut(struct curveType *trc, + uint16_t **output_gamma_lut, size_t *output_gamma_lut_length) +{ + if (trc->type == PARAMETRIC_CURVE_TYPE) { + float gamma_table[256]; + uint16_t gamma_table_uint[256]; + uint16_t i; + uint16_t *inverted; + int inverted_size = 4096; + + compute_curve_gamma_table_type_parametric(gamma_table, trc->parameter, trc->count); + for(i = 0; i < 256; i++) { + gamma_table_uint[i] = (uint16_t)(gamma_table[i] * 65535); + } + + //XXX: the choice of a minimum of 256 here is not backed by any theory, + // measurement or data, however it is what lcms uses. + // the maximum number we would need is 65535 because that's the + // accuracy used for computing the pre cache table + inverted = invert_lut(gamma_table_uint, 256, inverted_size); + if (!inverted) + return; + *output_gamma_lut = inverted; + *output_gamma_lut_length = inverted_size; + } else { + if (trc->count == 0) { + *output_gamma_lut = build_linear_table(4096); + *output_gamma_lut_length = 4096; + } else if (trc->count == 1) { + float gamma = 1./u8Fixed8Number_to_float(trc->data[0]); + *output_gamma_lut = build_pow_table(gamma, 4096); + *output_gamma_lut_length = 4096; + } else { + //XXX: the choice of a minimum of 256 here is not backed by any theory, + // measurement or data, however it is what lcms uses. + *output_gamma_lut_length = trc->count; + if (*output_gamma_lut_length < 256) + *output_gamma_lut_length = 256; + + *output_gamma_lut = invert_lut(trc->data, trc->count, *output_gamma_lut_length); + } + } + +} diff --git a/third_party/qcms/src/transform_util.h b/third_party/qcms/src/transform_util.h new file mode 100644 index 0000000000..f83477aed6 --- /dev/null +++ b/third_party/qcms/src/transform_util.h @@ -0,0 +1,58 @@ +/* vim: set ts=8 sw=8 noexpandtab: */ +// qcms +// Copyright (C) 2009 Mozilla Foundation +// 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. + +#ifndef _QCMS_TRANSFORM_UTIL_H +#define _QCMS_TRANSFORM_UTIL_H + +#include <stdlib.h> + +#define CLU(table,x,y,z) table[(x*len + y*x_len + z*xy_len)*3] + +//XXX: could use a bettername +typedef uint16_t uint16_fract_t; + +float lut_interp_linear(double input_value, uint16_t *table, size_t length); +float lut_interp_linear_float(float input_value, float *table, size_t length); +uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, size_t length); + + +static inline float lerp(float a, float b, float t) +{ + return a*(1.f-t) + b*t; +} + +unsigned char clamp_u8(float v); +float clamp_float(float a); + +float u8Fixed8Number_to_float(uint16_t x); + + +float *build_input_gamma_table(struct curveType *TRC); +struct matrix build_colorant_matrix(qcms_profile *p); +void build_output_lut(struct curveType *trc, + uint16_t **output_gamma_lut, size_t *output_gamma_lut_length); + +struct matrix matrix_invert(struct matrix mat); +qcms_bool compute_precache(struct curveType *trc, uint8_t *output); + +#endif |