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/SkColorSpace.h"
11 #include "include/core/SkImage.h"
12 #include "include/core/SkShader.h"
13 #include "include/effects/SkRuntimeEffect.h"
14 #include "include/private/SkGainmapInfo.h"
15 #include "src/core/SkColorFilterPriv.h"
16 #include "src/core/SkImageInfoPriv.h"
17
18 #ifdef SK_ENABLE_SKSL
19 static constexpr char gGainmapSKSL[] =
20 "uniform shader base;"
21 "uniform shader gainmap;"
22 "uniform half4 logRatioMin;"
23 "uniform half4 logRatioMax;"
24 "uniform half4 gainmapGamma;"
25 "uniform half4 epsilonSdr;"
26 "uniform half4 epsilonHdr;"
27 "uniform half W;"
28 "uniform int gainmapIsAlpha;"
29 "uniform int gainmapIsRed;"
30 "uniform int singleChannel;"
31 "uniform int noGamma;"
32 ""
33 "half4 main(float2 coord) {"
34 " half4 S = base.eval(coord);"
35 " half4 G = gainmap.eval(coord);"
36 " if (gainmapIsAlpha == 1) {"
37 " G = half4(G.a, G.a, G.a, 1.0);"
38 " }"
39 " if (gainmapIsRed == 1) {"
40 " G = half4(G.r, G.r, G.r, 1.0);"
41 " }"
42 " if (singleChannel == 1) {"
43 " half L;"
44 " if (noGamma == 1) {"
45 " L = mix(logRatioMin.r, logRatioMax.r, G.r);"
46 " } else {"
47 " L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));"
48 " }"
49 " half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;"
50 " return half4(H.r, H.g, H.b, S.a);"
51 " } else {"
52 " half3 L;"
53 " if (noGamma == 1) {"
54 " L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);"
55 " } else {"
56 " L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));"
57 " }"
58 " half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;"
59 " return half4(H.r, H.g, H.b, S.a);"
60 " }"
61 "}";
62
gainmap_apply_effect()63 static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
64 static const SkRuntimeEffect* effect =
65 SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {}).effect.release();
66 SkASSERT(effect);
67 return sk_ref_sp(effect);
68 }
69
all_channels_equal(const SkColor4f & c)70 static bool all_channels_equal(const SkColor4f& c) {
71 return c.fR == c.fG && c.fR == c.fB;
72 }
73 #endif // SK_ENABLE_SKSL
74
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)75 sk_sp<SkShader> SkGainmapShader::Make(const sk_sp<const SkImage>& baseImage,
76 const SkRect& baseRect,
77 const SkSamplingOptions& baseSamplingOptions,
78 const sk_sp<const SkImage>& gainmapImage,
79 const SkRect& gainmapRect,
80 const SkSamplingOptions& gainmapSamplingOptions,
81 const SkGainmapInfo& gainmapInfo,
82 const SkRect& dstRect,
83 float dstHdrRatio,
84 sk_sp<SkColorSpace> dstColorSpace) {
85 #ifdef SK_ENABLE_SKSL
86 sk_sp<SkColorSpace> baseColorSpace =
87 baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
88
89 // Determine the color space in which the gainmap math is to be applied.
90 sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
91 if (!dstColorSpace) {
92 dstColorSpace = SkColorSpace::MakeSRGB();
93 }
94
95 // Create a color filter to transform from the base image's color space to the color space in
96 // which the gainmap is to be applied.
97 auto colorXformSdrToGainmap =
98 SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
99
100 // Create a color filter to transform from the color space in which the gainmap is applied to
101 // the destination color space.
102 auto colorXformGainmapToDst =
103 SkColorFilterPriv::MakeColorSpaceXform(gainmapMathColorSpace, dstColorSpace);
104
105 // The base image shader will convert into the color space in which the gainmap is applied.
106 const SkMatrix baseRectToDstRect = SkMatrix::RectToRect(baseRect, dstRect);
107 auto baseImageShader = baseImage->makeRawShader(baseSamplingOptions, &baseRectToDstRect)
108 ->makeWithColorFilter(colorXformSdrToGainmap);
109
110 // The gainmap image shader will ignore any color space that the gainmap has.
111 const SkMatrix gainmapRectToDstRect = SkMatrix::RectToRect(gainmapRect, dstRect);
112 auto gainmapImageShader =
113 gainmapImage->makeRawShader(gainmapSamplingOptions, &gainmapRectToDstRect);
114
115 // Create the shader to apply the gainmap.
116 sk_sp<SkShader> gainmapMathShader;
117 {
118 SkRuntimeShaderBuilder builder(gainmap_apply_effect());
119 const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
120 sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
121 sk_float_log(gainmapInfo.fGainmapRatioMin.fB),
122 1.f});
123 const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
124 sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
125 sk_float_log(gainmapInfo.fGainmapRatioMax.fB),
126 1.f});
127 const float Wunclamped =
128 (sk_float_log(dstHdrRatio) - sk_float_log(gainmapInfo.fDisplayRatioSdr)) /
129 (sk_float_log(gainmapInfo.fDisplayRatioHdr) -
130 sk_float_log(gainmapInfo.fDisplayRatioSdr));
131 const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
132 const int noGamma =
133 gainmapInfo.fGainmapGamma.fR == 1.f &&
134 gainmapInfo.fGainmapGamma.fG == 1.f &&
135 gainmapInfo.fGainmapGamma.fB == 1.f;
136 const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
137 const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
138 const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
139 const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
140 all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
141 all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
142 (colorTypeFlags == kGray_SkColorChannelFlag ||
143 colorTypeFlags == kAlpha_SkColorChannelFlag ||
144 colorTypeFlags == kRed_SkColorChannelFlag);
145 builder.child("base") = baseImageShader;
146 builder.child("gainmap") = gainmapImageShader;
147 builder.uniform("logRatioMin") = logRatioMin;
148 builder.uniform("logRatioMax") = logRatioMax;
149 builder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
150 builder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
151 builder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
152 builder.uniform("noGamma") = noGamma;
153 builder.uniform("singleChannel") = singleChannel;
154 builder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
155 builder.uniform("gainmapIsRed") = gainmapIsRed;
156 builder.uniform("W") = W;
157 gainmapMathShader = builder.makeShader();
158 SkASSERT(gainmapMathShader);
159 }
160
161 // Return a shader that will apply the gainmap and then convert to the destination color space.
162 return gainmapMathShader->makeWithColorFilter(colorXformGainmapToDst);
163 #else
164 // This shader is currently only implemented using SkSL.
165 return nullptr;
166 #endif
167 }
168