mirror of
https://gitlab.freedesktop.org/wayland/weston.git
synced 2025-12-20 03:30:19 +01:00
This converts weston_matrix and weston_vector to use linalg-types internally. All direct accesses to members had to be converted everywhere, mostly in the simplest form possible which leaves some trivially reducable code around. The intention is that we can now gradually migrate away from weston_matrix in the future. Look like one trailing space got accidentally annihilated in compositor.c. Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
580 lines
16 KiB
C
580 lines
16 KiB
C
/*
|
|
* Copyright 2021 Advanced Micro Devices, Inc.
|
|
* Copyright 2022 Collabora, Ltd.
|
|
* Copyright (c) 1998-2023 Marti Maria Saguer
|
|
*
|
|
* 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 (including the
|
|
* next paragraph) 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 "config.h"
|
|
|
|
#include <math.h>
|
|
#include <lcms2.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <libweston/matrix.h>
|
|
#include <libweston/linalg-4.h>
|
|
#include "shared/helpers.h"
|
|
#include "color_util.h"
|
|
#include "lcms_util.h"
|
|
#include "weston-test-assert.h"
|
|
|
|
static const cmsCIExyY wp_d65 = { 0.31271, 0.32902, 1.0 };
|
|
|
|
/*
|
|
* MPE tone curves can only use LittleCMS parametric curve types 6-8 and not
|
|
* inverses.
|
|
* type 6: Y = (aX + b)^g + c; params [g, a, b, c]
|
|
* type 7: Y = a log(bX^g + c) + d; params [g, a, b, c, d]
|
|
* type 8: Y = a b^(cX + d) + e; params [a, b, c, d, e]
|
|
* Additionally, type 0 is sampled segment.
|
|
*
|
|
* cmsCurveSegment.x1 is the breakpoint stored in ICC files, except for the
|
|
* last segment. First segment always begins at -Inf, and last segment always
|
|
* ends at Inf.
|
|
*/
|
|
|
|
static cmsToneCurve *
|
|
build_MPE_curve_sRGB(cmsContext ctx)
|
|
{
|
|
cmsCurveSegment segments[] = {
|
|
{
|
|
/* Constant zero segment */
|
|
.x0 = -HUGE_VAL,
|
|
.x1 = 0.0,
|
|
.Type = 6,
|
|
.Params = { 1.0, 0.0, 0.0, 0.0 },
|
|
},
|
|
{
|
|
/* Linear segment y = x / 12.92 */
|
|
.x0 = 0.0,
|
|
.x1 = 0.04045,
|
|
.Type = 0,
|
|
.nGridPoints = 2,
|
|
.SampledPoints = (float[]){ 0.0, 0.04045 / 12.92 },
|
|
},
|
|
{
|
|
/* Power segment y = ((x + 0.055) / 1.055)^2.4
|
|
* which is translated to
|
|
* y = (1/1.055 * x + 0.055 / 1.055)^2.4 + 0.0
|
|
*/
|
|
.x0 = 0.04045,
|
|
.x1 = 1.0,
|
|
.Type = 6,
|
|
.Params = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 0.0 },
|
|
},
|
|
{
|
|
/* Constant one segment */
|
|
.x0 = 1.0,
|
|
.x1 = HUGE_VAL,
|
|
.Type = 6,
|
|
.Params = { 1.0, 0.0, 0.0, 1.0 },
|
|
}
|
|
};
|
|
|
|
return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments);
|
|
}
|
|
|
|
static cmsToneCurve *
|
|
build_MPE_curve_sRGB_inv(cmsContext ctx)
|
|
{
|
|
cmsCurveSegment segments[] = {
|
|
{
|
|
/* Constant zero segment */
|
|
.x0 = -HUGE_VAL,
|
|
.x1 = 0.0,
|
|
.Type = 6,
|
|
.Params = { 1.0, 0.0, 0.0, 0.0 },
|
|
},
|
|
{
|
|
/* Linear segment y = x * 12.92 */
|
|
.x0 = 0.0,
|
|
.x1 = 0.04045 / 12.92,
|
|
.Type = 0,
|
|
.nGridPoints = 2,
|
|
.SampledPoints = (float[]){ 0.0, 0.04045 },
|
|
},
|
|
{
|
|
/* Power segment y = 1.055 * x^(1/2.4) - 0.055
|
|
* which is translated to
|
|
* y = (1.055^2.4 * x + 0.0)^(1/2.4) - 0.055
|
|
*/
|
|
.x0 = 0.04045 / 12.92,
|
|
.x1 = 1.0,
|
|
.Type = 6,
|
|
.Params = { 1.0 / 2.4, pow(1.055, 2.4), 0.0, -0.055 },
|
|
},
|
|
{
|
|
/* Constant one segment */
|
|
.x0 = 1.0,
|
|
.x1 = HUGE_VAL,
|
|
.Type = 6,
|
|
.Params = { 1.0, 0.0, 0.0, 1.0 },
|
|
}
|
|
};
|
|
|
|
return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments);
|
|
}
|
|
|
|
static cmsToneCurve *
|
|
build_MPE_curve_power(cmsContext ctx, double exponent)
|
|
{
|
|
cmsCurveSegment segments[] = {
|
|
{
|
|
/* Constant zero segment */
|
|
.x0 = -HUGE_VAL,
|
|
.x1 = 0.0,
|
|
.Type = 6,
|
|
.Params = { 1.0, 0.0, 0.0, 0.0 },
|
|
},
|
|
{
|
|
/* Power segment y = x^exponent
|
|
* which is translated to
|
|
* y = (1.0 * x + 0.0)^exponent + 0.0
|
|
*/
|
|
.x0 = 0.0,
|
|
.x1 = 1.0,
|
|
.Type = 6,
|
|
.Params = { exponent, 1.0, 0.0, 0.0 },
|
|
},
|
|
{
|
|
/* Constant one segment */
|
|
.x0 = 1.0,
|
|
.x1 = HUGE_VAL,
|
|
.Type = 6,
|
|
.Params = { 1.0, 0.0, 0.0, 1.0 },
|
|
}
|
|
};
|
|
|
|
return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments);
|
|
}
|
|
|
|
cmsToneCurve *
|
|
build_MPE_curve(cmsContext ctx, enum transfer_fn fn)
|
|
{
|
|
switch (fn) {
|
|
case TRANSFER_FN_ADOBE_RGB_EOTF:
|
|
return build_MPE_curve_power(ctx, 563.0 / 256.0);
|
|
case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE:
|
|
return build_MPE_curve_power(ctx, 256.0 / 563.0);
|
|
case TRANSFER_FN_POWER2_4_EOTF:
|
|
return build_MPE_curve_power(ctx, 2.4);
|
|
case TRANSFER_FN_POWER2_4_EOTF_INVERSE:
|
|
return build_MPE_curve_power(ctx, 1.0 / 2.4);
|
|
case TRANSFER_FN_SRGB_EOTF:
|
|
return build_MPE_curve_sRGB(ctx);
|
|
case TRANSFER_FN_SRGB_EOTF_INVERSE:
|
|
return build_MPE_curve_sRGB_inv(ctx);
|
|
default:
|
|
test_assert_not_reached("unimplemented MPE curve");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
cmsStage *
|
|
build_MPE_curve_stage(cmsContext context_id, enum transfer_fn fn)
|
|
{
|
|
cmsToneCurve *c;
|
|
cmsStage *stage;
|
|
|
|
c = build_MPE_curve(context_id, fn);
|
|
stage = cmsStageAllocToneCurves(context_id, 3,
|
|
(cmsToneCurve *[3]){ c, c, c });
|
|
test_assert_ptr_not_null(stage);
|
|
cmsFreeToneCurve(c);
|
|
|
|
return stage;
|
|
}
|
|
|
|
/* This function is taken from LittleCMS, pardon the odd style */
|
|
cmsBool
|
|
SetTextTags(cmsHPROFILE hProfile, const wchar_t* Description)
|
|
{
|
|
cmsMLU *DescriptionMLU, *CopyrightMLU;
|
|
cmsBool rc = FALSE;
|
|
cmsContext ContextID = cmsGetProfileContextID(hProfile);
|
|
|
|
DescriptionMLU = cmsMLUalloc(ContextID, 1);
|
|
CopyrightMLU = cmsMLUalloc(ContextID, 1);
|
|
|
|
if (DescriptionMLU == NULL || CopyrightMLU == NULL) goto Error;
|
|
|
|
if (!cmsMLUsetWide(DescriptionMLU, "en", "US", Description)) goto Error;
|
|
if (!cmsMLUsetWide(CopyrightMLU, "en", "US", L"No copyright, use freely")) goto Error;
|
|
|
|
if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Error;
|
|
if (!cmsWriteTag(hProfile, cmsSigCopyrightTag, CopyrightMLU)) goto Error;
|
|
|
|
rc = TRUE;
|
|
|
|
Error:
|
|
|
|
if (DescriptionMLU)
|
|
cmsMLUfree(DescriptionMLU);
|
|
if (CopyrightMLU)
|
|
cmsMLUfree(CopyrightMLU);
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
test_roundtrip(uint8_t r, uint8_t g, uint8_t b, cmsPipeline *pip,
|
|
struct rgb_diff_stat *stat)
|
|
{
|
|
struct color_float in = { .rgb = { r / 255.0, g / 255.0, b / 255.0 } };
|
|
struct color_float out = {};
|
|
|
|
cmsPipelineEvalFloat(in.rgb, out.rgb, pip);
|
|
rgb_diff_stat_update(stat, &in, &out, &in);
|
|
}
|
|
|
|
/*
|
|
* Roundtrip verification tests that converting device -> PCS -> device
|
|
* results in the original color values close enough.
|
|
*
|
|
* This ensures that the two pipelines are probably built correctly, and we
|
|
* do not have problems with unexpected value clamping or with representing
|
|
* (inverse) EOTF curves.
|
|
*/
|
|
static void
|
|
roundtrip_verification(cmsPipeline *DToB, cmsPipeline *BToD, float tolerance)
|
|
{
|
|
unsigned r, g, b;
|
|
struct rgb_diff_stat stat = {};
|
|
cmsPipeline *pip;
|
|
|
|
pip = cmsPipelineDup(DToB);
|
|
cmsPipelineCat(pip, BToD);
|
|
|
|
/*
|
|
* Inverse-EOTF is known to have precision problems near zero, so
|
|
* sample near zero densely, the rest can be more sparse to run faster.
|
|
*/
|
|
for (r = 0; r < 256; r += (r < 15) ? 1 : 8) {
|
|
for (g = 0; g < 256; g += (g < 15) ? 1 : 8) {
|
|
for (b = 0; b < 256; b += (b < 15) ? 1 : 8)
|
|
test_roundtrip(r, g, b, pip, &stat);
|
|
}
|
|
}
|
|
|
|
cmsPipelineFree(pip);
|
|
|
|
rgb_diff_stat_print(&stat, "DToB->BToD roundtrip", 8);
|
|
test_assert_f32_lt(stat.two_norm.max, tolerance);
|
|
}
|
|
|
|
static const struct weston_vector ZEROS = { .v = WESTON_VEC4F_ZERO };
|
|
static const struct weston_vector PCS_BLACK = {
|
|
.v.el = {
|
|
cmsPERCEPTUAL_BLACK_X,
|
|
cmsPERCEPTUAL_BLACK_Y,
|
|
cmsPERCEPTUAL_BLACK_Z,
|
|
1.0
|
|
}
|
|
};
|
|
|
|
/* Whether BPC matrix applies never, after or before transformation */
|
|
enum bpc_dir {
|
|
BPC_DIR_NONE,
|
|
BPC_DIR_DTOB,
|
|
BPC_DIR_BTOD,
|
|
};
|
|
|
|
struct transform_sampler_context {
|
|
cmsHTRANSFORM t;
|
|
struct weston_matrix bpc;
|
|
enum bpc_dir dir;
|
|
};
|
|
|
|
static cmsInt32Number
|
|
transform_sampler(const float src[], float dst[], void *cargo)
|
|
{
|
|
const struct transform_sampler_context *tsc = cargo;
|
|
struct weston_vector stmp = { .v.el = { src[0], src[1], src[2], 1.0 } };
|
|
struct weston_vector dtmp = { .v.el = { 0.0, 0.0, 0.0, 1.0 } };
|
|
|
|
if (tsc->dir == BPC_DIR_BTOD)
|
|
weston_matrix_transform(&tsc->bpc, &stmp);
|
|
|
|
cmsDoTransform(tsc->t, stmp.v.el, dtmp.v.el, 1);
|
|
|
|
if (tsc->dir == BPC_DIR_DTOB)
|
|
weston_matrix_transform(&tsc->bpc, &dtmp);
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
dst[i] = dtmp.v.el[i];
|
|
|
|
return 1; /* Success. */
|
|
}
|
|
|
|
/*
|
|
* Black point compensation, copied from LittleCMS 2.16, cmscnvrt.c
|
|
* Adapted to Weston code base.
|
|
*/
|
|
static void
|
|
ComputeBlackPointCompensation(struct weston_matrix *m,
|
|
const struct weston_vector *src_bp,
|
|
const struct weston_vector *dst_bp)
|
|
{
|
|
double ax, ay, az, bx, by, bz, tx, ty, tz;
|
|
|
|
// Now we need to compute a matrix plus an offset m and of such of
|
|
// [m]*bpin + off = bpout
|
|
// [m]*D50 + off = D50
|
|
//
|
|
// This is a linear scaling in the form ax+b, where
|
|
// a = (bpout - D50) / (bpin - D50)
|
|
// b = - D50* (bpout - bpin) / (bpin - D50)
|
|
|
|
tx = src_bp->v.x - cmsD50_XYZ()->X;
|
|
ty = src_bp->v.y - cmsD50_XYZ()->Y;
|
|
tz = src_bp->v.z - cmsD50_XYZ()->Z;
|
|
|
|
ax = (dst_bp->v.x - cmsD50_XYZ()->X) / tx;
|
|
ay = (dst_bp->v.y - cmsD50_XYZ()->Y) / ty;
|
|
az = (dst_bp->v.z - cmsD50_XYZ()->Z) / tz;
|
|
|
|
bx = - cmsD50_XYZ()-> X * (dst_bp->v.x - src_bp->v.x) / tx;
|
|
by = - cmsD50_XYZ()-> Y * (dst_bp->v.y - src_bp->v.y) / ty;
|
|
bz = - cmsD50_XYZ()-> Z * (dst_bp->v.z - src_bp->v.z) / tz;
|
|
|
|
/*
|
|
* [ax, 0, 0, bx ]
|
|
* m = [ 0, ay, 0, by ]
|
|
* [ 0, 0, az, bz ]
|
|
* [ 0, 0, 0, 1 ]
|
|
*/
|
|
weston_matrix_init(m);
|
|
weston_matrix_scale(m, ax, ay, az);
|
|
weston_matrix_translate(m, bx, by, bz);
|
|
}
|
|
|
|
static cmsStage *
|
|
create_cLUT_from_transform(cmsContext context_id, const cmsHTRANSFORM t,
|
|
int dim_size,
|
|
enum bpc_dir dir)
|
|
{
|
|
struct transform_sampler_context tsc;
|
|
cmsStage *cLUT_stage;
|
|
|
|
test_assert_int_ne(dim_size, 0);
|
|
|
|
tsc.t = t;
|
|
tsc.dir = dir;
|
|
switch (tsc.dir) {
|
|
case BPC_DIR_NONE:
|
|
weston_matrix_init(&tsc.bpc);
|
|
break;
|
|
case BPC_DIR_DTOB:
|
|
ComputeBlackPointCompensation(&tsc.bpc, &ZEROS, &PCS_BLACK);
|
|
break;
|
|
case BPC_DIR_BTOD:
|
|
ComputeBlackPointCompensation(&tsc.bpc, &PCS_BLACK, &ZEROS);
|
|
break;
|
|
}
|
|
|
|
cLUT_stage = cmsStageAllocCLutFloat(context_id, dim_size, 3, 3, NULL);
|
|
cmsStageSampleCLutFloat(cLUT_stage, transform_sampler, &tsc, 0);
|
|
|
|
return cLUT_stage;
|
|
}
|
|
|
|
static void
|
|
vcgt_tag_add_to_profile(cmsContext context_id, cmsHPROFILE profile,
|
|
const double vcgt_exponents[COLOR_CHAN_NUM])
|
|
{
|
|
cmsToneCurve *vcgt_tag_curves[COLOR_CHAN_NUM];
|
|
unsigned int i;
|
|
|
|
if (!should_include_vcgt(vcgt_exponents))
|
|
return;
|
|
|
|
for (i = 0; i < COLOR_CHAN_NUM; i++)
|
|
vcgt_tag_curves[i] = cmsBuildGamma(context_id, vcgt_exponents[i]);
|
|
|
|
test_assert_true(cmsWriteTag(profile, cmsSigVcgtTag, vcgt_tag_curves));
|
|
|
|
cmsFreeToneCurveTriple(vcgt_tag_curves);
|
|
}
|
|
|
|
cmsHPROFILE
|
|
build_lcms_clut_profile_output(cmsContext context_id,
|
|
const struct lcms_pipeline *pipeline,
|
|
const double vcgt_exponents[COLOR_CHAN_NUM],
|
|
int clut_dim_size, float clut_roundtrip_tolerance)
|
|
{
|
|
enum transfer_fn inv_eotf_fn = pipeline->post_fn;
|
|
enum transfer_fn eotf_fn = transfer_fn_invert(inv_eotf_fn);
|
|
cmsHPROFILE hRGB;
|
|
cmsPipeline *DToB0, *BToD0;
|
|
cmsPipeline *DToB1, *BToD1;
|
|
cmsStage *stage;
|
|
cmsStage *stage_inv_eotf;
|
|
cmsStage *stage_eotf;
|
|
cmsToneCurve *identity_curves[3];
|
|
cmsHPROFILE linear_device;
|
|
cmsHPROFILE pcs;
|
|
cmsHTRANSFORM linear_device_to_pcs;
|
|
cmsHTRANSFORM pcs_to_linear_device;
|
|
|
|
identity_curves[0] = identity_curves[1] = identity_curves[2] =
|
|
cmsBuildGamma(context_id, 1.0);
|
|
|
|
linear_device = cmsCreateRGBProfileTHR(context_id, &wp_d65,
|
|
&pipeline->prim_output,
|
|
identity_curves);
|
|
test_assert_true(cmsIsMatrixShaper(linear_device));
|
|
cmsFreeToneCurve(identity_curves[0]);
|
|
|
|
pcs = cmsCreateXYZProfileTHR(context_id);
|
|
|
|
/*
|
|
* Since linear_device is a matrix-shaper profile, all rendering intents
|
|
* share the same device<->PCS transformations. We only need to pick
|
|
* an arbitrary rendering intent that allows to turn BPC both on and off.
|
|
*/
|
|
linear_device_to_pcs = cmsCreateTransformTHR(context_id,
|
|
linear_device, TYPE_RGB_FLT,
|
|
pcs, TYPE_XYZ_FLT,
|
|
INTENT_RELATIVE_COLORIMETRIC,
|
|
cmsFLAGS_NOOPTIMIZE);
|
|
pcs_to_linear_device = cmsCreateTransformTHR(context_id,
|
|
pcs, TYPE_XYZ_FLT,
|
|
linear_device, TYPE_RGB_FLT,
|
|
INTENT_RELATIVE_COLORIMETRIC,
|
|
cmsFLAGS_NOOPTIMIZE);
|
|
|
|
cmsCloseProfile(linear_device);
|
|
cmsCloseProfile(pcs);
|
|
|
|
hRGB = cmsCreateProfilePlaceholder(context_id);
|
|
cmsSetProfileVersion(hRGB, 4.3);
|
|
cmsSetDeviceClass(hRGB, cmsSigDisplayClass);
|
|
cmsSetColorSpace(hRGB, cmsSigRgbData);
|
|
cmsSetPCS(hRGB, cmsSigXYZData);
|
|
SetTextTags(hRGB, L"cLut profile");
|
|
|
|
stage_eotf = build_MPE_curve_stage(context_id, eotf_fn);
|
|
stage_inv_eotf = build_MPE_curve_stage(context_id, inv_eotf_fn);
|
|
|
|
/*
|
|
* Pipeline from PCS (optical) to device (electrical)
|
|
*/
|
|
|
|
/* Perceptual PCS black point is not zeros, so we need BPC */
|
|
BToD0 = cmsPipelineAlloc(context_id, 3, 3);
|
|
stage = create_cLUT_from_transform(context_id, pcs_to_linear_device,
|
|
clut_dim_size, BPC_DIR_BTOD);
|
|
cmsPipelineInsertStage(BToD0, cmsAT_END, stage);
|
|
cmsPipelineInsertStage(BToD0, cmsAT_END, cmsStageDup(stage_inv_eotf));
|
|
|
|
/* Media-relative colorimetric does not force BPC */
|
|
BToD1 = cmsPipelineAlloc(context_id, 3, 3);
|
|
stage = create_cLUT_from_transform(context_id, pcs_to_linear_device,
|
|
clut_dim_size, BPC_DIR_NONE);
|
|
cmsPipelineInsertStage(BToD1, cmsAT_END, stage);
|
|
cmsPipelineInsertStage(BToD1, cmsAT_END, cmsStageDup(stage_inv_eotf));
|
|
|
|
cmsWriteTag(hRGB, cmsSigBToD0Tag, BToD0);
|
|
cmsWriteTag(hRGB, cmsSigBToD1Tag, BToD1);
|
|
cmsLinkTag(hRGB, cmsSigBToD2Tag, cmsSigBToD0Tag);
|
|
cmsLinkTag(hRGB, cmsSigBToD3Tag, cmsSigBToD1Tag);
|
|
|
|
/*
|
|
* Pipeline from device (electrical) to PCS (optical)
|
|
*/
|
|
|
|
/* Perceptual PCS black point is not zeros, so we need BPC */
|
|
DToB0 = cmsPipelineAlloc(context_id, 3, 3);
|
|
cmsPipelineInsertStage(DToB0, cmsAT_END, cmsStageDup(stage_eotf));
|
|
stage = create_cLUT_from_transform(context_id, linear_device_to_pcs,
|
|
clut_dim_size, BPC_DIR_DTOB);
|
|
cmsPipelineInsertStage(DToB0, cmsAT_END, stage);
|
|
|
|
/* Media-relative colorimetric does not force BPC */
|
|
DToB1 = cmsPipelineAlloc(context_id, 3, 3);
|
|
cmsPipelineInsertStage(DToB1, cmsAT_END, cmsStageDup(stage_eotf));
|
|
stage = create_cLUT_from_transform(context_id, linear_device_to_pcs,
|
|
clut_dim_size, BPC_DIR_NONE);
|
|
cmsPipelineInsertStage(DToB1, cmsAT_END, stage);
|
|
|
|
cmsWriteTag(hRGB, cmsSigDToB0Tag, DToB0);
|
|
cmsWriteTag(hRGB, cmsSigDToB1Tag, DToB1);
|
|
cmsLinkTag(hRGB, cmsSigDToB2Tag, cmsSigDToB0Tag);
|
|
cmsLinkTag(hRGB, cmsSigDToB3Tag, cmsSigDToB1Tag);
|
|
|
|
vcgt_tag_add_to_profile(context_id, hRGB, vcgt_exponents);
|
|
|
|
roundtrip_verification(DToB0, BToD0, clut_roundtrip_tolerance);
|
|
roundtrip_verification(DToB1, BToD1, clut_roundtrip_tolerance);
|
|
|
|
cmsPipelineFree(BToD0);
|
|
cmsPipelineFree(DToB0);
|
|
cmsPipelineFree(BToD1);
|
|
cmsPipelineFree(DToB1);
|
|
cmsStageFree(stage_eotf);
|
|
cmsStageFree(stage_inv_eotf);
|
|
|
|
cmsDeleteTransform(linear_device_to_pcs);
|
|
cmsDeleteTransform(pcs_to_linear_device);
|
|
|
|
return hRGB;
|
|
}
|
|
|
|
cmsHPROFILE
|
|
build_lcms_matrix_shaper_profile_output(cmsContext context_id,
|
|
const struct lcms_pipeline *pipeline,
|
|
const double vcgt_exponents[COLOR_CHAN_NUM])
|
|
{
|
|
cmsToneCurve *arr_curves[3];
|
|
cmsHPROFILE hRGB;
|
|
int type_inverse_tone_curve;
|
|
double inverse_tone_curve_param[5];
|
|
|
|
test_assert_true(find_tone_curve_type(pipeline->post_fn, &type_inverse_tone_curve,
|
|
inverse_tone_curve_param));
|
|
|
|
/*
|
|
* We are creating output profile and therefore we can use the following:
|
|
* calling semantics:
|
|
* cmsBuildParametricToneCurve(type_inverse_tone_curve, inverse_tone_curve_param)
|
|
* The function find_tone_curve_type sets the type of curve positive if it
|
|
* is tone curve and negative if it is inverse. When we create an ICC
|
|
* profile we should use a tone curve, the inversion is done by LCMS
|
|
* when the profile is used for output.
|
|
*/
|
|
|
|
arr_curves[0] = arr_curves[1] = arr_curves[2] =
|
|
cmsBuildParametricToneCurve(context_id,
|
|
(-1) * type_inverse_tone_curve,
|
|
inverse_tone_curve_param);
|
|
|
|
test_assert_ptr_not_null(arr_curves[0]);
|
|
hRGB = cmsCreateRGBProfileTHR(context_id, &wp_d65,
|
|
&pipeline->prim_output, arr_curves);
|
|
test_assert_ptr_not_null(hRGB);
|
|
|
|
vcgt_tag_add_to_profile(context_id, hRGB, vcgt_exponents);
|
|
|
|
cmsFreeToneCurve(arr_curves[0]);
|
|
return hRGB;
|
|
}
|