summaryrefslogtreecommitdiffstats
path: root/image/decoders/nsBMPDecoder.cpp
blob: da971e054faf7752fc593eebfedea30a58bb7d74 (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
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

// This is a cross-platform BMP Decoder, which should work everywhere,
// including big-endian machines like the PowerPC.
//
// BMP is a format that has been extended multiple times. To understand the
// decoder you need to understand this history. The summary of the history
// below was determined from the following documents.
//
// - http://www.fileformat.info/format/bmp/egff.htm
// - http://www.fileformat.info/format/os2bmp/egff.htm
// - http://fileformats.archiveteam.org/wiki/BMP
// - http://fileformats.archiveteam.org/wiki/OS/2_BMP
// - https://en.wikipedia.org/wiki/BMP_file_format
// - https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png
//
// WINDOWS VERSIONS OF THE BMP FORMAT
// ----------------------------------
// WinBMPv1.
// - This version is no longer used and can be ignored.
//
// WinBMPv2.
// - First is a 14 byte file header that includes: the magic number ("BM"),
//   file size, and offset to the pixel data (|mDataOffset|).
// - Next is a 12 byte info header which includes: the info header size
//   (mBIHSize), width, height, number of color planes, and bits-per-pixel
//   (|mBpp|) which must be 1, 4, 8 or 24.
// - Next is the semi-optional color table, which has length 2^|mBpp| and has 3
//   bytes per value (BGR). The color table is required if |mBpp| is 1, 4, or 8.
// - Next is an optional gap.
// - Next is the pixel data, which is pointed to by |mDataOffset|.
//
// WinBMPv3. This is the most widely used version.
// - It changed the info header to 40 bytes by taking the WinBMPv2 info
//   header, enlargening its width and height fields, and adding more fields
//   including: a compression type (|mCompression|) and number of colors
//   (|mNumColors|).
// - The semi-optional color table is now 4 bytes per value (BGR0), and its
//   length is |mNumColors|, or 2^|mBpp| if |mNumColors| is zero.
// - |mCompression| can be RGB (i.e. no compression), RLE4 (if |mBpp|==4) or
//   RLE8 (if |mBpp|==8) values.
//
// WinBMPv3-NT. A variant of WinBMPv3.
// - It did not change the info header layout from WinBMPv3.
// - |mBpp| can now be 16 or 32, in which case |mCompression| can be RGB or the
//   new BITFIELDS value; in the latter case an additional 12 bytes of color
//   bitfields follow the info header.
//
// WinBMPv4.
// - It extended the info header to 108 bytes, including the 12 bytes of color
//   mask data from WinBMPv3-NT, plus alpha mask data, and also color-space and
//   gamma correction fields.
//
// WinBMPv5.
// - It extended the info header to 124 bytes, adding color profile data.
// - It also added an optional color profile table after the pixel data (and
//   another optional gap).
//
// WinBMPv3-ICO. This is a variant of WinBMPv3.
// - It's the BMP format used for BMP images within ICO files.
// - The only difference with WinBMPv3 is that if an image is 32bpp and has no
//   compression, then instead of treating the pixel data as 0RGB it is treated
//   as ARGB, but only if one or more of the A values are non-zero.
//
// Clipboard variants.
// - It's the BMP format used for BMP images captured from the clipboard.
// - It is missing the file header, containing the BM signature and the data
//   offset. Instead the data begins after the header.
// - If it uses BITFIELDS compression, then there is always an additional 12
//   bytes of data after the header that must be read. In WinBMPv4+, the masks
//   are supposed to be included in the header size, which are the values we use
//   for decoding purposes, but there is additional three masks following the
//   header which must be skipped to get to the pixel data.
//
// OS/2 VERSIONS OF THE BMP FORMAT
// -------------------------------
// OS2-BMPv1.
// - Almost identical to WinBMPv2; the differences are basically ignorable.
//
// OS2-BMPv2.
// - Similar to WinBMPv3.
// - The info header is 64 bytes but can be reduced to as little as 16; any
//   omitted fields are treated as zero. The first 40 bytes of these fields are
//   nearly identical to the WinBMPv3 info header; the remaining 24 bytes are
//   different.
// - Also adds compression types "Huffman 1D" and "RLE24", which we don't
//   support.
// - We treat OS2-BMPv2 files as if they are WinBMPv3 (i.e. ignore the extra 24
//   bytes in the info header), which in practice is good enough.

#include "ImageLogging.h"
#include "nsBMPDecoder.h"

#include <stdlib.h>

#include "mozilla/Attributes.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/Likely.h"
#include "mozilla/UniquePtrExtensions.h"

#include "RasterImage.h"
#include "SurfacePipeFactory.h"
#include "gfxPlatform.h"
#include <algorithm>

using namespace mozilla::gfx;

namespace mozilla {
namespace image {
namespace bmp {

struct Compression {
  enum { RGB = 0, RLE8 = 1, RLE4 = 2, BITFIELDS = 3 };
};

// RLE escape codes and constants.
struct RLE {
  enum {
    ESCAPE = 0,
    ESCAPE_EOL = 0,
    ESCAPE_EOF = 1,
    ESCAPE_DELTA = 2,

    SEGMENT_LENGTH = 2,
    DELTA_LENGTH = 2
  };
};

}  // namespace bmp

using namespace bmp;

static double FixedPoint2Dot30_To_Double(uint32_t aFixed) {
  constexpr double factor = 1.0 / 1073741824.0;  // 2^-30
  return double(aFixed) * factor;
}

static float FixedPoint16Dot16_To_Float(uint32_t aFixed) {
  constexpr double factor = 1.0 / 65536.0;  // 2^-16
  return double(aFixed) * factor;
}

static float CalRbgEndpointToQcms(const CalRgbEndpoint& aIn,
                                  qcms_CIE_xyY& aOut) {
  aOut.x = FixedPoint2Dot30_To_Double(aIn.mX);
  aOut.y = FixedPoint2Dot30_To_Double(aIn.mY);
  aOut.Y = FixedPoint2Dot30_To_Double(aIn.mZ);
  return FixedPoint16Dot16_To_Float(aIn.mGamma);
}

static void ReadCalRgbEndpoint(const char* aData, uint32_t aEndpointOffset,
                               uint32_t aGammaOffset, CalRgbEndpoint& aOut) {
  aOut.mX = LittleEndian::readUint32(aData + aEndpointOffset);
  aOut.mY = LittleEndian::readUint32(aData + aEndpointOffset + 4);
  aOut.mZ = LittleEndian::readUint32(aData + aEndpointOffset + 8);
  aOut.mGamma = LittleEndian::readUint32(aData + aGammaOffset);
}

/// Sets the pixel data in aDecoded to the given values.
/// @param aDecoded pointer to pixel to be set, will be incremented to point to
/// the next pixel.
static void SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen,
                     uint8_t aBlue, uint8_t aAlpha = 0xFF) {
  *aDecoded++ = gfxPackedPixelNoPreMultiply(aAlpha, aRed, aGreen, aBlue);
}

static void SetPixel(uint32_t*& aDecoded, uint8_t idx,
                     const UniquePtr<ColorTableEntry[]>& aColors) {
  SetPixel(aDecoded, aColors[idx].mRed, aColors[idx].mGreen,
           aColors[idx].mBlue);
}

/// Sets two (or one if aCount = 1) pixels
/// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes
/// depending on whether one or two pixels are written.
/// @param aData The values for the two pixels
/// @param aCount Current count. Is decremented by one or two.
static void Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount,
                         const UniquePtr<ColorTableEntry[]>& aColors) {
  uint8_t idx = aData >> 4;
  SetPixel(aDecoded, idx, aColors);
  if (--aCount > 0) {
    idx = aData & 0xF;
    SetPixel(aDecoded, idx, aColors);
    --aCount;
  }
}

static mozilla::LazyLogModule sBMPLog("BMPDecoder");

// The length of the mBIHSize field in the info header.
static const uint32_t BIHSIZE_FIELD_LENGTH = 4;

nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength,
                           bool aForClipboard)
    : Decoder(aImage),
      mLexer(Transition::To(aState, aLength), Transition::TerminateSuccess()),
      mIsWithinICO(false),
      mIsForClipboard(aForClipboard),
      mMayHaveTransparency(false),
      mDoesHaveTransparency(false),
      mNumColors(0),
      mColors(nullptr),
      mBytesPerColor(0),
      mPreGapLength(0),
      mPixelRowSize(0),
      mCurrentRow(0),
      mCurrentPos(0),
      mAbsoluteModeNumPixels(0) {}

// Constructor for normal BMP files or from the clipboard.
nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, bool aForClipboard)
    : nsBMPDecoder(aImage,
                   aForClipboard ? State::INFO_HEADER_SIZE : State::FILE_HEADER,
                   aForClipboard ? BIHSIZE_FIELD_LENGTH : FILE_HEADER_LENGTH,
                   aForClipboard) {}

// Constructor used for WinBMPv3-ICO files, which lack a file header.
nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset)
    : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH,
                   /* aForClipboard */ false) {
  SetIsWithinICO();

  // Even though the file header isn't present in this case, the dataOffset
  // field is set as if it is, and so we must increment mPreGapLength
  // accordingly.
  mPreGapLength += FILE_HEADER_LENGTH;

  // This is the one piece of data we normally get from a BMP file header, so
  // it must be provided via an argument.
  mH.mDataOffset = aDataOffset;
}

nsBMPDecoder::~nsBMPDecoder() {}

// Obtains the size of the compressed image resource.
int32_t nsBMPDecoder::GetCompressedImageSize() const {
  // In the RGB case mImageSize might not be set, so compute it manually.
  MOZ_ASSERT(mPixelRowSize != 0);
  return mH.mCompression == Compression::RGB ? mPixelRowSize * AbsoluteHeight()
                                             : mH.mImageSize;
}

nsresult nsBMPDecoder::BeforeFinishInternal() {
  if (!IsMetadataDecode() && !mImageData) {
    return NS_ERROR_FAILURE;  // No image; something went wrong.
  }

  return NS_OK;
}

nsresult nsBMPDecoder::FinishInternal() {
  // We shouldn't be called in error cases.
  MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!");

  // We should never make multiple frames.
  MOZ_ASSERT(GetFrameCount() <= 1, "Multiple BMP frames?");

  // Send notifications if appropriate.
  if (!IsMetadataDecode() && HasSize()) {
    // We should have image data.
    MOZ_ASSERT(mImageData);

    // If it was truncated, fill in the missing pixels as black.
    while (mCurrentRow > 0) {
      uint32_t* dst = RowBuffer();
      while (mCurrentPos < mH.mWidth) {
        SetPixel(dst, 0, 0, 0);
        mCurrentPos++;
      }
      mCurrentPos = 0;
      FinishRow();
    }

    MOZ_ASSERT_IF(mDoesHaveTransparency, mMayHaveTransparency);

    // We have transparency if we either detected some in the image itself
    // (i.e., |mDoesHaveTransparency| is true) or we're in an ICO, which could
    // mean we have an AND mask that provides transparency (i.e., |mIsWithinICO|
    // is true).
    // XXX(seth): We can tell when we create the decoder if the AND mask is
    // present, so we could be more precise about this.
    const Opacity opacity = mDoesHaveTransparency || mIsWithinICO
                                ? Opacity::SOME_TRANSPARENCY
                                : Opacity::FULLY_OPAQUE;

    PostFrameStop(opacity);
    PostDecodeDone();
  }

  return NS_OK;
}

// ----------------------------------------
// Actual Data Processing
// ----------------------------------------

