/src/mozilla-central/image/decoders/nsPNGDecoder.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
2 | | * |
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 | | #include "ImageLogging.h" // Must appear first |
8 | | #include "nsPNGDecoder.h" |
9 | | |
10 | | #include <algorithm> |
11 | | #include <cstdint> |
12 | | |
13 | | #include "gfxColor.h" |
14 | | #include "gfxPlatform.h" |
15 | | #include "imgFrame.h" |
16 | | #include "nsColor.h" |
17 | | #include "nsIInputStream.h" |
18 | | #include "nsMemory.h" |
19 | | #include "nsRect.h" |
20 | | #include "nspr.h" |
21 | | #include "png.h" |
22 | | |
23 | | #include "RasterImage.h" |
24 | | #include "SurfaceCache.h" |
25 | | #include "SurfacePipeFactory.h" |
26 | | #include "mozilla/DebugOnly.h" |
27 | | #include "mozilla/Telemetry.h" |
28 | | |
29 | | using namespace mozilla::gfx; |
30 | | |
31 | | using std::min; |
32 | | |
33 | | namespace mozilla { |
34 | | namespace image { |
35 | | |
36 | | static LazyLogModule sPNGLog("PNGDecoder"); |
37 | | static LazyLogModule sPNGDecoderAccountingLog("PNGDecoderAccounting"); |
38 | | |
39 | | // limit image dimensions (bug #251381, #591822, #967656, and #1283961) |
40 | | #ifndef MOZ_PNG_MAX_WIDTH |
41 | | # define MOZ_PNG_MAX_WIDTH 0x7fffffff // Unlimited |
42 | | #endif |
43 | | #ifndef MOZ_PNG_MAX_HEIGHT |
44 | | # define MOZ_PNG_MAX_HEIGHT 0x7fffffff // Unlimited |
45 | | #endif |
46 | | |
47 | | nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() |
48 | | : mDispose(DisposalMethod::KEEP) |
49 | | , mBlend(BlendMethod::OVER) |
50 | | , mTimeout(0) |
51 | 0 | { } |
52 | | |
53 | | #ifdef PNG_APNG_SUPPORTED |
54 | | |
55 | | int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo) |
56 | 0 | { |
57 | 0 | // Delay, in seconds, is delayNum / delayDen. |
58 | 0 | png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo); |
59 | 0 | png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo); |
60 | 0 |
|
61 | 0 | if (delayNum == 0) { |
62 | 0 | return 0; // SetFrameTimeout() will set to a minimum. |
63 | 0 | } |
64 | 0 | |
65 | 0 | if (delayDen == 0) { |
66 | 0 | delayDen = 100; // So says the APNG spec. |
67 | 0 | } |
68 | 0 |
|
69 | 0 | // Need to cast delay_num to float to have a proper division and |
70 | 0 | // the result to int to avoid a compiler warning. |
71 | 0 | return static_cast<int32_t>(static_cast<double>(delayNum) * 1000 / delayDen); |
72 | 0 | } |
73 | | |
74 | | nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) |
75 | | : mDispose(DisposalMethod::KEEP) |
76 | | , mBlend(BlendMethod::OVER) |
77 | | , mTimeout(0) |
78 | 0 | { |
79 | 0 | png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); |
80 | 0 | png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo); |
81 | 0 |
|
82 | 0 | if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { |
83 | 0 | mDispose = DisposalMethod::RESTORE_PREVIOUS; |
84 | 0 | } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) { |
85 | 0 | mDispose = DisposalMethod::CLEAR; |
86 | 0 | } else { |
87 | 0 | mDispose = DisposalMethod::KEEP; |
88 | 0 | } |
89 | 0 |
|
90 | 0 | if (blend_op == PNG_BLEND_OP_SOURCE) { |
91 | 0 | mBlend = BlendMethod::SOURCE; |
92 | 0 | } else { |
93 | 0 | mBlend = BlendMethod::OVER; |
94 | 0 | } |
95 | 0 |
|
96 | 0 | mTimeout = GetNextFrameDelay(aPNG, aInfo); |
97 | 0 | } |
98 | | #endif |
99 | | |
100 | | // First 8 bytes of a PNG file |
101 | | const uint8_t |
102 | | nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; |
103 | | |
104 | | nsPNGDecoder::nsPNGDecoder(RasterImage* aImage) |
105 | | : Decoder(aImage) |
106 | | , mLexer(Transition::ToUnbuffered(State::FINISHED_PNG_DATA, |
107 | | State::PNG_DATA, |
108 | | SIZE_MAX), |
109 | | Transition::TerminateSuccess()) |
110 | | , mNextTransition(Transition::ContinueUnbuffered(State::PNG_DATA)) |
111 | | , mLastChunkLength(0) |
112 | | , mPNG(nullptr) |
113 | | , mInfo(nullptr) |
114 | | , mCMSLine(nullptr) |
115 | | , interlacebuf(nullptr) |
116 | | , mInProfile(nullptr) |
117 | | , mTransform(nullptr) |
118 | | , mFormat(SurfaceFormat::UNKNOWN) |
119 | | , mCMSMode(0) |
120 | | , mChannels(0) |
121 | | , mPass(0) |
122 | | , mFrameIsHidden(false) |
123 | | , mDisablePremultipliedAlpha(false) |
124 | | , mNumFrames(0) |
125 | 0 | { } |
126 | | |
127 | | nsPNGDecoder::~nsPNGDecoder() |
128 | 0 | { |
129 | 0 | if (mPNG) { |
130 | 0 | png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr); |
131 | 0 | } |
132 | 0 | if (mCMSLine) { |
133 | 0 | free(mCMSLine); |
134 | 0 | } |
135 | 0 | if (interlacebuf) { |
136 | 0 | free(interlacebuf); |
137 | 0 | } |
138 | 0 | if (mInProfile) { |
139 | 0 | qcms_profile_release(mInProfile); |
140 | 0 |
|
141 | 0 | // mTransform belongs to us only if mInProfile is non-null |
142 | 0 | if (mTransform) { |
143 | 0 | qcms_transform_release(mTransform); |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | nsPNGDecoder::TransparencyType |
149 | | nsPNGDecoder::GetTransparencyType(const IntRect& aFrameRect) |
150 | 0 | { |
151 | 0 | // Check if the image has a transparent color in its palette. |
152 | 0 | if (HasAlphaChannel()) { |
153 | 0 | return TransparencyType::eAlpha; |
154 | 0 | } |
155 | 0 | if (!aFrameRect.IsEqualEdges(FullFrame())) { |
156 | 0 | MOZ_ASSERT(HasAnimation()); |
157 | 0 | return TransparencyType::eFrameRect; |
158 | 0 | } |
159 | 0 |
|
160 | 0 | return TransparencyType::eNone; |
161 | 0 | } |
162 | | |
163 | | void |
164 | | nsPNGDecoder::PostHasTransparencyIfNeeded(TransparencyType aTransparencyType) |
165 | 0 | { |
166 | 0 | switch (aTransparencyType) { |
167 | 0 | case TransparencyType::eNone: |
168 | 0 | return; |
169 | 0 |
|
170 | 0 | case TransparencyType::eAlpha: |
171 | 0 | PostHasTransparency(); |
172 | 0 | return; |
173 | 0 |
|
174 | 0 | case TransparencyType::eFrameRect: |
175 | 0 | // If the first frame of animated image doesn't draw into the whole image, |
176 | 0 | // then record that it is transparent. For subsequent frames, this doesn't |
177 | 0 | // affect transparency, because they're composited on top of all previous |
178 | 0 | // frames. |
179 | 0 | if (mNumFrames == 0) { |
180 | 0 | PostHasTransparency(); |
181 | 0 | } |
182 | 0 | return; |
183 | 0 | } |
184 | 0 | } |
185 | | |
186 | | // CreateFrame() is used for both simple and animated images. |
187 | | nsresult |
188 | | nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo) |
189 | 0 | { |
190 | 0 | MOZ_ASSERT(HasSize()); |
191 | 0 | MOZ_ASSERT(!IsMetadataDecode()); |
192 | 0 |
|
193 | 0 | // Check if we have transparency, and send notifications if needed. |
194 | 0 | auto transparency = GetTransparencyType(aFrameInfo.mFrameRect); |
195 | 0 | PostHasTransparencyIfNeeded(transparency); |
196 | 0 | mFormat = transparency == TransparencyType::eNone |
197 | 0 | ? SurfaceFormat::B8G8R8X8 |
198 | 0 | : SurfaceFormat::B8G8R8A8; |
199 | 0 |
|
200 | 0 | // Make sure there's no animation or padding if we're downscaling. |
201 | 0 | MOZ_ASSERT_IF(Size() != OutputSize(), mNumFrames == 0); |
202 | 0 | MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation()); |
203 | 0 | MOZ_ASSERT_IF(Size() != OutputSize(), |
204 | 0 | transparency != TransparencyType::eFrameRect); |
205 | 0 |
|
206 | 0 | Maybe<AnimationParams> animParams; |
207 | 0 | #ifdef PNG_APNG_SUPPORTED |
208 | 0 | if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) { |
209 | 0 | mAnimInfo = AnimFrameInfo(mPNG, mInfo); |
210 | 0 |
|
211 | 0 | if (mAnimInfo.mDispose == DisposalMethod::CLEAR) { |
212 | 0 | // We may have to display the background under this image during |
213 | 0 | // animation playback, so we regard it as transparent. |
214 | 0 | PostHasTransparency(); |
215 | 0 | } |
216 | 0 |
|
217 | 0 | animParams.emplace(AnimationParams { |
218 | 0 | aFrameInfo.mFrameRect, |
219 | 0 | FrameTimeout::FromRawMilliseconds(mAnimInfo.mTimeout), |
220 | 0 | mNumFrames, mAnimInfo.mBlend, mAnimInfo.mDispose |
221 | 0 | }); |
222 | 0 | } |
223 | 0 | #endif |
224 | 0 |
|
225 | 0 | // If this image is interlaced, we can display better quality intermediate |
226 | 0 | // results to the user by post processing them with ADAM7InterpolatingFilter. |
227 | 0 | SurfacePipeFlags pipeFlags = aFrameInfo.mIsInterlaced |
228 | 0 | ? SurfacePipeFlags::ADAM7_INTERPOLATE |
229 | 0 | : SurfacePipeFlags(); |
230 | 0 |
|
231 | 0 | if (mNumFrames == 0) { |
232 | 0 | // The first frame may be displayed progressively. |
233 | 0 | pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY; |
234 | 0 | } |
235 | 0 |
|
236 | 0 | if (ShouldBlendAnimation()) { |
237 | 0 | pipeFlags |= SurfacePipeFlags::BLEND_ANIMATION; |
238 | 0 | } |
239 | 0 |
|
240 | 0 | Maybe<SurfacePipe> pipe = |
241 | 0 | SurfacePipeFactory::CreateSurfacePipe(this, Size(), OutputSize(), |
242 | 0 | aFrameInfo.mFrameRect, mFormat, |
243 | 0 | animParams, pipeFlags); |
244 | 0 |
|
245 | 0 | if (!pipe) { |
246 | 0 | mPipe = SurfacePipe(); |
247 | 0 | return NS_ERROR_FAILURE; |
248 | 0 | } |
249 | 0 | |
250 | 0 | mPipe = std::move(*pipe); |
251 | 0 |
|
252 | 0 | mFrameRect = aFrameInfo.mFrameRect; |
253 | 0 | mPass = 0; |
254 | 0 |
|
255 | 0 | MOZ_LOG(sPNGDecoderAccountingLog, LogLevel::Debug, |
256 | 0 | ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created " |
257 | 0 | "image frame with %dx%d pixels for decoder %p", |
258 | 0 | mFrameRect.Width(), mFrameRect.Height(), this)); |
259 | 0 |
|
260 | 0 | return NS_OK; |
261 | 0 | } |
262 | | |
263 | | // set timeout and frame disposal method for the current frame |
264 | | void |
265 | | nsPNGDecoder::EndImageFrame() |
266 | 0 | { |
267 | 0 | if (mFrameIsHidden) { |
268 | 0 | return; |
269 | 0 | } |
270 | 0 | |
271 | 0 | mNumFrames++; |
272 | 0 |
|
273 | 0 | Opacity opacity = mFormat == SurfaceFormat::B8G8R8X8 |
274 | 0 | ? Opacity::FULLY_OPAQUE |
275 | 0 | : Opacity::SOME_TRANSPARENCY; |
276 | 0 |
|
277 | 0 | PostFrameStop(opacity); |
278 | 0 | } |
279 | | |
280 | | nsresult |
281 | | nsPNGDecoder::InitInternal() |
282 | 0 | { |
283 | 0 | mCMSMode = gfxPlatform::GetCMSMode(); |
284 | 0 | if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) { |
285 | 0 | mCMSMode = eCMSMode_Off; |
286 | 0 | } |
287 | 0 | mDisablePremultipliedAlpha = |
288 | 0 | bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA); |
289 | 0 |
|
290 | | #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED |
291 | | static png_byte color_chunks[]= |
292 | | { 99, 72, 82, 77, '\0', // cHRM |
293 | | 105, 67, 67, 80, '\0'}; // iCCP |
294 | | static png_byte unused_chunks[]= |
295 | | { 98, 75, 71, 68, '\0', // bKGD |
296 | | 101, 88, 73, 102, '\0', // eXIf |
297 | | 104, 73, 83, 84, '\0', // hIST |
298 | | 105, 84, 88, 116, '\0', // iTXt |
299 | | 111, 70, 70, 115, '\0', // oFFs |
300 | | 112, 67, 65, 76, '\0', // pCAL |
301 | | 115, 67, 65, 76, '\0', // sCAL |
302 | | 112, 72, 89, 115, '\0', // pHYs |
303 | | 115, 66, 73, 84, '\0', // sBIT |
304 | | 115, 80, 76, 84, '\0', // sPLT |
305 | | 116, 69, 88, 116, '\0', // tEXt |
306 | | 116, 73, 77, 69, '\0', // tIME |
307 | | 122, 84, 88, 116, '\0'}; // zTXt |
308 | | #endif |
309 | |
|
310 | 0 | // Initialize the container's source image header |
311 | 0 | // Always decode to 24 bit pixdepth |
312 | 0 |
|
313 | 0 | mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, |
314 | 0 | nullptr, nsPNGDecoder::error_callback, |
315 | 0 | nsPNGDecoder::warning_callback); |
316 | 0 | if (!mPNG) { |
317 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
318 | 0 | } |
319 | 0 | |
320 | 0 | mInfo = png_create_info_struct(mPNG); |
321 | 0 | if (!mInfo) { |
322 | 0 | png_destroy_read_struct(&mPNG, nullptr, nullptr); |
323 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
324 | 0 | } |
325 | 0 |
|
326 | | #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED |
327 | | // Ignore unused chunks |
328 | | if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) { |
329 | | png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2); |
330 | | } |
331 | | |
332 | | png_set_keep_unknown_chunks(mPNG, 1, unused_chunks, |
333 | | (int)sizeof(unused_chunks)/5); |
334 | | #endif |
335 | | |
336 | 0 | #ifdef PNG_SET_USER_LIMITS_SUPPORTED |
337 | 0 | png_set_user_limits(mPNG, MOZ_PNG_MAX_WIDTH, MOZ_PNG_MAX_HEIGHT); |
338 | 0 | if (mCMSMode != eCMSMode_Off) { |
339 | 0 | png_set_chunk_malloc_max(mPNG, 4000000L); |
340 | 0 | } |
341 | 0 | #endif |
342 | 0 |
|
343 | | #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED |
344 | | // Disallow palette-index checking, for speed; we would ignore the warning |
345 | | // anyhow. This feature was added at libpng version 1.5.10 and is disabled |
346 | | // in the embedded libpng but enabled by default in the system libpng. This |
347 | | // call also disables it in the system libpng, for decoding speed. |
348 | | // Bug #745202. |
349 | | png_set_check_for_invalid_index(mPNG, 0); |
350 | | #endif |
351 | |
|
352 | 0 | #ifdef PNG_SET_OPTION_SUPPORTED |
353 | | #if defined(PNG_sRGB_PROFILE_CHECKS) && PNG_sRGB_PROFILE_CHECKS >= 0 |
354 | | // Skip checking of sRGB ICC profiles |
355 | | png_set_option(mPNG, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); |
356 | | #endif |
357 | |
|
358 | 0 | #ifdef PNG_MAXIMUM_INFLATE_WINDOW |
359 | 0 | // Force a larger zlib inflate window as some images in the wild have |
360 | 0 | // incorrectly set metadata (specifically CMF bits) which prevent us from |
361 | 0 | // decoding them otherwise. |
362 | 0 | png_set_option(mPNG, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); |
363 | 0 | #endif |
364 | 0 | #endif |
365 | 0 |
|
366 | 0 | // use this as libpng "progressive pointer" (retrieve in callbacks) |
367 | 0 | png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this), |
368 | 0 | nsPNGDecoder::info_callback, |
369 | 0 | nsPNGDecoder::row_callback, |
370 | 0 | nsPNGDecoder::end_callback); |
371 | 0 |
|
372 | 0 | return NS_OK; |
373 | 0 | } |
374 | | |
375 | | LexerResult |
376 | | nsPNGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) |
377 | 0 | { |
378 | 0 | MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); |
379 | 0 |
|
380 | 0 | return mLexer.Lex(aIterator, aOnResume, |
381 | 0 | [=](State aState, const char* aData, size_t aLength) { |
382 | 0 | switch (aState) { |
383 | 0 | case State::PNG_DATA: |
384 | 0 | return ReadPNGData(aData, aLength); |
385 | 0 | case State::FINISHED_PNG_DATA: |
386 | 0 | return FinishedPNGData(); |
387 | 0 | } |
388 | 0 | MOZ_CRASH("Unknown State"); |
389 | 0 | }); |
390 | 0 | } |
391 | | |
392 | | LexerTransition<nsPNGDecoder::State> |
393 | | nsPNGDecoder::ReadPNGData(const char* aData, size_t aLength) |
394 | 0 | { |
395 | 0 | // If we were waiting until after returning from a yield to call |
396 | 0 | // CreateFrame(), call it now. |
397 | 0 | if (mNextFrameInfo) { |
398 | 0 | if (NS_FAILED(CreateFrame(*mNextFrameInfo))) { |
399 | 0 | return Transition::TerminateFailure(); |
400 | 0 | } |
401 | 0 | |
402 | 0 | MOZ_ASSERT(mImageData, "Should have a buffer now"); |
403 | 0 | mNextFrameInfo = Nothing(); |
404 | 0 | } |
405 | 0 |
|
406 | 0 | // libpng uses setjmp/longjmp for error handling. |
407 | 0 | if (setjmp(png_jmpbuf(mPNG))) { |
408 | 0 | return Transition::TerminateFailure(); |
409 | 0 | } |
410 | 0 | |
411 | 0 | // Pass the data off to libpng. |
412 | 0 | mLastChunkLength = aLength; |
413 | 0 | mNextTransition = Transition::ContinueUnbuffered(State::PNG_DATA); |
414 | 0 | png_process_data(mPNG, mInfo, |
415 | 0 | reinterpret_cast<unsigned char*>(const_cast<char*>((aData))), |
416 | 0 | aLength); |
417 | 0 |
|
418 | 0 | // Make sure that we've reached a terminal state if decoding is done. |
419 | 0 | MOZ_ASSERT_IF(GetDecodeDone(), mNextTransition.NextStateIsTerminal()); |
420 | 0 | MOZ_ASSERT_IF(HasError(), mNextTransition.NextStateIsTerminal()); |
421 | 0 |
|
422 | 0 | // Continue with whatever transition the callback code requested. We |
423 | 0 | // initialized this to Transition::ContinueUnbuffered(State::PNG_DATA) above, |
424 | 0 | // so by default we just continue the unbuffered read. |
425 | 0 | return mNextTransition; |
426 | 0 | } |
427 | | |
428 | | LexerTransition<nsPNGDecoder::State> |
429 | | nsPNGDecoder::FinishedPNGData() |
430 | 0 | { |
431 | 0 | // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read |
432 | 0 | // all that data something is really wrong. |
433 | 0 | MOZ_ASSERT_UNREACHABLE("Read the entire address space?"); |
434 | 0 | return Transition::TerminateFailure(); |
435 | 0 | } |
436 | | |
437 | | // Sets up gamma pre-correction in libpng before our callback gets called. |
438 | | // We need to do this if we don't end up with a CMS profile. |
439 | | static void |
440 | | PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr) |
441 | 0 | { |
442 | 0 | double aGamma; |
443 | 0 |
|
444 | 0 | if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) { |
445 | 0 | if ((aGamma <= 0.0) || (aGamma > 21474.83)) { |
446 | 0 | aGamma = 0.45455; |
447 | 0 | png_set_gAMA(png_ptr, info_ptr, aGamma); |
448 | 0 | } |
449 | 0 | png_set_gamma(png_ptr, 2.2, aGamma); |
450 | 0 | } else { |
451 | 0 | png_set_gamma(png_ptr, 2.2, 0.45455); |
452 | 0 | } |
453 | 0 | } |
454 | | |
455 | | // Adapted from http://www.littlecms.com/pngchrm.c example code |
456 | | static qcms_profile* |
457 | | PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr, |
458 | | int color_type, qcms_data_type* inType, uint32_t* intent) |
459 | 0 | { |
460 | 0 | qcms_profile* profile = nullptr; |
461 | 0 | *intent = QCMS_INTENT_PERCEPTUAL; // Our default |
462 | 0 |
|
463 | 0 | // First try to see if iCCP chunk is present |
464 | 0 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { |
465 | 0 | png_uint_32 profileLen; |
466 | 0 | png_bytep profileData; |
467 | 0 | png_charp profileName; |
468 | 0 | int compression; |
469 | 0 |
|
470 | 0 | png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, |
471 | 0 | &profileData, &profileLen); |
472 | 0 |
|
473 | 0 | profile = qcms_profile_from_memory((char*)profileData, profileLen); |
474 | 0 | if (profile) { |
475 | 0 | uint32_t profileSpace = qcms_profile_get_color_space(profile); |
476 | 0 |
|
477 | 0 | bool mismatch = false; |
478 | 0 | if (color_type & PNG_COLOR_MASK_COLOR) { |
479 | 0 | if (profileSpace != icSigRgbData) { |
480 | 0 | mismatch = true; |
481 | 0 | } |
482 | 0 | } else { |
483 | 0 | if (profileSpace == icSigRgbData) { |
484 | 0 | png_set_gray_to_rgb(png_ptr); |
485 | 0 | } else if (profileSpace != icSigGrayData) { |
486 | 0 | mismatch = true; |
487 | 0 | } |
488 | 0 | } |
489 | 0 |
|
490 | 0 | if (mismatch) { |
491 | 0 | qcms_profile_release(profile); |
492 | 0 | profile = nullptr; |
493 | 0 | } else { |
494 | 0 | *intent = qcms_profile_get_rendering_intent(profile); |
495 | 0 | } |
496 | 0 | } |
497 | 0 | } |
498 | 0 |
|
499 | 0 | // Check sRGB chunk |
500 | 0 | if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { |
501 | 0 | profile = qcms_profile_sRGB(); |
502 | 0 |
|
503 | 0 | if (profile) { |
504 | 0 | int fileIntent; |
505 | 0 | png_set_gray_to_rgb(png_ptr); |
506 | 0 | png_get_sRGB(png_ptr, info_ptr, &fileIntent); |
507 | 0 | uint32_t map[] = { QCMS_INTENT_PERCEPTUAL, |
508 | 0 | QCMS_INTENT_RELATIVE_COLORIMETRIC, |
509 | 0 | QCMS_INTENT_SATURATION, |
510 | 0 | QCMS_INTENT_ABSOLUTE_COLORIMETRIC }; |
511 | 0 | *intent = map[fileIntent]; |
512 | 0 | } |
513 | 0 | } |
514 | 0 |
|
515 | 0 | // Check gAMA/cHRM chunks |
516 | 0 | if (!profile && |
517 | 0 | png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) && |
518 | 0 | png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) { |
519 | 0 | qcms_CIE_xyYTRIPLE primaries; |
520 | 0 | qcms_CIE_xyY whitePoint; |
521 | 0 |
|
522 | 0 | png_get_cHRM(png_ptr, info_ptr, |
523 | 0 | &whitePoint.x, &whitePoint.y, |
524 | 0 | &primaries.red.x, &primaries.red.y, |
525 | 0 | &primaries.green.x, &primaries.green.y, |
526 | 0 | &primaries.blue.x, &primaries.blue.y); |
527 | 0 | whitePoint.Y = |
528 | 0 | primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0; |
529 | 0 |
|
530 | 0 | double gammaOfFile; |
531 | 0 |
|
532 | 0 | png_get_gAMA(png_ptr, info_ptr, &gammaOfFile); |
533 | 0 |
|
534 | 0 | profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries, |
535 | 0 | 1.0/gammaOfFile); |
536 | 0 |
|
537 | 0 | if (profile) { |
538 | 0 | png_set_gray_to_rgb(png_ptr); |
539 | 0 | } |
540 | 0 | } |
541 | 0 |
|
542 | 0 | if (profile) { |
543 | 0 | uint32_t profileSpace = qcms_profile_get_color_space(profile); |
544 | 0 | if (profileSpace == icSigGrayData) { |
545 | 0 | if (color_type & PNG_COLOR_MASK_ALPHA) { |
546 | 0 | *inType = QCMS_DATA_GRAYA_8; |
547 | 0 | } else { |
548 | 0 | *inType = QCMS_DATA_GRAY_8; |
549 | 0 | } |
550 | 0 | } else { |
551 | 0 | if (color_type & PNG_COLOR_MASK_ALPHA || |
552 | 0 | png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { |
553 | 0 | *inType = QCMS_DATA_RGBA_8; |
554 | 0 | } else { |
555 | 0 | *inType = QCMS_DATA_RGB_8; |
556 | 0 | } |
557 | 0 | } |
558 | 0 | } |
559 | 0 |
|
560 | 0 | return profile; |
561 | 0 | } |
562 | | |
563 | | void |
564 | | nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) |
565 | 0 | { |
566 | 0 | png_uint_32 width, height; |
567 | 0 | int bit_depth, color_type, interlace_type, compression_type, filter_type; |
568 | 0 | unsigned int channels; |
569 | 0 |
|
570 | 0 | png_bytep trans = nullptr; |
571 | 0 | int num_trans = 0; |
572 | 0 |
|
573 | 0 | nsPNGDecoder* decoder = |
574 | 0 | static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); |
575 | 0 |
|
576 | 0 | // Always decode to 24-bit RGB or 32-bit RGBA |
577 | 0 | png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, |
578 | 0 | &interlace_type, &compression_type, &filter_type); |
579 | 0 |
|
580 | 0 | const IntRect frameRect(0, 0, width, height); |
581 | 0 |
|
582 | 0 | // Post our size to the superclass |
583 | 0 | decoder->PostSize(frameRect.Width(), frameRect.Height()); |
584 | 0 |
|
585 | 0 | if (width > |
586 | 0 | SurfaceCache::MaximumCapacity()/(bit_depth > 8 ? 16:8)) { |
587 | 0 | // libpng needs space to allocate two row buffers |
588 | 0 | png_error(decoder->mPNG, "Image is too wide"); |
589 | 0 | } |
590 | 0 | |
591 | 0 | if (decoder->HasError()) { |
592 | 0 | // Setting the size led to an error. |
593 | 0 | png_error(decoder->mPNG, "Sizing error"); |
594 | 0 | } |
595 | 0 | |
596 | 0 | if (color_type == PNG_COLOR_TYPE_PALETTE) { |
597 | 0 | png_set_expand(png_ptr); |
598 | 0 | } |
599 | 0 |
|
600 | 0 | if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { |
601 | 0 | png_set_expand(png_ptr); |
602 | 0 | } |
603 | 0 |
|
604 | 0 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { |
605 | 0 | png_color_16p trans_values; |
606 | 0 | png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); |
607 | 0 | // libpng doesn't reject a tRNS chunk with out-of-range samples |
608 | 0 | // so we check it here to avoid setting up a useless opacity |
609 | 0 | // channel or producing unexpected transparent pixels (bug #428045) |
610 | 0 | if (bit_depth < 16) { |
611 | 0 | png_uint_16 sample_max = (1 << bit_depth) - 1; |
612 | 0 | if ((color_type == PNG_COLOR_TYPE_GRAY && |
613 | 0 | trans_values->gray > sample_max) || |
614 | 0 | (color_type == PNG_COLOR_TYPE_RGB && |
615 | 0 | (trans_values->red > sample_max || |
616 | 0 | trans_values->green > sample_max || |
617 | 0 | trans_values->blue > sample_max))) { |
618 | 0 | // clear the tRNS valid flag and release tRNS memory |
619 | 0 | png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); |
620 | 0 | num_trans = 0; |
621 | 0 | } |
622 | 0 | } |
623 | 0 | if (num_trans != 0) { |
624 | 0 | png_set_expand(png_ptr); |
625 | 0 | } |
626 | 0 | } |
627 | 0 |
|
628 | 0 | if (bit_depth == 16) { |
629 | 0 | png_set_scale_16(png_ptr); |
630 | 0 | } |
631 | 0 |
|
632 | 0 | qcms_data_type inType = QCMS_DATA_RGBA_8; |
633 | 0 | uint32_t intent = -1; |
634 | 0 | uint32_t pIntent; |
635 | 0 | if (decoder->mCMSMode != eCMSMode_Off) { |
636 | 0 | intent = gfxPlatform::GetRenderingIntent(); |
637 | 0 | decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, |
638 | 0 | color_type, &inType, &pIntent); |
639 | 0 | // If we're not mandating an intent, use the one from the image. |
640 | 0 | if (intent == uint32_t(-1)) { |
641 | 0 | intent = pIntent; |
642 | 0 | } |
643 | 0 | } |
644 | 0 | if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { |
645 | 0 | qcms_data_type outType; |
646 | 0 |
|
647 | 0 | if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) { |
648 | 0 | outType = QCMS_DATA_RGBA_8; |
649 | 0 | } else { |
650 | 0 | outType = QCMS_DATA_RGB_8; |
651 | 0 | } |
652 | 0 |
|
653 | 0 | decoder->mTransform = qcms_transform_create(decoder->mInProfile, |
654 | 0 | inType, |
655 | 0 | gfxPlatform::GetCMSOutputProfile(), |
656 | 0 | outType, |
657 | 0 | (qcms_intent)intent); |
658 | 0 | } else { |
659 | 0 | png_set_gray_to_rgb(png_ptr); |
660 | 0 |
|
661 | 0 | // only do gamma correction if CMS isn't entirely disabled |
662 | 0 | if (decoder->mCMSMode != eCMSMode_Off) { |
663 | 0 | PNGDoGammaCorrection(png_ptr, info_ptr); |
664 | 0 | } |
665 | 0 |
|
666 | 0 | if (decoder->mCMSMode == eCMSMode_All) { |
667 | 0 | if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) { |
668 | 0 | decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); |
669 | 0 | } else { |
670 | 0 | decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); |
671 | 0 | } |
672 | 0 | } |
673 | 0 | } |
674 | 0 |
|
675 | 0 | // Let libpng expand interlaced images. |
676 | 0 | const bool isInterlaced = interlace_type == PNG_INTERLACE_ADAM7; |
677 | 0 | if (isInterlaced) { |
678 | 0 | png_set_interlace_handling(png_ptr); |
679 | 0 | } |
680 | 0 |
|
681 | 0 | // now all of those things we set above are used to update various struct |
682 | 0 | // members and whatnot, after which we can get channels, rowbytes, etc. |
683 | 0 | png_read_update_info(png_ptr, info_ptr); |
684 | 0 | decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); |
685 | 0 |
|
686 | 0 | //---------------------------------------------------------------// |
687 | 0 | // copy PNG info into imagelib structs (formerly png_set_dims()) // |
688 | 0 | //---------------------------------------------------------------// |
689 | 0 |
|
690 | 0 | if (channels < 1 || channels > 4) { |
691 | 0 | png_error(decoder->mPNG, "Invalid number of channels"); |
692 | 0 | } |
693 | 0 | |
694 | 0 | #ifdef PNG_APNG_SUPPORTED |
695 | 0 | bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL); |
696 | 0 | if (isAnimated) { |
697 | 0 | int32_t rawTimeout = GetNextFrameDelay(png_ptr, info_ptr); |
698 | 0 | decoder->PostIsAnimated(FrameTimeout::FromRawMilliseconds(rawTimeout)); |
699 | 0 |
|
700 | 0 | if (decoder->Size() != decoder->OutputSize() && |
701 | 0 | !decoder->IsFirstFrameDecode()) { |
702 | 0 | MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode " |
703 | 0 | "for an animated image?"); |
704 | 0 | png_error(decoder->mPNG, "Invalid downscale attempt"); // Abort decode. |
705 | 0 | } |
706 | 0 | } |
707 | 0 | #endif |
708 | 0 |
|
709 | 0 | if (decoder->IsMetadataDecode()) { |
710 | 0 | // If we are animated then the first frame rect is either: |
711 | 0 | // 1) the whole image if the IDAT chunk is part of the animation |
712 | 0 | // 2) the frame rect of the first fDAT chunk otherwise. |
713 | 0 | // If we are not animated then we want to make sure to call |
714 | 0 | // PostHasTransparency in the metadata decode if we need to. So it's |
715 | 0 | // okay to pass IntRect(0, 0, width, height) here for animated images; |
716 | 0 | // they will call with the proper first frame rect in the full decode. |
717 | 0 | auto transparency = decoder->GetTransparencyType(frameRect); |
718 | 0 | decoder->PostHasTransparencyIfNeeded(transparency); |
719 | 0 |
|
720 | 0 | // We have the metadata we're looking for, so stop here, before we allocate |
721 | 0 | // buffers below. |
722 | 0 | return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS); |
723 | 0 | } |
724 | 0 | |
725 | 0 | #ifdef PNG_APNG_SUPPORTED |
726 | 0 | if (isAnimated) { |
727 | 0 | png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, |
728 | 0 | nullptr); |
729 | 0 | } |
730 | 0 |
|
731 | 0 | if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) { |
732 | 0 | decoder->mFrameIsHidden = true; |
733 | 0 | } else { |
734 | 0 | #endif |
735 | 0 | nsresult rv = decoder->CreateFrame(FrameInfo{ frameRect, |
736 | 0 | isInterlaced }); |
737 | 0 | if (NS_FAILED(rv)) { |
738 | 0 | png_error(decoder->mPNG, "CreateFrame failed"); |
739 | 0 | } |
740 | 0 | MOZ_ASSERT(decoder->mImageData, "Should have a buffer now"); |
741 | 0 | #ifdef PNG_APNG_SUPPORTED |
742 | 0 | } |
743 | 0 | #endif |
744 | 0 |
|
745 | 0 | if (decoder->mTransform && (channels <= 2 || isInterlaced)) { |
746 | 0 | uint32_t bpp[] = { 0, 3, 4, 3, 4 }; |
747 | 0 | decoder->mCMSLine = |
748 | 0 | static_cast<uint8_t*>(malloc(bpp[channels] * frameRect.Width())); |
749 | 0 | if (!decoder->mCMSLine) { |
750 | 0 | png_error(decoder->mPNG, "malloc of mCMSLine failed"); |
751 | 0 | } |
752 | 0 | } |
753 | 0 | |
754 | 0 | if (interlace_type == PNG_INTERLACE_ADAM7) { |
755 | 0 | if (frameRect.Height() < INT32_MAX / (frameRect.Width() * int32_t(channels))) { |
756 | 0 | const size_t bufferSize = channels * frameRect.Width() * frameRect.Height(); |
757 | 0 |
|
758 | 0 | if (bufferSize > SurfaceCache::MaximumCapacity()) { |
759 | 0 | png_error(decoder->mPNG, "Insufficient memory to deinterlace image"); |
760 | 0 | } |
761 | 0 | |
762 | 0 | decoder->interlacebuf = static_cast<uint8_t*>(malloc(bufferSize)); |
763 | 0 | } |
764 | 0 | if (!decoder->interlacebuf) { |
765 | 0 | png_error(decoder->mPNG, "malloc of interlacebuf failed"); |
766 | 0 | } |
767 | 0 | } |
768 | 0 | } |
769 | | |
770 | | void |
771 | | nsPNGDecoder::PostInvalidationIfNeeded() |
772 | 0 | { |
773 | 0 | Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect(); |
774 | 0 | if (!invalidRect) { |
775 | 0 | return; |
776 | 0 | } |
777 | 0 | |
778 | 0 | PostInvalidation(invalidRect->mInputSpaceRect, |
779 | 0 | Some(invalidRect->mOutputSpaceRect)); |
780 | 0 | } |
781 | | |
782 | | static NextPixel<uint32_t> |
783 | | PackRGBPixelAndAdvance(uint8_t*& aRawPixelInOut) |
784 | 0 | { |
785 | 0 | const uint32_t pixel = |
786 | 0 | gfxPackedPixel(0xFF, aRawPixelInOut[0], aRawPixelInOut[1], |
787 | 0 | aRawPixelInOut[2]); |
788 | 0 | aRawPixelInOut += 3; |
789 | 0 | return AsVariant(pixel); |
790 | 0 | } |
791 | | |
792 | | static NextPixel<uint32_t> |
793 | | PackRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut) |
794 | 0 | { |
795 | 0 | const uint32_t pixel = |
796 | 0 | gfxPackedPixel(aRawPixelInOut[3], aRawPixelInOut[0], |
797 | 0 | aRawPixelInOut[1], aRawPixelInOut[2]); |
798 | 0 | aRawPixelInOut += 4; |
799 | 0 | return AsVariant(pixel); |
800 | 0 | } |
801 | | |
802 | | static NextPixel<uint32_t> |
803 | | PackUnpremultipliedRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut) |
804 | 0 | { |
805 | 0 | const uint32_t pixel = |
806 | 0 | gfxPackedPixelNoPreMultiply(aRawPixelInOut[3], aRawPixelInOut[0], |
807 | 0 | aRawPixelInOut[1], aRawPixelInOut[2]); |
808 | 0 | aRawPixelInOut += 4; |
809 | 0 | return AsVariant(pixel); |
810 | 0 | } |
811 | | |
812 | | void |
813 | | nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, |
814 | | png_uint_32 row_num, int pass) |
815 | 0 | { |
816 | 0 | /* libpng comments: |
817 | 0 | * |
818 | 0 | * This function is called for every row in the image. If the |
819 | 0 | * image is interlacing, and you turned on the interlace handler, |
820 | 0 | * this function will be called for every row in every pass. |
821 | 0 | * Some of these rows will not be changed from the previous pass. |
822 | 0 | * When the row is not changed, the new_row variable will be |
823 | 0 | * nullptr. The rows and passes are called in order, so you don't |
824 | 0 | * really need the row_num and pass, but I'm supplying them |
825 | 0 | * because it may make your life easier. |
826 | 0 | * |
827 | 0 | * For the non-nullptr rows of interlaced images, you must call |
828 | 0 | * png_progressive_combine_row() passing in the row and the |
829 | 0 | * old row. You can call this function for nullptr rows (it will |
830 | 0 | * just return) and for non-interlaced images (it just does the |
831 | 0 | * memcpy for you) if it will make the code easier. Thus, you |
832 | 0 | * can just do this for all cases: |
833 | 0 | * |
834 | 0 | * png_progressive_combine_row(png_ptr, old_row, new_row); |
835 | 0 | * |
836 | 0 | * where old_row is what was displayed for previous rows. Note |
837 | 0 | * that the first pass (pass == 0 really) will completely cover |
838 | 0 | * the old row, so the rows do not have to be initialized. After |
839 | 0 | * the first pass (and only for interlaced images), you will have |
840 | 0 | * to pass the current row, and the function will combine the |
841 | 0 | * old row and the new row. |
842 | 0 | */ |
843 | 0 | nsPNGDecoder* decoder = |
844 | 0 | static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); |
845 | 0 |
|
846 | 0 | if (decoder->mFrameIsHidden) { |
847 | 0 | return; // Skip this frame. |
848 | 0 | } |
849 | 0 | |
850 | 0 | MOZ_ASSERT_IF(decoder->IsFirstFrameDecode(), decoder->mNumFrames == 0); |
851 | 0 |
|
852 | 0 | while (pass > decoder->mPass) { |
853 | 0 | // Advance to the next pass. We may have to do this multiple times because |
854 | 0 | // libpng will skip passes if the image is so small that no pixels have |
855 | 0 | // changed on a given pass, but ADAM7InterpolatingFilter needs to be reset |
856 | 0 | // once for every pass to perform interpolation properly. |
857 | 0 | decoder->mPipe.ResetToFirstRow(); |
858 | 0 | decoder->mPass++; |
859 | 0 | } |
860 | 0 |
|
861 | 0 | const png_uint_32 height = |
862 | 0 | static_cast<png_uint_32>(decoder->mFrameRect.Height()); |
863 | 0 |
|
864 | 0 | if (row_num >= height) { |
865 | 0 | // Bail if we receive extra rows. This is especially important because if we |
866 | 0 | // didn't, we might overflow the deinterlacing buffer. |
867 | 0 | MOZ_ASSERT_UNREACHABLE("libpng producing extra rows?"); |
868 | 0 | return; |
869 | 0 | } |
870 | 0 |
|
871 | 0 | // Note that |new_row| may be null here, indicating that this is an interlaced |
872 | 0 | // image and |row_callback| is being called for a row that hasn't changed. |
873 | 0 | MOZ_ASSERT_IF(!new_row, decoder->interlacebuf); |
874 | 0 | uint8_t* rowToWrite = new_row; |
875 | 0 |
|
876 | 0 | if (decoder->interlacebuf) { |
877 | 0 | uint32_t width = uint32_t(decoder->mFrameRect.Width()); |
878 | 0 |
|
879 | 0 | // We'll output the deinterlaced version of the row. |
880 | 0 | rowToWrite = decoder->interlacebuf + (row_num * decoder->mChannels * width); |
881 | 0 |
|
882 | 0 | // Update the deinterlaced version of this row with the new data. |
883 | 0 | png_progressive_combine_row(png_ptr, rowToWrite, new_row); |
884 | 0 | } |
885 | 0 |
|
886 | 0 | decoder->WriteRow(rowToWrite); |
887 | 0 | } |
888 | | |
889 | | void |
890 | | nsPNGDecoder::WriteRow(uint8_t* aRow) |
891 | 0 | { |
892 | 0 | MOZ_ASSERT(aRow); |
893 | 0 |
|
894 | 0 | uint8_t* rowToWrite = aRow; |
895 | 0 | uint32_t width = uint32_t(mFrameRect.Width()); |
896 | 0 |
|
897 | 0 | // Apply color management to the row, if necessary, before writing it out. |
898 | 0 | if (mTransform) { |
899 | 0 | if (mCMSLine) { |
900 | 0 | qcms_transform_data(mTransform, rowToWrite, mCMSLine, width); |
901 | 0 |
|
902 | 0 | // Copy alpha over. |
903 | 0 | if (HasAlphaChannel()) { |
904 | 0 | for (uint32_t i = 0; i < width; ++i) { |
905 | 0 | mCMSLine[4 * i + 3] = rowToWrite[mChannels * i + mChannels - 1]; |
906 | 0 | } |
907 | 0 | } |
908 | 0 |
|
909 | 0 | rowToWrite = mCMSLine; |
910 | 0 | } else { |
911 | 0 | qcms_transform_data(mTransform, rowToWrite, rowToWrite, width); |
912 | 0 | } |
913 | 0 | } |
914 | 0 |
|
915 | 0 | // Write this row to the SurfacePipe. |
916 | 0 | DebugOnly<WriteState> result; |
917 | 0 | if (HasAlphaChannel()) { |
918 | 0 | if (mDisablePremultipliedAlpha) { |
919 | 0 | result = mPipe.WritePixelsToRow<uint32_t>([&]{ |
920 | 0 | return PackUnpremultipliedRGBAPixelAndAdvance(rowToWrite); |
921 | 0 | }); |
922 | 0 | } else { |
923 | 0 | result = mPipe.WritePixelsToRow<uint32_t>([&]{ |
924 | 0 | return PackRGBAPixelAndAdvance(rowToWrite); |
925 | 0 | }); |
926 | 0 | } |
927 | 0 | } else { |
928 | 0 | result = mPipe.WritePixelsToRow<uint32_t>([&]{ |
929 | 0 | return PackRGBPixelAndAdvance(rowToWrite); |
930 | 0 | }); |
931 | 0 | } |
932 | 0 |
|
933 | 0 | MOZ_ASSERT(WriteState(result) != WriteState::FAILURE); |
934 | 0 |
|
935 | 0 | PostInvalidationIfNeeded(); |
936 | 0 | } |
937 | | |
938 | | void |
939 | | nsPNGDecoder::DoTerminate(png_structp aPNGStruct, TerminalState aState) |
940 | 0 | { |
941 | 0 | // Stop processing data. Note that we intentionally ignore the return value of |
942 | 0 | // png_process_data_pause(), which tells us how many bytes of the data that |
943 | 0 | // was passed to png_process_data() have not been consumed yet, because now |
944 | 0 | // that we've reached a terminal state, we won't do any more decoding or call |
945 | 0 | // back into libpng anymore. |
946 | 0 | png_process_data_pause(aPNGStruct, /* save = */ false); |
947 | 0 |
|
948 | 0 | mNextTransition = aState == TerminalState::SUCCESS |
949 | 0 | ? Transition::TerminateSuccess() |
950 | 0 | : Transition::TerminateFailure(); |
951 | 0 | } |
952 | | |
953 | | void |
954 | | nsPNGDecoder::DoYield(png_structp aPNGStruct) |
955 | 0 | { |
956 | 0 | // Pause data processing. png_process_data_pause() returns how many bytes of |
957 | 0 | // the data that was passed to png_process_data() have not been consumed yet. |
958 | 0 | // We use this information to tell StreamingLexer where to place us in the |
959 | 0 | // input stream when we come back from the yield. |
960 | 0 | png_size_t pendingBytes = png_process_data_pause(aPNGStruct, |
961 | 0 | /* save = */ false); |
962 | 0 |
|
963 | 0 | MOZ_ASSERT(pendingBytes < mLastChunkLength); |
964 | 0 | size_t consumedBytes = mLastChunkLength - min(pendingBytes, mLastChunkLength); |
965 | 0 |
|
966 | 0 | mNextTransition = |
967 | 0 | Transition::ContinueUnbufferedAfterYield(State::PNG_DATA, consumedBytes); |
968 | 0 | } |
969 | | |
970 | | nsresult |
971 | | nsPNGDecoder::FinishInternal() |
972 | 0 | { |
973 | 0 | // We shouldn't be called in error cases. |
974 | 0 | MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!"); |
975 | 0 |
|
976 | 0 | if (IsMetadataDecode()) { |
977 | 0 | return NS_OK; |
978 | 0 | } |
979 | 0 | |
980 | 0 | int32_t loop_count = 0; |
981 | 0 | #ifdef PNG_APNG_SUPPORTED |
982 | 0 | if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) { |
983 | 0 | int32_t num_plays = png_get_num_plays(mPNG, mInfo); |
984 | 0 | loop_count = num_plays - 1; |
985 | 0 | } |
986 | 0 | #endif |
987 | 0 |
|
988 | 0 | if (InFrame()) { |
989 | 0 | EndImageFrame(); |
990 | 0 | } |
991 | 0 | PostDecodeDone(loop_count); |
992 | 0 |
|
993 | 0 | return NS_OK; |
994 | 0 | } |
995 | | |
996 | | |
997 | | #ifdef PNG_APNG_SUPPORTED |
998 | | // got the header of a new frame that's coming |
999 | | void |
1000 | | nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num) |
1001 | 0 | { |
1002 | 0 | nsPNGDecoder* decoder = |
1003 | 0 | static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); |
1004 | 0 |
|
1005 | 0 | // old frame is done |
1006 | 0 | decoder->EndImageFrame(); |
1007 | 0 |
|
1008 | 0 | const bool previousFrameWasHidden = decoder->mFrameIsHidden; |
1009 | 0 |
|
1010 | 0 | if (!previousFrameWasHidden && decoder->IsFirstFrameDecode()) { |
1011 | 0 | // We're about to get a second non-hidden frame, but we only want the first. |
1012 | 0 | // Stop decoding now. (And avoid allocating the unnecessary buffers below.) |
1013 | 0 | return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS); |
1014 | 0 | } |
1015 | 0 | |
1016 | 0 | // Only the first frame can be hidden, so unhide unconditionally here. |
1017 | 0 | decoder->mFrameIsHidden = false; |
1018 | 0 |
|
1019 | 0 | // Save the information necessary to create the frame; we'll actually create |
1020 | 0 | // it when we return from the yield. |
1021 | 0 | const IntRect frameRect(png_get_next_frame_x_offset(png_ptr, decoder->mInfo), |
1022 | 0 | png_get_next_frame_y_offset(png_ptr, decoder->mInfo), |
1023 | 0 | png_get_next_frame_width(png_ptr, decoder->mInfo), |
1024 | 0 | png_get_next_frame_height(png_ptr, decoder->mInfo)); |
1025 | 0 | const bool isInterlaced = bool(decoder->interlacebuf); |
1026 | 0 |
|
1027 | | #ifndef MOZ_EMBEDDED_LIBPNG |
1028 | | // if using system library, check frame_width and height against 0 |
1029 | | if (frameRect.width == 0) { |
1030 | | png_error(png_ptr, "Frame width must not be 0"); |
1031 | | } |
1032 | | if (frameRect.height == 0) { |
1033 | | png_error(png_ptr, "Frame height must not be 0"); |
1034 | | } |
1035 | | #endif |
1036 | |
|
1037 | 0 | const FrameInfo info { frameRect, isInterlaced }; |
1038 | 0 |
|
1039 | 0 | // If the previous frame was hidden, skip the yield (which will mislead the |
1040 | 0 | // caller, who will think the previous frame was real) and just allocate the |
1041 | 0 | // new frame here. |
1042 | 0 | if (previousFrameWasHidden) { |
1043 | 0 | if (NS_FAILED(decoder->CreateFrame(info))) { |
1044 | 0 | return decoder->DoTerminate(png_ptr, TerminalState::FAILURE); |
1045 | 0 | } |
1046 | 0 | |
1047 | 0 | MOZ_ASSERT(decoder->mImageData, "Should have a buffer now"); |
1048 | 0 | return; // No yield, so we'll just keep decoding. |
1049 | 0 | } |
1050 | 0 | |
1051 | 0 | // Yield to the caller to notify them that the previous frame is now complete. |
1052 | 0 | decoder->mNextFrameInfo = Some(info); |
1053 | 0 | return decoder->DoYield(png_ptr); |
1054 | 0 | } |
1055 | | #endif |
1056 | | |
1057 | | void |
1058 | | nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr) |
1059 | 0 | { |
1060 | 0 | /* libpng comments: |
1061 | 0 | * |
1062 | 0 | * this function is called when the whole image has been read, |
1063 | 0 | * including any chunks after the image (up to and including |
1064 | 0 | * the IEND). You will usually have the same info chunk as you |
1065 | 0 | * had in the header, although some data may have been added |
1066 | 0 | * to the comments and time fields. |
1067 | 0 | * |
1068 | 0 | * Most people won't do much here, perhaps setting a flag that |
1069 | 0 | * marks the image as finished. |
1070 | 0 | */ |
1071 | 0 |
|
1072 | 0 | nsPNGDecoder* decoder = |
1073 | 0 | static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); |
1074 | 0 |
|
1075 | 0 | // We shouldn't get here if we've hit an error |
1076 | 0 | MOZ_ASSERT(!decoder->HasError(), "Finishing up PNG but hit error!"); |
1077 | 0 |
|
1078 | 0 | return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS); |
1079 | 0 | } |
1080 | | |
1081 | | |
1082 | | void |
1083 | | nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg) |
1084 | 0 | { |
1085 | 0 | MOZ_LOG(sPNGLog, LogLevel::Error, ("libpng error: %s\n", error_msg)); |
1086 | 0 | png_longjmp(png_ptr, 1); |
1087 | 0 | } |
1088 | | |
1089 | | |
1090 | | void |
1091 | | nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg) |
1092 | 0 | { |
1093 | 0 | MOZ_LOG(sPNGLog, LogLevel::Warning, ("libpng warning: %s\n", warning_msg)); |
1094 | 0 | } |
1095 | | |
1096 | | Maybe<Telemetry::HistogramID> |
1097 | | nsPNGDecoder::SpeedHistogram() const |
1098 | 0 | { |
1099 | 0 | return Some(Telemetry::IMAGE_DECODE_SPEED_PNG); |
1100 | 0 | } |
1101 | | |
1102 | | bool |
1103 | | nsPNGDecoder::IsValidICOResource() const |
1104 | 0 | { |
1105 | 0 | // Only 32-bit RGBA PNGs are valid ICO resources; see here: |
1106 | 0 | // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx |
1107 | 0 |
|
1108 | 0 | // If there are errors in the call to png_get_IHDR, the error_callback in |
1109 | 0 | // nsPNGDecoder.cpp is called. In this error callback we do a longjmp, so |
1110 | 0 | // we need to save the jump buffer here. Otherwise we'll end up without a |
1111 | 0 | // proper callstack. |
1112 | 0 | if (setjmp(png_jmpbuf(mPNG))) { |
1113 | 0 | // We got here from a longjmp call indirectly from png_get_IHDR |
1114 | 0 | return false; |
1115 | 0 | } |
1116 | 0 | |
1117 | 0 | png_uint_32 |
1118 | 0 | png_width, // Unused |
1119 | 0 | png_height; // Unused |
1120 | 0 |
|
1121 | 0 | int png_bit_depth, |
1122 | 0 | png_color_type; |
1123 | 0 |
|
1124 | 0 | if (png_get_IHDR(mPNG, mInfo, &png_width, &png_height, &png_bit_depth, |
1125 | 0 | &png_color_type, nullptr, nullptr, nullptr)) { |
1126 | 0 |
|
1127 | 0 | return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA || |
1128 | 0 | png_color_type == PNG_COLOR_TYPE_RGB) && |
1129 | 0 | png_bit_depth == 8); |
1130 | 0 | } else { |
1131 | 0 | return false; |
1132 | 0 | } |
1133 | 0 | } |
1134 | | |
1135 | | } // namespace image |
1136 | | } // namespace mozilla |