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