xref: /aosp_15_r20/external/skia/src/shaders/SkGainmapShader.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2023 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/SkGainmapShader.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorFilter.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorSpace.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImage.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkShader.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/effects/SkRuntimeEffect.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/SkGainmapInfo.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkColorFilterPriv.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkImageInfoPriv.h"
22*c8dee2aaSAndroid Build Coastguard Worker 
23*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
24*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
25*c8dee2aaSAndroid Build Coastguard Worker 
26*c8dee2aaSAndroid Build Coastguard Worker static constexpr char gGainmapSKSL[] =
27*c8dee2aaSAndroid Build Coastguard Worker         "uniform shader base;"
28*c8dee2aaSAndroid Build Coastguard Worker         "uniform shader gainmap;"
29*c8dee2aaSAndroid Build Coastguard Worker         "uniform half4 logRatioMin;"
30*c8dee2aaSAndroid Build Coastguard Worker         "uniform half4 logRatioMax;"
31*c8dee2aaSAndroid Build Coastguard Worker         "uniform half4 gainmapGamma;"
32*c8dee2aaSAndroid Build Coastguard Worker         "uniform half4 epsilonBase;"
33*c8dee2aaSAndroid Build Coastguard Worker         "uniform half4 epsilonOther;"
34*c8dee2aaSAndroid Build Coastguard Worker         "uniform half W;"
35*c8dee2aaSAndroid Build Coastguard Worker         "uniform int gainmapIsAlpha;"
36*c8dee2aaSAndroid Build Coastguard Worker         "uniform int gainmapIsRed;"
37*c8dee2aaSAndroid Build Coastguard Worker         "uniform int singleChannel;"
38*c8dee2aaSAndroid Build Coastguard Worker         "uniform int noGamma;"
39*c8dee2aaSAndroid Build Coastguard Worker         "uniform int isApple;"
40*c8dee2aaSAndroid Build Coastguard Worker         "uniform half appleG;"
41*c8dee2aaSAndroid Build Coastguard Worker         "uniform half appleH;"
42*c8dee2aaSAndroid Build Coastguard Worker         ""
43*c8dee2aaSAndroid Build Coastguard Worker         "half4 main(float2 coord) {"
44*c8dee2aaSAndroid Build Coastguard Worker             "half4 S = base.eval(coord);"
45*c8dee2aaSAndroid Build Coastguard Worker             "half4 G = gainmap.eval(coord);"
46*c8dee2aaSAndroid Build Coastguard Worker             "if (gainmapIsAlpha == 1) {"
47*c8dee2aaSAndroid Build Coastguard Worker                 "G = half4(G.a, G.a, G.a, 1.0);"
48*c8dee2aaSAndroid Build Coastguard Worker             "}"
49*c8dee2aaSAndroid Build Coastguard Worker             "if (gainmapIsRed == 1) {"
50*c8dee2aaSAndroid Build Coastguard Worker                 "G = half4(G.r, G.r, G.r, 1.0);"
51*c8dee2aaSAndroid Build Coastguard Worker             "}"
52*c8dee2aaSAndroid Build Coastguard Worker             "if (singleChannel == 1) {"
53*c8dee2aaSAndroid Build Coastguard Worker                 "half L;"
54*c8dee2aaSAndroid Build Coastguard Worker                 "if (isApple == 1) {"
55*c8dee2aaSAndroid Build Coastguard Worker                     "L = pow(G.r, appleG);"
56*c8dee2aaSAndroid Build Coastguard Worker                     "L = log(1.0 + (appleH - 1.0) * pow(G.r, appleG));"
57*c8dee2aaSAndroid Build Coastguard Worker                 "} else if (noGamma == 1) {"
58*c8dee2aaSAndroid Build Coastguard Worker                     "L = mix(logRatioMin.r, logRatioMax.r, G.r);"
59*c8dee2aaSAndroid Build Coastguard Worker                 "} else {"
60*c8dee2aaSAndroid Build Coastguard Worker                     "L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));"
61*c8dee2aaSAndroid Build Coastguard Worker                 "}"
62*c8dee2aaSAndroid Build Coastguard Worker                 "half3 H = (S.rgb + epsilonBase.rgb) * exp(L * W) - epsilonOther.rgb;"
63*c8dee2aaSAndroid Build Coastguard Worker                 "return half4(H.r, H.g, H.b, S.a);"
64*c8dee2aaSAndroid Build Coastguard Worker             "} else {"
65*c8dee2aaSAndroid Build Coastguard Worker                 "half3 L;"
66*c8dee2aaSAndroid Build Coastguard Worker                 "if (isApple == 1) {"
67*c8dee2aaSAndroid Build Coastguard Worker                     "L = pow(G.rgb, half3(appleG));"
68*c8dee2aaSAndroid Build Coastguard Worker                     "L = log(half3(1.0) + (appleH - 1.0) * L);"
69*c8dee2aaSAndroid Build Coastguard Worker                 "} else if (noGamma == 1) {"
70*c8dee2aaSAndroid Build Coastguard Worker                     "L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);"
71*c8dee2aaSAndroid Build Coastguard Worker                 "} else {"
72*c8dee2aaSAndroid Build Coastguard Worker                     "L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));"
73*c8dee2aaSAndroid Build Coastguard Worker                 "}"
74*c8dee2aaSAndroid Build Coastguard Worker                 "half3 H = (S.rgb + epsilonBase.rgb) * exp(L * W) - epsilonOther.rgb;"
75*c8dee2aaSAndroid Build Coastguard Worker                 "return half4(H.r, H.g, H.b, S.a);"
76*c8dee2aaSAndroid Build Coastguard Worker             "}"
77*c8dee2aaSAndroid Build Coastguard Worker         "}";
78*c8dee2aaSAndroid Build Coastguard Worker 
gainmap_apply_effect()79*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
80*c8dee2aaSAndroid Build Coastguard Worker     static const SkRuntimeEffect* effect =
81*c8dee2aaSAndroid Build Coastguard Worker             SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {}).effect.release();
82*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(effect);
83*c8dee2aaSAndroid Build Coastguard Worker     return sk_ref_sp(effect);
84*c8dee2aaSAndroid Build Coastguard Worker }
85*c8dee2aaSAndroid Build Coastguard Worker 
all_channels_equal(const SkColor4f & c)86*c8dee2aaSAndroid Build Coastguard Worker static bool all_channels_equal(const SkColor4f& c) {
87*c8dee2aaSAndroid Build Coastguard Worker     return c.fR == c.fG && c.fR == c.fB;
88*c8dee2aaSAndroid Build Coastguard Worker }
89*c8dee2aaSAndroid Build Coastguard Worker 
Make(const sk_sp<const SkImage> & baseImage,const SkRect & baseRect,const SkSamplingOptions & baseSamplingOptions,const sk_sp<const SkImage> & gainmapImage,const SkRect & gainmapRect,const SkSamplingOptions & gainmapSamplingOptions,const SkGainmapInfo & gainmapInfo,const SkRect & dstRect,float dstHdrRatio,sk_sp<SkColorSpace> dstColorSpace)90*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkShader> SkGainmapShader::Make(const sk_sp<const SkImage>& baseImage,
91*c8dee2aaSAndroid Build Coastguard Worker                                       const SkRect& baseRect,
92*c8dee2aaSAndroid Build Coastguard Worker                                       const SkSamplingOptions& baseSamplingOptions,
93*c8dee2aaSAndroid Build Coastguard Worker                                       const sk_sp<const SkImage>& gainmapImage,
94*c8dee2aaSAndroid Build Coastguard Worker                                       const SkRect& gainmapRect,
95*c8dee2aaSAndroid Build Coastguard Worker                                       const SkSamplingOptions& gainmapSamplingOptions,
96*c8dee2aaSAndroid Build Coastguard Worker                                       const SkGainmapInfo& gainmapInfo,
97*c8dee2aaSAndroid Build Coastguard Worker                                       const SkRect& dstRect,
98*c8dee2aaSAndroid Build Coastguard Worker                                       float dstHdrRatio,
99*c8dee2aaSAndroid Build Coastguard Worker                                       sk_sp<SkColorSpace> dstColorSpace) {
100*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkColorSpace> baseColorSpace =
101*c8dee2aaSAndroid Build Coastguard Worker             baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
102*c8dee2aaSAndroid Build Coastguard Worker 
103*c8dee2aaSAndroid Build Coastguard Worker     // Determine the color space in which the gainmap math is to be applied.
104*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkColorSpace> gainmapMathColorSpace =
105*c8dee2aaSAndroid Build Coastguard Worker             gainmapInfo.fGainmapMathColorSpace
106*c8dee2aaSAndroid Build Coastguard Worker                     ? gainmapInfo.fGainmapMathColorSpace->makeLinearGamma()
107*c8dee2aaSAndroid Build Coastguard Worker                     : baseColorSpace->makeLinearGamma();
108*c8dee2aaSAndroid Build Coastguard Worker     if (!dstColorSpace) {
109*c8dee2aaSAndroid Build Coastguard Worker         dstColorSpace = SkColorSpace::MakeSRGB();
110*c8dee2aaSAndroid Build Coastguard Worker     }
111*c8dee2aaSAndroid Build Coastguard Worker 
112*c8dee2aaSAndroid Build Coastguard Worker     // Compute the sampling transformation matrices.
113*c8dee2aaSAndroid Build Coastguard Worker     const SkMatrix baseRectToDstRect = SkMatrix::RectToRect(baseRect, dstRect);
114*c8dee2aaSAndroid Build Coastguard Worker     const SkMatrix gainmapRectToDstRect = SkMatrix::RectToRect(gainmapRect, dstRect);
115*c8dee2aaSAndroid Build Coastguard Worker 
116*c8dee2aaSAndroid Build Coastguard Worker     // Compute the weight parameter that will be used to blend between the images.
117*c8dee2aaSAndroid Build Coastguard Worker     float W = 0.f;
118*c8dee2aaSAndroid Build Coastguard Worker     if (dstHdrRatio > gainmapInfo.fDisplayRatioSdr) {
119*c8dee2aaSAndroid Build Coastguard Worker         if (dstHdrRatio < gainmapInfo.fDisplayRatioHdr) {
120*c8dee2aaSAndroid Build Coastguard Worker             W = (std::log(dstHdrRatio) - std::log(gainmapInfo.fDisplayRatioSdr)) /
121*c8dee2aaSAndroid Build Coastguard Worker                 (std::log(gainmapInfo.fDisplayRatioHdr) -
122*c8dee2aaSAndroid Build Coastguard Worker                  std::log(gainmapInfo.fDisplayRatioSdr));
123*c8dee2aaSAndroid Build Coastguard Worker         } else {
124*c8dee2aaSAndroid Build Coastguard Worker             W = 1.f;
125*c8dee2aaSAndroid Build Coastguard Worker         }
126*c8dee2aaSAndroid Build Coastguard Worker     }
127*c8dee2aaSAndroid Build Coastguard Worker 
128*c8dee2aaSAndroid Build Coastguard Worker     const bool baseImageIsHdr = (gainmapInfo.fBaseImageType == SkGainmapInfo::BaseImageType::kHDR);
129*c8dee2aaSAndroid Build Coastguard Worker     if (baseImageIsHdr) {
130*c8dee2aaSAndroid Build Coastguard Worker         W -= 1.f;
131*c8dee2aaSAndroid Build Coastguard Worker     }
132*c8dee2aaSAndroid Build Coastguard Worker 
133*c8dee2aaSAndroid Build Coastguard Worker     // Return the base image directly if the gainmap will not be applied at all.
134*c8dee2aaSAndroid Build Coastguard Worker     if (W == 0.f) {
135*c8dee2aaSAndroid Build Coastguard Worker         return baseImage->makeShader(baseSamplingOptions, &baseRectToDstRect);
136*c8dee2aaSAndroid Build Coastguard Worker     }
137*c8dee2aaSAndroid Build Coastguard Worker 
138*c8dee2aaSAndroid Build Coastguard Worker     // Create a color filter to transform from the base image's color space to the color space in
139*c8dee2aaSAndroid Build Coastguard Worker     // which the gainmap is to be applied.
140*c8dee2aaSAndroid Build Coastguard Worker     auto colorXformSdrToGainmap =
141*c8dee2aaSAndroid Build Coastguard Worker             SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
142*c8dee2aaSAndroid Build Coastguard Worker 
143*c8dee2aaSAndroid Build Coastguard Worker     // Create a color filter to transform from the color space in which the gainmap is applied to
144*c8dee2aaSAndroid Build Coastguard Worker     // the destination color space.
145*c8dee2aaSAndroid Build Coastguard Worker     auto colorXformGainmapToDst =
146*c8dee2aaSAndroid Build Coastguard Worker             SkColorFilterPriv::MakeColorSpaceXform(gainmapMathColorSpace, dstColorSpace);
147*c8dee2aaSAndroid Build Coastguard Worker 
148*c8dee2aaSAndroid Build Coastguard Worker     // The base image shader will convert into the color space in which the gainmap is applied.
149*c8dee2aaSAndroid Build Coastguard Worker     auto baseImageShader = baseImage->makeRawShader(baseSamplingOptions, &baseRectToDstRect)
150*c8dee2aaSAndroid Build Coastguard Worker                                    ->makeWithColorFilter(colorXformSdrToGainmap);
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker     // The gainmap image shader will ignore any color space that the gainmap has.
153*c8dee2aaSAndroid Build Coastguard Worker     auto gainmapImageShader =
154*c8dee2aaSAndroid Build Coastguard Worker             gainmapImage->makeRawShader(gainmapSamplingOptions, &gainmapRectToDstRect);
155*c8dee2aaSAndroid Build Coastguard Worker 
156*c8dee2aaSAndroid Build Coastguard Worker     // Create the shader to apply the gainmap.
157*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkShader> gainmapMathShader;
158*c8dee2aaSAndroid Build Coastguard Worker     {
159*c8dee2aaSAndroid Build Coastguard Worker         SkRuntimeShaderBuilder builder(gainmap_apply_effect());
160*c8dee2aaSAndroid Build Coastguard Worker         const SkColor4f logRatioMin({std::log(gainmapInfo.fGainmapRatioMin.fR),
161*c8dee2aaSAndroid Build Coastguard Worker                                      std::log(gainmapInfo.fGainmapRatioMin.fG),
162*c8dee2aaSAndroid Build Coastguard Worker                                      std::log(gainmapInfo.fGainmapRatioMin.fB),
163*c8dee2aaSAndroid Build Coastguard Worker                                      1.f});
164*c8dee2aaSAndroid Build Coastguard Worker         const SkColor4f logRatioMax({std::log(gainmapInfo.fGainmapRatioMax.fR),
165*c8dee2aaSAndroid Build Coastguard Worker                                      std::log(gainmapInfo.fGainmapRatioMax.fG),
166*c8dee2aaSAndroid Build Coastguard Worker                                      std::log(gainmapInfo.fGainmapRatioMax.fB),
167*c8dee2aaSAndroid Build Coastguard Worker                                      1.f});
168*c8dee2aaSAndroid Build Coastguard Worker         const int noGamma =
169*c8dee2aaSAndroid Build Coastguard Worker             gainmapInfo.fGainmapGamma.fR == 1.f &&
170*c8dee2aaSAndroid Build Coastguard Worker             gainmapInfo.fGainmapGamma.fG == 1.f &&
171*c8dee2aaSAndroid Build Coastguard Worker             gainmapInfo.fGainmapGamma.fB == 1.f;
172*c8dee2aaSAndroid Build Coastguard Worker         const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
173*c8dee2aaSAndroid Build Coastguard Worker         const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
174*c8dee2aaSAndroid Build Coastguard Worker         const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
175*c8dee2aaSAndroid Build Coastguard Worker         const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
176*c8dee2aaSAndroid Build Coastguard Worker                                   all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
177*c8dee2aaSAndroid Build Coastguard Worker                                   all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
178*c8dee2aaSAndroid Build Coastguard Worker                                   (colorTypeFlags == kGray_SkColorChannelFlag ||
179*c8dee2aaSAndroid Build Coastguard Worker                                    colorTypeFlags == kAlpha_SkColorChannelFlag ||
180*c8dee2aaSAndroid Build Coastguard Worker                                    colorTypeFlags == kRed_SkColorChannelFlag);
181*c8dee2aaSAndroid Build Coastguard Worker         const SkColor4f& epsilonBase =
182*c8dee2aaSAndroid Build Coastguard Worker                 baseImageIsHdr ? gainmapInfo.fEpsilonHdr : gainmapInfo.fEpsilonSdr;
183*c8dee2aaSAndroid Build Coastguard Worker         const SkColor4f& epsilonOther =
184*c8dee2aaSAndroid Build Coastguard Worker                 baseImageIsHdr ? gainmapInfo.fEpsilonSdr : gainmapInfo.fEpsilonHdr;
185*c8dee2aaSAndroid Build Coastguard Worker 
186*c8dee2aaSAndroid Build Coastguard Worker         const int isApple = gainmapInfo.fType == SkGainmapInfo::Type::kApple;
187*c8dee2aaSAndroid Build Coastguard Worker         const float appleG = 1.961f;
188*c8dee2aaSAndroid Build Coastguard Worker         const float appleH = gainmapInfo.fDisplayRatioHdr;
189*c8dee2aaSAndroid Build Coastguard Worker 
190*c8dee2aaSAndroid Build Coastguard Worker         builder.child("base") = baseImageShader;
191*c8dee2aaSAndroid Build Coastguard Worker         builder.child("gainmap") = gainmapImageShader;
192*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("logRatioMin") = logRatioMin;
193*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("logRatioMax") = logRatioMax;
194*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
195*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("epsilonBase") = epsilonBase;
196*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("epsilonOther") = epsilonOther;
197*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("noGamma") = noGamma;
198*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("singleChannel") = singleChannel;
199*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
200*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("gainmapIsRed") = gainmapIsRed;
201*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("W") = W;
202*c8dee2aaSAndroid Build Coastguard Worker 
203*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("isApple") = isApple;
204*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("appleG") = appleG;
205*c8dee2aaSAndroid Build Coastguard Worker         builder.uniform("appleH") = appleH;
206*c8dee2aaSAndroid Build Coastguard Worker 
207*c8dee2aaSAndroid Build Coastguard Worker         gainmapMathShader = builder.makeShader();
208*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(gainmapMathShader);
209*c8dee2aaSAndroid Build Coastguard Worker     }
210*c8dee2aaSAndroid Build Coastguard Worker 
211*c8dee2aaSAndroid Build Coastguard Worker     // Return a shader that will apply the gainmap and then convert to the destination color space.
212*c8dee2aaSAndroid Build Coastguard Worker     return gainmapMathShader->makeWithColorFilter(colorXformGainmapToDst);
213*c8dee2aaSAndroid Build Coastguard Worker }
214