From d95cafb4e467fae16f3fa8a42cc0daacfc326c59 Mon Sep 17 00:00:00 2001 From: Demez Date: Mon, 13 Feb 2023 23:07:15 -0500 Subject: [PATCH] JPEG-XL: Enabled by default, Support for Animation, Alpha, Progressive Decode, and Color Profiles --- assets/patches.txt | 2 + patches/JXL_enable_by_default.patch | 28 +++ patches/JXL_improved_support.patch | 375 ++++++++++++++++++++++++++++ 3 files changed, 405 insertions(+) create mode 100644 patches/JXL_enable_by_default.patch create mode 100644 patches/JXL_improved_support.patch diff --git a/assets/patches.txt b/assets/patches.txt index f6d9213..ce96396 100644 --- a/assets/patches.txt +++ b/assets/patches.txt @@ -6,6 +6,8 @@ patches/custom-ubo-assets-bootstrap-location.patch patches/disable-data-reporting-at-compile-time.patch patches/faster-package-multi-locale.patch patches/hide-passwordmgr.patch +patches/JXL_enable_by_default.patch +patches/JXL_improved_support.patch patches/librewolf-pref-pane.patch patches/librewolf-prefs.patch patches/mozilla_dirs.patch diff --git a/patches/JXL_enable_by_default.patch b/patches/JXL_enable_by_default.patch new file mode 100644 index 0000000..c5af534 --- /dev/null +++ b/patches/JXL_enable_by_default.patch @@ -0,0 +1,28 @@ +From 90e57f71621084111baa6098fc637b76e106114a Mon Sep 17 00:00:00 2001 +From: Demez +Date: Mon, 13 Feb 2023 18:07:23 -0500 +Subject: [PATCH] enable + +--- + modules/libpref/init/StaticPrefList.yaml | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml +index fca74af..4cc6d5d 100644 +--- a/modules/libpref/init/StaticPrefList.yaml ++++ b/modules/libpref/init/StaticPrefList.yaml +@@ -6975,7 +6975,11 @@ + # Whether we attempt to decode JXL images or not. + - name: image.jxl.enabled + type: RelaxedAtomicBool ++#if defined(MOZ_JXL) ++ value: true ++#else + value: false ++#endif + mirror: always + + #--------------------------------------------------------------------------- +-- +2.34.1.windows.1 + diff --git a/patches/JXL_improved_support.patch b/patches/JXL_improved_support.patch new file mode 100644 index 0000000..f66202c --- /dev/null +++ b/patches/JXL_improved_support.patch @@ -0,0 +1,375 @@ +From 771ed27676a39cb82deb2ed2548e9180415e815b Mon Sep 17 00:00:00 2001 +From: Demez +Date: Wed, 21 Dec 2022 19:22:37 -0500 +Subject: [PATCH] JPEG-XL: Support for Animation, Alpha, Progressive Decode, + and Color Profiles + +--- + image/DecoderFactory.cpp | 14 +- + image/decoders/nsJXLDecoder.cpp | 242 +++++++++++++++++++++++++++++--- + image/decoders/nsJXLDecoder.h | 14 +- + 3 files changed, 245 insertions(+), 25 deletions(-) + +diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp +index 5004b93eae09c..2f214a9c80a46 100644 +--- a/image/DecoderFactory.cpp ++++ b/image/DecoderFactory.cpp +@@ -217,7 +217,12 @@ nsresult DecoderFactory::CreateAnimationDecoder( + } + + MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG || +- aType == DecoderType::WEBP, ++ aType == DecoderType::WEBP ++#ifdef MOZ_JXL ++ || aType == DecoderType::JXL, ++#else ++ , ++#endif + "Calling CreateAnimationDecoder for non-animating DecoderType"); + + // Create an anonymous decoder. Interaction with the SurfaceCache and the +@@ -272,7 +277,12 @@ already_AddRefed DecoderFactory::CloneAnimationDecoder( + // rediscover it is animated). + DecoderType type = aDecoder->GetType(); + MOZ_ASSERT(type == DecoderType::GIF || type == DecoderType::PNG || +- type == DecoderType::WEBP, ++ type == DecoderType::WEBP ++#ifdef MOZ_JXL ++ || type == DecoderType::JXL, ++#else ++ , ++#endif + "Calling CloneAnimationDecoder for non-animating DecoderType"); + + RefPtr decoder = GetDecoder(type, nullptr, /* aIsRedecode = */ true); +diff --git a/image/decoders/nsJXLDecoder.cpp b/image/decoders/nsJXLDecoder.cpp +index 145fd9454340a..4311a7d3b7c28 100644 +--- a/image/decoders/nsJXLDecoder.cpp ++++ b/image/decoders/nsJXLDecoder.cpp +@@ -45,9 +45,20 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage) + Transition::TerminateSuccess()), + mDecoder(JxlDecoderMake(nullptr)), + mParallelRunner( +- JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())) { +- JxlDecoderSubscribeEvents(mDecoder.get(), +- JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); ++ JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())), ++ mUsePipeTransform(true), ++ mCMSLine(nullptr), ++ mNumFrames(0), ++ mTimeout(FrameTimeout::Forever()), ++ mSurfaceFormat(SurfaceFormat::OS_RGBX), ++ mContinue(false) { ++ int events = JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE; ++ ++ if (mCMSMode != CMSMode::Off) { ++ events |= JXL_DEC_COLOR_ENCODING; ++ } ++ ++ JxlDecoderSubscribeEvents(mDecoder.get(), events); + JxlDecoderSetParallelRunner(mDecoder.get(), JxlThreadParallelRunner, + mParallelRunner.get()); + +@@ -58,6 +69,10 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage) + nsJXLDecoder::~nsJXLDecoder() { + MOZ_LOG(sJXLLog, LogLevel::Debug, + ("[this=%p] nsJXLDecoder::~nsJXLDecoder", this)); ++ ++ if (mCMSLine) { ++ free(mCMSLine); ++ } + } + + size_t nsJXLDecoder::PreferredThreadCount() { +@@ -86,14 +101,20 @@ LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator, + + 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(); ++ // Ignore data we have already read. ++ // This will only occur as a result of a yield for animation. ++ if (!mContinue) { ++ 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)); + } +- JXL_TRY(JxlDecoderSetInput(mDecoder.get(), input, length)); ++ mContinue = false; + + while (true) { + JxlDecoderStatus status = JxlDecoderProcessInput(mDecoder.get()); +@@ -106,49 +127,226 @@ LexerTransition nsJXLDecoder::ReadJXLData( + size_t remaining = JxlDecoderReleaseInput(mDecoder.get()); + mBuffer.clear(); + JXL_TRY_BOOL(mBuffer.append(aData + aLength - remaining, remaining)); ++ ++ if (mNumFrames == 0 && InFrame()) { ++ // If an image was flushed by JxlDecoderFlushImage, then we know that ++ // JXL_DEC_FRAME has already been run and there is a pipe. ++ if (JxlDecoderFlushImage(mDecoder.get()) == JXL_DEC_SUCCESS) { ++ // A full frame partial image is written to the buffer. ++ mPipe.ResetToFirstRow(); ++ for (uint8_t* rowPtr = mOutBuffer.begin(); ++ rowPtr < mOutBuffer.end(); rowPtr += mInfo.xsize * mChannels) { ++ uint8_t* rowToWrite = rowPtr; ++ ++ if (!mUsePipeTransform && mTransform) { ++ qcms_transform_data(mTransform, rowToWrite, mCMSLine, ++ mInfo.xsize); ++ rowToWrite = mCMSLine; ++ } ++ ++ mPipe.WriteBuffer(reinterpret_cast(rowToWrite)); ++ } ++ ++ if (Maybe invalidRect = ++ mPipe.TakeInvalidRect()) { ++ PostInvalidation(invalidRect->mInputSpaceRect, ++ Some(invalidRect->mOutputSpaceRect)); ++ } ++ } ++ } ++ + 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) { ++ mSurfaceFormat = SurfaceFormat::OS_RGBA; + PostHasTransparency(); + } ++ ++ if (!mInfo.have_animation && IsMetadataDecode()) { ++ return Transition::TerminateSuccess(); ++ } ++ ++ // If CMS is off or the image is RGB, always output in RGBA. ++ // If the image is grayscale, then the pipe transform can't be used. ++ if (mCMSMode != CMSMode::Off) { ++ mChannels = mInfo.num_color_channels == 1 ++ ? 1 + (mInfo.alpha_bits > 0 ? 1 : 0) ++ : 4; ++ } else { ++ mChannels = 4; ++ } ++ ++ mFormat = {mChannels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; ++ ++ break; ++ } ++ ++ case JXL_DEC_COLOR_ENCODING: { ++ size_t size = 0; ++ JXL_TRY(JxlDecoderGetICCProfileSize( ++ mDecoder.get(), &mFormat, JXL_COLOR_PROFILE_TARGET_DATA, &size)) ++ std::vector icc_profile(size); ++ JXL_TRY(JxlDecoderGetColorAsICCProfile(mDecoder.get(), &mFormat, ++ JXL_COLOR_PROFILE_TARGET_DATA, ++ icc_profile.data(), size)) ++ ++ mInProfile = qcms_profile_from_memory((char*)icc_profile.data(), size); ++ ++ uint32_t profileSpace = qcms_profile_get_color_space(mInProfile); ++ ++ // Skip color management if color profile is not compatible with number ++ // of channels. ++ if (profileSpace != icSigRgbData && ++ (mInfo.num_color_channels == 3 || profileSpace != icSigGrayData)) { ++ break; ++ } ++ ++ mUsePipeTransform = ++ profileSpace == icSigRgbData && mInfo.num_color_channels == 3; ++ ++ qcms_data_type inType; ++ if (mInfo.num_color_channels == 3) { ++ inType = QCMS_DATA_RGBA_8; ++ } else if (mInfo.alpha_bits > 0) { ++ inType = QCMS_DATA_GRAYA_8; ++ } else { ++ inType = QCMS_DATA_GRAY_8; ++ } ++ ++ if (!mUsePipeTransform) { ++ mCMSLine = ++ static_cast(malloc(sizeof(uint32_t) * mInfo.xsize)); ++ } ++ ++ int intent = gfxPlatform::GetRenderingIntent(); ++ if (intent == -1) { ++ intent = qcms_profile_get_rendering_intent(mInProfile); ++ } ++ ++ mTransform = ++ qcms_transform_create(mInProfile, inType, GetCMSOutputProfile(), ++ QCMS_DATA_RGBA_8, (qcms_intent)intent); ++ ++ break; ++ } ++ ++ case JXL_DEC_FRAME: { ++ if (mInfo.have_animation) { ++ JXL_TRY(JxlDecoderGetFrameHeader(mDecoder.get(), &mFrameHeader)); ++ int32_t duration = (int32_t)(1000.0 * mFrameHeader.duration * ++ mInfo.animation.tps_denominator / ++ mInfo.animation.tps_numerator); ++ ++ mTimeout = FrameTimeout::FromRawMilliseconds(duration); ++ ++ if (!HasAnimation()) { ++ PostIsAnimated(mTimeout); ++ } ++ } ++ ++ bool is_last = mInfo.have_animation ? mFrameHeader.is_last : true; ++ MOZ_LOG(sJXLLog, LogLevel::Debug, ++ ("[this=%p] nsJXLDecoder::ReadJXLData - frame %d, is_last %d, " ++ "metadata decode %d, first frame decode %d\n", ++ this, mNumFrames, is_last, IsMetadataDecode(), ++ IsFirstFrameDecode())); ++ + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } ++ ++ OrientedIntSize size(mInfo.xsize, mInfo.ysize); ++ ++ Maybe animParams; ++ if (!IsFirstFrameDecode()) { ++ animParams.emplace(FullFrame().ToUnknownRect(), mTimeout, mNumFrames, ++ BlendMethod::SOURCE, DisposalMethod::CLEAR); ++ } ++ ++ SurfacePipeFlags pipeFlags = SurfacePipeFlags(); ++ ++ if (mNumFrames == 0) { ++ // The first frame may be displayed progressively. ++ pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY; ++ } ++ ++ if (mSurfaceFormat == SurfaceFormat::OS_RGBA && ++ !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) { ++ pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA; ++ } ++ ++ qcms_transform* pipeTransform = ++ mUsePipeTransform ? mTransform : nullptr; ++ ++ Maybe pipe = SurfacePipeFactory::CreateSurfacePipe( ++ this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8, ++ mSurfaceFormat, animParams, pipeTransform, pipeFlags); ++ ++ if (!pipe) { ++ MOZ_LOG(sJXLLog, LogLevel::Debug, ++ ("[this=%p] nsJXLDecoder::ReadJXLData - no pipe\n", this)); ++ return Transition::TerminateFailure(); ++ } ++ ++ mPipe = std::move(*pipe); ++ + 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)); ++ JXL_TRY(JxlDecoderImageOutBufferSize(mDecoder.get(), &mFormat, &size)); + + mOutBuffer.clear(); + JXL_TRY_BOOL(mOutBuffer.growBy(size)); +- JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &format, ++ JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &mFormat, + 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()); ++ mPipe.ResetToFirstRow(); + for (uint8_t* rowPtr = mOutBuffer.begin(); rowPtr < mOutBuffer.end(); +- rowPtr += mInfo.xsize * 4) { +- pipe->WriteBuffer(reinterpret_cast(rowPtr)); ++ rowPtr += mInfo.xsize * mChannels) { ++ uint8_t* rowToWrite = rowPtr; ++ ++ if (!mUsePipeTransform && mTransform) { ++ qcms_transform_data(mTransform, rowToWrite, mCMSLine, mInfo.xsize); ++ rowToWrite = mCMSLine; ++ } ++ ++ mPipe.WriteBuffer(reinterpret_cast(rowToWrite)); + } + +- if (Maybe invalidRect = pipe->TakeInvalidRect()) { ++ if (Maybe invalidRect = mPipe.TakeInvalidRect()) { + PostInvalidation(invalidRect->mInputSpaceRect, + Some(invalidRect->mOutputSpaceRect)); + } +- PostFrameStop(); +- PostDecodeDone(); ++ ++ Opacity opacity = mSurfaceFormat == SurfaceFormat::OS_RGBA ++ ? Opacity::SOME_TRANSPARENCY ++ : Opacity::FULLY_OPAQUE; ++ PostFrameStop(opacity); ++ ++ if (!IsFirstFrameDecode() && mInfo.have_animation && ++ !mFrameHeader.is_last) { ++ mNumFrames++; ++ mContinue = true; ++ // Notify for a new frame but there may be data in the current buffer ++ // that can immediately be processed. ++ return Transition::ToAfterYield(State::JXL_DATA); ++ } ++ [[fallthrough]]; // We are done. ++ } ++ ++ case JXL_DEC_SUCCESS: { ++ PostDecodeDone(HasAnimation() ? (int32_t)mInfo.animation.num_loops - 1 ++ : 0); + return Transition::TerminateSuccess(); + } + } +diff --git a/image/decoders/nsJXLDecoder.h b/image/decoders/nsJXLDecoder.h +index 6cde7456ca03f..6dc162f9397b0 100644 +--- a/image/decoders/nsJXLDecoder.h ++++ b/image/decoders/nsJXLDecoder.h +@@ -47,7 +47,19 @@ class nsJXLDecoder final : public Decoder { + JxlThreadParallelRunnerPtr mParallelRunner; + Vector mBuffer; + Vector mOutBuffer; +- JxlBasicInfo mInfo{}; ++ JxlBasicInfo mInfo; ++ JxlPixelFormat mFormat; ++ JxlFrameHeader mFrameHeader; ++ ++ bool mUsePipeTransform; ++ uint8_t mChannels; ++ uint8_t* mCMSLine; ++ ++ uint32_t mNumFrames; ++ FrameTimeout mTimeout; ++ gfx::SurfaceFormat mSurfaceFormat; ++ SurfacePipe mPipe; ++ bool mContinue; + }; + + } // namespace mozilla::image