blob: 71b84905dd52917b8f5058e7ed613951cc982214 [file] [log] [blame]
/*
* 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 <map>
#include <memory>
#include <string>
#include <vector>
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<SkCodec> codec;
std::string errorMsg;
};
static DecodeResult decode(std::string path) {
sk_sp<SkData> 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<SkCodec> 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<std::string, std::string> getGoldKeys() const override {
return std::map<std::string, std::string>{
{"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<SkCodec> 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<SkString, PNGCodecGM::DecodeMode> 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<SkString, PNGCodecGM::DstColorType> 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<SkString, SkAlphaType> 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<SkString> 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 "";
});