@@ -45,9 +45,20 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
45
45
Transition::TerminateSuccess()),
46
46
mDecoder(JxlDecoderMake(nullptr )),
47
47
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);
51
62
JxlDecoderSetParallelRunner (mDecoder .get (), JxlThreadParallelRunner,
52
63
mParallelRunner .get ());
53
64
@@ -58,6 +69,10 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
58
69
nsJXLDecoder::~nsJXLDecoder () {
59
70
MOZ_LOG (sJXLLog , LogLevel::Debug,
60
71
(" [this=%p] nsJXLDecoder::~nsJXLDecoder" , this ));
72
+
73
+ if (mCMSLine ) {
74
+ free (mCMSLine );
75
+ }
61
76
}
62
77
63
78
size_t nsJXLDecoder::PreferredThreadCount () {
@@ -86,14 +101,20 @@ LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator,
86
101
87
102
LexerTransition<nsJXLDecoder::State> nsJXLDecoder::ReadJXLData (
88
103
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));
95
116
}
96
- JXL_TRY ( JxlDecoderSetInput ( mDecoder . get (), input, length)) ;
117
+ mContinue = false ;
97
118
98
119
while (true ) {
99
120
JxlDecoderStatus status = JxlDecoderProcessInput (mDecoder .get ());
@@ -106,49 +127,226 @@ LexerTransition<nsJXLDecoder::State> nsJXLDecoder::ReadJXLData(
106
127
size_t remaining = JxlDecoderReleaseInput (mDecoder .get ());
107
128
mBuffer .clear ();
108
129
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
+
109
158
return Transition::ContinueUnbuffered (State::JXL_DATA);
110
159
}
111
160
112
161
case JXL_DEC_BASIC_INFO: {
113
162
JXL_TRY (JxlDecoderGetBasicInfo (mDecoder .get (), &mInfo ));
114
163
PostSize (mInfo .xsize , mInfo .ysize );
164
+
115
165
if (mInfo .alpha_bits > 0 ) {
166
+ mSurfaceFormat = SurfaceFormat::OS_RGBA;
116
167
PostHasTransparency ();
117
168
}
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
+
118
259
if (IsMetadataDecode ()) {
119
260
return Transition::TerminateSuccess ();
120
261
}
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
+
121
298
break ;
122
299
}
123
300
124
301
case JXL_DEC_NEED_IMAGE_OUT_BUFFER: {
125
302
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));
128
304
129
305
mOutBuffer .clear ();
130
306
JXL_TRY_BOOL (mOutBuffer .growBy (size));
131
- JXL_TRY (JxlDecoderSetImageOutBuffer (mDecoder .get (), &format ,
307
+ JXL_TRY (JxlDecoderSetImageOutBuffer (mDecoder .get (), &mFormat ,
132
308
mOutBuffer .begin (), size));
133
309
break ;
134
310
}
135
311
136
312
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 ();
141
314
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));
144
324
}
145
325
146
- if (Maybe<SurfaceInvalidRect> invalidRect = pipe -> TakeInvalidRect ()) {
326
+ if (Maybe<SurfaceInvalidRect> invalidRect = mPipe . TakeInvalidRect ()) {
147
327
PostInvalidation (invalidRect->mInputSpaceRect ,
148
328
Some (invalidRect->mOutputSpaceRect ));
149
329
}
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 );
152
350
return Transition::TerminateSuccess ();
153
351
}
154
352
}
0 commit comments