color-lcms: optimize linear TRCs

The icc<->parametric color transformation code uses "optical ICC
profiles" as part of the ICC pipeline. These profiles use a linear TRC
to encode the black point. When such TRC survives all optimizations, it
will cause the 3D LUT fallback path to be taken.

Detect such curve sets on the ICC pipeline optimizer, and convert them
to matrix stages. The matrix stages will then be optimized as usual,
often eliminating the stage completely.

The results can be seen in color-icc-output test after the stock sRGB
profile has been changed into a parametric one, causing all cases in the
test to hit the parametric-to-icc path. Some tests fail when they
suddenly start using the 3D LUT path which causes the errors to rise.
This patch fixes those (future) cases, and the errors remain the same as
before changing the stock profile.

Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
This commit is contained in:
Pekka Paalanen 2025-06-19 14:15:41 +03:00 committed by Leandro Ribeiro
parent 0e474b763f
commit 54b60ad51a
3 changed files with 90 additions and 0 deletions

View file

@ -676,3 +676,58 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
return true;
}
/** Create a matrix stage equivalent to the CurveSet stage.
*
* A tabulated curve segment with 2 samples is equivalent to a matrix
* (scaling and offset), ignoring possible input clamping and allowing
* extrapolation.
*
* \param context_id The matrix stage is created in this context.
* \param stage An arbitrary stage, can be NULL.
* \return A new matrix stage equivalent to the given (CurveSet) stage, or
* NULL otherwise.
*/
cmsStage *
lcms_matrix_stage_from_curve(cmsContext context_id, cmsStage *stage)
{
const _cmsStageToneCurvesData *data;
cmsFloat64Number Matrix[3 * 3] = {}; /* row-major */
cmsFloat64Number Offset[3];
unsigned i;
if (!stage || cmsStageType(stage) != cmsSigCurveSetElemType)
return NULL;
data = cmsStageData(stage);
if (data->nCurves != 3)
return NULL;
for (i = 0; i < 3; i++) {
const cmsCurveSegment *seg;
bool clamped_input;
double y0, y1, k;
seg = get_defining_curve_segment(data->TheCurves[i], &clamped_input);
if (!seg)
return NULL;
/* Type 0 is tabulated. */
if (seg->Type != 0)
return NULL;
if (seg->nGridPoints != 2)
return NULL;
y0 = seg->SampledPoints[0];
y1 = seg->SampledPoints[1];
/* y = k * x + Offset */
k = (y1 - y0) / (seg->x1 - seg->x0);
Offset[i] = y0 - k * seg->x0;
Matrix[3 * i + i] = k;
}
return cmsStageAllocMatrix(context_id, 3, 3, Matrix, Offset);
}

View file

@ -39,6 +39,9 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
float curveset_params[3][MAX_PARAMS_LCMS_PARAM_CURVE],
bool *clamped_input);
cmsStage *
lcms_matrix_stage_from_curve(cmsContext context_id, cmsStage *stage);
void
curveset_print(cmsStage *stage, struct weston_log_scope *scope);
@ -64,6 +67,12 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
return false;
}
cmsStage *
lcms_matrix_stage_from_curve(cmsContext ContextID, cmsStage *stage)
{
return NULL;
}
static inline void
curveset_print(cmsStage *stage, struct weston_log_scope *scope)
{

View file

@ -387,6 +387,30 @@ merge_curvesets(cmsPipeline **lut, cmsContext context_id)
return modified;
}
static void
linear_curvesets_to_matrices(cmsPipeline **lut, cmsContext context_id)
{
cmsPipeline *pipe;
cmsStage *elem;
cmsStage *matrix;
pipe = cmsPipelineAlloc(context_id, 3, 3);
abort_oom_if_null(pipe);
elem = cmsPipelineGetPtrToFirstStage(*lut);
for (; elem; elem = cmsStageNext(elem)) {
matrix = lcms_matrix_stage_from_curve(context_id, elem);
if (matrix) {
cmsPipelineInsertStage(pipe, cmsAT_END, matrix);
} else {
cmsPipelineInsertStage(pipe, cmsAT_END, cmsStageDup(elem));
}
}
cmsPipelineFree(*lut);
*lut = pipe;
}
static const struct weston_color_tf_info *
lcms_curve_matches_any_tf(struct weston_compositor *compositor,
uint32_t lcms_curve_type, bool clamped_input,
@ -1013,6 +1037,8 @@ lcms_optimize_pipeline(cmsPipeline **lut, cmsContext context_id)
{
bool cont_opt;
linear_curvesets_to_matrices(lut, context_id);
/**
* This optimization loop will delete identity stages. Deleting
* identity matrix stages is harmless, but deleting identity