• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2025 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/codec/SkAndroidCodec.h"
9 #include "include/codec/SkCodec.h"
10 #include "include/codec/SkPngDecoder.h"
11 #include "include/core/SkBitmap.h"
12 #include "include/core/SkCanvas.h"
13 #include "include/core/SkColor.h"
14 #include "include/core/SkColorType.h"
15 #include "include/core/SkImage.h"
16 #include "include/core/SkImageInfo.h"
17 #include "include/core/SkShader.h"
18 #include "include/core/SkSize.h"
19 #include "include/core/SkStream.h"
20 #include "include/encode/SkPngEncoder.h"
21 #include "include/private/SkGainmapInfo.h"
22 #include "include/private/SkGainmapShader.h"
23 #include "tests/Test.h"
24 #include "tools/Resources.h"
25 
26 #include <cstring>
27 #include <memory>
28 #include <utility>
29 
30 namespace {
31 
32 // Return true if the relative difference between x and y is less than epsilon.
approx_eq(float x,float y,float epsilon)33 static bool approx_eq(float x, float y, float epsilon) {
34     float numerator = std::abs(x - y);
35     // To avoid being too sensitive around zero, set the minimum denominator to epsilon.
36     float denominator = std::max(std::min(std::abs(x), std::abs(y)), epsilon);
37     if (numerator / denominator > epsilon) {
38         return false;
39     }
40     return true;
41 }
42 
approx_eq(const SkColor4f & x,const SkColor4f & y,float epsilon)43 static bool approx_eq(const SkColor4f& x, const SkColor4f& y, float epsilon) {
44     return approx_eq(x.fR, y.fR, epsilon) && approx_eq(x.fG, y.fG, epsilon) &&
45            approx_eq(x.fB, y.fB, epsilon);
46 }
47 
48 template <typename Reporter>
expect_approx_eq_info(Reporter & r,const SkGainmapInfo & a,const SkGainmapInfo & b)49 void expect_approx_eq_info(Reporter& r, const SkGainmapInfo& a, const SkGainmapInfo& b) {
50     float kEpsilon = 1e-4f;
51     REPORTER_ASSERT(r, approx_eq(a.fGainmapRatioMin, b.fGainmapRatioMin, kEpsilon));
52     REPORTER_ASSERT(r, approx_eq(a.fGainmapRatioMin, b.fGainmapRatioMin, kEpsilon));
53     REPORTER_ASSERT(r, approx_eq(a.fGainmapGamma, b.fGainmapGamma, kEpsilon));
54     REPORTER_ASSERT(r, approx_eq(a.fEpsilonSdr, b.fEpsilonSdr, kEpsilon));
55     REPORTER_ASSERT(r, approx_eq(a.fEpsilonHdr, b.fEpsilonHdr, kEpsilon));
56     REPORTER_ASSERT(r, approx_eq(a.fDisplayRatioSdr, b.fDisplayRatioSdr, kEpsilon));
57     REPORTER_ASSERT(r, approx_eq(a.fDisplayRatioHdr, b.fDisplayRatioHdr, kEpsilon));
58     REPORTER_ASSERT(r, a.fType == b.fType);
59     REPORTER_ASSERT(r, a.fBaseImageType == b.fBaseImageType);
60 
61     REPORTER_ASSERT(r, !!a.fGainmapMathColorSpace == !!b.fGainmapMathColorSpace);
62     if (a.fGainmapMathColorSpace) {
63         skcms_TransferFunction a_fn;
64         skcms_Matrix3x3 a_m;
65         a.fGainmapMathColorSpace->transferFn(&a_fn);
66         a.fGainmapMathColorSpace->toXYZD50(&a_m);
67         skcms_TransferFunction b_fn;
68         skcms_Matrix3x3 b_m;
69         b.fGainmapMathColorSpace->transferFn(&b_fn);
70         b.fGainmapMathColorSpace->toXYZD50(&b_m);
71 
72         REPORTER_ASSERT(r, approx_eq(a_fn.g, b_fn.g, kEpsilon));
73         REPORTER_ASSERT(r, approx_eq(a_fn.a, b_fn.a, kEpsilon));
74         REPORTER_ASSERT(r, approx_eq(a_fn.b, b_fn.b, kEpsilon));
75         REPORTER_ASSERT(r, approx_eq(a_fn.c, b_fn.c, kEpsilon));
76         REPORTER_ASSERT(r, approx_eq(a_fn.d, b_fn.d, kEpsilon));
77         REPORTER_ASSERT(r, approx_eq(a_fn.e, b_fn.e, kEpsilon));
78         REPORTER_ASSERT(r, approx_eq(a_fn.f, b_fn.f, kEpsilon));
79 
80         // The round-trip of the color space through the ICC profile loses significant precision.
81         // Use a larger epsilon for it.
82         const float kMatrixEpsilon = 1e-2f;
83         for (int i = 0; i < 3; ++i) {
84             for (int j = 0; j < 3; ++j) {
85                 REPORTER_ASSERT(r, approx_eq(a_m.vals[i][j], b_m.vals[i][j], kMatrixEpsilon));
86             }
87         }
88     }
89 }
90 
91 }  // namespace
92 
93 // Decode an image and its gainmap.
94 template <typename Reporter>
decode_all(Reporter & r,std::unique_ptr<SkStream> stream,SkBitmap & baseBitmap,SkBitmap & gainmapBitmap,SkGainmapInfo & gainmapInfo,bool & decodedGainmap)95 void decode_all(Reporter& r,
96                 std::unique_ptr<SkStream> stream,
97                 SkBitmap& baseBitmap,
98                 SkBitmap& gainmapBitmap,
99                 SkGainmapInfo& gainmapInfo,
100                 bool& decodedGainmap) {
101     // Decode the base bitmap.
102     SkCodec::Result result = SkCodec::kSuccess;
103     std::unique_ptr<SkCodec> baseCodec =
104             SkPngDecoder::Decode(std::move(stream), &result);
105     REPORTER_ASSERT(r, baseCodec);
106     baseBitmap.allocPixels(baseCodec->getInfo());
107     REPORTER_ASSERT(r,
108                     SkCodec::kSuccess == baseCodec->getPixels(baseBitmap.info(),
109                                                               baseBitmap.getPixels(),
110                                                               baseBitmap.rowBytes()));
111     std::unique_ptr<SkAndroidCodec> androidCodec =
112             SkAndroidCodec::MakeFromCodec(std::move(baseCodec));
113     REPORTER_ASSERT(r, androidCodec);
114 
115     // Extract the gainmap info and codec.
116     std::unique_ptr<SkAndroidCodec> gainmapCodec;
117 
118     decodedGainmap = androidCodec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec);
119     if (decodedGainmap) {
120         REPORTER_ASSERT(r, gainmapCodec);
121         // Decode the gainmap bitmap.
122         gainmapBitmap.allocPixels(gainmapCodec->getInfo());
123         REPORTER_ASSERT(
124                 r,
125                 SkCodec::kSuccess == gainmapCodec->getAndroidPixels(gainmapBitmap.info(),
126                                                                     gainmapBitmap.getPixels(),
127                                                                     gainmapBitmap.rowBytes()));
128     }
129 }
130 
DEF_TEST(AndroidCodec_pngGainmapDecode,r)131 DEF_TEST(AndroidCodec_pngGainmapDecode, r) {
132     const struct Rec {
133         const char* path;
134         SkISize dimensions;
135         SkColor originColor;
136         SkColor farCornerColor;
137         SkGainmapInfo info;
138     } recs[] = {
139             {"images/gainmap.png",
140              SkISize::Make(32, 32),
141              0xffffffff,
142              0xff000000,
143              {{25.f, 0.5f, 1.f, 1.f},
144               {2.f, 4.f, 8.f, 1.f},
145               {0.5, 1.f, 2.f, 1.f},
146               {0.01f, 0.001f, 0.0001f, 1.f},
147               {0.0001f, 0.001f, 0.01f, 1.f},
148               2.f,
149               4.f,
150               SkGainmapInfo::BaseImageType::kHDR,
151               SkGainmapInfo::Type::kDefault,
152               nullptr}},
153     };
154 
155     for (const auto& rec : recs) {
156         auto stream = GetResourceAsStream(rec.path, false);
157         REPORTER_ASSERT(r, stream);
158 
159         SkBitmap baseBitmap;
160         SkBitmap gainmapBitmap;
161         SkGainmapInfo gainmapInfo;
162         bool decodedGainmap;
163         decode_all(r, std::move(stream), baseBitmap, gainmapBitmap, gainmapInfo, decodedGainmap);
164 
165         REPORTER_ASSERT(r, decodedGainmap);
166 
167         // Spot-check the image size and pixels.
168         REPORTER_ASSERT(r, gainmapBitmap.dimensions() == rec.dimensions);
169         REPORTER_ASSERT(r, gainmapBitmap.getColor(0, 0) == rec.originColor);
170         REPORTER_ASSERT(r,
171                         gainmapBitmap.getColor(rec.dimensions.fWidth - 1,
172                                                rec.dimensions.fHeight - 1) == rec.farCornerColor);
173 
174         // Verify the gainmap rendering parameters.
175         expect_approx_eq_info(r, rec.info, gainmapInfo);
176     }
177 }
178 
DEF_TEST(AndroidCodec_pngGainmapInvalidDecode,r)179 DEF_TEST(AndroidCodec_pngGainmapInvalidDecode, r) {
180     const char* paths[] = {
181             "images/gainmap_no_gdat.png",
182             "images/gainmap_gdat_no_gmap.png",
183 
184     };
185 
186     for (const auto& path : paths) {
187         auto stream = GetResourceAsStream(path, false);
188         REPORTER_ASSERT(r, stream);
189 
190         SkBitmap baseBitmap;
191         SkBitmap gainmapBitmap;
192         SkGainmapInfo gainmapInfo;
193         bool decodedGainmap;
194         decode_all(r, std::move(stream), baseBitmap, gainmapBitmap, gainmapInfo, decodedGainmap);
195 
196         REPORTER_ASSERT(r, !decodedGainmap);
197     }
198 }
199 
DEF_TEST(AndroidCodec_pngGainmapEncodeAndDecode,r)200 DEF_TEST(AndroidCodec_pngGainmapEncodeAndDecode, r) {
201     SkColorType colorTypes[] = {
202             kRGBA_8888_SkColorType,
203             kAlpha_8_SkColorType,
204     };
205 
206     for (const auto& colorType : colorTypes) {
207         SkDynamicMemoryWStream stream;
208         SkGainmapInfo sourceGainmapInfo;
209         sourceGainmapInfo.fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
210         sourceGainmapInfo.fGainmapRatioMax = {5.f, 5.f, 5.f, 1.f};
211         sourceGainmapInfo.fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
212         sourceGainmapInfo.fEpsilonSdr = {0.01f, 0.01f, 0.01f, 0.01f};
213         sourceGainmapInfo.fEpsilonHdr = {0.001f, 0.001f, 0.001f, 0.001f};
214         sourceGainmapInfo.fDisplayRatioSdr = 1.f;
215         sourceGainmapInfo.fDisplayRatioHdr = 3.f;
216         sourceGainmapInfo.fGainmapMathColorSpace = SkColorSpace::MakeSRGB();
217         SkBitmap sourceBase;
218         sourceBase.allocPixels(
219                 SkImageInfo::Make(16, 16, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
220         sourceBase.eraseColor(SK_ColorRED);
221         SkBitmap sourceGainmap;
222         sourceGainmap.allocPixels(SkImageInfo::Make(4, 4, colorType, kOpaque_SkAlphaType));
223         sourceGainmap.eraseColor(SK_ColorGREEN);
224 
225         SkPngEncoder::Options options;
226         options.fGainmap = &(sourceGainmap.pixmap());
227         options.fGainmapInfo = &sourceGainmapInfo;
228         REPORTER_ASSERT(r, SkPngEncoder::Encode(&stream, sourceBase.pixmap(), options));
229 
230         SkBitmap baseBitmap;
231         SkBitmap gainmapBitmap;
232         SkGainmapInfo gainmapInfo;
233         bool decodedGainmap;
234         decode_all(
235                 r, stream.detachAsStream(), baseBitmap, gainmapBitmap, gainmapInfo, decodedGainmap);
236 
237         REPORTER_ASSERT(r, decodedGainmap);
238 
239         REPORTER_ASSERT(r, baseBitmap.dimensions().fHeight == 16);
240         REPORTER_ASSERT(r, baseBitmap.dimensions().fWidth == 16);
241         REPORTER_ASSERT(r, baseBitmap.getColor(0, 0) == SK_ColorRED);
242         REPORTER_ASSERT(r, baseBitmap.getColor(15, 15) == SK_ColorRED);
243 
244         REPORTER_ASSERT(r, gainmapBitmap.dimensions().fHeight == 4);
245         REPORTER_ASSERT(r, gainmapBitmap.dimensions().fWidth == 4);
246 
247         if (colorType == kAlpha_8_SkColorType) {
248             REPORTER_ASSERT(r, gainmapBitmap.getColor(0, 0) == SK_ColorBLACK);
249             REPORTER_ASSERT(r, gainmapBitmap.getColor(3, 3) == SK_ColorBLACK);
250         } else {
251             REPORTER_ASSERT(r, gainmapBitmap.getColor(0, 0) == SK_ColorGREEN);
252             REPORTER_ASSERT(r, gainmapBitmap.getColor(3, 3) == SK_ColorGREEN);
253         }
254         // Verify the gainmap rendering parameters.
255         if (colorType == kAlpha_8_SkColorType) {
256             sourceGainmapInfo.fGainmapMathColorSpace = nullptr;
257         }
258 
259         expect_approx_eq_info(r, sourceGainmapInfo, gainmapInfo);
260     }
261 }
262