/* * Copyright 2023 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "gm/gm.h" #include "include/codec/SkCodec.h" #include "include/codec/SkEncodedImageFormat.h" #include "include/codec/SkPngDecoder.h" #include "include/core/SkAlphaType.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkImage.h" #include "include/core/SkRect.h" #include "include/core/SkSize.h" #include "include/core/SkStream.h" #include "include/core/SkString.h" #include "include/private/base/SkTArray.h" #include "include/private/base/SkTemplates.h" #include "src/base/SkAutoMalloc.h" #include "src/core/SkSwizzlePriv.h" #include "src/utils/SkOSPath.h" #include "tools/flags/CommandLineFlags.h" #include "tools/flags/CommonFlags.h" #include #include #include #include DEFINE_string(pngCodecGMImages, "", "Zero or more images or directories where to find PNG images to test with " "PNGCodecGM. Directories are scanned non-recursively. All files are assumed to be " "PNG images."); DEFINE_string(pngCodecDecodeMode, "", "One of \"get-all-pixels\", \"incremental\" or \"zero-init\"."); DEFINE_string(pngCodecDstColorType, "", "One of \"force-grayscale\", " "\"force-nonnative-premul-color\" or \"get-from-canvas\"."); DEFINE_string(pngCodecDstAlphaType, "", "One of \"premul\" or \"unpremul\"."); static constexpr const char* sk_color_type_to_str(SkColorType colorType) { switch (colorType) { case kUnknown_SkColorType: return "kUnknown_SkColorType"; case kAlpha_8_SkColorType: return "kAlpha_8_SkColorType"; case kRGB_565_SkColorType: return "kRGB_565_SkColorType"; case kARGB_4444_SkColorType: return "kARGB_4444_SkColorType"; case kRGBA_8888_SkColorType: return "kRGBA_8888_SkColorType"; case kRGB_888x_SkColorType: return "kRGB_888x_SkColorType"; case kBGRA_8888_SkColorType: return "kBGRA_8888_SkColorType"; case kRGBA_1010102_SkColorType: return "kRGBA_1010102_SkColorType"; case kBGRA_1010102_SkColorType: return "kBGRA_1010102_SkColorType"; case kRGB_101010x_SkColorType: return "kRGB_101010x_SkColorType"; case kBGR_101010x_SkColorType: return "kBGR_101010x_SkColorType"; case kBGR_101010x_XR_SkColorType: return "kBGR_101010x_XR_SkColorType"; case kGray_8_SkColorType: return "kGray_8_SkColorType"; case kRGBA_F16Norm_SkColorType: return "kRGBA_F16Norm_SkColorType"; case kRGBA_F16_SkColorType: return "kRGBA_F16_SkColorType"; case kRGB_F16F16F16x_SkColorType: return "kRGB_F16F16F16x_SkColorType"; case kRGBA_F32_SkColorType: return "kRGBA_F32_SkColorType"; case kR8G8_unorm_SkColorType: return "kR8G8_unorm_SkColorType"; case kA16_float_SkColorType: return "kA16_float_SkColorType"; case kR16G16_float_SkColorType: return "kR16G16_float_SkColorType"; case kA16_unorm_SkColorType: return "kA16_unorm_SkColorType"; case kR16G16_unorm_SkColorType: return "kR16G16_unorm_SkColorType"; case kR16G16B16A16_unorm_SkColorType: return "kR16G16B16A16_unorm_SkColorType"; case kSRGBA_8888_SkColorType: return "kSRGBA_8888_SkColorType"; case kR8_unorm_SkColorType: return "kR8_unorm_SkColorType"; case kRGBA_10x6_SkColorType: return "kRGBA_10x6_SkColorType"; case kBGRA_10101010_XR_SkColorType: return "kBGRA_10101010_XR_SkColorType"; } SkUNREACHABLE; } static constexpr const char* sk_alpha_type_to_str(SkAlphaType alphaType) { switch (alphaType) { case kUnknown_SkAlphaType: return "kUnknown_SkAlphaType"; case kOpaque_SkAlphaType: return "kOpaque_SkAlphaType"; case kPremul_SkAlphaType: return "kPremul_SkAlphaType"; case kUnpremul_SkAlphaType: return "kUnpremul_SkAlphaType"; } SkUNREACHABLE; } struct DecodeResult { std::unique_ptr codec; std::string errorMsg; }; static DecodeResult decode(std::string path) { sk_sp encoded(SkData::MakeFromFileName(path.c_str())); if (!encoded) { return {.errorMsg = SkStringPrintf("Could not read \"%s\".", path.c_str()).c_str()}; } SkCodec::Result result; std::unique_ptr codec = SkPngDecoder::Decode(SkMemoryStream::Make(encoded), &result); if (result != SkCodec::Result::kSuccess) { return {.errorMsg = SkStringPrintf("Could not create codec for \"%s\": %s.", path.c_str(), SkCodec::ResultToString(result)) .c_str()}; } return {.codec = std::move(codec)}; } // This GM implements the PNG-related behaviors found in DM's CodecSrc class. It takes a single // image as an argument and applies the same logic as CodecSrc. // // See the CodecSrc class here: // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.h#158. class PNGCodecGM : public skiagm::GM { public: // Based on CodecSrc::Mode. // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.h#160 enum class DecodeMode { kGetAllPixels, kIncremental, kZeroInit, }; // Based on CodecSrc::DstColorType. // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.h#172 enum class DstColorType { kForceGrayscale, kForceNonNativePremulColor, kGetFromCanvas, }; static constexpr const char* DecodeModeToString(DecodeMode decodeMode) { switch (decodeMode) { case DecodeMode::kGetAllPixels: return "kGetAllPixels"; case DecodeMode::kIncremental: return "kIncremental"; case DecodeMode::kZeroInit: return "kZeroInit"; } SkUNREACHABLE; } static constexpr const char* DstColorTypeToString(DstColorType dstColorType) { switch (dstColorType) { case DstColorType::kForceGrayscale: return "kForceGrayscale"; case DstColorType::kForceNonNativePremulColor: return "kForceNonNativePremulColor"; case DstColorType::kGetFromCanvas: return "kGetFromCanvas"; } SkUNREACHABLE; } // Based on DM's CodecSrc::CodecSrc(). // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#371 PNGCodecGM(std::string path, DecodeMode decodeMode, DstColorType dstColorType, SkAlphaType dstAlphaType) : skiagm::GM() , fPath(path) , fDecodeMode(decodeMode) , fDstColorType(dstColorType) , fDstAlphaType(dstAlphaType) {} bool isBazelOnly() const override { // This GM class overlaps with DM's CodecSrc and related sources. return true; } std::map getGoldKeys() const override { return std::map{ {"name", getName().c_str()}, {"source_type", "image"}, {"decode_mode", DecodeModeToString(fDecodeMode)}, {"dst_color_type", DstColorTypeToString(fDstColorType)}, {"dst_alpha_type", sk_alpha_type_to_str(fDstAlphaType)}, }; } protected: // Based on CodecSrc::name(). // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#828 SkString getName() const override { SkString name = SkOSPath::Basename(fPath.c_str()); return name; } // Based on CodecSrc::size(). // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#803 SkISize getISize() override { DecodeResult decodeResult = decode(fPath); if (decodeResult.errorMsg != "") { return {0, 0}; } return decodeResult.codec->dimensions(); } // Based on CodecSrc::draw(). // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#450 DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { DecodeResult decodeResult = decode(fPath); if (decodeResult.errorMsg != "") { *errorMsg = decodeResult.errorMsg.c_str(); return DrawResult::kFail; } std::unique_ptr codec = std::move(decodeResult.codec); SkImageInfo decodeInfo = codec->getInfo(); if (*errorMsg = validateCanvasColorTypeAndGetDecodeInfo(&decodeInfo, canvas->imageInfo().colorType()); *errorMsg != SkString()) { return DrawResult::kFail; } SkISize size = codec->dimensions(); decodeInfo = decodeInfo.makeDimensions(size); const int bpp = decodeInfo.bytesPerPixel(); const size_t rowBytes = size.width() * bpp; const size_t safeSize = decodeInfo.computeByteSize(rowBytes); SkAutoMalloc pixels(safeSize); SkCodec::Options options; if (DecodeMode::kZeroInit == fDecodeMode) { memset(pixels.get(), 0, size.height() * rowBytes); options.fZeroInitialized = SkCodec::kYes_ZeroInitialized; } // For codec srcs, we want the "draw" step to be a memcpy. Any interesting color space or // color format conversions should be performed by the codec. Sometimes the output of the // decode will be in an interesting color space. On our srgb and f16 backends, we need to // "pretend" that the color space is standard sRGB to avoid triggering color conversion // at draw time. SkImageInfo bitmapInfo = decodeInfo.makeColorSpace(SkColorSpace::MakeSRGB()); if (kRGBA_8888_SkColorType == decodeInfo.colorType() || kBGRA_8888_SkColorType == decodeInfo.colorType()) { bitmapInfo = bitmapInfo.makeColorType(kN32_SkColorType); } switch (fDecodeMode) { case DecodeMode::kZeroInit: case DecodeMode::kGetAllPixels: { switch (codec->getPixels(decodeInfo, pixels.get(), rowBytes, &options)) { case SkCodec::kSuccess: // We consider these to be valid, since we should still decode what is // available. case SkCodec::kErrorInInput: case SkCodec::kIncompleteInput: break; default: // Everything else is considered a failure. *errorMsg = SkStringPrintf("Couldn't getPixels %s.", fPath.c_str()); return DrawResult::kFail; } drawToCanvas(canvas, bitmapInfo, pixels.get(), rowBytes); break; } case DecodeMode::kIncremental: { void* dst = pixels.get(); uint32_t height = decodeInfo.height(); if (SkCodec::kSuccess == codec->startIncrementalDecode(decodeInfo, dst, rowBytes, &options)) { int rowsDecoded; auto result = codec->incrementalDecode(&rowsDecoded); if (SkCodec::kIncompleteInput == result || SkCodec::kErrorInInput == result) { codec->fillIncompleteImage(decodeInfo, dst, rowBytes, SkCodec::kNo_ZeroInitialized, height, rowsDecoded); } } else { *errorMsg = "Could not start incremental decode"; return DrawResult::kFail; } drawToCanvas(canvas, bitmapInfo, dst, rowBytes); break; } default: SkASSERT(false); *errorMsg = "Invalid fDecodeMode"; return DrawResult::kFail; } return DrawResult::kOk; } private: // Checks that the canvas color type, destination color and alpha types and input image // constitute an interesting test case, and constructs the SkImageInfo to use when decoding the // image. // // Based on DM's get_decode_info() function. // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#398 SkString validateCanvasColorTypeAndGetDecodeInfo(SkImageInfo* decodeInfo, SkColorType canvasColorType) { switch (fDstColorType) { case DstColorType::kForceGrayscale: if (kRGB_565_SkColorType == canvasColorType) { return SkStringPrintf( "canvas color type %s and destination color type %s are redundant", sk_color_type_to_str(canvasColorType), DstColorTypeToString(fDstColorType)); } *decodeInfo = decodeInfo->makeColorType(kGray_8_SkColorType); break; case DstColorType::kForceNonNativePremulColor: if (kRGB_565_SkColorType == canvasColorType || kRGBA_F16_SkColorType == canvasColorType) { return SkStringPrintf( "canvas color type %s and destination color type %s are redundant", sk_color_type_to_str(canvasColorType), DstColorTypeToString(fDstColorType)); } #ifdef SK_PMCOLOR_IS_RGBA *decodeInfo = decodeInfo->makeColorType(kBGRA_8888_SkColorType); #else *decodeInfo = decodeInfo->makeColorType(kRGBA_8888_SkColorType); #endif break; case DstColorType::kGetFromCanvas: if (kRGB_565_SkColorType == canvasColorType && kOpaque_SkAlphaType != decodeInfo->alphaType()) { return SkStringPrintf( "image \"%s\" has alpha type %s; this is incompatible with with " "canvas color type %s and destination color type %s", fPath.c_str(), sk_alpha_type_to_str(decodeInfo->alphaType()), sk_color_type_to_str(canvasColorType), DstColorTypeToString(fDstColorType)); } *decodeInfo = decodeInfo->makeColorType(canvasColorType); break; default: SkUNREACHABLE; } *decodeInfo = decodeInfo->makeAlphaType(fDstAlphaType); return SkString(); } // Based on DM's draw_to_canvas() function. // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#432 void drawToCanvas(SkCanvas* canvas, const SkImageInfo& info, void* pixels, size_t rowBytes, SkScalar left = 0, SkScalar top = 0) { SkBitmap bitmap; bitmap.installPixels(info, pixels, rowBytes); swapRbIfNecessary(bitmap); canvas->drawImage(bitmap.asImage(), left, top); } // Allows us to test decodes to non-native 8888. // // Based on DM's swap_rb_if_necessary function. // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#387 void swapRbIfNecessary(SkBitmap& bitmap) { if (DstColorType::kForceNonNativePremulColor != fDstColorType) { return; } for (int y = 0; y < bitmap.height(); y++) { uint32_t* row = (uint32_t*)bitmap.getAddr(0, y); SkOpts::RGBA_to_BGRA(row, row, bitmap.width()); } } std::string fPath; DecodeMode fDecodeMode; DstColorType fDstColorType; SkAlphaType fDstAlphaType; }; // Registers GMs with zero or more PNGCodecGM instances for the given image. Returns a non-empty, // human-friendly error message in the case of errors. // // Based on DM's push_codec_srcs() function. It only covers "simple" codecs (lines 740-834). // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DM.cpp#740 // // Specifically, this function does not capture any behaviors found in the following DM classes: // // - AndroidCodecSrc // - BRDSrc // - ImageGenSrc // // TODO(lovisolo): Implement the above sources as GMs (if necessary). static std::string registerGMsForImage(std::string path, PNGCodecGM::DecodeMode decodeMode, PNGCodecGM::DstColorType dstColorType, SkAlphaType dstAlphaType) { DecodeResult decodeResult = decode(path); if (decodeResult.errorMsg != "") { return decodeResult.errorMsg; } if (dstColorType == PNGCodecGM::DstColorType::kForceGrayscale && decodeResult.codec->getInfo().colorType() != kGray_8_SkColorType) { return SkStringPrintf( "image \"%s\" has color type %s; this is incompatible with the given " "dstColorType argument: %s (expected image color type: %s)", path.c_str(), sk_color_type_to_str(decodeResult.codec->getInfo().colorType()), PNGCodecGM::DstColorTypeToString(PNGCodecGM::DstColorType::kForceGrayscale), sk_color_type_to_str(kGray_8_SkColorType)) .c_str(); } if (dstAlphaType == kUnpremul_SkAlphaType && decodeResult.codec->getInfo().alphaType() == kOpaque_SkAlphaType) { return SkStringPrintf( "image \"%s\" has alpha type %s; this is incompatible with the given " "dstAlphaType argument: %s", path.c_str(), sk_alpha_type_to_str(kOpaque_SkAlphaType), sk_alpha_type_to_str(kUnpremul_SkAlphaType)) .c_str(); } skiagm::Register(new PNGCodecGM(path, decodeMode, dstColorType, dstAlphaType)); return ""; } // Returns a non-empty message in the case of errors. static std::string parse_and_validate_flags(PNGCodecGM::DecodeMode* decodeMode, PNGCodecGM::DstColorType* dstColorType, SkAlphaType* dstAlphaType) { skia_private::THashMap decodeModeValues = { {SkString("get-all-pixels"), PNGCodecGM::DecodeMode::kGetAllPixels}, {SkString("incremental"), PNGCodecGM::DecodeMode::kIncremental}, {SkString("zero-init"), PNGCodecGM::DecodeMode::kZeroInit}, }; if (SkString errorMsg = FLAGS_pngCodecDecodeMode.parseAndValidate( "--pngCodecDecodeMode", decodeModeValues, decodeMode); errorMsg != SkString()) { return errorMsg.c_str(); } skia_private::THashMap dstColorTypeValues = { {SkString("get-from-canvas"), PNGCodecGM::DstColorType::kGetFromCanvas}, {SkString("force-grayscale"), PNGCodecGM::DstColorType::kForceGrayscale}, {SkString("force-nonnative-premul-color"), PNGCodecGM::DstColorType::kForceNonNativePremulColor}, }; if (SkString errorMsg = FLAGS_pngCodecDstColorType.parseAndValidate( "--pngCodecDstColorType", dstColorTypeValues, dstColorType); errorMsg != SkString()) { return errorMsg.c_str(); } skia_private::THashMap dstAlphaTypeValues = { {SkString("premul"), kPremul_SkAlphaType}, {SkString("unpremul"), kUnpremul_SkAlphaType}, }; if (SkString errorMsg = FLAGS_pngCodecDstAlphaType.parseAndValidate( "--pngCodecDstAlphaType", dstAlphaTypeValues, dstAlphaType); errorMsg != SkString()) { return errorMsg.c_str(); } return ""; } // Registers one PNGCodecGM instance for each image passed via the --pngCodecGMImages flag, which // can take files and directories. Directories are scanned non-recursively. // // Based on DM's gather_srcs() function. // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DM.cpp#953 DEF_GM_REGISTERER_FN([]() -> std::string { // Parse flags. PNGCodecGM::DecodeMode decodeMode; PNGCodecGM::DstColorType dstColorType; SkAlphaType dstAlphaType; if (std::string errorMsg = parse_and_validate_flags(&decodeMode, &dstColorType, &dstAlphaType); errorMsg != "") { return errorMsg; } // Collect images. skia_private::TArray images; if (!CommonFlags::CollectImages(FLAGS_pngCodecGMImages, &images)) { return "Failed to collect images."; } // Register one GM per image. for (const SkString& image : images) { if (std::string errorMsg = registerGMsForImage(image.c_str(), decodeMode, dstColorType, dstAlphaType); errorMsg != "") { return errorMsg; } } return ""; });