summaryrefslogtreecommitdiffstats
path: root/image/decoders/nsGIFDecoder2.cpp
blob: 9b2de9124ad809b8f128b1f9fd7affc58093a6d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
The Graphics Interchange Format(c) is the copyright property of CompuServe
Incorporated. Only CompuServe Incorporated is authorized to define, redefine,
enhance, alter, modify or change in any way the definition of the format.

CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free
license for the use of the Graphics Interchange Format(sm) in computer
software; computer software utilizing GIF(sm) must acknowledge ownership of the
Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in
User and Technical Documentation. Computer software utilizing GIF, which is
distributed or may be distributed without User or Technical Documentation must
display to the screen or printer a message acknowledging ownership of the
Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in
this case, the acknowledgement may be displayed in an opening screen or leading
banner, or a closing screen or trailing banner. A message such as the following
may be used:

    "The Graphics Interchange Format(c) is the Copyright property of
    CompuServe Incorporated. GIF(sm) is a Service Mark property of
    CompuServe Incorporated."

For further information, please contact :

    CompuServe Incorporated
    Graphics Technology Department
    5000 Arlington Center Boulevard
    Columbus, Ohio  43220
    U. S. A.

CompuServe Incorporated maintains a mailing list with all those individuals and
organizations who wish to receive copies of this document when it is corrected
or revised. This service is offered free of charge; please provide us with your
mailing address.
*/

#include "nsGIFDecoder2.h"

#include <stddef.h>

#include "imgFrame.h"
#include "mozilla/EndianUtils.h"
#include "RasterImage.h"
#include "SurfacePipeFactory.h"

#include "gfxColor.h"
#include "gfxPlatform.h"
#include "qcms.h"
#include <algorithm>
#include "mozilla/Telemetry.h"

using namespace mozilla::gfx;

using std::max;

namespace mozilla {
namespace image {

//////////////////////////////////////////////////////////////////////
// GIF Decoder Implementation

static const size_t GIF_HEADER_LEN = 6;
static const size_t GIF_SCREEN_DESCRIPTOR_LEN = 7;
static const size_t BLOCK_HEADER_LEN = 1;
static const size_t SUB_BLOCK_HEADER_LEN = 1;
static const size_t EXTENSION_HEADER_LEN = 2;
static const size_t GRAPHIC_CONTROL_EXTENSION_LEN = 4;
static const size_t APPLICATION_EXTENSION_LEN = 11;
static const size_t IMAGE_DESCRIPTOR_LEN = 9;

// Masks for reading color table information from packed fields in the screen
// descriptor and image descriptor blocks.
static const uint8_t PACKED_FIELDS_COLOR_TABLE_BIT = 0x80;
static const uint8_t PACKED_FIELDS_INTERLACED_BIT = 0x40;
static const uint8_t PACKED_FIELDS_TABLE_DEPTH_MASK = 0x07;

nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage)
    : Decoder(aImage),
      mLexer(Transition::To(State::GIF_HEADER, GIF_HEADER_LEN),
             Transition::TerminateSuccess()),
      mOldColor(0),
      mCurrentFrameIndex(-1),
      mColorTablePos(0),
      mColormap(nullptr),
      mColormapSize(0),
      mColorMask('\0'),
      mGIFOpen(false),
      mSawTransparency(false),
      mSwizzleFn(nullptr) {
  // Clear out the structure, excluding the arrays. Ensure that the global
  // colormap is initialized as opaque.
  memset(&mGIFStruct, 0, sizeof(mGIFStruct));
  memset(mGIFStruct.global_colormap, 0xFF, sizeof(mGIFStruct.global_colormap));

  // Each color table will need to be unpacked.
  mSwizzleFn = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::OS_RGBA);
  MOZ_ASSERT(mSwizzleFn);
}

nsGIFDecoder2::~nsGIFDecoder2() { free(mGIFStruct.local_colormap); }

nsresult nsGIFDecoder2::FinishInternal() {
  MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");

  // If the GIF got cut off, handle it anyway
  if (!IsMetadataDecode() && mGIFOpen) {
    if (mCurrentFrameIndex == mGIFStruct.images_decoded) {
      EndImageFrame();
    }
    PostDecodeDone(mGIFStruct.loop_count);
    mGIFOpen = false;
  }

  return NS_OK;
}

void nsGIFDecoder2::FlushImageData() {
  Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
  if (!invalidRect) {
    return;
  }

  PostInvalidation(invalidRect->mInputSpaceRect,
                   Some(invalidRect->mOutputSpaceRect));
}

//******************************************************************************
// GIF decoder callback methods. Part of public API for GIF2
//******************************************************************************

//******************************************************************************
void nsGIFDecoder2::BeginGIF() {
  if (mGIFOpen) {
    return;
  }

  mGIFOpen = true;

  PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
}

