xref: /aosp_15_r20/external/skia/gm/png_codec.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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