xref: /aosp_15_r20/external/skia/src/core/SkColorSpaceXformSteps.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/core/SkColorSpaceXformSteps.h"
9 
10 #include "include/core/SkAlphaType.h"
11 #include "include/core/SkColorSpace.h"
12 #include "include/core/SkTypes.h"
13 #include "include/private/base/SkFloatingPoint.h"
14 #include "modules/skcms/skcms.h"
15 #include "src/core/SkColorSpacePriv.h"
16 #include "src/core/SkRasterPipeline.h"
17 #include "src/core/SkRasterPipelineOpList.h"
18 
19 #include <cstring>
20 
21 // See skia.org/user/color  (== site/user/color.md).
22 
SkColorSpaceXformSteps(const SkColorSpace * src,SkAlphaType srcAT,const SkColorSpace * dst,SkAlphaType dstAT)23 SkColorSpaceXformSteps::SkColorSpaceXformSteps(const SkColorSpace* src, SkAlphaType srcAT,
24                                                const SkColorSpace* dst, SkAlphaType dstAT) {
25     // Opaque outputs are treated as the same alpha type as the source input.
26     // TODO: we'd really like to have a good way of explaining why we think this is useful.
27     if (dstAT == kOpaque_SkAlphaType) {
28         dstAT =  srcAT;
29     }
30 
31     // We have some options about what to do with null src or dst here.
32     // This pair seems to be the most consistent with legacy expectations.
33     if (!src) { src = sk_srgb_singleton(); }
34     if (!dst) { dst = src; }
35 
36     if (src->hash() == dst->hash() && srcAT == dstAT) {
37         SkASSERT(SkColorSpace::Equals(src,dst));
38         return;
39     }
40 
41     this->flags.unpremul        = srcAT == kPremul_SkAlphaType;
42     this->flags.linearize       = !src->gammaIsLinear();
43     this->flags.gamut_transform = src->toXYZD50Hash() != dst->toXYZD50Hash();
44     this->flags.encode          = !dst->gammaIsLinear();
45     this->flags.premul          = srcAT != kOpaque_SkAlphaType && dstAT == kPremul_SkAlphaType;
46 
47     if (this->flags.gamut_transform) {
48         skcms_Matrix3x3 src_to_dst;  // TODO: switch src_to_dst_matrix to row-major
49         src->gamutTransformTo(dst, &src_to_dst);
50 
51         this->src_to_dst_matrix[0] = src_to_dst.vals[0][0];
52         this->src_to_dst_matrix[1] = src_to_dst.vals[1][0];
53         this->src_to_dst_matrix[2] = src_to_dst.vals[2][0];
54 
55         this->src_to_dst_matrix[3] = src_to_dst.vals[0][1];
56         this->src_to_dst_matrix[4] = src_to_dst.vals[1][1];
57         this->src_to_dst_matrix[5] = src_to_dst.vals[2][1];
58 
59         this->src_to_dst_matrix[6] = src_to_dst.vals[0][2];
60         this->src_to_dst_matrix[7] = src_to_dst.vals[1][2];
61         this->src_to_dst_matrix[8] = src_to_dst.vals[2][2];
62     } else {
63     #ifdef SK_DEBUG
64         skcms_Matrix3x3 srcM, dstM;
65         src->toXYZD50(&srcM);
66         dst->toXYZD50(&dstM);
67         SkASSERT(0 == memcmp(&srcM, &dstM, 9*sizeof(float)) && "Hash collision");
68     #endif
69     }
70 
71     // Fill out all the transfer functions we'll use.
72     src->   transferFn(&this->srcTF   );
73     dst->invTransferFn(&this->dstTFInv);
74 
75     // If we linearize then immediately reencode with the same transfer function, skip both.
76     if ( this->flags.linearize       &&
77         !this->flags.gamut_transform &&
78          this->flags.encode          &&
79          src->transferFnHash() == dst->transferFnHash())
80     {
81     #ifdef SK_DEBUG
82         skcms_TransferFunction dstTF;
83         dst->transferFn(&dstTF);
84         for (int i = 0; i < 7; i++) {
85             SkASSERT( (&srcTF.g)[i] == (&dstTF.g)[i] && "Hash collision" );
86         }
87     #endif
88         this->flags.linearize  = false;
89         this->flags.encode     = false;
90     }
91 
92     // Skip unpremul...premul if there are no non-linear operations between.
93     if ( this->flags.unpremul   &&
94         !this->flags.linearize  &&
95         !this->flags.encode     &&
96          this->flags.premul)
97     {
98         this->flags.unpremul = false;
99         this->flags.premul   = false;
100     }
101 }
102 
apply(float * rgba) const103 void SkColorSpaceXformSteps::apply(float* rgba) const {
104     if (flags.unpremul) {
105         // I don't know why isfinite(x) stopped working on the Chromecast bots...
106         auto is_finite = [](float x) { return x*0 == 0; };
107 
108         float invA = sk_ieee_float_divide(1.0f, rgba[3]);
109         invA = is_finite(invA) ? invA : 0;
110         rgba[0] *= invA;
111         rgba[1] *= invA;
112         rgba[2] *= invA;
113     }
114     if (flags.linearize) {
115         rgba[0] = skcms_TransferFunction_eval(&srcTF, rgba[0]);
116         rgba[1] = skcms_TransferFunction_eval(&srcTF, rgba[1]);
117         rgba[2] = skcms_TransferFunction_eval(&srcTF, rgba[2]);
118     }
119     if (flags.gamut_transform) {
120         float temp[3] = { rgba[0], rgba[1], rgba[2] };
121         for (int i = 0; i < 3; ++i) {
122             rgba[i] = src_to_dst_matrix[    i] * temp[0] +
123                       src_to_dst_matrix[3 + i] * temp[1] +
124                       src_to_dst_matrix[6 + i] * temp[2];
125         }
126     }
127     if (flags.encode) {
128         rgba[0] = skcms_TransferFunction_eval(&dstTFInv, rgba[0]);
129         rgba[1] = skcms_TransferFunction_eval(&dstTFInv, rgba[1]);
130         rgba[2] = skcms_TransferFunction_eval(&dstTFInv, rgba[2]);
131     }
132     if (flags.premul) {
133         rgba[0] *= rgba[3];
134         rgba[1] *= rgba[3];
135         rgba[2] *= rgba[3];
136     }
137 }
138 
apply(SkRasterPipeline * p) const139 void SkColorSpaceXformSteps::apply(SkRasterPipeline* p) const {
140     if (flags.unpremul)        { p->append(SkRasterPipelineOp::unpremul); }
141     if (flags.linearize)       { p->appendTransferFunction(srcTF); }
142     if (flags.gamut_transform) { p->append(SkRasterPipelineOp::matrix_3x3, &src_to_dst_matrix); }
143     if (flags.encode)          { p->appendTransferFunction(dstTFInv); }
144     if (flags.premul)          { p->append(SkRasterPipelineOp::premul); }
145 }
146