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 0000000000..da7c1c76f1 Binary files /dev/null and b/image/test/reftest/jxl/jxl-size-33x33.jxl differ diff --git a/image/test/reftest/jxl/jxl-size-33x33.png b/image/test/reftest/jxl/jxl-size-33x33.png new file mode 100644 index 0000000000..72ef7eb636 Binary files /dev/null and b/image/test/reftest/jxl/jxl-size-33x33.png differ 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" },