[bazel] Add //gm/png_codec.cpp.
This proof-of-concept CL shows what it would be like if we reimplemented DM's CodecSrcs as GMs. The motivation is that the CodecSrc class, and other such classes, are conceptually very similar to a GM: they do some work and produce an image as their output. So, rather than introducing the notion of various "sources" in our Bazel work, I thought I would try rewriting DM's codec tests as GMs.
This particular CL focuses on PNG codec tests. Adding support for more file formats shouldn't be too different, and there is some potential for code reuse (e.g. a "BaseCodecGM" superclass).
Rather than relying on multiple nested loops to iterate over all possible combinations of options like DM does, this CL pushes those decisions to Bazel. In //gm/png_codec.bzl, we define multiple Bazel targets where each target focuses on a single combination of codec mode, color type, alpha type, etc. In //gm/png_codec.cpp, we take those options as command-line flags, and we register exactly one GM per image (whereas DM registers multiple GMs per image in a triply-nested loop; one GM per combination of options). This hopefully reduces the complexity of the C++ code, and provides better sharding by breaking up tests into smaller Bazel targets.
Notes to reviewers:
- I recommend reading //gm/png_codec.cpp from top to bottom, and reading the skia.googlesource.com links in my comments alongside this CL while reviewing. A lot of the code in //gm/png_codec.cpp is copied verbatim from the CodecSrc class and related functions.
- (Optional but recommended) See https://skia-review.googlesource.com/c/skia/+/728798 for an earlier attempt, where I tried to rewrite the entire CodecSrc class as a single CodecGM class. After chatting with kjlubick@, we decided it would be simpler to break that giant class into smaller ones focused e.g. on one file format per class. This CL is my attempt at isolating the parts of CodecSrc and related code that are only relevant to PNG files.
Tested with:
$ bazel test //gm:png_codec_tests \
--config=linux_rbe \
--config=debug \
--test_output=streamed \
--strategy=TestRunner=local
Bug: b/40045301
Change-Id: I3e36c1ce03ceb892e7fae7fef5c0882e76b233e0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/734379
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Leandro Lovisolo <lovisolo@google.com>
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
index 64f90d5..7c69242 100644
--- a/WORKSPACE.bazel
+++ b/WORKSPACE.bazel
@@ -484,6 +484,14 @@
tag = "git_revision:1c4151ff5c1d6fbf7fa800b8d4bb34d3abc03a41",
)
+cipd_install(
+ name = "skimage",
+ build_file = "//bazel/external/skimage:BUILD.bazel",
+ cipd_package = "skia/bots/skimage",
+ # From https://chrome-infra-packages.appspot.com/p/skia/bots/skimage/+/sRladEfUAXeYIBD3Pt3ke0Fd08vtYVLrg4IASKk5F6YC
+ sha256 = "b1195a7447d40177982010f73edde47b415dd3cbed6152eb83820048a93917a6",
+ tag = "version:47",
+)
##################################
# Docker rules and dependencies. #
diff --git a/bazel/cipd_install.bzl b/bazel/cipd_install.bzl
index f195cf1..300e85c 100644
--- a/bazel/cipd_install.bzl
+++ b/bazel/cipd_install.bzl
@@ -7,21 +7,24 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-def cipd_install(name, build_file_content, cipd_package, sha256, tag):
+def cipd_install(name, cipd_package, sha256, tag, build_file = None, build_file_content = None):
"""Download and extract the zipped archive from CIPD, making it available for Bazel rules.
Args:
name: The name of the Bazel "repository" created. For example, if name is "alpha_beta",
- the full Bazel label will start with @alpha_beta//
- build_file_content: CIPD packages do not come with BUILD.bazel files, so we must supply
- one. This should generally contain exports_files or filegroup.
+ the full Bazel label will start with "@alpha_beta//".
cipd_package: The full name of the CIPD package. This is a "path" from the root of CIPD.
- This should be a publicly accessible package, as authentication is not
- supported.
+ This should be a publicly accessible package, as authentication is not
+ supported.
sha256: The sha256 hash of the zip archive downloaded from CIPD. This should match the
- official CIPD website.
- tag: Represents the version of the CIPD package to download.
- For example, git_package:abc123...
+ official CIPD website.
+ tag: Represents the version of the CIPD package to download, e.g. "git_package:abc123...".
+ build_file: The file to use as the BUILD.bazel file for this repository. Such build files
+ typically contain "exports_files" and/or "filegroup" rules. Since CIPD packages do not
+ include BUILD.bazel files, we must provide our own. Either build_file or
+ build_file_content can be specified, but not both.
+ build_file_content: The content for the BUILD file for this repository. Either build_file
+ or build_file_content can be specified, but not both.
"""
cipd_url = "https://chrome-infra-packages.appspot.com/dl/"
cipd_url += cipd_package
@@ -31,13 +34,15 @@
mirror_url = "https://storage.googleapis.com/skia-world-readable/bazel/"
mirror_url += sha256
mirror_url += ".zip"
+
http_archive(
name = name,
- build_file_content = build_file_content,
sha256 = sha256,
urls = [
cipd_url,
mirror_url,
],
type = "zip",
+ build_file = build_file,
+ build_file_content = build_file_content,
)
diff --git a/bazel/external/skimage/BUILD.bazel b/bazel/external/skimage/BUILD.bazel
new file mode 100644
index 0000000..fb7fe0b
--- /dev/null
+++ b/bazel/external/skimage/BUILD.bazel
@@ -0,0 +1,209 @@
+# This file will be copied into the root of the @skimage external repository, which is populated
+# with the contents of the "skimage" CIPD package. All paths are relative to the root directory of
+# said package.
+
+filegroup(
+ name = "dm_pngs_gray8_opaque",
+ srcs = [
+ "dm/basi0g01.png",
+ "dm/basi0g02.png",
+ "dm/basi0g04.png",
+ "dm/basi0g08.png",
+ "dm/basi0g16.png",
+ "dm/basn0g01.png",
+ "dm/basn0g02.png",
+ "dm/basn0g04.png",
+ "dm/basn0g08.png",
+ "dm/basn0g16.png",
+ "dm/cm0n0g04.png",
+ "dm/cm7n0g04.png",
+ "dm/cm9n0g04.png",
+ "dm/ct0n0g04.png",
+ "dm/ct1n0g04.png",
+ "dm/cten0g04.png",
+ "dm/ctfn0g04.png",
+ "dm/ctgn0g04.png",
+ "dm/cthn0g04.png",
+ "dm/ctjn0g04.png",
+ "dm/ctzn0g04.png",
+ "dm/f00n0g08.png",
+ "dm/f01n0g08.png",
+ "dm/f02n0g08.png",
+ "dm/f03n0g08.png",
+ "dm/f04n0g08.png",
+ "dm/f99n0g04.png",
+ "dm/g03n0g16.png",
+ "dm/g04n0g16.png",
+ "dm/g05n0g16.png",
+ "dm/g07n0g16.png",
+ "dm/g10n0g16.png",
+ "dm/g25n0g16.png",
+ "dm/inc0.png",
+ "dm/inc1.png",
+ "dm/inc2.png",
+ "dm/oi1n0g16.png",
+ "dm/oi2n0g16.png",
+ "dm/oi4n0g16.png",
+ "dm/oi9n0g16.png",
+ "dm/ps1n0g08.png",
+ "dm/ps2n0g08.png",
+ "dm/tp0n0g08.png",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "dm_pngs_rgba8888_opaque",
+ srcs = [
+ "dm/PngSuite.png",
+ "dm/basi2c08.png",
+ "dm/basi2c16.png",
+ "dm/basi3p01.png",
+ "dm/basi3p02.png",
+ "dm/basi3p04.png",
+ "dm/basi3p08.png",
+ "dm/basn2c08.png",
+ "dm/basn2c16.png",
+ "dm/basn3p01.png",
+ "dm/basn3p02.png",
+ "dm/basn3p04.png",
+ "dm/basn3p08.png",
+ "dm/ccwn2c08.png",
+ "dm/ccwn3p08.png",
+ "dm/cdfn2c08.png",
+ "dm/cdhn2c08.png",
+ "dm/cdsn2c08.png",
+ "dm/cdun2c08.png",
+ "dm/ch1n3p04.png",
+ "dm/ch2n3p08.png",
+ "dm/cs3n2c16.png",
+ "dm/cs3n3p08.png",
+ "dm/cs5n2c08.png",
+ "dm/cs5n3p08.png",
+ "dm/cs8n2c08.png",
+ "dm/cs8n3p08.png",
+ "dm/errorInInputInterlaced.png",
+ "dm/f00n2c08.png",
+ "dm/f01n2c08.png",
+ "dm/f02n2c08.png",
+ "dm/f03n2c08.png",
+ "dm/f04n2c08.png",
+ "dm/g03n2c08.png",
+ "dm/g03n3p04.png",
+ "dm/g04n2c08.png",
+ "dm/g04n3p04.png",
+ "dm/g05n2c08.png",
+ "dm/g05n3p04.png",
+ "dm/g07n2c08.png",
+ "dm/g07n3p04.png",
+ "dm/g10n2c08.png",
+ "dm/g10n3p04.png",
+ "dm/g25n2c08.png",
+ "dm/g25n3p04.png",
+ "dm/inc13.png",
+ "dm/inc14.png",
+ "dm/inc3.png",
+ "dm/inc4.png",
+ "dm/inc5.png",
+ "dm/inc6.png",
+ "dm/inc7.png",
+ "dm/inc8.png",
+ "dm/incInterlaced.png",
+ "dm/interlaced1.png",
+ "dm/interlaced2.png",
+ "dm/interlaced3.png",
+ "dm/kokteylogo.png",
+ "dm/oi1n2c16.png",
+ "dm/oi2n2c16.png",
+ "dm/oi4n2c16.png",
+ "dm/oi9n2c16.png",
+ "dm/photo.jpg.png",
+ "dm/png_test.png",
+ "dm/pp0n2c16.png",
+ "dm/ps1n2c16.png",
+ "dm/ps2n2c16.png",
+ "dm/s01i3p01.png",
+ "dm/s01n3p01.png",
+ "dm/s02i3p01.png",
+ "dm/s02n3p01.png",
+ "dm/s03i3p01.png",
+ "dm/s03n3p01.png",
+ "dm/s04i3p01.png",
+ "dm/s04n3p01.png",
+ "dm/s05i3p02.png",
+ "dm/s05n3p02.png",
+ "dm/s06i3p02.png",
+ "dm/s06n3p02.png",
+ "dm/s07i3p02.png",
+ "dm/s07n3p02.png",
+ "dm/s08i3p02.png",
+ "dm/s08n3p02.png",
+ "dm/s09i3p02.png",
+ "dm/s09n3p02.png",
+ "dm/s32i3p04.png",
+ "dm/s32n3p04.png",
+ "dm/s33i3p04.png",
+ "dm/s33n3p04.png",
+ "dm/s34i3p04.png",
+ "dm/s34n3p04.png",
+ "dm/s35i3p04.png",
+ "dm/s35n3p04.png",
+ "dm/s36i3p04.png",
+ "dm/s36n3p04.png",
+ "dm/s37i3p04.png",
+ "dm/s37n3p04.png",
+ "dm/s38i3p04.png",
+ "dm/s38n3p04.png",
+ "dm/s39i3p04.png",
+ "dm/s39n3p04.png",
+ "dm/s40i3p04.png",
+ "dm/s40n3p04.png",
+ "dm/tp0n2c08.png",
+ "dm/tp0n3p08.png",
+ "dm/z00n2c08.png",
+ "dm/z03n2c08.png",
+ "dm/z06n2c08.png",
+ "dm/z09n2c08.png",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "dm_pngs_rgba8888_translucent",
+ srcs = [
+ "dm/basi4a08.png",
+ "dm/basi4a16.png",
+ "dm/basi6a08.png",
+ "dm/basi6a16.png",
+ "dm/basn4a08.png",
+ "dm/basn4a16.png",
+ "dm/basn6a08.png",
+ "dm/basn6a16.png",
+ "dm/bgai4a08.png",
+ "dm/bgai4a16.png",
+ "dm/bgan6a08.png",
+ "dm/bgan6a16.png",
+ "dm/bgbn4a08.png",
+ "dm/bggn4a16.png",
+ "dm/bgwn6a08.png",
+ "dm/bgyn6a16.png",
+ "dm/errorInInput.png",
+ "dm/inc10.png",
+ "dm/inc11.png",
+ "dm/inc12.png",
+ "dm/inc9.png",
+ "dm/pp0n6a08.png",
+ "dm/tbbn0g04.png",
+ "dm/tbbn2c16.png",
+ "dm/tbbn3p08.png",
+ "dm/tbgn2c16.png",
+ "dm/tbgn3p08.png",
+ "dm/tbrn2c08.png",
+ "dm/tbwn0g16.png",
+ "dm/tbwn3p08.png",
+ "dm/tbyn3p08.png",
+ "dm/tm3n3p02.png",
+ "dm/tp1n3p08.png",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/gm/BUILD.bazel b/gm/BUILD.bazel
index 55a85ab..ff8171f 100644
--- a/gm/BUILD.bazel
+++ b/gm/BUILD.bazel
@@ -1,6 +1,7 @@
load("//bazel:skia_rules.bzl", "exports_files_legacy", "skia_cc_library")
load("//bazel:cc_test_with_flags.bzl", "cc_test_with_flags")
load(":android_gm_test.bzl", "android_gm_test")
+load(":png_codec.bzl", "png_codec_tests")
licenses(["notice"])
@@ -41,6 +42,8 @@
srcs = [
"//src/utils:json_hdrs",
"//src/utils:json_srcs",
+ "//tools/flags",
+ "//tools/flags:common_flags",
],
deps = [
":gm",
@@ -586,6 +589,8 @@
deps = [":tests_base"],
)
+png_codec_tests(name = "png_codec_tests")
+
[
# Sample invocation (assuming there's a Pixel 5 or similar device available via adb):
#
diff --git a/gm/BazelGMRunner.cpp b/gm/BazelGMRunner.cpp
index ee8668e..d27161f 100644
--- a/gm/BazelGMRunner.cpp
+++ b/gm/BazelGMRunner.cpp
@@ -27,13 +27,12 @@
#include "tools/HashAndEncode.h"
#include <ctime>
+#include <filesystem>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
-struct tm;
-
// TODO(lovisolo): Add flag --skip.
// TODO(lovisolo): Add flag --omitDigestIfHashInFile (provides the known hashes file).
@@ -119,7 +118,7 @@
case skiagm::DrawResult::kSkip:
return "Skip";
default:
- return "Unknown";
+ SkUNREACHABLE;
}
}
@@ -136,7 +135,7 @@
std::unique_ptr<SurfaceManager> surface_manager =
SurfaceManager::FromConfig(config, gm->getISize().width(), gm->getISize().height());
if (surface_manager == nullptr) {
- SK_ABORT("unknown --surfaceConfig flag value: %s", config.c_str());
+ SK_ABORT("Unknown --surfaceConfig flag value: %s.", config.c_str());
}
// Set up GPU.
@@ -171,7 +170,7 @@
gNumSkippedGMs++;
break;
default:
- SK_ABORT("Unknown skiagm::DrawResult: %s", draw_result_to_string(result).c_str());
+ SkUNREACHABLE;
}
// Report GM result and optional message.
@@ -209,12 +208,11 @@
if (argc < 2) {
SkDebugf("GM runner invoked with no arguments.\n");
} else {
- std::ostringstream oss;
- oss << "GM runner invoked with arguments:";
+ SkDebugf("GM runner invoked with arguments:");
for (int i = 1; i < argc; i++) {
- oss << " " << argv[i];
+ SkDebugf(" %s", argv[i]);
}
- SkDebugf("%s\n", oss.str().c_str());
+ SkDebugf("\n");
}
// When running under Bazel (e.g. "bazel test //path/to:test"), we'll store output files in
@@ -230,36 +228,31 @@
// Parse and validate flags.
CommandLineFlags::Parse(argc, argv);
if (!isBazelTest && FLAGS_outputDir.isEmpty()) {
- SkDebugf("Flag --outputDir cannot be empty.\n");
- return 1;
+ SK_ABORT("Flag --outputDir cannot be empty.");
}
if (FLAGS_outputDir.size() > 1) {
- SkDebugf("Flag --outputDir takes one single value, got %d.\n", FLAGS_outputDir.size());
- return 1;
+ SK_ABORT("Flag --outputDir takes one single value, got %d.", FLAGS_outputDir.size());
}
if (FLAGS_surfaceConfig.isEmpty()) {
- SkDebugf("Flag --surfaceConfig cannot be empty.\n");
- return 1;
+ SK_ABORT("Flag --surfaceConfig cannot be empty.");
}
if (FLAGS_surfaceConfig.size() > 1) {
- SkDebugf("Flag --surfaceConfig takes one single value, got %d.\n",
+ SK_ABORT("Flag --surfaceConfig takes one single value, got %d.",
FLAGS_surfaceConfig.size());
- return 1;
}
if (FLAGS_via.size() > 1) {
- SkDebugf("Flag --via takes at most one value, got %d.\n", FLAGS_via.size());
- return 1;
+ SK_ABORT("Flag --via takes at most one value, got %d.", FLAGS_via.size());
}
std::string outputDir =
FLAGS_outputDir.isEmpty() ? testUndeclaredOutputsDir : FLAGS_outputDir[0];
- std::string config(FLAGS_surfaceConfig[0]);
+ std::string config = FLAGS_surfaceConfig[0];
// Execute all GM registerer functions, then run all registered GMs.
for (const skiagm::GMRegistererFn& f : skiagm::GMRegistererFnRegistry::Range()) {
std::string errorMsg = f();
if (errorMsg != "") {
- SK_ABORT("error while gathering GMs: %s", errorMsg.c_str());
+ SK_ABORT("Error while gathering GMs: %s", errorMsg.c_str());
}
}
for (const skiagm::GMFactory& f : skiagm::GMRegistry::Range()) {
diff --git a/gm/gm.h b/gm/gm.h
index 349fe09..dd21e31 100644
--- a/gm/gm.h
+++ b/gm/gm.h
@@ -231,7 +231,8 @@
void Register(skiagm::GM* gm);
// Registry of functions that dynamically register GMs. Useful for GMs that are unknown at
- // compile time, such as those that are created from images in a directory.
+ // compile time, such as those that are created from images in a directory (see e.g.
+ // //gm/png_codec.cpp).
//
// A GMRegistererFn may call skiagm::Register() zero or more times to register GMs as needed.
// It should return the empty string on success, or a human-friendly message in the case of
diff --git a/gm/png_codec.bzl b/gm/png_codec.bzl
new file mode 100644
index 0000000..5573984
--- /dev/null
+++ b/gm/png_codec.bzl
@@ -0,0 +1,181 @@
+"""This module defines the png_codec_tests macro."""
+
+load("//bazel:cc_test_with_flags.bzl", "cc_test_with_flags")
+
+# These lists of lists are shaped as follows:
+#
+# [images, decode_mode, dst_color_type, dst_alpha_type, surface_config]
+#
+# For each such list, we will define a test that decodes each image into an SkImage using
+# SkPngDecoder. The decode mode, destination color type and alpha type are specified via the
+# decode_mode, dst_color_type and dst_alpha_type fields, respectively. The resulting image is then
+# drawn into an SkSurface specified via the surface_config field, and is saved as an undeclared
+# test output which may be uploaded to Gold. See //gm/BazelGMRunner.cpp for more details.
+#
+# Some combinations of parameters are excluded because they are mutually incompatible or redundant.
+_GRAYSCALE_8888_TESTS = [
+ [
+ "@skimage//:dm_pngs_gray8_opaque",
+ decode_mode,
+ dst_color_type,
+ "premul",
+ "8888",
+ ]
+ for decode_mode in ["get-all-pixels", "incremental", "zero-init"]
+ for dst_color_type in ["force-grayscale", "force-nonnative-premul-color", "get-from-canvas"]
+]
+_GRAYSCALE_565_TESTS = [
+ [
+ "@skimage//:dm_pngs_gray8_opaque",
+ decode_mode,
+ "get-from-canvas",
+ "premul",
+ "565",
+ ]
+ for decode_mode in ["get-all-pixels", "incremental", "zero-init"]
+]
+_COLOR_TRANSLUCENT_TESTS = [
+ [
+ "@skimage//:dm_pngs_rgba8888_translucent",
+ decode_mode,
+ dst_color_type,
+ dst_alpha_type,
+ "8888",
+ ]
+ for decode_mode in ["get-all-pixels", "incremental", "zero-init"]
+ for dst_color_type in ["force-nonnative-premul-color", "get-from-canvas"]
+ for dst_alpha_type in ["premul", "unpremul"]
+]
+_COLOR_OPAQUE_8888_TESTS = [
+ [
+ "@skimage//:dm_pngs_rgba8888_opaque",
+ decode_mode,
+ dst_color_type,
+ "premul",
+ "8888",
+ ]
+ for decode_mode in ["get-all-pixels", "incremental", "zero-init"]
+ for dst_color_type in ["force-nonnative-premul-color", "get-from-canvas"]
+]
+_COLOR_OPAQUE_565_TESTS = [
+ [
+ "@skimage//:dm_pngs_rgba8888_opaque",
+ decode_mode,
+ "get-from-canvas",
+ "premul",
+ "565",
+ ]
+ for decode_mode in ["get-all-pixels", "incremental", "zero-init"]
+]
+_TESTS = (
+ _GRAYSCALE_8888_TESTS +
+ _GRAYSCALE_565_TESTS +
+ _COLOR_TRANSLUCENT_TESTS +
+ _COLOR_OPAQUE_8888_TESTS +
+ _COLOR_OPAQUE_565_TESTS
+)
+
+def png_codec_tests(name):
+ """Generates various cc_test_with_flags targets for png_codec.cpp.
+
+ Args:
+ name: The name of the test_suite to generate.
+ """
+
+ all_tests = []
+
+ for images, decode_mode, dst_color_type, dst_alpha_type, surface_config in _TESTS:
+ test_name = "png_codec_%s_%s_%s_%s_%s_test" % (
+ images.replace("@skimage//:dm_pngs_", "").replace("_", "-"),
+ decode_mode,
+ dst_color_type,
+ dst_alpha_type,
+ surface_config,
+ )
+ all_tests.append(test_name)
+
+ cc_test_with_flags(
+ name = test_name,
+ size = "large",
+ srcs = [
+ "BazelGMRunner.cpp",
+ "png_codec.cpp",
+ ],
+ args = [
+ "--surfaceConfig",
+ surface_config,
+ "--pngCodecGMImages",
+ "external/skimage/dm",
+ "--pngCodecDecodeMode",
+ decode_mode,
+ "--pngCodecDstColorType",
+ dst_color_type,
+ "--pngCodecDstAlphaType",
+ dst_alpha_type,
+ ],
+ data = [images],
+ set_flags = {
+ "include_decoder": [
+ "png_decode_codec",
+ ],
+ },
+ deps = [":tests_base"],
+ )
+
+ native.test_suite(
+ name = name,
+ tests = all_tests,
+ )
+
+ # List all generated target names for greppability.
+ #
+ # Editing this list does not by itself affect which targets are generated. Instead, edit the
+ # list comprehensions at the top of this file, try to run a target with Bazel, and update this
+ # list as instructed in the "out of sync" error message produced by the below fail() statement.
+ greppable_test_list = [
+ "png_codec_gray8-opaque_get-all-pixels_force-grayscale_premul_8888_test",
+ "png_codec_gray8-opaque_get-all-pixels_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_gray8-opaque_get-all-pixels_get-from-canvas_premul_8888_test",
+ "png_codec_gray8-opaque_incremental_force-grayscale_premul_8888_test",
+ "png_codec_gray8-opaque_incremental_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_gray8-opaque_incremental_get-from-canvas_premul_8888_test",
+ "png_codec_gray8-opaque_zero-init_force-grayscale_premul_8888_test",
+ "png_codec_gray8-opaque_zero-init_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_gray8-opaque_zero-init_get-from-canvas_premul_8888_test",
+ "png_codec_gray8-opaque_get-all-pixels_get-from-canvas_premul_565_test",
+ "png_codec_gray8-opaque_incremental_get-from-canvas_premul_565_test",
+ "png_codec_gray8-opaque_zero-init_get-from-canvas_premul_565_test",
+ "png_codec_rgba8888-translucent_get-all-pixels_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_rgba8888-translucent_get-all-pixels_force-nonnative-premul-color_unpremul_8888_test",
+ "png_codec_rgba8888-translucent_get-all-pixels_get-from-canvas_premul_8888_test",
+ "png_codec_rgba8888-translucent_get-all-pixels_get-from-canvas_unpremul_8888_test",
+ "png_codec_rgba8888-translucent_incremental_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_rgba8888-translucent_incremental_force-nonnative-premul-color_unpremul_8888_test",
+ "png_codec_rgba8888-translucent_incremental_get-from-canvas_premul_8888_test",
+ "png_codec_rgba8888-translucent_incremental_get-from-canvas_unpremul_8888_test",
+ "png_codec_rgba8888-translucent_zero-init_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_rgba8888-translucent_zero-init_force-nonnative-premul-color_unpremul_8888_test",
+ "png_codec_rgba8888-translucent_zero-init_get-from-canvas_premul_8888_test",
+ "png_codec_rgba8888-translucent_zero-init_get-from-canvas_unpremul_8888_test",
+ "png_codec_rgba8888-opaque_get-all-pixels_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_rgba8888-opaque_get-all-pixels_get-from-canvas_premul_8888_test",
+ "png_codec_rgba8888-opaque_incremental_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_rgba8888-opaque_incremental_get-from-canvas_premul_8888_test",
+ "png_codec_rgba8888-opaque_zero-init_force-nonnative-premul-color_premul_8888_test",
+ "png_codec_rgba8888-opaque_zero-init_get-from-canvas_premul_8888_test",
+ "png_codec_rgba8888-opaque_get-all-pixels_get-from-canvas_premul_565_test",
+ "png_codec_rgba8888-opaque_incremental_get-from-canvas_premul_565_test",
+ "png_codec_rgba8888-opaque_zero-init_get-from-canvas_premul_565_test",
+ ]
+ if greppable_test_list != all_tests:
+ msg = [
+ "Variable greppable_test_list is out of sync. Please update it as follows:",
+ "",
+ " greppable_test_list = [",
+ ] + [
+ " \"" + test + "\","
+ for test in all_tests
+ ] + [
+ " ]",
+ ]
+ fail("\n".join(msg))
diff --git a/gm/png_codec.cpp b/gm/png_codec.cpp
new file mode 100644
index 0000000..23068db
--- /dev/null
+++ b/gm/png_codec.cpp
@@ -0,0 +1,523 @@
+/*
+ * 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/SkAndroidCodec.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/SkOpts.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 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";
+ }
+ 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* ModeToString(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;
+ }
+
+protected:
+ // Based on CodecSrc::name().
+ // https://skia.googlesource.com/skia/+/ce49fc71bc7cc25244020cd3e64764a6d08e54fb/dm/DMSrcSink.cpp#828
+ SkString onShortName() 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 onISize() 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 "";
+});
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index ed758d8..69a973f 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -1008,6 +1008,7 @@
virtual SkSampler* getSampler(bool /*createIfNecessary*/) { return nullptr; }
friend class DM::CodecSrc; // for fillIncompleteImage
+ friend class PNGCodecGM; // for fillIncompleteImage
friend class SkSampledCodec;
friend class SkIcoCodec;
friend class SkAndroidCodec; // for fEncodedInfo
diff --git a/tools/flags/BUILD.bazel b/tools/flags/BUILD.bazel
index 5429651..5ac4ae8 100644
--- a/tools/flags/BUILD.bazel
+++ b/tools/flags/BUILD.bazel
@@ -12,6 +12,7 @@
"CommandLineFlags.h",
],
visibility = [
+ "//gm:__pkg__",
"//modules/skottie:__pkg__",
"//tests:__subpackages__",
"//tools:__pkg__",
@@ -25,9 +26,13 @@
srcs = [
"CommonFlags.h",
"CommonFlagsFontMgr.cpp",
- "CommonFlagsGpu.cpp",
- ],
+ "CommonFlagsImages.cpp",
+ ] + select({
+ "//src/gpu:has_ganesh_backend": ["CommonFlagsGpu.cpp"],
+ "//conditions:default": [],
+ }),
visibility = [
+ "//gm:__pkg__",
"//tools:__subpackages__",
],
)