1 /*
2 * Copyright 2023 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 "gm/gm.h"
9 #include "include/codec/SkCodec.h"
10 #include "include/codec/SkEncodedImageFormat.h"
11 #include "include/codec/SkPngDecoder.h"
12 #include "include/core/SkAlphaType.h"
13 #include "include/core/SkBitmap.h"
14 #include "include/core/SkCanvas.h"
15 #include "include/core/SkImage.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkSize.h"
18 #include "include/core/SkStream.h"
19 #include "include/core/SkString.h"
20 #include "include/private/base/SkTArray.h"
21 #include "include/private/base/SkTemplates.h"
22 #include "src/base/SkAutoMalloc.h"
23 #include "src/core/SkSwizzlePriv.h"
24 #include "src/utils/SkOSPath.h"
25 #include "tools/flags/CommandLineFlags.h"
26 #include "tools/flags/CommonFlags.h"
27
28 #include <map>
29 #include <memory>
30 #include <string>
31 #include <vector>
32
33 DEFINE_string(pngCodecGMImages,
34 "",
35 "Zero or more images or directories where to find PNG images to test with "
36 "PNGCodecGM. Directories are scanned non-recursively. All files are assumed to be "
37 "PNG images.");
38 DEFINE_string(pngCodecDecodeMode,
39 "",
40 "One of \"get-all-pixels\", \"incremental\" or \"zero-init\".");
41 DEFINE_string(pngCodecDstColorType,
42 "",
43 "One of \"force-grayscale\", "
44 "\"force-nonnative-premul-color\" or \"get-from-canvas\".");
45 DEFINE_string(pngCodecDstAlphaType, "", "One of \"premul\" or \"unpremul\".");
46
sk_color_type_to_str(SkColorType colorType)47 static constexpr const char* sk_color_type_to_str(SkColorType colorType) {
48 switch (colorType) {
49 case kUnknown_SkColorType:
50 return "kUnknown_SkColorType";
51 case kAlpha_8_SkColorType:
52 return "kAlpha_8_SkColorType";
53 case kRGB_565_SkColorType:
54 return "kRGB_565_SkColorType";
55 case kARGB_4444_SkColorType:
56 return "kARGB_4444_SkColorType";
57 case kRGBA_8888_SkColorType:
58 return "kRGBA_8888_SkColorType";
59 case kRGB_888x_SkColorType:
60 return "kRGB_888x_SkColorType";
61 case kBGRA_8888_SkColorType:
62 return "kBGRA_8888_SkColorType";
63 case kRGBA_1010102_SkColorType:
64 return "kRGBA_1010102_SkColorType";
65 case kBGRA_1010102_SkColorType:
66 return "kBGRA_1010102_SkColorType";
67 case kRGB_101010x_SkColorType:
68 return "kRGB_101010x_SkColorType";
69 case kBGR_101010x_SkColorType:
70 return "kBGR_101010x_SkColorType";
71 case kBGR_101010x_XR_SkColorType:
72 return "kBGR_101010x_XR_SkColorType";
73 case kGray_8_SkColorType:
74 return "kGray_8_SkColorType";
75 case kRGBA_F16Norm_SkColorType:
76 return "kRGBA_F16Norm_SkColorType";
77 case kRGBA_F16_SkColorType:
78 return "kRGBA_F16_SkColorType";
79 case kRGB_F16F16F16x_SkColorType:
80 return "kRGB_F16F16F16x_SkColorType";
81 case kRGBA_F32_SkColorType:
82 return "kRGBA_F32_SkColorType";
83 case kR8G8_unorm_SkColorType:
84 return "kR8G8_unorm_SkColorType";
85 case kA16_float_SkColorType:
86 return "kA16_float_SkColorType";
87 case kR16G16_float_SkColorType:
88 return "kR16G16_float_SkColorType";
89 case kA16_unorm_SkColorType:
90 return "kA16_unorm_SkColorType";
91 case kR16G16_unorm_SkColorType:
92 return "kR16G16_unorm_SkColorType";
93 case kR16G16B16A16_unorm_SkColorType:
94 return "kR16G16B16A16_unorm_SkColorType";
95 case kSRGBA_8888_SkColorType:
96 return "kSRGBA_8888_SkColorType";
97 case kR8_unorm_SkColorType:
98 return "kR8_unorm_SkColorType";
99 case kRGBA_10x6_SkColorType:
100 return "kRGBA_10x6_SkColorType";
101 case kBGRA_10101010_XR_SkColorType:
102 return "kBGRA_10101010_XR_SkColorType";
103 }
104 SkUNREACHABLE;
105 }
106
sk_alpha_type_to_str(SkAlphaType alphaType)107 static constexpr const char* sk_alpha_type_to_str(SkAlphaType alphaType) {
108 switch (alphaType) {
109 case kUnknown_SkAlphaType:
110 return "kUnknown_SkAlphaType";
111 case kOpaque_SkAlphaType:
112 return "kOpaque_SkAlphaType";
113 case kPremul_SkAlphaType:
114 return "kPremul_SkAlphaType";
115 case kUnpremul_SkAlphaType:
116 return "kUnpremul_SkAlphaType";
117 }
118 SkUNREACHABLE;
119 }
120
121 struct DecodeResult {
122 std::unique_ptr<SkCodec> codec;
123 std::string errorMsg;
124 };
125
decode(std::string path)126 static DecodeResult decode(std::string path) {
127 sk_sp<SkData> encoded(SkData::MakeFromFileName(path.c_str()));
128 if (!encoded) {
129 return {.errorMsg = SkStringPrintf("Could not read \"%s\".", path.c_str()).c_str()};
130 }
131 SkCodec::Result result;
132 std::unique_ptr<SkCodec> codec = SkPngDecoder::Decode(SkMemoryStream::Make(encoded), &result);
133 if (result != SkCodec::Result::kSuccess) {
134 return {.errorMsg = SkStringPrintf("Could not create codec for \"%s\": %s.",
135 path.c_str(),
136 SkCodec::ResultToString(result))
137 .c_str()};
138 }
139 return {.codec = std::move(codec)};
140 }
141
142 // This GM implements the PNG-related behaviors found in DM's CodecSrc class. It takes a single
143 // image as an argument and applies the same logic as CodecSrc.
144 //
145 // See the CodecSrc class here:
146 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.h#158.
147 class PNGCodecGM : public skiagm::GM {
148 public:
149 // Based on CodecSrc::Mode.
150 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.h#160
151 enum class DecodeMode {
152 kGetAllPixels,
153 kIncremental,
154 kZeroInit,
155 };
156
157 // Based on CodecSrc::DstColorType.
158 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.h#172
159 enum class DstColorType {
160 kForceGrayscale,
161 kForceNonNativePremulColor,
162 kGetFromCanvas,
163 };
164
DecodeModeToString(DecodeMode decodeMode)165 static constexpr const char* DecodeModeToString(DecodeMode decodeMode) {
166 switch (decodeMode) {
167 case DecodeMode::kGetAllPixels:
168 return "kGetAllPixels";
169 case DecodeMode::kIncremental:
170 return "kIncremental";
171 case DecodeMode::kZeroInit:
172 return "kZeroInit";
173 }
174 SkUNREACHABLE;
175 }
176
DstColorTypeToString(DstColorType dstColorType)177 static constexpr const char* DstColorTypeToString(DstColorType dstColorType) {
178 switch (dstColorType) {
179 case DstColorType::kForceGrayscale:
180 return "kForceGrayscale";
181 case DstColorType::kForceNonNativePremulColor:
182 return "kForceNonNativePremulColor";
183 case DstColorType::kGetFromCanvas:
184 return "kGetFromCanvas";
185 }
186 SkUNREACHABLE;
187 }
188
189 // Based on DM's CodecSrc::CodecSrc().
190 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#371
PNGCodecGM(std::string path,DecodeMode decodeMode,DstColorType dstColorType,SkAlphaType dstAlphaType)191 PNGCodecGM(std::string path,
192 DecodeMode decodeMode,
193 DstColorType dstColorType,
194 SkAlphaType dstAlphaType)
195 : skiagm::GM()
196 , fPath(path)
197 , fDecodeMode(decodeMode)
198 , fDstColorType(dstColorType)
199 , fDstAlphaType(dstAlphaType) {}
200
isBazelOnly() const201 bool isBazelOnly() const override {
202 // This GM class overlaps with DM's CodecSrc and related sources.
203 return true;
204 }
205
getGoldKeys() const206 std::map<std::string, std::string> getGoldKeys() const override {
207 return std::map<std::string, std::string>{
208 {"name", getName().c_str()},
209 {"source_type", "image"},
210 {"decode_mode", DecodeModeToString(fDecodeMode)},
211 {"dst_color_type", DstColorTypeToString(fDstColorType)},
212 {"dst_alpha_type", sk_alpha_type_to_str(fDstAlphaType)},
213 };
214 }
215
216 protected:
217 // Based on CodecSrc::name().
218 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#828
getName() const219 SkString getName() const override {
220 SkString name = SkOSPath::Basename(fPath.c_str());
221 return name;
222 }
223
224 // Based on CodecSrc::size().
225 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#803
getISize()226 SkISize getISize() override {
227 DecodeResult decodeResult = decode(fPath);
228 if (decodeResult.errorMsg != "") {
229 return {0, 0};
230 }
231 return decodeResult.codec->dimensions();
232 }
233
234 // Based on CodecSrc::draw().
235 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#450
onDraw(SkCanvas * canvas,SkString * errorMsg)236 DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
237 DecodeResult decodeResult = decode(fPath);
238 if (decodeResult.errorMsg != "") {
239 *errorMsg = decodeResult.errorMsg.c_str();
240 return DrawResult::kFail;
241 }
242 std::unique_ptr<SkCodec> codec = std::move(decodeResult.codec);
243
244 SkImageInfo decodeInfo = codec->getInfo();
245 if (*errorMsg = validateCanvasColorTypeAndGetDecodeInfo(&decodeInfo,
246 canvas->imageInfo().colorType());
247 *errorMsg != SkString()) {
248 return DrawResult::kFail;
249 }
250
251 SkISize size = codec->dimensions();
252 decodeInfo = decodeInfo.makeDimensions(size);
253
254 const int bpp = decodeInfo.bytesPerPixel();
255 const size_t rowBytes = size.width() * bpp;
256 const size_t safeSize = decodeInfo.computeByteSize(rowBytes);
257 SkAutoMalloc pixels(safeSize);
258
259 SkCodec::Options options;
260 if (DecodeMode::kZeroInit == fDecodeMode) {
261 memset(pixels.get(), 0, size.height() * rowBytes);
262 options.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
263 }
264
265 // For codec srcs, we want the "draw" step to be a memcpy. Any interesting color space or
266 // color format conversions should be performed by the codec. Sometimes the output of the
267 // decode will be in an interesting color space. On our srgb and f16 backends, we need to
268 // "pretend" that the color space is standard sRGB to avoid triggering color conversion
269 // at draw time.
270 SkImageInfo bitmapInfo = decodeInfo.makeColorSpace(SkColorSpace::MakeSRGB());
271
272 if (kRGBA_8888_SkColorType == decodeInfo.colorType() ||
273 kBGRA_8888_SkColorType == decodeInfo.colorType()) {
274 bitmapInfo = bitmapInfo.makeColorType(kN32_SkColorType);
275 }
276
277 switch (fDecodeMode) {
278 case DecodeMode::kZeroInit:
279 case DecodeMode::kGetAllPixels: {
280 switch (codec->getPixels(decodeInfo, pixels.get(), rowBytes, &options)) {
281 case SkCodec::kSuccess:
282 // We consider these to be valid, since we should still decode what is
283 // available.
284 case SkCodec::kErrorInInput:
285 case SkCodec::kIncompleteInput:
286 break;
287 default:
288 // Everything else is considered a failure.
289 *errorMsg = SkStringPrintf("Couldn't getPixels %s.", fPath.c_str());
290 return DrawResult::kFail;
291 }
292
293 drawToCanvas(canvas, bitmapInfo, pixels.get(), rowBytes);
294 break;
295 }
296 case DecodeMode::kIncremental: {
297 void* dst = pixels.get();
298 uint32_t height = decodeInfo.height();
299 if (SkCodec::kSuccess ==
300 codec->startIncrementalDecode(decodeInfo, dst, rowBytes, &options)) {
301 int rowsDecoded;
302 auto result = codec->incrementalDecode(&rowsDecoded);
303 if (SkCodec::kIncompleteInput == result || SkCodec::kErrorInInput == result) {
304 codec->fillIncompleteImage(decodeInfo,
305 dst,
306 rowBytes,
307 SkCodec::kNo_ZeroInitialized,
308 height,
309 rowsDecoded);
310 }
311 } else {
312 *errorMsg = "Could not start incremental decode";
313 return DrawResult::kFail;
314 }
315 drawToCanvas(canvas, bitmapInfo, dst, rowBytes);
316 break;
317 }
318 default:
319 SkASSERT(false);
320 *errorMsg = "Invalid fDecodeMode";
321 return DrawResult::kFail;
322 }
323 return DrawResult::kOk;
324 }
325
326 private:
327 // Checks that the canvas color type, destination color and alpha types and input image
328 // constitute an interesting test case, and constructs the SkImageInfo to use when decoding the
329 // image.
330 //
331 // Based on DM's get_decode_info() function.
332 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#398
validateCanvasColorTypeAndGetDecodeInfo(SkImageInfo * decodeInfo,SkColorType canvasColorType)333 SkString validateCanvasColorTypeAndGetDecodeInfo(SkImageInfo* decodeInfo,
334 SkColorType canvasColorType) {
335 switch (fDstColorType) {
336 case DstColorType::kForceGrayscale:
337 if (kRGB_565_SkColorType == canvasColorType) {
338 return SkStringPrintf(
339 "canvas color type %s and destination color type %s are redundant",
340 sk_color_type_to_str(canvasColorType),
341 DstColorTypeToString(fDstColorType));
342 }
343 *decodeInfo = decodeInfo->makeColorType(kGray_8_SkColorType);
344 break;
345
346 case DstColorType::kForceNonNativePremulColor:
347 if (kRGB_565_SkColorType == canvasColorType ||
348 kRGBA_F16_SkColorType == canvasColorType) {
349 return SkStringPrintf(
350 "canvas color type %s and destination color type %s are redundant",
351 sk_color_type_to_str(canvasColorType),
352 DstColorTypeToString(fDstColorType));
353 }
354 #ifdef SK_PMCOLOR_IS_RGBA
355 *decodeInfo = decodeInfo->makeColorType(kBGRA_8888_SkColorType);
356 #else
357 *decodeInfo = decodeInfo->makeColorType(kRGBA_8888_SkColorType);
358 #endif
359 break;
360
361 case DstColorType::kGetFromCanvas:
362 if (kRGB_565_SkColorType == canvasColorType &&
363 kOpaque_SkAlphaType != decodeInfo->alphaType()) {
364 return SkStringPrintf(
365 "image \"%s\" has alpha type %s; this is incompatible with with "
366 "canvas color type %s and destination color type %s",
367 fPath.c_str(),
368 sk_alpha_type_to_str(decodeInfo->alphaType()),
369 sk_color_type_to_str(canvasColorType),
370 DstColorTypeToString(fDstColorType));
371 }
372 *decodeInfo = decodeInfo->makeColorType(canvasColorType);
373 break;
374
375 default:
376 SkUNREACHABLE;
377 }
378
379 *decodeInfo = decodeInfo->makeAlphaType(fDstAlphaType);
380 return SkString();
381 }
382
383 // Based on DM's draw_to_canvas() function.
384 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#432
drawToCanvas(SkCanvas * canvas,const SkImageInfo & info,void * pixels,size_t rowBytes,SkScalar left=0,SkScalar top=0)385 void drawToCanvas(SkCanvas* canvas,
386 const SkImageInfo& info,
387 void* pixels,
388 size_t rowBytes,
389 SkScalar left = 0,
390 SkScalar top = 0) {
391 SkBitmap bitmap;
392 bitmap.installPixels(info, pixels, rowBytes);
393 swapRbIfNecessary(bitmap);
394 canvas->drawImage(bitmap.asImage(), left, top);
395 }
396
397 // Allows us to test decodes to non-native 8888.
398 //
399 // Based on DM's swap_rb_if_necessary function.
400 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#387
swapRbIfNecessary(SkBitmap & bitmap)401 void swapRbIfNecessary(SkBitmap& bitmap) {
402 if (DstColorType::kForceNonNativePremulColor != fDstColorType) {
403 return;
404 }
405
406 for (int y = 0; y < bitmap.height(); y++) {
407 uint32_t* row = (uint32_t*)bitmap.getAddr(0, y);
408 SkOpts::RGBA_to_BGRA(row, row, bitmap.width());
409 }
410 }
411
412 std::string fPath;
413 DecodeMode fDecodeMode;
414 DstColorType fDstColorType;
415 SkAlphaType fDstAlphaType;
416 };
417
418 // Registers GMs with zero or more PNGCodecGM instances for the given image. Returns a non-empty,
419 // human-friendly error message in the case of errors.
420 //
421 // Based on DM's push_codec_srcs() function. It only covers "simple" codecs (lines 740-834).
422 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DM.cpp#740
423 //
424 // Specifically, this function does not capture any behaviors found in the following DM classes:
425 //
426 // - AndroidCodecSrc
427 // - BRDSrc
428 // - ImageGenSrc
429 //
430 // TODO(lovisolo): Implement the above sources as GMs (if necessary).
registerGMsForImage(std::string path,PNGCodecGM::DecodeMode decodeMode,PNGCodecGM::DstColorType dstColorType,SkAlphaType dstAlphaType)431 static std::string registerGMsForImage(std::string path,
432 PNGCodecGM::DecodeMode decodeMode,
433 PNGCodecGM::DstColorType dstColorType,
434 SkAlphaType dstAlphaType) {
435 DecodeResult decodeResult = decode(path);
436 if (decodeResult.errorMsg != "") {
437 return decodeResult.errorMsg;
438 }
439
440 if (dstColorType == PNGCodecGM::DstColorType::kForceGrayscale &&
441 decodeResult.codec->getInfo().colorType() != kGray_8_SkColorType) {
442 return SkStringPrintf(
443 "image \"%s\" has color type %s; this is incompatible with the given "
444 "dstColorType argument: %s (expected image color type: %s)",
445 path.c_str(),
446 sk_color_type_to_str(decodeResult.codec->getInfo().colorType()),
447 PNGCodecGM::DstColorTypeToString(PNGCodecGM::DstColorType::kForceGrayscale),
448 sk_color_type_to_str(kGray_8_SkColorType))
449 .c_str();
450 }
451
452 if (dstAlphaType == kUnpremul_SkAlphaType &&
453 decodeResult.codec->getInfo().alphaType() == kOpaque_SkAlphaType) {
454 return SkStringPrintf(
455 "image \"%s\" has alpha type %s; this is incompatible with the given "
456 "dstAlphaType argument: %s",
457 path.c_str(),
458 sk_alpha_type_to_str(kOpaque_SkAlphaType),
459 sk_alpha_type_to_str(kUnpremul_SkAlphaType))
460 .c_str();
461 }
462
463 skiagm::Register(new PNGCodecGM(path, decodeMode, dstColorType, dstAlphaType));
464 return "";
465 }
466
467 // Returns a non-empty message in the case of errors.
parse_and_validate_flags(PNGCodecGM::DecodeMode * decodeMode,PNGCodecGM::DstColorType * dstColorType,SkAlphaType * dstAlphaType)468 static std::string parse_and_validate_flags(PNGCodecGM::DecodeMode* decodeMode,
469 PNGCodecGM::DstColorType* dstColorType,
470 SkAlphaType* dstAlphaType) {
471 skia_private::THashMap<SkString, PNGCodecGM::DecodeMode> decodeModeValues = {
472 {SkString("get-all-pixels"), PNGCodecGM::DecodeMode::kGetAllPixels},
473 {SkString("incremental"), PNGCodecGM::DecodeMode::kIncremental},
474 {SkString("zero-init"), PNGCodecGM::DecodeMode::kZeroInit},
475 };
476 if (SkString errorMsg = FLAGS_pngCodecDecodeMode.parseAndValidate(
477 "--pngCodecDecodeMode", decodeModeValues, decodeMode);
478 errorMsg != SkString()) {
479 return errorMsg.c_str();
480 }
481
482 skia_private::THashMap<SkString, PNGCodecGM::DstColorType> dstColorTypeValues = {
483 {SkString("get-from-canvas"), PNGCodecGM::DstColorType::kGetFromCanvas},
484 {SkString("force-grayscale"), PNGCodecGM::DstColorType::kForceGrayscale},
485 {SkString("force-nonnative-premul-color"),
486 PNGCodecGM::DstColorType::kForceNonNativePremulColor},
487 };
488 if (SkString errorMsg = FLAGS_pngCodecDstColorType.parseAndValidate(
489 "--pngCodecDstColorType", dstColorTypeValues, dstColorType);
490 errorMsg != SkString()) {
491 return errorMsg.c_str();
492 }
493
494 skia_private::THashMap<SkString, SkAlphaType> dstAlphaTypeValues = {
495 {SkString("premul"), kPremul_SkAlphaType},
496 {SkString("unpremul"), kUnpremul_SkAlphaType},
497 };
498 if (SkString errorMsg = FLAGS_pngCodecDstAlphaType.parseAndValidate(
499 "--pngCodecDstAlphaType", dstAlphaTypeValues, dstAlphaType);
500 errorMsg != SkString()) {
501 return errorMsg.c_str();
502 }
503
504 return "";
505 }
506
507 // Registers one PNGCodecGM instance for each image passed via the --pngCodecGMImages flag, which
508 // can take files and directories. Directories are scanned non-recursively.
509 //
510 // Based on DM's gather_srcs() function.
511 // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DM.cpp#953
__anonb277b3f40102() 512 DEF_GM_REGISTERER_FN([]() -> std::string {
513 // Parse flags.
514 PNGCodecGM::DecodeMode decodeMode;
515 PNGCodecGM::DstColorType dstColorType;
516 SkAlphaType dstAlphaType;
517 if (std::string errorMsg = parse_and_validate_flags(&decodeMode, &dstColorType, &dstAlphaType);
518 errorMsg != "") {
519 return errorMsg;
520 }
521
522 // Collect images.
523 skia_private::TArray<SkString> images;
524 if (!CommonFlags::CollectImages(FLAGS_pngCodecGMImages, &images)) {
525 return "Failed to collect images.";
526 }
527
528 // Register one GM per image.
529 for (const SkString& image : images) {
530 if (std::string errorMsg =
531 registerGMsForImage(image.c_str(), decodeMode, dstColorType, dstAlphaType);
532 errorMsg != "") {
533 return errorMsg;
534 }
535 }
536
537 return "";
538 });
539