bool nsGIFDecoder2::CheckForTransparency(const OrientedIntRect& aFrameRect) {
  // Check if the image has a transparent color in its palette.
  if (mGIFStruct.is_transparent) {
    PostHasTransparency();
    return true;
  }

  // This is a bit of a hack. Some sites will use a 1x1 gif that includes no
  // header information indicating it is transparent, no palette, and no image
  // data at all (so no pixels get written) to represent a transparent pixel
  // using the absolute least number of bytes. Generally things are setup to
  // detect transparency without decoding the image data. So to detect this kind
  // of transparency without decoing the image data we would have to assume
  // every gif is transparent, which we would like to avoid. Changing things so
  // that we can detect transparency at any point of decoding is a bigger change
  // and not worth it for one questionable 1x1 gif. Using this "trick" for
  // anything but 1x1 transparent spacer gifs doesn't make sense, so it's
  // reasonable to target 1x1 gifs just for this.
  if (mGIFStruct.screen_width == 1 && mGIFStruct.screen_height == 1) {
    PostHasTransparency();
    return true;
  }

  if (mGIFStruct.images_decoded > 0) {
    return false;  // We only care about first frame padding below.
  }

  // If we need padding on the first frame, that means we don't draw into part
  // of the image at all. Report that as transparency.
  OrientedIntRect imageRect(0, 0, mGIFStruct.screen_width,
                            mGIFStruct.screen_height);
  if (!imageRect.IsEqualEdges(aFrameRect)) {
    PostHasTransparency();
    mSawTransparency = true;  // Make sure we don't optimize it away.
    return true;
  }

  return false;
}

//******************************************************************************
nsresult nsGIFDecoder2::BeginImageFrame(const OrientedIntRect& aFrameRect,
                                        uint16_t aDepth, bool aIsInterlaced) {
  MOZ_ASSERT(HasSize());

  bool hasTransparency = CheckForTransparency(aFrameRect);

  // Make sure there's no animation if we're downscaling.
  MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());

  Maybe<AnimationParams> animParams;
  if (!IsFirstFrameDecode()) {
    animParams.emplace(aFrameRect.ToUnknownRect(),
                       FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time),
                       uint32_t(mGIFStruct.images_decoded), BlendMethod::OVER,
                       DisposalMethod(mGIFStruct.disposal_method));
  }

  SurfacePipeFlags pipeFlags =
      aIsInterlaced ? SurfacePipeFlags::DEINTERLACE : SurfacePipeFlags();

  gfx::SurfaceFormat format;
  if (mGIFStruct.images_decoded == 0) {
    // The first frame may be displayed progressively.
    pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;

    // Only allow opaque surfaces if we are decoding a single image without
    // transparency. For an animation, there isn't much benefit to RGBX given
    // the current frame is constantly changing, and there are many risks
    // since BlendAnimationFilter is able to clear rows of data.
    format = hasTransparency || animParams ? SurfaceFormat::OS_RGBA
                                           : SurfaceFormat::OS_RGBX;
  } else {
    format = SurfaceFormat::OS_RGBA;
  }

  Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
      this, Size(), OutputSize(), aFrameRect, format, format, animParams,
      mTransform, pipeFlags);
  mCurrentFrameIndex = mGIFStruct.images_decoded;

  if (!pipe) {
    mPipe = SurfacePipe();
    return NS_ERROR_FAILURE;
  }

  mPipe = std::move(*pipe);
  return NS_OK;
}

//******************************************************************************
void nsGIFDecoder2::EndImageFrame() {
  Opacity opacity = Opacity::SOME_TRANSPARENCY;

  if (mGIFStruct.images_decoded == 0) {
    // We need to send invalidations for the first frame.
    FlushImageData();

    // The first frame was preallocated with alpha; if it wasn't transparent, we
    // should fix that. We can also mark it opaque unconditionally if we didn't
    // actually see any transparent pixels - this test is only valid for the
    // first frame.
    if (!mGIFStruct.is_transparent && !mSawTransparency) {
      opacity = Opacity::FULLY_OPAQUE;
    }
  }

  // Unconditionally increment images_decoded, because we unconditionally
  // append frames in BeginImageFrame(). This ensures that images_decoded
  // always refers to the frame in mImage we're currently decoding,
  // even if some of them weren't decoded properly and thus are blank.
  mGIFStruct.images_decoded++;

  // Reset graphic control extension parameters that we shouldn't reuse
  // between frames.
  mGIFStruct.delay_time = 0;

  // Tell the superclass we finished a frame
  PostFrameStop(opacity);

  // Reset the transparent pixel
  if (mOldColor) {
    mColormap[mGIFStruct.tpixel] = mOldColor;
    mOldColor = 0;
  }

  mColormap = nullptr;
  mColormapSize = 0;
  mCurrentFrameIndex = -1;
}

template <typename PixelSize>
PixelSize nsGIFDecoder2::ColormapIndexToPixel(uint8_t aIndex) {
  MOZ_ASSERT(sizeof(PixelSize) == sizeof(uint32_t));

  // Retrieve the next color, clamping to the size of the colormap.
  uint32_t color = mColormap[aIndex & mColorMask];

  // Check for transparency.
  if (mGIFStruct.is_transparent) {
    mSawTransparency = mSawTransparency || color == 0;
  }

  return color;
}

