LeOSium_webview/LeOS/patches/00Add-support-to-jxl.patch

2729 lines
112 KiB
Diff
Raw Normal View History

2023-11-18 11:46:19 +01:00
From: uazo <uazo@users.noreply.github.com>
Date: Tue, 24 Oct 2023 10:02:00 +0000
Subject: Add support to jxl
Partial revert of https://chromium-review.googlesource.com/c/chromium/src/+/4095497
Enabled by default
---
DEPS | 7 +
build/linux/unbundle/libjxl.gn | 34 +
build/linux/unbundle/replace_gn_files.py | 1 +
cc/base/devtools_instrumentation.cc | 3 +
cc/base/devtools_instrumentation.h | 2 +-
cc/paint/paint_image.h | 2 +-
cc/tiles/image_decode_cache.h | 2 +
chrome/browser/about_flags.cc | 6 +
chrome/browser/flag-metadata.json | 5 +
chrome/browser/flag_descriptions.cc | 7 +
chrome/browser/flag_descriptions.h | 5 +
content/common/content_constants_internal.cc | 11 +-
content/common/content_constants_internal.h | 3 +-
content/public/browser/frame_accept_header.cc | 13 +-
media/BUILD.gn | 1 +
media/media_options.gni | 3 +
net/base/mime_util.cc | 2 +
net/base/mime_util_unittest.cc | 3 +
third_party/.gitignore | 1 +
third_party/blink/common/features.cc | 3 +
.../blink/common/loader/network_utils.cc | 16 +-
.../blink/common/mime_util/mime_util.cc | 7 +
.../common/mime_util/mime_util_unittest.cc | 6 +
third_party/blink/public/common/features.h | 2 +
.../devtools_protocol/browser_protocol.pdl | 1 +
.../inspector/inspector_emulation_agent.cc | 7 +-
.../inspector_emulation_agent_test.cc | 37 +
.../generate_image_corpus.py | 1 +
.../modules/webcodecs/image_decoder_fuzzer.cc | 5 +
third_party/blink/renderer/platform/BUILD.gn | 5 +
.../platform/graphics/bitmap_image_metrics.cc | 9 +-
.../platform/graphics/bitmap_image_metrics.h | 4 +-
.../renderer/platform/image-decoders/BUILD.gn | 9 +
.../platform/image-decoders/image_decoder.cc | 23 +
.../image-decoders/jxl/jxl_image_decoder.cc | 683 ++++++++++++++++++
.../image-decoders/jxl/jxl_image_decoder.h | 123 ++++
.../jxl/jxl_image_decoder_test.cc | 626 ++++++++++++++++
.../blink/tools/commit_stats/git-dirs.txt | 1 +
third_party/blink/web_tests/TestExpectations | 6 +
third_party/blink/web_tests/VirtualTestSuites | 9 +
...-set-disabled-image-types-jxl-expected.txt | 13 +
.../emulation-set-disabled-image-types-jxl.js | 50 ++
.../resources/image-jxl-fallback-img.html | 1 +
.../resources/image-jxl-fallback-picture.html | 4 +
.../web_tests/images/jxl/jxl-images.html | 22 +
.../web_tests/images/jxl/progressive.html | 6 +
.../web_tests/images/resources/jxl/README.md | 79 ++
.../web_tests/virtual/jxl-enabled/README.md | 5 +
third_party/libjxl/BUILD.gn | 79 ++
third_party/libjxl/DIR_METADATA | 4 +
third_party/libjxl/LICENSE | 27 +
third_party/libjxl/OWNERS | 9 +
third_party/libjxl/README.chromium | 15 +
.../libjxl/gen_headers/jxl/jxl_export.h | 11 +
tools/metrics/histograms/enums.xml | 3 +-
55 files changed, 2005 insertions(+), 17 deletions(-)
create mode 100644 build/linux/unbundle/libjxl.gn
create mode 100644 third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.cc
create mode 100644 third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.h
create mode 100644 third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc
create mode 100644 third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl-expected.txt
create mode 100644 third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl.js
create mode 100644 third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-img.html
create mode 100644 third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-picture.html
create mode 100644 third_party/blink/web_tests/images/jxl/jxl-images.html
create mode 100644 third_party/blink/web_tests/images/jxl/progressive.html
create mode 100644 third_party/blink/web_tests/images/resources/jxl/README.md
create mode 100644 third_party/blink/web_tests/virtual/jxl-enabled/README.md
create mode 100644 third_party/libjxl/BUILD.gn
create mode 100644 third_party/libjxl/DIR_METADATA
create mode 100644 third_party/libjxl/LICENSE
create mode 100644 third_party/libjxl/OWNERS
create mode 100644 third_party/libjxl/README.chromium
create mode 100644 third_party/libjxl/gen_headers/jxl/jxl_export.h
diff --git a/DEPS b/DEPS
--- a/DEPS
+++ b/DEPS
@@ -515,6 +515,10 @@ vars = {
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling feed
# and whatever else without interference from each other.
+ 'libjxl_revision': '954b460768c08a147abf47689ad69b0e7beff65e',
+ # Three lines of non-changing comments so that
+ # the commit queue can handle CLs rolling feed
+ # and whatever else without interference from each other.
'highway_revision': '8f20644eca693cfb74aa795b0006b6779c370e7a',
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling ffmpeg
@@ -1205,6 +1209,9 @@ deps = {
'src/third_party/dawn':
Var('dawn_git') + '/dawn.git' + '@' + Var('dawn_revision'),
+ 'src/third_party/libjxl/src':
+ Var('chromium_git') + '/external/github.com/libjxl/libjxl.git' + '@' + Var('libjxl_revision'),
+
'src/third_party/highway/src':
Var('chromium_git') + '/external/github.com/google/highway.git' + '@' + Var('highway_revision'),
diff --git a/build/linux/unbundle/libjxl.gn b/build/linux/unbundle/libjxl.gn
new file mode 100644
--- /dev/null
+++ b/build/linux/unbundle/libjxl.gn
@@ -0,0 +1,34 @@
+import("//build/config/linux/pkg_config.gni")
+import("//build/shim_headers.gni")
+
+pkg_config("system_libjxl") {
+ packages = [ "libjxl" ]
+}
+
+shim_headers("jxl_shim") {
+ root_path = "src/lib/include"
+ headers = [
+ "jxl/butteraugli.h",
+ "jxl/butteraugli_cxx.h",
+ "jxl/codestream_header.h",
+ "jxl/color_encoding.h",
+ "jxl/decode.h",
+ "jxl/decode_cxx.h",
+ "jxl/encode.h",
+ "jxl/encode_cxx.h",
+ "jxl/jxl_export.h",
+ "jxl/jxl_threads_export.h",
+ "jxl/memory_manager.h",
+ "jxl/parallel_runner.h",
+ "jxl/resizable_parallel_runner.h",
+ "jxl/resizable_parallel_runner_cxx.h",
+ "jxl/thread_parallel_runner.h",
+ "jxl/thread_parallel_runner_cxx.h",
+ "jxl/types.h",
+ ]
+}
+
+source_set("libjxl") {
+ deps = [ ":jxl_shim" ]
+ public_configs = [ ":system_libjxl" ]
+}
diff --git a/build/linux/unbundle/replace_gn_files.py b/build/linux/unbundle/replace_gn_files.py
--- a/build/linux/unbundle/replace_gn_files.py
+++ b/build/linux/unbundle/replace_gn_files.py
@@ -53,6 +53,7 @@ REPLACEMENTS = {
'libdrm': 'third_party/libdrm/BUILD.gn',
'libevent': 'third_party/libevent/BUILD.gn',
'libjpeg': 'third_party/libjpeg.gni',
+ 'libjxl' : 'third_party/libjxl/BUILD.gn',
'libpng': 'third_party/libpng/BUILD.gn',
'libvpx': 'third_party/libvpx/BUILD.gn',
'libwebp': 'third_party/libwebp/BUILD.gn',
diff --git a/cc/base/devtools_instrumentation.cc b/cc/base/devtools_instrumentation.cc
--- a/cc/base/devtools_instrumentation.cc
+++ b/cc/base/devtools_instrumentation.cc
@@ -90,6 +90,9 @@ ScopedImageDecodeTask::~ScopedImageDecodeTask() {
auto duration = base::TimeTicks::Now() - start_time_;
const char* histogram_name = nullptr;
switch (image_type_) {
+ case ImageType::kJxl:
+ histogram_name = "Renderer4.ImageUploadTaskDurationUs.Jxl";
+ break;
case ImageType::kAvif:
histogram_name = "Renderer4.ImageDecodeTaskDurationUs.Avif";
break;
diff --git a/cc/base/devtools_instrumentation.h b/cc/base/devtools_instrumentation.h
--- a/cc/base/devtools_instrumentation.h
+++ b/cc/base/devtools_instrumentation.h
@@ -72,7 +72,7 @@ class CC_BASE_EXPORT ScopedLayerTask {
class CC_BASE_EXPORT ScopedImageTask {
public:
- enum ImageType { kAvif, kBmp, kGif, kIco, kJpeg, kPng, kWebP, kOther };
+ enum ImageType { kJxl, kAvif, kBmp, kGif, kIco, kJpeg, kPng, kWebP, kOther };
explicit ScopedImageTask(ImageType image_type)
: image_type_(image_type), start_time_(base::TimeTicks::Now()) {}
diff --git a/cc/paint/paint_image.h b/cc/paint/paint_image.h
--- a/cc/paint/paint_image.h
+++ b/cc/paint/paint_image.h
@@ -40,7 +40,7 @@ class PaintImageGenerator;
class PaintWorkletInput;
class TextureBacking;
-enum class ImageType { kPNG, kJPEG, kWEBP, kGIF, kICO, kBMP, kAVIF, kInvalid };
+enum class ImageType { kPNG, kJPEG, kWEBP, kGIF, kICO, kBMP, kAVIF, kJXL, kInvalid };
enum class AuxImage : size_t { kDefault = 0, kGainmap = 1 };
static constexpr std::array<AuxImage, 2> kAllAuxImages = {AuxImage::kDefault,
diff --git a/cc/tiles/image_decode_cache.h b/cc/tiles/image_decode_cache.h
--- a/cc/tiles/image_decode_cache.h
+++ b/cc/tiles/image_decode_cache.h
@@ -84,6 +84,8 @@ class CC_EXPORT ImageDecodeCache {
using ScopedImageType =
devtools_instrumentation::ScopedImageDecodeTask::ImageType;
switch (image_type) {
+ case ImageType::kJXL:
+ return ScopedImageType::kJxl;
case ImageType::kAVIF:
return ScopedImageType::kAvif;
case ImageType::kBMP:
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -8918,6 +8918,12 @@ const FeatureEntry kFeatureEntries[] = {
FEATURE_VALUE_TYPE(download::features::kSmartSuggestionForLargeDownloads)},
#endif // BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ {"enable-jxl", flag_descriptions::kEnableJXLName,
+ flag_descriptions::kEnableJXLDescription, kOsAll,
+ FEATURE_VALUE_TYPE(blink::features::kJXL)},
+#endif // BUILDFLAG(ENABLE_JXL_DECODER)
+
#if BUILDFLAG(IS_ANDROID)
{"messages-for-android-ads-blocked",
flag_descriptions::kMessagesForAndroidAdsBlockedName,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3095,6 +3095,11 @@
// This flag is used by web developers to test upcoming javascript features.
"expiry_milestone": -1
},
+ {
+ "name": "enable-jxl",
+ "owners": [ "eustas@chromium.org", "firsching", "sboukortt", "veluca" ],
+ "expiry_milestone": 150
+ },
{
"name": "enable-keyboard-backlight-toggle",
"owners": [ "rtinkoff" ],
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -7714,6 +7714,13 @@ const char kDcheckIsFatalDescription[] =
"rather than crashing. If enabled, DCHECKs will crash the calling process.";
#endif // BUILDFLAG(DCHECK_IS_CONFIGURABLE)
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+const char kEnableJXLName[] = "Enable JXL image format";
+const char kEnableJXLDescription[] =
+ "Adds image decoding support for the JPEG XL image format. NOTE: JPEG XL "
+ "format will be removed in Chrome 110 release.";
+#endif // BUILDFLAG(ENABLE_JXL_DECODER)
+
#if BUILDFLAG(ENABLE_CARDBOARD)
const char kEnableCardboardName[] = "Enable Cardboard VR WebXR Runtime";
const char kEnableCardboardDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -4459,6 +4459,11 @@ extern const char kDcheckIsFatalName[];
extern const char kDcheckIsFatalDescription[];
#endif // BUILDFLAG(DCHECK_IS_CONFIGURABLE)
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+extern const char kEnableJXLName[];
+extern const char kEnableJXLDescription[];
+#endif // BUILDFLAG(ENABLE_JXL_DECODER)
+
#if BUILDFLAG(ENABLE_CARDBOARD)
extern const char kEnableCardboardName[];
extern const char kEnableCardboardDescription[];
diff --git a/content/common/content_constants_internal.cc b/content/common/content_constants_internal.cc
--- a/content/common/content_constants_internal.cc
+++ b/content/common/content_constants_internal.cc
@@ -19,13 +19,16 @@ const int kTraceEventGpuProcessSortIndex = -1;
const int kTraceEventRendererMainThreadSortIndex = -1;
+const char kFrameAcceptHeaderValue_Prefix[] =
+ "text/html,application/xhtml+xml,application/xml;q=0.9,";
+
#if BUILDFLAG(ENABLE_AV1_DECODER)
-const char kFrameAcceptHeaderValue[] =
- "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
+const char kFrameAcceptHeaderValue_Suffix[] =
+ "image/avif,"
"image/webp,image/apng,*/*;q=0.8";
#else
-const char kFrameAcceptHeaderValue[] =
- "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,"
+const char kFrameAcceptHeaderValue_Suffix[] =
+ "image/webp,"
"image/apng,*/*;q=0.8";
#endif
diff --git a/content/common/content_constants_internal.h b/content/common/content_constants_internal.h
--- a/content/common/content_constants_internal.h
+++ b/content/common/content_constants_internal.h
@@ -39,7 +39,8 @@ CONTENT_EXPORT extern const int kTraceEventGpuProcessSortIndex;
CONTENT_EXPORT extern const int kTraceEventRendererMainThreadSortIndex;
// Accept header used for frame requests.
-CONTENT_EXPORT extern const char kFrameAcceptHeaderValue[];
+CONTENT_EXPORT extern const char kFrameAcceptHeaderValue_Prefix[];
+CONTENT_EXPORT extern const char kFrameAcceptHeaderValue_Suffix[];
// Constants for attaching message pipes to the mojo invitation used to
// initialize child processes.
diff --git a/content/public/browser/frame_accept_header.cc b/content/public/browser/frame_accept_header.cc
--- a/content/public/browser/frame_accept_header.cc
+++ b/content/public/browser/frame_accept_header.cc
@@ -7,13 +7,22 @@
#include "content/browser/web_package/signed_exchange_consts.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/common/content_constants_internal.h"
+#include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
namespace content {
std::string FrameAcceptHeaderValue(bool allow_sxg_responses,
BrowserContext* browser_context) {
- std::string header_value = kFrameAcceptHeaderValue;
-
+ std::string header_value = kFrameAcceptHeaderValue_Prefix;
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ // In case the buildflag and the runtime flag are enables, we insert
+ // "image/jxl," into the header value at the correct place.
+ if (base::FeatureList::IsEnabled(blink::features::kJXL)) {
+ header_value.append("image/jxl,");
+ }
+#endif // BUILDFLAG(ENABLE_JXL_DECODER)
+ header_value.append(kFrameAcceptHeaderValue_Suffix);
if (allow_sxg_responses &&
content::signed_exchange_utils::IsSignedExchangeHandlingEnabled(
browser_context)) {
diff --git a/media/BUILD.gn b/media/BUILD.gn
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -37,6 +37,7 @@ buildflag_header("media_buildflags") {
"ENABLE_CAST_AUDIO_RENDERER=$enable_cast_audio_renderer",
"ENABLE_DAV1D_DECODER=$enable_dav1d_decoder",
"ENABLE_AV1_DECODER=$enable_av1_decoder",
+ "ENABLE_JXL_DECODER=$enable_jxl_decoder",
"ENABLE_PLATFORM_DOLBY_VISION=$enable_platform_dolby_vision",
"ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION=$enable_platform_encrypted_dolby_vision",
"ENABLE_FFMPEG=$media_use_ffmpeg",
diff --git a/media/media_options.gni b/media/media_options.gni
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -125,6 +125,9 @@ declare_args() {
# `enable_libaom` should likely also be overriddent to false.
enable_av1_decoder = enable_dav1d_decoder
+ # If true, adds support for JPEG XL image decoding.
+ enable_jxl_decoder = is_android || is_win
+
# Enable HEVC/H265 demuxing. Actual decoding must be provided by the
# platform. Always enable this for Lacros, it determines support at runtime.
# TODO(crbug.com/1336055): Revisit the default value for this setting as it
diff --git a/net/base/mime_util.cc b/net/base/mime_util.cc
--- a/net/base/mime_util.cc
+++ b/net/base/mime_util.cc
@@ -163,6 +163,7 @@ static const MimeInfo kPrimaryMappings[] = {
{"image/avif", "avif"},
{"image/gif", "gif"},
{"image/jpeg", "jpeg,jpg"},
+ {"image/jxl", "jxl"},
{"image/png", "png"},
{"image/apng", "png,apng"},
{"image/svg+xml", "svg,svgz"},
@@ -666,6 +667,7 @@ static const char* const kStandardImageTypes[] = {"image/avif",
"image/gif",
"image/ief",
"image/jpeg",
+ "image/jxl",
"image/webp",
"image/pict",
"image/pipeg",
diff --git a/net/base/mime_util_unittest.cc b/net/base/mime_util_unittest.cc
--- a/net/base/mime_util_unittest.cc
+++ b/net/base/mime_util_unittest.cc
@@ -39,6 +39,7 @@ TEST(MimeUtilTest, GetWellKnownMimeTypeFromExtension) {
{FILE_PATH_LITERAL("webm"), "video/webm"},
{FILE_PATH_LITERAL("weba"), "audio/webm"},
{FILE_PATH_LITERAL("avif"), "image/avif"},
+ {FILE_PATH_LITERAL("jxl"), "image/jxl"},
{FILE_PATH_LITERAL("epub"), "application/epub+zip"},
{FILE_PATH_LITERAL("apk"), "application/vnd.android.package-archive"},
{FILE_PATH_LITERAL("cer"), "application/x-x509-ca-cert"},
@@ -80,6 +81,7 @@ TEST(MimeUtilTest, ExtensionTest) {
{FILE_PATH_LITERAL("webm"), {"video/webm"}},
{FILE_PATH_LITERAL("weba"), {"audio/webm"}},
{FILE_PATH_LITERAL("avif"), {"image/avif"}},
+ {FILE_PATH_LITERAL("jxl"), {"image/jxl"}},
#if BUILDFLAG(IS_CHROMEOS_ASH)
// These are test cases for testing platform mime types on ChromeOS.
{FILE_PATH_LITERAL("epub"), {"application/epub+zip"}},
@@ -497,6 +499,7 @@ TEST(MimeUtilTest, TestGetExtensionsForMimeType) {
{"MeSsAge/*", 1, "eml"},
{"message/", 0, nullptr, true},
{"image/avif", 1, "avif"},
+ {"image/jxl", 1, "jxl"},
{"image/bmp", 1, "bmp"},
{"video/*", 6, "mp4"},
{"video/*", 6, "mpeg"},
diff --git a/third_party/.gitignore b/third_party/.gitignore
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -97,6 +97,7 @@
/libgifcodec
/libjingle/source
/libupnp
+#/libjxl/src
/llvm
/llvm-allocated-type
/llvm-bootstrap
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -422,6 +422,9 @@ BASE_FEATURE(kCORSErrorsIssueOnly,
"CORSErrorsIssueOnly",
base::FEATURE_DISABLED_BY_DEFAULT);
+// Enables the JPEG XL Image File Format (JXL).
+BASE_FEATURE(kJXL, "JXL", base::FEATURE_ENABLED_BY_DEFAULT);
+
// When enabled, code cache is produced asynchronously from the script execution
// (https://crbug.com/1260908).
BASE_FEATURE(kCacheCodeOnIdle,
diff --git a/third_party/blink/common/loader/network_utils.cc b/third_party/blink/common/loader/network_utils.cc
--- a/third_party/blink/common/loader/network_utils.cc
+++ b/third_party/blink/common/loader/network_utils.cc
@@ -9,6 +9,7 @@
#include "services/network/public/cpp/constants.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
namespace blink {
namespace network_utils {
@@ -33,7 +34,20 @@ bool AlwaysAccessNetwork(
}
const char* ImageAcceptHeader() {
-#if BUILDFLAG(ENABLE_AV1_DECODER)
+#if BUILDFLAG(ENABLE_JXL_DECODER) && BUILDFLAG(ENABLE_AV1_DECODER)
+ if (base::FeatureList::IsEnabled(blink::features::kJXL)) {
+ return "image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/"
+ "*;q=0.8";
+ } else {
+ return "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ }
+#elif BUILDFLAG(ENABLE_JXL_DECODER)
+ if (base::FeatureList::IsEnabled(blink::features::kJXL)) {
+ return "image/jxl,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ } else {
+ return "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ }
+#elif BUILDFLAG(ENABLE_AV1_DECODER)
return "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
#else
return "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
diff --git a/third_party/blink/common/mime_util/mime_util.cc b/third_party/blink/common/mime_util/mime_util.cc
--- a/third_party/blink/common/mime_util/mime_util.cc
+++ b/third_party/blink/common/mime_util/mime_util.cc
@@ -14,6 +14,7 @@
#include "media/media_buildflags.h"
#include "net/base/mime_util.h"
#include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
#if !BUILDFLAG(IS_IOS)
// iOS doesn't use and must not depend on //media
@@ -144,6 +145,12 @@ MimeUtil::MimeUtil() {
non_image_types_.insert(type);
for (const char* type : kSupportedImageTypes)
image_types_.insert(type);
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ // TODO(firsching): Add "image/jxl" to the kSupportedImageTypes array when the
+ // JXL feature is shipped.
+ if (base::FeatureList::IsEnabled(features::kJXL))
+ image_types_.insert("image/jxl");
+#endif
for (const char* type : kUnsupportedTextTypes)
unsupported_text_types_.insert(type);
for (const char* type : kSupportedJavascriptTypes) {
diff --git a/third_party/blink/common/mime_util/mime_util_unittest.cc b/third_party/blink/common/mime_util/mime_util_unittest.cc
--- a/third_party/blink/common/mime_util/mime_util_unittest.cc
+++ b/third_party/blink/common/mime_util/mime_util_unittest.cc
@@ -9,6 +9,7 @@
#include "net/base/mime_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
namespace blink {
@@ -18,7 +19,12 @@ TEST(MimeUtilTest, LookupTypes) {
EXPECT_TRUE(IsSupportedImageMimeType("image/jpeg"));
EXPECT_TRUE(IsSupportedImageMimeType("Image/JPEG"));
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ EXPECT_EQ(IsSupportedImageMimeType("image/jxl"),
+ base::FeatureList::IsEnabled(features::kJXL));
+#else
EXPECT_FALSE(IsSupportedImageMimeType("image/jxl"));
+#endif
EXPECT_EQ(IsSupportedImageMimeType("image/avif"),
BUILDFLAG(ENABLE_AV1_DECODER));
EXPECT_FALSE(IsSupportedImageMimeType("image/lolcat"));
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -145,6 +145,8 @@ BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBackgroundResourceFetch);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kShowAlwaysContextMenuOnLinks);
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kJXL);
+
// Used to configure a per-origin allowlist of performance.mark events that are
// permitted to be included in slow reports traces. See crbug.com/1181774.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBackgroundTracingPerformanceMark);
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -4133,6 +4133,7 @@ domain Emulation
experimental type DisabledImageType extends string
enum
avif
+ jxl
webp
experimental command setDisabledImageTypes
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
@@ -495,10 +495,10 @@ AtomicString InspectorEmulationAgent::OverrideAcceptImageHeader(
String header(network_utils::ImageAcceptHeader());
for (String type : *disabled_image_types) {
// The header string is expected to be like
- // `image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8`
+ // `image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8`
// and is expected to be always ending with `image/*,*/*;q=xxx`, therefore,
- // to remove a type we replace `image/x,` with empty string. Only webp and
- // avif types can be disabled.
+ // to remove a type we replace `image/x,` with empty string. Only webp, avif
+ // and jxl types can be disabled.
header.Replace(String(type + ","), "");
}
return AtomicString(header);
@@ -842,6 +842,7 @@ protocol::Response InspectorEmulationAgent::setDisabledImageTypes(
namespace DisabledImageTypeEnum = protocol::Emulation::DisabledImageTypeEnum;
for (protocol::Emulation::DisabledImageType type : *disabled_types) {
if (DisabledImageTypeEnum::Avif == type ||
+ DisabledImageTypeEnum::Jxl == type ||
DisabledImageTypeEnum::Webp == type) {
disabled_image_types_.Set(prefix + type, true);
continue;
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent_test.cc b/third_party/blink/renderer/core/inspector/inspector_emulation_agent_test.cc
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent_test.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent_test.cc
@@ -7,6 +7,7 @@
#include "media/media_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
namespace blink {
@@ -22,6 +23,8 @@ TEST_F(InspectorEmulationAgentTest, ModifiesAcceptHeader) {
"image/apng,image/svg+xml,image/*,*/*;q=0.8";
String expected_no_avif =
"image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ String expected_no_jxl =
+ "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
#else
String expected_default =
"image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
@@ -30,8 +33,38 @@ TEST_F(InspectorEmulationAgentTest, ModifiesAcceptHeader) {
"image/apng,image/svg+xml,image/*,*/*;q=0.8";
String expected_no_avif =
"image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ String expected_no_jxl =
+ "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
#endif
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ bool jxl_enabled = base::FeatureList::IsEnabled(features::kJXL);
+ if (jxl_enabled) {
+#if BUILDFLAG(ENABLE_AV1_DECODER)
+ expected_default =
+ "image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/"
+ "*;q=0.8";
+ expected_no_webp =
+ "image/jxl,image/avif,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ expected_no_webp_and_avif =
+ "image/jxl,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ expected_no_avif =
+ "image/jxl,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ expected_no_jxl =
+ "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+#else // BUILDFLAG(ENABLE_AV1_DECODER)
+ expected_default =
+ "image/jxl,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ expected_no_webp = "image/jxl,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ expected_no_webp_and_avif =
+ "image/jxl,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ expected_no_avif =
+ "image/jxl,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+ expected_no_jxl = "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
+#endif // BUILDFLAG(ENABLE_AV1_DECODER)
+ }
+#endif // BUILDFLAG(ENABLE_JXL_DECODER)
+
HashSet<String> disabled_types;
EXPECT_EQ(InspectorEmulationAgent::OverrideAcceptImageHeader(&disabled_types),
expected_default);
@@ -44,6 +77,10 @@ TEST_F(InspectorEmulationAgentTest, ModifiesAcceptHeader) {
disabled_types.erase("image/webp");
EXPECT_EQ(InspectorEmulationAgent::OverrideAcceptImageHeader(&disabled_types),
expected_no_avif);
+ disabled_types.erase("image/avif");
+ disabled_types.insert("image/jxl");
+ EXPECT_EQ(InspectorEmulationAgent::OverrideAcceptImageHeader(&disabled_types),
+ expected_no_jxl);
}
} // namespace blink
diff --git a/third_party/blink/renderer/modules/webcodecs/fuzzer_seed_corpus/generate_image_corpus.py b/third_party/blink/renderer/modules/webcodecs/fuzzer_seed_corpus/generate_image_corpus.py
--- a/third_party/blink/renderer/modules/webcodecs/fuzzer_seed_corpus/generate_image_corpus.py
+++ b/third_party/blink/renderer/modules/webcodecs/fuzzer_seed_corpus/generate_image_corpus.py
@@ -32,6 +32,7 @@ EXTENSIONS_MAP = {
"ico": "image/x-icon",
"bmp": "image/bmp",
"jpg": "image/jpeg",
+ "jxl": "image/jxl",
"gif": "image/gif",
"cur": "image/x-icon",
"webp": "image/webp",
diff --git a/third_party/blink/renderer/modules/webcodecs/image_decoder_fuzzer.cc b/third_party/blink/renderer/modules/webcodecs/image_decoder_fuzzer.cc
--- a/third_party/blink/renderer/modules/webcodecs/image_decoder_fuzzer.cc
+++ b/third_party/blink/renderer/modules/webcodecs/image_decoder_fuzzer.cc
@@ -3,8 +3,10 @@
// found in the LICENSE file.
#include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
+#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_arraybufferallowshared_arraybufferviewallowshared_readablestream.h"
@@ -89,6 +91,9 @@ DEFINE_BINARY_PROTO_FUZZER(
auto scoped_gc =
MakeScopedGarbageCollectionRequest(test_support.GetIsolate());
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(features::kJXL);
+
//
// NOTE: GC objects that need to survive iterations of the loop below
// must be Persistent<>!
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2232,6 +2232,10 @@ source_set("blink_platform_unittests_sources") {
sources += [ "text/locale_icu_test.cc" ]
}
+ if (enable_jxl_decoder) {
+ sources += [ "image-decoders/jxl/jxl_image_decoder_test.cc" ]
+ }
+
sources += [ "testing/run_all_tests.cc" ]
configs += [
@@ -2275,6 +2279,7 @@ source_set("blink_platform_unittests_sources") {
"//third_party/blink/renderer/platform/scheduler:unit_tests",
"//third_party/blink/renderer/platform/wtf",
"//third_party/libavif:libavif",
+ "//third_party/libjxl:libjxl",
"//third_party/libyuv",
"//third_party/webrtc/api/task_queue:task_queue_test",
"//third_party/webrtc_overrides:metronome_like_task_queue_test",
diff --git a/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.cc b/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.cc
--- a/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.cc
+++ b/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.cc
@@ -6,7 +6,6 @@
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_macros.h"
-#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "media/media_buildflags.h"
#include "third_party/blink/public/common/buildflags.h"
@@ -37,6 +36,10 @@ BitmapImageMetrics::StringToDecodedImageType(const String& type) {
#if BUILDFLAG(ENABLE_AV1_DECODER)
if (type == "avif")
return BitmapImageMetrics::DecodedImageType::kAVIF;
+#endif
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ if (type == "jxl")
+ return BitmapImageMetrics::DecodedImageType::kJXL;
#endif
return BitmapImageMetrics::DecodedImageType::kUnknown;
}
@@ -55,6 +58,10 @@ void BitmapImageMetrics::CountDecodedImageType(const String& type,
} else if (type == "avif") {
use_counter->CountUse(WebFeature::kAVIFImage);
#endif
+// #if BUILDFLAG(ENABLE_JXL_DECODER)
+// } else if (type == "jxl") {
+// use_counter->CountUse(WebFeature::kJXLImage);
+// #endif
}
}
}
diff --git a/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h b/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h
--- a/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h
+++ b/third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h
@@ -29,8 +29,8 @@ class PLATFORM_EXPORT BitmapImageMetrics {
kICO = 5,
kBMP = 6,
kAVIF = 7,
- kREMOVED_JXL = 8,
- kMaxValue = kREMOVED_JXL,
+ kJXL = 8,
+ kMaxValue = kJXL,
};
// Categories for the JPEG color space histogram. Synced with 'JpegColorSpace'
diff --git a/third_party/blink/renderer/platform/image-decoders/BUILD.gn b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
--- a/third_party/blink/renderer/platform/image-decoders/BUILD.gn
+++ b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
@@ -70,6 +70,15 @@ component("image_decoders") {
"//third_party/libyuv",
]
+ if (enable_jxl_decoder) {
+ sources += [
+ "jxl/jxl_image_decoder.cc",
+ "jxl/jxl_image_decoder.h",
+ ]
+
+ deps += [ "//third_party/libjxl:libjxl" ]
+ }
+
if (enable_av1_decoder) {
sources += [
"avif/avif_image_decoder.cc",
diff --git a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
--- a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
@@ -23,6 +23,7 @@
#include <memory>
#include "base/logging.h"
+#include "base/feature_list.h"
#include "base/numerics/safe_conversions.h"
#include "base/sys_byteorder.h"
#include "base/trace_event/trace_event.h"
@@ -30,6 +31,7 @@
#include "media/media_buildflags.h"
#include "skia/ext/cicp.h"
#include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h"
#include "third_party/blink/renderer/platform/image-decoders/exif_reader.h"
@@ -47,6 +49,9 @@
#include "third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h"
#endif
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+#include "third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.h"
+#endif
namespace blink {
namespace {
@@ -74,6 +79,11 @@ cc::ImageType FileExtensionToImageType(String image_extension) {
if (image_extension == "avif") {
return cc::ImageType::kAVIF;
}
+#endif
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ if (image_extension == "jxl") {
+ return cc::ImageType::kJXL;
+ }
#endif
return cc::ImageType::kInvalid;
}
@@ -191,6 +201,12 @@ String SniffMimeTypeInternal(scoped_refptr<SegmentReader> reader) {
return "image/avif";
}
#endif
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ if (base::FeatureList::IsEnabled(blink::features::kJXL) &&
+ JXLImageDecoder::MatchesJXLSignature(fast_reader)) {
+ return "image/jxl";
+ }
+#endif
return String();
}
@@ -296,6 +312,13 @@ std::unique_ptr<ImageDecoder> ImageDecoder::CreateByMimeType(
decoder = std::make_unique<AVIFImageDecoder>(
alpha_option, high_bit_depth_decoding_option, color_behavior,
max_decoded_bytes, animation_option);
+#endif
+#if BUILDFLAG(ENABLE_JXL_DECODER)
+ } else if (base::FeatureList::IsEnabled(blink::features::kJXL) &&
+ mime_type == "image/jxl") {
+ decoder = std::make_unique<JXLImageDecoder>(
+ alpha_option, high_bit_depth_decoding_option, color_behavior,
+ max_decoded_bytes);
#endif
}
diff --git a/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.cc
new file mode 100644
--- /dev/null
+++ b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.cc
@@ -0,0 +1,683 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h"
+#include "third_party/skia/include/core/SkColorSpace.h"
+
+namespace blink {
+
+namespace {
+// Returns transfer function which approximates HLG with linear range 0..1,
+// while skcms_TransferFunction_makeHLGish uses linear range 0..12.
+void MakeTransferFunctionHLG01(skcms_TransferFunction* tf) {
+ skcms_TransferFunction_makeScaledHLGish(
+ tf, 1 / 12.0f, 2.0f, 2.0f, 1 / 0.17883277f, 0.28466892f, 0.55991073f);
+}
+
+// The input profile must outlive the output one as they will share their
+// buffers.
+skcms_ICCProfile ReplaceTransferFunction(skcms_ICCProfile profile,
+ const skcms_TransferFunction& tf) {
+ // Override the transfer function with a known parametric curve.
+ profile.has_trc = true;
+ for (int c = 0; c < 3; c++) {
+ profile.trc[c].table_entries = 0;
+ profile.trc[c].parametric = tf;
+ }
+ return profile;
+}
+
+// Computes whether the transfer function from the ColorProfile, that was
+// created from a parsed ICC profile, approximately matches the given parametric
+// transfer function.
+bool ApproximatelyMatchesTF(const ColorProfile& profile,
+ const skcms_TransferFunction& tf) {
+ skcms_ICCProfile parsed_copy =
+ ReplaceTransferFunction(*profile.GetProfile(), tf);
+ return skcms_ApproximatelyEqualProfiles(profile.GetProfile(), &parsed_copy);
+}
+
+std::unique_ptr<ColorProfile> NewColorProfileWithSameBuffer(
+ const ColorProfile& buffer_donor,
+ skcms_ICCProfile new_profile) {
+ // The input ColorProfile owns the buffer memory, make a new copy for
+ // the newly created one and pass the ownership of the new copy to the new
+ // color profile.
+ std::unique_ptr<uint8_t[]> owned_buffer(
+ new uint8_t[buffer_donor.GetProfile()->size]);
+ memcpy(owned_buffer.get(), buffer_donor.GetProfile()->buffer,
+ buffer_donor.GetProfile()->size);
+ new_profile.buffer = owned_buffer.get();
+ return std::make_unique<ColorProfile>(new_profile, std::move(owned_buffer));
+}
+} // namespace
+
+JXLImageDecoder::JXLImageDecoder(
+ AlphaOption alpha_option,
+ HighBitDepthDecodingOption high_bit_depth_decoding_option,
+ const ColorBehavior& color_behavior,
+ wtf_size_t max_decoded_bytes)
+ : ImageDecoder(alpha_option,
+ high_bit_depth_decoding_option,
+ color_behavior,
+ max_decoded_bytes) {
+ info_.have_animation = false;
+}
+
+// Use the provisional Mime type "image/jxl" for JPEG XL images. See
+// https://www.iana.org/assignments/provisional-standard-media-types/provisional-standard-media-types.xhtml.
+const AtomicString& JXLImageDecoder::MimeType() const {
+ DEFINE_STATIC_LOCAL(const AtomicString, jxl_mime_type, ("image/jxl"));
+ return jxl_mime_type;
+}
+
+bool JXLImageDecoder::ReadBytes(size_t remaining,
+ wtf_size_t* offset,
+ WTF::Vector<uint8_t>* segment,
+ FastSharedBufferReader* reader,
+ const uint8_t** jxl_data,
+ size_t* jxl_size) {
+ *offset -= remaining;
+ if (*offset + remaining >= reader->size()) {
+ segment->clear();
+ if (IsAllDataReceived()) {
+ DVLOG(1) << "need more input but all data received";
+ SetFailed();
+ return false;
+ }
+ // Return because we need more input from the reader, to continue
+ // decoding in the next call.
+ return false;
+ }
+ const char* buffer = nullptr;
+ size_t read = reader->GetSomeData(buffer, *offset);
+
+ if (read > remaining) {
+ // Sufficient data present in the segment from the
+ // FastSharedBufferReader, no need to copy to segment_.
+ *jxl_data = reinterpret_cast<const uint8_t*>(buffer);
+ *jxl_size = read;
+ *offset += read;
+ segment->clear();
+ } else {
+ if (segment->size() == remaining) {
+ // Keep reading from the end of the segment_ we already are
+ // appending to. The above read is ignored, and start reading after the
+ // end of the data we already have.
+ *offset += remaining;
+ read = 0;
+ } else {
+ // segment_->size() could be greater than or smaller than remaining.
+ // Typically, it'll be smaller than. If it is greater than, then we could
+ // do something similar as in the segment->size() == remaining case but
+ // remove the non-remaining bytes from the beginning of the segment_
+ // vector. This would avoid re-reading, however the case where
+ // segment->size() > remaining is rare since normally if the JXL decoder
+ // returns a positive value for remaining, it will be consistent, making
+ // the sizes match exactly, so this more complex case is not implemented.
+ // Clear the segment, the bytes from the GetSomeData above will be
+ // appended and then we continue reading from the position after the
+ // above GetSomeData read.
+ segment->clear();
+ }
+
+ for (;;) {
+ if (read) {
+ *offset += read;
+ segment->Append(buffer, base::checked_cast<wtf_size_t>(read));
+ }
+ if (segment->size() > remaining) {
+ *jxl_data = segment->data();
+ *jxl_size = segment->size();
+ // Have enough data, break and continue JXL decoding, rather than
+ // copy more input than needed into segment_.
+ break;
+ }
+ read = reader->GetSomeData(buffer, *offset);
+ if (read == 0) {
+ // We tested above that *offset + remaining >= reader.size() so
+ // should be able to read all data.
+ DVLOG(1) << "couldn't read all available data";
+ SetFailed();
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void JXLImageDecoder::DecodeImpl(wtf_size_t index, bool only_size) {
+ if (Failed())
+ return;
+
+ if (IsDecodedSizeAvailable() && only_size) {
+ // Also SetEmbeddedProfile is done already if the size was set.
+ return;
+ }
+
+ DCHECK_LE(num_decoded_frames_, frame_buffer_cache_.size());
+ if (num_decoded_frames_ > index &&
+ frame_buffer_cache_[index].GetStatus() == ImageFrame::kFrameComplete) {
+ // Frame already complete
+ return;
+ }
+ if ((index < num_decoded_frames_) && dec_ &&
+ frame_buffer_cache_[index].GetStatus() != ImageFrame::kFramePartial) {
+ // An animation frame that already has been decoded, but does not have
+ // status ImageFrame::kFrameComplete, was requested.
+ // This can mean two things:
+ // (1) an earlier animation frame was purged but is to be re-decoded now.
+ // Rewind the decoder and skip to the requested frame.
+ // (2) During progressive decoding the frame has the status
+ // ImageFrame::kFramePartial.
+ JxlDecoderRewind(dec_.get());
+ offset_ = 0;
+ // No longer subscribe to JXL_DEC_BASIC_INFO or JXL_DEC_COLOR_ENCODING.
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSubscribeEvents(
+ dec_.get(), JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME_PROGRESSION)) {
+ SetFailed();
+ return;
+ }
+ JxlDecoderSkipFrames(dec_.get(), index);
+ num_decoded_frames_ = index;
+ }
+
+ if (!dec_) {
+ dec_ = JxlDecoderMake(nullptr);
+ // Subscribe to color encoding event even when only getting size, because
+ // SetSize must be called after SetEmbeddedColorProfile
+ const int events = JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING |
+ JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME_PROGRESSION;
+
+ if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec_.get(), events)) {
+ SetFailed();
+ return;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetProgressiveDetail(dec_.get(), JxlProgressiveDetail::kDC)) {
+ SetFailed();
+ return;
+ }
+ } else {
+ offset_ -= JxlDecoderReleaseInput(dec_.get());
+ }
+
+ FastSharedBufferReader reader(data_.get());
+
+ const JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
+
+ const bool size_available = IsDecodedSizeAvailable();
+
+ if (have_color_info_) {
+ xform_ = ColorTransform();
+ }
+
+ // The JXL API guarantees that we eventually get JXL_DEC_ERROR,
+ // JXL_DEC_SUCCESS or JXL_DEC_NEED_MORE_INPUT, and we exit the loop below in
+ // each case.
+ for (;;) {
+ if (only_size && have_color_info_)
+ return;
+ JxlDecoderStatus status = JxlDecoderProcessInput(dec_.get());
+ switch (status) {
+ case JXL_DEC_ERROR: {
+ DVLOG(1) << "Decoder error " << status;
+ SetFailed();
+ return;
+ }
+ case JXL_DEC_NEED_MORE_INPUT: {
+ // The decoder returns how many bytes it has not yet processed, and
+ // must be included in the next JxlDecoderSetInput call.
+ const size_t remaining = JxlDecoderReleaseInput(dec_.get());
+ const uint8_t* jxl_data = nullptr;
+ size_t jxl_size = 0;
+ if (!ReadBytes(remaining, &offset_, &segment_, &reader, &jxl_data,
+ &jxl_size)) {
+ if (IsAllDataReceived()) {
+ // Happens only if a partial image file was transferred, otherwise
+ // status will be JXL_DEC_FULL_IMAGE or JXL_DEC_SUCCESS. In
+ // this case we flush one more time in order to get the progressive
+ // image plus everything known so far. The progressive image was not
+ // flushed when status was JXL_DEC_FRAME_PROGRESSION because all
+ // data seemed to have been received (not knowing then that it was
+ // only a partial file).
+ if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec_.get())) {
+ DVLOG(1) << "JxlDecoderSetImageOutCallback failed";
+ SetFailed();
+ return;
+ }
+ ImageFrame& frame = frame_buffer_cache_[num_decoded_frames_ - 1];
+ frame.SetPixelsChanged(true);
+ frame.SetStatus(ImageFrame::kFramePartial);
+ }
+ return;
+ }
+
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetInput(dec_.get(), jxl_data, jxl_size)) {
+ DVLOG(1) << "JxlDecoderSetInput failed";
+ SetFailed();
+ return;
+ }
+ break;
+ }
+ case JXL_DEC_BASIC_INFO: {
+ if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec_.get(), &info_)) {
+ DVLOG(1) << "JxlDecoderGetBasicInfo failed";
+ SetFailed();
+ return;
+ }
+ if (!size_available && !SetSize(info_.xsize, info_.ysize)) {
+ return;
+ }
+ break;
+ }
+ case JXL_DEC_COLOR_ENCODING: {
+ if (IgnoresColorSpace()) {
+ have_color_info_ = true;
+ continue;
+ }
+
+ // If the decoder was used before with only_size == true, the color
+ // encoding is already decoded as well, and SetEmbeddedColorProfile
+ // should not be called a second time anymore.
+ if (size_available) {
+ continue;
+ }
+
+ // Detect whether the JXL image is intended to be an HDR image: when it
+ // uses more than 8 bits per pixel, or when it has explicitly marked
+ // PQ or HLG color profile.
+ if (info_.bits_per_sample > 8) {
+ is_hdr_ = true;
+ }
+ JxlColorEncoding color_encoding;
+ if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsEncodedProfile(
+ dec_.get(), &format,
+ JXL_COLOR_PROFILE_TARGET_ORIGINAL,
+ &color_encoding)) {
+ if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_PQ ||
+ color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_HLG) {
+ is_hdr_ = true;
+ }
+ }
+
+ std::unique_ptr<ColorProfile> profile;
+
+ if (is_hdr_ &&
+ high_bit_depth_decoding_option_ == kHighBitDepthToHalfFloat) {
+ decode_to_half_float_ = true;
+ }
+
+ bool have_data_profile = false;
+ if (JXL_DEC_SUCCESS ==
+ JxlDecoderGetColorAsEncodedProfile(dec_.get(), &format,
+ JXL_COLOR_PROFILE_TARGET_DATA,
+ &color_encoding)) {
+ bool known_transfer_function = true;
+ bool known_gamut = true;
+ gfx::ColorSpace::PrimaryID gamut;
+ gfx::ColorSpace::TransferID transfer;
+ if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_PQ) {
+ transfer = gfx::ColorSpace::TransferID::PQ;
+ } else if (color_encoding.transfer_function ==
+ JXL_TRANSFER_FUNCTION_HLG) {
+ transfer = gfx::ColorSpace::TransferID::HLG;
+ } else if (color_encoding.transfer_function ==
+ JXL_TRANSFER_FUNCTION_LINEAR) {
+ transfer = gfx::ColorSpace::TransferID::LINEAR;
+ } else if (color_encoding.transfer_function ==
+ JXL_TRANSFER_FUNCTION_SRGB) {
+ transfer = gfx::ColorSpace::TransferID::SRGB;
+ } else {
+ known_transfer_function = false;
+ }
+
+ if (color_encoding.white_point == JXL_WHITE_POINT_D65 &&
+ color_encoding.primaries == JXL_PRIMARIES_2100) {
+ gamut = gfx::ColorSpace::PrimaryID::BT2020;
+ } else if (color_encoding.white_point == JXL_WHITE_POINT_D65 &&
+ color_encoding.primaries == JXL_PRIMARIES_SRGB) {
+ gamut = gfx::ColorSpace::PrimaryID::BT709;
+ } else if (color_encoding.white_point == JXL_WHITE_POINT_D65 &&
+ color_encoding.primaries == JXL_PRIMARIES_P3) {
+ gamut = gfx::ColorSpace::PrimaryID::P3;
+ } else {
+ known_gamut = false;
+ }
+
+ have_data_profile = known_transfer_function && known_gamut;
+
+ if (have_data_profile) {
+ skcms_ICCProfile dataProfile;
+ gfx::ColorSpace(gamut, transfer)
+ .ToSkColorSpace()
+ ->toProfile(&dataProfile);
+ profile = std::make_unique<ColorProfile>(dataProfile);
+ }
+ }
+
+ // Did not handle exact enum values, get as ICC profile instead.
+ if (!have_data_profile) {
+ size_t icc_size;
+ bool got_size =
+ JXL_DEC_SUCCESS == JxlDecoderGetICCProfileSize(
+ dec_.get(), &format,
+ JXL_COLOR_PROFILE_TARGET_DATA, &icc_size);
+ std::vector<uint8_t> icc_profile(icc_size);
+ if (got_size &&
+ JXL_DEC_SUCCESS == JxlDecoderGetColorAsICCProfile(
+ dec_.get(), &format,
+ JXL_COLOR_PROFILE_TARGET_DATA,
+ icc_profile.data(), icc_profile.size())) {
+ profile =
+ ColorProfile::Create(icc_profile.data(), icc_profile.size());
+ have_data_profile = true;
+
+ // Detect whether the ICC profile approximately equals PQ or HLG,
+ // and set the profile to one that indicates this transfer function
+ // more clearly than a raw ICC profile does, so Chrome considers
+ // the profile as HDR.
+ skcms_TransferFunction tf_pq;
+ skcms_TransferFunction tf_hlg01;
+ skcms_TransferFunction tf_hlg12;
+ skcms_TransferFunction_makePQ(&tf_pq);
+ MakeTransferFunctionHLG01(&tf_hlg01);
+ skcms_TransferFunction_makeHLG(&tf_hlg12);
+
+ if (ApproximatelyMatchesTF(*profile, tf_pq)) {
+ is_hdr_ = true;
+ auto hdr10 = gfx::ColorSpace::CreateHDR10().ToSkColorSpace();
+ skcms_TransferFunction pq;
+ hdr10->transferFn(&pq);
+ profile = NewColorProfileWithSameBuffer(
+ *profile,
+ ReplaceTransferFunction(*profile->GetProfile(), pq));
+ } else {
+ for (skcms_TransferFunction tf : {tf_hlg01, tf_hlg12}) {
+ if (ApproximatelyMatchesTF(*profile, tf)) {
+ is_hdr_ = true;
+ auto hlg_colorspace =
+ gfx::ColorSpace::CreateHLG().ToSkColorSpace();
+ skcms_TransferFunction hlg;
+ hlg_colorspace->transferFn(&hlg);
+ profile = NewColorProfileWithSameBuffer(
+ *profile,
+ ReplaceTransferFunction(*profile->GetProfile(), hlg));
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (is_hdr_ &&
+ high_bit_depth_decoding_option_ == kHighBitDepthToHalfFloat) {
+ decode_to_half_float_ = true;
+ }
+
+ if (have_data_profile) {
+ if (profile->GetProfile()->data_color_space == skcms_Signature_RGB) {
+ SetEmbeddedColorProfile(std::move(profile));
+ }
+ }
+ have_color_info_ = true;
+ break;
+ }
+ case JXL_DEC_NEED_IMAGE_OUT_BUFFER: {
+ const wtf_size_t frame_index = num_decoded_frames_++;
+ ImageFrame& frame = frame_buffer_cache_[frame_index];
+ // This is guaranteed to occur after JXL_DEC_BASIC_INFO so the size
+ // is correct.
+ if (!InitFrameBuffer(frame_index)) {
+ DVLOG(1) << "InitFrameBuffer failed";
+ SetFailed();
+ return;
+ }
+ frame.SetHasAlpha(info_.alpha_bits != 0);
+
+ size_t buffer_size;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderImageOutBufferSize(dec_.get(), &format, &buffer_size)) {
+ DVLOG(1) << "JxlDecoderImageOutBufferSize failed";
+ SetFailed();
+ return;
+ }
+ if (buffer_size != info_.xsize * info_.ysize * 16) {
+ DVLOG(1) << "Unexpected buffer size";
+ SetFailed();
+ return;
+ }
+
+ // TODO(http://crbug.com/1210465): Add Munsell chart color accuracy
+ // tests for JXL
+ xform_ = ColorTransform();
+ auto callback = [](void* opaque, size_t x, size_t y, size_t num_pixels,
+ const void* pixels) {
+ JXLImageDecoder* self = reinterpret_cast<JXLImageDecoder*>(opaque);
+ ImageFrame& frame =
+ self->frame_buffer_cache_[self->num_decoded_frames_ - 1];
+ void* row_dst = self->decode_to_half_float_
+ ? reinterpret_cast<void*>(frame.GetAddrF16(
+ static_cast<int>(x), static_cast<int>(y)))
+ : reinterpret_cast<void*>(frame.GetAddr(
+ static_cast<int>(x), static_cast<int>(y)));
+
+ bool dst_premultiply = frame.PremultiplyAlpha();
+
+ const skcms_PixelFormat kSrcFormat = skcms_PixelFormat_RGBA_ffff;
+ const skcms_PixelFormat kDstFormat = self->decode_to_half_float_
+ ? skcms_PixelFormat_RGBA_hhhh
+ : XformColorFormat();
+
+ if (self->xform_ || (kDstFormat != kSrcFormat) ||
+ (dst_premultiply && frame.HasAlpha())) {
+ skcms_AlphaFormat src_alpha = skcms_AlphaFormat_Unpremul;
+ skcms_AlphaFormat dst_alpha =
+ (dst_premultiply && self->info_.alpha_bits)
+ ? skcms_AlphaFormat_PremulAsEncoded
+ : skcms_AlphaFormat_Unpremul;
+ const auto* src_profile =
+ self->xform_ ? self->xform_->SrcProfile() : nullptr;
+ const auto* dst_profile =
+ self->xform_ ? self->xform_->DstProfile() : nullptr;
+ bool color_conversion_successful = skcms_Transform(
+ pixels, kSrcFormat, src_alpha, src_profile, row_dst, kDstFormat,
+ dst_alpha, dst_profile, num_pixels);
+ DCHECK(color_conversion_successful);
+ }
+ };
+ if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutCallback(
+ dec_.get(), &format, callback, this)) {
+ DVLOG(1) << "JxlDecoderSetImageOutCallback failed";
+ SetFailed();
+ return;
+ }
+ break;
+ }
+ case JXL_DEC_FRAME_PROGRESSION: {
+ if (IsAllDataReceived()) {
+ break;
+ } else {
+ if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec_.get())) {
+ DVLOG(1) << "JxlDecoderSetImageOutCallback failed";
+ SetFailed();
+ return;
+ }
+ ImageFrame& frame = frame_buffer_cache_[num_decoded_frames_ - 1];
+ frame.SetPixelsChanged(true);
+ frame.SetStatus(ImageFrame::kFramePartial);
+ break;
+ }
+ }
+ case JXL_DEC_FULL_IMAGE: {
+ ImageFrame& frame = frame_buffer_cache_[num_decoded_frames_ - 1];
+ frame.SetPixelsChanged(true);
+ frame.SetStatus(ImageFrame::kFrameComplete);
+ // All required frames were decoded.
+ if (num_decoded_frames_ > index) {
+ return;
+ }
+ break;
+ }
+ case JXL_DEC_SUCCESS: {
+ // Finished decoding entire image, with all frames in case of animation.
+ // Don't reset dec_, since we may want to rewind it if an earlier
+ // animation frame has to be decoded again.
+ segment_.clear();
+ return;
+ }
+ default: {
+ DVLOG(1) << "Unexpected decoder status " << status;
+ SetFailed();
+ return;
+ }
+ }
+ }
+}
+
+bool JXLImageDecoder::MatchesJXLSignature(
+ const FastSharedBufferReader& fast_reader) {
+ char buffer[12];
+ if (fast_reader.size() < sizeof(buffer))
+ return false;
+ const char* contents = reinterpret_cast<const char*>(
+ fast_reader.GetConsecutiveData(0, sizeof(buffer), buffer));
+ // Direct codestream
+ if (!memcmp(contents, "\xFF\x0A", 2))
+ return true;
+ // Box format container
+ if (!memcmp(contents, "\0\0\0\x0CJXL \x0D\x0A\x87\x0A", 12))
+ return true;
+ return false;
+}
+
+void JXLImageDecoder::InitializeNewFrame(wtf_size_t index) {
+ auto& buffer = frame_buffer_cache_[index];
+ if (decode_to_half_float_)
+ buffer.SetPixelFormat(ImageFrame::PixelFormat::kRGBA_F16);
+ buffer.SetHasAlpha(info_.alpha_bits != 0);
+ buffer.SetPremultiplyAlpha(premultiply_alpha_);
+}
+
+bool JXLImageDecoder::FrameIsReceivedAtIndex(wtf_size_t index) const {
+ return IsAllDataReceived() ||
+ (index < num_decoded_frames_ &&
+ frame_buffer_cache_[index].GetStatus() == ImageFrame::kFrameComplete);
+}
+
+int JXLImageDecoder::RepetitionCount() const {
+ if (!info_.have_animation)
+ return kAnimationNone;
+
+ if (info_.animation.num_loops == 0)
+ return kAnimationLoopInfinite;
+
+ if (info_.animation.num_loops == 1)
+ return kAnimationLoopOnce;
+
+ return info_.animation.num_loops;
+}
+
+base::TimeDelta JXLImageDecoder::FrameDurationAtIndex(wtf_size_t index) const {
+ if (index < frame_durations_.size())
+ return base::Seconds(frame_durations_[index]);
+
+ return base::TimeDelta();
+}
+
+wtf_size_t JXLImageDecoder::DecodeFrameCount() {
+ DecodeSize();
+ if (!info_.have_animation) {
+ frame_durations_.resize(1);
+ frame_durations_[0] = 0;
+ return 1;
+ }
+
+ FastSharedBufferReader reader(data_.get());
+ if (has_full_frame_count_ || size_at_last_frame_count_ == reader.size()) {
+ return frame_buffer_cache_.size();
+ }
+ size_at_last_frame_count_ = reader.size();
+
+ // Decode the metadata of every frame that is available.
+ if (frame_count_dec_ == nullptr) {
+ frame_durations_.clear();
+ frame_count_dec_ = JxlDecoderMake(nullptr);
+ frame_count_offset_ = 0;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSubscribeEvents(frame_count_dec_.get(), JXL_DEC_FRAME)) {
+ SetFailed();
+ return frame_buffer_cache_.size();
+ }
+ }
+
+ for (;;) {
+ JxlDecoderStatus status = JxlDecoderProcessInput(frame_count_dec_.get());
+ switch (status) {
+ case JXL_DEC_ERROR: {
+ DVLOG(1) << "Decoder error " << status;
+ SetFailed();
+ return frame_buffer_cache_.size();
+ }
+ case JXL_DEC_NEED_MORE_INPUT: {
+ // The decoder returns how many bytes it has not yet processed, and
+ // must be included in the next JxlDecoderSetInput call.
+ const size_t remaining = JxlDecoderReleaseInput(frame_count_dec_.get());
+ const uint8_t* jxl_data = nullptr;
+ size_t jxl_size = 0;
+ if (!ReadBytes(remaining, &frame_count_offset_, &frame_count_segment_,
+ &reader, &jxl_data, &jxl_size)) {
+ if (Failed()) {
+ return frame_buffer_cache_.size();
+ }
+ return frame_durations_.size();
+ }
+
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetInput(frame_count_dec_.get(), jxl_data, jxl_size)) {
+ DVLOG(1) << "JxlDecoderSetInput failed";
+ SetFailed();
+ return frame_buffer_cache_.size();
+ }
+ break;
+ }
+ case JXL_DEC_FRAME: {
+ JxlFrameHeader frame_header;
+ if (JxlDecoderGetFrameHeader(frame_count_dec_.get(), &frame_header) !=
+ JXL_DEC_SUCCESS) {
+ DVLOG(1) << "GetFrameHeader failed";
+ SetFailed();
+ return frame_buffer_cache_.size();
+ }
+ if (frame_header.is_last) {
+ has_full_frame_count_ = true;
+ }
+ frame_durations_.push_back(1.0f * frame_header.duration *
+ info_.animation.tps_denominator /
+ info_.animation.tps_numerator);
+ break;
+ }
+ case JXL_DEC_SUCCESS: {
+ // If the file is fully processed, we won't need to run the decoder
+ // anymore: we can free the memory.
+ frame_count_dec_ = nullptr;
+ DCHECK(has_full_frame_count_);
+ frame_count_segment_.clear();
+ return frame_durations_.size();
+ }
+ default: {
+ DVLOG(1) << "Unexpected decoder status " << status;
+ SetFailed();
+ return frame_buffer_cache_.size();
+ }
+ }
+ }
+}
+
+} // namespace blink
diff --git a/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.h b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.h
new file mode 100644
--- /dev/null
+++ b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2021, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_JXL_JXL_IMAGE_DECODER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_JXL_JXL_IMAGE_DECODER_H_
+
+#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h"
+#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
+
+#include "third_party/libjxl/src/lib/include/jxl/decode.h"
+#include "third_party/libjxl/src/lib/include/jxl/decode_cxx.h"
+
+namespace blink {
+
+// This class decodes the JXL image format.
+class PLATFORM_EXPORT JXLImageDecoder final : public ImageDecoder {
+ public:
+ JXLImageDecoder(AlphaOption,
+ HighBitDepthDecodingOption high_bit_depth_decoding_option,
+ const ColorBehavior&,
+ wtf_size_t max_decoded_bytes);
+
+ // ImageDecoder:
+ String FilenameExtension() const override { return "jxl"; }
+ const AtomicString& MimeType() const override;
+ bool ImageIsHighBitDepth() override { return is_hdr_; }
+
+ // Returns true if the data in fast_reader begins with
+ static bool MatchesJXLSignature(const FastSharedBufferReader& fast_reader);
+
+ private:
+ // ImageDecoder:
+ void DecodeSize() override { DecodeImpl(0, true); }
+ wtf_size_t DecodeFrameCount() override;
+ void Decode(wtf_size_t frame) override { DecodeImpl(frame); }
+ void InitializeNewFrame(wtf_size_t) override;
+
+ // Decodes up to a given frame. If |only_size| is true, stops decoding after
+ // calculating the image size. If decoding fails but there is no more
+ // data coming, sets the "decode failure" flag.
+ void DecodeImpl(wtf_size_t frame, bool only_size = false);
+
+ bool FrameIsReceivedAtIndex(wtf_size_t) const override;
+ base::TimeDelta FrameDurationAtIndex(wtf_size_t) const override;
+ int RepetitionCount() const override;
+ bool CanReusePreviousFrameBuffer(wtf_size_t) const override { return false; }
+
+ // Reads bytes from the segment reader, after releasing input from the JXL
+ // decoder, which required `remaining` previous bytes to still be available.
+ // Starts reading from *offset - remaining, and ensures more than remaining
+ // bytes are read, if possible. Returns false if not enough bytes are
+ // available or if Failed() was set.
+ bool ReadBytes(size_t remaining,
+ wtf_size_t* offset,
+ WTF::Vector<uint8_t>* segment,
+ FastSharedBufferReader* reader,
+ const uint8_t** jxl_data,
+ size_t* jxl_size);
+
+ JxlDecoderPtr dec_ = nullptr;
+ wtf_size_t offset_ = 0;
+
+ JxlDecoderPtr frame_count_dec_ = nullptr;
+ wtf_size_t frame_count_offset_ = 0;
+
+ // The image is considered to be HDR, such as using PQ or HLG transfer
+ // function in the color space.
+ bool is_hdr_ = false;
+ bool decode_to_half_float_ = false;
+
+ JxlBasicInfo info_;
+ bool have_color_info_ = false;
+
+ // Preserved for JXL pixel callback. Not owned.
+ ColorProfileTransform* xform_;
+
+ // Fields for animation support.
+
+ // The amount of frames the JXL decoder has decoded. This can be reset to
+ // an earlier amount if frame buffers were cleared and decoding was
+ // restarted from an earlier frame. This is used to keep track of the index
+ // in the frame_buffer_cache_.
+ wtf_size_t num_decoded_frames_ = 0;
+ bool has_full_frame_count_ = false;
+ size_t size_at_last_frame_count_ = 0;
+ WTF::Vector<float> frame_durations_;
+ // Multiple concatenated segments from the FastSharedBufferReader, these are
+ // only used when a single segment did not contain enough data for the JXL
+ // parser.
+ WTF::Vector<uint8_t> segment_;
+ WTF::Vector<uint8_t> frame_count_segment_;
+};
+
+} // namespace blink
+
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_JXL_JXL_IMAGE_DECODER_H_
diff --git a/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc
new file mode 100644
--- /dev/null
+++ b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc
@@ -0,0 +1,626 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder.h"
+
+#include <memory>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h"
+#include "third_party/skia/include/core/SkColorSpace.h"
+#include "ui/gfx/geometry/point.h"
+
+namespace blink {
+
+namespace {
+
+std::unique_ptr<ImageDecoder> CreateJXLDecoderWithArguments(
+ const char* jxl_file,
+ ImageDecoder::AlphaOption alpha_option,
+ ImageDecoder::HighBitDepthDecodingOption high_bit_depth_decoding_option,
+ ColorBehavior color_behavior) {
+ auto decoder = std::make_unique<JXLImageDecoder>(
+ alpha_option, high_bit_depth_decoding_option, color_behavior,
+ ImageDecoder::kNoDecodedImageByteLimit);
+ scoped_refptr<SharedBuffer> data = ReadFile(jxl_file);
+ EXPECT_FALSE(data->empty());
+ decoder->SetData(data.get(), true);
+ return decoder;
+}
+
+std::unique_ptr<ImageDecoder> CreateJXLDecoder() {
+ return std::make_unique<JXLImageDecoder>(
+ ImageDecoder::kAlphaNotPremultiplied, ImageDecoder::kDefaultBitDepth,
+ ColorBehavior::Tag(), ImageDecoder::kNoDecodedImageByteLimit);
+}
+
+std::unique_ptr<ImageDecoder> CreateJXLDecoderWithData(const char* jxl_file) {
+ auto decoder = CreateJXLDecoder();
+ scoped_refptr<SharedBuffer> data = ReadFile(jxl_file);
+ EXPECT_FALSE(data->empty());
+ decoder->SetData(data.get(), true);
+ return decoder;
+}
+
+// expected_color must match the expected top left pixel
+void TestColorProfile(const char* jxl_file,
+ ColorBehavior color_behavior,
+ SkColor expected_color) {
+ auto decoder = CreateJXLDecoderWithArguments(
+ jxl_file, ImageDecoder::AlphaOption::kAlphaNotPremultiplied,
+ ImageDecoder::kDefaultBitDepth, color_behavior);
+ EXPECT_EQ(1u, decoder->FrameCount());
+ ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0);
+ ASSERT_TRUE(frame);
+ EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus());
+ EXPECT_FALSE(decoder->Failed());
+ const SkBitmap& bitmap = frame->Bitmap();
+ SkColor frame_color = bitmap.getColor(0, 0);
+ for (int i = 0; i < 4; ++i) {
+ int frame_comp = (frame_color >> (8 * i)) & 255;
+ int expected_comp = (expected_color >> (8 * i)) & 255;
+ EXPECT_GE(1, abs(frame_comp - expected_comp));
+ }
+}
+
+// Convert from float16 bits in a uint16_t, to 32-bit float, for testing
+static float FromFloat16(uint16_t a) {
+ // 5 bits exponent
+ int exp = (a >> 10) & 31;
+ // 10 bits fractional part
+ float frac = a & 1023;
+ // 1 bit sign
+ int sign = (a & 32768) ? 1 : 0;
+ bool subnormal = exp == 0;
+ // Infinity and NaN are not supported here.
+ exp -= 15;
+ if (subnormal)
+ exp++;
+ frac /= 1024.0;
+ if (!subnormal)
+ frac++;
+ frac *= std::pow(2, exp);
+ if (sign)
+ frac = -frac;
+ return frac;
+}
+
+// expected_color must match the expected top left pixel
+void TestHDR(const char* jxl_file,
+ ColorBehavior color_behavior,
+ bool expect_f16,
+ float expected_r,
+ float expected_g,
+ float expected_b,
+ float expected_a) {
+ auto decoder = CreateJXLDecoderWithArguments(
+ jxl_file, ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ImageDecoder::kHighBitDepthToHalfFloat, color_behavior);
+ EXPECT_TRUE(decoder->IsSizeAvailable());
+ EXPECT_EQ(1u, decoder->FrameCount());
+ ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0);
+ ASSERT_TRUE(frame);
+ EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus());
+ EXPECT_FALSE(decoder->Failed());
+ float r, g, b, a;
+ if (expect_f16) {
+ EXPECT_EQ(ImageFrame::kRGBA_F16, frame->GetPixelFormat());
+ } else {
+ EXPECT_EQ(ImageFrame::kN32, frame->GetPixelFormat());
+ }
+ if (ImageFrame::kRGBA_F16 == frame->GetPixelFormat()) {
+ uint64_t first_pixel = *frame->GetAddrF16(0, 0);
+ r = FromFloat16(first_pixel >> 0);
+ g = FromFloat16(first_pixel >> 16);
+ b = FromFloat16(first_pixel >> 32);
+ a = FromFloat16(first_pixel >> 48);
+ } else {
+ uint32_t first_pixel = *frame->GetAddr(0, 0);
+ a = ((first_pixel >> SK_A32_SHIFT) & 255) / 255.0;
+ r = ((first_pixel >> SK_R32_SHIFT) & 255) / 255.0;
+ g = ((first_pixel >> SK_G32_SHIFT) & 255) / 255.0;
+ b = ((first_pixel >> SK_B32_SHIFT) & 255) / 255.0;
+ }
+ constexpr float eps = 0.01;
+ EXPECT_NEAR(expected_r, r, eps);
+ EXPECT_NEAR(expected_g, g, eps);
+ EXPECT_NEAR(expected_b, b, eps);
+ EXPECT_NEAR(expected_a, a, eps);
+}
+
+void TestSize(const char* jxl_file, gfx::Size expected_size) {
+ auto decoder = CreateJXLDecoderWithData(jxl_file);
+ EXPECT_TRUE(decoder->IsSizeAvailable());
+ EXPECT_EQ(expected_size, decoder->Size());
+}
+
+struct FramePoint {
+ size_t frame;
+ gfx::Point point;
+};
+
+void TestPixel(const char* jxl_file,
+ gfx::Size expected_size,
+ const WTF::Vector<FramePoint>& coordinates,
+ const WTF::Vector<SkColor>& expected_colors,
+ ImageDecoder::AlphaOption alpha_option,
+ ColorBehavior color_behavior,
+ int accuracy,
+ size_t num_frames = 1) {
+ SCOPED_TRACE(testing::Message()
+ << "TestPixel jxl_file: " << jxl_file
+ << ", alpha_option:" << static_cast<int>(alpha_option));
+ EXPECT_EQ(coordinates.size(), expected_colors.size());
+ auto decoder = CreateJXLDecoderWithArguments(
+ jxl_file, alpha_option, ImageDecoder::kDefaultBitDepth, color_behavior);
+ EXPECT_TRUE(decoder->IsSizeAvailable());
+ EXPECT_EQ(expected_size, decoder->Size());
+ ASSERT_EQ(num_frames, decoder->FrameCount());
+ for (size_t i = 0; i < num_frames; ++i) {
+ ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i);
+ ASSERT_TRUE(frame);
+ EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus());
+ }
+ EXPECT_FALSE(decoder->Failed());
+ for (size_t i = 0; i < coordinates.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "Coordinate: " << i);
+ const SkBitmap& bitmap =
+ decoder->DecodeFrameBufferAtIndex(coordinates[i].frame)->Bitmap();
+ EXPECT_TRUE(SkColorSpace::Equals(bitmap.colorSpace(),
+ decoder->ColorSpaceForSkImages().get()));
+ int x = coordinates[i].point.x();
+ int y = coordinates[i].point.y();
+ SkColor frame_color = bitmap.getColor(x, y);
+ int r_expected = (expected_colors[i] >> 16) & 255;
+ int g_expected = (expected_colors[i] >> 8) & 255;
+ int b_expected = (expected_colors[i] >> 0) & 255;
+ int a_expected = (expected_colors[i] >> 24) & 255;
+ int r_actual = (frame_color >> 16) & 255;
+ int g_actual = (frame_color >> 8) & 255;
+ int b_actual = (frame_color >> 0) & 255;
+ int a_actual = (frame_color >> 24) & 255;
+ EXPECT_NEAR(r_expected, r_actual, accuracy);
+ EXPECT_NEAR(g_expected, g_actual, accuracy);
+ EXPECT_NEAR(b_expected, b_actual, accuracy);
+ // Alpha is always lossless.
+ EXPECT_EQ(a_expected, a_actual);
+ }
+}
+
+// SegmentReader implementation for testing, which always returns segments
+// of size 1. This allows to test whether the decoder handles streaming
+// correctly in the most fine-grained case.
+class PerByteSegmentReader : public SegmentReader {
+ public:
+ PerByteSegmentReader(SharedBuffer& buffer) : buffer_(buffer) {}
+ size_t size() const override { return buffer_.size(); }
+ size_t GetSomeData(const char*& data, size_t position) const override {
+ if (position >= buffer_.size()) {
+ return 0;
+ }
+ data = buffer_.Data() + position;
+ return 1;
+ }
+ sk_sp<SkData> GetAsSkData() const override { return nullptr; }
+
+ private:
+ SharedBuffer& buffer_;
+};
+
+// Tests whether the decoder successfully parses the file without errors or
+// infinite loop in the worst case of the reader returning 1-byte segments.
+void TestSegmented(const char* jxl_file, gfx::Size expected_size) {
+ auto decoder = std::make_unique<JXLImageDecoder>(
+ ImageDecoder::kAlphaNotPremultiplied, ImageDecoder::kDefaultBitDepth,
+ ColorBehavior::Tag(), ImageDecoder::kNoDecodedImageByteLimit);
+ scoped_refptr<SharedBuffer> data = ReadFile(jxl_file);
+ EXPECT_FALSE(data->empty());
+
+ scoped_refptr<SegmentReader> reader =
+ base::AdoptRef(new PerByteSegmentReader(*data.get()));
+ decoder->SetData(reader, true);
+
+ ImageFrame* frame;
+ for (;;) {
+ frame = decoder->DecodeFrameBufferAtIndex(0);
+ if (decoder->Failed())
+ break;
+ if (frame)
+ break;
+ }
+
+ EXPECT_TRUE(decoder->IsSizeAvailable());
+ EXPECT_LE(1u, decoder->FrameCount());
+ EXPECT_TRUE(!!frame);
+ EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus());
+ EXPECT_FALSE(decoder->Failed());
+ EXPECT_EQ(expected_size, decoder->Size());
+}
+
+TEST(JXLTests, SegmentedTest) {
+ TestSegmented("/images/resources/jxl/alpha-lossless.jxl", gfx::Size(2, 10));
+ TestSegmented("/images/resources/jxl/3x3_srgb_lossy.jxl", gfx::Size(3, 3));
+ TestSegmented("/images/resources/jxl/pq_gradient_icc_lossy.jxl",
+ gfx::Size(16, 16));
+ TestSegmented("/images/resources/jxl/animated.jxl", gfx::Size(16, 16));
+}
+
+TEST(JXLTests, SizeTest) {
+ TestSize("/images/resources/jxl/alpha-lossless.jxl", gfx::Size(2, 10));
+}
+
+TEST(JXLTests, PixelTest) {
+ TestPixel("/images/resources/jxl/red-10-default.jxl", gfx::Size(10, 10),
+ {{0, {0, 0}}}, {SkColorSetARGB(255, 255, 0, 0)},
+ ImageDecoder::AlphaOption::kAlphaNotPremultiplied,
+ ColorBehavior::Tag(), 0);
+ TestPixel("/images/resources/jxl/red-10-lossless.jxl", gfx::Size(10, 10),
+ {{0, {0, 1}}}, {SkColorSetARGB(255, 255, 0, 0)},
+ ImageDecoder::AlphaOption::kAlphaNotPremultiplied,
+ ColorBehavior::Tag(), 0);
+ TestPixel("/images/resources/jxl/red-10-container.jxl", gfx::Size(10, 10),
+ {{0, {1, 0}}}, {SkColorSetARGB(255, 255, 0, 0)},
+ ImageDecoder::AlphaOption::kAlphaNotPremultiplied,
+ ColorBehavior::Tag(), 0);
+ TestPixel("/images/resources/jxl/green-10-lossless.jxl", gfx::Size(10, 10),
+ {{0, {2, 3}}}, {SkColorSetARGB(255, 0, 255, 0)},
+ ImageDecoder::AlphaOption::kAlphaNotPremultiplied,
+ ColorBehavior::Tag(), 0);
+ TestPixel("/images/resources/jxl/blue-10-lossless.jxl", gfx::Size(10, 10),
+ {{0, {9, 9}}}, {SkColorSetARGB(255, 0, 0, 255)},
+ ImageDecoder::AlphaOption::kAlphaNotPremultiplied,
+ ColorBehavior::Tag(), 0);
+ TestPixel("/images/resources/jxl/alpha-lossless.jxl", gfx::Size(2, 10),
+ {{0, {0, 1}}}, {SkColorSetARGB(0, 255, 255, 255)},
+ ImageDecoder::AlphaOption::kAlphaNotPremultiplied,
+ ColorBehavior::Tag(), 0);
+ TestPixel("/images/resources/jxl/alpha-lossless.jxl", gfx::Size(2, 10),
+ {{0, {0, 1}}}, {SkColorSetARGB(0, 0, 0, 0)},
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 0);
+
+ WTF::Vector<FramePoint> coordinates_3x3 = {
+ {0, {0, 0}}, {0, {1, 0}}, {0, {2, 0}}, {0, {0, 1}}, {0, {1, 1}},
+ {0, {2, 1}}, {0, {0, 2}}, {0, {1, 2}}, {0, {2, 2}},
+ };
+
+ TestPixel("/images/resources/jxl/3x3_srgb_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 255, 0, 0),
+ SkColorSetARGB(255, 0, 255, 0),
+ SkColorSetARGB(255, 0, 0, 255),
+ SkColorSetARGB(255, 128, 64, 64),
+ SkColorSetARGB(255, 64, 128, 64),
+ SkColorSetARGB(255, 64, 64, 128),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 128, 128, 128),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 0);
+
+ TestPixel("/images/resources/jxl/3x3_srgb_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 255, 0, 0),
+ SkColorSetARGB(255, 0, 255, 0),
+ SkColorSetARGB(255, 0, 0, 255),
+ SkColorSetARGB(255, 128, 64, 64),
+ SkColorSetARGB(255, 64, 128, 64),
+ SkColorSetARGB(255, 64, 64, 128),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 128, 128, 128),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 15);
+
+ TestPixel("/images/resources/jxl/3x3a_srgb_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 255, 0, 0),
+ SkColorSetARGB(128, 0, 255, 0),
+ SkColorSetARGB(128, 0, 0, 255),
+ SkColorSetARGB(128, 128, 64, 64),
+ SkColorSetARGB(128, 64, 128, 64),
+ SkColorSetARGB(128, 64, 64, 128),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 128, 128, 128),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 0);
+
+ TestPixel("/images/resources/jxl/3x3a_srgb_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 255, 0, 0),
+ SkColorSetARGB(128, 0, 255, 0),
+ SkColorSetARGB(128, 0, 0, 255),
+ SkColorSetARGB(128, 128, 64, 64),
+ SkColorSetARGB(128, 64, 128, 64),
+ SkColorSetARGB(128, 64, 64, 128),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 128, 128, 128),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 15);
+
+ // Lossless, but allow some inaccuracy due to the color profile conversion.
+ TestPixel("/images/resources/jxl/3x3_gbr_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 0, 255, 0),
+ SkColorSetARGB(255, 0, 0, 255),
+ SkColorSetARGB(255, 255, 0, 0),
+ SkColorSetARGB(255, 64, 128, 64),
+ SkColorSetARGB(255, 64, 64, 128),
+ SkColorSetARGB(255, 128, 64, 64),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 128, 128, 128),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 3);
+
+ TestPixel("/images/resources/jxl/3x3_gbr_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 0, 255, 0),
+ SkColorSetARGB(255, 0, 0, 255),
+ SkColorSetARGB(255, 255, 0, 0),
+ SkColorSetARGB(255, 64, 128, 64),
+ SkColorSetARGB(255, 64, 64, 128),
+ SkColorSetARGB(255, 128, 64, 64),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 128, 128, 128),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 35);
+
+ // Lossless, but allow some inaccuracy due to the color profile conversion.
+ TestPixel("/images/resources/jxl/3x3a_gbr_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 0, 255, 0),
+ SkColorSetARGB(128, 0, 0, 255),
+ SkColorSetARGB(128, 255, 0, 0),
+ SkColorSetARGB(128, 64, 128, 64),
+ SkColorSetARGB(128, 64, 64, 128),
+ SkColorSetARGB(128, 128, 64, 64),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 128, 128, 128),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 3);
+
+ TestPixel("/images/resources/jxl/3x3a_gbr_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 0, 255, 0),
+ SkColorSetARGB(128, 0, 0, 255),
+ SkColorSetARGB(128, 255, 0, 0),
+ SkColorSetARGB(128, 64, 128, 64),
+ SkColorSetARGB(128, 64, 64, 128),
+ SkColorSetARGB(128, 128, 64, 64),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 128, 128, 128),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::TransformToSRGB(), 35);
+
+ // Lossless, but allow some inaccuracy due to the color profile conversion.
+ TestPixel("/images/resources/jxl/3x3_pq_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 255, 0, 0),
+ SkColorSetARGB(255, 0, 255, 0),
+ SkColorSetARGB(255, 0, 0, 255),
+ SkColorSetARGB(255, 128, 64, 64),
+ SkColorSetARGB(255, 64, 128, 64),
+ SkColorSetARGB(255, 64, 64, 128),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 128, 128, 128),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 2);
+
+ TestPixel("/images/resources/jxl/3x3_pq_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 255, 0, 0),
+ SkColorSetARGB(255, 64, 255, 64),
+ SkColorSetARGB(255, 39, 76, 255),
+ SkColorSetARGB(255, 128, 64, 64),
+ SkColorSetARGB(255, 64, 128, 64),
+ SkColorSetARGB(255, 64, 64, 128),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 128, 128, 128),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 15);
+
+ TestPixel("/images/resources/jxl/3x3a_pq_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 255, 0, 0),
+ SkColorSetARGB(128, 0, 255, 0),
+ SkColorSetARGB(128, 0, 0, 255),
+ SkColorSetARGB(128, 128, 64, 64),
+ SkColorSetARGB(128, 64, 128, 64),
+ SkColorSetARGB(128, 64, 64, 128),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 128, 128, 128),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 2);
+
+ TestPixel("/images/resources/jxl/3x3a_pq_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 255, 0, 0),
+ SkColorSetARGB(128, 64, 255, 64),
+ SkColorSetARGB(128, 40, 82, 255),
+ SkColorSetARGB(128, 128, 64, 64),
+ SkColorSetARGB(128, 64, 128, 64),
+ SkColorSetARGB(128, 64, 64, 128),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 128, 128, 128),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 15);
+
+ TestPixel("/images/resources/jxl/3x3_hlg_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 255, 0, 0),
+ SkColorSetARGB(255, 0, 255, 0),
+ SkColorSetARGB(255, 0, 0, 255),
+ SkColorSetARGB(255, 86, 46, 46),
+ SkColorSetARGB(255, 46, 86, 46),
+ SkColorSetARGB(255, 46, 46, 86),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 85, 85, 85),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 2);
+
+ TestPixel("/images/resources/jxl/3x3_hlg_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(255, 255, 13, 13),
+ SkColorSetARGB(255, 13, 255, 13),
+ SkColorSetARGB(255, 13, 13, 255),
+ SkColorSetARGB(255, 128, 64, 64),
+ SkColorSetARGB(255, 64, 128, 64),
+ SkColorSetARGB(255, 64, 64, 128),
+ SkColorSetARGB(255, 255, 255, 255),
+ SkColorSetARGB(255, 128, 128, 128),
+ SkColorSetARGB(255, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 15);
+
+ TestPixel("/images/resources/jxl/3x3a_hlg_lossless.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 255, 0, 0),
+ SkColorSetARGB(128, 0, 255, 0),
+ SkColorSetARGB(128, 0, 0, 255),
+ SkColorSetARGB(128, 86, 46, 46),
+ SkColorSetARGB(128, 46, 86, 46),
+ SkColorSetARGB(128, 46, 46, 86),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 85, 85, 85),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 6);
+
+ TestPixel("/images/resources/jxl/3x3a_hlg_lossy.jxl", gfx::Size(3, 3),
+ coordinates_3x3,
+ {
+ SkColorSetARGB(128, 255, 13, 13),
+ SkColorSetARGB(128, 13, 255, 13),
+ SkColorSetARGB(128, 13, 13, 255),
+ SkColorSetARGB(128, 128, 64, 64),
+ SkColorSetARGB(128, 64, 128, 64),
+ SkColorSetARGB(128, 74, 64, 128),
+ SkColorSetARGB(128, 255, 255, 255),
+ SkColorSetARGB(128, 128, 128, 128),
+ SkColorSetARGB(128, 0, 0, 0),
+ },
+ ImageDecoder::AlphaOption::kAlphaPremultiplied,
+ ColorBehavior::Tag(), 15);
+}
+
+TEST(JXLTests, ColorProfileTest) {
+ TestColorProfile("/images/resources/jxl/icc-v2-gbr.jxl", ColorBehavior::Tag(),
+ SkColorSetARGB(255, 0xaf, 0xfe, 0x6b));
+ TestColorProfile("/images/resources/jxl/icc-v2-gbr.jxl",
+ ColorBehavior::TransformToSRGB(),
+ SkColorSetARGB(255, 0x6b, 0xb1, 0xfe));
+ TestColorProfile("/images/resources/jxl/icc-v2-gbr.jxl",
+ ColorBehavior::Ignore(),
+ SkColorSetARGB(255, 0xaf, 0xfe, 0x6b));
+}
+
+TEST(JXLTests, AnimatedPixelTest) {
+ TestPixel(
+ "/images/resources/jxl/animated.jxl", gfx::Size(16, 16),
+ {{0, {0, 0}}, {1, {0, 0}}},
+ {SkColorSetARGB(255, 204, 0, 153), SkColorSetARGB(255, 0, 102, 102)},
+ ImageDecoder::AlphaOption::kAlphaNotPremultiplied, ColorBehavior::Tag(),
+ 0, 2);
+}
+
+TEST(JXLTests, JXLHDRTest) {
+ // PQ tests
+ // PQ values, as expected
+ TestHDR("/images/resources/jxl/pq_gradient_lossy.jxl",
+ ColorBehavior::Ignore(), false, 0.58039218187332153,
+ 0.73333334922790527, 0.43921568989753723, 1);
+ // sRGB as expected, but not an exact match
+ TestHDR("/images/resources/jxl/pq_gradient_lossy.jxl",
+ ColorBehavior::TransformToSRGB(), true, -0.9248046875, 1.943359375,
+ -0.4443359375, 1);
+
+ // linear sRGB as expected.
+ TestHDR("/images/resources/jxl/pq_gradient_lossy.jxl", ColorBehavior::Tag(),
+ true, 0.58039218187332153, 0.73333334922790527, 0.43921568989753723,
+ 1);
+
+ // correct, original PQ values
+ TestHDR("/images/resources/jxl/pq_gradient_lossless.jxl",
+ ColorBehavior::Ignore(), false, 0.58039218187332153,
+ 0.73725491762161255, 0.45098039507865906, 1);
+ TestHDR("/images/resources/jxl/pq_gradient_lossless.jxl",
+ ColorBehavior::TransformToSRGB(), true, -0.95751953125, 1.9677734375,
+ -0.416748046875, 1);
+ // correct, original PQ values
+ TestHDR("/images/resources/jxl/pq_gradient_lossless.jxl",
+ ColorBehavior::Tag(), true, 0.58056640625, 0.7373046875,
+ 0.450927734375, 1);
+
+ // with ICC
+ // clipped linear sRGB, as expected from current JXL implementation
+ TestHDR("/images/resources/jxl/pq_gradient_icc_lossy.jxl",
+ ColorBehavior::Ignore(), false, 0, 0.0930381, 0, 1);
+
+ TestHDR("/images/resources/jxl/pq_gradient_icc_lossy.jxl",
+ ColorBehavior::TransformToSRGB(), false, 0, 0.338623046875, 0, 1);
+ TestHDR("/images/resources/jxl/pq_gradient_icc_lossy.jxl",
+ ColorBehavior::Tag(), false, 0, 0.0930381, 0, 1);
+
+ TestHDR("/images/resources/jxl/pq_gradient_icc_lossless.jxl",
+ ColorBehavior::Ignore(), false, 0.58039218187332153,
+ 0.73725491762161255, 0.45098039507865906, 1);
+ TestHDR("/images/resources/jxl/pq_gradient_icc_lossless.jxl",
+ ColorBehavior::TransformToSRGB(), true, -0.95751953125, 1.9677734375,
+ -0.416748046875, 1);
+ TestHDR("/images/resources/jxl/pq_gradient_icc_lossless.jxl",
+ ColorBehavior::Tag(), true, 0.58039218187332153, 0.73725491762161255,
+ 0.45098039507865906, 1);
+}
+
+TEST(JXLTests, RandomFrameDecode) {
+ TestRandomFrameDecode(&CreateJXLDecoder, "/images/resources/jxl/count.jxl");
+}
+
+TEST(JXLTests, RandomDecodeAfterClearFrameBufferCache) {
+ TestRandomDecodeAfterClearFrameBufferCache(&CreateJXLDecoder,
+ "/images/resources/jxl/count.jxl");
+}
+
+} // namespace
+} // namespace blink
diff --git a/third_party/blink/tools/commit_stats/git-dirs.txt b/third_party/blink/tools/commit_stats/git-dirs.txt
--- a/third_party/blink/tools/commit_stats/git-dirs.txt
+++ b/third_party/blink/tools/commit_stats/git-dirs.txt
@@ -75,6 +75,7 @@
./third_party/angle/third_party/glmark2/src,ANGLE
./third_party/openh264/src,OpenH264
./third_party/googletest/src,GoogleTest
+./third_party/libjxl/src,libjxl
./third_party/highway/src,highway
./third_party/wuffs/src,wuffs
./third_party/catapult,catapult
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4781,6 +4781,12 @@ crbug.com/1199522 http/tests/devtools/layers/layers-3d-view-hit-testing.js [ Fai
# Started failing after rolling new version of check-layout-th.js
css3/flexbox/perpendicular-writing-modes-inside-flex-item.html [ Failure ]
+# JXL tests fail due to rounding error differences.
+# TODO(https://crbug.com/1274220): Rebaseline all images once new tone mapping
+# lands.
+crbug.com/1210658 virtual/jxl-enabled/images/jxl/jxl-images.html [ Crash Failure Pass Timeout ]
+crbug.com/1358616 virtual/jxl-enabled/images/jxl/progressive.html [ Crash Failure Pass Timeout ]
+
# Temporarily disabled to unblock https://crrev.com/c/3099011
crbug.com/1199701 http/tests/devtools/console/console-big-array.js [ Crash Failure Pass Timeout ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -447,6 +447,15 @@
"--disable-threaded-compositing", "--disable-threaded-animation"],
"expires": "Jul 1, 2023"
},
+ {
+ "prefix": "jxl-enabled",
+ "platforms": ["Linux", "Mac", "Win"],
+ "bases": ["http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl.js",
+ "images/jxl"],
+ "exclusive_tests": ["http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl.js"],
+ "args": ["--enable-features=JXL"],
+ "expires": "Jul 1, 2023"
+ },
{
"prefix": "scalefactor150",
"platforms": ["Linux", "Win"],
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl-expected.txt
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl-expected.txt
@@ -0,0 +1,13 @@
+Tests the Emulation.setDisabledImageTypes method for JPEG XL.
+With emulation (jxl enabled):
+Expected jxl image: http://127.0.0.1:8000/inspector-protocol/emulation/resources/test.jxl
+Image request Accept header: image/jxl,image/avif,image/apng,image/svg+xml,image/*,*/*;q=0.8
+With emulation (jxl disabled):
+Expected png image: http://127.0.0.1:8000/inspector-protocol/emulation/resources/test.png
+Image request Accept header: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
+With emulation (jxl enabled):
+Expected jxl image: http://127.0.0.1:8000/inspector-protocol/emulation/resources/test.jxl
+Image request Accept header: image/jxl,image/avif,image/apng,image/svg+xml,image/*,*/*;q=0.8
+With emulation (jxl disabled):
+Expected png image: http://127.0.0.1:8000/inspector-protocol/emulation/resources/test.png
+Image request Accept header: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl.js b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl.js
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/emulation-set-disabled-image-types-jxl.js
@@ -0,0 +1,50 @@
+(async function(testRunner) {
+ const {page, session, dp} =
+ await testRunner.startBlank('Tests the Emulation.setDisabledImageTypes method for JPEG XL.');
+
+ await dp.Page.enable();
+ await dp.Network.enable();
+
+ let requestEvents = [];
+ dp.Network.onRequestWillBeSent(event => requestEvents.push(event));
+
+ await dp.Emulation.setDisabledImageTypes({ imageTypes: ['webp'] });
+
+ testRunner.log('With emulation (jxl enabled):');
+ await page.navigate(testRunner.url('resources/image-jxl-fallback-img.html'));
+ testRunner.log('Expected jxl image: ' + await session.evaluate(() => document.querySelector('img').currentSrc));
+ let jxlRequest = requestEvents.map(event => event.params.request).find(request => request.url.endsWith('test.jxl'));
+ testRunner.log('Image request Accept header: ' + jxlRequest.headers.Accept);
+
+ requestEvents = [];
+
+ testRunner.log('With emulation (jxl disabled):');
+ await dp.Emulation.setDisabledImageTypes({ imageTypes: ['jxl'] });
+ dp.Page.reload({ ignoreCache: true });
+ await dp.Page.onceLoadEventFired();
+ testRunner.log('Expected png image: ' + await session.evaluate(() => document.querySelector('img').currentSrc));
+ let pngRequest = requestEvents.map(event => event.params.request).find(request => request.url.endsWith('test.png'));
+ testRunner.log('Image request Accept header: ' + pngRequest.headers.Accept);
+
+ requestEvents = [];
+
+ await dp.Emulation.setDisabledImageTypes({ imageTypes: ['webp'] });
+
+ testRunner.log('With emulation (jxl enabled):');
+ await page.navigate(testRunner.url('resources/image-jxl-fallback-picture.html'));
+ testRunner.log('Expected jxl image: ' + await session.evaluate(() => document.querySelector('img').currentSrc));
+ jxlRequest = requestEvents.map(event => event.params.request).find(request => request.url.endsWith('test.jxl'));
+ testRunner.log('Image request Accept header: ' + jxlRequest.headers.Accept);
+
+ requestEvents = [];
+
+ testRunner.log('With emulation (jxl disabled):');
+ await dp.Emulation.setDisabledImageTypes({ imageTypes: ['jxl'] });
+ dp.Page.reload({ ignoreCache: true });
+ await dp.Page.onceLoadEventFired();
+ testRunner.log('Expected png image: ' + await session.evaluate(() => document.querySelector('img').currentSrc));
+ pngRequest = requestEvents.map(event => event.params.request).find(request => request.url.endsWith('test.png'));
+ testRunner.log('Image request Accept header: ' + pngRequest.headers.Accept);
+
+ testRunner.completeTest();
+})
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-img.html b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-img.html
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-img.html
@@ -0,0 +1 @@
+<img src="test.jxl" onerror="src='test.png'">
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-picture.html b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-picture.html
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/emulation/resources/image-jxl-fallback-picture.html
@@ -0,0 +1,4 @@
+<picture>
+ <source srcset="test.jxl" type="image/jxl">
+ <img src="test.png">
+</picture>
diff --git a/third_party/blink/web_tests/images/jxl/jxl-images.html b/third_party/blink/web_tests/images/jxl/jxl-images.html
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/images/jxl/jxl-images.html
@@ -0,0 +1,22 @@
+<img src="../resources/jxl/3x3a_gbr_lossless.jxl">
+<img src="../resources/jxl/3x3a_gbr_lossy.jxl">
+<img src="../resources/jxl/3x3a_pq_lossless.jxl">
+<img src="../resources/jxl/3x3a_pq_lossy.jxl">
+<img src="../resources/jxl/3x3a_srgb_lossless.jxl">
+<img src="../resources/jxl/3x3a_srgb_lossy.jxl">
+<img src="../resources/jxl/3x3_gbr_lossless.jxl">
+<img src="../resources/jxl/3x3_gbr_lossy.jxl">
+<img src="../resources/jxl/3x3_pq_lossless.jxl">
+<img src="../resources/jxl/3x3_pq_lossy.jxl">
+<img src="../resources/jxl/3x3_srgb_lossless.jxl">
+<img src="../resources/jxl/3x3_srgb_lossy.jxl">
+<img src="../resources/jxl/alpha-large-dice.jxl">
+<img src="../resources/jxl/alpha-lossless.jxl">
+<img src="../resources/jxl/blue-10-lossless.jxl">
+<img src="../resources/jxl/green-10-lossless.jxl">
+<img src="../resources/jxl/icc-grb2.jxl">
+<img src="../resources/jxl/icc-grb.jxl">
+<img src="../resources/jxl/icc-v2-gbr.jxl">
+<img src="../resources/jxl/red-10-container.jxl">
+<img src="../resources/jxl/red-10-default.jxl">
+<img src="../resources/jxl/red-10-lossless.jxl">
diff --git a/third_party/blink/web_tests/images/jxl/progressive.html b/third_party/blink/web_tests/images/jxl/progressive.html
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/images/jxl/progressive.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="../resources/jxl/partial_black.jxl">
+</body>
+</html>
diff --git a/third_party/blink/web_tests/images/resources/jxl/README.md b/third_party/blink/web_tests/images/resources/jxl/README.md
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/images/resources/jxl/README.md
@@ -0,0 +1,79 @@
+# JPEG XL Test files
+
+## How to generate the test set
+
+We assume to have a the following images (from
+`third_party/blink/web_tests/images/resources/`) available:
+```
+red-10.png
+green-10.png
+blue-10.png
+png_per_row_alpha.png
+icc-v2-gbr.jpg
+dice.png
+animated.gif
+jxl/3x3.png
+jxl/3x3a.png
+```
+Then we run:
+```
+cjxl red-10.png red-10-default.jxl
+cjxl --container red-10.png red-10-container.jxl
+cjxl -d 0 red-10.png red-10-lossless.jxl
+cjxl -d 0 green-10.png green-10-lossless.jxl
+cjxl -d 0 blue-10.png blue-10-lossless.jxl
+cjxl -d 0 png_per_row_alpha.png alpha-lossless.jxl
+cjxl icc-v2-gbr.jpg icc-v2-gbr.jxl
+cjxl -d 0 dice.png alpha-large-dice.jxl
+
+cjxl 3x3.png temp.jxl -d 0
+djxl temp.jxl 3x3_srgb.png
+cjxl 3x3_srgb.png 3x3_srgb_lossy.jxl -d 0.1 -e 7
+cjxl 3x3_srgb.png 3x3_srgb_lossless.jxl -d 0
+
+cjxl 3x3a.png temp.jxl -d 0
+djxl temp.jxl 3x3a_srgb.png
+cjxl 3x3a_srgb.png 3x3a_srgb_lossy.jxl -d 0.1 -e 7
+cjxl 3x3a_srgb.png 3x3a_srgb_lossless.jxl -d 0
+
+cjxl 3x3.png temp.jxl -x color_space=RGB_D65_202_Rel_PeQ -d 0
+djxl temp.jxl 3x3_pq.png
+cjxl 3x3_pq.png 3x3_pq_lossy.jxl -d 0.1 -e 7
+cjxl 3x3_pq.png 3x3_pq_lossless.jxl -d 0
+
+cjxl 3x3a.png temp.jxl -x color_space=RGB_D65_202_Rel_PeQ -d 0
+djxl temp.jxl 3x3a_pq.png
+cjxl 3x3a_pq.png 3x3a_pq_lossy.jxl -d 0.1 -e 7
+cjxl 3x3a_pq.png 3x3a_pq_lossless.jxl -d 0
+
+cjxl 3x3.png temp.jxl -x color_space=RGB_D65_202_Rel_HLG -d 0
+djxl temp.jxl 3x3_hlg.png
+cjxl 3x3_hlg.png 3x3_hlg_lossy.jxl -d 0.1 -e 7
+cjxl 3x3_hlg.png 3x3_hlg_lossless.jxl -d 0
+
+cjxl 3x3a.png temp.jxl -x color_space=RGB_D65_202_Rel_HLG -d 0
+djxl temp.jxl 3x3a_hlg.png
+cjxl 3x3a_hlg.png 3x3a_hlg_lossy.jxl -d 0.1 -e 7
+cjxl 3x3a_hlg.png 3x3a_hlg_lossless.jxl -d 0
+
+convert icc-v2-gbr.jpg icc-v2-gbr.icc
+cjxl 3x3.png temp.jxl -x icc_pathname=icc-v2-gbr.icc -d 0
+djxl temp.jxl 3x3_gbr.png
+cjxl 3x3_gbr.png 3x3_gbr_lossy.jxl -d 0.1 -e 7
+cjxl 3x3_gbr.png 3x3_gbr_lossless.jxl -d 0
+
+cjxl 3x3a.png temp.jxl -x icc_pathname=icc-v2-gbr.icc -d 0
+djxl temp.jxl 3x3a_gbr.png
+cjxl 3x3a_gbr.png 3x3a_gbr_lossy.jxl -d 0.1 -e 7
+cjxl 3x3a_gbr.png 3x3a_gbr_lossless.jxl -d 0
+
+cjxl animated.gif animated.jxl
+
+for i in $(seq 0 9); do J=$(printf '%03d' $i); convert -fill black -size 500x500 -font 'Courier' -pointsize 72 -gravity center label:$J $J.png; done
+convert -delay 20 *.png count.gif
+cjxl count.gif count.jxl
+
+convert -size 680x420 xc:black black.png
+cjxl --group_order 1 -d 0 black.png black.jxl
+dd bs=1 count=46 if=black.jxl of=partial_black.jxl
+```
diff --git a/third_party/blink/web_tests/virtual/jxl-enabled/README.md b/third_party/blink/web_tests/virtual/jxl-enabled/README.md
new file mode 100644
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/jxl-enabled/README.md
@@ -0,0 +1,5 @@
+This suite runs the tests with
+--enable-features=JXL
+
+See the issue for more details:
+https://crbug.com/1178058
diff --git a/third_party/libjxl/BUILD.gn b/third_party/libjxl/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/third_party/libjxl/BUILD.gn
@@ -0,0 +1,79 @@
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Import list of source files and flags from the jpeg-xl project.
+import("src/lib/lib.gni")
+import("//build/util/process_version.gni")
+
+# This config is applied to targets that depend on libjxl.
+config("libjxl_external_config") {
+ include_dirs = [
+ # Path to the public headers.
+ "src/lib/include",
+
+ # Configuration headers normally generated by the cmake build system.
+ "gen_headers",
+
+ "${target_gen_dir}/",
+ ]
+}
+
+source_set("libjxl") {
+ sources = rebase_path(libjxl_dec_sources, ".", "src/lib")
+
+ cflags_cc = [
+ "-Wno-shadow",
+ "-Wno-unused-function",
+ ]
+
+ defines = [
+ "JPEGXL_ENABLE_SKCMS=1",
+
+ # Disabling decode-to-JPEG bytes in the library removes about 20%
+ # of the binary size (as measured in android arm builds).
+ # Transcoding back to JPEG is not used in Chrome, only decoding to
+ # pixels is used even for files that were originally transcoded
+ # *from* JPEG.
+ "JPEGXL_ENABLE_TRANSCODE_JPEG=0",
+ ]
+
+ if (is_official_build) {
+ # Disable assertion messages, saving about 6 kB in android.
+ defines += [ "JXL_DEBUG_ON_ABORT=0" ]
+ }
+
+ include_dirs = [
+ "src",
+ "src/lib/include/",
+ "${target_gen_dir}/",
+ "//third_party/skia/include/third_party/skcms", # for "skcms.h"
+ ]
+
+ deps = [
+ ":libjxml_version",
+ "//skia:skcms",
+ "//third_party/brotli:dec",
+ "//third_party/highway:libhwy",
+ ]
+
+ public_configs = [ ":libjxl_external_config" ]
+}
+
+process_version("libjxml_version") {
+ write_file("$target_gen_dir/jxl/version", libjxl_version_defines)
+
+ template_file = "src/lib/jxl/version.h.in"
+ output = "$target_gen_dir/jxl/version.h"
+
+ sources = [ "$target_gen_dir/jxl/version" ]
+
+ extra_args = [
+ "-e",
+ "JPEGXL_MAJOR_VERSION=\"%s\"%(JPEGXL_MAJOR_VERSION)",
+ "-e",
+ "JPEGXL_MINOR_VERSION=\"%s\"%(JPEGXL_MINOR_VERSION)",
+ "-e",
+ "JPEGXL_PATCH_VERSION=\"%s\"%(JPEGXL_PATCH_VERSION)",
+ ]
+}
diff --git a/third_party/libjxl/DIR_METADATA b/third_party/libjxl/DIR_METADATA
new file mode 100644
--- /dev/null
+++ b/third_party/libjxl/DIR_METADATA
@@ -0,0 +1,4 @@
+
+monorail: {
+ component: "Internals>Images>Codecs"
+}
diff --git a/third_party/libjxl/LICENSE b/third_party/libjxl/LICENSE
new file mode 100644
--- /dev/null
+++ b/third_party/libjxl/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) the JPEG XL Project Authors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/libjxl/OWNERS b/third_party/libjxl/OWNERS
new file mode 100644
--- /dev/null
+++ b/third_party/libjxl/OWNERS
@@ -0,0 +1,9 @@
+# Owners:
+noel@chromium.org
+scroggo@google.com
+
+# Reviewers:
+# eustas@chromium.org
+# firsching@google.com
+# sboukortt@google.com
+# veluca@google.com
diff --git a/third_party/libjxl/README.chromium b/third_party/libjxl/README.chromium
new file mode 100644
--- /dev/null
+++ b/third_party/libjxl/README.chromium
@@ -0,0 +1,15 @@
+Name: JPEG XL image decoder library
+Short Name: libjxl
+URL: https://github.com/libjxl/libjxl
+Version: 0.7rc
+Date: 2022-08-24
+Revision: 3e246a860ea6d510d91ceca5413dcc50e8c41dd9
+License: BSD 3-Clause
+Security Critical: yes
+CPEPrefix: cpe:/a:libjxl_project:libjxl:0.7rc
+
+Description:
+The reference implementation for the JPEG XL image encoder/decoder.
+
+Local Modifications:
+None. Only decoder-side is compiled.
diff --git a/third_party/libjxl/gen_headers/jxl/jxl_export.h b/third_party/libjxl/gen_headers/jxl/jxl_export.h
new file mode 100644
--- /dev/null
+++ b/third_party/libjxl/gen_headers/jxl/jxl_export.h
@@ -0,0 +1,11 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBJXL_GEN_HEADERS_JXL_JXL_EXPORT_H_
+#define THIRD_PARTY_LIBJXL_GEN_HEADERS_JXL_JXL_EXPORT_H_
+
+#define JXL_EXPORT
+#define JXL_DEPRECATED
+
+#endif // THIRD_PARTY_LIBJXL_GEN_HEADERS_JXL_JXL_EXPORT_H_
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -26495,7 +26495,7 @@ Called by update_debug_scenarios.py.-->
<int value="5" label="kImageICO"/>
<int value="6" label="kImageBMP"/>
<int value="7" label="kImageAVIF"/>
- <int value="8" label="(obsolete) kImageJXL"/>
+ <int value="8" label="kImageJXL"/>
</enum>
<enum name="DecodeSwapChainNotUsedReason">
@@ -65579,6 +65579,7 @@ from previous Chrome versions.
<int value="188610022" label="NewMessageListView:enabled"/>
<int value="189728101" label="FasterLocationReload:disabled"/>
<int value="189777537" label="DisableInitialMostVisitedFadeIn:enabled"/>
+ <int value="190282969" label="JXL:disabled"/>
<int value="190395448" label="SearchWebInSidePanel:enabled"/>
<int value="191737931" label="enable-mark-http-as"/>
<int value="191891628" label="AutocompleteExtendedSuggestions:enabled"/>
--
2.25.1