xref: /aosp_15_r20/external/skia/tests/GainmapShaderTest.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 <cmath>
9 #include "include/core/SkAlphaType.h"
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkColorSpace.h"
14 #include "include/core/SkColorType.h"
15 #include "include/core/SkImage.h"
16 #include "include/core/SkImageInfo.h"
17 #include "include/core/SkPaint.h"
18 #include "include/core/SkPixmap.h"
19 #include "include/core/SkRect.h"
20 #include "include/core/SkRefCnt.h"
21 #include "include/core/SkSamplingOptions.h"
22 #include "include/core/SkShader.h"
23 #include "include/private/SkGainmapInfo.h"
24 #include "include/private/SkGainmapShader.h"
25 #include "tests/Test.h"
26 
approx_equal(const SkColor4f & a,const SkColor4f & b)27 static bool approx_equal(const SkColor4f& a, const SkColor4f& b) {
28     constexpr float kEpsilon = 1e-3f;
29     return std::abs(a.fR - b.fR) < kEpsilon && std::abs(a.fG - b.fG) < kEpsilon &&
30            std::abs(a.fB - b.fB) < kEpsilon && std::abs(a.fA - b.fA) < kEpsilon;
31 }
32 
33 // Create a 1x1 image with a specified color.
make_1x1_image(sk_sp<SkColorSpace> imageColorSpace,SkAlphaType imageAlphaType,SkColor4f imageColor,sk_sp<SkColorSpace> imageColorColorSpace=SkColorSpace::MakeSRGBLinear ())34 static sk_sp<SkImage> make_1x1_image(
35         sk_sp<SkColorSpace> imageColorSpace,
36         SkAlphaType imageAlphaType,
37         SkColor4f imageColor,
38         sk_sp<SkColorSpace> imageColorColorSpace = SkColorSpace::MakeSRGBLinear()) {
39     SkImageInfo bmInfo =
40             SkImageInfo::Make(1, 1, kRGBA_F32_SkColorType, imageAlphaType, imageColorSpace);
41     SkBitmap bm;
42     bm.allocPixels(bmInfo);
43 
44     SkImageInfo writePixelsInfo = SkImageInfo::Make(
45             1, 1, kRGBA_F32_SkColorType, kUnpremul_SkAlphaType, imageColorColorSpace);
46     SkPixmap writePixelsPixmap(writePixelsInfo, &imageColor, writePixelsInfo.minRowBytes());
47     bm.writePixels(writePixelsPixmap, 0, 0);
48     return SkImages::RasterFromBitmap(bm);
49 }
50 
51 // Return gainmap info that will scale 1 up to the specified hdrRatioMax.
simple_gainmap_info(float hdrRatioMax)52 static SkGainmapInfo simple_gainmap_info(float hdrRatioMax) {
53     SkGainmapInfo gainmapInfo;
54     gainmapInfo.fDisplayRatioSdr = 1.f;
55     gainmapInfo.fDisplayRatioHdr = hdrRatioMax;
56     gainmapInfo.fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
57     gainmapInfo.fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
58     gainmapInfo.fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
59     gainmapInfo.fGainmapRatioMax = {hdrRatioMax, hdrRatioMax, hdrRatioMax, 1.f};
60     return gainmapInfo;
61 }
62 
63 // Draw using a gainmap to a canvas with the specified HDR to SDR ratio and the specified color
64 // space. Return the result as unpremultiplied sRGB linear.
draw_1x1_gainmap(sk_sp<SkImage> baseImage,sk_sp<SkImage> gainmapImage,const SkGainmapInfo & gainmapInfo,float dstRatio,sk_sp<SkColorSpace> dstColorSpace=SkColorSpace::MakeSRGB ())65 static SkColor4f draw_1x1_gainmap(sk_sp<SkImage> baseImage,
66                                   sk_sp<SkImage> gainmapImage,
67                                   const SkGainmapInfo& gainmapInfo,
68                                   float dstRatio,
69                                   sk_sp<SkColorSpace> dstColorSpace = SkColorSpace::MakeSRGB()) {
70     constexpr auto kRect = SkRect::MakeWH(1.f, 1.f);
71     SkImageInfo canvasInfo =
72             SkImageInfo::Make(1, 1, kRGBA_F32_SkColorType, kPremul_SkAlphaType, dstColorSpace);
73     SkBitmap canvasBitmap;
74     canvasBitmap.allocPixels(canvasInfo);
75     canvasBitmap.eraseColor(SK_ColorTRANSPARENT);
76 
77     sk_sp<SkShader> shader = SkGainmapShader::Make(baseImage,
78                                                    kRect,
79                                                    SkSamplingOptions(),
80                                                    gainmapImage,
81                                                    kRect,
82                                                    SkSamplingOptions(),
83                                                    gainmapInfo,
84                                                    kRect,
85                                                    dstRatio,
86                                                    dstColorSpace);
87     SkPaint paint;
88     paint.setShader(shader);
89     SkCanvas canvas(canvasBitmap);
90     canvas.drawRect(kRect, paint);
91 
92     SkColor4f result = {0.f, 0.f, 0.f, 0.f};
93     SkImageInfo readPixelsInfo = SkImageInfo::Make(
94             1, 1, kRGBA_F32_SkColorType, kUnpremul_SkAlphaType, SkColorSpace::MakeSRGBLinear());
95     canvas.readPixels(readPixelsInfo, &result, sizeof(result), 0, 0);
96     return result;
97 }
98 
99 // Verify that the gainmap shader correctly applies the base, gainmap, and destination rectangles.
DEF_TEST(GainmapShader_rects,r)100 DEF_TEST(GainmapShader_rects, r) {
101     SkColor4f sdrColors[5][2] = {
102             {{-1.f, -1.f, -1.f, 1.0f}, {-1.f, -1.f, -1.f, 1.0f}},
103             {{1.0f, 1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 0.5f, 1.0f}},
104             {{1.0f, 0.5f, 1.0f, 1.0f}, {1.0f, 0.5f, 0.5f, 1.0f}},
105             {{0.5f, 1.0f, 1.0f, 1.0f}, {0.5f, 1.0f, 0.5f, 1.0f}},
106             {{0.5f, 0.5f, 1.0f, 1.0f}, {0.5f, 0.5f, 0.5f, 1.0f}},
107     };
108     SkPixmap sdrPixmap(SkImageInfo::Make(2, 5, kRGBA_F32_SkColorType, kOpaque_SkAlphaType),
109                        sdrColors,
110                        2 * sizeof(SkColor4f));
111     auto sdrImage = SkImages::RasterFromPixmap(sdrPixmap, nullptr, nullptr);
112     const auto sdrImageRect = SkRect::MakeXYWH(0.f, 1.f, 2.f, 4.f);
113 
114     // The top pixel indicates to gain only red, and the bottom pixel indicates to gain everything
115     // except red.
116     SkColor4f gainmapColors[2][2] = {
117             {{-1.f, -1.f, -1.f, 1.f}, {1.0f, 0.0f, 0.0f, 1.f}},
118             {{-1.f, -1.f, -1.f, 1.f}, {0.0f, 1.0f, 1.0f, 1.f}},
119     };
120     SkPixmap gainmapPixmap(SkImageInfo::Make(2, 2, kRGBA_F32_SkColorType, kOpaque_SkAlphaType),
121                            gainmapColors,
122                            2 * sizeof(SkColor4f));
123     auto gainmapImage = SkImages::RasterFromPixmap(gainmapPixmap, nullptr, nullptr);
124     const auto gainmapImageRect = SkRect::MakeXYWH(1.f, 0.f, 1.f, 2.f);
125     SkGainmapInfo gainmapInfo = simple_gainmap_info(2.f);
126     gainmapInfo.fEpsilonHdr[0] = 0.1f;
127 
128     SkImageInfo canvasInfo = SkImageInfo::Make(
129             4, 6, kRGBA_F32_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
130     SkBitmap canvasBitmap;
131     canvasBitmap.allocPixels(canvasInfo);
132     canvasBitmap.eraseColor(SK_ColorTRANSPARENT);
133     const auto canvasRect = SkRect::MakeXYWH(1.f, 1.f, 2.f, 4.f);
134 
135     sk_sp<SkShader> shader = SkGainmapShader::Make(sdrImage,
136                                                    sdrImageRect,
137                                                    SkSamplingOptions(),
138                                                    gainmapImage,
139                                                    gainmapImageRect,
140                                                    SkSamplingOptions(),
141                                                    gainmapInfo,
142                                                    canvasRect,
143                                                    gainmapInfo.fDisplayRatioHdr,
144                                                    canvasInfo.refColorSpace());
145     SkPaint paint;
146     paint.setShader(shader);
147     SkCanvas canvas(canvasBitmap);
148     canvas.drawRect(canvasRect, paint);
149 
150     // Compute and compare the expected colors.
151     // This is linearToSRGB(srgbToLinear(1.0)*2.0) = linearToSRGB(2.0).
152     constexpr float k10G = 1.353256028586302f;
153     // This is linearToSRGB(srgbToLinear(0.5)*2.0)
154     constexpr float k05G = 0.6858361015012847f;
155     // The 'R' component also has a fEpsilonHdr set.
156     // This is linearToSRGB(srgbToLinear(1.0)*2.0-0.1) = linearToSRGB(1.9).
157     constexpr float kR10G = 1.3234778541409058f;
158     // This is linearToSRGB(srgbToLinear(0.5)-0.1)
159     // The gain map is 0.f (no gain), but there are still affectd by the offset.
160     constexpr float kR05G = 0.371934685412575f;
161     SkColor4f expectedColors[4][2] = {
162             {{kR10G, 1.0f, 1.0f, 1.0f}, {kR10G, 1.0f, 0.5f, 1.0f}},
163             {{kR10G, 0.5f, 1.0f, 1.0f}, {kR10G, 0.5f, 0.5f, 1.0f}},
164             {{kR05G, k10G, k10G, 1.0f}, {kR05G, k10G, k05G, 1.0f}},
165             {{kR05G, k05G, k10G, 1.0f}, {kR05G, k05G, k05G, 1.0f}},
166     };
167     for (int y = 0; y < 4; ++y) {
168         for (int x = 0; x < 2; ++x) {
169             const auto color = canvasBitmap.getColor4f(x + 1, y + 1);
170             const auto& expected = expectedColors[y][x];
171             REPORTER_ASSERT(r,
172                             approx_equal(color, expected),
173                             "color (%.3f %.3f %.3f %.3f) does not match expected color (%.3f %.3f "
174                             "%.3f %.3f) at "
175                             "pixel (%d, %d)",
176                             color.fR,
177                             color.fG,
178                             color.fB,
179                             color.fA,
180                             expected.fR,
181                             expected.fG,
182                             expected.fB,
183                             expected.fA,
184                             x,
185                             y);
186         }
187     }
188 }
189 
DEF_TEST(GainmapShader_baseImageIsHdr,r)190 DEF_TEST(GainmapShader_baseImageIsHdr, r) {
191     SkColor4f hdrColors[4][2] = {
192             {{1.0f, 1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 0.5f, 1.0f}},
193             {{1.0f, 0.5f, 1.0f, 1.0f}, {1.0f, 0.5f, 0.5f, 1.0f}},
194             {{0.5f, 1.0f, 1.0f, 1.0f}, {0.5f, 1.0f, 0.5f, 1.0f}},
195             {{0.5f, 0.5f, 1.0f, 1.0f}, {0.5f, 0.5f, 0.5f, 1.0f}},
196     };
197     SkPixmap hdrPixmap(SkImageInfo::Make(2, 4, kRGBA_F32_SkColorType, kOpaque_SkAlphaType),
198                        hdrColors,
199                        2 * sizeof(SkColor4f));
200     auto hdrImage = SkImages::RasterFromPixmap(hdrPixmap, nullptr, nullptr);
201     const auto hdrImageRect = SkRect::MakeXYWH(0.f, 0.f, 2.f, 4.f);
202 
203     // The top pixel indicates to gain only red, and the bottom pixel indicates to gain everything
204     // except red.
205     SkColor4f gainmapColors[2][1] = {
206             {{1.0f, 0.0f, 0.0f, 1.f}},
207             {{0.0f, 1.0f, 1.0f, 1.f}},
208     };
209     SkPixmap gainmapPixmap(SkImageInfo::Make(1, 2, kRGBA_F32_SkColorType, kOpaque_SkAlphaType),
210                            gainmapColors,
211                            1 * sizeof(SkColor4f));
212     auto gainmapImage = SkImages::RasterFromPixmap(gainmapPixmap, nullptr, nullptr);
213     const auto gainmapImageRect = SkRect::MakeXYWH(0.f, 0.f, 1.f, 2.f);
214     SkGainmapInfo gainmapInfo = simple_gainmap_info(2.f);
215     gainmapInfo.fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
216     gainmapInfo.fEpsilonSdr[0] = 0.1f;
217 
218     SkImageInfo canvasInfo = SkImageInfo::Make(
219             2, 4, kRGBA_F32_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
220     SkBitmap canvasBitmap;
221     canvasBitmap.allocPixels(canvasInfo);
222     canvasBitmap.eraseColor(SK_ColorTRANSPARENT);
223     const auto canvasRect = SkRect::MakeXYWH(0.f, 0.f, 2.f, 4.f);
224 
225     sk_sp<SkShader> shader = SkGainmapShader::Make(hdrImage,
226                                                    hdrImageRect,
227                                                    SkSamplingOptions(),
228                                                    gainmapImage,
229                                                    gainmapImageRect,
230                                                    SkSamplingOptions(),
231                                                    gainmapInfo,
232                                                    canvasRect,
233                                                    gainmapInfo.fDisplayRatioSdr,
234                                                    canvasInfo.refColorSpace());
235     SkPaint paint;
236     paint.setShader(shader);
237     SkCanvas canvas(canvasBitmap);
238     canvas.drawRect(canvasRect, paint);
239 
240     // Compute and compare the expected colors.
241     // This is linearToSRGB(srgbToLinear(1.0)*0.5) = linearToSRGB(0.5).
242     constexpr float k10G = 0.7353569830524495f;
243     // This is linearToSRGB(srgbToLinear(0.5)*0.5)
244     constexpr float k05G = 0.3607802138332792f;
245     // The 'R' component also has a fEpsilonSdr set.
246     // This is linearToSRGB(srgbToLinear(1.0)*0.5-0.1) = linearToSRGB(0.4).
247     constexpr float kR10G = 0.6651850846308363f;
248     // This is linearToSRGB(srgbToLinear(0.5)-0.1)
249     // The gain map is 0.f (no gain), but there are still affectd by the offset.
250     constexpr float kR05G = 0.371934685412575f;
251     SkColor4f expectedColors[4][2] = {
252             {{kR10G, 1.0f, 1.0f, 1.0f}, {kR10G, 1.0f, 0.5f, 1.0f}},
253             {{kR10G, 0.5f, 1.0f, 1.0f}, {kR10G, 0.5f, 0.5f, 1.0f}},
254             {{kR05G, k10G, k10G, 1.0f}, {kR05G, k10G, k05G, 1.0f}},
255             {{kR05G, k05G, k10G, 1.0f}, {kR05G, k05G, k05G, 1.0f}},
256     };
257     for (int y = 0; y < 4; ++y) {
258         for (int x = 0; x < 2; ++x) {
259             const auto color = canvasBitmap.getColor4f(x, y);
260             const auto& expected = expectedColors[y][x];
261             REPORTER_ASSERT(r,
262                             approx_equal(color, expected),
263                             "color (%.3f %.3f %.3f %.3f) does not match expected color (%.3f %.3f "
264                             "%.3f %.3f) at "
265                             "pixel (%d, %d)",
266                             color.fR,
267                             color.fG,
268                             color.fB,
269                             color.fA,
270                             expected.fR,
271                             expected.fG,
272                             expected.fB,
273                             expected.fA,
274                             x,
275                             y);
276         }
277     }
278 }
279 
280 // Verify that the gainmap shader isn't affected by the color spaces of the base, gainmap, or
281 // destination. But the fGainmapMathColorSpace is taken into account.
DEF_TEST(GainmapShader_colorSpace,r)282 DEF_TEST(GainmapShader_colorSpace, r) {
283     auto sdrColorSpace =
284             SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kSRGB)->makeColorSpin();
285     auto gainmapColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, SkNamedGamut::kRec2020);
286     auto dstColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, SkNamedGamut::kDisplayP3);
287 
288     constexpr SkColor4f kSdrColor = {0.25f, 0.5f, 1.f, 1.f};
289     constexpr SkColor4f kGainmapColor = {
290             0.0f,  // The sRGB G channel will have a exp2(0.0)=1.000 gain.
291             0.5f,  // The sRGB B channel will have a exp2(0.5)=1.414 gain.
292             1.0f,  // The sRGB R channel will have a exp2(1.0)=2.000 gain.
293             1.f};
294     constexpr SkColor4f kExpectedColor = {0.5f, 0.5f, 1.414f, 1.f};
295 
296     auto sdrImage = make_1x1_image(sdrColorSpace, kOpaque_SkAlphaType, kSdrColor);
297     auto gainmapImage = make_1x1_image(
298             gainmapColorSpace, kOpaque_SkAlphaType, kGainmapColor, gainmapColorSpace);
299     SkGainmapInfo gainmapInfo = simple_gainmap_info(2.f);
300 
301     auto color = draw_1x1_gainmap(
302             sdrImage, gainmapImage, gainmapInfo, gainmapInfo.fDisplayRatioHdr, dstColorSpace);
303     REPORTER_ASSERT(r, approx_equal(color, kExpectedColor));
304 
305     // Setting fGainmapMathColorSpace to the base image's color space does not change the result.
306     gainmapInfo.fGainmapMathColorSpace = sdrColorSpace;
307     color = draw_1x1_gainmap(
308             sdrImage, gainmapImage, gainmapInfo, gainmapInfo.fDisplayRatioHdr, dstColorSpace);
309     REPORTER_ASSERT(r, approx_equal(color, kExpectedColor));
310 
311     // Setting fGainmapMathColorSpace ot a different color space does change the result.
312     gainmapInfo.fGainmapMathColorSpace =
313             SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, SkNamedGamut::kRec2020);
314     color = draw_1x1_gainmap(
315             sdrImage, gainmapImage, gainmapInfo, gainmapInfo.fDisplayRatioHdr, dstColorSpace);
316     REPORTER_ASSERT(r, !approx_equal(color, kExpectedColor));
317 }
318 
319 // Verify that a fully applied Apple gainmap maps the specification.
DEF_TEST(GainmapShader_apple,r)320 DEF_TEST(GainmapShader_apple, r) {
321     constexpr SkColor4f kSdrColor = {0.25f, 0.5f, 1.f, 1.f};
322     const SkColor4f kGainmapColor = {0.0f,       // The R channel will have a linear value of 0.0.
323                                      0.702250f,  // The G channel will have a linear value of 0.5.
324                                      1.0f,       // The B channel will have a linear value 0f 1.0.
325                                      1.f};
326 
327     // Set the HDR headroom to 5.0.
328     const float kH = 5.f;
329 
330     const SkColor4f kExpectedColor = {0.25f * (1 + (kH - 1) * 0.0f),  // 0.25,
331                                       0.50f * (1 + (kH - 1) * 0.5f),  // 0.5,
332                                       1.00f * (1 + (kH - 1) * 1.0f),  // 5.0,
333                                       1.f};
334 
335     auto sdrImage = make_1x1_image(SkColorSpace::MakeSRGB(), kOpaque_SkAlphaType, kSdrColor);
336     auto gainmapImage = make_1x1_image(nullptr, kOpaque_SkAlphaType, kGainmapColor);
337     SkGainmapInfo gainmapInfo = simple_gainmap_info(kH);
338     gainmapInfo.fType = SkGainmapInfo::Type::kApple;
339 
340     auto color = draw_1x1_gainmap(
341             sdrImage, gainmapImage, gainmapInfo, kH, SkColorSpace::MakeSRGBLinear());
342 
343     // Note that (0.250, 1.548, 5.000) is *not* an acceptable answer here (even though it's sort-of
344     // close). That number is the result of not using the the Apple-compatible math.
345     REPORTER_ASSERT(r, approx_equal(color, kExpectedColor));
346 }
347