/src/mozilla-central/image/decoders/nsBMPDecoder.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | // This is a cross-platform BMP Decoder, which should work everywhere, |
8 | | // including big-endian machines like the PowerPC. |
9 | | // |
10 | | // BMP is a format that has been extended multiple times. To understand the |
11 | | // decoder you need to understand this history. The summary of the history |
12 | | // below was determined from the following documents. |
13 | | // |
14 | | // - http://www.fileformat.info/format/bmp/egff.htm |
15 | | // - http://www.fileformat.info/format/os2bmp/egff.htm |
16 | | // - http://fileformats.archiveteam.org/wiki/BMP |
17 | | // - http://fileformats.archiveteam.org/wiki/OS/2_BMP |
18 | | // - https://en.wikipedia.org/wiki/BMP_file_format |
19 | | // - https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png |
20 | | // |
21 | | // WINDOWS VERSIONS OF THE BMP FORMAT |
22 | | // ---------------------------------- |
23 | | // WinBMPv1. |
24 | | // - This version is no longer used and can be ignored. |
25 | | // |
26 | | // WinBMPv2. |
27 | | // - First is a 14 byte file header that includes: the magic number ("BM"), |
28 | | // file size, and offset to the pixel data (|mDataOffset|). |
29 | | // - Next is a 12 byte info header which includes: the info header size |
30 | | // (mBIHSize), width, height, number of color planes, and bits-per-pixel |
31 | | // (|mBpp|) which must be 1, 4, 8 or 24. |
32 | | // - Next is the semi-optional color table, which has length 2^|mBpp| and has 3 |
33 | | // bytes per value (BGR). The color table is required if |mBpp| is 1, 4, or 8. |
34 | | // - Next is an optional gap. |
35 | | // - Next is the pixel data, which is pointed to by |mDataOffset|. |
36 | | // |
37 | | // WinBMPv3. This is the most widely used version. |
38 | | // - It changed the info header to 40 bytes by taking the WinBMPv2 info |
39 | | // header, enlargening its width and height fields, and adding more fields |
40 | | // including: a compression type (|mCompression|) and number of colors |
41 | | // (|mNumColors|). |
42 | | // - The semi-optional color table is now 4 bytes per value (BGR0), and its |
43 | | // length is |mNumColors|, or 2^|mBpp| if |mNumColors| is zero. |
44 | | // - |mCompression| can be RGB (i.e. no compression), RLE4 (if |mBpp|==4) or |
45 | | // RLE8 (if |mBpp|==8) values. |
46 | | // |
47 | | // WinBMPv3-NT. A variant of WinBMPv3. |
48 | | // - It did not change the info header layout from WinBMPv3. |
49 | | // - |mBpp| can now be 16 or 32, in which case |mCompression| can be RGB or the |
50 | | // new BITFIELDS value; in the latter case an additional 12 bytes of color |
51 | | // bitfields follow the info header. |
52 | | // |
53 | | // WinBMPv4. |
54 | | // - It extended the info header to 108 bytes, including the 12 bytes of color |
55 | | // mask data from WinBMPv3-NT, plus alpha mask data, and also color-space and |
56 | | // gamma correction fields. |
57 | | // |
58 | | // WinBMPv5. |
59 | | // - It extended the info header to 124 bytes, adding color profile data. |
60 | | // - It also added an optional color profile table after the pixel data (and |
61 | | // another optional gap). |
62 | | // |
63 | | // WinBMPv3-ICO. This is a variant of WinBMPv3. |
64 | | // - It's the BMP format used for BMP images within ICO files. |
65 | | // - The only difference with WinBMPv3 is that if an image is 32bpp and has no |
66 | | // compression, then instead of treating the pixel data as 0RGB it is treated |
67 | | // as ARGB, but only if one or more of the A values are non-zero. |
68 | | // |
69 | | // OS/2 VERSIONS OF THE BMP FORMAT |
70 | | // ------------------------------- |
71 | | // OS2-BMPv1. |
72 | | // - Almost identical to WinBMPv2; the differences are basically ignorable. |
73 | | // |
74 | | // OS2-BMPv2. |
75 | | // - Similar to WinBMPv3. |
76 | | // - The info header is 64 bytes but can be reduced to as little as 16; any |
77 | | // omitted fields are treated as zero. The first 40 bytes of these fields are |
78 | | // nearly identical to the WinBMPv3 info header; the remaining 24 bytes are |
79 | | // different. |
80 | | // - Also adds compression types "Huffman 1D" and "RLE24", which we don't |
81 | | // support. |
82 | | // - We treat OS2-BMPv2 files as if they are WinBMPv3 (i.e. ignore the extra 24 |
83 | | // bytes in the info header), which in practice is good enough. |
84 | | |
85 | | #include "ImageLogging.h" |
86 | | #include "nsBMPDecoder.h" |
87 | | |
88 | | #include <stdlib.h> |
89 | | |
90 | | #include "mozilla/Attributes.h" |
91 | | #include "mozilla/EndianUtils.h" |
92 | | #include "mozilla/Likely.h" |
93 | | |
94 | | #include "nsIInputStream.h" |
95 | | #include "RasterImage.h" |
96 | | #include <algorithm> |
97 | | |
98 | | using namespace mozilla::gfx; |
99 | | |
100 | | namespace mozilla { |
101 | | namespace image { |
102 | | namespace bmp { |
103 | | |
104 | | struct Compression { |
105 | | enum { |
106 | | RGB = 0, |
107 | | RLE8 = 1, |
108 | | RLE4 = 2, |
109 | | BITFIELDS = 3 |
110 | | }; |
111 | | }; |
112 | | |
113 | | // RLE escape codes and constants. |
114 | | struct RLE { |
115 | | enum { |
116 | | ESCAPE = 0, |
117 | | ESCAPE_EOL = 0, |
118 | | ESCAPE_EOF = 1, |
119 | | ESCAPE_DELTA = 2, |
120 | | |
121 | | SEGMENT_LENGTH = 2, |
122 | | DELTA_LENGTH = 2 |
123 | | }; |
124 | | }; |
125 | | |
126 | | } // namespace bmp |
127 | | |
128 | | using namespace bmp; |
129 | | |
130 | | /// Sets the pixel data in aDecoded to the given values. |
131 | | /// @param aDecoded pointer to pixel to be set, will be incremented to point to |
132 | | /// the next pixel. |
133 | | static void |
134 | | SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen, |
135 | | uint8_t aBlue, uint8_t aAlpha = 0xFF) |
136 | 0 | { |
137 | 0 | *aDecoded++ = gfxPackedPixel(aAlpha, aRed, aGreen, aBlue); |
138 | 0 | } |
139 | | |
140 | | static void |
141 | | SetPixel(uint32_t*& aDecoded, uint8_t idx, |
142 | | const UniquePtr<ColorTableEntry[]>& aColors) |
143 | 0 | { |
144 | 0 | SetPixel(aDecoded, |
145 | 0 | aColors[idx].mRed, aColors[idx].mGreen, aColors[idx].mBlue); |
146 | 0 | } |
147 | | |
148 | | /// Sets two (or one if aCount = 1) pixels |
149 | | /// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes |
150 | | /// depending on whether one or two pixels are written. |
151 | | /// @param aData The values for the two pixels |
152 | | /// @param aCount Current count. Is decremented by one or two. |
153 | | static void |
154 | | Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount, |
155 | | const UniquePtr<ColorTableEntry[]>& aColors) |
156 | 0 | { |
157 | 0 | uint8_t idx = aData >> 4; |
158 | 0 | SetPixel(aDecoded, idx, aColors); |
159 | 0 | if (--aCount > 0) { |
160 | 0 | idx = aData & 0xF; |
161 | 0 | SetPixel(aDecoded, idx, aColors); |
162 | 0 | --aCount; |
163 | 0 | } |
164 | 0 | } |
165 | | |
166 | | static mozilla::LazyLogModule sBMPLog("BMPDecoder"); |
167 | | |
168 | | // The length of the mBIHSize field in the info header. |
169 | | static const uint32_t BIHSIZE_FIELD_LENGTH = 4; |
170 | | |
171 | | nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength) |
172 | | : Decoder(aImage) |
173 | | , mLexer(Transition::To(aState, aLength), Transition::TerminateSuccess()) |
174 | | , mIsWithinICO(false) |
175 | | , mMayHaveTransparency(false) |
176 | | , mDoesHaveTransparency(false) |
177 | | , mNumColors(0) |
178 | | , mColors(nullptr) |
179 | | , mBytesPerColor(0) |
180 | | , mPreGapLength(0) |
181 | | , mPixelRowSize(0) |
182 | | , mCurrentRow(0) |
183 | | , mCurrentPos(0) |
184 | | , mAbsoluteModeNumPixels(0) |
185 | 0 | { |
186 | 0 | } |
187 | | |
188 | | // Constructor for normal BMP files. |
189 | | nsBMPDecoder::nsBMPDecoder(RasterImage* aImage) |
190 | | : nsBMPDecoder(aImage, State::FILE_HEADER, FILE_HEADER_LENGTH) |
191 | 0 | { |
192 | 0 | } |
193 | | |
194 | | // Constructor used for WinBMPv3-ICO files, which lack a file header. |
195 | | nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset) |
196 | | : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH) |
197 | 0 | { |
198 | 0 | SetIsWithinICO(); |
199 | 0 |
|
200 | 0 | // Even though the file header isn't present in this case, the dataOffset |
201 | 0 | // field is set as if it is, and so we must increment mPreGapLength |
202 | 0 | // accordingly. |
203 | 0 | mPreGapLength += FILE_HEADER_LENGTH; |
204 | 0 |
|
205 | 0 | // This is the one piece of data we normally get from a BMP file header, so |
206 | 0 | // it must be provided via an argument. |
207 | 0 | mH.mDataOffset = aDataOffset; |
208 | 0 | } |
209 | | |
210 | | nsBMPDecoder::~nsBMPDecoder() |
211 | 0 | { |
212 | 0 | } |
213 | | |
214 | | // Obtains the size of the compressed image resource. |
215 | | int32_t |
216 | | nsBMPDecoder::GetCompressedImageSize() const |
217 | 0 | { |
218 | 0 | // In the RGB case mImageSize might not be set, so compute it manually. |
219 | 0 | MOZ_ASSERT(mPixelRowSize != 0); |
220 | 0 | return mH.mCompression == Compression::RGB |
221 | 0 | ? mPixelRowSize * AbsoluteHeight() |
222 | 0 | : mH.mImageSize; |
223 | 0 | } |
224 | | |
225 | | nsresult |
226 | | nsBMPDecoder::BeforeFinishInternal() |
227 | 0 | { |
228 | 0 | if (!IsMetadataDecode() && !mImageData) { |
229 | 0 | return NS_ERROR_FAILURE; // No image; something went wrong. |
230 | 0 | } |
231 | 0 | |
232 | 0 | return NS_OK; |
233 | 0 | } |
234 | | |
235 | | nsresult |
236 | | nsBMPDecoder::FinishInternal() |
237 | 0 | { |
238 | 0 | // We shouldn't be called in error cases. |
239 | 0 | MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!"); |
240 | 0 |
|
241 | 0 | // We should never make multiple frames. |
242 | 0 | MOZ_ASSERT(GetFrameCount() <= 1, "Multiple BMP frames?"); |
243 | 0 |
|
244 | 0 | // Send notifications if appropriate. |
245 | 0 | if (!IsMetadataDecode() && HasSize()) { |
246 | 0 |
|
247 | 0 | // We should have image data. |
248 | 0 | MOZ_ASSERT(mImageData); |
249 | 0 |
|
250 | 0 | // If it was truncated, fill in the missing pixels as black. |
251 | 0 | while (mCurrentRow > 0) { |
252 | 0 | uint32_t* dst = RowBuffer(); |
253 | 0 | while (mCurrentPos < mH.mWidth) { |
254 | 0 | SetPixel(dst, 0, 0, 0); |
255 | 0 | mCurrentPos++; |
256 | 0 | } |
257 | 0 | mCurrentPos = 0; |
258 | 0 | FinishRow(); |
259 | 0 | } |
260 | 0 |
|
261 | 0 | // Invalidate. |
262 | 0 | nsIntRect r(0, 0, mH.mWidth, AbsoluteHeight()); |
263 | 0 | PostInvalidation(r); |
264 | 0 |
|
265 | 0 | MOZ_ASSERT_IF(mDoesHaveTransparency, mMayHaveTransparency); |
266 | 0 |
|
267 | 0 | // We have transparency if we either detected some in the image itself |
268 | 0 | // (i.e., |mDoesHaveTransparency| is true) or we're in an ICO, which could |
269 | 0 | // mean we have an AND mask that provides transparency (i.e., |mIsWithinICO| |
270 | 0 | // is true). |
271 | 0 | // XXX(seth): We can tell when we create the decoder if the AND mask is |
272 | 0 | // present, so we could be more precise about this. |
273 | 0 | const Opacity opacity = mDoesHaveTransparency || mIsWithinICO |
274 | 0 | ? Opacity::SOME_TRANSPARENCY |
275 | 0 | : Opacity::FULLY_OPAQUE; |
276 | 0 |
|
277 | 0 | PostFrameStop(opacity); |
278 | 0 | PostDecodeDone(); |
279 | 0 | } |
280 | 0 |
|
281 | 0 | return NS_OK; |
282 | 0 | } |
283 | | |
284 | | // ---------------------------------------- |
285 | | // Actual Data Processing |
286 | | // ---------------------------------------- |
287 | | |
288 | | void |
289 | | BitFields::Value::Set(uint32_t aMask) |
290 | 0 | { |
291 | 0 | mMask = aMask; |
292 | 0 |
|
293 | 0 | // Handle this exceptional case first. The chosen values don't matter |
294 | 0 | // (because a mask of zero will always give a value of zero) except that |
295 | 0 | // mBitWidth: |
296 | 0 | // - shouldn't be zero, because that would cause an infinite loop in Get(); |
297 | 0 | // - shouldn't be 5 or 8, because that could cause a false positive match in |
298 | 0 | // IsR5G5B5() or IsR8G8B8(). |
299 | 0 | if (mMask == 0x0) { |
300 | 0 | mRightShift = 0; |
301 | 0 | mBitWidth = 1; |
302 | 0 | return; |
303 | 0 | } |
304 | 0 | |
305 | 0 | // Find the rightmost 1. |
306 | 0 | uint8_t i; |
307 | 0 | for (i = 0; i < 32; i++) { |
308 | 0 | if (mMask & (1 << i)) { |
309 | 0 | break; |
310 | 0 | } |
311 | 0 | } |
312 | 0 | mRightShift = i; |
313 | 0 |
|
314 | 0 | // Now find the leftmost 1 in the same run of 1s. (If there are multiple runs |
315 | 0 | // of 1s -- which isn't valid -- we'll behave as if only the lowest run was |
316 | 0 | // present, which seems reasonable.) |
317 | 0 | for (i = i + 1; i < 32; i++) { |
318 | 0 | if (!(mMask & (1 << i))) { |
319 | 0 | break; |
320 | 0 | } |
321 | 0 | } |
322 | 0 | mBitWidth = i - mRightShift; |
323 | 0 | } |
324 | | |
325 | | MOZ_ALWAYS_INLINE uint8_t |
326 | | BitFields::Value::Get(uint32_t aValue) const |
327 | 0 | { |
328 | 0 | // Extract the unscaled value. |
329 | 0 | uint32_t v = (aValue & mMask) >> mRightShift; |
330 | 0 |
|
331 | 0 | // Idea: to upscale v precisely we need to duplicate its bits, possibly |
332 | 0 | // repeatedly, possibly partially in the last case, from bit 7 down to bit 0 |
333 | 0 | // in v2. For example: |
334 | 0 | // |
335 | 0 | // - mBitWidth=1: v2 = v<<7 | v<<6 | ... | v<<1 | v>>0 k -> kkkkkkkk |
336 | 0 | // - mBitWidth=2: v2 = v<<6 | v<<4 | v<<2 | v>>0 jk -> jkjkjkjk |
337 | 0 | // - mBitWidth=3: v2 = v<<5 | v<<2 | v>>1 ijk -> ijkijkij |
338 | 0 | // - mBitWidth=4: v2 = v<<4 | v>>0 hijk -> hijkhijk |
339 | 0 | // - mBitWidth=5: v2 = v<<3 | v>>2 ghijk -> ghijkghi |
340 | 0 | // - mBitWidth=6: v2 = v<<2 | v>>4 fghijk -> fghijkfg |
341 | 0 | // - mBitWidth=7: v2 = v<<1 | v>>6 efghijk -> efghijke |
342 | 0 | // - mBitWidth=8: v2 = v>>0 defghijk -> defghijk |
343 | 0 | // - mBitWidth=9: v2 = v>>1 cdefghijk -> cdefghij |
344 | 0 | // - mBitWidth=10: v2 = v>>2 bcdefghijk -> bcdefghi |
345 | 0 | // - mBitWidth=11: v2 = v>>3 abcdefghijk -> abcdefgh |
346 | 0 | // - etc. |
347 | 0 | // |
348 | 0 | uint8_t v2 = 0; |
349 | 0 | int32_t i; // must be a signed integer |
350 | 0 | for (i = 8 - mBitWidth; i > 0; i -= mBitWidth) { |
351 | 0 | v2 |= v << uint32_t(i); |
352 | 0 | } |
353 | 0 | v2 |= v >> uint32_t(-i); |
354 | 0 | return v2; |
355 | 0 | } |
356 | | |
357 | | MOZ_ALWAYS_INLINE uint8_t |
358 | | BitFields::Value::GetAlpha(uint32_t aValue, bool& aHasAlphaOut) const |
359 | 0 | { |
360 | 0 | if (mMask == 0x0) { |
361 | 0 | return 0xff; |
362 | 0 | } |
363 | 0 | aHasAlphaOut = true; |
364 | 0 | return Get(aValue); |
365 | 0 | } |
366 | | |
367 | | MOZ_ALWAYS_INLINE uint8_t |
368 | | BitFields::Value::Get5(uint32_t aValue) const |
369 | 0 | { |
370 | 0 | MOZ_ASSERT(mBitWidth == 5); |
371 | 0 | uint32_t v = (aValue & mMask) >> mRightShift; |
372 | 0 | return (v << 3u) | (v >> 2u); |
373 | 0 | } |
374 | | |
375 | | MOZ_ALWAYS_INLINE uint8_t |
376 | | BitFields::Value::Get8(uint32_t aValue) const |
377 | 0 | { |
378 | 0 | MOZ_ASSERT(mBitWidth == 8); |
379 | 0 | uint32_t v = (aValue & mMask) >> mRightShift; |
380 | 0 | return v; |
381 | 0 | } |
382 | | |
383 | | void |
384 | | BitFields::SetR5G5B5() |
385 | 0 | { |
386 | 0 | mRed.Set(0x7c00); |
387 | 0 | mGreen.Set(0x03e0); |
388 | 0 | mBlue.Set(0x001f); |
389 | 0 | } |
390 | | |
391 | | void |
392 | | BitFields::SetR8G8B8() |
393 | 0 | { |
394 | 0 | mRed.Set(0xff0000); |
395 | 0 | mGreen.Set(0xff00); |
396 | 0 | mBlue.Set(0x00ff); |
397 | 0 | } |
398 | | |
399 | | bool |
400 | | BitFields::IsR5G5B5() const |
401 | 0 | { |
402 | 0 | return mRed.mBitWidth == 5 && |
403 | 0 | mGreen.mBitWidth == 5 && |
404 | 0 | mBlue.mBitWidth == 5 && |
405 | 0 | mAlpha.mMask == 0x0; |
406 | 0 | } |
407 | | |
408 | | bool |
409 | | BitFields::IsR8G8B8() const |
410 | 0 | { |
411 | 0 | return mRed.mBitWidth == 8 && |
412 | 0 | mGreen.mBitWidth == 8 && |
413 | 0 | mBlue.mBitWidth == 8 && |
414 | 0 | mAlpha.mMask == 0x0; |
415 | 0 | } |
416 | | |
417 | | uint32_t* |
418 | | nsBMPDecoder::RowBuffer() |
419 | 0 | { |
420 | 0 | if (mDownscaler) { |
421 | 0 | return reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer()) + mCurrentPos; |
422 | 0 | } |
423 | 0 | |
424 | 0 | // Convert from row (1..mHeight) to absolute line (0..mHeight-1). |
425 | 0 | int32_t line = (mH.mHeight < 0) |
426 | 0 | ? -mH.mHeight - mCurrentRow |
427 | 0 | : mCurrentRow - 1; |
428 | 0 | int32_t offset = line * mH.mWidth + mCurrentPos; |
429 | 0 | return reinterpret_cast<uint32_t*>(mImageData) + offset; |
430 | 0 | } |
431 | | |
432 | | void |
433 | | nsBMPDecoder::FinishRow() |
434 | 0 | { |
435 | 0 | if (mDownscaler) { |
436 | 0 | mDownscaler->CommitRow(); |
437 | 0 |
|
438 | 0 | if (mDownscaler->HasInvalidation()) { |
439 | 0 | DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect(); |
440 | 0 | PostInvalidation(invalidRect.mOriginalSizeRect, |
441 | 0 | Some(invalidRect.mTargetSizeRect)); |
442 | 0 | } |
443 | 0 | } else { |
444 | 0 | PostInvalidation(IntRect(0, mCurrentRow, mH.mWidth, 1)); |
445 | 0 | } |
446 | 0 | mCurrentRow--; |
447 | 0 | } |
448 | | |
449 | | LexerResult |
450 | | nsBMPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) |
451 | 0 | { |
452 | 0 | MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); |
453 | 0 |
|
454 | 0 | return mLexer.Lex(aIterator, aOnResume, |
455 | 0 | [=](State aState, const char* aData, size_t aLength) { |
456 | 0 | switch (aState) { |
457 | 0 | case State::FILE_HEADER: return ReadFileHeader(aData, aLength); |
458 | 0 | case State::INFO_HEADER_SIZE: return ReadInfoHeaderSize(aData, aLength); |
459 | 0 | case State::INFO_HEADER_REST: return ReadInfoHeaderRest(aData, aLength); |
460 | 0 | case State::BITFIELDS: return ReadBitfields(aData, aLength); |
461 | 0 | case State::COLOR_TABLE: return ReadColorTable(aData, aLength); |
462 | 0 | case State::GAP: return SkipGap(); |
463 | 0 | case State::AFTER_GAP: return AfterGap(); |
464 | 0 | case State::PIXEL_ROW: return ReadPixelRow(aData); |
465 | 0 | case State::RLE_SEGMENT: return ReadRLESegment(aData); |
466 | 0 | case State::RLE_DELTA: return ReadRLEDelta(aData); |
467 | 0 | case State::RLE_ABSOLUTE: return ReadRLEAbsolute(aData, aLength); |
468 | 0 | default: |
469 | 0 | MOZ_CRASH("Unknown State"); |
470 | 0 | } |
471 | 0 | }); |
472 | 0 | } |
473 | | |
474 | | LexerTransition<nsBMPDecoder::State> |
475 | | nsBMPDecoder::ReadFileHeader(const char* aData, size_t aLength) |
476 | 0 | { |
477 | 0 | mPreGapLength += aLength; |
478 | 0 |
|
479 | 0 | bool signatureOk = aData[0] == 'B' && aData[1] == 'M'; |
480 | 0 | if (!signatureOk) { |
481 | 0 | return Transition::TerminateFailure(); |
482 | 0 | } |
483 | 0 | |
484 | 0 | // We ignore the filesize (aData + 2) and reserved (aData + 6) fields. |
485 | 0 | |
486 | 0 | mH.mDataOffset = LittleEndian::readUint32(aData + 10); |
487 | 0 |
|
488 | 0 | return Transition::To(State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH); |
489 | 0 | } |
490 | | |
491 | | // We read the info header in two steps: (a) read the mBIHSize field to |
492 | | // determine how long the header is; (b) read the rest of the header. |
493 | | LexerTransition<nsBMPDecoder::State> |
494 | | nsBMPDecoder::ReadInfoHeaderSize(const char* aData, size_t aLength) |
495 | 0 | { |
496 | 0 | mPreGapLength += aLength; |
497 | 0 |
|
498 | 0 | mH.mBIHSize = LittleEndian::readUint32(aData); |
499 | 0 |
|
500 | 0 | bool bihSizeOk = mH.mBIHSize == InfoHeaderLength::WIN_V2 || |
501 | 0 | mH.mBIHSize == InfoHeaderLength::WIN_V3 || |
502 | 0 | mH.mBIHSize == InfoHeaderLength::WIN_V4 || |
503 | 0 | mH.mBIHSize == InfoHeaderLength::WIN_V5 || |
504 | 0 | (mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN && |
505 | 0 | mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX); |
506 | 0 | if (!bihSizeOk) { |
507 | 0 | return Transition::TerminateFailure(); |
508 | 0 | } |
509 | 0 | // ICO BMPs must have a WinBMPv3 header. nsICODecoder should have already |
510 | 0 | // terminated decoding if this isn't the case. |
511 | 0 | MOZ_ASSERT_IF(mIsWithinICO, mH.mBIHSize == InfoHeaderLength::WIN_V3); |
512 | 0 |
|
513 | 0 | return Transition::To(State::INFO_HEADER_REST, |
514 | 0 | mH.mBIHSize - BIHSIZE_FIELD_LENGTH); |
515 | 0 | } |
516 | | |
517 | | LexerTransition<nsBMPDecoder::State> |
518 | | nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength) |
519 | 0 | { |
520 | 0 | mPreGapLength += aLength; |
521 | 0 |
|
522 | 0 | // |mWidth| and |mHeight| may be signed (Windows) or unsigned (OS/2). We just |
523 | 0 | // read as unsigned because in practice that's good enough. |
524 | 0 | if (mH.mBIHSize == InfoHeaderLength::WIN_V2) { |
525 | 0 | mH.mWidth = LittleEndian::readUint16(aData + 0); |
526 | 0 | mH.mHeight = LittleEndian::readUint16(aData + 2); |
527 | 0 | // We ignore the planes (aData + 4) field; it should always be 1. |
528 | 0 | mH.mBpp = LittleEndian::readUint16(aData + 6); |
529 | 0 | } else { |
530 | 0 | mH.mWidth = LittleEndian::readUint32(aData + 0); |
531 | 0 | mH.mHeight = LittleEndian::readUint32(aData + 4); |
532 | 0 | // We ignore the planes (aData + 4) field; it should always be 1. |
533 | 0 | mH.mBpp = LittleEndian::readUint16(aData + 10); |
534 | 0 |
|
535 | 0 | // For OS2-BMPv2 the info header may be as little as 16 bytes, so be |
536 | 0 | // careful for these fields. |
537 | 0 | mH.mCompression = aLength >= 16 ? LittleEndian::readUint32(aData + 12) : 0; |
538 | 0 | mH.mImageSize = aLength >= 20 ? LittleEndian::readUint32(aData + 16) : 0; |
539 | 0 | // We ignore the xppm (aData + 20) and yppm (aData + 24) fields. |
540 | 0 | mH.mNumColors = aLength >= 32 ? LittleEndian::readUint32(aData + 28) : 0; |
541 | 0 | // We ignore the important_colors (aData + 36) field. |
542 | 0 |
|
543 | 0 | // For WinBMPv4, WinBMPv5 and (possibly) OS2-BMPv2 there are additional |
544 | 0 | // fields in the info header which we ignore, with the possible exception |
545 | 0 | // of the color bitfields (see below). |
546 | 0 | } |
547 | 0 |
|
548 | 0 | // The height for BMPs embedded inside an ICO includes spaces for the AND |
549 | 0 | // mask even if it is not present, thus we need to adjust for that here. |
550 | 0 | if (mIsWithinICO) { |
551 | 0 | // XXX(seth): Should we really be writing the absolute value from |
552 | 0 | // the BIH below? Seems like this could be problematic for inverted BMPs. |
553 | 0 | mH.mHeight = abs(mH.mHeight) / 2; |
554 | 0 | } |
555 | 0 |
|
556 | 0 | // Run with MOZ_LOG=BMPDecoder:5 set to see this output. |
557 | 0 | MOZ_LOG(sBMPLog, LogLevel::Debug, |
558 | 0 | ("BMP: bihsize=%u, %d x %d, bpp=%u, compression=%u, colors=%u\n", |
559 | 0 | mH.mBIHSize, mH.mWidth, mH.mHeight, uint32_t(mH.mBpp), |
560 | 0 | mH.mCompression, mH.mNumColors)); |
561 | 0 |
|
562 | 0 | // BMPs with negative width are invalid. Also, reject extremely wide images |
563 | 0 | // to keep the math sane. And reject INT_MIN as a height because you can't |
564 | 0 | // get its absolute value (because -INT_MIN is one more than INT_MAX). |
565 | 0 | const int32_t k64KWidth = 0x0000FFFF; |
566 | 0 | bool sizeOk = 0 <= mH.mWidth && mH.mWidth <= k64KWidth && |
567 | 0 | mH.mHeight != INT_MIN; |
568 | 0 | if (!sizeOk) { |
569 | 0 | return Transition::TerminateFailure(); |
570 | 0 | } |
571 | 0 | |
572 | 0 | // Check mBpp and mCompression. |
573 | 0 | bool bppCompressionOk = |
574 | 0 | (mH.mCompression == Compression::RGB && |
575 | 0 | (mH.mBpp == 1 || mH.mBpp == 4 || mH.mBpp == 8 || |
576 | 0 | mH.mBpp == 16 || mH.mBpp == 24 || mH.mBpp == 32)) || |
577 | 0 | (mH.mCompression == Compression::RLE8 && mH.mBpp == 8) || |
578 | 0 | (mH.mCompression == Compression::RLE4 && mH.mBpp == 4) || |
579 | 0 | (mH.mCompression == Compression::BITFIELDS && |
580 | 0 | // For BITFIELDS compression we require an exact match for one of the |
581 | 0 | // WinBMP BIH sizes; this clearly isn't an OS2 BMP. |
582 | 0 | (mH.mBIHSize == InfoHeaderLength::WIN_V3 || |
583 | 0 | mH.mBIHSize == InfoHeaderLength::WIN_V4 || |
584 | 0 | mH.mBIHSize == InfoHeaderLength::WIN_V5) && |
585 | 0 | (mH.mBpp == 16 || mH.mBpp == 32)); |
586 | 0 | if (!bppCompressionOk) { |
587 | 0 | return Transition::TerminateFailure(); |
588 | 0 | } |
589 | 0 | |
590 | 0 | // Initialize our current row to the top of the image. |
591 | 0 | mCurrentRow = AbsoluteHeight(); |
592 | 0 |
|
593 | 0 | // Round it up to the nearest byte count, then pad to 4-byte boundary. |
594 | 0 | // Compute this even for a metadate decode because GetCompressedImageSize() |
595 | 0 | // relies on it. |
596 | 0 | mPixelRowSize = (mH.mBpp * mH.mWidth + 7) / 8; |
597 | 0 | uint32_t surplus = mPixelRowSize % 4; |
598 | 0 | if (surplus != 0) { |
599 | 0 | mPixelRowSize += 4 - surplus; |
600 | 0 | } |
601 | 0 |
|
602 | 0 | size_t bitFieldsLengthStillToRead = 0; |
603 | 0 | if (mH.mCompression == Compression::BITFIELDS) { |
604 | 0 | // Need to read bitfields. |
605 | 0 | if (mH.mBIHSize >= InfoHeaderLength::WIN_V4) { |
606 | 0 | // Bitfields are present in the info header, so we can read them |
607 | 0 | // immediately. |
608 | 0 | mBitFields.ReadFromHeader(aData + 36, /* aReadAlpha = */ true); |
609 | 0 | } else { |
610 | 0 | // Bitfields are present after the info header, so we will read them in |
611 | 0 | // ReadBitfields(). |
612 | 0 | bitFieldsLengthStillToRead = BitFields::LENGTH; |
613 | 0 | } |
614 | 0 | } else if (mH.mBpp == 16) { |
615 | 0 | // No bitfields specified; use the default 5-5-5 values. |
616 | 0 | mBitFields.SetR5G5B5(); |
617 | 0 | } else if (mH.mBpp == 32) { |
618 | 0 | // No bitfields specified; use the default 8-8-8 values. |
619 | 0 | mBitFields.SetR8G8B8(); |
620 | 0 | } |
621 | 0 |
|
622 | 0 | return Transition::To(State::BITFIELDS, bitFieldsLengthStillToRead); |
623 | 0 | } |
624 | | |
625 | | void |
626 | | BitFields::ReadFromHeader(const char* aData, bool aReadAlpha) |
627 | 0 | { |
628 | 0 | mRed.Set (LittleEndian::readUint32(aData + 0)); |
629 | 0 | mGreen.Set(LittleEndian::readUint32(aData + 4)); |
630 | 0 | mBlue.Set (LittleEndian::readUint32(aData + 8)); |
631 | 0 | if (aReadAlpha) { |
632 | 0 | mAlpha.Set(LittleEndian::readUint32(aData + 12)); |
633 | 0 | } |
634 | 0 | } |
635 | | |
636 | | LexerTransition<nsBMPDecoder::State> |
637 | | nsBMPDecoder::ReadBitfields(const char* aData, size_t aLength) |
638 | 0 | { |
639 | 0 | mPreGapLength += aLength; |
640 | 0 |
|
641 | 0 | // If aLength is zero there are no bitfields to read, or we already read them |
642 | 0 | // in ReadInfoHeader(). |
643 | 0 | if (aLength != 0) { |
644 | 0 | mBitFields.ReadFromHeader(aData, /* aReadAlpha = */ false); |
645 | 0 | } |
646 | 0 |
|
647 | 0 | // Note that RLE-encoded BMPs might be transparent because the 'delta' mode |
648 | 0 | // can skip pixels and cause implicit transparency. |
649 | 0 | mMayHaveTransparency = |
650 | 0 | mIsWithinICO || |
651 | 0 | mH.mCompression == Compression::RLE8 || |
652 | 0 | mH.mCompression == Compression::RLE4 || |
653 | 0 | (mH.mCompression == Compression::BITFIELDS && |
654 | 0 | mBitFields.mAlpha.IsPresent()); |
655 | 0 | if (mMayHaveTransparency) { |
656 | 0 | PostHasTransparency(); |
657 | 0 | } |
658 | 0 |
|
659 | 0 | // Post our size to the superclass. |
660 | 0 | PostSize(mH.mWidth, AbsoluteHeight()); |
661 | 0 | if (HasError()) { |
662 | 0 | return Transition::TerminateFailure(); |
663 | 0 | } |
664 | 0 | |
665 | 0 | // We've now read all the headers. If we're doing a metadata decode, we're |
666 | 0 | // done. |
667 | 0 | if (IsMetadataDecode()) { |
668 | 0 | return Transition::TerminateSuccess(); |
669 | 0 | } |
670 | 0 | |
671 | 0 | // Set up the color table, if present; it'll be filled in by ReadColorTable(). |
672 | 0 | if (mH.mBpp <= 8) { |
673 | 0 | mNumColors = 1 << mH.mBpp; |
674 | 0 | if (0 < mH.mNumColors && mH.mNumColors < mNumColors) { |
675 | 0 | mNumColors = mH.mNumColors; |
676 | 0 | } |
677 | 0 |
|
678 | 0 | // Always allocate and zero 256 entries, even though mNumColors might be |
679 | 0 | // smaller, because the file might erroneously index past mNumColors. |
680 | 0 | mColors = MakeUnique<ColorTableEntry[]>(256); |
681 | 0 | memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry)); |
682 | 0 |
|
683 | 0 | // OS/2 Bitmaps have no padding byte. |
684 | 0 | mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4; |
685 | 0 | } |
686 | 0 |
|
687 | 0 | MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); |
688 | 0 | nsresult rv = AllocateFrame(OutputSize(), FullOutputFrame(), |
689 | 0 | mMayHaveTransparency ? SurfaceFormat::B8G8R8A8 |
690 | 0 | : SurfaceFormat::B8G8R8X8); |
691 | 0 | if (NS_FAILED(rv)) { |
692 | 0 | return Transition::TerminateFailure(); |
693 | 0 | } |
694 | 0 | MOZ_ASSERT(mImageData, "Should have a buffer now"); |
695 | 0 |
|
696 | 0 | if (mDownscaler) { |
697 | 0 | // BMPs store their rows in reverse order, so the downscaler needs to |
698 | 0 | // reverse them again when writing its output. Unless the height is |
699 | 0 | // negative! |
700 | 0 | rv = mDownscaler->BeginFrame(Size(), Nothing(), |
701 | 0 | mImageData, mMayHaveTransparency, |
702 | 0 | /* aFlipVertically = */ mH.mHeight >= 0); |
703 | 0 | if (NS_FAILED(rv)) { |
704 | 0 | return Transition::TerminateFailure(); |
705 | 0 | } |
706 | 0 | } |
707 | 0 | |
708 | 0 | return Transition::To(State::COLOR_TABLE, mNumColors * mBytesPerColor); |
709 | 0 | } |
710 | | |
711 | | LexerTransition<nsBMPDecoder::State> |
712 | | nsBMPDecoder::ReadColorTable(const char* aData, size_t aLength) |
713 | 0 | { |
714 | 0 | MOZ_ASSERT_IF(aLength != 0, mNumColors > 0 && mColors); |
715 | 0 |
|
716 | 0 | mPreGapLength += aLength; |
717 | 0 |
|
718 | 0 | for (uint32_t i = 0; i < mNumColors; i++) { |
719 | 0 | // The format is BGR or BGR0. |
720 | 0 | mColors[i].mBlue = uint8_t(aData[0]); |
721 | 0 | mColors[i].mGreen = uint8_t(aData[1]); |
722 | 0 | mColors[i].mRed = uint8_t(aData[2]); |
723 | 0 | aData += mBytesPerColor; |
724 | 0 | } |
725 | 0 |
|
726 | 0 | // We know how many bytes we've read so far (mPreGapLength) and we know the |
727 | 0 | // offset of the pixel data (mH.mDataOffset), so we can determine the length |
728 | 0 | // of the gap (possibly zero) between the color table and the pixel data. |
729 | 0 | // |
730 | 0 | // If the gap is negative the file must be malformed (e.g. mH.mDataOffset |
731 | 0 | // points into the middle of the color palette instead of past the end) and |
732 | 0 | // we give up. |
733 | 0 | if (mPreGapLength > mH.mDataOffset) { |
734 | 0 | return Transition::TerminateFailure(); |
735 | 0 | } |
736 | 0 | |
737 | 0 | uint32_t gapLength = mH.mDataOffset - mPreGapLength; |
738 | 0 | return Transition::ToUnbuffered(State::AFTER_GAP, State::GAP, gapLength); |
739 | 0 | } |
740 | | |
741 | | LexerTransition<nsBMPDecoder::State> |
742 | | nsBMPDecoder::SkipGap() |
743 | 0 | { |
744 | 0 | return Transition::ContinueUnbuffered(State::GAP); |
745 | 0 | } |
746 | | |
747 | | LexerTransition<nsBMPDecoder::State> |
748 | | nsBMPDecoder::AfterGap() |
749 | 0 | { |
750 | 0 | // If there are no pixels we can stop. |
751 | 0 | // |
752 | 0 | // XXX: normally, if there are no pixels we will have stopped decoding before |
753 | 0 | // now, outside of this decoder. However, if the BMP is within an ICO file, |
754 | 0 | // it's possible that the ICO claimed the image had a non-zero size while the |
755 | 0 | // BMP claims otherwise. This test is to catch that awkward case. If we ever |
756 | 0 | // come up with a more general solution to this ICO-and-BMP-disagree-on-size |
757 | 0 | // problem, this test can be removed. |
758 | 0 | if (mH.mWidth == 0 || mH.mHeight == 0) { |
759 | 0 | return Transition::TerminateSuccess(); |
760 | 0 | } |
761 | 0 | |
762 | 0 | bool hasRLE = mH.mCompression == Compression::RLE8 || |
763 | 0 | mH.mCompression == Compression::RLE4; |
764 | 0 | return hasRLE |
765 | 0 | ? Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH) |
766 | 0 | : Transition::To(State::PIXEL_ROW, mPixelRowSize); |
767 | 0 | } |
768 | | |
769 | | LexerTransition<nsBMPDecoder::State> |
770 | | nsBMPDecoder::ReadPixelRow(const char* aData) |
771 | 0 | { |
772 | 0 | MOZ_ASSERT(mCurrentRow > 0); |
773 | 0 | MOZ_ASSERT(mCurrentPos == 0); |
774 | 0 |
|
775 | 0 | const uint8_t* src = reinterpret_cast<const uint8_t*>(aData); |
776 | 0 | uint32_t* dst = RowBuffer(); |
777 | 0 | uint32_t lpos = mH.mWidth; |
778 | 0 | switch (mH.mBpp) { |
779 | 0 | case 1: |
780 | 0 | while (lpos > 0) { |
781 | 0 | int8_t bit; |
782 | 0 | uint8_t idx; |
783 | 0 | for (bit = 7; bit >= 0 && lpos > 0; bit--) { |
784 | 0 | idx = (*src >> bit) & 1; |
785 | 0 | SetPixel(dst, idx, mColors); |
786 | 0 | --lpos; |
787 | 0 | } |
788 | 0 | ++src; |
789 | 0 | } |
790 | 0 | break; |
791 | 0 |
|
792 | 0 | case 4: |
793 | 0 | while (lpos > 0) { |
794 | 0 | Set4BitPixel(dst, *src, lpos, mColors); |
795 | 0 | ++src; |
796 | 0 | } |
797 | 0 | break; |
798 | 0 |
|
799 | 0 | case 8: |
800 | 0 | while (lpos > 0) { |
801 | 0 | SetPixel(dst, *src, mColors); |
802 | 0 | --lpos; |
803 | 0 | ++src; |
804 | 0 | } |
805 | 0 | break; |
806 | 0 |
|
807 | 0 | case 16: |
808 | 0 | if (mBitFields.IsR5G5B5()) { |
809 | 0 | // Specialize this common case. |
810 | 0 | while (lpos > 0) { |
811 | 0 | uint16_t val = LittleEndian::readUint16(src); |
812 | 0 | SetPixel(dst, mBitFields.mRed.Get5(val), |
813 | 0 | mBitFields.mGreen.Get5(val), |
814 | 0 | mBitFields.mBlue.Get5(val)); |
815 | 0 | --lpos; |
816 | 0 | src += 2; |
817 | 0 | } |
818 | 0 | } else { |
819 | 0 | bool anyHasAlpha = false; |
820 | 0 | while (lpos > 0) { |
821 | 0 | uint16_t val = LittleEndian::readUint16(src); |
822 | 0 | SetPixel(dst, mBitFields.mRed.Get(val), |
823 | 0 | mBitFields.mGreen.Get(val), |
824 | 0 | mBitFields.mBlue.Get(val), |
825 | 0 | mBitFields.mAlpha.GetAlpha(val, anyHasAlpha)); |
826 | 0 | --lpos; |
827 | 0 | src += 2; |
828 | 0 | } |
829 | 0 | if (anyHasAlpha) { |
830 | 0 | MOZ_ASSERT(mMayHaveTransparency); |
831 | 0 | mDoesHaveTransparency = true; |
832 | 0 | } |
833 | 0 | } |
834 | 0 | break; |
835 | 0 |
|
836 | 0 | case 24: |
837 | 0 | while (lpos > 0) { |
838 | 0 | SetPixel(dst, src[2], src[1], src[0]); |
839 | 0 | --lpos; |
840 | 0 | src += 3; |
841 | 0 | } |
842 | 0 | break; |
843 | 0 |
|
844 | 0 | case 32: |
845 | 0 | if (mH.mCompression == Compression::RGB && mIsWithinICO && |
846 | 0 | mH.mBpp == 32) { |
847 | 0 | // This is a special case only used for 32bpp WinBMPv3-ICO files, which |
848 | 0 | // could be in either 0RGB or ARGB format. We start by assuming it's |
849 | 0 | // an 0RGB image. If we hit a non-zero alpha value, then we know it's |
850 | 0 | // actually an ARGB image, and change tack accordingly. |
851 | 0 | // (Note: a fully-transparent ARGB image is indistinguishable from a |
852 | 0 | // 0RGB image, and we will render such an image as a 0RGB image, i.e. |
853 | 0 | // opaquely. This is unlikely to be a problem in practice.) |
854 | 0 | while (lpos > 0) { |
855 | 0 | if (!mDoesHaveTransparency && src[3] != 0) { |
856 | 0 | // Up until now this looked like an 0RGB image, but we now know |
857 | 0 | // it's actually an ARGB image. Which means every pixel we've seen |
858 | 0 | // so far has been fully transparent. So we go back and redo them. |
859 | 0 |
|
860 | 0 | // Tell the Downscaler to go back to the start. |
861 | 0 | if (mDownscaler) { |
862 | 0 | mDownscaler->ResetForNextProgressivePass(); |
863 | 0 | } |
864 | 0 |
|
865 | 0 | // Redo the complete rows we've already done. |
866 | 0 | MOZ_ASSERT(mCurrentPos == 0); |
867 | 0 | int32_t currentRow = mCurrentRow; |
868 | 0 | mCurrentRow = AbsoluteHeight(); |
869 | 0 | while (mCurrentRow > currentRow) { |
870 | 0 | dst = RowBuffer(); |
871 | 0 | for (int32_t i = 0; i < mH.mWidth; i++) { |
872 | 0 | SetPixel(dst, 0, 0, 0, 0); |
873 | 0 | } |
874 | 0 | FinishRow(); |
875 | 0 | } |
876 | 0 |
|
877 | 0 | // Redo the part of this row we've already done. |
878 | 0 | dst = RowBuffer(); |
879 | 0 | int32_t n = mH.mWidth - lpos; |
880 | 0 | for (int32_t i = 0; i < n; i++) { |
881 | 0 | SetPixel(dst, 0, 0, 0, 0); |
882 | 0 | } |
883 | 0 |
|
884 | 0 | MOZ_ASSERT(mMayHaveTransparency); |
885 | 0 | mDoesHaveTransparency = true; |
886 | 0 | } |
887 | 0 |
|
888 | 0 | // If mDoesHaveTransparency is false, treat this as an 0RGB image. |
889 | 0 | // Otherwise, treat this as an ARGB image. |
890 | 0 | SetPixel(dst, src[2], src[1], src[0], |
891 | 0 | mDoesHaveTransparency ? src[3] : 0xff); |
892 | 0 | src += 4; |
893 | 0 | --lpos; |
894 | 0 | } |
895 | 0 | } else if (mBitFields.IsR8G8B8()) { |
896 | 0 | // Specialize this common case. |
897 | 0 | while (lpos > 0) { |
898 | 0 | uint32_t val = LittleEndian::readUint32(src); |
899 | 0 | SetPixel(dst, mBitFields.mRed.Get8(val), |
900 | 0 | mBitFields.mGreen.Get8(val), |
901 | 0 | mBitFields.mBlue.Get8(val)); |
902 | 0 | --lpos; |
903 | 0 | src += 4; |
904 | 0 | } |
905 | 0 | } else { |
906 | 0 | bool anyHasAlpha = false; |
907 | 0 | while (lpos > 0) { |
908 | 0 | uint32_t val = LittleEndian::readUint32(src); |
909 | 0 | SetPixel(dst, mBitFields.mRed.Get(val), |
910 | 0 | mBitFields.mGreen.Get(val), |
911 | 0 | mBitFields.mBlue.Get(val), |
912 | 0 | mBitFields.mAlpha.GetAlpha(val, anyHasAlpha)); |
913 | 0 | --lpos; |
914 | 0 | src += 4; |
915 | 0 | } |
916 | 0 | if (anyHasAlpha) { |
917 | 0 | MOZ_ASSERT(mMayHaveTransparency); |
918 | 0 | mDoesHaveTransparency = true; |
919 | 0 | } |
920 | 0 | } |
921 | 0 | break; |
922 | 0 |
|
923 | 0 | default: |
924 | 0 | MOZ_CRASH("Unsupported color depth; earlier check didn't catch it?"); |
925 | 0 | } |
926 | 0 |
|
927 | 0 | FinishRow(); |
928 | 0 | return mCurrentRow == 0 |
929 | 0 | ? Transition::TerminateSuccess() |
930 | 0 | : Transition::To(State::PIXEL_ROW, mPixelRowSize); |
931 | 0 | } |
932 | | |
933 | | LexerTransition<nsBMPDecoder::State> |
934 | | nsBMPDecoder::ReadRLESegment(const char* aData) |
935 | 0 | { |
936 | 0 | if (mCurrentRow == 0) { |
937 | 0 | return Transition::TerminateSuccess(); |
938 | 0 | } |
939 | 0 | |
940 | 0 | uint8_t byte1 = uint8_t(aData[0]); |
941 | 0 | uint8_t byte2 = uint8_t(aData[1]); |
942 | 0 |
|
943 | 0 | if (byte1 != RLE::ESCAPE) { |
944 | 0 | // Encoded mode consists of two bytes: byte1 specifies the number of |
945 | 0 | // consecutive pixels to be drawn using the color index contained in |
946 | 0 | // byte2. |
947 | 0 | // |
948 | 0 | // Work around bitmaps that specify too many pixels. |
949 | 0 | uint32_t pixelsNeeded = |
950 | 0 | std::min<uint32_t>(mH.mWidth - mCurrentPos, byte1); |
951 | 0 | if (pixelsNeeded) { |
952 | 0 | uint32_t* dst = RowBuffer(); |
953 | 0 | mCurrentPos += pixelsNeeded; |
954 | 0 | if (mH.mCompression == Compression::RLE8) { |
955 | 0 | do { |
956 | 0 | SetPixel(dst, byte2, mColors); |
957 | 0 | pixelsNeeded --; |
958 | 0 | } while (pixelsNeeded); |
959 | 0 | } else { |
960 | 0 | do { |
961 | 0 | Set4BitPixel(dst, byte2, pixelsNeeded, mColors); |
962 | 0 | } while (pixelsNeeded); |
963 | 0 | } |
964 | 0 | } |
965 | 0 | return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); |
966 | 0 | } |
967 | 0 |
|
968 | 0 | if (byte2 == RLE::ESCAPE_EOL) { |
969 | 0 | mCurrentPos = 0; |
970 | 0 | FinishRow(); |
971 | 0 | return mCurrentRow == 0 |
972 | 0 | ? Transition::TerminateSuccess() |
973 | 0 | : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); |
974 | 0 | } |
975 | 0 |
|
976 | 0 | if (byte2 == RLE::ESCAPE_EOF) { |
977 | 0 | return Transition::TerminateSuccess(); |
978 | 0 | } |
979 | 0 | |
980 | 0 | if (byte2 == RLE::ESCAPE_DELTA) { |
981 | 0 | return Transition::To(State::RLE_DELTA, RLE::DELTA_LENGTH); |
982 | 0 | } |
983 | 0 | |
984 | 0 | // Absolute mode. |byte2| gives the number of pixels. The length depends on |
985 | 0 | // whether it's 4-bit or 8-bit RLE. Also, the length must be even (and zero |
986 | 0 | // padding is used to achieve this when necessary). |
987 | 0 | MOZ_ASSERT(mAbsoluteModeNumPixels == 0); |
988 | 0 | mAbsoluteModeNumPixels = byte2; |
989 | 0 | uint32_t length = byte2; |
990 | 0 | if (mH.mCompression == Compression::RLE4) { |
991 | 0 | length = (length + 1) / 2; // halve, rounding up |
992 | 0 | } |
993 | 0 | if (length & 1) { |
994 | 0 | length++; |
995 | 0 | } |
996 | 0 | return Transition::To(State::RLE_ABSOLUTE, length); |
997 | 0 | } |
998 | | |
999 | | LexerTransition<nsBMPDecoder::State> |
1000 | | nsBMPDecoder::ReadRLEDelta(const char* aData) |
1001 | 0 | { |
1002 | 0 | // Delta encoding makes it possible to skip pixels making part of the image |
1003 | 0 | // transparent. |
1004 | 0 | MOZ_ASSERT(mMayHaveTransparency); |
1005 | 0 | mDoesHaveTransparency = true; |
1006 | 0 |
|
1007 | 0 | if (mDownscaler) { |
1008 | 0 | // Clear the skipped pixels. (This clears to the end of the row, |
1009 | 0 | // which is perfect if there's a Y delta and harmless if not). |
1010 | 0 | mDownscaler->ClearRestOfRow(/* aStartingAtCol = */ mCurrentPos); |
1011 | 0 | } |
1012 | 0 |
|
1013 | 0 | // Handle the XDelta. |
1014 | 0 | mCurrentPos += uint8_t(aData[0]); |
1015 | 0 | if (mCurrentPos > mH.mWidth) { |
1016 | 0 | mCurrentPos = mH.mWidth; |
1017 | 0 | } |
1018 | 0 |
|
1019 | 0 | // Handle the Y Delta. |
1020 | 0 | int32_t yDelta = std::min<int32_t>(uint8_t(aData[1]), mCurrentRow); |
1021 | 0 | mCurrentRow -= yDelta; |
1022 | 0 |
|
1023 | 0 | if (mDownscaler && yDelta > 0) { |
1024 | 0 | // Commit the current row (the first of the skipped rows). |
1025 | 0 | mDownscaler->CommitRow(); |
1026 | 0 |
|
1027 | 0 | // Clear and commit the remaining skipped rows. |
1028 | 0 | for (int32_t line = 1; line < yDelta; line++) { |
1029 | 0 | mDownscaler->ClearRow(); |
1030 | 0 | mDownscaler->CommitRow(); |
1031 | 0 | } |
1032 | 0 | } |
1033 | 0 |
|
1034 | 0 | return mCurrentRow == 0 |
1035 | 0 | ? Transition::TerminateSuccess() |
1036 | 0 | : Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); |
1037 | 0 | } |
1038 | | |
1039 | | LexerTransition<nsBMPDecoder::State> |
1040 | | nsBMPDecoder::ReadRLEAbsolute(const char* aData, size_t aLength) |
1041 | 0 | { |
1042 | 0 | uint32_t n = mAbsoluteModeNumPixels; |
1043 | 0 | mAbsoluteModeNumPixels = 0; |
1044 | 0 |
|
1045 | 0 | if (mCurrentPos + n > uint32_t(mH.mWidth)) { |
1046 | 0 | // Bad data. Stop decoding; at least part of the image may have been |
1047 | 0 | // decoded. |
1048 | 0 | return Transition::TerminateSuccess(); |
1049 | 0 | } |
1050 | 0 | |
1051 | 0 | // In absolute mode, n represents the number of pixels that follow, each of |
1052 | 0 | // which contains the color index of a single pixel. |
1053 | 0 | uint32_t* dst = RowBuffer(); |
1054 | 0 | uint32_t iSrc = 0; |
1055 | 0 | uint32_t* oldPos = dst; |
1056 | 0 | if (mH.mCompression == Compression::RLE8) { |
1057 | 0 | while (n > 0) { |
1058 | 0 | SetPixel(dst, aData[iSrc], mColors); |
1059 | 0 | n--; |
1060 | 0 | iSrc++; |
1061 | 0 | } |
1062 | 0 | } else { |
1063 | 0 | while (n > 0) { |
1064 | 0 | Set4BitPixel(dst, aData[iSrc], n, mColors); |
1065 | 0 | iSrc++; |
1066 | 0 | } |
1067 | 0 | } |
1068 | 0 | mCurrentPos += dst - oldPos; |
1069 | 0 |
|
1070 | 0 | // We should read all the data (unless the last byte is zero padding). |
1071 | 0 | MOZ_ASSERT(iSrc == aLength - 1 || iSrc == aLength); |
1072 | 0 |
|
1073 | 0 | return Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH); |
1074 | 0 | } |
1075 | | |
1076 | | } // namespace image |
1077 | | } // namespace mozilla |