• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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