void BitFields::Value::Set(uint32_t aMask) {
  mMask = aMask;

  // Handle this exceptional case first. The chosen values don't matter
  // (because a mask of zero will always give a value of zero) except that
  // mBitWidth:
  // - shouldn't be zero, because that would cause an infinite loop in Get();
  // - shouldn't be 5 or 8, because that could cause a false positive match in
  //   IsR5G5B5() or IsR8G8B8().
  if (mMask == 0x0) {
    mRightShift = 0;
    mBitWidth = 1;
    return;
  }

  // Find the rightmost 1.
  uint8_t i;
  for (i = 0; i < 32; i++) {
    if (mMask & (1 << i)) {
      break;
    }
  }
  mRightShift = i;

  // Now find the leftmost 1 in the same run of 1s. (If there are multiple runs
  // of 1s -- which isn't valid -- we'll behave as if only the lowest run was
  // present, which seems reasonable.)
  for (i = i + 1; i < 32; i++) {
    if (!(mMask & (1 << i))) {
      break;
    }
  }
  mBitWidth = i - mRightShift;
}

MOZ_ALWAYS_INLINE uint8_t BitFields::Value::Get(uint32_t aValue) const {
  // Extract the unscaled value.
  uint32_t v = (aValue & mMask) >> mRightShift;

  // Idea: to upscale v precisely we need to duplicate its bits, possibly
  // repeatedly, possibly partially in the last case, from bit 7 down to bit 0
  // in v2. For example:
  //
  // - mBitWidth=1:  v2 = v<<7 | v<<6 | ... | v<<1 | v>>0     k -> kkkkkkkk
  // - mBitWidth=2:  v2 = v<<6 | v<<4 | v<<2 | v>>0          jk -> jkjkjkjk
  // - mBitWidth=3:  v2 = v<<5 | v<<2 | v>>1                ijk -> ijkijkij
  // - mBitWidth=4:  v2 = v<<4 | v>>0                      hijk -> hijkhijk
  // - mBitWidth=5:  v2 = v<<3 | v>>2                     ghijk -> ghijkghi
  // - mBitWidth=6:  v2 = v<<2 | v>>4                    fghijk -> fghijkfg
  // - mBitWidth=7:  v2 = v<<1 | v>>6                   efghijk -> efghijke
  // - mBitWidth=8:  v2 = v>>0                         defghijk -> defghijk
  // - mBitWidth=9:  v2 = v>>1                        cdefghijk -> cdefghij
  // - mBitWidth=10: v2 = v>>2                       bcdefghijk -> bcdefghi
  // - mBitWidth=11: v2 = v>>3                      abcdefghijk -> abcdefgh
  // - etc.
  //
  uint8_t v2 = 0;
  int32_t i;  // must be a signed integer
  for (i = 8 - mBitWidth; i > 0; i -= mBitWidth) {
    v2 |= v << uint32_t(i);
  }
  v2 |= v >> uint32_t(-i);
  return v2;
}

MOZ_ALWAYS_INLINE uint8_t BitFields::Value::GetAlpha(uint32_t aValue,
                                                     bool& aHasAlphaOut) const {
  if (mMask == 0x0) {
    return 0xff;
  }
  aHasAlphaOut = true;
  return Get(aValue);
}

MOZ_ALWAYS_INLINE uint8_t BitFields::Value::Get5(uint32_t aValue) const {
  MOZ_ASSERT(mBitWidth == 5);
  uint32_t v = (aValue & mMask) >> mRightShift;
  return (v << 3u) | (v >> 2u);
}

MOZ_ALWAYS_INLINE uint8_t BitFields::Value::Get8(uint32_t aValue) const {
  MOZ_ASSERT(mBitWidth == 8);
  uint32_t v = (aValue & mMask) >> mRightShift;
  return v;
}

void BitFields::SetR5G5B5() {
  mRed.Set(0x7c00);
  mGreen.Set(0x03e0);
  mBlue.Set(0x001f);
}

void BitFields::SetR8G8B8() {
  mRed.Set(0xff0000);
  mGreen.Set(0xff00);
  mBlue.Set(0x00ff);
}

bool BitFields::IsR5G5B5() const {
  return mRed.mBitWidth == 5 && mGreen.mBitWidth == 5 && mBlue.mBitWidth == 5 &&
         mAlpha.mMask == 0x0;
}

bool BitFields::IsR8G8B8() const {
  return mRed.mBitWidth == 8 && mGreen.mBitWidth == 8 && mBlue.mBitWidth == 8 &&
         mAlpha.mMask == 0x0;
}

uint32_t* nsBMPDecoder::RowBuffer() { return mRowBuffer.get() + mCurrentPos; }

void nsBMPDecoder::ClearRowBufferRemainder() {
  int32_t len = mH.mWidth - mCurrentPos;
  memset(RowBuffer(), mMayHaveTransparency ? 0 : 0xFF, len * sizeof(uint32_t));
}

void nsBMPDecoder::FinishRow() {
  mPipe.WriteBuffer(mRowBuffer.get());
  Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
  if (invalidRect) {
    PostInvalidation(invalidRect->mInputSpaceRect,
                     Some(invalidRect->mOutputSpaceRect));
  }
  mCurrentRow--;
}

LexerResult nsBMPDecoder::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::FILE_HEADER:
            return ReadFileHeader(aData, aLength);
          case State::INFO_HEADER_SIZE:
            return ReadInfoHeaderSize(aData, aLength);
          case State::INFO_HEADER_REST:
            return ReadInfoHeaderRest(aData, aLength);
          case State::BITFIELDS:
            return ReadBitfields(aData, aLength);
          case State::SKIP_TO_COLOR_PROFILE:
            return Transition::ContinueUnbuffered(State::SKIP_TO_COLOR_PROFILE);
          case State::FOUND_COLOR_PROFILE:
            return Transition::To(State::COLOR_PROFILE,
                                  mH.mColorSpace.mProfile.mLength);
          case State::COLOR_PROFILE:
            return ReadColorProfile(aData, aLength);
          case State::ALLOCATE_SURFACE:
            return AllocateSurface();
          case State::COLOR_TABLE:
            return ReadColorTable(aData, aLength);
          case State::GAP:
            return SkipGap();
          case State::AFTER_GAP:
            return AfterGap();
          case State::PIXEL_ROW:
            return ReadPixelRow(aData);
          case State::RLE_SEGMENT:
            return ReadRLESegment(aData);
          case State::RLE_DELTA:
            return ReadRLEDelta(aData);
          case State::RLE_ABSOLUTE:
            return ReadRLEAbsolute(aData, aLength);
          default:
            MOZ_CRASH("Unknown State");
        }
      });
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadFileHeader(
    const char* aData, size_t aLength) {
  mPreGapLength += aLength;

  bool signatureOk = aData[0] == 'B' && aData[1] == 'M';
  if (!signatureOk) {
    return Transition::TerminateFailure();
  }

  // We ignore the filesize (aData + 2) and reserved (aData + 6) fields.

  mH.mDataOffset = LittleEndian::readUint32(aData + 10);

  return Transition::To(State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH);
}

