From 51ea0e4f3a19beaa837a90a0b965f89e9eefd6c7 Mon Sep 17 00:00:00 2001 From: Job Bautista Date: Sun, 19 Jun 2022 15:40:33 +0800 Subject: [PATCH] Issue #1769 - Part 2: Implement JPEG-XL decoder and about:config and MIME plumbing. Backported from Mozilla bug 1707590 whereever possible. --- gfx/thebes/gfxPrefs.h | 1 + image/DecoderFactory.cpp | 14 ++ image/DecoderFactory.h | 7 +- image/build/nsImageModule.cpp | 1 + image/decoders/moz.build | 5 + image/decoders/nsJXLDecoder.cpp | 162 ++++++++++++++++++ image/decoders/nsJXLDecoder.h | 53 ++++++ image/imgLoader.cpp | 6 + image/test/reftest/jxl/jxl-size-33x33.jxl | Bin 0 -> 916 bytes image/test/reftest/jxl/jxl-size-33x33.png | Bin 0 -> 3303 bytes image/test/reftest/jxl/reftest.list | 3 + image/test/reftest/reftest.list | 3 + modules/libpref/init/all.js | 4 + netwerk/mime/nsMimeTypes.h | 1 + .../exthandler/nsExternalHelperAppService.cpp | 1 + 15 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 image/decoders/nsJXLDecoder.cpp create mode 100644 image/decoders/nsJXLDecoder.h create mode 100644 image/test/reftest/jxl/jxl-size-33x33.jxl create mode 100644 image/test/reftest/jxl/jxl-size-33x33.png create mode 100644 image/test/reftest/jxl/reftest.list diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index c2b28eee64..dcf3077378 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -448,6 +448,7 @@ private: DECL_GFX_PREF(Live, "image.mozsamplesize.enabled", ImageMozSampleSizeEnabled, bool, false); DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1); DECL_GFX_PREF(Live, "image.webp.enabled", ImageWebPEnabled, bool, true); + DECL_GFX_PREF(Live, "image.jxl.enabled", ImageJXLEnabled, bool, true); DECL_GFX_PREF(Once, "layers.acceleration.enabled", LayersAccelerationEnabledDoNotUseDirectly, bool, true); DECL_GFX_PREF(Live, "layers.acceleration.draw-fps", LayersDrawFPS, bool, false); diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 33d2b56558..cecf12ad78 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -19,6 +19,9 @@ #include "nsICODecoder.h" #include "nsIconDecoder.h" #include "nsWebPDecoder.h" +#ifdef MOZ_JXL +# include "nsJXLDecoder.h" +#endif namespace mozilla { @@ -77,6 +80,12 @@ DecoderFactory::GetDecoderType(const char* aMimeType) gfxPrefs::ImageWebPEnabled()) { type = DecoderType::WEBP; } +#ifdef MOZ_JXL + else if (!strcmp(aMimeType, IMAGE_JXL) && + gfxPrefs::ImageJXLEnabled()) { + type = DecoderType::JXL; + } +#endif return type; } @@ -116,6 +125,11 @@ DecoderFactory::GetDecoder(DecoderType aType, case DecoderType::WEBP: decoder = new nsWebPDecoder(aImage); break; +#ifdef MOZ_JXL + case DecoderType::JXL: + decoder = new nsJXLDecoder(aImage); + break; +#endif default: MOZ_ASSERT_UNREACHABLE("Unknown decoder type"); } diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index 58b5709aa1..05e720a347 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -15,8 +15,7 @@ #include "nsCOMPtr.h" #include "SurfaceFlags.h" -namespace mozilla { -namespace image { +namespace mozilla::image { class Decoder; class IDecodingTask; @@ -38,6 +37,7 @@ enum class DecoderType ICO, ICON, WEBP, + JXL, UNKNOWN }; @@ -200,7 +200,6 @@ private: bool aIsRedecode); }; -} // namespace image -} // namespace mozilla +} // namespace mozilla::image #endif // mozilla_image_DecoderFactory_h diff --git a/image/build/nsImageModule.cpp b/image/build/nsImageModule.cpp index 9d52c07b61..ed17c4b63e 100644 --- a/image/build/nsImageModule.cpp +++ b/image/build/nsImageModule.cpp @@ -83,6 +83,7 @@ static const mozilla::Module::CategoryEntry kImageCategories[] = { { "Gecko-Content-Viewers", IMAGE_APNG, "@mozilla.org/content/document-loader-factory;1" }, { "Gecko-Content-Viewers", IMAGE_X_PNG, "@mozilla.org/content/document-loader-factory;1" }, { "Gecko-Content-Viewers", IMAGE_WEBP, "@mozilla.org/content/document-loader-factory;1" }, + { "Gecko-Content-Viewers", IMAGE_JXL, "@mozilla.org/content/document-loader-factory;1" }, { "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" }, { nullptr } }; diff --git a/image/decoders/moz.build b/image/decoders/moz.build index 907b81bda8..e24e47e3cb 100644 --- a/image/decoders/moz.build +++ b/image/decoders/moz.build @@ -28,6 +28,11 @@ UNIFIED_SOURCES += [ 'nsWebPDecoder.cpp', ] +if CONFIG["MOZ_JXL"]: + UNIFIED_SOURCES += [ + "nsJXLDecoder.cpp", + ] + include('/ipc/chromium/chromium-config.mozbuild') LOCAL_INCLUDES += [ diff --git a/image/decoders/nsJXLDecoder.cpp b/image/decoders/nsJXLDecoder.cpp new file mode 100644 index 0000000000..ce83937490 --- /dev/null +++ b/image/decoders/nsJXLDecoder.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLogging.h" // Must appear first +#include "gfxPlatform.h" +#include "jxl/codestream_header.h" +#include "jxl/decode_cxx.h" +#include "jxl/types.h" +#include "mozilla/gfx/Point.h" +#include "nsJXLDecoder.h" + +#include "RasterImage.h" +#include "SurfacePipeFactory.h" + +using namespace mozilla::gfx; + +namespace mozilla::image { + +#define JXL_TRY(expr) \ + do { \ + JxlDecoderStatus status = (expr); \ + if (status != JXL_DEC_SUCCESS) { \ + return Transition::TerminateFailure(); \ + } \ + } while (0); + +#define JXL_TRY_BOOL(expr) \ + do { \ + bool succeeded = (expr); \ + if (!succeeded) { \ + return Transition::TerminateFailure(); \ + } \ + } while (0); + +static LazyLogModule sJXLLog("JXLDecoder"); + +nsJXLDecoder::nsJXLDecoder(RasterImage* aImage) + : Decoder(aImage), + mLexer(Transition::ToUnbuffered(State::FINISHED_JXL_DATA, State::JXL_DATA, + SIZE_MAX), + Transition::TerminateSuccess()), + mDecoder(JxlDecoderMake(nullptr)), + mParallelRunner( + JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())) { + JxlDecoderSubscribeEvents(mDecoder.get(), + JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); + JxlDecoderSetParallelRunner(mDecoder.get(), JxlThreadParallelRunner, + mParallelRunner.get()); + + MOZ_LOG(sJXLLog, LogLevel::Debug, + ("[this=%p] nsJXLDecoder::nsJXLDecoder", this)); +} + +nsJXLDecoder::~nsJXLDecoder() { + MOZ_LOG(sJXLLog, LogLevel::Debug, + ("[this=%p] nsJXLDecoder::~nsJXLDecoder", this)); +} + +size_t nsJXLDecoder::PreferredThreadCount() { + if (IsMetadataDecode()) { + return 0; // no additional worker thread + } + return JxlThreadParallelRunnerDefaultNumWorkerThreads(); +} + +LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) { + // return LexerResult(TerminalState::FAILURE); + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](State aState, const char* aData, size_t aLength) { + switch (aState) { + case State::JXL_DATA: + return ReadJXLData(aData, aLength); + case State::FINISHED_JXL_DATA: + return FinishedJXLData(); + } + MOZ_CRASH("Unknown State"); + }); +}; + +LexerTransition nsJXLDecoder::ReadJXLData( + const char* aData, size_t aLength) { + const uint8_t* input = (const uint8_t*)aData; + size_t length = aLength; + if (mBuffer.length() != 0) { + JXL_TRY_BOOL(mBuffer.append(aData, aLength)); + input = mBuffer.begin(); + length = mBuffer.length(); + } + JXL_TRY(JxlDecoderSetInput(mDecoder.get(), input, length)); + + while (true) { + JxlDecoderStatus status = JxlDecoderProcessInput(mDecoder.get()); + switch (status) { + case JXL_DEC_ERROR: + default: + return Transition::TerminateFailure(); + + case JXL_DEC_NEED_MORE_INPUT: { + size_t remaining = JxlDecoderReleaseInput(mDecoder.get()); + mBuffer.clear(); + JXL_TRY_BOOL(mBuffer.append(aData + aLength - remaining, remaining)); + return Transition::ContinueUnbuffered(State::JXL_DATA); + } + + case JXL_DEC_BASIC_INFO: { + JXL_TRY(JxlDecoderGetBasicInfo(mDecoder.get(), &mInfo)); + PostSize(mInfo.xsize, mInfo.ysize); + if (mInfo.alpha_bits > 0) { + PostHasTransparency(); + } + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } + break; + } + + case JXL_DEC_NEED_IMAGE_OUT_BUFFER: { + size_t size = 0; + JxlPixelFormat format{4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; + JXL_TRY(JxlDecoderImageOutBufferSize(mDecoder.get(), &format, &size)); + + mOutBuffer.clear(); + JXL_TRY_BOOL(mOutBuffer.growBy(size)); + JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &format, + mOutBuffer.begin(), size)); + break; + } + + case JXL_DEC_FULL_IMAGE: { + OrientedIntSize size(mInfo.xsize, mInfo.ysize); + Maybe pipe = SurfacePipeFactory::CreateSurfacePipe( + this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8, + SurfaceFormat::OS_RGBA, Nothing(), nullptr, SurfacePipeFlags()); + for (uint8_t* rowPtr = mOutBuffer.begin(); rowPtr < mOutBuffer.end(); + rowPtr += mInfo.xsize * 4) { + pipe->WriteBuffer(reinterpret_cast(rowPtr)); + } + + if (Maybe invalidRect = pipe->TakeInvalidRect()) { + PostInvalidation(invalidRect->mInputSpaceRect, + Some(invalidRect->mOutputSpaceRect)); + } + PostFrameStop(); + PostDecodeDone(); + return Transition::TerminateSuccess(); + } + } + } +} + +LexerTransition nsJXLDecoder::FinishedJXLData() { + MOZ_ASSERT_UNREACHABLE("Read the entire address space?"); + return Transition::TerminateFailure(); +} + +} // namespace mozilla::image diff --git a/image/decoders/nsJXLDecoder.h b/image/decoders/nsJXLDecoder.h new file mode 100644 index 0000000000..0b723878ae --- /dev/null +++ b/image/decoders/nsJXLDecoder.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_nsJXLDecoder_h +#define mozilla_image_decoders_nsJXLDecoder_h + +#include "Decoder.h" +#include "mp4parse.h" +#include "SurfacePipe.h" + +#include "jxl/decode_cxx.h" +#include "jxl/thread_parallel_runner_cxx.h" + +namespace mozilla::image { +class RasterImage; + +class nsJXLDecoder final : public Decoder { + public: + virtual ~nsJXLDecoder(); + + DecoderType GetType() const override { return DecoderType::JXL; } + + protected: + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + + private: + friend class DecoderFactory; + + // Decoders should only be instantiated via DecoderFactory. + explicit nsJXLDecoder(RasterImage* aImage); + + size_t PreferredThreadCount(); + + enum class State { JXL_DATA, FINISHED_JXL_DATA }; + + LexerTransition ReadJXLData(const char* aData, size_t aLength); + LexerTransition FinishedJXLData(); + + StreamingLexer mLexer; + JxlDecoderPtr mDecoder; + JxlThreadParallelRunnerPtr mParallelRunner; + Vector mBuffer; + Vector mOutBuffer; + JxlBasicInfo mInfo{}; +}; + +} // namespace mozilla::image + +#endif // mozilla_image_decoders_nsJXLDecoder_h diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index 0f516bc800..1bf6ece905 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -2550,6 +2550,12 @@ imgLoader::GetMimeTypeFromContent(const char* aContents, !memcmp(aContents + 8, "WEBP", 4)) { aContentType.AssignLiteral(IMAGE_WEBP); + } else if ((aLength >= 2 && !memcmp(aContents, "\xFF\x0A", 2)) || + (aLength >= 12 && + !memcmp(aContents, "\x00\x00\x00\x0CJXL \x0D\x0A\x87\x0A", 12))) { + // Each version is for containerless and containerful files respectively. + aContentType.AssignLiteral(IMAGE_JXL); + } else { /* none of the above? I give up */ return NS_ERROR_NOT_AVAILABLE; diff --git a/image/test/reftest/jxl/jxl-size-33x33.jxl b/image/test/reftest/jxl/jxl-size-33x33.jxl new file mode 100644 index 0000000000000000000000000000000000000000..da7c1c76f1a34b72947e85baa4c624fc1ba6b891 GIT binary patch literal 916 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 literal 0 HcmV?d00001 diff --git a/image/test/reftest/jxl/reftest.list b/image/test/reftest/jxl/reftest.list new file mode 100644 index 0000000000..04819b5543 --- /dev/null +++ b/image/test/reftest/jxl/reftest.list @@ -0,0 +1,3 @@ +# JXL tests + +pref(image.jxl.enabled,true) == jxl-size-33x33.jxl jxl-size-33x33.png diff --git a/image/test/reftest/reftest.list b/image/test/reftest/reftest.list index bfa154e6aa..8547e50369 100644 --- a/image/test/reftest/reftest.list +++ b/image/test/reftest/reftest.list @@ -28,6 +28,9 @@ skip-if(Android) include ico/reftest.list # JPEG tests include jpeg/reftest.list +# JXL tests +skip-if(Android) include jxl/reftest.list + # GIF tests include gif/reftest.list diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index f94674039e..8d89c5aea1 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4262,6 +4262,10 @@ pref("image.multithreaded_decoding.limit", -1); // Whether we attempt to decode WebP images or not. pref("image.webp.enabled", true); +#ifdef MOZ_JXL +pref("image.jxl.enabled", false); +#endif + // Limit for the canvas image cache. 0 means we don't limit the size of the // cache. pref("canvas.image.cache.limit", 0); diff --git a/netwerk/mime/nsMimeTypes.h b/netwerk/mime/nsMimeTypes.h index 57a85e9bc6..7434db383c 100644 --- a/netwerk/mime/nsMimeTypes.h +++ b/netwerk/mime/nsMimeTypes.h @@ -117,6 +117,7 @@ #define IMAGE_JNG "image/x-jng" #define IMAGE_SVG_XML "image/svg+xml" #define IMAGE_WEBP "image/webp" +#define IMAGE_JXL "image/jxl" #define MESSAGE_EXTERNAL_BODY "message/external-body" #define MESSAGE_NEWS "message/news" diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index 7d5592f948..8c70622ced 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -503,6 +503,7 @@ static const nsExtraMimeTypeEntry extraMimeEntries[] = { IMAGE_XBM, "xbm", "XBM Image" }, { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" }, { IMAGE_WEBP, "webp", "WebP Image" }, + { IMAGE_JXL, "jxl", "JPEG XL Image File" }, { MESSAGE_RFC822, "eml", "RFC-822 data" }, { TEXT_PLAIN, "txt,text", "Text File" }, { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },