nsICODecoder.cpp (25529B)
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 <utility> 12 13 #include "RasterImage.h" 14 #include "mozilla/EndianUtils.h" 15 #include "mozilla/gfx/Swizzle.h" 16 #include "mozilla/UniquePtrExtensions.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 nsICODecoder::GetNumColors() { 33 uint16_t numColors = 0; 34 if (mBPP <= 8) { 35 switch (mBPP) { 36 case 1: 37 numColors = 2; 38 break; 39 case 4: 40 numColors = 16; 41 break; 42 case 8: 43 numColors = 256; 44 break; 45 default: 46 numColors = (uint16_t)-1; 47 } 48 } 49 return numColors; 50 } 51 52 nsICODecoder::nsICODecoder(RasterImage* aImage) 53 : Decoder(aImage), 54 mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE), 55 Transition::TerminateSuccess()), 56 mDirEntry(nullptr), 57 mNumIcons(0), 58 mCurrIcon(0), 59 mBPP(0), 60 mMaskRowSize(0), 61 mCurrMaskLine(0), 62 mIsCursor(false), 63 mHasMaskAlpha(false) {} 64 65 nsresult nsICODecoder::FinishInternal() { 66 // We shouldn't be called in error cases 67 MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!"); 68 69 return GetFinalStateFromContainedDecoder(); 70 } 71 72 nsresult nsICODecoder::FinishWithErrorInternal() { 73 // No need to assert !mInFrame here because this condition is enforced by 74 // mContainedDecoder. 75 return GetFinalStateFromContainedDecoder(); 76 } 77 78 nsresult nsICODecoder::GetFinalStateFromContainedDecoder() { 79 if (!mContainedDecoder) { 80 return NS_OK; 81 } 82 83 // Let the contained decoder finish up if necessary. 84 FlushContainedDecoder(); 85 86 // Make our state the same as the state of the contained decoder. 87 mDecodeDone = mContainedDecoder->GetDecodeDone(); 88 mProgress |= mContainedDecoder->TakeProgress(); 89 mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); 90 mCurrentFrame = mContainedDecoder->GetCurrentFrameRef(); 91 92 // Finalize the frame which we deferred to ensure we could modify the final 93 // result (e.g. to apply the BMP mask). 94 MOZ_ASSERT(!mContainedDecoder->GetFinalizeFrames()); 95 if (mCurrentFrame) { 96 mCurrentFrame->FinalizeSurface(); 97 } 98 99 // Propagate errors. 100 nsresult rv = 101 HasError() || mContainedDecoder->HasError() ? NS_ERROR_FAILURE : NS_OK; 102 103 MOZ_ASSERT(NS_FAILED(rv) || !mCurrentFrame || mCurrentFrame->IsFinished()); 104 return rv; 105 } 106 107 LexerTransition<ICOState> nsICODecoder::ReadHeader(const char* aData) { 108 // If the third byte is 1, this is an icon. If 2, a cursor. 109 if ((aData[2] != 1) && (aData[2] != 2)) { 110 return Transition::TerminateFailure(); 111 } 112 mIsCursor = (aData[2] == 2); 113 114 // The fifth and sixth bytes specify the number of resources in the file. 115 mNumIcons = LittleEndian::readUint16(aData + 4); 116 if (mNumIcons == 0) { 117 return Transition::TerminateSuccess(); // Nothing to do. 118 } 119 120 // Downscale-during-decode can end up decoding different resources in the ICO 121 // file depending on the target size. Since the resources are not necessarily 122 // scaled versions of the same image, some may be transparent and some may not 123 // be. We could be precise about transparency if we decoded the metadata of 124 // every resource, but for now we don't and it's safest to assume that 125 // transparency could be present. 126 PostHasTransparency(); 127 128 return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); 129 } 130 131 size_t nsICODecoder::FirstResourceOffset() const { 132 MOZ_ASSERT(mNumIcons > 0, 133 "Calling FirstResourceOffset before processing header"); 134 135 // The first resource starts right after the directory, which starts right 136 // after the ICO header. 137 return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE; 138 } 139 140 LexerTransition<ICOState> nsICODecoder::ReadDirEntry(const char* aData) { 141 mCurrIcon++; 142 143 // Ensure the resource has an offset past the ICO headers. 144 uint32_t offset = LittleEndian::readUint32(aData + 12); 145 if (offset >= FirstResourceOffset()) { 146 // Read the directory entry. 147 IconDirEntryEx e; 148 e.mWidth = aData[0]; 149 e.mHeight = aData[1]; 150 e.mColorCount = aData[2]; 151 e.mReserved = aData[3]; 152 e.mPlanes = LittleEndian::readUint16(aData + 4); 153 e.mBitCount = LittleEndian::readUint16(aData + 6); 154 e.mBytesInRes = LittleEndian::readUint32(aData + 8); 155 e.mImageOffset = offset; 156 e.mSize = OrientedIntSize(e.mWidth, e.mHeight); 157 158 // Only accept entries with sufficient resource data to actually contain 159 // some image data. 160 if (e.mBytesInRes > BITMAPINFOSIZE) { 161 if (e.mWidth == 0 || e.mHeight == 0) { 162 mUnsizedDirEntries.AppendElement(e); 163 } else { 164 mDirEntries.AppendElement(e); 165 } 166 } 167 } 168 169 if (mCurrIcon == mNumIcons) { 170 if (mUnsizedDirEntries.IsEmpty()) { 171 return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0); 172 } 173 return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0); 174 } 175 176 return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); 177 } 178 179 LexerTransition<ICOState> nsICODecoder::IterateUnsizedDirEntry() { 180 MOZ_ASSERT(!mUnsizedDirEntries.IsEmpty()); 181 182 if (!mDirEntry) { 183 // The first time we are here, there is no entry selected. We must prepare a 184 // new iterator for the contained decoder to advance as it wills. Cloning at 185 // this point ensures it will begin at the end of the dir entries. 186 mReturnIterator = mLexer.Clone(*mIterator, SIZE_MAX); 187 if (mReturnIterator.isNothing()) { 188 // If we cannot read further than this point, then there is no resource 189 // data to read. 190 return Transition::TerminateFailure(); 191 } 192 } else { 193 // We have already selected an entry which means a metadata decoder has 194 // finished. Verify the size is valid and if so, add to the discovered 195 // resources. 196 if (mDirEntry->mSize.width > 0 && mDirEntry->mSize.height > 0) { 197 mDirEntries.AppendElement(*mDirEntry); 198 } 199 200 // Remove the entry from the unsized list either way. 201 mDirEntry = nullptr; 202 mUnsizedDirEntries.RemoveElementAt(0); 203 204 // Our iterator is at an unknown point, so reset it to the point that we 205 // saved. 206 mIterator = mLexer.Clone(*mReturnIterator, SIZE_MAX); 207 if (mIterator.isNothing()) { 208 MOZ_ASSERT_UNREACHABLE("Cannot re-clone return iterator"); 209 return Transition::TerminateFailure(); 210 } 211 } 212 213 // There are no more unsized entries, so we can finally decide which entry to 214 // select for decoding. 215 if (mUnsizedDirEntries.IsEmpty()) { 216 mReturnIterator.reset(); 217 return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0); 218 } 219 220 // Move to the resource data to start metadata decoding. 221 mDirEntry = &mUnsizedDirEntries[0]; 222 size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset(); 223 return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, 224 ICOState::SKIP_TO_RESOURCE, offsetToResource); 225 } 226 227 LexerTransition<ICOState> nsICODecoder::FinishDirEntry() { 228 MOZ_ASSERT(!mDirEntry); 229 230 if (mDirEntries.IsEmpty()) { 231 return Transition::TerminateFailure(); 232 } 233 234 // If an explicit output size was specified, we'll try to select the resource 235 // that matches it best below. 236 const Maybe<OrientedIntSize> desiredSize = ExplicitOutputSize(); 237 238 // Determine the biggest resource. We always use the biggest resource for the 239 // intrinsic size, and if we don't have a specific desired size, we select it 240 // as the best resource as well. 241 int32_t bestDelta = INT32_MIN; 242 IconDirEntryEx* biggestEntry = nullptr; 243 244 for (size_t i = 0; i < mDirEntries.Length(); ++i) { 245 IconDirEntryEx& e = mDirEntries[i]; 246 mImageMetadata.AddNativeSize(e.mSize); 247 248 if (!biggestEntry || 249 (e.mBitCount >= biggestEntry->mBitCount && 250 e.mSize.width * e.mSize.height >= 251 biggestEntry->mSize.width * biggestEntry->mSize.height)) { 252 biggestEntry = &e; 253 254 if (!desiredSize) { 255 mDirEntry = &e; 256 } 257 } 258 259 if (desiredSize) { 260 // Calculate the delta between this resource's size and the desired size, 261 // so we can see if it is better than our current-best option. In the 262 // case of several equally-good resources, we use the last one. "Better" 263 // in this case is determined by |delta|, a measure of the difference in 264 // size between the entry we've found and the desired size. We will choose 265 // the smallest resource that is greater than or equal to the desired size 266 // (i.e. we assume it's better to downscale a larger icon than to upscale 267 // a smaller one). 268 int32_t delta = std::min(e.mSize.width - desiredSize->width, 269 e.mSize.height - desiredSize->height); 270 if (!mDirEntry || (e.mBitCount >= mDirEntry->mBitCount && 271 ((bestDelta < 0 && delta >= bestDelta) || 272 (delta >= 0 && delta <= bestDelta)))) { 273 mDirEntry = &e; 274 bestDelta = delta; 275 } 276 } 277 } 278 279 MOZ_ASSERT(mDirEntry); 280 MOZ_ASSERT(biggestEntry); 281 282 // If this is a cursor, set the hotspot. We use the hotspot from the biggest 283 // resource since we also use that resource for the intrinsic size. 284 if (mIsCursor) { 285 mImageMetadata.SetHotspot(biggestEntry->mXHotspot, biggestEntry->mYHotspot); 286 } 287 288 // We always report the biggest resource's size as the intrinsic size; this 289 // is necessary for downscale-during-decode to work since we won't even 290 // attempt to *upscale* while decoding. 291 PostSize(biggestEntry->mSize.width, biggestEntry->mSize.height); 292 if (WantsFrameCount()) { 293 PostFrameCount(/* aFrameCount */ 1); 294 } 295 if (HasError()) { 296 return Transition::TerminateFailure(); 297 } 298 299 if (IsMetadataDecode()) { 300 return Transition::TerminateSuccess(); 301 } 302 303 if (mDirEntry->mSize == OutputSize()) { 304 // If the resource we selected matches the output size perfectly, we don't 305 // need to do any downscaling. 306 MOZ_ASSERT_IF(desiredSize, mDirEntry->mSize == *desiredSize); 307 MOZ_ASSERT_IF(!desiredSize, mDirEntry->mSize == Size()); 308 } else if (OutputSize().width < mDirEntry->mSize.width || 309 OutputSize().height < mDirEntry->mSize.height) { 310 // Create a downscaler if we need to downscale. 311 // 312 // TODO(aosmond): This is the last user of Downscaler. We should switch this 313 // to SurfacePipe as well so we can remove the code from tree. 314 mDownscaler.emplace(OutputSize().ToUnknownSize()); 315 } 316 317 size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset(); 318 return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, 319 ICOState::SKIP_TO_RESOURCE, offsetToResource); 320 } 321 322 LexerTransition<ICOState> nsICODecoder::SniffResource(const char* aData) { 323 MOZ_ASSERT(mDirEntry); 324 325 // We have BITMAPINFOSIZE bytes buffered at this point. We know an embedded 326 // BMP will have at least that many bytes by definition. We can also infer 327 // that any valid embedded PNG will contain that many bytes as well because: 328 // BITMAPINFOSIZE 329 // < 330 // signature (8 bytes) + 331 // IHDR (12 bytes header + 13 bytes data) 332 // IDAT (12 bytes header) 333 334 // We use the first PNGSIGNATURESIZE bytes to determine whether this resource 335 // is a PNG or a BMP. 336 bool isPNG = 337 !memcmp(aData, nsPNGDecoder::pngSignatureBytes, PNGSIGNATURESIZE); 338 if (isPNG) { 339 if (mDirEntry->mBytesInRes <= BITMAPINFOSIZE) { 340 return Transition::TerminateFailure(); 341 } 342 343 // Prepare a new iterator for the contained decoder to advance as it wills. 344 // Cloning at the point ensures it will begin at the resource offset. 345 Maybe<SourceBufferIterator> containedIterator = 346 mLexer.Clone(*mIterator, mDirEntry->mBytesInRes); 347 if (containedIterator.isNothing()) { 348 return Transition::TerminateFailure(); 349 } 350 351 // Create a PNG decoder which will do the rest of the work for us. 352 bool metadataDecode = mReturnIterator.isSome(); 353 Maybe<OrientedIntSize> expectedSize = 354 metadataDecode ? Nothing() : Some(mDirEntry->mSize); 355 mContainedDecoder = DecoderFactory::CreateDecoderForICOResource( 356 DecoderType::PNG, std::move(containedIterator.ref()), WrapNotNull(this), 357 metadataDecode, expectedSize); 358 359 // Read in the rest of the PNG unbuffered. 360 size_t toRead = mDirEntry->mBytesInRes - BITMAPINFOSIZE; 361 return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, 362 ICOState::READ_RESOURCE, toRead); 363 } 364 365 // Make sure we have a sane size for the bitmap information header. 366 int32_t bihSize = LittleEndian::readUint32(aData); 367 if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) { 368 return Transition::TerminateFailure(); 369 } 370 371 // Read in the rest of the bitmap information header. 372 return ReadBIH(aData); 373 } 374 375 LexerTransition<ICOState> nsICODecoder::ReadResource() { 376 if (!FlushContainedDecoder()) { 377 return Transition::TerminateFailure(); 378 } 379 380 return Transition::ContinueUnbuffered(ICOState::READ_RESOURCE); 381 } 382 383 LexerTransition<ICOState> nsICODecoder::ReadBIH(const char* aData) { 384 MOZ_ASSERT(mDirEntry); 385 386 // Extract the BPP from the BIH header; it should be trusted over the one 387 // we have from the ICO header which is usually set to 0. 388 mBPP = LittleEndian::readUint16(aData + 14); 389 390 // Check to make sure we have valid color settings. 391 uint16_t numColors = GetNumColors(); 392 if (numColors == uint16_t(-1)) { 393 return Transition::TerminateFailure(); 394 } 395 396 // The color table is present only if BPP is <= 8. 397 MOZ_ASSERT_IF(mBPP > 8, numColors == 0); 398 399 // The ICO format when containing a BMP does not include the 14 byte 400 // bitmap file header. So we create the BMP decoder via the constructor that 401 // tells it to skip this, and pass in the required data (dataOffset) that 402 // would have been present in the header. 403 uint32_t dataOffset = 404 bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE + 4 * numColors; 405 406 // Prepare a new iterator for the contained decoder to advance as it wills. 407 // Cloning at the point ensures it will begin at the resource offset. 408 Maybe<SourceBufferIterator> containedIterator = 409 mLexer.Clone(*mIterator, mDirEntry->mBytesInRes); 410 if (containedIterator.isNothing()) { 411 return Transition::TerminateFailure(); 412 } 413 414 // Create a BMP decoder which will do most of the work for us; the exception 415 // is the AND mask, which isn't present in standalone BMPs. 416 bool metadataDecode = mReturnIterator.isSome(); 417 Maybe<OrientedIntSize> expectedSize = 418 metadataDecode ? Nothing() : Some(mDirEntry->mSize); 419 mContainedDecoder = DecoderFactory::CreateDecoderForICOResource( 420 DecoderType::BMP, std::move(containedIterator.ref()), WrapNotNull(this), 421 metadataDecode, expectedSize, Some(dataOffset)); 422 423 RefPtr<nsBMPDecoder> bmpDecoder = 424 static_cast<nsBMPDecoder*>(mContainedDecoder.get()); 425 426 // Ensure the decoder has parsed at least the BMP's bitmap info header. 427 if (!FlushContainedDecoder()) { 428 return Transition::TerminateFailure(); 429 } 430 431 // If this is a metadata decode, FinishResource will any necessary checks. 432 if (mContainedDecoder->IsMetadataDecode()) { 433 return Transition::To(ICOState::FINISHED_RESOURCE, 0); 434 } 435 436 // Do we have an AND mask on this BMP? If so, we need to read it after we read 437 // the BMP data itself. 438 uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors; 439 bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry->mBytesInRes; 440 ICOState afterBMPState = 441 hasANDMask ? ICOState::PREPARE_FOR_MASK : ICOState::FINISHED_RESOURCE; 442 443 // Read in the rest of the BMP unbuffered. 444 return Transition::ToUnbuffered(afterBMPState, ICOState::READ_RESOURCE, 445 bmpDataLength); 446 } 447 448 LexerTransition<ICOState> nsICODecoder::PrepareForMask() { 449 MOZ_ASSERT(mDirEntry); 450 MOZ_ASSERT(mContainedDecoder->GetDecodeDone()); 451 452 // We have received all of the data required by the BMP decoder so flushing 453 // here guarantees the decode has finished. 454 if (!FlushContainedDecoder()) { 455 return Transition::TerminateFailure(); 456 } 457 458 MOZ_ASSERT(mContainedDecoder->GetDecodeDone()); 459 460 RefPtr<nsBMPDecoder> bmpDecoder = 461 static_cast<nsBMPDecoder*>(mContainedDecoder.get()); 462 463 uint16_t numColors = GetNumColors(); 464 MOZ_ASSERT(numColors != uint16_t(-1)); 465 466 // Determine the length of the AND mask. 467 uint32_t bmpLengthWithHeader = 468 BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors; 469 MOZ_ASSERT(bmpLengthWithHeader < mDirEntry->mBytesInRes); 470 uint32_t maskLength = mDirEntry->mBytesInRes - bmpLengthWithHeader; 471 472 // If the BMP provides its own transparency, we ignore the AND mask. 473 if (bmpDecoder->HasTransparency()) { 474 return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, 475 ICOState::SKIP_MASK, maskLength); 476 } 477 478 // Compute the row size for the mask. 479 mMaskRowSize = ((mDirEntry->mSize.width + 31) / 32) * 4; // + 31 to round up 480 481 // If the expected size of the AND mask is larger than its actual size, then 482 // we must have a truncated (and therefore corrupt) AND mask. 483 uint32_t expectedLength = mMaskRowSize * mDirEntry->mSize.height; 484 if (maskLength < expectedLength) { 485 return Transition::TerminateFailure(); 486 } 487 488 // If we're downscaling, the mask is the wrong size for the surface we've 489 // produced, so we need to downscale the mask into a temporary buffer and then 490 // combine the mask's alpha values with the color values from the image. 491 if (mDownscaler) { 492 MOZ_ASSERT(bmpDecoder->GetImageDataLength() == 493 mDownscaler->TargetSize().width * 494 mDownscaler->TargetSize().height * sizeof(uint32_t)); 495 mMaskBuffer = 496 MakeUniqueFallible<uint8_t[]>(bmpDecoder->GetImageDataLength()); 497 if (NS_WARN_IF(!mMaskBuffer)) { 498 return Transition::TerminateFailure(); 499 } 500 nsresult rv = mDownscaler->BeginFrame( 501 mDirEntry->mSize.ToUnknownSize(), Nothing(), mMaskBuffer.get(), 502 /* aFormat = */ gfx::SurfaceFormat::B8G8R8A8, 503 /* aFlipVertically = */ true); 504 if (NS_FAILED(rv)) { 505 return Transition::TerminateFailure(); 506 } 507 } 508 509 mCurrMaskLine = mDirEntry->mSize.height; 510 return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); 511 } 512 513 LexerTransition<ICOState> nsICODecoder::ReadMaskRow(const char* aData) { 514 MOZ_ASSERT(mDirEntry); 515 516 mCurrMaskLine--; 517 518 uint8_t sawTransparency = 0; 519 520 // Get the mask row we're reading. 521 const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData); 522 const uint8_t* maskRowEnd = mask + mMaskRowSize; 523 524 // Get the corresponding row of the mask buffer (if we're downscaling) or the 525 // decoded image data (if we're not). 526 uint32_t* decoded = nullptr; 527 if (mDownscaler) { 528 // Initialize the row to all white and fully opaque. 529 memset(mDownscaler->RowBuffer(), 0xFF, 530 mDirEntry->mSize.width * sizeof(uint32_t)); 531 532 decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer()); 533 } else { 534 RefPtr<nsBMPDecoder> bmpDecoder = 535 static_cast<nsBMPDecoder*>(mContainedDecoder.get()); 536 uint32_t* imageData = bmpDecoder->GetImageData(); 537 if (!imageData) { 538 return Transition::TerminateFailure(); 539 } 540 541 decoded = imageData + mCurrMaskLine * mDirEntry->mSize.width; 542 } 543 544 MOZ_ASSERT(decoded); 545 uint32_t* decodedRowEnd = decoded + mDirEntry->mSize.width; 546 547 // Iterate simultaneously through the AND mask and the image data. 548 while (mask < maskRowEnd) { 549 uint8_t idx = *mask++; 550 sawTransparency |= idx; 551 for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) { 552 // Clear pixel completely for transparency. 553 if (idx & bit) { 554 *decoded = 0; 555 } 556 decoded++; 557 } 558 } 559 560 if (mDownscaler) { 561 mDownscaler->CommitRow(); 562 } 563 564 // If any bits are set in sawTransparency, then we know at least one pixel was 565 // transparent. 566 if (sawTransparency) { 567 mHasMaskAlpha = true; 568 } 569 570 if (mCurrMaskLine == 0) { 571 return Transition::To(ICOState::FINISH_MASK, 0); 572 } 573 574 return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); 575 } 576 577 LexerTransition<ICOState> nsICODecoder::FinishMask() { 578 // If we're downscaling, we now have the appropriate alpha values in 579 // mMaskBuffer. We just need to transfer them to the image. 580 if (mDownscaler) { 581 // Retrieve the image data. 582 RefPtr<nsBMPDecoder> bmpDecoder = 583 static_cast<nsBMPDecoder*>(mContainedDecoder.get()); 584 uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData()); 585 if (!imageData) { 586 return Transition::TerminateFailure(); 587 } 588 589 // Iterate through the alpha values, copying from mask to image. 590 MOZ_ASSERT(mMaskBuffer); 591 MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0); 592 for (size_t i = 3; i < bmpDecoder->GetImageDataLength(); i += 4) { 593 imageData[i] = mMaskBuffer[i]; 594 } 595 int32_t stride = mDownscaler->TargetSize().width * sizeof(uint32_t); 596 DebugOnly<bool> ret = 597 // We know the format is OS_RGBA because we always assume bmp's inside 598 // ico's are transparent. 599 PremultiplyData(imageData, stride, SurfaceFormat::OS_RGBA, imageData, 600 stride, SurfaceFormat::OS_RGBA, 601 mDownscaler->TargetSize()); 602 MOZ_ASSERT(ret); 603 } 604 605 return Transition::To(ICOState::FINISHED_RESOURCE, 0); 606 } 607 608 LexerTransition<ICOState> nsICODecoder::FinishResource() { 609 MOZ_ASSERT(mDirEntry); 610 611 // We have received all of the data required by the PNG/BMP decoder so 612 // flushing here guarantees the decode has finished. 613 if (!FlushContainedDecoder()) { 614 return Transition::TerminateFailure(); 615 } 616 617 MOZ_ASSERT(mContainedDecoder->GetDecodeDone()); 618 619 // If it is a metadata decode, all we were trying to get was the size 620 // information missing from the dir entry. 621 if (mContainedDecoder->IsMetadataDecode()) { 622 if (mContainedDecoder->HasSize()) { 623 mDirEntry->mSize = mContainedDecoder->Size(); 624 } 625 return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0); 626 } 627 628 // Raymond Chen says that 32bpp only are valid PNG ICOs 629 // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx 630 if (!mContainedDecoder->IsValidICOResource()) { 631 return Transition::TerminateFailure(); 632 } 633 634 // This size from the resource should match that from the dir entry. 635 MOZ_ASSERT_IF(mContainedDecoder->HasSize(), 636 mContainedDecoder->Size() == mDirEntry->mSize); 637 638 return Transition::TerminateSuccess(); 639 } 640 641 LexerResult nsICODecoder::DoDecode(SourceBufferIterator& aIterator, 642 IResumable* aOnResume) { 643 MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); 644 645 return mLexer.Lex( 646 aIterator, aOnResume, 647 [=](ICOState aState, const char* aData, size_t aLength) { 648 switch (aState) { 649 case ICOState::HEADER: 650 return ReadHeader(aData); 651 case ICOState::DIR_ENTRY: 652 return ReadDirEntry(aData); 653 case ICOState::FINISHED_DIR_ENTRY: 654 return FinishDirEntry(); 655 case ICOState::ITERATE_UNSIZED_DIR_ENTRY: 656 return IterateUnsizedDirEntry(); 657 case ICOState::SKIP_TO_RESOURCE: 658 return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE); 659 case ICOState::FOUND_RESOURCE: 660 return Transition::To(ICOState::SNIFF_RESOURCE, BITMAPINFOSIZE); 661 case ICOState::SNIFF_RESOURCE: 662 return SniffResource(aData); 663 case ICOState::READ_RESOURCE: 664 return ReadResource(); 665 case ICOState::PREPARE_FOR_MASK: 666 return PrepareForMask(); 667 case ICOState::READ_MASK_ROW: 668 return ReadMaskRow(aData); 669 case ICOState::FINISH_MASK: 670 return FinishMask(); 671 case ICOState::SKIP_MASK: 672 return Transition::ContinueUnbuffered(ICOState::SKIP_MASK); 673 case ICOState::FINISHED_RESOURCE: 674 return FinishResource(); 675 default: 676 MOZ_CRASH("Unknown ICOState"); 677 } 678 }); 679 } 680 681 bool nsICODecoder::FlushContainedDecoder() { 682 MOZ_ASSERT(mContainedDecoder); 683 684 bool succeeded = true; 685 686 // If we run out of data, the ICO decoder will get resumed when there's more 687 // data available, as usual, so we don't need the contained decoder to get 688 // resumed too. To avoid that, we provide an IResumable which just does 689 // nothing. All the caller needs to do is flush when there is new data. 690 LexerResult result = mContainedDecoder->Decode(); 691 if (result == LexerResult(TerminalState::FAILURE)) { 692 succeeded = false; 693 } 694 695 MOZ_ASSERT(result != LexerResult(Yield::OUTPUT_AVAILABLE), 696 "Unexpected yield"); 697 698 // Make our state the same as the state of the contained decoder, and 699 // propagate errors. 700 mProgress |= mContainedDecoder->TakeProgress(); 701 mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); 702 if (mContainedDecoder->HasError()) { 703 succeeded = false; 704 } 705 706 return succeeded; 707 } 708 709 } // namespace image 710 } // namespace mozilla