Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : *
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 : /*
7 : The Graphics Interchange Format(c) is the copyright property of CompuServe
8 : Incorporated. Only CompuServe Incorporated is authorized to define, redefine,
9 : enhance, alter, modify or change in any way the definition of the format.
10 :
11 : CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free
12 : license for the use of the Graphics Interchange Format(sm) in computer
13 : software; computer software utilizing GIF(sm) must acknowledge ownership of the
14 : Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in
15 : User and Technical Documentation. Computer software utilizing GIF, which is
16 : distributed or may be distributed without User or Technical Documentation must
17 : display to the screen or printer a message acknowledging ownership of the
18 : Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in
19 : this case, the acknowledgement may be displayed in an opening screen or leading
20 : banner, or a closing screen or trailing banner. A message such as the following
21 : may be used:
22 :
23 : "The Graphics Interchange Format(c) is the Copyright property of
24 : CompuServe Incorporated. GIF(sm) is a Service Mark property of
25 : CompuServe Incorporated."
26 :
27 : For further information, please contact :
28 :
29 : CompuServe Incorporated
30 : Graphics Technology Department
31 : 5000 Arlington Center Boulevard
32 : Columbus, Ohio 43220
33 : U. S. A.
34 :
35 : CompuServe Incorporated maintains a mailing list with all those individuals and
36 : organizations who wish to receive copies of this document when it is corrected
37 : or revised. This service is offered free of charge; please provide us with your
38 : mailing address.
39 : */
40 :
41 : #include "nsGIFDecoder2.h"
42 :
43 : #include <stddef.h>
44 :
45 : #include "imgFrame.h"
46 : #include "mozilla/EndianUtils.h"
47 : #include "nsIInputStream.h"
48 : #include "RasterImage.h"
49 : #include "SurfacePipeFactory.h"
50 :
51 : #include "gfxColor.h"
52 : #include "gfxPlatform.h"
53 : #include "qcms.h"
54 : #include <algorithm>
55 : #include "mozilla/Telemetry.h"
56 :
57 : using namespace mozilla::gfx;
58 :
59 : using std::max;
60 :
61 : namespace mozilla {
62 : namespace image {
63 :
64 : //////////////////////////////////////////////////////////////////////
65 : // GIF Decoder Implementation
66 :
67 : static const size_t GIF_HEADER_LEN = 6;
68 : static const size_t GIF_SCREEN_DESCRIPTOR_LEN = 7;
69 : static const size_t BLOCK_HEADER_LEN = 1;
70 : static const size_t SUB_BLOCK_HEADER_LEN = 1;
71 : static const size_t EXTENSION_HEADER_LEN = 2;
72 : static const size_t GRAPHIC_CONTROL_EXTENSION_LEN = 4;
73 : static const size_t APPLICATION_EXTENSION_LEN = 11;
74 : static const size_t IMAGE_DESCRIPTOR_LEN = 9;
75 :
76 : // Masks for reading color table information from packed fields in the screen
77 : // descriptor and image descriptor blocks.
78 : static const uint8_t PACKED_FIELDS_COLOR_TABLE_BIT = 0x80;
79 : static const uint8_t PACKED_FIELDS_INTERLACED_BIT = 0x40;
80 : static const uint8_t PACKED_FIELDS_TABLE_DEPTH_MASK = 0x07;
81 :
82 2 : nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage)
83 : : Decoder(aImage)
84 4 : , mLexer(Transition::To(State::GIF_HEADER, GIF_HEADER_LEN),
85 : Transition::TerminateSuccess())
86 : , mOldColor(0)
87 : , mCurrentFrameIndex(-1)
88 : , mColorTablePos(0)
89 : , mGIFOpen(false)
90 6 : , mSawTransparency(false)
91 : {
92 : // Clear out the structure, excluding the arrays.
93 2 : memset(&mGIFStruct, 0, sizeof(mGIFStruct));
94 2 : }
95 :
96 6 : nsGIFDecoder2::~nsGIFDecoder2()
97 : {
98 2 : free(mGIFStruct.local_colormap);
99 6 : }
100 :
101 : nsresult
102 4 : nsGIFDecoder2::FinishInternal()
103 : {
104 4 : MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
105 :
106 : // If the GIF got cut off, handle it anyway
107 4 : if (!IsMetadataDecode() && mGIFOpen) {
108 1 : if (mCurrentFrameIndex == mGIFStruct.images_decoded) {
109 0 : EndImageFrame();
110 : }
111 1 : PostDecodeDone(mGIFStruct.loop_count);
112 1 : mGIFOpen = false;
113 : }
114 :
115 4 : return NS_OK;
116 : }
117 :
118 : void
119 1 : nsGIFDecoder2::FlushImageData()
120 : {
121 2 : Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
122 1 : if (!invalidRect) {
123 0 : return;
124 : }
125 :
126 1 : PostInvalidation(invalidRect->mInputSpaceRect,
127 2 : Some(invalidRect->mOutputSpaceRect));
128 : }
129 :
130 : //******************************************************************************
131 : // GIF decoder callback methods. Part of public API for GIF2
132 : //******************************************************************************
133 :
134 : //******************************************************************************
135 : void
136 2 : nsGIFDecoder2::BeginGIF()
137 : {
138 2 : if (mGIFOpen) {
139 0 : return;
140 : }
141 :
142 2 : mGIFOpen = true;
143 :
144 2 : PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
145 : }
146 :
147 : bool
148 2 : nsGIFDecoder2::CheckForTransparency(const IntRect& aFrameRect)
149 : {
150 : // Check if the image has a transparent color in its palette.
151 2 : if (mGIFStruct.is_transparent) {
152 2 : PostHasTransparency();
153 2 : return true;
154 : }
155 :
156 0 : if (mGIFStruct.images_decoded > 0) {
157 0 : return false; // We only care about first frame padding below.
158 : }
159 :
160 : // If we need padding on the first frame, that means we don't draw into part
161 : // of the image at all. Report that as transparency.
162 0 : IntRect imageRect(0, 0, mGIFStruct.screen_width, mGIFStruct.screen_height);
163 0 : if (!imageRect.IsEqualEdges(aFrameRect)) {
164 0 : PostHasTransparency();
165 0 : mSawTransparency = true; // Make sure we don't optimize it away.
166 0 : return true;
167 : }
168 :
169 0 : return false;
170 : }
171 :
172 : //******************************************************************************
173 : nsresult
174 1 : nsGIFDecoder2::BeginImageFrame(const IntRect& aFrameRect,
175 : uint16_t aDepth,
176 : bool aIsInterlaced)
177 : {
178 1 : MOZ_ASSERT(HasSize());
179 :
180 1 : bool hasTransparency = CheckForTransparency(aFrameRect);
181 :
182 : // Make sure there's no animation if we're downscaling.
183 1 : MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
184 :
185 : SurfacePipeFlags pipeFlags = aIsInterlaced
186 1 : ? SurfacePipeFlags::DEINTERLACE
187 1 : : SurfacePipeFlags();
188 :
189 2 : Maybe<SurfacePipe> pipe;
190 1 : if (mGIFStruct.images_decoded == 0) {
191 1 : gfx::SurfaceFormat format = hasTransparency ? SurfaceFormat::B8G8R8A8
192 1 : : SurfaceFormat::B8G8R8X8;
193 :
194 : // The first frame may be displayed progressively.
195 1 : pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
196 :
197 : // The first frame is always decoded into an RGB surface.
198 : pipe =
199 3 : SurfacePipeFactory::CreateSurfacePipe(this, mGIFStruct.images_decoded,
200 2 : Size(), OutputSize(),
201 1 : aFrameRect, format, pipeFlags);
202 : } else {
203 : // This is an animation frame (and not the first). To minimize the memory
204 : // usage of animations, the image data is stored in paletted form.
205 : //
206 : // We should never use paletted surfaces with a draw target directly, so
207 : // the only practical difference between B8G8R8A8 and B8G8R8X8 is the
208 : // cleared pixel value if we get truncated. We want 0 in that case to
209 : // ensure it is an acceptable value for the color map as was the case
210 : // historically.
211 0 : MOZ_ASSERT(Size() == OutputSize());
212 : pipe =
213 0 : SurfacePipeFactory::CreatePalettedSurfacePipe(this, mGIFStruct.images_decoded,
214 0 : Size(), aFrameRect,
215 : SurfaceFormat::B8G8R8A8,
216 0 : aDepth, pipeFlags);
217 : }
218 :
219 1 : mCurrentFrameIndex = mGIFStruct.images_decoded;
220 :
221 1 : if (!pipe) {
222 0 : mPipe = SurfacePipe();
223 0 : return NS_ERROR_FAILURE;
224 : }
225 :
226 1 : mPipe = Move(*pipe);
227 1 : return NS_OK;
228 : }
229 :
230 :
231 : //******************************************************************************
232 : void
233 1 : nsGIFDecoder2::EndImageFrame()
234 : {
235 1 : Opacity opacity = Opacity::SOME_TRANSPARENCY;
236 :
237 1 : if (mGIFStruct.images_decoded == 0) {
238 : // We need to send invalidations for the first frame.
239 1 : FlushImageData();
240 :
241 : // The first frame was preallocated with alpha; if it wasn't transparent, we
242 : // should fix that. We can also mark it opaque unconditionally if we didn't
243 : // actually see any transparent pixels - this test is only valid for the
244 : // first frame.
245 1 : if (!mGIFStruct.is_transparent && !mSawTransparency) {
246 0 : opacity = Opacity::FULLY_OPAQUE;
247 : }
248 : }
249 :
250 : // Unconditionally increment images_decoded, because we unconditionally
251 : // append frames in BeginImageFrame(). This ensures that images_decoded
252 : // always refers to the frame in mImage we're currently decoding,
253 : // even if some of them weren't decoded properly and thus are blank.
254 1 : mGIFStruct.images_decoded++;
255 :
256 : // Tell the superclass we finished a frame
257 4 : PostFrameStop(opacity,
258 1 : DisposalMethod(mGIFStruct.disposal_method),
259 2 : FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time));
260 :
261 : // Reset the transparent pixel
262 1 : if (mOldColor) {
263 1 : mColormap[mGIFStruct.tpixel] = mOldColor;
264 1 : mOldColor = 0;
265 : }
266 :
267 1 : mCurrentFrameIndex = -1;
268 1 : }
269 :
270 : template <typename PixelSize>
271 : PixelSize
272 40 : nsGIFDecoder2::ColormapIndexToPixel(uint8_t aIndex)
273 : {
274 : MOZ_ASSERT(sizeof(PixelSize) == sizeof(uint32_t));
275 :
276 : // Retrieve the next color, clamping to the size of the colormap.
277 40 : uint32_t color = mColormap[aIndex & mColorMask];
278 :
279 : // Check for transparency.
280 40 : if (mGIFStruct.is_transparent) {
281 40 : mSawTransparency = mSawTransparency || color == 0;
282 : }
283 :
284 40 : return color;
285 : }
286 :
287 : template <>
288 : uint8_t
289 0 : nsGIFDecoder2::ColormapIndexToPixel<uint8_t>(uint8_t aIndex)
290 : {
291 0 : return aIndex & mColorMask;
292 : }
293 :
294 : template <typename PixelSize>
295 : NextPixel<PixelSize>
296 41 : nsGIFDecoder2::YieldPixel(const uint8_t* aData,
297 : size_t aLength,
298 : size_t* aBytesReadOut)
299 : {
300 41 : MOZ_ASSERT(aData);
301 41 : MOZ_ASSERT(aBytesReadOut);
302 41 : MOZ_ASSERT(mGIFStruct.stackp >= mGIFStruct.stack);
303 :
304 : // Advance to the next byte we should read.
305 41 : const uint8_t* data = aData + *aBytesReadOut;
306 :
307 : // If we don't have any decoded data to yield, try to read some input and
308 : // produce some.
309 41 : if (mGIFStruct.stackp == mGIFStruct.stack) {
310 35 : while (mGIFStruct.bits < mGIFStruct.codesize && *aBytesReadOut < aLength) {
311 : // Feed the next byte into the decoder's 32-bit input buffer.
312 9 : mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits;
313 9 : mGIFStruct.bits += 8;
314 9 : data += 1;
315 9 : *aBytesReadOut += 1;
316 : }
317 :
318 17 : if (mGIFStruct.bits < mGIFStruct.codesize) {
319 0 : return AsVariant(WriteState::NEED_MORE_DATA);
320 : }
321 :
322 : // Get the leading variable-length symbol from the data stream.
323 17 : int code = mGIFStruct.datum & mGIFStruct.codemask;
324 17 : mGIFStruct.datum >>= mGIFStruct.codesize;
325 17 : mGIFStruct.bits -= mGIFStruct.codesize;
326 :
327 17 : const int clearCode = ClearCode();
328 :
329 : // Reset the dictionary to its original state, if requested
330 17 : if (code == clearCode) {
331 1 : mGIFStruct.codesize = mGIFStruct.datasize + 1;
332 1 : mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
333 1 : mGIFStruct.avail = clearCode + 2;
334 1 : mGIFStruct.oldcode = -1;
335 1 : return AsVariant(WriteState::NEED_MORE_DATA);
336 : }
337 :
338 : // Check for explicit end-of-stream code. It should only appear after all
339 : // image data, but if that was the case we wouldn't be in this function, so
340 : // this is always an error condition.
341 16 : if (code == (clearCode + 1)) {
342 0 : return AsVariant(WriteState::FAILURE);
343 : }
344 :
345 16 : if (mGIFStruct.oldcode == -1) {
346 1 : if (code >= MAX_BITS) {
347 0 : return AsVariant(WriteState::FAILURE); // The code's too big; something's wrong.
348 : }
349 :
350 1 : mGIFStruct.firstchar = mGIFStruct.oldcode = code;
351 :
352 : // Yield a pixel at the appropriate index in the colormap.
353 1 : mGIFStruct.pixels_remaining--;
354 1 : return AsVariant(ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]));
355 : }
356 :
357 15 : int incode = code;
358 15 : if (code >= mGIFStruct.avail) {
359 0 : *mGIFStruct.stackp++ = mGIFStruct.firstchar;
360 0 : code = mGIFStruct.oldcode;
361 :
362 0 : if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
363 0 : return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong.
364 : }
365 : }
366 :
367 63 : while (code >= clearCode) {
368 24 : if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
369 0 : return AsVariant(WriteState::FAILURE);
370 : }
371 :
372 24 : *mGIFStruct.stackp++ = mGIFStruct.suffix[code];
373 24 : code = mGIFStruct.prefix[code];
374 :
375 24 : if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
376 0 : return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong.
377 : }
378 : }
379 :
380 15 : *mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];
381 :
382 : // Define a new codeword in the dictionary.
383 15 : if (mGIFStruct.avail < 4096) {
384 15 : mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
385 15 : mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
386 15 : mGIFStruct.avail++;
387 :
388 : // If we've used up all the codewords of a given length increase the
389 : // length of codewords by one bit, but don't exceed the specified maximum
390 : // codeword size of 12 bits.
391 17 : if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
392 2 : (mGIFStruct.avail < 4096)) {
393 2 : mGIFStruct.codesize++;
394 2 : mGIFStruct.codemask += mGIFStruct.avail;
395 : }
396 : }
397 :
398 15 : mGIFStruct.oldcode = incode;
399 : }
400 :
401 39 : if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
402 0 : MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
403 : return AsVariant(WriteState::FAILURE);
404 : }
405 :
406 : // Yield a pixel at the appropriate index in the colormap.
407 39 : mGIFStruct.pixels_remaining--;
408 39 : return AsVariant(ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp));
409 : }
410 :
411 : /// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
412 : /// And apply any LCMS transformation.
413 : static void
414 2 : ConvertColormap(uint32_t* aColormap, uint32_t aColors)
415 : {
416 : // Apply CMS transformation if enabled and available
417 2 : if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
418 0 : qcms_transform* transform = gfxPlatform::GetCMSRGBTransform();
419 0 : if (transform) {
420 0 : qcms_transform_data(transform, aColormap, aColormap, aColors);
421 : }
422 : }
423 :
424 : // Convert from the GIF's RGB format to the Cairo format.
425 : // Work from end to begin, because of the in-place expansion
426 2 : uint8_t* from = ((uint8_t*)aColormap) + 3 * aColors;
427 2 : uint32_t* to = aColormap + aColors;
428 :
429 : // Convert color entries to Cairo format
430 :
431 : // set up for loops below
432 2 : if (!aColors) {
433 0 : return;
434 : }
435 2 : uint32_t c = aColors;
436 :
437 : // copy as bytes until source pointer is 32-bit-aligned
438 : // NB: can't use 32-bit reads, they might read off the end of the buffer
439 2 : for (; (NS_PTR_TO_UINT32(from) & 0x3) && c; --c) {
440 0 : from -= 3;
441 0 : *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]);
442 : }
443 :
444 : // bulk copy of pixels.
445 6 : while (c >= 4) {
446 2 : from -= 12;
447 2 : to -= 4;
448 2 : c -= 4;
449 2 : GFX_BLOCK_RGB_TO_FRGB(from,to);
450 : }
451 :
452 : // copy remaining pixel(s)
453 : // NB: can't use 32-bit reads, they might read off the end of the buffer
454 2 : while (c--) {
455 0 : from -= 3;
456 0 : *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]);
457 : }
458 : }
459 :
460 : LexerResult
461 2 : nsGIFDecoder2::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
462 : {
463 2 : MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
464 :
465 : return mLexer.Lex(aIterator, aOnResume,
466 26 : [=](State aState, const char* aData, size_t aLength) {
467 26 : switch(aState) {
468 : case State::GIF_HEADER:
469 23 : return ReadGIFHeader(aData);
470 : case State::SCREEN_DESCRIPTOR:
471 2 : return ReadScreenDescriptor(aData);
472 : case State::GLOBAL_COLOR_TABLE:
473 2 : return ReadGlobalColorTable(aData, aLength);
474 : case State::FINISHED_GLOBAL_COLOR_TABLE:
475 2 : return FinishedGlobalColorTable();
476 : case State::BLOCK_HEADER:
477 5 : return ReadBlockHeader(aData);
478 : case State::EXTENSION_HEADER:
479 2 : return ReadExtensionHeader(aData);
480 : case State::GRAPHIC_CONTROL_EXTENSION:
481 2 : return ReadGraphicControlExtension(aData);
482 : case State::APPLICATION_IDENTIFIER:
483 0 : return ReadApplicationIdentifier(aData);
484 : case State::NETSCAPE_EXTENSION_SUB_BLOCK:
485 0 : return ReadNetscapeExtensionSubBlock(aData);
486 : case State::NETSCAPE_EXTENSION_DATA:
487 0 : return ReadNetscapeExtensionData(aData);
488 : case State::IMAGE_DESCRIPTOR:
489 2 : return ReadImageDescriptor(aData);
490 : case State::FINISH_IMAGE_DESCRIPTOR:
491 0 : return FinishImageDescriptor(aData);
492 : case State::LOCAL_COLOR_TABLE:
493 0 : return ReadLocalColorTable(aData, aLength);
494 : case State::FINISHED_LOCAL_COLOR_TABLE:
495 0 : return FinishedLocalColorTable();
496 : case State::IMAGE_DATA_BLOCK:
497 1 : return ReadImageDataBlock(aData);
498 : case State::IMAGE_DATA_SUB_BLOCK:
499 2 : return ReadImageDataSubBlock(aData);
500 : case State::LZW_DATA:
501 1 : return ReadLZWData(aData, aLength);
502 : case State::SKIP_LZW_DATA:
503 0 : return Transition::ContinueUnbuffered(State::SKIP_LZW_DATA);
504 : case State::FINISHED_LZW_DATA:
505 1 : return Transition::To(State::IMAGE_DATA_SUB_BLOCK, SUB_BLOCK_HEADER_LEN);
506 : case State::SKIP_SUB_BLOCKS:
507 2 : return SkipSubBlocks(aData);
508 : case State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS:
509 0 : return Transition::ContinueUnbuffered(State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS);
510 : case State::FINISHED_SKIPPING_DATA:
511 0 : return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
512 : default:
513 0 : MOZ_CRASH("Unknown State");
514 : }
515 2 : });
516 : }
517 :
518 : LexerTransition<nsGIFDecoder2::State>
519 2 : nsGIFDecoder2::ReadGIFHeader(const char* aData)
520 : {
521 : // We retrieve the version here but because many GIF encoders set header
522 : // fields incorrectly, we barely use it; features which should only appear in
523 : // GIF89a are always accepted.
524 2 : if (strncmp(aData, "GIF87a", GIF_HEADER_LEN) == 0) {
525 0 : mGIFStruct.version = 87;
526 2 : } else if (strncmp(aData, "GIF89a", GIF_HEADER_LEN) == 0) {
527 2 : mGIFStruct.version = 89;
528 : } else {
529 0 : return Transition::TerminateFailure();
530 : }
531 :
532 2 : return Transition::To(State::SCREEN_DESCRIPTOR, GIF_SCREEN_DESCRIPTOR_LEN);
533 : }
534 :
535 : LexerTransition<nsGIFDecoder2::State>
536 2 : nsGIFDecoder2::ReadScreenDescriptor(const char* aData)
537 : {
538 2 : mGIFStruct.screen_width = LittleEndian::readUint16(aData + 0);
539 2 : mGIFStruct.screen_height = LittleEndian::readUint16(aData + 2);
540 :
541 2 : const uint8_t packedFields = aData[4];
542 :
543 : // XXX: Should we be capturing these values even if there is no global color
544 : // table?
545 2 : mGIFStruct.global_colormap_depth =
546 2 : (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1;
547 2 : mGIFStruct.global_colormap_count = 1 << mGIFStruct.global_colormap_depth;
548 :
549 : // We ignore several fields in the header. We don't care about the 'sort
550 : // flag', which indicates if the global color table's entries are sorted in
551 : // order of importance - if we need to render this image for a device with a
552 : // narrower color gamut than GIF supports we'll handle that at a different
553 : // layer. We have no use for the pixel aspect ratio as well. Finally, we
554 : // intentionally ignore the background color index, as implementing that
555 : // feature would not be web compatible - when a GIF image frame doesn't cover
556 : // the entire area of the image, the area that's not covered should always be
557 : // transparent.
558 :
559 2 : if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) {
560 2 : MOZ_ASSERT(mColorTablePos == 0);
561 :
562 : // We read the global color table in unbuffered mode since it can be quite
563 : // large and it'd be preferable to avoid unnecessary copies.
564 2 : const size_t globalColorTableSize = 3 * mGIFStruct.global_colormap_count;
565 : return Transition::ToUnbuffered(State::FINISHED_GLOBAL_COLOR_TABLE,
566 : State::GLOBAL_COLOR_TABLE,
567 2 : globalColorTableSize);
568 : }
569 :
570 0 : return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
571 : }
572 :
573 : LexerTransition<nsGIFDecoder2::State>
574 2 : nsGIFDecoder2::ReadGlobalColorTable(const char* aData, size_t aLength)
575 : {
576 2 : uint8_t* dest = reinterpret_cast<uint8_t*>(mGIFStruct.global_colormap)
577 2 : + mColorTablePos;
578 2 : memcpy(dest, aData, aLength);
579 2 : mColorTablePos += aLength;
580 2 : return Transition::ContinueUnbuffered(State::GLOBAL_COLOR_TABLE);
581 : }
582 :
583 : LexerTransition<nsGIFDecoder2::State>
584 2 : nsGIFDecoder2::FinishedGlobalColorTable()
585 : {
586 2 : ConvertColormap(mGIFStruct.global_colormap, mGIFStruct.global_colormap_count);
587 2 : mColorTablePos = 0;
588 2 : return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
589 : }
590 :
591 : LexerTransition<nsGIFDecoder2::State>
592 5 : nsGIFDecoder2::ReadBlockHeader(const char* aData)
593 : {
594 : // Determine what type of block we're dealing with.
595 5 : switch (aData[0]) {
596 : case GIF_EXTENSION_INTRODUCER:
597 2 : return Transition::To(State::EXTENSION_HEADER, EXTENSION_HEADER_LEN);
598 :
599 : case GIF_IMAGE_SEPARATOR:
600 2 : return Transition::To(State::IMAGE_DESCRIPTOR, IMAGE_DESCRIPTOR_LEN);
601 :
602 : case GIF_TRAILER:
603 1 : FinishInternal();
604 1 : return Transition::TerminateSuccess();
605 :
606 : default:
607 : // If we get anything other than GIF_IMAGE_SEPARATOR,
608 : // GIF_EXTENSION_INTRODUCER, or GIF_TRAILER, there is extraneous data
609 : // between blocks. The GIF87a spec tells us to keep reading until we find
610 : // an image separator, but GIF89a says such a file is corrupt. We follow
611 : // GIF89a and bail out.
612 :
613 0 : if (mGIFStruct.images_decoded > 0) {
614 : // The file is corrupt, but we successfully decoded some frames, so we
615 : // may as well consider the decode successful and display them.
616 0 : FinishInternal();
617 0 : return Transition::TerminateSuccess();
618 : }
619 :
620 : // No images decoded; there is nothing to display.
621 0 : return Transition::TerminateFailure();
622 : }
623 : }
624 :
625 : LexerTransition<nsGIFDecoder2::State>
626 2 : nsGIFDecoder2::ReadExtensionHeader(const char* aData)
627 : {
628 2 : const uint8_t label = aData[0];
629 2 : const uint8_t extensionHeaderLength = aData[1];
630 :
631 : // If the extension header is zero length, just treat it as a block terminator
632 : // and move on to the next block immediately.
633 2 : if (extensionHeaderLength == 0) {
634 0 : return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
635 : }
636 :
637 2 : switch (label) {
638 : case GIF_GRAPHIC_CONTROL_LABEL:
639 : // The GIF spec mandates that the Control Extension header block length is
640 : // 4 bytes, and the parser for this block reads 4 bytes, so we must
641 : // enforce that the buffer contains at least this many bytes. If the GIF
642 : // specifies a different length, we allow that, so long as it's larger;
643 : // the additional data will simply be ignored.
644 : return Transition::To(State::GRAPHIC_CONTROL_EXTENSION,
645 2 : max<uint8_t>(extensionHeaderLength,
646 4 : GRAPHIC_CONTROL_EXTENSION_LEN));
647 :
648 : case GIF_APPLICATION_EXTENSION_LABEL:
649 : // Again, the spec specifies that an application extension header is 11
650 : // bytes, but for compatibility with GIFs in the wild, we allow deviation
651 : // from the spec. This is important for real-world compatibility, as GIFs
652 : // in the wild exist with application extension headers that are both
653 : // shorter and longer than 11 bytes. However, we only try to actually
654 : // interpret the application extension if the length is correct;
655 : // otherwise, we just skip the block unconditionally.
656 0 : return extensionHeaderLength == APPLICATION_EXTENSION_LEN
657 : ? Transition::To(State::APPLICATION_IDENTIFIER, extensionHeaderLength)
658 : : Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
659 : State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
660 0 : extensionHeaderLength);
661 :
662 : default:
663 : // Skip over any other type of extension block, including comment and
664 : // plain text blocks.
665 : return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
666 : State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
667 0 : extensionHeaderLength);
668 : }
669 : }
670 :
671 : LexerTransition<nsGIFDecoder2::State>
672 2 : nsGIFDecoder2::ReadGraphicControlExtension(const char* aData)
673 : {
674 2 : mGIFStruct.is_transparent = aData[0] & 0x1;
675 2 : mGIFStruct.tpixel = uint8_t(aData[3]);
676 2 : mGIFStruct.disposal_method = (aData[0] >> 2) & 0x7;
677 :
678 2 : if (mGIFStruct.disposal_method == 4) {
679 : // Some encoders (and apparently some specs) represent
680 : // DisposalMethod::RESTORE_PREVIOUS as 4, but 3 is used in the canonical
681 : // spec and is more popular, so we normalize to 3.
682 0 : mGIFStruct.disposal_method = 3;
683 2 : } else if (mGIFStruct.disposal_method > 4) {
684 : // This GIF is using a disposal method which is undefined in the spec.
685 : // Treat it as DisposalMethod::NOT_SPECIFIED.
686 0 : mGIFStruct.disposal_method = 0;
687 : }
688 :
689 2 : DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method);
690 2 : if (method == DisposalMethod::CLEAR_ALL || method == DisposalMethod::CLEAR) {
691 : // We may have to display the background under this image during animation
692 : // playback, so we regard it as transparent.
693 0 : PostHasTransparency();
694 : }
695 :
696 2 : mGIFStruct.delay_time = LittleEndian::readUint16(aData + 1) * 10;
697 2 : if (mGIFStruct.delay_time > 0) {
698 0 : PostIsAnimated(FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time));
699 : }
700 :
701 2 : return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
702 : }
703 :
704 : LexerTransition<nsGIFDecoder2::State>
705 0 : nsGIFDecoder2::ReadApplicationIdentifier(const char* aData)
706 : {
707 0 : if ((strncmp(aData, "NETSCAPE2.0", 11) == 0) ||
708 0 : (strncmp(aData, "ANIMEXTS1.0", 11) == 0)) {
709 : // This is a Netscape application extension block.
710 : return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
711 0 : SUB_BLOCK_HEADER_LEN);
712 : }
713 :
714 : // This is an application extension we don't care about. Just skip it.
715 0 : return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
716 : }
717 :
718 : LexerTransition<nsGIFDecoder2::State>
719 0 : nsGIFDecoder2::ReadNetscapeExtensionSubBlock(const char* aData)
720 : {
721 0 : const uint8_t blockLength = aData[0];
722 0 : if (blockLength == 0) {
723 : // We hit the block terminator.
724 0 : return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
725 : }
726 :
727 : // We consume a minimum of 3 bytes in accordance with the specs for the
728 : // Netscape application extension block, such as they are.
729 0 : const size_t extensionLength = max<uint8_t>(blockLength, 3);
730 0 : return Transition::To(State::NETSCAPE_EXTENSION_DATA, extensionLength);
731 : }
732 :
733 : LexerTransition<nsGIFDecoder2::State>
734 0 : nsGIFDecoder2::ReadNetscapeExtensionData(const char* aData)
735 : {
736 : // Documentation for NETSCAPE2.0 / ANIMEXTS1.0 extensions can be found at:
737 : // https://wiki.whatwg.org/wiki/GIF
738 : static const uint8_t NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID = 1;
739 : static const uint8_t NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID = 2;
740 :
741 0 : const uint8_t subBlockID = aData[0] & 7;
742 0 : switch (subBlockID) {
743 : case NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID:
744 : // This is looping extension.
745 0 : mGIFStruct.loop_count = LittleEndian::readUint16(aData + 1);
746 : // Zero loop count is infinite animation loop request.
747 0 : if (mGIFStruct.loop_count == 0) {
748 0 : mGIFStruct.loop_count = -1;
749 : }
750 :
751 : return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
752 0 : SUB_BLOCK_HEADER_LEN);
753 :
754 : case NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID:
755 : // We allow, but ignore, this extension.
756 : return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
757 0 : SUB_BLOCK_HEADER_LEN);
758 :
759 : default:
760 0 : return Transition::TerminateFailure();
761 : }
762 : }
763 :
764 : LexerTransition<nsGIFDecoder2::State>
765 2 : nsGIFDecoder2::ReadImageDescriptor(const char* aData)
766 : {
767 : // On the first frame, we don't need to yield, and none of the other checks
768 : // below apply, so we can just jump right into FinishImageDescriptor().
769 2 : if (mGIFStruct.images_decoded == 0) {
770 2 : return FinishImageDescriptor(aData);
771 : }
772 :
773 0 : if (!HasAnimation()) {
774 : // We should've already called PostIsAnimated(); this must be a corrupt
775 : // animated image with a first frame timeout of zero. Signal that we're
776 : // animated now, before the first-frame decode early exit below, so that
777 : // RasterImage can detect that this happened.
778 0 : PostIsAnimated(FrameTimeout::FromRawMilliseconds(0));
779 : }
780 :
781 0 : if (IsFirstFrameDecode()) {
782 : // We're about to get a second frame, but we only want the first. Stop
783 : // decoding now.
784 0 : FinishInternal();
785 0 : return Transition::TerminateSuccess();
786 : }
787 :
788 0 : MOZ_ASSERT(Size() == OutputSize(), "Downscaling an animated image?");
789 :
790 : // Yield to allow access to the previous frame before we start a new one.
791 0 : return Transition::ToAfterYield(State::FINISH_IMAGE_DESCRIPTOR);
792 : }
793 :
794 : LexerTransition<nsGIFDecoder2::State>
795 2 : nsGIFDecoder2::FinishImageDescriptor(const char* aData)
796 : {
797 2 : IntRect frameRect;
798 :
799 : // Get image offsets with respect to the screen origin.
800 2 : frameRect.x = LittleEndian::readUint16(aData + 0);
801 2 : frameRect.y = LittleEndian::readUint16(aData + 2);
802 2 : frameRect.width = LittleEndian::readUint16(aData + 4);
803 2 : frameRect.height = LittleEndian::readUint16(aData + 6);
804 :
805 2 : if (!mGIFStruct.images_decoded) {
806 : // Work around GIF files where
807 : // * at least one of the logical screen dimensions is smaller than the
808 : // same dimension in the first image, or
809 : // * GIF87a files where the first image's dimensions do not match the
810 : // logical screen dimensions.
811 4 : if (mGIFStruct.screen_height < frameRect.height ||
812 4 : mGIFStruct.screen_width < frameRect.width ||
813 2 : mGIFStruct.version == 87) {
814 0 : mGIFStruct.screen_height = frameRect.height;
815 0 : mGIFStruct.screen_width = frameRect.width;
816 0 : frameRect.MoveTo(0, 0);
817 : }
818 :
819 : // Create the image container with the right size.
820 2 : BeginGIF();
821 2 : if (HasError()) {
822 : // Setting the size led to an error.
823 0 : return Transition::TerminateFailure();
824 : }
825 :
826 : // If we're doing a metadata decode, we're done.
827 2 : if (IsMetadataDecode()) {
828 1 : CheckForTransparency(frameRect);
829 1 : FinishInternal();
830 1 : return Transition::TerminateSuccess();
831 : }
832 : }
833 :
834 : // Work around broken GIF files that have zero frame width or height; in this
835 : // case, we'll treat the frame as having the same size as the overall image.
836 1 : if (frameRect.height == 0 || frameRect.width == 0) {
837 0 : frameRect.height = mGIFStruct.screen_height;
838 0 : frameRect.width = mGIFStruct.screen_width;
839 :
840 : // If that still resulted in zero frame width or height, give up.
841 0 : if (frameRect.height == 0 || frameRect.width == 0) {
842 0 : return Transition::TerminateFailure();
843 : }
844 : }
845 :
846 : // Determine |depth| (log base 2 of the number of colors in the palette).
847 1 : bool haveLocalColorTable = false;
848 1 : uint16_t depth = 0;
849 1 : uint8_t packedFields = aData[8];
850 :
851 1 : if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) {
852 : // Get the palette depth from the local color table.
853 0 : depth = (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1;
854 0 : haveLocalColorTable = true;
855 : } else {
856 : // Get the palette depth from the global color table.
857 1 : depth = mGIFStruct.global_colormap_depth;
858 : }
859 :
860 : // If the transparent color index is greater than the number of colors in the
861 : // color table, we may need a higher color depth than |depth| would specify.
862 : // Our internal representation of the image will instead use |realDepth|,
863 : // which is the smallest color depth that can accomodate the existing palette
864 : // *and* the transparent color index.
865 1 : uint16_t realDepth = depth;
866 1 : while (mGIFStruct.tpixel >= (1 << realDepth) &&
867 0 : realDepth < 8) {
868 0 : realDepth++;
869 : }
870 :
871 : // Create a mask used to ensure that color values fit within the colormap.
872 1 : mColorMask = 0xFF >> (8 - realDepth);
873 :
874 : // Determine if this frame is interlaced or not.
875 1 : const bool isInterlaced = packedFields & PACKED_FIELDS_INTERLACED_BIT;
876 :
877 : // Create the SurfacePipe we'll use to write output for this frame.
878 1 : if (NS_FAILED(BeginImageFrame(frameRect, realDepth, isInterlaced))) {
879 0 : return Transition::TerminateFailure();
880 : }
881 :
882 : // Clear state from last image.
883 1 : mGIFStruct.pixels_remaining = frameRect.width * frameRect.height;
884 :
885 1 : if (haveLocalColorTable) {
886 : // We have a local color table, so prepare to read it into the palette of
887 : // the current frame.
888 0 : mGIFStruct.local_colormap_size = 1 << depth;
889 :
890 0 : if (mGIFStruct.images_decoded == 0) {
891 : // The first frame has a local color table. Allocate space for it as we
892 : // use a BGRA or BGRX surface for the first frame; such surfaces don't
893 : // have their own palettes internally.
894 0 : mColormapSize = sizeof(uint32_t) << realDepth;
895 0 : if (!mGIFStruct.local_colormap) {
896 0 : mGIFStruct.local_colormap =
897 0 : static_cast<uint32_t*>(moz_xmalloc(mColormapSize));
898 : }
899 0 : mColormap = mGIFStruct.local_colormap;
900 : }
901 :
902 0 : const size_t size = 3 << depth;
903 0 : if (mColormapSize > size) {
904 : // Clear the part of the colormap which will be unused with this palette.
905 : // If a GIF references an invalid palette entry, ensure the entry is opaque white.
906 : // This is needed for Skia as if it isn't, RGBX surfaces will cause blending issues
907 : // with Skia.
908 0 : memset(reinterpret_cast<uint8_t*>(mColormap) + size, 0xFF,
909 0 : mColormapSize - size);
910 : }
911 :
912 0 : MOZ_ASSERT(mColorTablePos == 0);
913 :
914 : // We read the local color table in unbuffered mode since it can be quite
915 : // large and it'd be preferable to avoid unnecessary copies.
916 : return Transition::ToUnbuffered(State::FINISHED_LOCAL_COLOR_TABLE,
917 : State::LOCAL_COLOR_TABLE,
918 0 : size);
919 : }
920 :
921 : // There's no local color table; copy the global color table into the palette
922 : // of the current frame.
923 1 : if (mGIFStruct.images_decoded > 0) {
924 0 : memcpy(mColormap, mGIFStruct.global_colormap, mColormapSize);
925 : } else {
926 1 : mColormap = mGIFStruct.global_colormap;
927 : }
928 :
929 1 : return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN);
930 : }
931 :
932 : LexerTransition<nsGIFDecoder2::State>
933 0 : nsGIFDecoder2::ReadLocalColorTable(const char* aData, size_t aLength)
934 : {
935 0 : uint8_t* dest = reinterpret_cast<uint8_t*>(mColormap) + mColorTablePos;
936 0 : memcpy(dest, aData, aLength);
937 0 : mColorTablePos += aLength;
938 0 : return Transition::ContinueUnbuffered(State::LOCAL_COLOR_TABLE);
939 : }
940 :
941 : LexerTransition<nsGIFDecoder2::State>
942 0 : nsGIFDecoder2::FinishedLocalColorTable()
943 : {
944 0 : ConvertColormap(mColormap, mGIFStruct.local_colormap_size);
945 0 : mColorTablePos = 0;
946 0 : return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN);
947 : }
948 :
949 : LexerTransition<nsGIFDecoder2::State>
950 1 : nsGIFDecoder2::ReadImageDataBlock(const char* aData)
951 : {
952 : // Make sure the transparent pixel is transparent in the colormap.
953 1 : if (mGIFStruct.is_transparent) {
954 : // Save the old value so we can restore it later.
955 1 : if (mColormap == mGIFStruct.global_colormap) {
956 1 : mOldColor = mColormap[mGIFStruct.tpixel];
957 : }
958 1 : mColormap[mGIFStruct.tpixel] = 0;
959 : }
960 :
961 : // Initialize the LZW decoder.
962 1 : mGIFStruct.datasize = uint8_t(aData[0]);
963 1 : const int clearCode = ClearCode();
964 1 : if (mGIFStruct.datasize > MAX_LZW_BITS || clearCode >= MAX_BITS) {
965 0 : return Transition::TerminateFailure();
966 : }
967 :
968 1 : mGIFStruct.avail = clearCode + 2;
969 1 : mGIFStruct.oldcode = -1;
970 1 : mGIFStruct.codesize = mGIFStruct.datasize + 1;
971 1 : mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
972 1 : mGIFStruct.datum = mGIFStruct.bits = 0;
973 :
974 : // Initialize the tables.
975 5 : for (int i = 0; i < clearCode; i++) {
976 4 : mGIFStruct.suffix[i] = i;
977 : }
978 :
979 1 : mGIFStruct.stackp = mGIFStruct.stack;
980 :
981 : // Begin reading image data sub-blocks.
982 1 : return Transition::To(State::IMAGE_DATA_SUB_BLOCK, SUB_BLOCK_HEADER_LEN);
983 : }
984 :
985 : LexerTransition<nsGIFDecoder2::State>
986 2 : nsGIFDecoder2::ReadImageDataSubBlock(const char* aData)
987 : {
988 2 : const uint8_t subBlockLength = aData[0];
989 2 : if (subBlockLength == 0) {
990 : // We hit the block terminator.
991 1 : EndImageFrame();
992 1 : return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
993 : }
994 :
995 1 : if (mGIFStruct.pixels_remaining == 0) {
996 : // We've already written to the entire image; we should've hit the block
997 : // terminator at this point. This image is corrupt, but we'll tolerate it.
998 :
999 0 : if (subBlockLength == GIF_TRAILER) {
1000 : // This GIF is missing the block terminator for the final block; we'll put
1001 : // up with it.
1002 0 : FinishInternal();
1003 0 : return Transition::TerminateSuccess();
1004 : }
1005 :
1006 : // We're not at the end of the image, so just skip the extra data.
1007 : return Transition::ToUnbuffered(State::FINISHED_LZW_DATA,
1008 : State::SKIP_LZW_DATA,
1009 0 : subBlockLength);
1010 : }
1011 :
1012 : // Handle the standard case: there's data in the sub-block and pixels left to
1013 : // fill in the image. We read the sub-block unbuffered so we can get pixels on
1014 : // the screen as soon as possible.
1015 : return Transition::ToUnbuffered(State::FINISHED_LZW_DATA,
1016 : State::LZW_DATA,
1017 1 : subBlockLength);
1018 : }
1019 :
1020 : LexerTransition<nsGIFDecoder2::State>
1021 1 : nsGIFDecoder2::ReadLZWData(const char* aData, size_t aLength)
1022 : {
1023 1 : const uint8_t* data = reinterpret_cast<const uint8_t*>(aData);
1024 1 : size_t length = aLength;
1025 :
1026 7 : while (mGIFStruct.pixels_remaining > 0 &&
1027 2 : (length > 0 || mGIFStruct.bits >= mGIFStruct.codesize)) {
1028 2 : size_t bytesRead = 0;
1029 :
1030 2 : auto result = mGIFStruct.images_decoded == 0
1031 43 : ? mPipe.WritePixels<uint32_t>([&]{ return YieldPixel<uint32_t>(data, length, &bytesRead); })
1032 2 : : mPipe.WritePixels<uint8_t>([&]{ return YieldPixel<uint8_t>(data, length, &bytesRead); });
1033 :
1034 2 : if (MOZ_UNLIKELY(bytesRead > length)) {
1035 0 : MOZ_ASSERT_UNREACHABLE("Overread?");
1036 : bytesRead = length;
1037 : }
1038 :
1039 : // Advance our position in the input based upon what YieldPixel() consumed.
1040 2 : data += bytesRead;
1041 2 : length -= bytesRead;
1042 :
1043 2 : switch (result) {
1044 : case WriteState::NEED_MORE_DATA:
1045 1 : continue;
1046 :
1047 : case WriteState::FINISHED:
1048 1 : NS_WARNING_ASSERTION(mGIFStruct.pixels_remaining <= 0,
1049 : "too many pixels");
1050 1 : mGIFStruct.pixels_remaining = 0;
1051 1 : break;
1052 :
1053 : case WriteState::FAILURE:
1054 0 : return Transition::TerminateFailure();
1055 : }
1056 : }
1057 :
1058 : // We're done, but keep going until we consume all the data in the sub-block.
1059 1 : return Transition::ContinueUnbuffered(State::LZW_DATA);
1060 : }
1061 :
1062 : LexerTransition<nsGIFDecoder2::State>
1063 2 : nsGIFDecoder2::SkipSubBlocks(const char* aData)
1064 : {
1065 : // In the SKIP_SUB_BLOCKS state we skip over data sub-blocks that we're not
1066 : // interested in. Blocks consist of a block header (which can be up to 255
1067 : // bytes in length) and a series of data sub-blocks. Each data sub-block
1068 : // consists of a single byte length value, followed by the data itself. A data
1069 : // sub-block with a length of zero terminates the overall block.
1070 : // SKIP_SUB_BLOCKS reads a sub-block length value. If it's zero, we've arrived
1071 : // at the next block. Otherwise, we enter the SKIP_DATA_THEN_SKIP_SUB_BLOCKS
1072 : // state to skip over the sub-block data and return to SKIP_SUB_BLOCKS at the
1073 : // start of the next sub-block.
1074 :
1075 2 : const uint8_t nextSubBlockLength = aData[0];
1076 2 : if (nextSubBlockLength == 0) {
1077 : // We hit the block terminator, so the sequence of data sub-blocks is over;
1078 : // begin processing another block.
1079 2 : return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
1080 : }
1081 :
1082 : // Skip to the next sub-block length value.
1083 : return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
1084 : State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
1085 0 : nextSubBlockLength);
1086 : }
1087 :
1088 : Maybe<Telemetry::HistogramID>
1089 2 : nsGIFDecoder2::SpeedHistogram() const
1090 : {
1091 2 : return Some(Telemetry::IMAGE_DECODE_SPEED_GIF);
1092 : }
1093 :
1094 : } // namespace image
1095 : } // namespace mozilla
|