// We read the info header in two steps: (a) read the mBIHSize field to
// determine how long the header is; (b) read the rest of the header.
LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadInfoHeaderSize(
    const char* aData, size_t aLength) {
  mH.mBIHSize = LittleEndian::readUint32(aData);

  // Data offset can be wrong so fix it using the BIH size.
  if (!mIsForClipboard && mH.mDataOffset < mPreGapLength + mH.mBIHSize) {
    mH.mDataOffset = mPreGapLength + mH.mBIHSize;
  }

  mPreGapLength += aLength;

  bool bihSizeOk = mH.mBIHSize == InfoHeaderLength::WIN_V2 ||
                   mH.mBIHSize == InfoHeaderLength::WIN_V3 ||
                   mH.mBIHSize == InfoHeaderLength::WIN_V4 ||
                   mH.mBIHSize == InfoHeaderLength::WIN_V5 ||
                   (mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN &&
                    mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX);
  if (!bihSizeOk) {
    return Transition::TerminateFailure();
  }
  // ICO BMPs must have a WinBMPv3 header. nsICODecoder should have already
  // terminated decoding if this isn't the case.
  MOZ_ASSERT_IF(mIsWithinICO, mH.mBIHSize == InfoHeaderLength::WIN_V3);

  return Transition::To(State::INFO_HEADER_REST,
                        mH.mBIHSize - BIHSIZE_FIELD_LENGTH);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadInfoHeaderRest(
    const char* aData, size_t aLength) {
  mPreGapLength += aLength;

  // |mWidth| and |mHeight| may be signed (Windows) or unsigned (OS/2). We just
  // read as unsigned because in practice that's good enough.
  if (mH.mBIHSize == InfoHeaderLength::WIN_V2) {
    mH.mWidth = LittleEndian::readUint16(aData + 0);
    mH.mHeight = LittleEndian::readUint16(aData + 2);
    // We ignore the planes (aData + 4) field; it should always be 1.
    mH.mBpp = LittleEndian::readUint16(aData + 6);
  } else {
    mH.mWidth = LittleEndian::readUint32(aData + 0);
    mH.mHeight = LittleEndian::readUint32(aData + 4);
    // We ignore the planes (aData + 4) field; it should always be 1.
    mH.mBpp = LittleEndian::readUint16(aData + 10);

    // For OS2-BMPv2 the info header may be as little as 16 bytes, so be
    // careful for these fields.
    mH.mCompression = aLength >= 16 ? LittleEndian::readUint32(aData + 12) : 0;
    mH.mImageSize = aLength >= 20 ? LittleEndian::readUint32(aData + 16) : 0;
    // We ignore the xppm (aData + 20) and yppm (aData + 24) fields.
    mH.mNumColors = aLength >= 32 ? LittleEndian::readUint32(aData + 28) : 0;
    // We ignore the important_colors (aData + 36) field.

    // Read color management properties we may need later.
    mH.mCsType =
        aLength >= 56
            ? static_cast<InfoColorSpace>(LittleEndian::readUint32(aData + 52))
            : InfoColorSpace::SRGB;
    mH.mCsIntent = aLength >= 108 ? static_cast<InfoColorIntent>(
                                        LittleEndian::readUint32(aData + 104))
                                  : InfoColorIntent::IMAGES;

    switch (mH.mCsType) {
      case InfoColorSpace::CALIBRATED_RGB:
        if (aLength >= 104) {
          ReadCalRgbEndpoint(aData, 56, 92, mH.mColorSpace.mCalibrated.mRed);
          ReadCalRgbEndpoint(aData, 68, 96, mH.mColorSpace.mCalibrated.mGreen);
          ReadCalRgbEndpoint(aData, 80, 100, mH.mColorSpace.mCalibrated.mBlue);
        } else {
          mH.mCsType = InfoColorSpace::SRGB;
        }
        break;
      case InfoColorSpace::EMBEDDED:
        if (aLength >= 116) {
          mH.mColorSpace.mProfile.mOffset =
              LittleEndian::readUint32(aData + 108);
          mH.mColorSpace.mProfile.mLength =
              LittleEndian::readUint32(aData + 112);
        } else {
          mH.mCsType = InfoColorSpace::SRGB;
        }
        break;
      case InfoColorSpace::LINKED:
      case InfoColorSpace::SRGB:
      case InfoColorSpace::WIN:
      default:
        // Nothing to be done at this time.
        break;
    }

    // For WinBMPv4, WinBMPv5 and (possibly) OS2-BMPv2 there are additional
    // fields in the info header which we ignore, with the possible exception
    // of the color bitfields (see below).
  }

  // The height for BMPs embedded inside an ICO includes spaces for the AND
  // mask even if it is not present, thus we need to adjust for that here.
  if (mIsWithinICO) {
    // XXX(seth): Should we really be writing the absolute value from
    // the BIH below? Seems like this could be problematic for inverted BMPs.
    mH.mHeight = abs(mH.mHeight) / 2;
  }

  // Run with MOZ_LOG=BMPDecoder:5 set to see this output.
  MOZ_LOG(sBMPLog, LogLevel::Debug,
          ("BMP: bihsize=%u, %d x %d, bpp=%u, compression=%u, colors=%u, "
           "data-offset=%u\n",
           mH.mBIHSize, mH.mWidth, mH.mHeight, uint32_t(mH.mBpp),
           mH.mCompression, mH.mNumColors, mH.mDataOffset));

  // BMPs with negative width are invalid. Also, reject extremely wide images
  // to keep the math sane. And reject INT_MIN as a height because you can't
  // get its absolute value (because -INT_MIN is one more than INT_MAX).
  const int32_t k64KWidth = 0x0000FFFF;
  bool sizeOk =
      0 <= mH.mWidth && mH.mWidth <= k64KWidth && mH.mHeight != INT_MIN;
  if (!sizeOk) {
    return Transition::TerminateFailure();
  }

  // Check mBpp and mCompression.
  bool bppCompressionOk =
      (mH.mCompression == Compression::RGB &&
       (mH.mBpp == 1 || mH.mBpp == 4 || mH.mBpp == 8 || mH.mBpp == 16 ||
        mH.mBpp == 24 || mH.mBpp == 32)) ||
      (mH.mCompression == Compression::RLE8 && mH.mBpp == 8) ||
      (mH.mCompression == Compression::RLE4 && mH.mBpp == 4) ||
      (mH.mCompression == Compression::BITFIELDS &&
       // For BITFIELDS compression we require an exact match for one of the
       // WinBMP BIH sizes; this clearly isn't an OS2 BMP.
       (mH.mBIHSize == InfoHeaderLength::WIN_V3 ||
        mH.mBIHSize == InfoHeaderLength::WIN_V4 ||
        mH.mBIHSize == InfoHeaderLength::WIN_V5) &&
       (mH.mBpp == 16 || mH.mBpp == 32));
  if (!bppCompressionOk) {
    return Transition::TerminateFailure();
  }

  // Initialize our current row to the top of the image.
  mCurrentRow = AbsoluteHeight();

  // Round it up to the nearest byte count, then pad to 4-byte boundary.
  // Compute this even for a metadate decode because GetCompressedImageSize()
  // relies on it.
  mPixelRowSize = (mH.mBpp * mH.mWidth + 7) / 8;
  uint32_t surplus = mPixelRowSize % 4;
  if (surplus != 0) {
    mPixelRowSize += 4 - surplus;
  }

  size_t bitFieldsLengthStillToRead = 0;
  if (mH.mCompression == Compression::BITFIELDS) {
    // Need to read bitfields.
    if (mH.mBIHSize >= InfoHeaderLength::WIN_V4) {
      // Bitfields are present in the info header, so we can read them
      // immediately.
      mBitFields.ReadFromHeader(aData + 36, /* aReadAlpha = */ true);

      // If this came from the clipboard, then we know that even if the header
      // explicitly includes the bitfield masks, we need to add an additional
      // offset for the start of the RGB data.
      if (mIsForClipboard) {
        mH.mDataOffset += BitFields::LENGTH;
      }
    } else {
      // Bitfields are present after the info header, so we will read them in
      // ReadBitfields().
      bitFieldsLengthStillToRead = BitFields::LENGTH;
    }
  } else if (mH.mBpp == 16) {
    // No bitfields specified; use the default 5-5-5 values.
    mBitFields.SetR5G5B5();
  } else if (mH.mBpp == 32) {
    // No bitfields specified; use the default 8-8-8 values.
    mBitFields.SetR8G8B8();
  }

  return Transition::To(State::BITFIELDS, bitFieldsLengthStillToRead);
}

void BitFields::ReadFromHeader(const char* aData, bool aReadAlpha) {
  mRed.Set(LittleEndian::readUint32(aData + 0));
  mGreen.Set(LittleEndian::readUint32(aData + 4));
  mBlue.Set(LittleEndian::readUint32(aData + 8));
  if (aReadAlpha) {
    mAlpha.Set(LittleEndian::readUint32(aData + 12));
  }
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadBitfields(
    const char* aData, size_t aLength) {
  mPreGapLength += aLength;

  // If aLength is zero there are no bitfields to read, or we already read them
  // in ReadInfoHeader().
  if (aLength != 0) {
    mBitFields.ReadFromHeader(aData, /* aReadAlpha = */ false);
  }

  // Note that RLE-encoded BMPs might be transparent because the 'delta' mode
  // can skip pixels and cause implicit transparency.
  mMayHaveTransparency = mIsWithinICO || mH.mCompression == Compression::RLE8 ||
                         mH.mCompression == Compression::RLE4 ||
                         (mH.mCompression == Compression::BITFIELDS &&
                          mBitFields.mAlpha.IsPresent());
  if (mMayHaveTransparency) {
    PostHasTransparency();
  }

  // Post our size to the superclass.
  PostSize(mH.mWidth, AbsoluteHeight());
  if (HasError()) {
    return Transition::TerminateFailure();
  }

  // We've now read all the headers. If we're doing a metadata decode, we're
  // done.
  if (IsMetadataDecode()) {
    return Transition::TerminateSuccess();
  }

  // Set up the color table, if present; it'll be filled in by ReadColorTable().
  if (mH.mBpp <= 8) {
    mNumColors = 1 << mH.mBpp;
    if (0 < mH.mNumColors && mH.mNumColors < mNumColors) {
      mNumColors = mH.mNumColors;
    }

    // Always allocate and zero 256 entries, even though mNumColors might be
    // smaller, because the file might erroneously index past mNumColors.
    mColors = MakeUniqueFallible<ColorTableEntry[]>(256);
    if (NS_WARN_IF(!mColors)) {
      return Transition::TerminateFailure();
    }
    memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry));

    // OS/2 Bitmaps have no padding byte.
    mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4;
  }

  if (mCMSMode != CMSMode::Off) {
    switch (mH.mCsType) {
      case InfoColorSpace::EMBEDDED:
        return SeekColorProfile(aLength);
      case InfoColorSpace::CALIBRATED_RGB:
        PrepareCalibratedColorProfile();
        break;
      case InfoColorSpace::SRGB:
      case InfoColorSpace::WIN:
        MOZ_LOG(sBMPLog, LogLevel::Debug, ("using sRGB color profile\n"));
        if (mColors) {
          // We will transform the color table instead of the output pixels.
          mTransform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8);
        } else {
          mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
        }
        break;
      case InfoColorSpace::LINKED:
      default:
        // Not supported, no color management.
        MOZ_LOG(sBMPLog, LogLevel::Debug, ("color space type not provided\n"));
        break;
    }
  }

  return Transition::To(State::ALLOCATE_SURFACE, 0);
}

