Line data Source code
1 : /* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : /* This is a Cross-Platform ICO Decoder, which should work everywhere, including
7 : * Big-Endian machines like the PowerPC. */
8 :
9 : #include "nsICODecoder.h"
10 :
11 : #include <stdlib.h>
12 :
13 : #include "mozilla/EndianUtils.h"
14 : #include "mozilla/Move.h"
15 :
16 : #include "RasterImage.h"
17 :
18 : using namespace mozilla::gfx;
19 :
20 : namespace mozilla {
21 : namespace image {
22 :
23 : // Constants.
24 : static const uint32_t ICOHEADERSIZE = 6;
25 : static const uint32_t BITMAPINFOSIZE = bmp::InfoHeaderLength::WIN_ICO;
26 :
27 : // ----------------------------------------
28 : // Actual Data Processing
29 : // ----------------------------------------
30 :
31 : // Obtains the number of colors from the bits per pixel
32 : uint16_t
33 0 : nsICODecoder::GetNumColors()
34 : {
35 0 : uint16_t numColors = 0;
36 0 : if (mBPP <= 8) {
37 0 : switch (mBPP) {
38 : case 1:
39 0 : numColors = 2;
40 0 : break;
41 : case 4:
42 0 : numColors = 16;
43 0 : break;
44 : case 8:
45 0 : numColors = 256;
46 0 : break;
47 : default:
48 0 : numColors = (uint16_t)-1;
49 : }
50 : }
51 0 : return numColors;
52 : }
53 :
54 0 : nsICODecoder::nsICODecoder(RasterImage* aImage)
55 : : Decoder(aImage)
56 0 : , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE),
57 : Transition::TerminateSuccess())
58 : , mBiggestResourceColorDepth(0)
59 : , mBestResourceDelta(INT_MIN)
60 : , mBestResourceColorDepth(0)
61 : , mNumIcons(0)
62 : , mCurrIcon(0)
63 : , mBPP(0)
64 : , mMaskRowSize(0)
65 : , mCurrMaskLine(0)
66 : , mIsCursor(false)
67 0 : , mHasMaskAlpha(false)
68 0 : { }
69 :
70 : nsresult
71 0 : nsICODecoder::FinishInternal()
72 : {
73 : // We shouldn't be called in error cases
74 0 : MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
75 :
76 0 : return GetFinalStateFromContainedDecoder();
77 : }
78 :
79 : nsresult
80 0 : nsICODecoder::FinishWithErrorInternal()
81 : {
82 : // No need to assert !mInFrame here because this condition is enforced by
83 : // mContainedDecoder.
84 0 : return GetFinalStateFromContainedDecoder();
85 : }
86 :
87 : nsresult
88 0 : nsICODecoder::GetFinalStateFromContainedDecoder()
89 : {
90 0 : if (!mContainedDecoder) {
91 0 : return NS_OK;
92 : }
93 :
94 0 : MOZ_ASSERT(mContainedSourceBuffer,
95 : "Should have a SourceBuffer if we have a decoder");
96 :
97 : // Let the contained decoder finish up if necessary.
98 0 : if (!mContainedSourceBuffer->IsComplete()) {
99 0 : mContainedSourceBuffer->Complete(NS_OK);
100 0 : mContainedDecoder->Decode();
101 : }
102 :
103 : // Make our state the same as the state of the contained decoder.
104 0 : mDecodeDone = mContainedDecoder->GetDecodeDone();
105 0 : mProgress |= mContainedDecoder->TakeProgress();
106 0 : mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
107 0 : mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
108 :
109 : // Propagate errors.
110 0 : nsresult rv = HasError() || mContainedDecoder->HasError()
111 0 : ? NS_ERROR_FAILURE
112 0 : : NS_OK;
113 :
114 0 : MOZ_ASSERT(NS_FAILED(rv) || !mCurrentFrame || mCurrentFrame->IsFinished());
115 0 : return rv;
116 : }
117 :
118 : bool
119 0 : nsICODecoder::CheckAndFixBitmapSize(int8_t* aBIH)
120 : {
121 : // Get the width from the BMP file information header. This is
122 : // (unintuitively) a signed integer; see the documentation at:
123 : //
124 : // https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
125 : //
126 : // However, we reject negative widths since they aren't meaningful.
127 0 : const int32_t width = LittleEndian::readInt32(aBIH + 4);
128 0 : if (width <= 0 || width > 256) {
129 0 : return false;
130 : }
131 :
132 : // Verify that the BMP width matches the width we got from the ICO directory
133 : // entry. If not, decoding fails, because if we were to allow it to continue
134 : // the intrinsic size of the image wouldn't match the size of the decoded
135 : // surface.
136 0 : if (width != int32_t(GetRealWidth())) {
137 0 : return false;
138 : }
139 :
140 : // Get the height from the BMP file information header. This is also signed,
141 : // but in this case negative values are meaningful; see below.
142 0 : int32_t height = LittleEndian::readInt32(aBIH + 8);
143 0 : if (height == 0) {
144 0 : return false;
145 : }
146 :
147 : // BMPs can be stored inverted by having a negative height.
148 : // XXX(seth): Should we really be writing the absolute value into the BIH
149 : // below? Seems like this could be problematic for inverted BMPs.
150 0 : height = abs(height);
151 :
152 : // The height field is double the actual height of the image to account for
153 : // the AND mask. This is true even if the AND mask is not present.
154 0 : height /= 2;
155 0 : if (height > 256) {
156 0 : return false;
157 : }
158 :
159 : // Verify that the BMP height matches the height we got from the ICO directory
160 : // entry. If not, again, decoding fails.
161 0 : if (height != int32_t(GetRealHeight())) {
162 0 : return false;
163 : }
164 :
165 : // Fix the BMP height in the BIH so that the BMP decoder, which does not know
166 : // about the AND mask that may follow the actual bitmap, can work properly.
167 0 : LittleEndian::writeInt32(aBIH + 8, GetRealHeight());
168 :
169 0 : return true;
170 : }
171 :
172 : LexerTransition<ICOState>
173 0 : nsICODecoder::ReadHeader(const char* aData)
174 : {
175 : // If the third byte is 1, this is an icon. If 2, a cursor.
176 0 : if ((aData[2] != 1) && (aData[2] != 2)) {
177 0 : return Transition::TerminateFailure();
178 : }
179 0 : mIsCursor = (aData[2] == 2);
180 :
181 : // The fifth and sixth bytes specify the number of resources in the file.
182 0 : mNumIcons = LittleEndian::readUint16(aData + 4);
183 0 : if (mNumIcons == 0) {
184 0 : return Transition::TerminateSuccess(); // Nothing to do.
185 : }
186 :
187 : // Downscale-during-decode can end up decoding different resources in the ICO
188 : // file depending on the target size. Since the resources are not necessarily
189 : // scaled versions of the same image, some may be transparent and some may not
190 : // be. We could be precise about transparency if we decoded the metadata of
191 : // every resource, but for now we don't and it's safest to assume that
192 : // transparency could be present.
193 0 : PostHasTransparency();
194 :
195 0 : return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
196 : }
197 :
198 : size_t
199 0 : nsICODecoder::FirstResourceOffset() const
200 : {
201 0 : MOZ_ASSERT(mNumIcons > 0,
202 : "Calling FirstResourceOffset before processing header");
203 :
204 : // The first resource starts right after the directory, which starts right
205 : // after the ICO header.
206 0 : return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE;
207 : }
208 :
209 : LexerTransition<ICOState>
210 0 : nsICODecoder::ReadDirEntry(const char* aData)
211 : {
212 0 : mCurrIcon++;
213 :
214 : // Read the directory entry.
215 : IconDirEntry e;
216 0 : e.mWidth = aData[0];
217 0 : e.mHeight = aData[1];
218 0 : e.mColorCount = aData[2];
219 0 : e.mReserved = aData[3];
220 0 : e.mPlanes = LittleEndian::readUint16(aData + 4);
221 0 : e.mBitCount = LittleEndian::readUint16(aData + 6);
222 0 : e.mBytesInRes = LittleEndian::readUint32(aData + 8);
223 0 : e.mImageOffset = LittleEndian::readUint32(aData + 12);
224 :
225 : // If an explicit output size was specified, we'll try to select the resource
226 : // that matches it best below.
227 0 : const Maybe<IntSize> desiredSize = ExplicitOutputSize();
228 :
229 : // Determine if this is the biggest resource we've seen so far. We always use
230 : // the biggest resource for the intrinsic size, and if we don't have a
231 : // specific desired size, we select it as the best resource as well.
232 0 : IntSize entrySize(GetRealWidth(e), GetRealHeight(e));
233 0 : if (e.mBitCount >= mBiggestResourceColorDepth &&
234 0 : entrySize.width * entrySize.height >=
235 0 : mBiggestResourceSize.width * mBiggestResourceSize.height) {
236 0 : mBiggestResourceSize = entrySize;
237 0 : mBiggestResourceColorDepth = e.mBitCount;
238 0 : mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot);
239 :
240 0 : if (!desiredSize) {
241 0 : mDirEntry = e;
242 : }
243 : }
244 :
245 0 : mImageMetadata.AddNativeSize(entrySize);
246 :
247 0 : if (desiredSize) {
248 : // Calculate the delta between this resource's size and the desired size, so
249 : // we can see if it is better than our current-best option. In the case of
250 : // several equally-good resources, we use the last one. "Better" in this
251 : // case is determined by |delta|, a measure of the difference in size
252 : // between the entry we've found and the desired size. We will choose the
253 : // smallest resource that is greater than or equal to the desired size (i.e.
254 : // we assume it's better to downscale a larger icon than to upscale a
255 : // smaller one).
256 0 : int32_t delta = std::min(entrySize.width - desiredSize->width,
257 0 : entrySize.height - desiredSize->height);
258 0 : if (e.mBitCount >= mBestResourceColorDepth &&
259 0 : ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
260 0 : (delta >= 0 && delta <= mBestResourceDelta))) {
261 0 : mBestResourceDelta = delta;
262 0 : mBestResourceColorDepth = e.mBitCount;
263 0 : mDirEntry = e;
264 : }
265 : }
266 :
267 0 : if (mCurrIcon == mNumIcons) {
268 : // Ensure the resource we selected has an offset past the ICO headers.
269 0 : if (mDirEntry.mImageOffset < FirstResourceOffset()) {
270 0 : return Transition::TerminateFailure();
271 : }
272 :
273 : // If this is a cursor, set the hotspot. We use the hotspot from the biggest
274 : // resource since we also use that resource for the intrinsic size.
275 0 : if (mIsCursor) {
276 0 : mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width,
277 0 : mBiggestResourceHotSpot.height);
278 : }
279 :
280 : // We always report the biggest resource's size as the intrinsic size; this
281 : // is necessary for downscale-during-decode to work since we won't even
282 : // attempt to *upscale* while decoding.
283 0 : PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height);
284 0 : if (IsMetadataDecode()) {
285 0 : return Transition::TerminateSuccess();
286 : }
287 :
288 : // If the resource we selected matches the output size perfectly, we don't
289 : // need to do any downscaling.
290 0 : if (GetRealSize() == OutputSize()) {
291 0 : MOZ_ASSERT_IF(desiredSize, GetRealSize() == *desiredSize);
292 0 : MOZ_ASSERT_IF(!desiredSize, GetRealSize() == Size());
293 0 : mDownscaler.reset();
294 : }
295 :
296 0 : size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
297 : return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
298 : ICOState::SKIP_TO_RESOURCE,
299 0 : offsetToResource);
300 : }
301 :
302 0 : return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
303 : }
304 :
305 : LexerTransition<ICOState>
306 0 : nsICODecoder::SniffResource(const char* aData)
307 : {
308 : // We use the first PNGSIGNATURESIZE bytes to determine whether this resource
309 : // is a PNG or a BMP.
310 0 : bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes,
311 0 : PNGSIGNATURESIZE);
312 0 : if (isPNG) {
313 : // Create a PNG decoder which will do the rest of the work for us.
314 0 : mContainedSourceBuffer = new SourceBuffer();
315 0 : mContainedSourceBuffer->ExpectLength(mDirEntry.mBytesInRes);
316 : mContainedDecoder =
317 0 : DecoderFactory::CreateDecoderForICOResource(DecoderType::PNG,
318 0 : WrapNotNull(mContainedSourceBuffer),
319 0 : WrapNotNull(this));
320 :
321 0 : if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
322 0 : return Transition::TerminateFailure();
323 : }
324 :
325 0 : if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
326 0 : return Transition::TerminateFailure();
327 : }
328 :
329 : // Read in the rest of the PNG unbuffered.
330 0 : size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
331 : return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
332 : ICOState::READ_PNG,
333 0 : toRead);
334 : } else {
335 : // Make sure we have a sane size for the bitmap information header.
336 0 : int32_t bihSize = LittleEndian::readUint32(aData);
337 0 : if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
338 0 : return Transition::TerminateFailure();
339 : }
340 :
341 : // Buffer the first part of the bitmap information header.
342 0 : memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
343 :
344 : // Read in the rest of the bitmap information header.
345 : return Transition::To(ICOState::READ_BIH,
346 0 : BITMAPINFOSIZE - PNGSIGNATURESIZE);
347 : }
348 : }
349 :
350 : LexerTransition<ICOState>
351 0 : nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
352 : {
353 0 : if (!WriteToContainedDecoder(aData, aLen)) {
354 0 : return Transition::TerminateFailure();
355 : }
356 :
357 : // Raymond Chen says that 32bpp only are valid PNG ICOs
358 : // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
359 0 : if (!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
360 0 : return Transition::TerminateFailure();
361 : }
362 :
363 0 : return Transition::ContinueUnbuffered(ICOState::READ_PNG);
364 : }
365 :
366 : LexerTransition<ICOState>
367 0 : nsICODecoder::ReadBIH(const char* aData)
368 : {
369 : // Buffer the rest of the bitmap information header.
370 0 : memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE);
371 :
372 : // Extract the BPP from the BIH header; it should be trusted over the one
373 : // we have from the ICO header which is usually set to 0.
374 0 : mBPP = LittleEndian::readUint16(mBIHraw + 14);
375 :
376 : // The ICO format when containing a BMP does not include the 14 byte
377 : // bitmap file header. So we create the BMP decoder via the constructor that
378 : // tells it to skip this, and pass in the required data (dataOffset) that
379 : // would have been present in the header.
380 0 : uint32_t dataOffset = bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE;
381 0 : if (mBPP <= 8) {
382 : // The color table is present only if BPP is <= 8.
383 0 : uint16_t numColors = GetNumColors();
384 0 : if (numColors == (uint16_t)-1) {
385 0 : return Transition::TerminateFailure();
386 : }
387 0 : dataOffset += 4 * numColors;
388 : }
389 :
390 : // Create a BMP decoder which will do most of the work for us; the exception
391 : // is the AND mask, which isn't present in standalone BMPs.
392 0 : mContainedSourceBuffer = new SourceBuffer();
393 0 : mContainedSourceBuffer->ExpectLength(mDirEntry.mBytesInRes);
394 : mContainedDecoder =
395 0 : DecoderFactory::CreateDecoderForICOResource(DecoderType::BMP,
396 0 : WrapNotNull(mContainedSourceBuffer),
397 : WrapNotNull(this),
398 0 : Some(dataOffset));
399 : RefPtr<nsBMPDecoder> bmpDecoder =
400 0 : static_cast<nsBMPDecoder*>(mContainedDecoder.get());
401 :
402 : // Verify that the BIH width and height values match the ICO directory entry,
403 : // and fix the BIH height value to compensate for the fact that the underlying
404 : // BMP decoder doesn't know about AND masks.
405 0 : if (!CheckAndFixBitmapSize(reinterpret_cast<int8_t*>(mBIHraw))) {
406 0 : return Transition::TerminateFailure();
407 : }
408 :
409 : // Write out the BMP's bitmap info header.
410 0 : if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
411 0 : return Transition::TerminateFailure();
412 : }
413 :
414 : // Check to make sure we have valid color settings.
415 0 : uint16_t numColors = GetNumColors();
416 0 : if (numColors == uint16_t(-1)) {
417 0 : return Transition::TerminateFailure();
418 : }
419 :
420 : // Do we have an AND mask on this BMP? If so, we need to read it after we read
421 : // the BMP data itself.
422 0 : uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
423 0 : bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes;
424 0 : ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK
425 0 : : ICOState::FINISHED_RESOURCE;
426 :
427 : // Read in the rest of the BMP unbuffered.
428 : return Transition::ToUnbuffered(afterBMPState,
429 : ICOState::READ_BMP,
430 0 : bmpDataLength);
431 : }
432 :
433 : LexerTransition<ICOState>
434 0 : nsICODecoder::ReadBMP(const char* aData, uint32_t aLen)
435 : {
436 0 : if (!WriteToContainedDecoder(aData, aLen)) {
437 0 : return Transition::TerminateFailure();
438 : }
439 :
440 0 : return Transition::ContinueUnbuffered(ICOState::READ_BMP);
441 : }
442 :
443 : LexerTransition<ICOState>
444 0 : nsICODecoder::PrepareForMask()
445 : {
446 : RefPtr<nsBMPDecoder> bmpDecoder =
447 0 : static_cast<nsBMPDecoder*>(mContainedDecoder.get());
448 :
449 0 : uint16_t numColors = GetNumColors();
450 0 : MOZ_ASSERT(numColors != uint16_t(-1));
451 :
452 : // Determine the length of the AND mask.
453 : uint32_t bmpLengthWithHeader =
454 0 : BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors;
455 0 : MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes);
456 0 : uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader;
457 :
458 : // If the BMP provides its own transparency, we ignore the AND mask. We can
459 : // also obviously ignore it if the image has zero width or zero height.
460 0 : if (bmpDecoder->HasTransparency() ||
461 0 : GetRealWidth() == 0 || GetRealHeight() == 0) {
462 : return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
463 : ICOState::SKIP_MASK,
464 0 : maskLength);
465 : }
466 :
467 : // Compute the row size for the mask.
468 0 : mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
469 :
470 : // If the expected size of the AND mask is larger than its actual size, then
471 : // we must have a truncated (and therefore corrupt) AND mask.
472 0 : uint32_t expectedLength = mMaskRowSize * GetRealHeight();
473 0 : if (maskLength < expectedLength) {
474 0 : return Transition::TerminateFailure();
475 : }
476 :
477 : // If we're downscaling, the mask is the wrong size for the surface we've
478 : // produced, so we need to downscale the mask into a temporary buffer and then
479 : // combine the mask's alpha values with the color values from the image.
480 0 : if (mDownscaler) {
481 0 : MOZ_ASSERT(bmpDecoder->GetImageDataLength() ==
482 : mDownscaler->TargetSize().width *
483 : mDownscaler->TargetSize().height *
484 : sizeof(uint32_t));
485 0 : mMaskBuffer = MakeUnique<uint8_t[]>(bmpDecoder->GetImageDataLength());
486 0 : nsresult rv = mDownscaler->BeginFrame(GetRealSize(), Nothing(),
487 : mMaskBuffer.get(),
488 : /* aHasAlpha = */ true,
489 0 : /* aFlipVertically = */ true);
490 0 : if (NS_FAILED(rv)) {
491 0 : return Transition::TerminateFailure();
492 : }
493 : }
494 :
495 0 : mCurrMaskLine = GetRealHeight();
496 0 : return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
497 : }
498 :
499 :
500 : LexerTransition<ICOState>
501 0 : nsICODecoder::ReadMaskRow(const char* aData)
502 : {
503 0 : mCurrMaskLine--;
504 :
505 0 : uint8_t sawTransparency = 0;
506 :
507 : // Get the mask row we're reading.
508 0 : const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
509 0 : const uint8_t* maskRowEnd = mask + mMaskRowSize;
510 :
511 : // Get the corresponding row of the mask buffer (if we're downscaling) or the
512 : // decoded image data (if we're not).
513 0 : uint32_t* decoded = nullptr;
514 0 : if (mDownscaler) {
515 : // Initialize the row to all white and fully opaque.
516 0 : memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t));
517 :
518 0 : decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
519 : } else {
520 : RefPtr<nsBMPDecoder> bmpDecoder =
521 0 : static_cast<nsBMPDecoder*>(mContainedDecoder.get());
522 0 : uint32_t* imageData = bmpDecoder->GetImageData();
523 0 : if (!imageData) {
524 0 : return Transition::TerminateFailure();
525 : }
526 :
527 0 : decoded = imageData + mCurrMaskLine * GetRealWidth();
528 : }
529 :
530 0 : MOZ_ASSERT(decoded);
531 0 : uint32_t* decodedRowEnd = decoded + GetRealWidth();
532 :
533 : // Iterate simultaneously through the AND mask and the image data.
534 0 : while (mask < maskRowEnd) {
535 0 : uint8_t idx = *mask++;
536 0 : sawTransparency |= idx;
537 0 : for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
538 : // Clear pixel completely for transparency.
539 0 : if (idx & bit) {
540 0 : *decoded = 0;
541 : }
542 0 : decoded++;
543 : }
544 : }
545 :
546 0 : if (mDownscaler) {
547 0 : mDownscaler->CommitRow();
548 : }
549 :
550 : // If any bits are set in sawTransparency, then we know at least one pixel was
551 : // transparent.
552 0 : if (sawTransparency) {
553 0 : mHasMaskAlpha = true;
554 : }
555 :
556 0 : if (mCurrMaskLine == 0) {
557 0 : return Transition::To(ICOState::FINISH_MASK, 0);
558 : }
559 :
560 0 : return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
561 : }
562 :
563 : LexerTransition<ICOState>
564 0 : nsICODecoder::FinishMask()
565 : {
566 : // If we're downscaling, we now have the appropriate alpha values in
567 : // mMaskBuffer. We just need to transfer them to the image.
568 0 : if (mDownscaler) {
569 : // Retrieve the image data.
570 : RefPtr<nsBMPDecoder> bmpDecoder =
571 0 : static_cast<nsBMPDecoder*>(mContainedDecoder.get());
572 0 : uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData());
573 0 : if (!imageData) {
574 0 : return Transition::TerminateFailure();
575 : }
576 :
577 : // Iterate through the alpha values, copying from mask to image.
578 0 : MOZ_ASSERT(mMaskBuffer);
579 0 : MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0);
580 0 : for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) {
581 0 : imageData[i] = mMaskBuffer[i];
582 : }
583 : }
584 :
585 0 : return Transition::To(ICOState::FINISHED_RESOURCE, 0);
586 : }
587 :
588 : LexerTransition<ICOState>
589 0 : nsICODecoder::FinishResource()
590 : {
591 : // Make sure the actual size of the resource matches the size in the directory
592 : // entry. If not, we consider the image corrupt.
593 0 : if (mContainedDecoder->HasSize() &&
594 0 : mContainedDecoder->Size() != GetRealSize()) {
595 0 : return Transition::TerminateFailure();
596 : }
597 :
598 : // Finalize the frame which we deferred to ensure we could modify the final
599 : // result (e.g. to apply the BMP mask).
600 0 : MOZ_ASSERT(!mContainedDecoder->GetFinalizeFrames());
601 0 : if (mCurrentFrame) {
602 0 : mCurrentFrame->FinalizeSurface();
603 : }
604 :
605 0 : return Transition::TerminateSuccess();
606 : }
607 :
608 : LexerResult
609 0 : nsICODecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
610 : {
611 0 : MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
612 :
613 : return mLexer.Lex(aIterator, aOnResume,
614 0 : [=](ICOState aState, const char* aData, size_t aLength) {
615 0 : switch (aState) {
616 : case ICOState::HEADER:
617 0 : return ReadHeader(aData);
618 : case ICOState::DIR_ENTRY:
619 0 : return ReadDirEntry(aData);
620 : case ICOState::SKIP_TO_RESOURCE:
621 0 : return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
622 : case ICOState::FOUND_RESOURCE:
623 0 : return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE);
624 : case ICOState::SNIFF_RESOURCE:
625 0 : return SniffResource(aData);
626 : case ICOState::READ_PNG:
627 0 : return ReadPNG(aData, aLength);
628 : case ICOState::READ_BIH:
629 0 : return ReadBIH(aData);
630 : case ICOState::READ_BMP:
631 0 : return ReadBMP(aData, aLength);
632 : case ICOState::PREPARE_FOR_MASK:
633 0 : return PrepareForMask();
634 : case ICOState::READ_MASK_ROW:
635 0 : return ReadMaskRow(aData);
636 : case ICOState::FINISH_MASK:
637 0 : return FinishMask();
638 : case ICOState::SKIP_MASK:
639 0 : return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
640 : case ICOState::FINISHED_RESOURCE:
641 0 : return FinishResource();
642 : default:
643 0 : MOZ_CRASH("Unknown ICOState");
644 : }
645 0 : });
646 : }
647 :
648 : bool
649 0 : nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
650 : {
651 0 : MOZ_ASSERT(mContainedDecoder);
652 0 : MOZ_ASSERT(mContainedSourceBuffer);
653 :
654 : // Append the provided data to the SourceBuffer that the contained decoder is
655 : // reading from.
656 0 : mContainedSourceBuffer->Append(aBuffer, aCount);
657 :
658 0 : bool succeeded = true;
659 :
660 : // Write to the contained decoder. If we run out of data, the ICO decoder will
661 : // get resumed when there's more data available, as usual, so we don't need
662 : // the contained decoder to get resumed too. To avoid that, we provide an
663 : // IResumable which just does nothing.
664 0 : LexerResult result = mContainedDecoder->Decode();
665 0 : if (result == LexerResult(TerminalState::FAILURE)) {
666 0 : succeeded = false;
667 : }
668 :
669 0 : MOZ_ASSERT(result != LexerResult(Yield::OUTPUT_AVAILABLE),
670 : "Unexpected yield");
671 :
672 : // Make our state the same as the state of the contained decoder, and
673 : // propagate errors.
674 0 : mProgress |= mContainedDecoder->TakeProgress();
675 0 : mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
676 0 : if (mContainedDecoder->HasError()) {
677 0 : succeeded = false;
678 : }
679 :
680 0 : return succeeded;
681 : }
682 :
683 : } // namespace image
684 : } // namespace mozilla
|