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