void nsBMPDecoder::PrepareCalibratedColorProfile() {
  // BMP does not define a white point. Use the same as sRGB. This matches what
  // Chrome does as well.
  qcms_CIE_xyY white_point = qcms_white_point_sRGB();

  qcms_CIE_xyYTRIPLE primaries;
  float redGamma =
      CalRbgEndpointToQcms(mH.mColorSpace.mCalibrated.mRed, primaries.red);
  float greenGamma =
      CalRbgEndpointToQcms(mH.mColorSpace.mCalibrated.mGreen, primaries.green);
  float blueGamma =
      CalRbgEndpointToQcms(mH.mColorSpace.mCalibrated.mBlue, primaries.blue);

  // Explicitly verify the profile because sometimes the values from the BMP
  // header are just garbage.
  mInProfile = qcms_profile_create_rgb_with_gamma_set(
      white_point, primaries, redGamma, greenGamma, blueGamma);
  if (mInProfile && qcms_profile_is_bogus(mInProfile)) {
    // Bad profile, just use sRGB instead. Release the profile here, so that
    // our destructor doesn't assume we are the owner for the transform.
    qcms_profile_release(mInProfile);
    mInProfile = nullptr;
  }

  if (mInProfile) {
    MOZ_LOG(sBMPLog, LogLevel::Debug, ("using calibrated RGB color profile\n"));
    PrepareColorProfileTransform();
  } else {
    MOZ_LOG(sBMPLog, LogLevel::Debug,
            ("failed to create calibrated RGB color profile, using sRGB\n"));
    if (mColors) {
      // We will transform the color table instead of the output pixels.
      mTransform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8);
    } else {
      mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
    }
  }
}

