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