1 /*
2 * Copyright 2020 Google LLC
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/core/SkTypes.h"
9 #ifdef SK_ENABLE_NDK_IMAGES
10 #include "include/core/SkColor.h"
11 #include "include/core/SkImageEncoder.h"
12 #include "include/core/SkImageGenerator.h"
13 #include "include/private/SkMalloc.h"
14 #include "src/images/SkImageEncoderPriv.h"
15 #include "tests/Test.h"
16 #include "tools/Resources.h"
17 #include "tools/ToolUtils.h"
18
19 #include <stdint.h>
20 #include <vector>
21
22 static const char* kPng = "png";
23 static const char* kJpeg = "jpeg";
24 static const char* kWebpLossless = "webp_lossless";
25 static const char* kWebpLossy = "webp_lossy";
26
27 namespace {
28 static const struct {
29 SkEncodedImageFormat format;
30 int quality;
31 const char* name;
32 } gRecs[] = {
33 { SkEncodedImageFormat::kPNG, 100, kPng},
34 { SkEncodedImageFormat::kJPEG, 100, kJpeg},
35 { SkEncodedImageFormat::kWEBP, 100, kWebpLossless},
36 { SkEncodedImageFormat::kWEBP, 80, kWebpLossy},
37 };
38 }
39
encode_ndk(const SkPixmap & pmap,SkEncodedImageFormat format,int quality)40 static sk_sp<SkData> encode_ndk(const SkPixmap& pmap, SkEncodedImageFormat format, int quality) {
41 SkDynamicMemoryWStream stream;
42 return SkEncodeImageWithNDK(&stream, pmap, format, quality) ? stream.detachAsData() : nullptr;
43 }
44
DEF_TEST(NdkEncode,r)45 DEF_TEST(NdkEncode, r) {
46 for (auto ct : { kRGBA_8888_SkColorType,
47 kRGB_565_SkColorType,
48 kRGBA_F16_SkColorType }) {
49 SkBitmap bm;
50 bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType));
51 bm.eraseColor(SK_ColorBLUE);
52 for (const auto& rec : gRecs) {
53 auto encoded = encode_ndk(bm.pixmap(), rec.format, rec.quality);
54 if (!encoded) {
55 ERRORF(r, "Failed to encode %s to %s\n", ToolUtils::colortype_name(ct), rec.name);
56 continue;
57 }
58 auto gen = SkImageGenerator::MakeFromEncoded(std::move(encoded));
59 if (!gen) {
60 ERRORF(r, "Failed to decode from %s as %s\n", ToolUtils::colortype_name(ct),
61 rec.name);
62 continue;
63 }
64
65 if (rec.name == kPng && bm.colorType() == kRGB_565_SkColorType) {
66 REPORTER_ASSERT(r, gen->getInfo().colorType() == kRGB_565_SkColorType);
67 } else {
68 REPORTER_ASSERT(r, gen->getInfo().colorType() == kN32_SkColorType);
69 }
70
71 SkBitmap bm2;
72 bm2.allocPixels(bm.info());
73 REPORTER_ASSERT(r, gen->getPixels(bm2.pixmap()));
74
75 for (int x = 0; x < bm.width(); x++)
76 for (int y = 0; y < bm.height(); y++) {
77 SkColor orig = bm .getColor(x, y);
78 SkColor actual = bm2.getColor(x, y);
79
80 REPORTER_ASSERT(r, SkColorGetA(orig) == SkColorGetA(actual));
81 REPORTER_ASSERT(r, SkColorGetA(orig) == 0xFF);
82
83 if (rec.name == kPng || rec.name == kWebpLossless) {
84 REPORTER_ASSERT(r, orig == actual);
85 } else {
86 int diffR = std::abs((int) SkColorGetR(orig) - (int) SkColorGetR(actual));
87 int diffG = std::abs((int) SkColorGetG(orig) - (int) SkColorGetG(actual));
88 int diffB = std::abs((int) SkColorGetB(orig) - (int) SkColorGetB(actual));
89 REPORTER_ASSERT(r, diffR <= 2 && diffG <= 1 && diffB <= 1);
90 }
91 }
92 }
93 }
94 }
95
DEF_TEST(NdkEncode_unsupportedFormats,r)96 DEF_TEST(NdkEncode_unsupportedFormats, r) {
97 for (auto ct : { kRGBA_8888_SkColorType,
98 kRGB_565_SkColorType,
99 kRGBA_F16_SkColorType }) {
100 SkBitmap bm;
101 bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType));
102 bm.eraseColor(SK_ColorBLUE);
103 for (auto format : { SkEncodedImageFormat::kBMP,
104 SkEncodedImageFormat::kGIF,
105 SkEncodedImageFormat::kICO,
106 SkEncodedImageFormat::kWBMP,
107 SkEncodedImageFormat::kPKM,
108 SkEncodedImageFormat::kKTX,
109 SkEncodedImageFormat::kASTC,
110 SkEncodedImageFormat::kDNG,
111 SkEncodedImageFormat::kHEIF }) {
112 REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), format, 100));
113 }
114 }
115 }
116
DEF_TEST(NdkEncode_badQuality,r)117 DEF_TEST(NdkEncode_badQuality, r) {
118 for (auto ct : { kRGBA_8888_SkColorType,
119 kRGB_565_SkColorType,
120 kRGBA_F16_SkColorType }) {
121 SkBitmap bm;
122 bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType));
123 bm.eraseColor(SK_ColorBLUE);
124 for (auto format : { SkEncodedImageFormat::kJPEG,
125 SkEncodedImageFormat::kPNG,
126 SkEncodedImageFormat::kWEBP }) {
127 for (int quality : {-1, -100, 101, 200}) {
128 REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), format, quality));
129 }
130 }
131 }
132 }
133
DEF_TEST(NdkEncode_nullPixels,r)134 DEF_TEST(NdkEncode_nullPixels, r) {
135 for (auto info : { SkImageInfo::MakeUnknown(),
136 SkImageInfo::MakeN32Premul(10, 10),
137 SkImageInfo::MakeN32Premul(0, 0)}) {
138 SkPixmap pm(info, nullptr, info.minRowBytes());
139 for (const auto& rec : gRecs) {
140 REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality));
141 }
142 }
143 }
144
DEF_TEST(NdkEncode_badInfo,r)145 DEF_TEST(NdkEncode_badInfo, r) {
146 // Allocate an arbitrary amount of memory. These infos don't have a natural
147 // amount to allocate, and the encoder shouldn't touch the memory anyway.
148 // But this allows us to verify that the bad info fails, even when the pixel
149 // pointer is not null.
150 void* pixels = sk_malloc_throw(1024);
151 std::vector<SkPixmap> pixmaps{ SkPixmap(SkImageInfo::MakeN32Premul(-10, 10), pixels, 1000),
152 SkPixmap(SkImageInfo::MakeN32Premul(10, -10), pixels, 200),
153 SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 20),
154 SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 41),
155 SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 0),
156 SkPixmap(SkImageInfo::MakeN32Premul( 0, 0), pixels, 40)};
157 if (sizeof(size_t) > sizeof(uint32_t)) {
158 pixmaps.emplace_back(SkImageInfo::MakeN32Premul(10, 10), pixels,
159 static_cast<size_t>(UINT32_MAX) + 1);
160 }
161 for (const auto& pm : pixmaps) {
162 for (const auto& rec : gRecs) {
163 REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality));
164 }
165 }
166 free(pixels);
167 }
168
DEF_TEST(NdkEncode_unsupportedColorTypes,r)169 DEF_TEST(NdkEncode_unsupportedColorTypes, r) {
170 for (SkColorType ct : {
171 kUnknown_SkColorType,
172 kAlpha_8_SkColorType,
173 kARGB_4444_SkColorType,
174 kRGB_888x_SkColorType,
175 kBGRA_8888_SkColorType,
176 kRGBA_1010102_SkColorType,
177 kBGRA_1010102_SkColorType,
178 kRGB_101010x_SkColorType,
179 kBGR_101010x_SkColorType,
180 kGray_8_SkColorType,
181 kRGBA_F16Norm_SkColorType,
182 kRGBA_F32_SkColorType,
183 kR8G8_unorm_SkColorType,
184 kA16_float_SkColorType,
185 kR16G16_float_SkColorType,
186 kA16_unorm_SkColorType,
187 kR16G16_unorm_SkColorType,
188 kR16G16B16A16_unorm_SkColorType,
189 }) {
190 auto info = SkImageInfo::Make(7, 13, ct, kOpaque_SkAlphaType, SkColorSpace::MakeSRGB());
191 SkBitmap bm;
192 bm.allocPixels(info);
193 bm.eraseColor(SK_ColorGREEN);
194 for (const auto& rec : gRecs) {
195 REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality));
196 }
197 if (!SkColorTypeIsAlwaysOpaque(ct)) {
198 for (auto at : { kPremul_SkAlphaType, kUnpremul_SkAlphaType}) {
199 info = info.makeAlphaType(at);
200 bm.allocPixels(info);
201 bm.eraseARGB(0x7F, 0xFF, 0xFF, 0xFF);
202 }
203 for (const auto& rec : gRecs) {
204 REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality));
205 }
206 }
207 }
208 }
209
DEF_TEST(NdkEncode_unsupportedAlphaTypes,r)210 DEF_TEST(NdkEncode_unsupportedAlphaTypes, r) {
211 for (auto ct : { kRGBA_8888_SkColorType,
212 kRGB_565_SkColorType,
213 kRGBA_F16_SkColorType }) {
214 for (auto at : { kUnknown_SkAlphaType, (SkAlphaType) -1}) {
215 auto info = SkImageInfo::Make(10, 10, ct, at);
216 size_t rowBytes = info.minRowBytes();
217 void* pixels = sk_malloc_throw(info.computeByteSize(rowBytes));
218 SkPixmap pm(info, pixels, rowBytes);
219 for (const auto& rec : gRecs) {
220 REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality));
221 }
222 free(pixels);
223 }
224 }
225 }
226
227 static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
228
229 static constexpr skcms_Matrix3x3 kDCIP3 = {{
230 {0.486143, 0.323835, 0.154234},
231 {0.226676, 0.710327, 0.0629966},
232 {0.000800549, 0.0432385, 0.78275},
233 }};
234
235
nearly_equal(float a,float b)236 static bool nearly_equal(float a, float b) {
237 return fabs(a - b) < .002f;
238 }
239
nearly_equal(const skcms_TransferFunction & x,const skcms_TransferFunction & y)240 static bool nearly_equal(const skcms_TransferFunction& x, const skcms_TransferFunction& y) {
241 return nearly_equal(x.g, y.g)
242 && nearly_equal(x.a, y.a)
243 && nearly_equal(x.b, y.b)
244 && nearly_equal(x.c, y.c)
245 && nearly_equal(x.d, y.d)
246 && nearly_equal(x.e, y.e)
247 && nearly_equal(x.f, y.f);
248 }
249
nearly_equal(const skcms_Matrix3x3 & a,const skcms_Matrix3x3 & b)250 static bool nearly_equal(const skcms_Matrix3x3& a, const skcms_Matrix3x3& b) {
251 for (int i = 0; i < 3; i++)
252 for (int j = 0; j < 3; j++) {
253 if (!nearly_equal(a.vals[i][j], b.vals[i][j])) return false;
254 }
255 return true;
256 }
257
nearly_equal(SkColorSpace * a,SkColorSpace * b)258 static bool nearly_equal(SkColorSpace* a, SkColorSpace* b) {
259 skcms_TransferFunction fnA, fnB;
260 skcms_Matrix3x3 gamutA, gamutB;
261 return a && b && a->isNumericalTransferFn(&fnA) && a->toXYZD50(&gamutA)
262 && b->isNumericalTransferFn(&fnB) && b->toXYZD50(&gamutB)
263 && nearly_equal(fnA, fnB) && nearly_equal(gamutA, gamutB);
264 }
265
DEF_TEST(NdkEncode_ColorSpace,r)266 DEF_TEST(NdkEncode_ColorSpace, r) {
267 const struct {
268 sk_sp<SkColorSpace> cs;
269 const char* name;
270 } colorSpaces[] = {
271 { sk_sp<SkColorSpace>(nullptr), "null" },
272 { SkColorSpace::MakeSRGB(), "srgb" },
273 { SkColorSpace::MakeSRGBLinear(), "srgb-linear"},
274 { SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB), "bt709" },
275 { SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020), "rec2020" },
276 { SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3), "p3" },
277 { SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB), "adobeRGB"},
278 { SkColorSpace::MakeRGB(k2Dot6, kDCIP3), "dci-p3" },
279 };
280 for (const auto& colorSpace : colorSpaces) {
281 for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) {
282 SkBitmap bm;
283 bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType, colorSpace.cs));
284 bm.eraseColor(SK_ColorRED);
285
286 for (const auto& rec : gRecs) {
287 auto encoded = encode_ndk(bm.pixmap(), rec.format, rec.quality);
288 REPORTER_ASSERT(r, encoded);
289 auto gen = SkImageGenerator::MakeFromEncoded(std::move(encoded));
290 REPORTER_ASSERT(r, gen);
291
292 auto expected = colorSpace.cs ? colorSpace.cs : SkColorSpace::MakeSRGB();
293 auto* actual = gen->getInfo().colorSpace();
294 if (!nearly_equal(actual, expected.get())) {
295 const char* name = "unknown";
296 for (auto named : colorSpaces) {
297 if (nearly_equal(actual, named.cs.get())) {
298 name = named.name;
299 break;
300 }
301 }
302
303 ERRORF(r, "Mismatch: expected: %s\tactual:%s", colorSpace.name, name);
304 }
305 }
306 }
307 }
308 }
309
DEF_TEST(NdkEncode_unsupportedColorSpace,r)310 DEF_TEST(NdkEncode_unsupportedColorSpace, r) {
311 std::vector<sk_sp<SkColorSpace>> unsupportedCs;
312 for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3,
313 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
314 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut));
315 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut));
316 unsupportedCs.push_back(SkColorSpace::MakeRGB(k2Dot6, gamut));
317 }
318
319 for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kDisplayP3,
320 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
321 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gamut));
322 }
323
324 for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3,
325 SkNamedGamut::kXYZ }) {
326 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut));
327 }
328
329 for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3,
330 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
331 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut));
332 }
333
334 for (auto gamut : { SkNamedGamut::kAdobeRGB,
335 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) {
336 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut));
337 }
338
339 for (auto fn : { SkNamedTransferFn::kSRGB, SkNamedTransferFn::k2Dot2,
340 SkNamedTransferFn::kLinear, SkNamedTransferFn::kRec2020 }) {
341 unsupportedCs.push_back(SkColorSpace::MakeRGB(fn, kDCIP3));
342 }
343
344 for (auto unsupported : unsupportedCs) {
345 for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) {
346 SkBitmap bm;
347 bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType, unsupported));
348 bm.eraseColor(SK_ColorBLUE);
349
350 for (const auto& rec : gRecs) {
351 REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality));
352 }
353 }
354 }
355 }
356
357 #endif // SK_ENABLE_NDK_IMAGES
358