1 | /* |
---|---|
2 | * Copyright 2015 Google Inc. |
3 | * |
4 | * Use of this source code is governed by a BSD-style license that can be |
5 | * found in the LICENSE file. |
6 | */ |
7 | |
8 | #include "src/codec/SkPngCodec.h" |
9 | |
10 | #include "include/codec/SkPngChunkReader.h" |
11 | #include "include/codec/SkPngDecoder.h" |
12 | #include "include/core/SkAlphaType.h" |
13 | #include "include/core/SkColor.h" |
14 | #include "include/core/SkColorType.h" |
15 | #include "include/core/SkData.h" |
16 | #include "include/core/SkImageInfo.h" |
17 | #include "include/core/SkRect.h" |
18 | #include "include/core/SkSize.h" |
19 | #include "include/core/SkStream.h" |
20 | #include "include/core/SkTypes.h" |
21 | #include "include/private/SkEncodedInfo.h" |
22 | #include "include/private/base/SkNoncopyable.h" |
23 | #include "include/private/base/SkTemplates.h" |
24 | #include "modules/skcms/skcms.h" |
25 | #include "src/codec/SkCodecPriv.h" |
26 | #include "src/codec/SkColorPalette.h" |
27 | #include "src/codec/SkPngPriv.h" |
28 | #include "src/codec/SkSwizzler.h" |
29 | #include "src/core/SkOpts.h" |
30 | |
31 | #include <csetjmp> |
32 | #include <algorithm> |
33 | #include <cstring> |
34 | #include <utility> |
35 | |
36 | #include <png.h> |
37 | #include <pngconf.h> |
38 | |
39 | using namespace skia_private; |
40 | |
41 | class SkSampler; |
42 | |
43 | #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK |
44 | #include "include/android/SkAndroidFrameworkUtils.h" |
45 | #endif |
46 | |
47 | // This warning triggers false positives way too often in here. |
48 | #if defined(__GNUC__) && !defined(__clang__) |
49 | #pragma GCC diagnostic ignored "-Wclobbered" |
50 | #endif |
51 | |
52 | // FIXME (scroggo): We can use png_jumpbuf directly once Google3 is on 1.6 |
53 | #define PNG_JMPBUF(x) png_jmpbuf((png_structp) x) |
54 | |
55 | /////////////////////////////////////////////////////////////////////////////// |
56 | // Callback functions |
57 | /////////////////////////////////////////////////////////////////////////////// |
58 | |
59 | // When setjmp is first called, it returns 0, meaning longjmp was not called. |
60 | constexpr int kSetJmpOkay = 0; |
61 | // An error internal to libpng. |
62 | constexpr int kPngError = 1; |
63 | // Passed to longjmp when we have decoded as many lines as we need. |
64 | constexpr int kStopDecoding = 2; |
65 | |
66 | static void sk_error_fn(png_structp png_ptr, png_const_charp msg) { |
67 | SkCodecPrintf("------ png error %s\n", msg); |
68 | longjmp(PNG_JMPBUF(png_ptr), val: kPngError); |
69 | } |
70 | |
71 | void sk_warning_fn(png_structp, png_const_charp msg) { |
72 | SkCodecPrintf("----- png warning %s\n", msg); |
73 | } |
74 | |
75 | #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED |
76 | static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) { |
77 | SkPngChunkReader* chunkReader = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr); |
78 | // readChunk() returning true means continue decoding |
79 | return chunkReader->readChunk(tag: (const char*)chunk->name, data: chunk->data, length: chunk->size) ? 1 : -1; |
80 | } |
81 | #endif |
82 | |
83 | /////////////////////////////////////////////////////////////////////////////// |
84 | // Helpers |
85 | /////////////////////////////////////////////////////////////////////////////// |
86 | |
87 | class AutoCleanPng : public SkNoncopyable { |
88 | public: |
89 | /* |
90 | * This class does not take ownership of stream or reader, but if codecPtr |
91 | * is non-NULL, and decodeBounds succeeds, it will have created a new |
92 | * SkCodec (pointed to by *codecPtr) which will own/ref them, as well as |
93 | * the png_ptr and info_ptr. |
94 | */ |
95 | AutoCleanPng(png_structp png_ptr, SkStream* stream, SkPngChunkReader* reader, |
96 | SkCodec** codecPtr) |
97 | : fPng_ptr(png_ptr) |
98 | , fInfo_ptr(nullptr) |
99 | , fStream(stream) |
100 | , fChunkReader(reader) |
101 | , fOutCodec(codecPtr) |
102 | {} |
103 | |
104 | ~AutoCleanPng() { |
105 | // fInfo_ptr will never be non-nullptr unless fPng_ptr is. |
106 | if (fPng_ptr) { |
107 | png_infopp info_pp = fInfo_ptr ? &fInfo_ptr : nullptr; |
108 | png_destroy_read_struct(png_ptr_ptr: &fPng_ptr, info_ptr_ptr: info_pp, end_info_ptr_ptr: nullptr); |
109 | } |
110 | } |
111 | |
112 | void setInfoPtr(png_infop info_ptr) { |
113 | SkASSERT(nullptr == fInfo_ptr); |
114 | fInfo_ptr = info_ptr; |
115 | } |
116 | |
117 | /** |
118 | * Reads enough of the input stream to decode the bounds. |
119 | * @return false if the stream is not a valid PNG (or too short). |
120 | * true if it read enough of the stream to determine the bounds. |
121 | * In the latter case, the stream may have been read beyond the |
122 | * point to determine the bounds, and the png_ptr will have saved |
123 | * any extra data. Further, if the codecPtr supplied to the |
124 | * constructor was not NULL, it will now point to a new SkCodec, |
125 | * which owns (or refs, in the case of the SkPngChunkReader) the |
126 | * inputs. If codecPtr was NULL, the png_ptr and info_ptr are |
127 | * unowned, and it is up to the caller to destroy them. |
128 | */ |
129 | bool decodeBounds(); |
130 | |
131 | private: |
132 | png_structp fPng_ptr; |
133 | png_infop fInfo_ptr; |
134 | SkStream* fStream; |
135 | SkPngChunkReader* fChunkReader; |
136 | SkCodec** fOutCodec; |
137 | |
138 | void infoCallback(size_t idatLength); |
139 | |
140 | void releasePngPtrs() { |
141 | fPng_ptr = nullptr; |
142 | fInfo_ptr = nullptr; |
143 | } |
144 | }; |
145 | |
146 | static inline bool is_chunk(const png_byte* chunk, const char* tag) { |
147 | return memcmp(s1: chunk + 4, s2: tag, n: 4) == 0; |
148 | } |
149 | |
150 | static inline bool process_data(png_structp png_ptr, png_infop info_ptr, |
151 | SkStream* stream, void* buffer, size_t bufferSize, size_t length) { |
152 | while (length > 0) { |
153 | const size_t bytesToProcess = std::min(a: bufferSize, b: length); |
154 | const size_t bytesRead = stream->read(buffer, size: bytesToProcess); |
155 | png_process_data(png_ptr, info_ptr, buffer: (png_bytep) buffer, buffer_size: bytesRead); |
156 | if (bytesRead < bytesToProcess) { |
157 | return false; |
158 | } |
159 | length -= bytesToProcess; |
160 | } |
161 | return true; |
162 | } |
163 | |
164 | bool AutoCleanPng::decodeBounds() { |
165 | SkASSERT(fStream); |
166 | if (setjmp(PNG_JMPBUF(fPng_ptr))) { |
167 | return false; |
168 | } |
169 | |
170 | png_set_progressive_read_fn(png_ptr: fPng_ptr, progressive_ptr: nullptr, info_fn: nullptr, row_fn: nullptr, end_fn: nullptr); |
171 | |
172 | // Arbitrary buffer size, though note that it matches (below) |
173 | // SkPngCodec::processData(). FIXME: Can we better suit this to the size of |
174 | // the PNG header? |
175 | constexpr size_t kBufferSize = 4096; |
176 | char buffer[kBufferSize]; |
177 | |
178 | { |
179 | // Parse the signature. |
180 | if (fStream->read(buffer, size: 8) < 8) { |
181 | return false; |
182 | } |
183 | |
184 | png_process_data(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, buffer: (png_bytep) buffer, buffer_size: 8); |
185 | } |
186 | |
187 | while (true) { |
188 | // Parse chunk length and type. |
189 | if (fStream->read(buffer, size: 8) < 8) { |
190 | // We have read to the end of the input without decoding bounds. |
191 | break; |
192 | } |
193 | |
194 | png_byte* chunk = reinterpret_cast<png_byte*>(buffer); |
195 | const size_t length = png_get_uint_32(buf: chunk); |
196 | |
197 | if (is_chunk(chunk, tag: "IDAT")) { |
198 | this->infoCallback(idatLength: length); |
199 | return true; |
200 | } |
201 | |
202 | png_process_data(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, buffer: chunk, buffer_size: 8); |
203 | // Process the full chunk + CRC. |
204 | if (!process_data(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, stream: fStream, buffer, bufferSize: kBufferSize, length: length + 4)) { |
205 | return false; |
206 | } |
207 | } |
208 | |
209 | return false; |
210 | } |
211 | |
212 | bool SkPngCodec::processData() { |
213 | switch (setjmp(PNG_JMPBUF(fPng_ptr))) { |
214 | case kPngError: |
215 | // There was an error. Stop processing data. |
216 | // FIXME: Do we need to discard png_ptr? |
217 | return false; |
218 | case kStopDecoding: |
219 | // We decoded all the lines we want. |
220 | return true; |
221 | case kSetJmpOkay: |
222 | // Everything is okay. |
223 | break; |
224 | default: |
225 | // No other values should be passed to longjmp. |
226 | SkASSERT(false); |
227 | } |
228 | |
229 | // Arbitrary buffer size |
230 | constexpr size_t kBufferSize = 4096; |
231 | char buffer[kBufferSize]; |
232 | |
233 | bool iend = false; |
234 | while (true) { |
235 | size_t length; |
236 | if (fDecodedIdat) { |
237 | // Parse chunk length and type. |
238 | if (this->stream()->read(buffer, size: 8) < 8) { |
239 | break; |
240 | } |
241 | |
242 | png_byte* chunk = reinterpret_cast<png_byte*>(buffer); |
243 | png_process_data(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, buffer: chunk, buffer_size: 8); |
244 | if (is_chunk(chunk, tag: "IEND")) { |
245 | iend = true; |
246 | } |
247 | |
248 | length = png_get_uint_32(buf: chunk); |
249 | } else { |
250 | length = fIdatLength; |
251 | png_byte idat[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'}; |
252 | png_save_uint_32(buf: idat, i: length); |
253 | png_process_data(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, buffer: idat, buffer_size: 8); |
254 | fDecodedIdat = true; |
255 | } |
256 | |
257 | // Process the full chunk + CRC. |
258 | if (!process_data(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, stream: this->stream(), buffer, bufferSize: kBufferSize, length: length + 4) |
259 | || iend) { |
260 | break; |
261 | } |
262 | } |
263 | |
264 | return true; |
265 | } |
266 | |
267 | static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType; |
268 | |
269 | static inline bool needs_premul(SkAlphaType dstAT, SkEncodedInfo::Alpha encodedAlpha) { |
270 | return kPremul_SkAlphaType == dstAT && SkEncodedInfo::kUnpremul_Alpha == encodedAlpha; |
271 | } |
272 | |
273 | // Note: SkColorPalette claims to store SkPMColors, which is not necessarily the case here. |
274 | bool SkPngCodec::createColorTable(const SkImageInfo& dstInfo) { |
275 | |
276 | int numColors; |
277 | png_color* palette; |
278 | if (!png_get_PLTE(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, palette: &palette, num_palette: &numColors)) { |
279 | return false; |
280 | } |
281 | |
282 | // Contents depend on tableColorType and our choice of if/when to premultiply: |
283 | // { kPremul, kUnpremul, kOpaque } x { RGBA, BGRA } |
284 | SkPMColor colorTable[256]; |
285 | SkColorType tableColorType = this->colorXform() ? kXformSrcColorType : dstInfo.colorType(); |
286 | |
287 | png_bytep alphas; |
288 | int numColorsWithAlpha = 0; |
289 | if (png_get_tRNS(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, trans_alpha: &alphas, num_trans: &numColorsWithAlpha, trans_color: nullptr)) { |
290 | bool premultiply = needs_premul(dstAT: dstInfo.alphaType(), encodedAlpha: this->getEncodedInfo().alpha()); |
291 | |
292 | // Choose which function to use to create the color table. If the final destination's |
293 | // colortype is unpremultiplied, the color table will store unpremultiplied colors. |
294 | PackColorProc proc = choose_pack_color_proc(isPremul: premultiply, colorType: tableColorType); |
295 | |
296 | for (int i = 0; i < numColorsWithAlpha; i++) { |
297 | // We don't have a function in SkOpts that combines a set of alphas with a set |
298 | // of RGBs. We could write one, but it's hardly worth it, given that this |
299 | // is such a small fraction of the total decode time. |
300 | colorTable[i] = proc(alphas[i], palette->red, palette->green, palette->blue); |
301 | palette++; |
302 | } |
303 | } |
304 | |
305 | if (numColorsWithAlpha < numColors) { |
306 | // The optimized code depends on a 3-byte png_color struct with the colors |
307 | // in RGB order. These checks make sure it is safe to use. |
308 | static_assert(3 == sizeof(png_color), "png_color struct has changed. Opts are broken."); |
309 | #ifdef SK_DEBUG |
310 | SkASSERT(&palette->red < &palette->green); |
311 | SkASSERT(&palette->green < &palette->blue); |
312 | #endif |
313 | |
314 | if (is_rgba(colorType: tableColorType)) { |
315 | SkOpts::RGB_to_RGB1(colorTable + numColorsWithAlpha, (const uint8_t*)palette, |
316 | numColors - numColorsWithAlpha); |
317 | } else { |
318 | SkOpts::RGB_to_BGR1(colorTable + numColorsWithAlpha, (const uint8_t*)palette, |
319 | numColors - numColorsWithAlpha); |
320 | } |
321 | } |
322 | |
323 | if (this->colorXform() && !this->xformOnDecode()) { |
324 | this->applyColorXform(dst: colorTable, src: colorTable, count: numColors); |
325 | } |
326 | |
327 | // Pad the color table with the last color in the table (or black) in the case that |
328 | // invalid pixel indices exceed the number of colors in the table. |
329 | const int maxColors = 1 << fBitDepth; |
330 | if (numColors < maxColors) { |
331 | SkPMColor lastColor = numColors > 0 ? colorTable[numColors - 1] : SK_ColorBLACK; |
332 | SkOpts::memset32(colorTable + numColors, lastColor, maxColors - numColors); |
333 | } |
334 | |
335 | fColorTable.reset(ptr: new SkColorPalette(colorTable, maxColors)); |
336 | return true; |
337 | } |
338 | |
339 | /////////////////////////////////////////////////////////////////////////////// |
340 | // Creation |
341 | /////////////////////////////////////////////////////////////////////////////// |
342 | |
343 | bool SkPngCodec::IsPng(const void* buf, size_t bytesRead) { |
344 | return !png_sig_cmp(sig: (png_bytep) buf, start: (png_size_t)0, num_to_check: bytesRead); |
345 | } |
346 | |
347 | #if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) |
348 | |
349 | static float png_fixed_point_to_float(png_fixed_point x) { |
350 | // We multiply by the same factor that libpng used to convert |
351 | // fixed point -> double. Since we want floats, we choose to |
352 | // do the conversion ourselves rather than convert |
353 | // fixed point -> double -> float. |
354 | return ((float) x) * 0.00001f; |
355 | } |
356 | |
357 | static float png_inverted_fixed_point_to_float(png_fixed_point x) { |
358 | // This is necessary because the gAMA chunk actually stores 1/gamma. |
359 | return 1.0f / png_fixed_point_to_float(x); |
360 | } |
361 | |
362 | #endif // LIBPNG >= 1.6 |
363 | |
364 | // If there is no color profile information, it will use sRGB. |
365 | std::unique_ptr<SkEncodedInfo::ICCProfile> read_color_profile(png_structp png_ptr, |
366 | png_infop info_ptr) { |
367 | |
368 | #if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) |
369 | // First check for an ICC profile |
370 | png_bytep profile; |
371 | png_uint_32 length; |
372 | // The below variables are unused, however, we need to pass them in anyway or |
373 | // png_get_iCCP() will return nothing. |
374 | // Could knowing the |name| of the profile ever be interesting? Maybe for debugging? |
375 | png_charp name; |
376 | // The |compression| is uninteresting since: |
377 | // (1) libpng has already decompressed the profile for us. |
378 | // (2) "deflate" is the only mode of decompression that libpng supports. |
379 | int compression; |
380 | if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, name: &name, compression_type: &compression, profile: &profile, |
381 | proflen: &length)) { |
382 | auto data = SkData::MakeWithCopy(data: profile, length); |
383 | return SkEncodedInfo::ICCProfile::Make(std::move(data)); |
384 | } |
385 | |
386 | // Second, check for sRGB. |
387 | // Note that Blink does this first. This code checks ICC first, with the thinking that |
388 | // an image has both truly wants the potentially more specific ICC chunk, with sRGB as a |
389 | // backup in case the decoder does not support full color management. |
390 | if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { |
391 | // sRGB chunks also store a rendering intent: Absolute, Relative, |
392 | // Perceptual, and Saturation. |
393 | // FIXME (scroggo): Extract this information from the sRGB chunk once |
394 | // we are able to handle this information in |
395 | // skcms_ICCProfile |
396 | return nullptr; |
397 | } |
398 | |
399 | // Default to SRGB gamut. |
400 | skcms_Matrix3x3 toXYZD50 = skcms_sRGB_profile()->toXYZD50; |
401 | // Next, check for chromaticities. |
402 | png_fixed_point chrm[8]; |
403 | png_fixed_point gamma; |
404 | if (png_get_cHRM_fixed(png_ptr, info_ptr, int_white_x: &chrm[0], int_white_y: &chrm[1], int_red_x: &chrm[2], int_red_y: &chrm[3], int_green_x: &chrm[4], |
405 | int_green_y: &chrm[5], int_blue_x: &chrm[6], int_blue_y: &chrm[7])) |
406 | { |
407 | float rx = png_fixed_point_to_float(x: chrm[2]); |
408 | float ry = png_fixed_point_to_float(x: chrm[3]); |
409 | float gx = png_fixed_point_to_float(x: chrm[4]); |
410 | float gy = png_fixed_point_to_float(x: chrm[5]); |
411 | float bx = png_fixed_point_to_float(x: chrm[6]); |
412 | float by = png_fixed_point_to_float(x: chrm[7]); |
413 | float wx = png_fixed_point_to_float(x: chrm[0]); |
414 | float wy = png_fixed_point_to_float(x: chrm[1]); |
415 | |
416 | skcms_Matrix3x3 tmp; |
417 | if (skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, toXYZD50: &tmp)) { |
418 | toXYZD50 = tmp; |
419 | } else { |
420 | // Note that Blink simply returns nullptr in this case. We'll fall |
421 | // back to srgb. |
422 | } |
423 | } |
424 | |
425 | skcms_TransferFunction fn; |
426 | if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, int_file_gamma: &gamma)) { |
427 | fn.a = 1.0f; |
428 | fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f; |
429 | fn.g = png_inverted_fixed_point_to_float(x: gamma); |
430 | } else { |
431 | // Default to sRGB gamma if the image has color space information, |
432 | // but does not specify gamma. |
433 | // Note that Blink would again return nullptr in this case. |
434 | fn = *skcms_sRGB_TransferFunction(); |
435 | } |
436 | |
437 | skcms_ICCProfile skcmsProfile; |
438 | skcms_Init(p: &skcmsProfile); |
439 | skcms_SetTransferFunction(p: &skcmsProfile, tf: &fn); |
440 | skcms_SetXYZD50(p: &skcmsProfile, m: &toXYZD50); |
441 | |
442 | return SkEncodedInfo::ICCProfile::Make(skcmsProfile); |
443 | #else // LIBPNG >= 1.6 |
444 | return nullptr; |
445 | #endif // LIBPNG >= 1.6 |
446 | } |
447 | |
448 | void SkPngCodec::allocateStorage(const SkImageInfo& dstInfo) { |
449 | switch (fXformMode) { |
450 | case kSwizzleOnly_XformMode: |
451 | break; |
452 | case kColorOnly_XformMode: |
453 | // Intentional fall through. A swizzler hasn't been created yet, but one will |
454 | // be created later if we are sampling. We'll go ahead and allocate |
455 | // enough memory to swizzle if necessary. |
456 | case kSwizzleColor_XformMode: { |
457 | const int bitsPerPixel = this->getEncodedInfo().bitsPerPixel(); |
458 | |
459 | // If we have more than 8-bits (per component) of precision, we will keep that |
460 | // extra precision. Otherwise, we will swizzle to RGBA_8888 before transforming. |
461 | const size_t bytesPerPixel = (bitsPerPixel > 32) ? bitsPerPixel / 8 : 4; |
462 | const size_t colorXformBytes = dstInfo.width() * bytesPerPixel; |
463 | fStorage.reset(count: colorXformBytes); |
464 | fColorXformSrcRow = fStorage.get(); |
465 | break; |
466 | } |
467 | } |
468 | } |
469 | |
470 | static skcms_PixelFormat png_select_xform_format(const SkEncodedInfo& info) { |
471 | // We use kRGB and kRGBA formats because color PNGs are always RGB or RGBA. |
472 | if (16 == info.bitsPerComponent()) { |
473 | if (SkEncodedInfo::kRGBA_Color == info.color()) { |
474 | return skcms_PixelFormat_RGBA_16161616BE; |
475 | } else if (SkEncodedInfo::kRGB_Color == info.color()) { |
476 | return skcms_PixelFormat_RGB_161616BE; |
477 | } |
478 | } else if (SkEncodedInfo::kGray_Color == info.color()) { |
479 | return skcms_PixelFormat_G_8; |
480 | } |
481 | |
482 | return skcms_PixelFormat_RGBA_8888; |
483 | } |
484 | |
485 | void SkPngCodec::applyXformRow(void* dst, const void* src) { |
486 | switch (fXformMode) { |
487 | case kSwizzleOnly_XformMode: |
488 | fSwizzler->swizzle(dst, src: (const uint8_t*) src); |
489 | break; |
490 | case kColorOnly_XformMode: |
491 | this->applyColorXform(dst, src, count: fXformWidth); |
492 | break; |
493 | case kSwizzleColor_XformMode: |
494 | fSwizzler->swizzle(dst: fColorXformSrcRow, src: (const uint8_t*) src); |
495 | this->applyColorXform(dst, src: fColorXformSrcRow, count: fXformWidth); |
496 | break; |
497 | } |
498 | } |
499 | |
500 | static SkCodec::Result log_and_return_error(bool success) { |
501 | if (success) return SkCodec::kIncompleteInput; |
502 | #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK |
503 | SkAndroidFrameworkUtils::SafetyNetLog("117838472"); |
504 | #endif |
505 | return SkCodec::kErrorInInput; |
506 | } |
507 | |
508 | class SkPngNormalDecoder : public SkPngCodec { |
509 | public: |
510 | SkPngNormalDecoder(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, |
511 | SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth) |
512 | : INHERITED(std::move(info), std::move(stream), reader, png_ptr, info_ptr, bitDepth) |
513 | , fRowsWrittenToOutput(0) |
514 | , fDst(nullptr) |
515 | , fRowBytes(0) |
516 | , fFirstRow(0) |
517 | , fLastRow(0) |
518 | {} |
519 | |
520 | static void AllRowsCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { |
521 | GetDecoder(png_ptr)->allRowsCallback(row, rowNum); |
522 | } |
523 | |
524 | static void RowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { |
525 | GetDecoder(png_ptr)->rowCallback(row, rowNum); |
526 | } |
527 | |
528 | private: |
529 | int fRowsWrittenToOutput; |
530 | void* fDst; |
531 | size_t fRowBytes; |
532 | |
533 | // Variables for partial decode |
534 | int fFirstRow; // FIXME: Move to baseclass? |
535 | int fLastRow; |
536 | int fRowsNeeded; |
537 | |
538 | using INHERITED = SkPngCodec; |
539 | |
540 | static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) { |
541 | return static_cast<SkPngNormalDecoder*>(png_get_progressive_ptr(png_ptr)); |
542 | } |
543 | |
544 | Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { |
545 | const int height = this->dimensions().height(); |
546 | png_set_progressive_read_fn(png_ptr: this->png_ptr(), progressive_ptr: this, info_fn: nullptr, row_fn: AllRowsCallback, end_fn: nullptr); |
547 | fDst = dst; |
548 | fRowBytes = rowBytes; |
549 | |
550 | fRowsWrittenToOutput = 0; |
551 | fFirstRow = 0; |
552 | fLastRow = height - 1; |
553 | |
554 | const bool success = this->processData(); |
555 | if (success && fRowsWrittenToOutput == height) { |
556 | return kSuccess; |
557 | } |
558 | |
559 | if (rowsDecoded) { |
560 | *rowsDecoded = fRowsWrittenToOutput; |
561 | } |
562 | |
563 | return log_and_return_error(success); |
564 | } |
565 | |
566 | void allRowsCallback(png_bytep row, int rowNum) { |
567 | SkASSERT(rowNum == fRowsWrittenToOutput); |
568 | fRowsWrittenToOutput++; |
569 | this->applyXformRow(dst: fDst, src: row); |
570 | fDst = SkTAddOffset<void>(ptr: fDst, byteOffset: fRowBytes); |
571 | } |
572 | |
573 | void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { |
574 | png_set_progressive_read_fn(png_ptr: this->png_ptr(), progressive_ptr: this, info_fn: nullptr, row_fn: RowCallback, end_fn: nullptr); |
575 | fFirstRow = firstRow; |
576 | fLastRow = lastRow; |
577 | fDst = dst; |
578 | fRowBytes = rowBytes; |
579 | fRowsWrittenToOutput = 0; |
580 | fRowsNeeded = fLastRow - fFirstRow + 1; |
581 | } |
582 | |
583 | Result decode(int* rowsDecoded) override { |
584 | if (this->swizzler()) { |
585 | const int sampleY = this->swizzler()->sampleY(); |
586 | fRowsNeeded = get_scaled_dimension(srcDimension: fLastRow - fFirstRow + 1, sampleSize: sampleY); |
587 | } |
588 | |
589 | const bool success = this->processData(); |
590 | if (success && fRowsWrittenToOutput == fRowsNeeded) { |
591 | return kSuccess; |
592 | } |
593 | |
594 | if (rowsDecoded) { |
595 | *rowsDecoded = fRowsWrittenToOutput; |
596 | } |
597 | |
598 | return log_and_return_error(success); |
599 | } |
600 | |
601 | void rowCallback(png_bytep row, int rowNum) { |
602 | if (rowNum < fFirstRow) { |
603 | // Ignore this row. |
604 | return; |
605 | } |
606 | |
607 | SkASSERT(rowNum <= fLastRow); |
608 | SkASSERT(fRowsWrittenToOutput < fRowsNeeded); |
609 | |
610 | // If there is no swizzler, all rows are needed. |
611 | if (!this->swizzler() || this->swizzler()->rowNeeded(row: rowNum - fFirstRow)) { |
612 | this->applyXformRow(dst: fDst, src: row); |
613 | fDst = SkTAddOffset<void>(ptr: fDst, byteOffset: fRowBytes); |
614 | fRowsWrittenToOutput++; |
615 | } |
616 | |
617 | if (fRowsWrittenToOutput == fRowsNeeded) { |
618 | // Fake error to stop decoding scanlines. |
619 | longjmp(PNG_JMPBUF(this->png_ptr()), val: kStopDecoding); |
620 | } |
621 | } |
622 | }; |
623 | |
624 | class SkPngInterlacedDecoder : public SkPngCodec { |
625 | public: |
626 | SkPngInterlacedDecoder(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, |
627 | SkPngChunkReader* reader, png_structp png_ptr, |
628 | png_infop info_ptr, int bitDepth, int numberPasses) |
629 | : INHERITED(std::move(info), std::move(stream), reader, png_ptr, info_ptr, bitDepth) |
630 | , fNumberPasses(numberPasses) |
631 | , fFirstRow(0) |
632 | , fLastRow(0) |
633 | , fLinesDecoded(0) |
634 | , fInterlacedComplete(false) |
635 | , fPng_rowbytes(0) |
636 | {} |
637 | |
638 | static void InterlacedRowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int pass) { |
639 | auto decoder = static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr)); |
640 | decoder->interlacedRowCallback(row, rowNum, pass); |
641 | } |
642 | |
643 | private: |
644 | const int fNumberPasses; |
645 | int fFirstRow; |
646 | int fLastRow; |
647 | void* fDst; |
648 | size_t fRowBytes; |
649 | int fLinesDecoded; |
650 | bool fInterlacedComplete; |
651 | size_t fPng_rowbytes; |
652 | AutoTMalloc<png_byte> fInterlaceBuffer; |
653 | |
654 | using INHERITED = SkPngCodec; |
655 | |
656 | // FIXME: Currently sharing interlaced callback for all rows and subset. It's not |
657 | // as expensive as the subset version of non-interlaced, but it still does extra |
658 | // work. |
659 | void interlacedRowCallback(png_bytep row, int rowNum, int pass) { |
660 | if (rowNum < fFirstRow || rowNum > fLastRow || fInterlacedComplete) { |
661 | // Ignore this row |
662 | return; |
663 | } |
664 | |
665 | png_bytep oldRow = fInterlaceBuffer.get() + (rowNum - fFirstRow) * fPng_rowbytes; |
666 | png_progressive_combine_row(png_ptr: this->png_ptr(), old_row: oldRow, new_row: row); |
667 | |
668 | if (0 == pass) { |
669 | // The first pass initializes all rows. |
670 | SkASSERT(row); |
671 | SkASSERT(fLinesDecoded == rowNum - fFirstRow); |
672 | fLinesDecoded++; |
673 | } else { |
674 | SkASSERT(fLinesDecoded == fLastRow - fFirstRow + 1); |
675 | if (fNumberPasses - 1 == pass && rowNum == fLastRow) { |
676 | // Last pass, and we have read all of the rows we care about. |
677 | fInterlacedComplete = true; |
678 | if (fLastRow != this->dimensions().height() - 1 || |
679 | (this->swizzler() && this->swizzler()->sampleY() != 1)) { |
680 | // Fake error to stop decoding scanlines. Only stop if we're not decoding the |
681 | // whole image, in which case processing the rest of the image might be |
682 | // expensive. When decoding the whole image, read through the IEND chunk to |
683 | // preserve Android behavior of leaving the input stream in the right place. |
684 | longjmp(PNG_JMPBUF(this->png_ptr()), val: kStopDecoding); |
685 | } |
686 | } |
687 | } |
688 | } |
689 | |
690 | Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { |
691 | const int height = this->dimensions().height(); |
692 | this->setUpInterlaceBuffer(height); |
693 | png_set_progressive_read_fn(png_ptr: this->png_ptr(), progressive_ptr: this, info_fn: nullptr, row_fn: InterlacedRowCallback, |
694 | end_fn: nullptr); |
695 | |
696 | fFirstRow = 0; |
697 | fLastRow = height - 1; |
698 | fLinesDecoded = 0; |
699 | |
700 | const bool success = this->processData(); |
701 | png_bytep srcRow = fInterlaceBuffer.get(); |
702 | // FIXME: When resuming, this may rewrite rows that did not change. |
703 | for (int rowNum = 0; rowNum < fLinesDecoded; rowNum++) { |
704 | this->applyXformRow(dst, src: srcRow); |
705 | dst = SkTAddOffset<void>(ptr: dst, byteOffset: rowBytes); |
706 | srcRow = SkTAddOffset<png_byte>(ptr: srcRow, byteOffset: fPng_rowbytes); |
707 | } |
708 | if (success && fInterlacedComplete) { |
709 | return kSuccess; |
710 | } |
711 | |
712 | if (rowsDecoded) { |
713 | *rowsDecoded = fLinesDecoded; |
714 | } |
715 | |
716 | return log_and_return_error(success); |
717 | } |
718 | |
719 | void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { |
720 | // FIXME: We could skip rows in the interlace buffer that we won't put in the output. |
721 | this->setUpInterlaceBuffer(lastRow - firstRow + 1); |
722 | png_set_progressive_read_fn(png_ptr: this->png_ptr(), progressive_ptr: this, info_fn: nullptr, row_fn: InterlacedRowCallback, end_fn: nullptr); |
723 | fFirstRow = firstRow; |
724 | fLastRow = lastRow; |
725 | fDst = dst; |
726 | fRowBytes = rowBytes; |
727 | fLinesDecoded = 0; |
728 | } |
729 | |
730 | Result decode(int* rowsDecoded) override { |
731 | const bool success = this->processData(); |
732 | |
733 | // Now apply Xforms on all the rows that were decoded. |
734 | if (!fLinesDecoded) { |
735 | if (rowsDecoded) { |
736 | *rowsDecoded = 0; |
737 | } |
738 | return log_and_return_error(success); |
739 | } |
740 | |
741 | const int sampleY = this->swizzler() ? this->swizzler()->sampleY() : 1; |
742 | const int rowsNeeded = get_scaled_dimension(srcDimension: fLastRow - fFirstRow + 1, sampleSize: sampleY); |
743 | |
744 | // FIXME: For resuming interlace, we may swizzle a row that hasn't changed. But it |
745 | // may be too tricky/expensive to handle that correctly. |
746 | |
747 | // Offset srcRow by get_start_coord rows. We do not need to account for fFirstRow, |
748 | // since the first row in fInterlaceBuffer corresponds to fFirstRow. |
749 | int srcRow = get_start_coord(sampleFactor: sampleY); |
750 | void* dst = fDst; |
751 | int rowsWrittenToOutput = 0; |
752 | while (rowsWrittenToOutput < rowsNeeded && srcRow < fLinesDecoded) { |
753 | png_bytep src = SkTAddOffset<png_byte>(ptr: fInterlaceBuffer.get(), byteOffset: fPng_rowbytes * srcRow); |
754 | this->applyXformRow(dst, src); |
755 | dst = SkTAddOffset<void>(ptr: dst, byteOffset: fRowBytes); |
756 | |
757 | rowsWrittenToOutput++; |
758 | srcRow += sampleY; |
759 | } |
760 | |
761 | if (success && fInterlacedComplete) { |
762 | return kSuccess; |
763 | } |
764 | |
765 | if (rowsDecoded) { |
766 | *rowsDecoded = rowsWrittenToOutput; |
767 | } |
768 | return log_and_return_error(success); |
769 | } |
770 | |
771 | void setUpInterlaceBuffer(int height) { |
772 | fPng_rowbytes = png_get_rowbytes(png_ptr: this->png_ptr(), info_ptr: this->info_ptr()); |
773 | fInterlaceBuffer.reset(count: fPng_rowbytes * height); |
774 | fInterlacedComplete = false; |
775 | } |
776 | }; |
777 | |
778 | // Reads the header and initializes the output fields, if not NULL. |
779 | // |
780 | // @param stream Input data. Will be read to get enough information to properly |
781 | // setup the codec. |
782 | // @param chunkReader SkPngChunkReader, for reading unknown chunks. May be NULL. |
783 | // If not NULL, png_ptr will hold an *unowned* pointer to it. The caller is |
784 | // expected to continue to own it for the lifetime of the png_ptr. |
785 | // @param outCodec Optional output variable. If non-NULL, will be set to a new |
786 | // SkPngCodec on success. |
787 | // @param png_ptrp Optional output variable. If non-NULL, will be set to a new |
788 | // png_structp on success. |
789 | // @param info_ptrp Optional output variable. If non-NULL, will be set to a new |
790 | // png_infop on success; |
791 | // @return if kSuccess, the caller is responsible for calling |
792 | // png_destroy_read_struct(png_ptrp, info_ptrp). |
793 | // Otherwise, the passed in fields (except stream) are unchanged. |
794 | static SkCodec::Result read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
795 | SkCodec** outCodec, |
796 | png_structp* png_ptrp, png_infop* info_ptrp) { |
797 | // The image is known to be a PNG. Decode enough to know the SkImageInfo. |
798 | png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, error_ptr: nullptr, |
799 | error_fn: sk_error_fn, warn_fn: sk_warning_fn); |
800 | if (!png_ptr) { |
801 | return SkCodec::kInternalError; |
802 | } |
803 | |
804 | #ifdef PNG_SET_OPTION_SUPPORTED |
805 | // This setting ensures that we display images with incorrect CMF bytes. |
806 | // See crbug.com/807324. |
807 | png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); |
808 | #endif |
809 | |
810 | AutoCleanPng autoClean(png_ptr, stream, chunkReader, outCodec); |
811 | |
812 | png_infop info_ptr = png_create_info_struct(png_ptr); |
813 | if (info_ptr == nullptr) { |
814 | return SkCodec::kInternalError; |
815 | } |
816 | |
817 | autoClean.setInfoPtr(info_ptr); |
818 | |
819 | if (setjmp(PNG_JMPBUF(png_ptr))) { |
820 | return SkCodec::kInvalidInput; |
821 | } |
822 | |
823 | #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED |
824 | // Hookup our chunkReader so we can see any user-chunks the caller may be interested in. |
825 | // This needs to be installed before we read the png header. Android may store ninepatch |
826 | // chunks in the header. |
827 | if (chunkReader) { |
828 | png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, chunk_list: (png_byte*)"", num_chunks: 0); |
829 | png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr: (png_voidp) chunkReader, read_user_chunk_fn: sk_read_user_chunk); |
830 | } |
831 | #endif |
832 | |
833 | const bool decodedBounds = autoClean.decodeBounds(); |
834 | |
835 | if (!decodedBounds) { |
836 | return SkCodec::kIncompleteInput; |
837 | } |
838 | |
839 | // On success, decodeBounds releases ownership of png_ptr and info_ptr. |
840 | if (png_ptrp) { |
841 | *png_ptrp = png_ptr; |
842 | } |
843 | if (info_ptrp) { |
844 | *info_ptrp = info_ptr; |
845 | } |
846 | |
847 | // decodeBounds takes care of setting outCodec |
848 | if (outCodec) { |
849 | SkASSERT(*outCodec); |
850 | } |
851 | return SkCodec::kSuccess; |
852 | } |
853 | |
854 | void AutoCleanPng::infoCallback(size_t idatLength) { |
855 | png_uint_32 origWidth, origHeight; |
856 | int bitDepth, encodedColorType; |
857 | png_get_IHDR(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, width: &origWidth, height: &origHeight, bit_depth: &bitDepth, |
858 | color_type: &encodedColorType, interlace_method: nullptr, compression_method: nullptr, filter_method: nullptr); |
859 | |
860 | // TODO: Should we support 16-bits of precision for gray images? |
861 | if (bitDepth == 16 && (PNG_COLOR_TYPE_GRAY == encodedColorType || |
862 | PNG_COLOR_TYPE_GRAY_ALPHA == encodedColorType)) { |
863 | bitDepth = 8; |
864 | png_set_strip_16(png_ptr: fPng_ptr); |
865 | } |
866 | |
867 | // Now determine the default colorType and alphaType and set the required transforms. |
868 | // Often, we depend on SkSwizzler to perform any transforms that we need. However, we |
869 | // still depend on libpng for many of the rare and PNG-specific cases. |
870 | SkEncodedInfo::Color color; |
871 | SkEncodedInfo::Alpha alpha; |
872 | switch (encodedColorType) { |
873 | case PNG_COLOR_TYPE_PALETTE: |
874 | // Extract multiple pixels with bit depths of 1, 2, and 4 from a single |
875 | // byte into separate bytes (useful for paletted and grayscale images). |
876 | if (bitDepth < 8) { |
877 | // TODO: Should we use SkSwizzler here? |
878 | bitDepth = 8; |
879 | png_set_packing(png_ptr: fPng_ptr); |
880 | } |
881 | |
882 | color = SkEncodedInfo::kPalette_Color; |
883 | // Set the alpha depending on if a transparency chunk exists. |
884 | alpha = png_get_valid(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, PNG_INFO_tRNS) ? |
885 | SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha; |
886 | break; |
887 | case PNG_COLOR_TYPE_RGB: |
888 | if (png_get_valid(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, PNG_INFO_tRNS)) { |
889 | // Convert to RGBA if transparency chunk exists. |
890 | png_set_tRNS_to_alpha(png_ptr: fPng_ptr); |
891 | color = SkEncodedInfo::kRGBA_Color; |
892 | alpha = SkEncodedInfo::kBinary_Alpha; |
893 | } else { |
894 | color = SkEncodedInfo::kRGB_Color; |
895 | alpha = SkEncodedInfo::kOpaque_Alpha; |
896 | } |
897 | break; |
898 | case PNG_COLOR_TYPE_GRAY: |
899 | // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel. |
900 | if (bitDepth < 8) { |
901 | // TODO: Should we use SkSwizzler here? |
902 | bitDepth = 8; |
903 | png_set_expand_gray_1_2_4_to_8(png_ptr: fPng_ptr); |
904 | } |
905 | |
906 | if (png_get_valid(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, PNG_INFO_tRNS)) { |
907 | png_set_tRNS_to_alpha(png_ptr: fPng_ptr); |
908 | color = SkEncodedInfo::kGrayAlpha_Color; |
909 | alpha = SkEncodedInfo::kBinary_Alpha; |
910 | } else { |
911 | color = SkEncodedInfo::kGray_Color; |
912 | alpha = SkEncodedInfo::kOpaque_Alpha; |
913 | } |
914 | break; |
915 | case PNG_COLOR_TYPE_GRAY_ALPHA: |
916 | color = SkEncodedInfo::kGrayAlpha_Color; |
917 | alpha = SkEncodedInfo::kUnpremul_Alpha; |
918 | break; |
919 | case PNG_COLOR_TYPE_RGBA: |
920 | color = SkEncodedInfo::kRGBA_Color; |
921 | alpha = SkEncodedInfo::kUnpremul_Alpha; |
922 | break; |
923 | default: |
924 | // All the color types have been covered above. |
925 | SkASSERT(false); |
926 | color = SkEncodedInfo::kRGBA_Color; |
927 | alpha = SkEncodedInfo::kUnpremul_Alpha; |
928 | } |
929 | |
930 | const int numberPasses = png_set_interlace_handling(png_ptr: fPng_ptr); |
931 | |
932 | if (fOutCodec) { |
933 | SkASSERT(nullptr == *fOutCodec); |
934 | auto profile = read_color_profile(png_ptr: fPng_ptr, info_ptr: fInfo_ptr); |
935 | if (profile) { |
936 | switch (profile->profile()->data_color_space) { |
937 | case skcms_Signature_CMYK: |
938 | profile = nullptr; |
939 | break; |
940 | case skcms_Signature_Gray: |
941 | if (SkEncodedInfo::kGray_Color != color && |
942 | SkEncodedInfo::kGrayAlpha_Color != color) |
943 | { |
944 | profile = nullptr; |
945 | } |
946 | break; |
947 | default: |
948 | break; |
949 | } |
950 | } |
951 | |
952 | switch (encodedColorType) { |
953 | case PNG_COLOR_TYPE_GRAY_ALPHA:{ |
954 | png_color_8p sigBits; |
955 | if (png_get_sBIT(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, sig_bit: &sigBits)) { |
956 | if (8 == sigBits->alpha && kGraySigBit_GrayAlphaIsJustAlpha == sigBits->gray) { |
957 | color = SkEncodedInfo::kXAlpha_Color; |
958 | } |
959 | } |
960 | break; |
961 | } |
962 | case PNG_COLOR_TYPE_RGB:{ |
963 | png_color_8p sigBits; |
964 | if (png_get_sBIT(png_ptr: fPng_ptr, info_ptr: fInfo_ptr, sig_bit: &sigBits)) { |
965 | if (5 == sigBits->red && 6 == sigBits->green && 5 == sigBits->blue) { |
966 | // Recommend a decode to 565 if the sBIT indicates 565. |
967 | color = SkEncodedInfo::k565_Color; |
968 | } |
969 | } |
970 | break; |
971 | } |
972 | } |
973 | |
974 | #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK |
975 | if (encodedColorType != PNG_COLOR_TYPE_GRAY_ALPHA |
976 | && SkEncodedInfo::kOpaque_Alpha == alpha) { |
977 | png_color_8p sigBits; |
978 | if (png_get_sBIT(fPng_ptr, fInfo_ptr, &sigBits)) { |
979 | if (5 == sigBits->red && 6 == sigBits->green && 5 == sigBits->blue) { |
980 | SkAndroidFrameworkUtils::SafetyNetLog("190188264"); |
981 | } |
982 | } |
983 | } |
984 | #endif // SK_BUILD_FOR_ANDROID_FRAMEWORK |
985 | |
986 | SkEncodedInfo encodedInfo = SkEncodedInfo::Make(width: origWidth, height: origHeight, color, alpha, |
987 | bitsPerComponent: bitDepth, profile: std::move(profile)); |
988 | if (1 == numberPasses) { |
989 | *fOutCodec = new SkPngNormalDecoder(std::move(encodedInfo), |
990 | std::unique_ptr<SkStream>(fStream), fChunkReader, fPng_ptr, fInfo_ptr, bitDepth); |
991 | } else { |
992 | *fOutCodec = new SkPngInterlacedDecoder(std::move(encodedInfo), |
993 | std::unique_ptr<SkStream>(fStream), fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, |
994 | numberPasses); |
995 | } |
996 | static_cast<SkPngCodec*>(*fOutCodec)->setIdatLength(idatLength); |
997 | } |
998 | |
999 | // Release the pointers, which are now owned by the codec or the caller is expected to |
1000 | // take ownership. |
1001 | this->releasePngPtrs(); |
1002 | } |
1003 | |
1004 | SkPngCodec::SkPngCodec(SkEncodedInfo&& encodedInfo, std::unique_ptr<SkStream> stream, |
1005 | SkPngChunkReader* chunkReader, void* png_ptr, void* info_ptr, int bitDepth) |
1006 | : INHERITED(std::move(encodedInfo), png_select_xform_format(info: encodedInfo), std::move(stream)) |
1007 | , fPngChunkReader(SkSafeRef(obj: chunkReader)) |
1008 | , fPng_ptr(png_ptr) |
1009 | , fInfo_ptr(info_ptr) |
1010 | , fColorXformSrcRow(nullptr) |
1011 | , fBitDepth(bitDepth) |
1012 | , fIdatLength(0) |
1013 | , fDecodedIdat(false) |
1014 | {} |
1015 | |
1016 | SkPngCodec::~SkPngCodec() { |
1017 | this->destroyReadStruct(); |
1018 | } |
1019 | |
1020 | void SkPngCodec::destroyReadStruct() { |
1021 | if (fPng_ptr) { |
1022 | // We will never have a nullptr fInfo_ptr with a non-nullptr fPng_ptr |
1023 | SkASSERT(fInfo_ptr); |
1024 | png_destroy_read_struct(png_ptr_ptr: (png_struct**)&fPng_ptr, info_ptr_ptr: (png_info**)&fInfo_ptr, end_info_ptr_ptr: nullptr); |
1025 | fPng_ptr = nullptr; |
1026 | fInfo_ptr = nullptr; |
1027 | } |
1028 | } |
1029 | |
1030 | /////////////////////////////////////////////////////////////////////////////// |
1031 | // Getting the pixels |
1032 | /////////////////////////////////////////////////////////////////////////////// |
1033 | |
1034 | SkCodec::Result SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& options) { |
1035 | if (setjmp(PNG_JMPBUF((png_struct*)fPng_ptr))) { |
1036 | SkCodecPrintf("Failed on png_read_update_info.\n"); |
1037 | return kInvalidInput; |
1038 | } |
1039 | png_read_update_info(png_ptr: fPng_ptr, info_ptr: fInfo_ptr); |
1040 | |
1041 | // Reset fSwizzler and this->colorXform(). We can't do this in onRewind() because the |
1042 | // interlaced scanline decoder may need to rewind. |
1043 | fSwizzler.reset(p: nullptr); |
1044 | |
1045 | // If skcms directly supports the encoded PNG format, we should skip format |
1046 | // conversion in the swizzler (or skip swizzling altogether). |
1047 | bool skipFormatConversion = false; |
1048 | switch (this->getEncodedInfo().color()) { |
1049 | case SkEncodedInfo::kRGB_Color: |
1050 | if (this->getEncodedInfo().bitsPerComponent() != 16) { |
1051 | break; |
1052 | } |
1053 | [[fallthrough]]; |
1054 | case SkEncodedInfo::kRGBA_Color: |
1055 | case SkEncodedInfo::kGray_Color: |
1056 | skipFormatConversion = this->colorXform(); |
1057 | break; |
1058 | default: |
1059 | break; |
1060 | } |
1061 | if (skipFormatConversion && !options.fSubset) { |
1062 | fXformMode = kColorOnly_XformMode; |
1063 | return kSuccess; |
1064 | } |
1065 | |
1066 | if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) { |
1067 | if (!this->createColorTable(dstInfo)) { |
1068 | return kInvalidInput; |
1069 | } |
1070 | } |
1071 | |
1072 | this->initializeSwizzler(dstInfo, options, skipFormatConversion); |
1073 | return kSuccess; |
1074 | } |
1075 | |
1076 | void SkPngCodec::initializeXformParams() { |
1077 | switch (fXformMode) { |
1078 | case kColorOnly_XformMode: |
1079 | fXformWidth = this->dstInfo().width(); |
1080 | break; |
1081 | case kSwizzleColor_XformMode: |
1082 | fXformWidth = this->swizzler()->swizzleWidth(); |
1083 | break; |
1084 | default: |
1085 | break; |
1086 | } |
1087 | } |
1088 | |
1089 | void SkPngCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, |
1090 | bool skipFormatConversion) { |
1091 | SkImageInfo swizzlerInfo = dstInfo; |
1092 | Options swizzlerOptions = options; |
1093 | fXformMode = kSwizzleOnly_XformMode; |
1094 | if (this->colorXform() && this->xformOnDecode()) { |
1095 | if (SkEncodedInfo::kGray_Color == this->getEncodedInfo().color()) { |
1096 | swizzlerInfo = swizzlerInfo.makeColorType(newColorType: kGray_8_SkColorType); |
1097 | } else { |
1098 | swizzlerInfo = swizzlerInfo.makeColorType(newColorType: kXformSrcColorType); |
1099 | } |
1100 | if (kPremul_SkAlphaType == dstInfo.alphaType()) { |
1101 | swizzlerInfo = swizzlerInfo.makeAlphaType(newAlphaType: kUnpremul_SkAlphaType); |
1102 | } |
1103 | |
1104 | fXformMode = kSwizzleColor_XformMode; |
1105 | |
1106 | // Here, we swizzle into temporary memory, which is not zero initialized. |
1107 | // FIXME (msarett): |
1108 | // Is this a problem? |
1109 | swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; |
1110 | } |
1111 | |
1112 | if (skipFormatConversion) { |
1113 | // We cannot skip format conversion when there is a color table. |
1114 | SkASSERT(!fColorTable); |
1115 | int srcBPP = 0; |
1116 | switch (this->getEncodedInfo().color()) { |
1117 | case SkEncodedInfo::kRGB_Color: |
1118 | SkASSERT(this->getEncodedInfo().bitsPerComponent() == 16); |
1119 | srcBPP = 6; |
1120 | break; |
1121 | case SkEncodedInfo::kRGBA_Color: |
1122 | srcBPP = this->getEncodedInfo().bitsPerComponent() / 2; |
1123 | break; |
1124 | case SkEncodedInfo::kGray_Color: |
1125 | srcBPP = 1; |
1126 | break; |
1127 | default: |
1128 | SkASSERT(false); |
1129 | break; |
1130 | } |
1131 | fSwizzler = SkSwizzler::MakeSimple(srcBPP, dstInfo: swizzlerInfo, swizzlerOptions); |
1132 | } else { |
1133 | const SkPMColor* colors = get_color_ptr(colorTable: fColorTable.get()); |
1134 | fSwizzler = SkSwizzler::Make(encodedInfo: this->getEncodedInfo(), ctable: colors, dstInfo: swizzlerInfo, |
1135 | swizzlerOptions); |
1136 | } |
1137 | SkASSERT(fSwizzler); |
1138 | } |
1139 | |
1140 | SkSampler* SkPngCodec::getSampler(bool createIfNecessary) { |
1141 | if (fSwizzler || !createIfNecessary) { |
1142 | return fSwizzler.get(); |
1143 | } |
1144 | |
1145 | this->initializeSwizzler(dstInfo: this->dstInfo(), options: this->options(), skipFormatConversion: true); |
1146 | return fSwizzler.get(); |
1147 | } |
1148 | |
1149 | bool SkPngCodec::onRewind() { |
1150 | // This sets fPng_ptr and fInfo_ptr to nullptr. If read_header |
1151 | // succeeds, they will be repopulated, and if it fails, they will |
1152 | // remain nullptr. Any future accesses to fPng_ptr and fInfo_ptr will |
1153 | // come through this function which will rewind and again attempt |
1154 | // to reinitialize them. |
1155 | this->destroyReadStruct(); |
1156 | |
1157 | png_structp png_ptr; |
1158 | png_infop info_ptr; |
1159 | if (kSuccess != read_header(stream: this->stream(), chunkReader: fPngChunkReader.get(), outCodec: nullptr, |
1160 | png_ptrp: &png_ptr, info_ptrp: &info_ptr)) { |
1161 | return false; |
1162 | } |
1163 | |
1164 | fPng_ptr = png_ptr; |
1165 | fInfo_ptr = info_ptr; |
1166 | fDecodedIdat = false; |
1167 | return true; |
1168 | } |
1169 | |
1170 | SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, |
1171 | size_t rowBytes, const Options& options, |
1172 | int* rowsDecoded) { |
1173 | Result result = this->initializeXforms(dstInfo, options); |
1174 | if (kSuccess != result) { |
1175 | return result; |
1176 | } |
1177 | |
1178 | if (options.fSubset) { |
1179 | return kUnimplemented; |
1180 | } |
1181 | |
1182 | this->allocateStorage(dstInfo); |
1183 | this->initializeXformParams(); |
1184 | return this->decodeAllRows(dst, rowBytes, rowsDecoded); |
1185 | } |
1186 | |
1187 | SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, |
1188 | void* dst, size_t rowBytes, const SkCodec::Options& options) { |
1189 | Result result = this->initializeXforms(dstInfo, options); |
1190 | if (kSuccess != result) { |
1191 | return result; |
1192 | } |
1193 | |
1194 | this->allocateStorage(dstInfo); |
1195 | |
1196 | int firstRow, lastRow; |
1197 | if (options.fSubset) { |
1198 | firstRow = options.fSubset->top(); |
1199 | lastRow = options.fSubset->bottom() - 1; |
1200 | } else { |
1201 | firstRow = 0; |
1202 | lastRow = dstInfo.height() - 1; |
1203 | } |
1204 | this->setRange(firstRow, lastRow, dst, rowBytes); |
1205 | return kSuccess; |
1206 | } |
1207 | |
1208 | SkCodec::Result SkPngCodec::onIncrementalDecode(int* rowsDecoded) { |
1209 | // FIXME: Only necessary on the first call. |
1210 | this->initializeXformParams(); |
1211 | |
1212 | return this->decode(rowsDecoded); |
1213 | } |
1214 | |
1215 | std::unique_ptr<SkCodec> SkPngCodec::MakeFromStream(std::unique_ptr<SkStream> stream, |
1216 | Result* result, SkPngChunkReader* chunkReader) { |
1217 | SkASSERT(result); |
1218 | if (!stream) { |
1219 | *result = SkCodec::kInvalidInput; |
1220 | return nullptr; |
1221 | } |
1222 | SkCodec* outCodec = nullptr; |
1223 | *result = read_header(stream: stream.get(), chunkReader, outCodec: &outCodec, png_ptrp: nullptr, info_ptrp: nullptr); |
1224 | if (kSuccess == *result) { |
1225 | // Codec has taken ownership of the stream. |
1226 | SkASSERT(outCodec); |
1227 | stream.release(); |
1228 | } |
1229 | return std::unique_ptr<SkCodec>(outCodec); |
1230 | } |
1231 | |
1232 | namespace SkPngDecoder { |
1233 | bool IsPng(const void* data, size_t len) { |
1234 | return SkPngCodec::IsPng(buf: data, bytesRead: len); |
1235 | } |
1236 | |
1237 | std::unique_ptr<SkCodec> Decode(std::unique_ptr<SkStream> stream, |
1238 | SkCodec::Result* outResult, |
1239 | SkCodecs::DecodeContext ctx) { |
1240 | SkCodec::Result resultStorage; |
1241 | if (!outResult) { |
1242 | outResult = &resultStorage; |
1243 | } |
1244 | SkPngChunkReader* chunkReader = nullptr; |
1245 | if (ctx) { |
1246 | chunkReader = static_cast<SkPngChunkReader*>(ctx); |
1247 | } |
1248 | return SkPngCodec::MakeFromStream(stream: std::move(stream), result: outResult, chunkReader); |
1249 | } |
1250 | |
1251 | std::unique_ptr<SkCodec> Decode(sk_sp<SkData> data, |
1252 | SkCodec::Result* outResult, |
1253 | SkCodecs::DecodeContext ctx) { |
1254 | if (!data) { |
1255 | if (outResult) { |
1256 | *outResult = SkCodec::kInvalidInput; |
1257 | } |
1258 | return nullptr; |
1259 | } |
1260 | return Decode(stream: SkMemoryStream::Make(data: std::move(data)), outResult, ctx); |
1261 | } |
1262 | } // namespace SkPngDecoder |
1263 |
Definitions
- kSetJmpOkay
- kPngError
- kStopDecoding
- sk_error_fn
- sk_warning_fn
- sk_read_user_chunk
- AutoCleanPng
- AutoCleanPng
- ~AutoCleanPng
- setInfoPtr
- releasePngPtrs
- is_chunk
- process_data
- decodeBounds
- processData
- kXformSrcColorType
- needs_premul
- createColorTable
- IsPng
- png_fixed_point_to_float
- png_inverted_fixed_point_to_float
- read_color_profile
- allocateStorage
- png_select_xform_format
- applyXformRow
- log_and_return_error
- SkPngNormalDecoder
- SkPngNormalDecoder
- AllRowsCallback
- RowCallback
- GetDecoder
- decodeAllRows
- allRowsCallback
- setRange
- decode
- rowCallback
- SkPngInterlacedDecoder
- SkPngInterlacedDecoder
- InterlacedRowCallback
- interlacedRowCallback
- decodeAllRows
- setRange
- decode
- setUpInterlaceBuffer
- read_header
- infoCallback
- SkPngCodec
- ~SkPngCodec
- destroyReadStruct
- initializeXforms
- initializeXformParams
- initializeSwizzler
- getSampler
- onRewind
- onGetPixels
- onStartIncrementalDecode
- onIncrementalDecode
- MakeFromStream
- IsPng
- Decode
Learn more about Flutter for embedded and desktop on industrialflutter.com