template <>
uint8_t nsGIFDecoder2::ColormapIndexToPixel<uint8_t>(uint8_t aIndex) {
  return aIndex & mColorMask;
}

template <typename PixelSize>
std::tuple<int32_t, Maybe<WriteState>> nsGIFDecoder2::YieldPixels(
    const uint8_t* aData, size_t aLength, size_t* aBytesReadOut,
    PixelSize* aPixelBlock, int32_t aBlockSize) {
  MOZ_ASSERT(aData);
  MOZ_ASSERT(aBytesReadOut);
  MOZ_ASSERT(mGIFStruct.stackp >= mGIFStruct.stack);

  // Advance to the next byte we should read.
  const uint8_t* data = aData + *aBytesReadOut;

  int32_t written = 0;
  while (aBlockSize > written) {
    // If we don't have any decoded data to yield, try to read some input and
    // produce some.
    if (mGIFStruct.stackp == mGIFStruct.stack) {
      while (mGIFStruct.bits < mGIFStruct.codesize &&
             *aBytesReadOut < aLength) {
        // Feed the next byte into the decoder's 32-bit input buffer.
        mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits;
        mGIFStruct.bits += 8;
        data += 1;
        *aBytesReadOut += 1;
      }

      if (mGIFStruct.bits < mGIFStruct.codesize) {
        return std::make_tuple(written, Some(WriteState::NEED_MORE_DATA));
      }

      // Get the leading variable-length symbol from the data stream.
      int code = mGIFStruct.datum & mGIFStruct.codemask;
      mGIFStruct.datum >>= mGIFStruct.codesize;
      mGIFStruct.bits -= mGIFStruct.codesize;

      const int clearCode = ClearCode();

      // Reset the dictionary to its original state, if requested
      if (code == clearCode) {
        mGIFStruct.codesize = mGIFStruct.datasize + 1;
        mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
        mGIFStruct.avail = clearCode + 2;
        mGIFStruct.oldcode = -1;
        return std::make_tuple(written, Some(WriteState::NEED_MORE_DATA));
      }

      // Check for explicit end-of-stream code. It should only appear after all
      // image data, but if that was the case we wouldn't be in this function,
      // so this is always an error condition.
      if (code == (clearCode + 1)) {
        return std::make_tuple(written, Some(WriteState::FAILURE));
      }

      if (mGIFStruct.oldcode == -1) {
        if (code >= MAX_BITS) {
          // The code's too big; something's wrong.
          return std::make_tuple(written, Some(WriteState::FAILURE));
        }

        mGIFStruct.firstchar = mGIFStruct.oldcode = code;

        // Yield a pixel at the appropriate index in the colormap.
        mGIFStruct.pixels_remaining--;
        aPixelBlock[written++] =
            ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]);
        continue;
      }

      int incode = code;
      if (code >= mGIFStruct.avail) {
        *mGIFStruct.stackp++ = mGIFStruct.firstchar;
        code = mGIFStruct.oldcode;

        if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
          // Stack overflow; something's wrong.
          return std::make_tuple(written, Some(WriteState::FAILURE));
        }
      }

      while (code >= clearCode) {
        if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
          return std::make_tuple(written, Some(WriteState::FAILURE));
        }

        *mGIFStruct.stackp++ = mGIFStruct.suffix[code];
        code = mGIFStruct.prefix[code];

        if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
          // Stack overflow; something's wrong.
          return std::make_tuple(written, Some(WriteState::FAILURE));
        }
      }

      *mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];

      // Define a new codeword in the dictionary.
      if (mGIFStruct.avail < 4096) {
        mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
        mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
        mGIFStruct.avail++;

        // If we've used up all the codewords of a given length increase the
        // length of codewords by one bit, but don't exceed the specified
        // maximum codeword size of 12 bits.
        if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
            (mGIFStruct.avail < 4096)) {
          mGIFStruct.codesize++;
          mGIFStruct.codemask += mGIFStruct.avail;
        }
      }

      mGIFStruct.oldcode = incode;
    }

    if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
      MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
      return std::make_tuple(written, Some(WriteState::FAILURE));
    }

    // Yield a pixel at the appropriate index in the colormap.
    mGIFStruct.pixels_remaining--;
    aPixelBlock[written++] =
        ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp);
  }

  return std::make_tuple(written, Maybe<WriteState>());
}

/// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
/// And apply any LCMS transformation.
void nsGIFDecoder2::ConvertColormap(uint32_t* aColormap, uint32_t aColors) {
  if (!aColors) {
    return;
  }

  // Apply CMS transformation if enabled and available
  if (mCMSMode == CMSMode::All) {
    qcms_transform* transform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8);
    if (transform) {
      qcms_transform_data(transform, aColormap, aColormap, aColors);
    }
  }

  // Expand color table from RGB to BGRA.
  MOZ_ASSERT(mSwizzleFn);
  uint8_t* data = reinterpret_cast<uint8_t*>(aColormap);
  mSwizzleFn(data, data, aColors);
}

