-
-
Notifications
You must be signed in to change notification settings - Fork 659
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a more complete JXL features
Based on this following phabricator patch/commits: - https://phabricator.services.mozilla.com/D119700 - https://phabricator.services.mozilla.com/D122158 - https://phabricator.services.mozilla.com/D122159 Which add the following: - Proper color profiles support for JPEG XL - Animated JPEG XL - Progressive decoding Co-authored-by: wwwwwwww <[email protected]>
- Loading branch information
Showing
3 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp | ||
index f36f03c7f2..d2cdd79f70 100644 | ||
--- a/image/DecoderFactory.cpp | ||
+++ b/image/DecoderFactory.cpp | ||
@@ -244,7 +244,12 @@ nsresult DecoderFactory::CreateAnimationDecoder( | ||
} | ||
|
||
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG || | ||
- aType == DecoderType::WEBP || aType == DecoderType::AVIF, | ||
+ aType == DecoderType::WEBP || aType == DecoderType::AVIF | ||
+#ifdef MOZ_JXL | ||
+ || aType == DecoderType::JXL, | ||
+#else | ||
+ , | ||
+#endif | ||
"Calling CreateAnimationDecoder for non-animating DecoderType"); | ||
|
||
// Create an anonymous decoder. Interaction with the SurfaceCache and the | ||
@@ -299,7 +304,12 @@ already_AddRefed<Decoder> DecoderFactory::CloneAnimationDecoder( | ||
// rediscover it is animated). | ||
DecoderType type = aDecoder->GetType(); | ||
MOZ_ASSERT(type == DecoderType::GIF || type == DecoderType::PNG || | ||
- type == DecoderType::WEBP || type == DecoderType::AVIF, | ||
+ type == DecoderType::WEBP || type == DecoderType::AVIF | ||
+#ifdef MOZ_JXL | ||
+ || aType == DecoderType::JXL, | ||
+#else | ||
+ , | ||
+#endif | ||
"Calling CloneAnimationDecoder for non-animating DecoderType"); | ||
|
||
RefPtr<Decoder> decoder = GetDecoder(type, nullptr, /* aIsRedecode = */ true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
diff --git a/image/decoders/nsJXLDecoder.cpp b/image/decoders/nsJXLDecoder.cpp | ||
index ffb7f3cd51e1d0e480bf8ac5e936a90c11f0c93d..282472afe4fbab7d41adaf15928fa93c99e74452 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::State> 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,51 +127,229 @@ LexerTransition<nsJXLDecoder::State> 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<uint32_t*>(rowToWrite)); | ||
+ } | ||
+ | ||
+ if (Maybe<SurfaceInvalidRect> 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 (WantsFrameCount()) { | ||
PostFrameCount(/* aFrameCount */ 1); | ||
} | ||
+ | ||
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(), JXL_COLOR_PROFILE_TARGET_DATA, &size)) | ||
+ std::vector<uint8_t> icc_profile(size); | ||
+ JXL_TRY(JxlDecoderGetColorAsICCProfile(mDecoder.get(), | ||
+ 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<uint8_t*>(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<AnimationParams> 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<SurfacePipe> 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<SurfacePipe> 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<uint32_t*>(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<uint32_t*>(rowToWrite)); | ||
} | ||
|
||
- if (Maybe<SurfaceInvalidRect> invalidRect = pipe->TakeInvalidRect()) { | ||
+ if (Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect()) { | ||
PostInvalidation(invalidRect->mInputSpaceRect, | ||
Some(invalidRect->mOutputSpaceRect)); | ||
} | ||
- PostFrameStop(); | ||
+ | ||
+ 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: { | ||
+ PostLoopCount(HasAnimation() ? (int32_t)mInfo.animation.num_loops - 1 | ||
+ : 0); | ||
PostDecodeDone(); | ||
return Transition::TerminateSuccess(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
diff --git a/image/decoders/nsJXLDecoder.h b/image/decoders/nsJXLDecoder.h | ||
index 6cde7456ca03f79e74401c1d215b9d50453ebf41..2f593ca3b70100c600b86e753d7a458c83b4f15c 100644 | ||
--- a/image/decoders/nsJXLDecoder.h | ||
+++ b/image/decoders/nsJXLDecoder.h | ||
@@ -48,6 +48,18 @@ class nsJXLDecoder final : public Decoder { | ||
Vector<uint8_t> mBuffer; | ||
Vector<uint8_t> mOutBuffer; | ||
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 |