void nsBMPDecoder::PrepareColorProfileTransform() {
  if (!mInProfile || !GetCMSOutputProfile()) {
    return;
  }

  qcms_data_type inType;
  qcms_data_type outType;
  if (mColors) {
    // We will transform the color table instead of the output pixels.
    inType = QCMS_DATA_RGB_8;
    outType = QCMS_DATA_RGB_8;
  } else {
    inType = gfxPlatform::GetCMSOSRGBAType();
    outType = inType;
  }

  qcms_intent intent;
  switch (mH.mCsIntent) {
    case InfoColorIntent::BUSINESS:
      intent = QCMS_INTENT_SATURATION;
      break;
    case InfoColorIntent::GRAPHICS:
      intent = QCMS_INTENT_RELATIVE_COLORIMETRIC;
      break;
    case InfoColorIntent::ABS_COLORIMETRIC:
      intent = QCMS_INTENT_ABSOLUTE_COLORIMETRIC;
      break;
    case InfoColorIntent::IMAGES:
    default:
      intent = QCMS_INTENT_PERCEPTUAL;
      break;
  }

  mTransform = qcms_transform_create(mInProfile, inType, GetCMSOutputProfile(),
                                     outType, intent);
  if (!mTransform) {
    MOZ_LOG(sBMPLog, LogLevel::Debug,
            ("failed to create color profile transform\n"));
  }
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::SeekColorProfile(
    size_t aLength) {
  // The offset needs to be at least after the color table.
  uint32_t offset = mH.mColorSpace.mProfile.mOffset;
  if (offset <= mH.mBIHSize + aLength + mNumColors * mBytesPerColor ||
      mH.mColorSpace.mProfile.mLength == 0) {
    return Transition::To(State::ALLOCATE_SURFACE, 0);
  }

  // We have already read the header and bitfields.
  offset -= mH.mBIHSize + aLength;

  // We need to skip ahead to search for the embedded color profile. We want
  // to return to this point once we read it.
  mReturnIterator = mLexer.Clone(*mIterator, SIZE_MAX);
  if (!mReturnIterator) {
    return Transition::TerminateFailure();
  }

  return Transition::ToUnbuffered(State::FOUND_COLOR_PROFILE,
                                  State::SKIP_TO_COLOR_PROFILE, offset);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadColorProfile(
    const char* aData, size_t aLength) {
  mInProfile = qcms_profile_from_memory(aData, aLength);
  if (mInProfile) {
    MOZ_LOG(sBMPLog, LogLevel::Debug, ("using embedded color profile\n"));
    PrepareColorProfileTransform();
  }

  // Jump back to where we left off.
  mIterator = std::move(mReturnIterator);
  return Transition::To(State::ALLOCATE_SURFACE, 0);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::AllocateSurface() {
  SurfaceFormat format;
  SurfacePipeFlags pipeFlags = SurfacePipeFlags();

  if (mMayHaveTransparency) {
    format = SurfaceFormat::OS_RGBA;
    if (!(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) {
      pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA;
    }
  } else {
    format = SurfaceFormat::OS_RGBX;
  }

  if (mH.mHeight >= 0) {
    // BMPs store their rows in reverse order, so we may need to flip.
    pipeFlags |= SurfacePipeFlags::FLIP_VERTICALLY;
  }

  mRowBuffer.reset(new (fallible) uint32_t[mH.mWidth]);
  if (!mRowBuffer) {
    return Transition::TerminateFailure();
  }

  // Only give the color transform to the SurfacePipe if we are not transforming
  // the color table in advance.
  qcms_transform* transform = mColors ? nullptr : mTransform;

  Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
      this, Size(), OutputSize(), FullFrame(), format, format, Nothing(),
      transform, pipeFlags);
  if (!pipe) {
    return Transition::TerminateFailure();
  }

  mPipe = std::move(*pipe);
  ClearRowBufferRemainder();
  return Transition::To(State::COLOR_TABLE, mNumColors * mBytesPerColor);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadColorTable(
    const char* aData, size_t aLength) {
  MOZ_ASSERT_IF(aLength != 0, mNumColors > 0 && mColors);

  mPreGapLength += aLength;

  for (uint32_t i = 0; i < mNumColors; i++) {
    // The format is BGR or BGR0.
    mColors[i].mBlue = uint8_t(aData[0]);
    mColors[i].mGreen = uint8_t(aData[1]);
    mColors[i].mRed = uint8_t(aData[2]);
    aData += mBytesPerColor;
  }

  // If we have a color table and a transform, we can avoid transforming each
  // pixel by doing the table in advance. We color manage every entry in the
  // table, even if it is smaller in case the BMP is malformed and overruns
  // its stated color range.
  if (mColors && mTransform) {
    qcms_transform_data(mTransform, mColors.get(), mColors.get(), 256);
  }

  // If we are decoding a BMP from the clipboard, we did not know the data
  // offset in advance. It is just defined as after the header and color table.
  if (mIsForClipboard) {
    mH.mDataOffset += mPreGapLength;
  }

  // We know how many bytes we've read so far (mPreGapLength) and we know the
  // offset of the pixel data (mH.mDataOffset), so we can determine the length
  // of the gap (possibly zero) between the color table and the pixel data.
  //
  // If the gap is negative the file must be malformed (e.g. mH.mDataOffset
  // points into the middle of the color palette instead of past the end) and
  // we give up.
  if (mPreGapLength > mH.mDataOffset) {
    return Transition::TerminateFailure();
  }

  uint32_t gapLength = mH.mDataOffset - mPreGapLength;

  return Transition::ToUnbuffered(State::AFTER_GAP, State::GAP, gapLength);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::SkipGap() {
  return Transition::ContinueUnbuffered(State::GAP);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::AfterGap() {
  // If there are no pixels we can stop.
  //
  // XXX: normally, if there are no pixels we will have stopped decoding before
  // now, outside of this decoder. However, if the BMP is within an ICO file,
  // it's possible that the ICO claimed the image had a non-zero size while the
  // BMP claims otherwise. This test is to catch that awkward case. If we ever
  // come up with a more general solution to this ICO-and-BMP-disagree-on-size
  // problem, this test can be removed.
  if (mH.mWidth == 0 || mH.mHeight == 0) {
    return Transition::TerminateSuccess();
  }

  bool hasRLE = mH.mCompression == Compression::RLE8 ||
                mH.mCompression == Compression::RLE4;
  return hasRLE ? Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH)
                : Transition::To(State::PIXEL_ROW, mPixelRowSize);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadPixelRow(
    const char* aData) {
  MOZ_ASSERT(mCurrentRow > 0);
  MOZ_ASSERT(mCurrentPos == 0);

  const uint8_t* src = reinterpret_cast<const uint8_t*>(aData);
  uint32_t* dst = RowBuffer();
  uint32_t lpos = mH.mWidth;
  switch (mH.mBpp) {
    case 1:
      while (lpos > 0) {
        int8_t bit;
        uint8_t idx;
        for (bit = 7; bit >= 0 && lpos > 0; bit--) {
          idx = (*src >> bit) & 1;
          SetPixel(dst, idx, mColors);
          --lpos;
        }
        ++src;
      }
      break;

    case 4:
      while (lpos > 0) {
        Set4BitPixel(dst, *src, lpos, mColors);
        ++src;
      }
      break;

    case 8:
      while (lpos > 0) {
        SetPixel(dst, *src, mColors);
        --lpos;
        ++src;
      }
      break;

    case 16:
      if (mBitFields.IsR5G5B5()) {
        // Specialize this common case.
        while (lpos > 0) {
          uint16_t val = LittleEndian::readUint16(src);
          SetPixel(dst, mBitFields.mRed.Get5(val), mBitFields.mGreen.Get5(val),
                   mBitFields.mBlue.Get5(val));
          --lpos;
          src += 2;
        }
      } else {
        bool anyHasAlpha = false;
        while (lpos > 0) {
          uint16_t val = LittleEndian::readUint16(src);
          SetPixel(dst, mBitFields.mRed.Get(val), mBitFields.mGreen.Get(val),
                   mBitFields.mBlue.Get(val),
                   mBitFields.mAlpha.GetAlpha(val, anyHasAlpha));
          --lpos;
          src += 2;
        }
        if (anyHasAlpha) {
          MOZ_ASSERT(mMayHaveTransparency);
          mDoesHaveTransparency = true;
        }
      }
      break;

    case 24:
      while (lpos > 0) {
        SetPixel(dst, src[2], src[1], src[0]);
        --lpos;
        src += 3;
      }
      break;

    case 32:
      if (mH.mCompression == Compression::RGB && mIsWithinICO &&
          mH.mBpp == 32) {
        // This is a special case only used for 32bpp WinBMPv3-ICO files, which
        // could be in either 0RGB or ARGB format. We start by assuming it's
        // an 0RGB image. If we hit a non-zero alpha value, then we know it's
        // actually an ARGB image, and change tack accordingly.
        // (Note: a fully-transparent ARGB image is indistinguishable from a
        // 0RGB image, and we will render such an image as a 0RGB image, i.e.
        // opaquely. This is unlikely to be a problem in practice.)
        while (lpos > 0) {
          if (!mDoesHaveTransparency && src[3] != 0) {
            // Up until now this looked like an 0RGB image, but we now know
            // it's actually an ARGB image. Which means every pixel we've seen
            // so far has been fully transparent. So we go back and redo them.

            // Tell the SurfacePipe to go back to the start.
            mPipe.ResetToFirstRow();

            // Redo the complete rows we've already done.
            MOZ_ASSERT(mCurrentPos == 0);
            int32_t currentRow = mCurrentRow;
            mCurrentRow = AbsoluteHeight();
            ClearRowBufferRemainder();
            while (mCurrentRow > currentRow) {
              FinishRow();
            }

            // Reset the row pointer back to where we started.
            dst = RowBuffer() + (mH.mWidth - lpos);

            MOZ_ASSERT(mMayHaveTransparency);
            mDoesHaveTransparency = true;
          }

          // If mDoesHaveTransparency is false, treat this as an 0RGB image.
          // Otherwise, treat this as an ARGB image.
          SetPixel(dst, src[2], src[1], src[0],
                   mDoesHaveTransparency ? src[3] : 0xff);
          src += 4;
          --lpos;
        }
      } else if (mBitFields.IsR8G8B8()) {
        // Specialize this common case.
        while (lpos > 0) {
          uint32_t val = LittleEndian::readUint32(src);
          SetPixel(dst, mBitFields.mRed.Get8(val), mBitFields.mGreen.Get8(val),
                   mBitFields.mBlue.Get8(val));
          --lpos;
          src += 4;
        }
      } else {
        bool anyHasAlpha = false;
        while (lpos > 0) {
          uint32_t val = LittleEndian::readUint32(src);
          SetPixel(dst, mBitFields.mRed.Get(val), mBitFields.mGreen.Get(val),
                   mBitFields.mBlue.Get(val),
                   mBitFields.mAlpha.GetAlpha(val, anyHasAlpha));
          --lpos;
          src += 4;
        }
        if (anyHasAlpha) {
          MOZ_ASSERT(mMayHaveTransparency);
          mDoesHaveTransparency = true;
        }
      }
      break;

    default:
      MOZ_CRASH("Unsupported color depth; earlier check didn't catch it?");
  }

  FinishRow();
  return mCurrentRow == 0 ? Transition::TerminateSuccess()
                          : Transition::To(State::PIXEL_ROW, mPixelRowSize);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadRLESegment(
    const char* aData) {
  if (mCurrentRow == 0) {
    return Transition::TerminateSuccess();
  }

  uint8_t byte1 = uint8_t(aData[0]);
  uint8_t byte2 = uint8_t(aData[1]);

  if (byte1 != RLE::ESCAPE) {
    // Encoded mode consists of two bytes: byte1 specifies the number of
    // consecutive pixels to be drawn using the color index contained in
    // byte2.
    //
    // Work around bitmaps that specify too many pixels.
    uint32_t pixelsNeeded = std::min<uint32_t>(mH.mWidth - mCurrentPos, byte1);
    if (pixelsNeeded) {
      uint32_t* dst = RowBuffer();
      mCurrentPos += pixelsNeeded;
      if (mH.mCompression == Compression::RLE8) {
        do {
          SetPixel(dst, byte2, mColors);
          pixelsNeeded--;
        } while (pixelsNeeded);
      } else {
        do {
          Set4BitPixel(dst, byte2, pixelsNeeded, mColors);
        } while (pixelsNeeded);
      }
    }
    return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
  }

  if (byte2 == RLE::ESCAPE_EOL) {
    ClearRowBufferRemainder();
    mCurrentPos = 0;
    FinishRow();
    return mCurrentRow == 0
               ? Transition::TerminateSuccess()
               : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
  }

  if (byte2 == RLE::ESCAPE_EOF) {
    return Transition::TerminateSuccess();
  }

  if (byte2 == RLE::ESCAPE_DELTA) {
    return Transition::To(State::RLE_DELTA, RLE::DELTA_LENGTH);
  }

  // Absolute mode. |byte2| gives the number of pixels. The length depends on
  // whether it's 4-bit or 8-bit RLE. Also, the length must be even (and zero
  // padding is used to achieve this when necessary).
  MOZ_ASSERT(mAbsoluteModeNumPixels == 0);
  mAbsoluteModeNumPixels = byte2;
  uint32_t length = byte2;
  if (mH.mCompression == Compression::RLE4) {
    length = (length + 1) / 2;  // halve, rounding up
  }
  if (length & 1) {
    length++;
  }
  return Transition::To(State::RLE_ABSOLUTE, length);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadRLEDelta(
    const char* aData) {
  // Delta encoding makes it possible to skip pixels making part of the image
  // transparent.
  MOZ_ASSERT(mMayHaveTransparency);
  mDoesHaveTransparency = true;

  // Clear the skipped pixels. (This clears to the end of the row,
  // which is perfect if there's a Y delta and harmless if not).
  ClearRowBufferRemainder();

  // Handle the XDelta.
  mCurrentPos += uint8_t(aData[0]);
  if (mCurrentPos > mH.mWidth) {
    mCurrentPos = mH.mWidth;
  }

  // Handle the Y Delta.
  int32_t yDelta = std::min<int32_t>(uint8_t(aData[1]), mCurrentRow);
  if (yDelta > 0) {
    // Commit the current row (the first of the skipped rows).
    FinishRow();

    // Clear and commit the remaining skipped rows. We want to be careful not
    // to change mCurrentPos here.
    memset(mRowBuffer.get(), 0, mH.mWidth * sizeof(uint32_t));
    for (int32_t line = 1; line < yDelta; line++) {
      FinishRow();
    }
  }

  return mCurrentRow == 0
             ? Transition::TerminateSuccess()
             : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
}

LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadRLEAbsolute(
    const char* aData, size_t aLength) {
  uint32_t n = mAbsoluteModeNumPixels;
  mAbsoluteModeNumPixels = 0;

  if (mCurrentPos + n > uint32_t(mH.mWidth)) {
    // Some DIB RLE8 encoders count a padding byte as the absolute mode
    // pixel number at the end of the row.
    if (mH.mCompression == Compression::RLE8 && n > 0 && (n & 1) == 0 &&
        mCurrentPos + n - uint32_t(mH.mWidth) == 1 && aLength > 0 &&
        aData[aLength - 1] == 0) {
      n--;
    } else {
      // Bad data. Stop decoding; at least part of the image may have been
      // decoded.
      return Transition::TerminateSuccess();
    }
  }

  // In absolute mode, n represents the number of pixels that follow, each of
  // which contains the color index of a single pixel.
  uint32_t* dst = RowBuffer();
  uint32_t iSrc = 0;
  uint32_t* oldPos = dst;
  if (mH.mCompression == Compression::RLE8) {
    while (n > 0) {
      SetPixel(dst, aData[iSrc], mColors);
      n--;
      iSrc++;
    }
  } else {
    while (n > 0) {
      Set4BitPixel(dst, aData[iSrc], n, mColors);
      iSrc++;
    }
  }
  mCurrentPos += dst - oldPos;

  // We should read all the data (unless the last byte is zero padding).
  MOZ_ASSERT(iSrc == aLength - 1 || iSrc == aLength);

  return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH);
}

}  // namespace image
}  // namespace mozilla