LexerResult nsGIFDecoder2::DoDecode(SourceBufferIterator& aIterator,
                                    IResumable* aOnResume) {
  MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");

  return mLexer.Lex(
      aIterator, aOnResume,
      [=](State aState, const char* aData, size_t aLength) {
        switch (aState) {
          case State::GIF_HEADER:
            return ReadGIFHeader(aData);
          case State::SCREEN_DESCRIPTOR:
            return ReadScreenDescriptor(aData);
          case State::GLOBAL_COLOR_TABLE:
            return ReadGlobalColorTable(aData, aLength);
          case State::FINISHED_GLOBAL_COLOR_TABLE:
            return FinishedGlobalColorTable();
          case State::BLOCK_HEADER:
            return ReadBlockHeader(aData);
          case State::EXTENSION_HEADER:
            return ReadExtensionHeader(aData);
          case State::GRAPHIC_CONTROL_EXTENSION:
            return ReadGraphicControlExtension(aData);
          case State::APPLICATION_IDENTIFIER:
            return ReadApplicationIdentifier(aData);
          case State::NETSCAPE_EXTENSION_SUB_BLOCK:
            return ReadNetscapeExtensionSubBlock(aData);
          case State::NETSCAPE_EXTENSION_DATA:
            return ReadNetscapeExtensionData(aData);
          case State::IMAGE_DESCRIPTOR:
            return ReadImageDescriptor(aData);
          case State::FINISH_IMAGE_DESCRIPTOR:
            return FinishImageDescriptor(aData);
          case State::LOCAL_COLOR_TABLE:
            return ReadLocalColorTable(aData, aLength);
          case State::FINISHED_LOCAL_COLOR_TABLE:
            return FinishedLocalColorTable();
          case State::IMAGE_DATA_BLOCK:
            return ReadImageDataBlock(aData);
          case State::IMAGE_DATA_SUB_BLOCK:
            return ReadImageDataSubBlock(aData);
          case State::LZW_DATA:
            return ReadLZWData(aData, aLength);
          case State::SKIP_LZW_DATA:
            return Transition::ContinueUnbuffered(State::SKIP_LZW_DATA);
          case State::FINISHED_LZW_DATA:
            return Transition::To(State::IMAGE_DATA_SUB_BLOCK,
                                  SUB_BLOCK_HEADER_LEN);
          case State::SKIP_SUB_BLOCKS:
            return SkipSubBlocks(aData);
          case State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS:
            return Transition::ContinueUnbuffered(
                State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS);
          case State::FINISHED_SKIPPING_DATA:
            return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
          default:
            MOZ_CRASH("Unknown State");
        }
      });
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadGIFHeader(
    const char* aData) {
  // We retrieve the version here but because many GIF encoders set header
  // fields incorrectly, we barely use it; features which should only appear in
  // GIF89a are always accepted.
  if (strncmp(aData, "GIF87a", GIF_HEADER_LEN) == 0) {
    mGIFStruct.version = 87;
  } else if (strncmp(aData, "GIF89a", GIF_HEADER_LEN) == 0) {
    mGIFStruct.version = 89;
  } else {
    return Transition::TerminateFailure();
  }

  return Transition::To(State::SCREEN_DESCRIPTOR, GIF_SCREEN_DESCRIPTOR_LEN);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadScreenDescriptor(
    const char* aData) {
  mGIFStruct.screen_width = LittleEndian::readUint16(aData + 0);
  mGIFStruct.screen_height = LittleEndian::readUint16(aData + 2);

  const uint8_t packedFields = aData[4];

  // XXX: Should we be capturing these values even if there is no global color
  // table?
  mGIFStruct.global_colormap_depth =
      (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1;
  mGIFStruct.global_colormap_count = 1 << mGIFStruct.global_colormap_depth;

  // We ignore several fields in the header. We don't care about the 'sort
  // flag', which indicates if the global color table's entries are sorted in
  // order of importance - if we need to render this image for a device with a
  // narrower color gamut than GIF supports we'll handle that at a different
  // layer. We have no use for the pixel aspect ratio as well. Finally, we
  // intentionally ignore the background color index, as implementing that
  // feature would not be web compatible - when a GIF image frame doesn't cover
  // the entire area of the image, the area that's not covered should always be
  // transparent.

  if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) {
    MOZ_ASSERT(mColorTablePos == 0);

    // We read the global color table in unbuffered mode since it can be quite
    // large and it'd be preferable to avoid unnecessary copies.
    const size_t globalColorTableSize = 3 * mGIFStruct.global_colormap_count;
    return Transition::ToUnbuffered(State::FINISHED_GLOBAL_COLOR_TABLE,
                                    State::GLOBAL_COLOR_TABLE,
                                    globalColorTableSize);
  }

  return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadGlobalColorTable(
    const char* aData, size_t aLength) {
  uint8_t* dest =
      reinterpret_cast<uint8_t*>(mGIFStruct.global_colormap) + mColorTablePos;
  memcpy(dest, aData, aLength);
  mColorTablePos += aLength;
  return Transition::ContinueUnbuffered(State::GLOBAL_COLOR_TABLE);
}

LexerTransition<nsGIFDecoder2::State>
nsGIFDecoder2::FinishedGlobalColorTable() {
  ConvertColormap(mGIFStruct.global_colormap, mGIFStruct.global_colormap_count);
  mColorTablePos = 0;
  return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadBlockHeader(
    const char* aData) {
  // Determine what type of block we're dealing with.
  switch (aData[0]) {
    case GIF_EXTENSION_INTRODUCER:
      return Transition::To(State::EXTENSION_HEADER, EXTENSION_HEADER_LEN);

    case GIF_IMAGE_SEPARATOR:
      return Transition::To(State::IMAGE_DESCRIPTOR, IMAGE_DESCRIPTOR_LEN);

    case GIF_TRAILER:
      FinishInternal();
      return Transition::TerminateSuccess();

    default:
      // If we get anything other than GIF_IMAGE_SEPARATOR,
      // GIF_EXTENSION_INTRODUCER, or GIF_TRAILER, there is extraneous data
      // between blocks. The GIF87a spec tells us to keep reading until we find
      // an image separator, but GIF89a says such a file is corrupt. We follow
      // GIF89a and bail out.

      if (mGIFStruct.images_decoded > 0) {
        // The file is corrupt, but we successfully decoded some frames, so we
        // may as well consider the decode successful and display them.
        FinishInternal();
        return Transition::TerminateSuccess();
      }

      // No images decoded; there is nothing to display.
      return Transition::TerminateFailure();
  }
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadExtensionHeader(
    const char* aData) {
  const uint8_t label = aData[0];
  const uint8_t extensionHeaderLength = aData[1];

  // If the extension header is zero length, just treat it as a block terminator
  // and move on to the next block immediately.
  if (extensionHeaderLength == 0) {
    return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
  }

  switch (label) {
    case GIF_GRAPHIC_CONTROL_LABEL:
      // The GIF spec mandates that the Control Extension header block length is
      // 4 bytes, and the parser for this block reads 4 bytes, so we must
      // enforce that the buffer contains at least this many bytes. If the GIF
      // specifies a different length, we allow that, so long as it's larger;
      // the additional data will simply be ignored.
      return Transition::To(
          State::GRAPHIC_CONTROL_EXTENSION,
          max<uint8_t>(extensionHeaderLength, GRAPHIC_CONTROL_EXTENSION_LEN));

    case GIF_APPLICATION_EXTENSION_LABEL:
      // Again, the spec specifies that an application extension header is 11
      // bytes, but for compatibility with GIFs in the wild, we allow deviation
      // from the spec. This is important for real-world compatibility, as GIFs
      // in the wild exist with application extension headers that are both
      // shorter and longer than 11 bytes. However, we only try to actually
      // interpret the application extension if the length is correct;
      // otherwise, we just skip the block unconditionally.
      return extensionHeaderLength == APPLICATION_EXTENSION_LEN
                 ? Transition::To(State::APPLICATION_IDENTIFIER,
                                  extensionHeaderLength)
                 : Transition::ToUnbuffered(
                       State::FINISHED_SKIPPING_DATA,
                       State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
                       extensionHeaderLength);

    default:
      // Skip over any other type of extension block, including comment and
      // plain text blocks.
      return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
                                      State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
                                      extensionHeaderLength);
  }
}

LexerTransition<nsGIFDecoder2::State>
nsGIFDecoder2::ReadGraphicControlExtension(const char* aData) {
  mGIFStruct.is_transparent = aData[0] & 0x1;
  mGIFStruct.tpixel = uint8_t(aData[3]);
  mGIFStruct.disposal_method = (aData[0] >> 2) & 0x7;

  if (mGIFStruct.disposal_method == 4) {
    // Some encoders (and apparently some specs) represent
    // DisposalMethod::RESTORE_PREVIOUS as 4, but 3 is used in the canonical
    // spec and is more popular, so we normalize to 3.
    mGIFStruct.disposal_method = 3;
  } else if (mGIFStruct.disposal_method > 4) {
    // This GIF is using a disposal method which is undefined in the spec.
    // Treat it as DisposalMethod::NOT_SPECIFIED.
    mGIFStruct.disposal_method = 0;
  }

  DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method);
  if (method == DisposalMethod::CLEAR_ALL || method == DisposalMethod::CLEAR) {
    // We may have to display the background under this image during animation
    // playback, so we regard it as transparent.
    PostHasTransparency();
  }

  mGIFStruct.delay_time = LittleEndian::readUint16(aData + 1) * 10;
  if (!HasAnimation() && mGIFStruct.delay_time > 0) {
    PostIsAnimated(FrameTimeout::FromRawMilliseconds(mGIFStruct.delay_time));
  }

  return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadApplicationIdentifier(
    const char* aData) {
  if ((strncmp(aData, "NETSCAPE2.0", 11) == 0) ||
      (strncmp(aData, "ANIMEXTS1.0", 11) == 0)) {
    // This is a Netscape application extension block.
    return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
                          SUB_BLOCK_HEADER_LEN);
  }

  // This is an application extension we don't care about. Just skip it.
  return Transition::To(State::SKIP_SUB_BLOCKS, SUB_BLOCK_HEADER_LEN);
}

LexerTransition<nsGIFDecoder2::State>
nsGIFDecoder2::ReadNetscapeExtensionSubBlock(const char* aData) {
  const uint8_t blockLength = aData[0];
  if (blockLength == 0) {
    // We hit the block terminator.
    return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
  }

  // We consume a minimum of 3 bytes in accordance with the specs for the
  // Netscape application extension block, such as they are.
  const size_t extensionLength = max<uint8_t>(blockLength, 3);
  return Transition::To(State::NETSCAPE_EXTENSION_DATA, extensionLength);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadNetscapeExtensionData(
    const char* aData) {
  // Documentation for NETSCAPE2.0 / ANIMEXTS1.0 extensions can be found at:
  //   https://wiki.whatwg.org/wiki/GIF
  static const uint8_t NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID = 1;
  static const uint8_t NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID = 2;

  const uint8_t subBlockID = aData[0] & 7;
  switch (subBlockID) {
    case NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID:
      // This is looping extension.
      mGIFStruct.loop_count = LittleEndian::readUint16(aData + 1);
      // Zero loop count is infinite animation loop request.
      if (mGIFStruct.loop_count == 0) {
        mGIFStruct.loop_count = -1;
      }

      return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
                            SUB_BLOCK_HEADER_LEN);

    case NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID:
      // We allow, but ignore, this extension.
      return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
                            SUB_BLOCK_HEADER_LEN);

    default:
      return Transition::TerminateFailure();
  }
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadImageDescriptor(
    const char* aData) {
  // On the first frame, we don't need to yield, and none of the other checks
  // below apply, so we can just jump right into FinishImageDescriptor().
  if (mGIFStruct.images_decoded == 0) {
    return FinishImageDescriptor(aData);
  }

  if (!HasAnimation()) {
    // We should've already called PostIsAnimated(); this must be a corrupt
    // animated image with a first frame timeout of zero. Signal that we're
    // animated now, before the first-frame decode early exit below, so that
    // RasterImage can detect that this happened.
    PostIsAnimated(FrameTimeout::FromRawMilliseconds(0));
  }

  if (IsFirstFrameDecode()) {
    // We're about to get a second frame, but we only want the first. Stop
    // decoding now.
    FinishInternal();
    return Transition::TerminateSuccess();
  }

  MOZ_ASSERT(Size() == OutputSize(), "Downscaling an animated image?");

  // Yield to allow access to the previous frame before we start a new one.
  return Transition::ToAfterYield(State::FINISH_IMAGE_DESCRIPTOR);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::FinishImageDescriptor(
    const char* aData) {
  OrientedIntRect frameRect;

  // Get image offsets with respect to the screen origin.
  frameRect.SetRect(
      LittleEndian::readUint16(aData + 0), LittleEndian::readUint16(aData + 2),
      LittleEndian::readUint16(aData + 4), LittleEndian::readUint16(aData + 6));

  if (!mGIFStruct.images_decoded) {
    // Work around GIF files where
    //   * at least one of the logical screen dimensions is smaller than the
    //     same dimension in the first image, or
    //   * GIF87a files where the first image's dimensions do not match the
    //     logical screen dimensions.
    if (mGIFStruct.screen_height < frameRect.Height() ||
        mGIFStruct.screen_width < frameRect.Width() ||
        mGIFStruct.version == 87) {
      mGIFStruct.screen_height = frameRect.Height();
      mGIFStruct.screen_width = frameRect.Width();
      frameRect.MoveTo(0, 0);
    }

    // Create the image container with the right size.
    BeginGIF();
    if (HasError()) {
      // Setting the size led to an error.
      return Transition::TerminateFailure();
    }

    // If we're doing a metadata decode, we're done.
    if (IsMetadataDecode()) {
      CheckForTransparency(frameRect);
      FinishInternal();
      return Transition::TerminateSuccess();
    }
  }

  // Work around broken GIF files that have zero frame width or height; in this
  // case, we'll treat the frame as having the same size as the overall image.
  if (frameRect.Height() == 0 || frameRect.Width() == 0) {
    frameRect.SetHeight(mGIFStruct.screen_height);
    frameRect.SetWidth(mGIFStruct.screen_width);

    // If that still resulted in zero frame width or height, give up.
    if (frameRect.Height() == 0 || frameRect.Width() == 0) {
      return Transition::TerminateFailure();
    }
  }

  // Determine |depth| (log base 2 of the number of colors in the palette).
  bool haveLocalColorTable = false;
  uint16_t depth = 0;
  uint8_t packedFields = aData[8];

  if (packedFields & PACKED_FIELDS_COLOR_TABLE_BIT) {
    // Get the palette depth from the local color table.
    depth = (packedFields & PACKED_FIELDS_TABLE_DEPTH_MASK) + 1;
    haveLocalColorTable = true;
  } else {
    // Get the palette depth from the global color table.
    depth = mGIFStruct.global_colormap_depth;
  }

  // If the transparent color index is greater than the number of colors in the
  // color table, we may need a higher color depth than |depth| would specify.
  // Our internal representation of the image will instead use |realDepth|,
  // which is the smallest color depth that can accommodate the existing palette
  // *and* the transparent color index.
  uint16_t realDepth = depth;
  while (mGIFStruct.tpixel >= (1 << realDepth) && realDepth < 8) {
    realDepth++;
  }

  // Create a mask used to ensure that color values fit within the colormap.
  mColorMask = 0xFF >> (8 - realDepth);

  // Determine if this frame is interlaced or not.
  const bool isInterlaced = packedFields & PACKED_FIELDS_INTERLACED_BIT;

  // Create the SurfacePipe we'll use to write output for this frame.
  if (NS_FAILED(BeginImageFrame(frameRect, realDepth, isInterlaced))) {
    return Transition::TerminateFailure();
  }

  // Clear state from last image.
  mGIFStruct.pixels_remaining =
      int64_t(frameRect.Width()) * int64_t(frameRect.Height());

  if (haveLocalColorTable) {
    // We have a local color table, so prepare to read it into the palette of
    // the current frame.
    mGIFStruct.local_colormap_size = 1 << depth;

    if (!mColormap) {
      // Ensure our current colormap buffer is large enough to hold the new one.
      mColormapSize = sizeof(uint32_t) << realDepth;
      if (mGIFStruct.local_colormap_buffer_size < mColormapSize) {
        if (mGIFStruct.local_colormap) {
          free(mGIFStruct.local_colormap);
        }
        mGIFStruct.local_colormap_buffer_size = mColormapSize;
        mGIFStruct.local_colormap =
            static_cast<uint32_t*>(moz_xmalloc(mColormapSize));
        // Ensure the local colormap is initialized as opaque.
        memset(mGIFStruct.local_colormap, 0xFF, mColormapSize);
      } else {
        mColormapSize = mGIFStruct.local_colormap_buffer_size;
      }

      mColormap = mGIFStruct.local_colormap;
    }

    MOZ_ASSERT(mColormap);

    const size_t size = 3 << depth;
    if (mColormapSize > size) {
      // Clear the part of the colormap which will be unused with this palette.
      // If a GIF references an invalid palette entry, ensure the entry is
      // opaque white. This is needed for Skia as if it isn't, RGBX surfaces
      // will cause blending issues with Skia.
      memset(reinterpret_cast<uint8_t*>(mColormap) + size, 0xFF,
             mColormapSize - size);
    }

    MOZ_ASSERT(mColorTablePos == 0);

    // We read the local color table in unbuffered mode since it can be quite
    // large and it'd be preferable to avoid unnecessary copies.
    return Transition::ToUnbuffered(State::FINISHED_LOCAL_COLOR_TABLE,
                                    State::LOCAL_COLOR_TABLE, size);
  }

  // There's no local color table; copy the global color table into the palette
  // of the current frame.
  if (mColormap) {
    memcpy(mColormap, mGIFStruct.global_colormap, mColormapSize);
  } else {
    mColormap = mGIFStruct.global_colormap;
  }

  return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadLocalColorTable(
    const char* aData, size_t aLength) {
  uint8_t* dest = reinterpret_cast<uint8_t*>(mColormap) + mColorTablePos;
  memcpy(dest, aData, aLength);
  mColorTablePos += aLength;
  return Transition::ContinueUnbuffered(State::LOCAL_COLOR_TABLE);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::FinishedLocalColorTable() {
  ConvertColormap(mColormap, mGIFStruct.local_colormap_size);
  mColorTablePos = 0;
  return Transition::To(State::IMAGE_DATA_BLOCK, BLOCK_HEADER_LEN);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadImageDataBlock(
    const char* aData) {
  // Make sure the transparent pixel is transparent in the colormap.
  if (mGIFStruct.is_transparent) {
    // Save the old value so we can restore it later.
    if (mColormap == mGIFStruct.global_colormap) {
      mOldColor = mColormap[mGIFStruct.tpixel];
    }
    mColormap[mGIFStruct.tpixel] = 0;
  }

  // Initialize the LZW decoder.
  mGIFStruct.datasize = uint8_t(aData[0]);
  if (mGIFStruct.datasize > MAX_LZW_BITS) {
    return Transition::TerminateFailure();
  }
  const int clearCode = ClearCode();
  if (clearCode >= MAX_BITS) {
    return Transition::TerminateFailure();
  }

  mGIFStruct.avail = clearCode + 2;
  mGIFStruct.oldcode = -1;
  mGIFStruct.codesize = mGIFStruct.datasize + 1;
  mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
  mGIFStruct.datum = mGIFStruct.bits = 0;

  // Initialize the tables.
  for (int i = 0; i < clearCode; i++) {
    mGIFStruct.suffix[i] = i;
  }

  mGIFStruct.stackp = mGIFStruct.stack;

  // Begin reading image data sub-blocks.
  return Transition::To(State::IMAGE_DATA_SUB_BLOCK, SUB_BLOCK_HEADER_LEN);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadImageDataSubBlock(
    const char* aData) {
  const uint8_t subBlockLength = aData[0];
  if (subBlockLength == 0) {
    // We hit the block terminator.
    EndImageFrame();
    return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
  }

  if (mGIFStruct.pixels_remaining == 0) {
    // We've already written to the entire image; we should've hit the block
    // terminator at this point. This image is corrupt, but we'll tolerate it.

    if (subBlockLength == GIF_TRAILER) {
      // This GIF is missing the block terminator for the final block; we'll put
      // up with it.
      FinishInternal();
      return Transition::TerminateSuccess();
    }

    // We're not at the end of the image, so just skip the extra data.
    return Transition::ToUnbuffered(State::FINISHED_LZW_DATA,
                                    State::SKIP_LZW_DATA, subBlockLength);
  }

  // Handle the standard case: there's data in the sub-block and pixels left to
  // fill in the image. We read the sub-block unbuffered so we can get pixels on
  // the screen as soon as possible.
  return Transition::ToUnbuffered(State::FINISHED_LZW_DATA, State::LZW_DATA,
                                  subBlockLength);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::ReadLZWData(
    const char* aData, size_t aLength) {
  const uint8_t* data = reinterpret_cast<const uint8_t*>(aData);
  size_t length = aLength;

  while (mGIFStruct.pixels_remaining > 0 &&
         (length > 0 || mGIFStruct.bits >= mGIFStruct.codesize)) {
    size_t bytesRead = 0;

    auto result = mPipe.WritePixelBlocks<uint32_t>(
        [&](uint32_t* aPixelBlock, int32_t aBlockSize) {
          return YieldPixels<uint32_t>(data, length, &bytesRead, aPixelBlock,
                                       aBlockSize);
        });

    if (MOZ_UNLIKELY(bytesRead > length)) {
      MOZ_ASSERT_UNREACHABLE("Overread?");
      bytesRead = length;
    }

    // Advance our position in the input based upon what YieldPixel() consumed.
    data += bytesRead;
    length -= bytesRead;

    switch (result) {
      case WriteState::NEED_MORE_DATA:
        continue;

      case WriteState::FINISHED:
        NS_WARNING_ASSERTION(mGIFStruct.pixels_remaining <= 0,
                             "too many pixels");
        mGIFStruct.pixels_remaining = 0;
        break;

      case WriteState::FAILURE:
        return Transition::TerminateFailure();
    }
  }

  // We're done, but keep going until we consume all the data in the sub-block.
  return Transition::ContinueUnbuffered(State::LZW_DATA);
}

LexerTransition<nsGIFDecoder2::State> nsGIFDecoder2::SkipSubBlocks(
    const char* aData) {
  // In the SKIP_SUB_BLOCKS state we skip over data sub-blocks that we're not
  // interested in. Blocks consist of a block header (which can be up to 255
  // bytes in length) and a series of data sub-blocks. Each data sub-block
  // consists of a single byte length value, followed by the data itself. A data
  // sub-block with a length of zero terminates the overall block.
  // SKIP_SUB_BLOCKS reads a sub-block length value. If it's zero, we've arrived
  // at the next block. Otherwise, we enter the SKIP_DATA_THEN_SKIP_SUB_BLOCKS
  // state to skip over the sub-block data and return to SKIP_SUB_BLOCKS at the
  // start of the next sub-block.

  const uint8_t nextSubBlockLength = aData[0];
  if (nextSubBlockLength == 0) {
    // We hit the block terminator, so the sequence of data sub-blocks is over;
    // begin processing another block.
    return Transition::To(State::BLOCK_HEADER, BLOCK_HEADER_LEN);
  }

  // Skip to the next sub-block length value.
  return Transition::ToUnbuffered(State::FINISHED_SKIPPING_DATA,
                                  State::SKIP_DATA_THEN_SKIP_SUB_BLOCKS,
                                  nextSubBlockLength);
}

Maybe<Telemetry::HistogramID> nsGIFDecoder2::SpeedHistogram() const {
  return Some(Telemetry::IMAGE_DECODE_SPEED_GIF);
}

}  // namespace image
}  // namespace mozilla