Skip to content

Commit 6df4047

Browse files
committed
JXL_improved_support.patch from Librewolf
1 parent ada3433 commit 6df4047

File tree

3 files changed

+246
-25
lines changed

3 files changed

+246
-25
lines changed

image/DecoderFactory.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,12 @@ nsresult DecoderFactory::CreateAnimationDecoder(
234234
}
235235

236236
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG ||
237-
aType == DecoderType::WEBP || aType == DecoderType::AVIF,
237+
aType == DecoderType::WEBP || aType == DecoderType::AVIF
238+
#ifdef MOZ_JXL
239+
|| aType == DecoderType::JXL,
240+
#else
241+
,
242+
#endif
238243
"Calling CreateAnimationDecoder for non-animating DecoderType");
239244

240245
// Create an anonymous decoder. Interaction with the SurfaceCache and the
@@ -289,7 +294,13 @@ already_AddRefed<Decoder> DecoderFactory::CloneAnimationDecoder(
289294
// rediscover it is animated).
290295
DecoderType type = aDecoder->GetType();
291296
MOZ_ASSERT(type == DecoderType::GIF || type == DecoderType::PNG ||
292-
type == DecoderType::WEBP || type == DecoderType::AVIF,
297+
type == DecoderType::WEBP || type == DecoderType::AVIF
298+
#ifdef MOZ_JXL
299+
|| aType == DecoderType::JXL,
300+
#else
301+
,
302+
#endif
303+
293304
"Calling CloneAnimationDecoder for non-animating DecoderType");
294305

295306
RefPtr<Decoder> decoder = GetDecoder(type, nullptr, /* aIsRedecode = */ true);

image/decoders/nsJXLDecoder.cpp

Lines changed: 220 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,20 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
4545
Transition::TerminateSuccess()),
4646
mDecoder(JxlDecoderMake(nullptr)),
4747
mParallelRunner(
48-
JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())) {
49-
JxlDecoderSubscribeEvents(mDecoder.get(),
50-
JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
48+
JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())),
49+
mUsePipeTransform(true),
50+
mCMSLine(nullptr),
51+
mNumFrames(0),
52+
mTimeout(FrameTimeout::Forever()),
53+
mSurfaceFormat(SurfaceFormat::OS_RGBX),
54+
mContinue(false) {
55+
int events = JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE;
56+
57+
if (mCMSMode != CMSMode::Off) {
58+
events |= JXL_DEC_COLOR_ENCODING;
59+
}
60+
61+
JxlDecoderSubscribeEvents(mDecoder.get(), events);
5162
JxlDecoderSetParallelRunner(mDecoder.get(), JxlThreadParallelRunner,
5263
mParallelRunner.get());
5364

@@ -58,6 +69,10 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
5869
nsJXLDecoder::~nsJXLDecoder() {
5970
MOZ_LOG(sJXLLog, LogLevel::Debug,
6071
("[this=%p] nsJXLDecoder::~nsJXLDecoder", this));
72+
73+
if (mCMSLine) {
74+
free(mCMSLine);
75+
}
6176
}
6277

6378
size_t nsJXLDecoder::PreferredThreadCount() {
@@ -86,14 +101,20 @@ LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator,
86101

87102
LexerTransition<nsJXLDecoder::State> nsJXLDecoder::ReadJXLData(
88103
const char* aData, size_t aLength) {
89-
const uint8_t* input = (const uint8_t*)aData;
90-
size_t length = aLength;
91-
if (mBuffer.length() != 0) {
92-
JXL_TRY_BOOL(mBuffer.append(aData, aLength));
93-
input = mBuffer.begin();
94-
length = mBuffer.length();
104+
// Ignore data we have already read.
105+
// This will only occur as a result of a yield for animation.
106+
if (!mContinue) {
107+
const uint8_t* input = (const uint8_t*)aData;
108+
size_t length = aLength;
109+
if (mBuffer.length() != 0) {
110+
JXL_TRY_BOOL(mBuffer.append(aData, aLength));
111+
input = mBuffer.begin();
112+
length = mBuffer.length();
113+
}
114+
115+
JXL_TRY(JxlDecoderSetInput(mDecoder.get(), input, length));
95116
}
96-
JXL_TRY(JxlDecoderSetInput(mDecoder.get(), input, length));
117+
mContinue = false;
97118

98119
while (true) {
99120
JxlDecoderStatus status = JxlDecoderProcessInput(mDecoder.get());
@@ -106,49 +127,226 @@ LexerTransition<nsJXLDecoder::State> nsJXLDecoder::ReadJXLData(
106127
size_t remaining = JxlDecoderReleaseInput(mDecoder.get());
107128
mBuffer.clear();
108129
JXL_TRY_BOOL(mBuffer.append(aData + aLength - remaining, remaining));
130+
131+
if (mNumFrames == 0 && InFrame()) {
132+
// If an image was flushed by JxlDecoderFlushImage, then we know that
133+
// JXL_DEC_FRAME has already been run and there is a pipe.
134+
if (JxlDecoderFlushImage(mDecoder.get()) == JXL_DEC_SUCCESS) {
135+
// A full frame partial image is written to the buffer.
136+
mPipe.ResetToFirstRow();
137+
for (uint8_t* rowPtr = mOutBuffer.begin();
138+
rowPtr < mOutBuffer.end(); rowPtr += mInfo.xsize * mChannels) {
139+
uint8_t* rowToWrite = rowPtr;
140+
141+
if (!mUsePipeTransform && mTransform) {
142+
qcms_transform_data(mTransform, rowToWrite, mCMSLine,
143+
mInfo.xsize);
144+
rowToWrite = mCMSLine;
145+
}
146+
147+
mPipe.WriteBuffer(reinterpret_cast<uint32_t*>(rowToWrite));
148+
}
149+
150+
if (Maybe<SurfaceInvalidRect> invalidRect =
151+
mPipe.TakeInvalidRect()) {
152+
PostInvalidation(invalidRect->mInputSpaceRect,
153+
Some(invalidRect->mOutputSpaceRect));
154+
}
155+
}
156+
}
157+
109158
return Transition::ContinueUnbuffered(State::JXL_DATA);
110159
}
111160

112161
case JXL_DEC_BASIC_INFO: {
113162
JXL_TRY(JxlDecoderGetBasicInfo(mDecoder.get(), &mInfo));
114163
PostSize(mInfo.xsize, mInfo.ysize);
164+
115165
if (mInfo.alpha_bits > 0) {
166+
mSurfaceFormat = SurfaceFormat::OS_RGBA;
116167
PostHasTransparency();
117168
}
169+
170+
if (!mInfo.have_animation && IsMetadataDecode()) {
171+
return Transition::TerminateSuccess();
172+
}
173+
174+
// If CMS is off or the image is RGB, always output in RGBA.
175+
// If the image is grayscale, then the pipe transform can't be used.
176+
if (mCMSMode != CMSMode::Off) {
177+
mChannels = mInfo.num_color_channels == 1
178+
? 1 + (mInfo.alpha_bits > 0 ? 1 : 0)
179+
: 4;
180+
} else {
181+
mChannels = 4;
182+
}
183+
184+
mFormat = {mChannels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
185+
186+
break;
187+
}
188+
189+
case JXL_DEC_COLOR_ENCODING: {
190+
size_t size = 0;
191+
JXL_TRY(JxlDecoderGetICCProfileSize(
192+
mDecoder.get(), JXL_COLOR_PROFILE_TARGET_DATA, &size))
193+
std::vector<uint8_t> icc_profile(size);
194+
JXL_TRY(JxlDecoderGetColorAsICCProfile(mDecoder.get(),
195+
JXL_COLOR_PROFILE_TARGET_DATA,
196+
icc_profile.data(), size))
197+
198+
mInProfile = qcms_profile_from_memory((char*)icc_profile.data(), size);
199+
200+
uint32_t profileSpace = qcms_profile_get_color_space(mInProfile);
201+
202+
// Skip color management if color profile is not compatible with number
203+
// of channels.
204+
if (profileSpace != icSigRgbData &&
205+
(mInfo.num_color_channels == 3 || profileSpace != icSigGrayData)) {
206+
break;
207+
}
208+
209+
mUsePipeTransform =
210+
profileSpace == icSigRgbData && mInfo.num_color_channels == 3;
211+
212+
qcms_data_type inType;
213+
if (mInfo.num_color_channels == 3) {
214+
inType = QCMS_DATA_RGBA_8;
215+
} else if (mInfo.alpha_bits > 0) {
216+
inType = QCMS_DATA_GRAYA_8;
217+
} else {
218+
inType = QCMS_DATA_GRAY_8;
219+
}
220+
221+
if (!mUsePipeTransform) {
222+
mCMSLine =
223+
static_cast<uint8_t*>(malloc(sizeof(uint32_t) * mInfo.xsize));
224+
}
225+
226+
int intent = gfxPlatform::GetRenderingIntent();
227+
if (intent == -1) {
228+
intent = qcms_profile_get_rendering_intent(mInProfile);
229+
}
230+
231+
mTransform =
232+
qcms_transform_create(mInProfile, inType, GetCMSOutputProfile(),
233+
QCMS_DATA_RGBA_8, (qcms_intent)intent);
234+
235+
break;
236+
}
237+
238+
case JXL_DEC_FRAME: {
239+
if (mInfo.have_animation) {
240+
JXL_TRY(JxlDecoderGetFrameHeader(mDecoder.get(), &mFrameHeader));
241+
int32_t duration = (int32_t)(1000.0 * mFrameHeader.duration *
242+
mInfo.animation.tps_denominator /
243+
mInfo.animation.tps_numerator);
244+
245+
mTimeout = FrameTimeout::FromRawMilliseconds(duration);
246+
247+
if (!HasAnimation()) {
248+
PostIsAnimated(mTimeout);
249+
}
250+
}
251+
252+
bool is_last = mInfo.have_animation ? mFrameHeader.is_last : true;
253+
MOZ_LOG(sJXLLog, LogLevel::Debug,
254+
("[this=%p] nsJXLDecoder::ReadJXLData - frame %d, is_last %d, "
255+
"metadata decode %d, first frame decode %d\n",
256+
this, mNumFrames, is_last, IsMetadataDecode(),
257+
IsFirstFrameDecode()));
258+
118259
if (IsMetadataDecode()) {
119260
return Transition::TerminateSuccess();
120261
}
262+
263+
OrientedIntSize size(mInfo.xsize, mInfo.ysize);
264+
265+
Maybe<AnimationParams> animParams;
266+
if (!IsFirstFrameDecode()) {
267+
animParams.emplace(FullFrame().ToUnknownRect(), mTimeout, mNumFrames,
268+
BlendMethod::SOURCE, DisposalMethod::CLEAR);
269+
}
270+
271+
SurfacePipeFlags pipeFlags = SurfacePipeFlags();
272+
273+
if (mNumFrames == 0) {
274+
// The first frame may be displayed progressively.
275+
pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
276+
}
277+
278+
if (mSurfaceFormat == SurfaceFormat::OS_RGBA &&
279+
!(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) {
280+
pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA;
281+
}
282+
283+
qcms_transform* pipeTransform =
284+
mUsePipeTransform ? mTransform : nullptr;
285+
286+
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
287+
this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8,
288+
mSurfaceFormat, animParams, pipeTransform, pipeFlags);
289+
290+
if (!pipe) {
291+
MOZ_LOG(sJXLLog, LogLevel::Debug,
292+
("[this=%p] nsJXLDecoder::ReadJXLData - no pipe\n", this));
293+
return Transition::TerminateFailure();
294+
}
295+
296+
mPipe = std::move(*pipe);
297+
121298
break;
122299
}
123300

124301
case JXL_DEC_NEED_IMAGE_OUT_BUFFER: {
125302
size_t size = 0;
126-
JxlPixelFormat format{4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
127-
JXL_TRY(JxlDecoderImageOutBufferSize(mDecoder.get(), &format, &size));
303+
JXL_TRY(JxlDecoderImageOutBufferSize(mDecoder.get(), &mFormat, &size));
128304

129305
mOutBuffer.clear();
130306
JXL_TRY_BOOL(mOutBuffer.growBy(size));
131-
JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &format,
307+
JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &mFormat,
132308
mOutBuffer.begin(), size));
133309
break;
134310
}
135311

136312
case JXL_DEC_FULL_IMAGE: {
137-
OrientedIntSize size(mInfo.xsize, mInfo.ysize);
138-
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
139-
this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8,
140-
SurfaceFormat::OS_RGBA, Nothing(), nullptr, SurfacePipeFlags());
313+
mPipe.ResetToFirstRow();
141314
for (uint8_t* rowPtr = mOutBuffer.begin(); rowPtr < mOutBuffer.end();
142-
rowPtr += mInfo.xsize * 4) {
143-
pipe->WriteBuffer(reinterpret_cast<uint32_t*>(rowPtr));
315+
rowPtr += mInfo.xsize * mChannels) {
316+
uint8_t* rowToWrite = rowPtr;
317+
318+
if (!mUsePipeTransform && mTransform) {
319+
qcms_transform_data(mTransform, rowToWrite, mCMSLine, mInfo.xsize);
320+
rowToWrite = mCMSLine;
321+
}
322+
323+
mPipe.WriteBuffer(reinterpret_cast<uint32_t*>(rowToWrite));
144324
}
145325

146-
if (Maybe<SurfaceInvalidRect> invalidRect = pipe->TakeInvalidRect()) {
326+
if (Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect()) {
147327
PostInvalidation(invalidRect->mInputSpaceRect,
148328
Some(invalidRect->mOutputSpaceRect));
149329
}
150-
PostFrameStop();
151-
PostDecodeDone();
330+
331+
Opacity opacity = mSurfaceFormat == SurfaceFormat::OS_RGBA
332+
? Opacity::SOME_TRANSPARENCY
333+
: Opacity::FULLY_OPAQUE;
334+
PostFrameStop(opacity);
335+
336+
if (!IsFirstFrameDecode() && mInfo.have_animation &&
337+
!mFrameHeader.is_last) {
338+
mNumFrames++;
339+
mContinue = true;
340+
// Notify for a new frame but there may be data in the current buffer
341+
// that can immediately be processed.
342+
return Transition::ToAfterYield(State::JXL_DATA);
343+
}
344+
[[fallthrough]]; // We are done.
345+
}
346+
347+
case JXL_DEC_SUCCESS: {
348+
PostDecodeDone(HasAnimation() ? (int32_t)mInfo.animation.num_loops - 1
349+
: 0);
152350
return Transition::TerminateSuccess();
153351
}
154352
}

image/decoders/nsJXLDecoder.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,19 @@ class nsJXLDecoder final : public Decoder {
4747
JxlThreadParallelRunnerPtr mParallelRunner;
4848
Vector<uint8_t> mBuffer;
4949
Vector<uint8_t> mOutBuffer;
50-
JxlBasicInfo mInfo{};
50+
JxlBasicInfo mInfo;
51+
JxlPixelFormat mFormat;
52+
JxlFrameHeader mFrameHeader;
53+
54+
bool mUsePipeTransform;
55+
uint8_t mChannels;
56+
uint8_t* mCMSLine;
57+
58+
uint32_t mNumFrames;
59+
FrameTimeout mTimeout;
60+
gfx::SurfaceFormat mSurfaceFormat;
61+
SurfacePipe mPipe;
62+
bool mContinue;
5163
};
5264

5365
} // namespace mozilla::image

0 commit comments

Comments
 (0)