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