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