blob: 56995801fcfbbc5cba08959b69387913eb254743 [file] [log] [blame]
scroggof24f2242015-03-03 08:59:20 -08001/*
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
msarettad8bcfe2016-03-07 07:09:03 -08008#include "SkBitmap.h"
msarett74114382015-03-16 11:55:18 -07009#include "SkCodecPriv.h"
scroggof24f2242015-03-03 08:59:20 -080010#include "SkColorPriv.h"
Matt Sarettc434f512016-10-13 18:24:20 -040011#include "SkColorSpace.h"
scroggof24f2242015-03-03 08:59:20 -080012#include "SkColorTable.h"
scroggof24f2242015-03-03 08:59:20 -080013#include "SkMath.h"
msarett13a91232016-02-01 08:03:29 -080014#include "SkOpts.h"
msarettbe1d5552016-01-21 09:05:23 -080015#include "SkPngCodec.h"
msarett55447952016-07-22 14:07:23 -070016#include "SkPoint3.h"
scroggof24f2242015-03-03 08:59:20 -080017#include "SkSize.h"
18#include "SkStream.h"
19#include "SkSwizzler.h"
scroggo565901d2015-12-10 10:44:13 -080020#include "SkTemplates.h"
bungeman5d2cd6e2016-02-23 07:34:25 -080021#include "SkUtils.h"
scroggof24f2242015-03-03 08:59:20 -080022
mtklein63213812016-08-24 09:55:56 -070023#include "png.h"
24
mtkleindc90b532016-07-28 14:45:28 -070025// This warning triggers false postives way too often in here.
26#if defined(__GNUC__) && !defined(__clang__)
27 #pragma GCC diagnostic ignored "-Wclobbered"
28#endif
29
scroggo8e6c7ad2016-09-16 08:20:38 -070030#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
31 // This is not needed with version 1.5
32 #undef SK_GOOGLE3_PNG_HACK
33#endif
34
35// FIXME (scroggo): We can use png_jumpbuf directly once Google3 is on 1.6
36#define PNG_JMPBUF(x) png_jmpbuf((png_structp) x)
37
scroggof24f2242015-03-03 08:59:20 -080038///////////////////////////////////////////////////////////////////////////////
scroggof24f2242015-03-03 08:59:20 -080039// Callback functions
40///////////////////////////////////////////////////////////////////////////////
41
scroggo8e6c7ad2016-09-16 08:20:38 -070042// When setjmp is first called, it returns 0, meaning longjmp was not called.
43constexpr int kSetJmpOkay = 0;
44// An error internal to libpng.
45constexpr int kPngError = 1;
46// Passed to longjmp when we have decoded as many lines as we need.
47constexpr int kStopDecoding = 2;
48
scroggof24f2242015-03-03 08:59:20 -080049static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
scroggo230d4ac2015-03-26 07:15:55 -070050 SkCodecPrintf("------ png error %s\n", msg);
scroggo8e6c7ad2016-09-16 08:20:38 -070051 longjmp(PNG_JMPBUF(png_ptr), kPngError);
scroggof24f2242015-03-03 08:59:20 -080052}
53
scroggo0eed6df2015-03-26 10:07:56 -070054void sk_warning_fn(png_structp, png_const_charp msg) {
55 SkCodecPrintf("----- png warning %s\n", msg);
56}
57
scroggocf98fa92015-11-23 08:14:40 -080058#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
59static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
60 SkPngChunkReader* chunkReader = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr);
61 // readChunk() returning true means continue decoding
62 return chunkReader->readChunk((const char*)chunk->name, chunk->data, chunk->size) ? 1 : -1;
63}
64#endif
65
scroggof24f2242015-03-03 08:59:20 -080066///////////////////////////////////////////////////////////////////////////////
67// Helpers
68///////////////////////////////////////////////////////////////////////////////
69
70class AutoCleanPng : public SkNoncopyable {
71public:
scroggo8e6c7ad2016-09-16 08:20:38 -070072 /*
73 * This class does not take ownership of stream or reader, but if codecPtr
74 * is non-NULL, and decodeBounds succeeds, it will have created a new
75 * SkCodec (pointed to by *codecPtr) which will own/ref them, as well as
76 * the png_ptr and info_ptr.
77 */
78 AutoCleanPng(png_structp png_ptr, SkStream* stream, SkPngChunkReader* reader,
79 SkCodec** codecPtr)
scroggof24f2242015-03-03 08:59:20 -080080 : fPng_ptr(png_ptr)
scroggo8e6c7ad2016-09-16 08:20:38 -070081 , fInfo_ptr(nullptr)
82 , fDecodedBounds(false)
83 , fReadHeader(false)
84 , fStream(stream)
85 , fChunkReader(reader)
86 , fOutCodec(codecPtr)
87 {}
scroggof24f2242015-03-03 08:59:20 -080088
89 ~AutoCleanPng() {
halcanary96fcdcc2015-08-27 07:41:13 -070090 // fInfo_ptr will never be non-nullptr unless fPng_ptr is.
scroggof24f2242015-03-03 08:59:20 -080091 if (fPng_ptr) {
halcanary96fcdcc2015-08-27 07:41:13 -070092 png_infopp info_pp = fInfo_ptr ? &fInfo_ptr : nullptr;
msarett13a91232016-02-01 08:03:29 -080093 png_destroy_read_struct(&fPng_ptr, info_pp, nullptr);
scroggof24f2242015-03-03 08:59:20 -080094 }
95 }
96
97 void setInfoPtr(png_infop info_ptr) {
halcanary96fcdcc2015-08-27 07:41:13 -070098 SkASSERT(nullptr == fInfo_ptr);
scroggof24f2242015-03-03 08:59:20 -080099 fInfo_ptr = info_ptr;
100 }
101
scroggo8e6c7ad2016-09-16 08:20:38 -0700102 /**
103 * Reads enough of the input stream to decode the bounds.
104 * @return false if the stream is not a valid PNG (or too short).
105 * true if it read enough of the stream to determine the bounds.
106 * In the latter case, the stream may have been read beyond the
107 * point to determine the bounds, and the png_ptr will have saved
108 * any extra data. Further, if the codecPtr supplied to the
109 * constructor was not NULL, it will now point to a new SkCodec,
110 * which owns (or refs, in the case of the SkPngChunkReader) the
111 * inputs. If codecPtr was NULL, the png_ptr and info_ptr are
112 * unowned, and it is up to the caller to destroy them.
113 */
114 bool decodeBounds();
115
116private:
117 png_structp fPng_ptr;
118 png_infop fInfo_ptr;
119 bool fDecodedBounds;
120 bool fReadHeader;
121 SkStream* fStream;
122 SkPngChunkReader* fChunkReader;
123 SkCodec** fOutCodec;
124
125 /**
126 * Supplied to libpng to call when it has read enough data to determine
127 * bounds.
128 */
129 static void InfoCallback(png_structp png_ptr, png_infop) {
130 // png_get_progressive_ptr returns the pointer we set on the png_ptr with
131 // png_set_progressive_read_fn
132 static_cast<AutoCleanPng*>(png_get_progressive_ptr(png_ptr))->infoCallback();
133 }
134
135 void infoCallback();
136
137#ifdef SK_GOOGLE3_PNG_HACK
138// public so it can be called by SkPngCodec::rereadHeaderIfNecessary().
139public:
140#endif
141 void releasePngPtrs() {
halcanary96fcdcc2015-08-27 07:41:13 -0700142 fPng_ptr = nullptr;
143 fInfo_ptr = nullptr;
scroggof24f2242015-03-03 08:59:20 -0800144 }
scroggof24f2242015-03-03 08:59:20 -0800145};
146#define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng)
147
scroggo8e6c7ad2016-09-16 08:20:38 -0700148bool AutoCleanPng::decodeBounds() {
149 if (setjmp(PNG_JMPBUF(fPng_ptr))) {
150 return false;
151 }
152
153 png_set_progressive_read_fn(fPng_ptr, this, InfoCallback, nullptr, nullptr);
154
155 // Arbitrary buffer size, though note that it matches (below)
156 // SkPngCodec::processData(). FIXME: Can we better suit this to the size of
157 // the PNG header?
158 constexpr size_t kBufferSize = 4096;
159 char buffer[kBufferSize];
160
161 while (true) {
162 const size_t bytesRead = fStream->read(buffer, kBufferSize);
163 if (!bytesRead) {
164 // We have read to the end of the input without decoding bounds.
165 break;
166 }
167
168 png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
169 if (fReadHeader) {
170 break;
171 }
172 }
173
174 // For safety, clear the pointer to this object.
175 png_set_progressive_read_fn(fPng_ptr, nullptr, nullptr, nullptr, nullptr);
176 return fDecodedBounds;
177}
178
179void SkPngCodec::processData() {
180 switch (setjmp(PNG_JMPBUF(fPng_ptr))) {
181 case kPngError:
182 // There was an error. Stop processing data.
183 // FIXME: Do we need to discard png_ptr?
184 return;
185 case kStopDecoding:
186 // We decoded all the lines we want.
187 return;
188 case kSetJmpOkay:
189 // Everything is okay.
190 break;
191 default:
192 // No other values should be passed to longjmp.
193 SkASSERT(false);
194 }
195
196 // Arbitrary buffer size
197 constexpr size_t kBufferSize = 4096;
198 char buffer[kBufferSize];
199
200 while (true) {
201 const size_t bytesRead = this->stream()->read(buffer, kBufferSize);
202 png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
203
204 if (!bytesRead) {
205 // We have read to the end of the input. Note that we quit *after*
206 // calling png_process_data, because decodeBounds may have told
207 // libpng to save the remainder of the buffer, in which case
208 // png_process_data will process the saved buffer, though the
209 // stream has no more to read.
210 break;
211 }
212 }
213}
214
Matt Sarett562e6812016-11-08 16:13:43 -0500215static const SkColorType kXformSrcColorType = kRGBA_8888_SkColorType;
216
msarettd1ec89b2016-08-03 12:59:27 -0700217// Note: SkColorTable claims to store SkPMColors, which is not necessarily the case here.
msarettdcd5e652016-08-22 08:48:40 -0700218bool SkPngCodec::createColorTable(const SkImageInfo& dstInfo, int* ctableCount) {
scroggof24f2242015-03-03 08:59:20 -0800219
msarett13a91232016-02-01 08:03:29 -0800220 int numColors;
221 png_color* palette;
222 if (!png_get_PLTE(fPng_ptr, fInfo_ptr, &palette, &numColors)) {
scroggo05245902015-03-25 11:11:52 -0700223 return false;
scroggof24f2242015-03-03 08:59:20 -0800224 }
225
msarettdcd5e652016-08-22 08:48:40 -0700226 // Contents depend on tableColorType and our choice of if/when to premultiply:
227 // { kPremul, kUnpremul, kOpaque } x { RGBA, BGRA }
228 SkPMColor colorTable[256];
Matt Sarett562e6812016-11-08 16:13:43 -0500229 SkColorType tableColorType = this->colorXform() ? kXformSrcColorType : dstInfo.colorType();
scroggof24f2242015-03-03 08:59:20 -0800230
msarett13a91232016-02-01 08:03:29 -0800231 png_bytep alphas;
232 int numColorsWithAlpha = 0;
233 if (png_get_tRNS(fPng_ptr, fInfo_ptr, &alphas, &numColorsWithAlpha, nullptr)) {
msarettdcd5e652016-08-22 08:48:40 -0700234 // If we are performing a color xform, it will handle the premultiply. Otherwise,
235 // we'll do it here.
Matt Sarett61eedeb2016-11-04 13:19:48 -0400236 bool premultiply = !this->colorXform() && needs_premul(dstInfo, this->getEncodedInfo());
msarettdcd5e652016-08-22 08:48:40 -0700237
msarett13a91232016-02-01 08:03:29 -0800238 // Choose which function to use to create the color table. If the final destination's
239 // colortype is unpremultiplied, the color table will store unpremultiplied colors.
msarettdcd5e652016-08-22 08:48:40 -0700240 PackColorProc proc = choose_pack_color_proc(premultiply, tableColorType);
msarett13a91232016-02-01 08:03:29 -0800241
242 for (int i = 0; i < numColorsWithAlpha; i++) {
243 // We don't have a function in SkOpts that combines a set of alphas with a set
244 // of RGBs. We could write one, but it's hardly worth it, given that this
245 // is such a small fraction of the total decode time.
msarettdcd5e652016-08-22 08:48:40 -0700246 colorTable[i] = proc(alphas[i], palette->red, palette->green, palette->blue);
msarett13a91232016-02-01 08:03:29 -0800247 palette++;
248 }
scroggof24f2242015-03-03 08:59:20 -0800249 }
250
msarett13a91232016-02-01 08:03:29 -0800251 if (numColorsWithAlpha < numColors) {
252 // The optimized code depends on a 3-byte png_color struct with the colors
253 // in RGB order. These checks make sure it is safe to use.
254 static_assert(3 == sizeof(png_color), "png_color struct has changed. Opts are broken.");
255#ifdef SK_DEBUG
256 SkASSERT(&palette->red < &palette->green);
257 SkASSERT(&palette->green < &palette->blue);
258#endif
259
msarettdcd5e652016-08-22 08:48:40 -0700260 if (is_rgba(tableColorType)) {
261 SkOpts::RGB_to_RGB1(colorTable + numColorsWithAlpha, palette,
msarett34e0ec42016-04-22 16:27:24 -0700262 numColors - numColorsWithAlpha);
263 } else {
msarettdcd5e652016-08-22 08:48:40 -0700264 SkOpts::RGB_to_BGR1(colorTable + numColorsWithAlpha, palette,
msarett34e0ec42016-04-22 16:27:24 -0700265 numColors - numColorsWithAlpha);
266 }
scroggof24f2242015-03-03 08:59:20 -0800267 }
268
msarettdcd5e652016-08-22 08:48:40 -0700269 // If we are not decoding to F16, we can color xform now and store the results
270 // in the color table.
Matt Sarett313c4632016-10-20 12:35:23 -0400271 if (this->colorXform() && kRGBA_F16_SkColorType != dstInfo.colorType()) {
Matt Sarett562e6812016-11-08 16:13:43 -0500272 const SkColorSpaceXform::ColorFormat dstFormat = select_xform_format(dstInfo.colorType());
273 const SkColorSpaceXform::ColorFormat srcFormat = select_xform_format(kXformSrcColorType);
274 const SkAlphaType xformAlphaType = select_xform_alpha(dstInfo.alphaType(),
275 this->getInfo().alphaType());
276 SkAssertResult(this->colorXform()->apply(dstFormat, colorTable, srcFormat, colorTable,
277 numColors, xformAlphaType));
msarettdcd5e652016-08-22 08:48:40 -0700278 }
279
msarett13a91232016-02-01 08:03:29 -0800280 // Pad the color table with the last color in the table (or black) in the case that
281 // invalid pixel indices exceed the number of colors in the table.
282 const int maxColors = 1 << fBitDepth;
283 if (numColors < maxColors) {
msarettdcd5e652016-08-22 08:48:40 -0700284 SkPMColor lastColor = numColors > 0 ? colorTable[numColors - 1] : SK_ColorBLACK;
285 sk_memset32(colorTable + numColors, lastColor, maxColors - numColors);
scroggof24f2242015-03-03 08:59:20 -0800286 }
287
msarett13a91232016-02-01 08:03:29 -0800288 // Set the new color count.
halcanary96fcdcc2015-08-27 07:41:13 -0700289 if (ctableCount != nullptr) {
msarett13a91232016-02-01 08:03:29 -0800290 *ctableCount = maxColors;
scroggof24f2242015-03-03 08:59:20 -0800291 }
292
msarettdcd5e652016-08-22 08:48:40 -0700293 fColorTable.reset(new SkColorTable(colorTable, maxColors));
scroggo05245902015-03-25 11:11:52 -0700294 return true;
scroggof24f2242015-03-03 08:59:20 -0800295}
296
297///////////////////////////////////////////////////////////////////////////////
298// Creation
299///////////////////////////////////////////////////////////////////////////////
300
scroggodb30be22015-12-08 18:54:13 -0800301bool SkPngCodec::IsPng(const char* buf, size_t bytesRead) {
302 return !png_sig_cmp((png_bytep) buf, (png_size_t)0, bytesRead);
scroggof24f2242015-03-03 08:59:20 -0800303}
304
scroggo8e6c7ad2016-09-16 08:20:38 -0700305#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6)
306
msarett6a738212016-03-04 13:27:35 -0800307static float png_fixed_point_to_float(png_fixed_point x) {
308 // We multiply by the same factor that libpng used to convert
309 // fixed point -> double. Since we want floats, we choose to
310 // do the conversion ourselves rather than convert
311 // fixed point -> double -> float.
312 return ((float) x) * 0.00001f;
313}
314
msarett128245c2016-03-30 12:01:47 -0700315static float png_inverted_fixed_point_to_float(png_fixed_point x) {
316 // This is necessary because the gAMA chunk actually stores 1/gamma.
317 return 1.0f / png_fixed_point_to_float(x);
318}
319
msarettc4ce6b52016-06-16 07:37:41 -0700320static constexpr float gSRGB_toXYZD50[] {
brianosmande68d6c2016-09-09 10:36:17 -0700321 0.4358f, 0.3853f, 0.1430f, // Rx, Gx, Bx
322 0.2224f, 0.7170f, 0.0606f, // Ry, Gy, Gz
323 0.0139f, 0.0971f, 0.7139f, // Rz, Gz, Bz
msarettc4ce6b52016-06-16 07:37:41 -0700324};
325
scroggo8e6c7ad2016-09-16 08:20:38 -0700326#endif // LIBPNG >= 1.6
327
msarett6a738212016-03-04 13:27:35 -0800328// Returns a colorSpace object that represents any color space information in
raftiasd737bee2016-12-08 10:53:24 -0500329// the encoded data. If the encoded data contains an invalid/unsupported color space,
330// this will return NULL. If there is no color space information, it will guess sRGB
msarettad8bcfe2016-03-07 07:09:03 -0800331sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) {
msarett6a738212016-03-04 13:27:35 -0800332
msarette2443222016-03-04 14:20:49 -0800333#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6)
334
msarett6a738212016-03-04 13:27:35 -0800335 // First check for an ICC profile
336 png_bytep profile;
337 png_uint_32 length;
338 // The below variables are unused, however, we need to pass them in anyway or
339 // png_get_iCCP() will return nothing.
340 // Could knowing the |name| of the profile ever be interesting? Maybe for debugging?
341 png_charp name;
342 // The |compression| is uninteresting since:
343 // (1) libpng has already decompressed the profile for us.
344 // (2) "deflate" is the only mode of decompression that libpng supports.
345 int compression;
346 if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile,
347 &length)) {
Brian Osman526972e2016-10-24 09:24:02 -0400348 return SkColorSpace::MakeICC(profile, length);
msarett6a738212016-03-04 13:27:35 -0800349 }
350
351 // Second, check for sRGB.
352 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
353
354 // sRGB chunks also store a rendering intent: Absolute, Relative,
355 // Perceptual, and Saturation.
356 // FIXME (msarett): Extract this information from the sRGB chunk once
357 // we are able to handle this information in
358 // SkColorSpace.
Brian Osman526972e2016-10-24 09:24:02 -0400359 return SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
msarett6a738212016-03-04 13:27:35 -0800360 }
361
362 // Next, check for chromaticities.
msaretta5a31dd2016-10-11 09:41:16 -0700363 png_fixed_point chrm[8];
msarett6a738212016-03-04 13:27:35 -0800364 png_fixed_point gamma;
msaretta5a31dd2016-10-11 09:41:16 -0700365 if (png_get_cHRM_fixed(png_ptr, info_ptr, &chrm[0], &chrm[1], &chrm[2], &chrm[3], &chrm[4],
366 &chrm[5], &chrm[6], &chrm[7]))
msarett55447952016-07-22 14:07:23 -0700367 {
msaretta5a31dd2016-10-11 09:41:16 -0700368 SkColorSpacePrimaries primaries;
369 primaries.fRX = png_fixed_point_to_float(chrm[2]);
370 primaries.fRY = png_fixed_point_to_float(chrm[3]);
371 primaries.fGX = png_fixed_point_to_float(chrm[4]);
372 primaries.fGY = png_fixed_point_to_float(chrm[5]);
373 primaries.fBX = png_fixed_point_to_float(chrm[6]);
374 primaries.fBY = png_fixed_point_to_float(chrm[7]);
375 primaries.fWX = png_fixed_point_to_float(chrm[0]);
376 primaries.fWY = png_fixed_point_to_float(chrm[1]);
msarett55447952016-07-22 14:07:23 -0700377
378 SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
msaretta5a31dd2016-10-11 09:41:16 -0700379 if (!primaries.toXYZD50(&toXYZD50)) {
msarett55447952016-07-22 14:07:23 -0700380 toXYZD50.set3x3RowMajorf(gSRGB_toXYZD50);
381 }
msarett6a738212016-03-04 13:27:35 -0800382
msarett128245c2016-03-30 12:01:47 -0700383 if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) {
Matt Sarett99e3f7d2016-10-28 12:51:08 -0400384 SkColorSpaceTransferFn fn;
385 fn.fA = 1.0f;
386 fn.fB = fn.fC = fn.fD = fn.fE = fn.fF = 0.0f;
387 fn.fG = png_inverted_fixed_point_to_float(gamma);
msarettffc2aea2016-05-02 11:12:14 -0700388
Matt Sarett99e3f7d2016-10-28 12:51:08 -0400389 return SkColorSpace::MakeRGB(fn, toXYZD50);
msarett6a738212016-03-04 13:27:35 -0800390 }
msarett128245c2016-03-30 12:01:47 -0700391
msarettc4ce6b52016-06-16 07:37:41 -0700392 // Default to sRGB gamma if the image has color space information,
393 // but does not specify gamma.
Brian Osman526972e2016-10-24 09:24:02 -0400394 return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, toXYZD50);
msarett6a738212016-03-04 13:27:35 -0800395 }
396
397 // Last, check for gamma.
398 if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) {
Matt Sarett99e3f7d2016-10-28 12:51:08 -0400399 SkColorSpaceTransferFn fn;
400 fn.fA = 1.0f;
401 fn.fB = fn.fC = fn.fD = fn.fE = fn.fF = 0.0f;
402 fn.fG = png_inverted_fixed_point_to_float(gamma);
msarett6a738212016-03-04 13:27:35 -0800403
msarettc4ce6b52016-06-16 07:37:41 -0700404 // Since there is no cHRM, we will guess sRGB gamut.
msarett55447952016-07-22 14:07:23 -0700405 SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
406 toXYZD50.set3x3RowMajorf(gSRGB_toXYZD50);
msarettc4ce6b52016-06-16 07:37:41 -0700407
Matt Sarett99e3f7d2016-10-28 12:51:08 -0400408 return SkColorSpace::MakeRGB(fn, toXYZD50);
msarett6a738212016-03-04 13:27:35 -0800409 }
410
msarette2443222016-03-04 14:20:49 -0800411#endif // LIBPNG >= 1.6
412
raftiasd737bee2016-12-08 10:53:24 -0500413 // Report that there is no color space information in the PNG.
414 // Guess sRGB in this case.
415 return SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
msarett6a738212016-03-04 13:27:35 -0800416}
417
msarett400a93b2016-09-01 18:32:52 -0700418void SkPngCodec::allocateStorage(const SkImageInfo& dstInfo) {
419 switch (fXformMode) {
420 case kSwizzleOnly_XformMode:
msarett400a93b2016-09-01 18:32:52 -0700421 break;
422 case kColorOnly_XformMode:
423 // Intentional fall through. A swizzler hasn't been created yet, but one will
424 // be created later if we are sampling. We'll go ahead and allocate
425 // enough memory to swizzle if necessary.
426 case kSwizzleColor_XformMode: {
scroggo8e6c7ad2016-09-16 08:20:38 -0700427 const size_t colorXformBytes = dstInfo.width() * sizeof(uint32_t);
428 fStorage.reset(colorXformBytes);
429 fColorXformSrcRow = (uint32_t*) fStorage.get();
msarett400a93b2016-09-01 18:32:52 -0700430 break;
431 }
432 }
msarettd1ec89b2016-08-03 12:59:27 -0700433}
434
scroggo8e6c7ad2016-09-16 08:20:38 -0700435void SkPngCodec::applyXformRow(void* dst, const void* src) {
Matt Sarett562e6812016-11-08 16:13:43 -0500436 const SkColorSpaceXform::ColorFormat srcColorFormat = select_xform_format(kXformSrcColorType);
msarett400a93b2016-09-01 18:32:52 -0700437 switch (fXformMode) {
438 case kSwizzleOnly_XformMode:
439 fSwizzler->swizzle(dst, (const uint8_t*) src);
440 break;
441 case kColorOnly_XformMode:
Matt Sarett313c4632016-10-20 12:35:23 -0400442 SkAssertResult(this->colorXform()->apply(fXformColorFormat, dst, srcColorFormat, src,
443 fXformWidth, fXformAlphaType));
msarett400a93b2016-09-01 18:32:52 -0700444 break;
445 case kSwizzleColor_XformMode:
446 fSwizzler->swizzle(fColorXformSrcRow, (const uint8_t*) src);
Matt Sarett313c4632016-10-20 12:35:23 -0400447 SkAssertResult(this->colorXform()->apply(fXformColorFormat, dst, srcColorFormat,
448 fColorXformSrcRow, fXformWidth, fXformAlphaType));
msarett400a93b2016-09-01 18:32:52 -0700449 break;
450 }
msarettdcd5e652016-08-22 08:48:40 -0700451}
452
scroggo8e6c7ad2016-09-16 08:20:38 -0700453class SkPngNormalDecoder : public SkPngCodec {
scroggo05245902015-03-25 11:11:52 -0700454public:
scroggo8e6c7ad2016-09-16 08:20:38 -0700455 SkPngNormalDecoder(const SkEncodedInfo& info, const SkImageInfo& imageInfo, SkStream* stream,
456 SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth)
457 : INHERITED(info, imageInfo, stream, reader, png_ptr, info_ptr, bitDepth)
scroggoff9f7bb2016-10-10 11:35:01 -0700458 , fRowsWrittenToOutput(0)
scroggo8e6c7ad2016-09-16 08:20:38 -0700459 , fDst(nullptr)
460 , fRowBytes(0)
461 , fFirstRow(0)
462 , fLastRow(0)
scroggo1c005e42015-08-04 09:24:45 -0700463 {}
464
scroggo8e6c7ad2016-09-16 08:20:38 -0700465 static void AllRowsCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) {
466 GetDecoder(png_ptr)->allRowsCallback(row, rowNum);
scroggo05245902015-03-25 11:11:52 -0700467 }
468
scroggo8e6c7ad2016-09-16 08:20:38 -0700469 static void RowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) {
470 GetDecoder(png_ptr)->rowCallback(row, rowNum);
msarettd1ec89b2016-08-03 12:59:27 -0700471 }
472
scroggo8e6c7ad2016-09-16 08:20:38 -0700473#ifdef SK_GOOGLE3_PNG_HACK
474 static void RereadInfoCallback(png_structp png_ptr, png_infop) {
475 GetDecoder(png_ptr)->rereadInfoCallback();
scroggo05245902015-03-25 11:11:52 -0700476 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700477#endif
emmaleer8f4ba762015-08-14 07:44:46 -0700478
emmaleer0a4c3cb2015-06-22 10:40:21 -0700479private:
scroggoff9f7bb2016-10-10 11:35:01 -0700480 int fRowsWrittenToOutput;
scroggo8e6c7ad2016-09-16 08:20:38 -0700481 void* fDst;
482 size_t fRowBytes;
483
484 // Variables for partial decode
485 int fFirstRow; // FIXME: Move to baseclass?
486 int fLastRow;
scroggo4f2a88c2016-10-17 14:32:41 -0700487 int fRowsNeeded;
emmaleer0a4c3cb2015-06-22 10:40:21 -0700488
scroggo46c57472015-09-30 08:57:13 -0700489 typedef SkPngCodec INHERITED;
scroggo8e6c7ad2016-09-16 08:20:38 -0700490
491 static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) {
492 return static_cast<SkPngNormalDecoder*>(png_get_progressive_ptr(png_ptr));
493 }
494
495 Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
496 const int height = this->getInfo().height();
497 png_progressive_info_ptr callback = nullptr;
498#ifdef SK_GOOGLE3_PNG_HACK
499 callback = RereadInfoCallback;
500#endif
501 png_set_progressive_read_fn(this->png_ptr(), this, callback, AllRowsCallback, nullptr);
502 fDst = dst;
503 fRowBytes = rowBytes;
504
scroggoff9f7bb2016-10-10 11:35:01 -0700505 fRowsWrittenToOutput = 0;
scroggoc46cdd42016-10-10 06:45:32 -0700506 fFirstRow = 0;
507 fLastRow = height - 1;
scroggo8e6c7ad2016-09-16 08:20:38 -0700508
509 this->processData();
510
scroggoff9f7bb2016-10-10 11:35:01 -0700511 if (fRowsWrittenToOutput == height) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700512 return SkCodec::kSuccess;
513 }
514
515 if (rowsDecoded) {
scroggoff9f7bb2016-10-10 11:35:01 -0700516 *rowsDecoded = fRowsWrittenToOutput;
scroggo8e6c7ad2016-09-16 08:20:38 -0700517 }
518
519 return SkCodec::kIncompleteInput;
520 }
521
522 void allRowsCallback(png_bytep row, int rowNum) {
scroggoff9f7bb2016-10-10 11:35:01 -0700523 SkASSERT(rowNum == fRowsWrittenToOutput);
524 fRowsWrittenToOutput++;
scroggo8e6c7ad2016-09-16 08:20:38 -0700525 this->applyXformRow(fDst, row);
526 fDst = SkTAddOffset<void>(fDst, fRowBytes);
527 }
528
529 void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
530 png_progressive_info_ptr callback = nullptr;
531#ifdef SK_GOOGLE3_PNG_HACK
532 callback = RereadInfoCallback;
533#endif
534 png_set_progressive_read_fn(this->png_ptr(), this, callback, RowCallback, nullptr);
535 fFirstRow = firstRow;
536 fLastRow = lastRow;
537 fDst = dst;
538 fRowBytes = rowBytes;
scroggoff9f7bb2016-10-10 11:35:01 -0700539 fRowsWrittenToOutput = 0;
scroggo4f2a88c2016-10-17 14:32:41 -0700540 fRowsNeeded = fLastRow - fFirstRow + 1;
scroggo8e6c7ad2016-09-16 08:20:38 -0700541 }
542
543 SkCodec::Result decode(int* rowsDecoded) override {
scroggo4f2a88c2016-10-17 14:32:41 -0700544 if (this->swizzler()) {
545 const int sampleY = this->swizzler()->sampleY();
546 fRowsNeeded = get_scaled_dimension(fLastRow - fFirstRow + 1, sampleY);
547 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700548 this->processData();
549
scroggo4f2a88c2016-10-17 14:32:41 -0700550 if (fRowsWrittenToOutput == fRowsNeeded) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700551 return SkCodec::kSuccess;
552 }
553
554 if (rowsDecoded) {
scroggoff9f7bb2016-10-10 11:35:01 -0700555 *rowsDecoded = fRowsWrittenToOutput;
scroggo8e6c7ad2016-09-16 08:20:38 -0700556 }
557
558 return SkCodec::kIncompleteInput;
559 }
560
561 void rowCallback(png_bytep row, int rowNum) {
562 if (rowNum < fFirstRow) {
563 // Ignore this row.
564 return;
565 }
566
567 SkASSERT(rowNum <= fLastRow);
scroggo4f2a88c2016-10-17 14:32:41 -0700568 SkASSERT(fRowsWrittenToOutput < fRowsNeeded);
scroggo8e6c7ad2016-09-16 08:20:38 -0700569
570 // If there is no swizzler, all rows are needed.
scroggo4f2a88c2016-10-17 14:32:41 -0700571 if (!this->swizzler() || this->swizzler()->rowNeeded(rowNum - fFirstRow)) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700572 this->applyXformRow(fDst, row);
573 fDst = SkTAddOffset<void>(fDst, fRowBytes);
scroggoff9f7bb2016-10-10 11:35:01 -0700574 fRowsWrittenToOutput++;
scroggo8e6c7ad2016-09-16 08:20:38 -0700575 }
576
scroggo4f2a88c2016-10-17 14:32:41 -0700577 if (fRowsWrittenToOutput == fRowsNeeded) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700578 // Fake error to stop decoding scanlines.
579 longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding);
580 }
581 }
emmaleer0a4c3cb2015-06-22 10:40:21 -0700582};
583
scroggo8e6c7ad2016-09-16 08:20:38 -0700584class SkPngInterlacedDecoder : public SkPngCodec {
585public:
586 SkPngInterlacedDecoder(const SkEncodedInfo& info, const SkImageInfo& imageInfo,
587 SkStream* stream, SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr,
588 int bitDepth, int numberPasses)
589 : INHERITED(info, imageInfo, stream, reader, png_ptr, info_ptr, bitDepth)
590 , fNumberPasses(numberPasses)
591 , fFirstRow(0)
592 , fLastRow(0)
593 , fLinesDecoded(0)
594 , fInterlacedComplete(false)
595 , fPng_rowbytes(0)
596 {}
597
598 static void InterlacedRowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int pass) {
599 auto decoder = static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr));
600 decoder->interlacedRowCallback(row, rowNum, pass);
601 }
602
603#ifdef SK_GOOGLE3_PNG_HACK
604 static void RereadInfoInterlacedCallback(png_structp png_ptr, png_infop) {
605 static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr))->rereadInfoInterlaced();
606 }
607#endif
608
609private:
610 const int fNumberPasses;
611 int fFirstRow;
612 int fLastRow;
613 void* fDst;
614 size_t fRowBytes;
615 int fLinesDecoded;
616 bool fInterlacedComplete;
617 size_t fPng_rowbytes;
618 SkAutoTMalloc<png_byte> fInterlaceBuffer;
619
620 typedef SkPngCodec INHERITED;
621
622#ifdef SK_GOOGLE3_PNG_HACK
623 void rereadInfoInterlaced() {
624 this->rereadInfoCallback();
625 // Note: This allocates more memory than necessary, if we are sampling/subset.
626 this->setUpInterlaceBuffer(this->getInfo().height());
627 }
628#endif
629
630 // FIXME: Currently sharing interlaced callback for all rows and subset. It's not
631 // as expensive as the subset version of non-interlaced, but it still does extra
632 // work.
633 void interlacedRowCallback(png_bytep row, int rowNum, int pass) {
634 if (rowNum < fFirstRow || rowNum > fLastRow) {
635 // Ignore this row
636 return;
637 }
638
639 png_bytep oldRow = fInterlaceBuffer.get() + (rowNum - fFirstRow) * fPng_rowbytes;
640 png_progressive_combine_row(this->png_ptr(), oldRow, row);
641
642 if (0 == pass) {
643 // The first pass initializes all rows.
644 SkASSERT(row);
645 SkASSERT(fLinesDecoded == rowNum - fFirstRow);
646 fLinesDecoded++;
647 } else {
648 SkASSERT(fLinesDecoded == fLastRow - fFirstRow + 1);
649 if (fNumberPasses - 1 == pass && rowNum == fLastRow) {
650 // Last pass, and we have read all of the rows we care about. Note that
651 // we do not care about reading anything beyond the end of the image (or
652 // beyond the last scanline requested).
653 fInterlacedComplete = true;
654 // Fake error to stop decoding scanlines.
655 longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding);
656 }
657 }
658 }
659
660 SkCodec::Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
661 const int height = this->getInfo().height();
662 this->setUpInterlaceBuffer(height);
663 png_progressive_info_ptr callback = nullptr;
664#ifdef SK_GOOGLE3_PNG_HACK
665 callback = RereadInfoInterlacedCallback;
666#endif
667 png_set_progressive_read_fn(this->png_ptr(), this, callback, InterlacedRowCallback,
668 nullptr);
669
670 fFirstRow = 0;
671 fLastRow = height - 1;
672 fLinesDecoded = 0;
673
674 this->processData();
675
676 png_bytep srcRow = fInterlaceBuffer.get();
677 // FIXME: When resuming, this may rewrite rows that did not change.
678 for (int rowNum = 0; rowNum < fLinesDecoded; rowNum++) {
679 this->applyXformRow(dst, srcRow);
680 dst = SkTAddOffset<void>(dst, rowBytes);
681 srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes);
682 }
683 if (fInterlacedComplete) {
684 return SkCodec::kSuccess;
685 }
686
687 if (rowsDecoded) {
688 *rowsDecoded = fLinesDecoded;
689 }
690
691 return SkCodec::kIncompleteInput;
692 }
693
694 void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
695 // FIXME: We could skip rows in the interlace buffer that we won't put in the output.
696 this->setUpInterlaceBuffer(lastRow - firstRow + 1);
697 png_progressive_info_ptr callback = nullptr;
698#ifdef SK_GOOGLE3_PNG_HACK
699 callback = RereadInfoInterlacedCallback;
700#endif
701 png_set_progressive_read_fn(this->png_ptr(), this, callback, InterlacedRowCallback, nullptr);
702 fFirstRow = firstRow;
703 fLastRow = lastRow;
704 fDst = dst;
705 fRowBytes = rowBytes;
706 fLinesDecoded = 0;
707 }
708
709 SkCodec::Result decode(int* rowsDecoded) override {
710 this->processData();
711
712 // Now apply Xforms on all the rows that were decoded.
713 if (!fLinesDecoded) {
scroggoe61b3b42016-10-10 07:17:32 -0700714 if (rowsDecoded) {
715 *rowsDecoded = 0;
716 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700717 return SkCodec::kIncompleteInput;
718 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700719
scroggo4f2a88c2016-10-17 14:32:41 -0700720 const int sampleY = this->swizzler() ? this->swizzler()->sampleY() : 1;
721 const int rowsNeeded = get_scaled_dimension(fLastRow - fFirstRow + 1, sampleY);
scroggoff9f7bb2016-10-10 11:35:01 -0700722 int rowsWrittenToOutput = 0;
723
scroggo8e6c7ad2016-09-16 08:20:38 -0700724 // FIXME: For resuming interlace, we may swizzle a row that hasn't changed. But it
725 // may be too tricky/expensive to handle that correctly.
scroggoe9ea3492016-10-18 09:14:11 -0700726
727 // Offset srcRow by get_start_coord rows. We do not need to account for fFirstRow,
728 // since the first row in fInterlaceBuffer corresponds to fFirstRow.
729 png_bytep srcRow = SkTAddOffset<png_byte>(fInterlaceBuffer.get(),
730 fPng_rowbytes * get_start_coord(sampleY));
scroggo8e6c7ad2016-09-16 08:20:38 -0700731 void* dst = fDst;
scroggoe9ea3492016-10-18 09:14:11 -0700732 for (; rowsWrittenToOutput < rowsNeeded; rowsWrittenToOutput++) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700733 this->applyXformRow(dst, srcRow);
734 dst = SkTAddOffset<void>(dst, fRowBytes);
735 srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes * sampleY);
736 }
737
738 if (fInterlacedComplete) {
739 return SkCodec::kSuccess;
740 }
741
742 if (rowsDecoded) {
scroggoff9f7bb2016-10-10 11:35:01 -0700743 *rowsDecoded = rowsWrittenToOutput;
scroggo8e6c7ad2016-09-16 08:20:38 -0700744 }
745 return SkCodec::kIncompleteInput;
746 }
747
748 void setUpInterlaceBuffer(int height) {
749 fPng_rowbytes = png_get_rowbytes(this->png_ptr(), this->info_ptr());
750 fInterlaceBuffer.reset(fPng_rowbytes * height);
751 fInterlacedComplete = false;
752 }
753};
754
755#ifdef SK_GOOGLE3_PNG_HACK
756bool SkPngCodec::rereadHeaderIfNecessary() {
757 if (!fNeedsToRereadHeader) {
758 return true;
759 }
760
761 // On the first call, we'll need to rewind ourselves. Future calls will
762 // have already rewound in rewindIfNecessary.
763 if (this->stream()->getPosition() > 0) {
764 this->stream()->rewind();
765 }
766
767 this->destroyReadStruct();
768 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
769 sk_error_fn, sk_warning_fn);
770 if (!png_ptr) {
771 return false;
772 }
773
774 // Only use the AutoCleanPng to delete png_ptr as necessary.
775 // (i.e. not for reading bounds etc.)
776 AutoCleanPng autoClean(png_ptr, nullptr, nullptr, nullptr);
777
778 png_infop info_ptr = png_create_info_struct(png_ptr);
779 if (info_ptr == nullptr) {
780 return false;
781 }
782
783 autoClean.setInfoPtr(info_ptr);
784
785#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
786 // Hookup our chunkReader so we can see any user-chunks the caller may be interested in.
787 // This needs to be installed before we read the png header. Android may store ninepatch
788 // chunks in the header.
789 if (fPngChunkReader.get()) {
790 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
791 png_set_read_user_chunk_fn(png_ptr, (png_voidp) fPngChunkReader.get(), sk_read_user_chunk);
792 }
793#endif
794
795 fPng_ptr = png_ptr;
796 fInfo_ptr = info_ptr;
797 autoClean.releasePngPtrs();
798 fNeedsToRereadHeader = false;
799 return true;
800}
801#endif // SK_GOOGLE3_PNG_HACK
802
msarettac6c7502016-04-25 09:30:24 -0700803// Reads the header and initializes the output fields, if not NULL.
804//
805// @param stream Input data. Will be read to get enough information to properly
806// setup the codec.
807// @param chunkReader SkPngChunkReader, for reading unknown chunks. May be NULL.
808// If not NULL, png_ptr will hold an *unowned* pointer to it. The caller is
809// expected to continue to own it for the lifetime of the png_ptr.
810// @param outCodec Optional output variable. If non-NULL, will be set to a new
811// SkPngCodec on success.
812// @param png_ptrp Optional output variable. If non-NULL, will be set to a new
813// png_structp on success.
814// @param info_ptrp Optional output variable. If non-NULL, will be set to a new
815// png_infop on success;
816// @return true on success, in which case the caller is responsible for calling
817// png_destroy_read_struct(png_ptrp, info_ptrp).
818// If it returns false, the passed in fields (except stream) are unchanged.
819static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec** outCodec,
820 png_structp* png_ptrp, png_infop* info_ptrp) {
821 // The image is known to be a PNG. Decode enough to know the SkImageInfo.
822 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
823 sk_error_fn, sk_warning_fn);
824 if (!png_ptr) {
825 return false;
826 }
827
scroggo8e6c7ad2016-09-16 08:20:38 -0700828 AutoCleanPng autoClean(png_ptr, stream, chunkReader, outCodec);
msarettac6c7502016-04-25 09:30:24 -0700829
830 png_infop info_ptr = png_create_info_struct(png_ptr);
831 if (info_ptr == nullptr) {
832 return false;
833 }
834
835 autoClean.setInfoPtr(info_ptr);
836
837 // FIXME: Could we use the return value of setjmp to specify the type of
838 // error?
scroggo8e6c7ad2016-09-16 08:20:38 -0700839 if (setjmp(PNG_JMPBUF(png_ptr))) {
msarettac6c7502016-04-25 09:30:24 -0700840 return false;
841 }
842
msarettac6c7502016-04-25 09:30:24 -0700843#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
844 // Hookup our chunkReader so we can see any user-chunks the caller may be interested in.
845 // This needs to be installed before we read the png header. Android may store ninepatch
846 // chunks in the header.
847 if (chunkReader) {
848 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
849 png_set_read_user_chunk_fn(png_ptr, (png_voidp) chunkReader, sk_read_user_chunk);
850 }
851#endif
852
scroggo8e6c7ad2016-09-16 08:20:38 -0700853 const bool decodedBounds = autoClean.decodeBounds();
854
855 if (!decodedBounds) {
856 return false;
857 }
858
859 // On success, decodeBounds releases ownership of png_ptr and info_ptr.
860 if (png_ptrp) {
861 *png_ptrp = png_ptr;
862 }
863 if (info_ptrp) {
864 *info_ptrp = info_ptr;
865 }
866
867 // decodeBounds takes care of setting outCodec
868 if (outCodec) {
869 SkASSERT(*outCodec);
870 }
871 return true;
872}
873
874// FIXME (scroggo): Once SK_GOOGLE3_PNG_HACK is no more, this method can be inline in
875// AutoCleanPng::infoCallback
876static void general_info_callback(png_structp png_ptr, png_infop info_ptr,
Matt Sarett7a1cc672016-12-14 11:48:31 -0500877 SkEncodedInfo::Color* outColor, SkEncodedInfo::Alpha* outAlpha,
878 int* outBitDepth) {
msarettac6c7502016-04-25 09:30:24 -0700879 png_uint_32 origWidth, origHeight;
880 int bitDepth, encodedColorType;
scroggod8d68552016-06-06 11:26:17 -0700881 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
msarettac6c7502016-04-25 09:30:24 -0700882 &encodedColorType, nullptr, nullptr, nullptr);
883
Matt Sarett7a1cc672016-12-14 11:48:31 -0500884 // TODO: Should we support 16-bits of precision for gray images?
885 if (bitDepth == 16 && (PNG_COLOR_TYPE_GRAY == encodedColorType ||
886 PNG_COLOR_TYPE_GRAY_ALPHA == encodedColorType)) {
887 bitDepth = 8;
scroggod8d68552016-06-06 11:26:17 -0700888 png_set_strip_16(png_ptr);
msarettac6c7502016-04-25 09:30:24 -0700889 }
890
891 // Now determine the default colorType and alphaType and set the required transforms.
892 // Often, we depend on SkSwizzler to perform any transforms that we need. However, we
893 // still depend on libpng for many of the rare and PNG-specific cases.
894 SkEncodedInfo::Color color;
895 SkEncodedInfo::Alpha alpha;
896 switch (encodedColorType) {
897 case PNG_COLOR_TYPE_PALETTE:
898 // Extract multiple pixels with bit depths of 1, 2, and 4 from a single
899 // byte into separate bytes (useful for paletted and grayscale images).
900 if (bitDepth < 8) {
901 // TODO: Should we use SkSwizzler here?
Matt Sarett7a1cc672016-12-14 11:48:31 -0500902 bitDepth = 8;
scroggod8d68552016-06-06 11:26:17 -0700903 png_set_packing(png_ptr);
msarettac6c7502016-04-25 09:30:24 -0700904 }
905
906 color = SkEncodedInfo::kPalette_Color;
907 // Set the alpha depending on if a transparency chunk exists.
scroggod8d68552016-06-06 11:26:17 -0700908 alpha = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ?
msarettac6c7502016-04-25 09:30:24 -0700909 SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha;
910 break;
911 case PNG_COLOR_TYPE_RGB:
scroggod8d68552016-06-06 11:26:17 -0700912 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
msarettac6c7502016-04-25 09:30:24 -0700913 // Convert to RGBA if transparency chunk exists.
scroggod8d68552016-06-06 11:26:17 -0700914 png_set_tRNS_to_alpha(png_ptr);
msarettac6c7502016-04-25 09:30:24 -0700915 color = SkEncodedInfo::kRGBA_Color;
916 alpha = SkEncodedInfo::kBinary_Alpha;
917 } else {
918 color = SkEncodedInfo::kRGB_Color;
919 alpha = SkEncodedInfo::kOpaque_Alpha;
920 }
921 break;
922 case PNG_COLOR_TYPE_GRAY:
923 // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel.
924 if (bitDepth < 8) {
925 // TODO: Should we use SkSwizzler here?
Matt Sarett7a1cc672016-12-14 11:48:31 -0500926 bitDepth = 8;
scroggod8d68552016-06-06 11:26:17 -0700927 png_set_expand_gray_1_2_4_to_8(png_ptr);
msarettac6c7502016-04-25 09:30:24 -0700928 }
929
scroggod8d68552016-06-06 11:26:17 -0700930 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
931 png_set_tRNS_to_alpha(png_ptr);
msarettac6c7502016-04-25 09:30:24 -0700932 color = SkEncodedInfo::kGrayAlpha_Color;
933 alpha = SkEncodedInfo::kBinary_Alpha;
934 } else {
935 color = SkEncodedInfo::kGray_Color;
936 alpha = SkEncodedInfo::kOpaque_Alpha;
937 }
938 break;
939 case PNG_COLOR_TYPE_GRAY_ALPHA:
940 color = SkEncodedInfo::kGrayAlpha_Color;
941 alpha = SkEncodedInfo::kUnpremul_Alpha;
942 break;
943 case PNG_COLOR_TYPE_RGBA:
944 color = SkEncodedInfo::kRGBA_Color;
945 alpha = SkEncodedInfo::kUnpremul_Alpha;
946 break;
947 default:
948 // All the color types have been covered above.
949 SkASSERT(false);
950 color = SkEncodedInfo::kRGBA_Color;
951 alpha = SkEncodedInfo::kUnpremul_Alpha;
952 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700953 if (outColor) {
954 *outColor = color;
scroggo9a89a092016-05-31 13:52:47 -0700955 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700956 if (outAlpha) {
957 *outAlpha = alpha;
scroggod8d68552016-06-06 11:26:17 -0700958 }
Matt Sarett7a1cc672016-12-14 11:48:31 -0500959 if (outBitDepth) {
960 *outBitDepth = bitDepth;
961 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700962}
scroggod8d68552016-06-06 11:26:17 -0700963
scroggo8e6c7ad2016-09-16 08:20:38 -0700964#ifdef SK_GOOGLE3_PNG_HACK
965void SkPngCodec::rereadInfoCallback() {
Matt Sarett7a1cc672016-12-14 11:48:31 -0500966 general_info_callback(fPng_ptr, fInfo_ptr, nullptr, nullptr, nullptr);
scroggo8e6c7ad2016-09-16 08:20:38 -0700967 png_set_interlace_handling(fPng_ptr);
968 png_read_update_info(fPng_ptr, fInfo_ptr);
969}
970#endif
971
972void AutoCleanPng::infoCallback() {
973 SkEncodedInfo::Color color;
974 SkEncodedInfo::Alpha alpha;
Matt Sarett7a1cc672016-12-14 11:48:31 -0500975 int bitDepth;
976 general_info_callback(fPng_ptr, fInfo_ptr, &color, &alpha, &bitDepth);
scroggo8e6c7ad2016-09-16 08:20:38 -0700977
978 const int numberPasses = png_set_interlace_handling(fPng_ptr);
979
980 fReadHeader = true;
981 fDecodedBounds = true;
982#ifndef SK_GOOGLE3_PNG_HACK
983 // 1 tells libpng to save any extra data. We may be able to be more efficient by saving
984 // it ourselves.
985 png_process_data_pause(fPng_ptr, 1);
986#else
987 // Hack to make png_process_data stop.
988 fPng_ptr->buffer_size = 0;
989#endif
990 if (fOutCodec) {
991 SkASSERT(nullptr == *fOutCodec);
992 sk_sp<SkColorSpace> colorSpace = read_color_space(fPng_ptr, fInfo_ptr);
raftiasd737bee2016-12-08 10:53:24 -0500993 const bool unsupportedICC = !colorSpace;
msarettf34cd632016-05-25 10:13:53 -0700994 if (!colorSpace) {
raftiasd737bee2016-12-08 10:53:24 -0500995 // Treat unsupported/invalid color spaces as sRGB.
Brian Osman526972e2016-10-24 09:24:02 -0400996 colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
msarettf34cd632016-05-25 10:13:53 -0700997 }
scroggod8d68552016-06-06 11:26:17 -0700998
Matt Sarett7a1cc672016-12-14 11:48:31 -0500999 SkEncodedInfo encodedInfo = SkEncodedInfo::Make(color, alpha, bitDepth);
scroggo8e6c7ad2016-09-16 08:20:38 -07001000 // FIXME (scroggo): Once we get rid of SK_GOOGLE3_PNG_HACK, general_info_callback can
1001 // be inlined, so these values will already be set.
1002 png_uint_32 origWidth = png_get_image_width(fPng_ptr, fInfo_ptr);
1003 png_uint_32 origHeight = png_get_image_height(fPng_ptr, fInfo_ptr);
1004 png_byte bitDepth = png_get_bit_depth(fPng_ptr, fInfo_ptr);
msarett549ca322016-08-17 08:54:08 -07001005 SkImageInfo imageInfo = encodedInfo.makeImageInfo(origWidth, origHeight, colorSpace);
1006
1007 if (SkEncodedInfo::kOpaque_Alpha == alpha) {
1008 png_color_8p sigBits;
scroggo8e6c7ad2016-09-16 08:20:38 -07001009 if (png_get_sBIT(fPng_ptr, fInfo_ptr, &sigBits)) {
msarett549ca322016-08-17 08:54:08 -07001010 if (5 == sigBits->red && 6 == sigBits->green && 5 == sigBits->blue) {
1011 // Recommend a decode to 565 if the sBIT indicates 565.
1012 imageInfo = imageInfo.makeColorType(kRGB_565_SkColorType);
1013 }
1014 }
1015 }
scroggod8d68552016-06-06 11:26:17 -07001016
msarettac6c7502016-04-25 09:30:24 -07001017 if (1 == numberPasses) {
scroggo8e6c7ad2016-09-16 08:20:38 -07001018 *fOutCodec = new SkPngNormalDecoder(encodedInfo, imageInfo, fStream,
1019 fChunkReader, fPng_ptr, fInfo_ptr, bitDepth);
msarettac6c7502016-04-25 09:30:24 -07001020 } else {
scroggo8e6c7ad2016-09-16 08:20:38 -07001021 *fOutCodec = new SkPngInterlacedDecoder(encodedInfo, imageInfo, fStream,
1022 fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, numberPasses);
msarettac6c7502016-04-25 09:30:24 -07001023 }
raftiasd737bee2016-12-08 10:53:24 -05001024 (*fOutCodec)->setUnsupportedICC(unsupportedICC);
msarettac6c7502016-04-25 09:30:24 -07001025 }
1026
scroggo8e6c7ad2016-09-16 08:20:38 -07001027
1028 // Release the pointers, which are now owned by the codec or the caller is expected to
1029 // take ownership.
1030 this->releasePngPtrs();
msarettac6c7502016-04-25 09:30:24 -07001031}
1032
msarett549ca322016-08-17 08:54:08 -07001033SkPngCodec::SkPngCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imageInfo,
mtklein6dc5b9a2016-08-24 12:22:32 -07001034 SkStream* stream, SkPngChunkReader* chunkReader, void* png_ptr,
scroggo8e6c7ad2016-09-16 08:20:38 -07001035 void* info_ptr, int bitDepth)
msarett549ca322016-08-17 08:54:08 -07001036 : INHERITED(encodedInfo, imageInfo, stream)
msarettac6c7502016-04-25 09:30:24 -07001037 , fPngChunkReader(SkSafeRef(chunkReader))
1038 , fPng_ptr(png_ptr)
1039 , fInfo_ptr(info_ptr)
msarettd1ec89b2016-08-03 12:59:27 -07001040 , fColorXformSrcRow(nullptr)
msarettac6c7502016-04-25 09:30:24 -07001041 , fBitDepth(bitDepth)
scroggo8e6c7ad2016-09-16 08:20:38 -07001042#ifdef SK_GOOGLE3_PNG_HACK
1043 , fNeedsToRereadHeader(true)
1044#endif
msarettac6c7502016-04-25 09:30:24 -07001045{}
1046
1047SkPngCodec::~SkPngCodec() {
1048 this->destroyReadStruct();
1049}
1050
1051void SkPngCodec::destroyReadStruct() {
1052 if (fPng_ptr) {
1053 // We will never have a nullptr fInfo_ptr with a non-nullptr fPng_ptr
1054 SkASSERT(fInfo_ptr);
mtklein6dc5b9a2016-08-24 12:22:32 -07001055 png_destroy_read_struct((png_struct**)&fPng_ptr, (png_info**)&fInfo_ptr, nullptr);
msarettac6c7502016-04-25 09:30:24 -07001056 fPng_ptr = nullptr;
1057 fInfo_ptr = nullptr;
1058 }
1059}
1060
1061///////////////////////////////////////////////////////////////////////////////
1062// Getting the pixels
1063///////////////////////////////////////////////////////////////////////////////
1064
msarettd1ec89b2016-08-03 12:59:27 -07001065bool SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& options,
1066 SkPMColor ctable[], int* ctableCount) {
scroggo8e6c7ad2016-09-16 08:20:38 -07001067 if (setjmp(PNG_JMPBUF((png_struct*)fPng_ptr))) {
msarettd1ec89b2016-08-03 12:59:27 -07001068 SkCodecPrintf("Failed on png_read_update_info.\n");
1069 return false;
msarettac6c7502016-04-25 09:30:24 -07001070 }
1071 png_read_update_info(fPng_ptr, fInfo_ptr);
1072
Matt Sarett313c4632016-10-20 12:35:23 -04001073 // Reset fSwizzler and this->colorXform(). We can't do this in onRewind() because the
msarett400a93b2016-09-01 18:32:52 -07001074 // interlaced scanline decoder may need to rewind.
1075 fSwizzler.reset(nullptr);
msarett400a93b2016-09-01 18:32:52 -07001076
Matt Sarett313c4632016-10-20 12:35:23 -04001077 if (!this->initializeColorXform(dstInfo)) {
1078 return false;
msarett400a93b2016-09-01 18:32:52 -07001079 }
msarettd3317422016-08-22 13:00:05 -07001080
Matt Sarett7a1cc672016-12-14 11:48:31 -05001081 // If the image is 32-bit RGBA and we have a color xform, we can skip the swizzler.
Matt Sarett313c4632016-10-20 12:35:23 -04001082 if (this->colorXform() && SkEncodedInfo::kRGBA_Color == this->getEncodedInfo().color() &&
Matt Sarett7a1cc672016-12-14 11:48:31 -05001083 8 == this->getEncodedInfo().bitsPerComponent() && !options.fSubset)
msarett400a93b2016-09-01 18:32:52 -07001084 {
1085 fXformMode = kColorOnly_XformMode;
1086 return true;
msarettd1ec89b2016-08-03 12:59:27 -07001087 }
1088
msarettac6c7502016-04-25 09:30:24 -07001089 if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) {
msarettdcd5e652016-08-22 08:48:40 -07001090 if (!this->createColorTable(dstInfo, ctableCount)) {
msarettd1ec89b2016-08-03 12:59:27 -07001091 return false;
msarettac6c7502016-04-25 09:30:24 -07001092 }
1093 }
1094
msarett400a93b2016-09-01 18:32:52 -07001095 // Copy the color table to the client if they request kIndex8 mode.
Hal Canary67b39de2016-11-07 11:47:44 -05001096 copy_color_table(dstInfo, fColorTable.get(), ctable, ctableCount);
msarettac6c7502016-04-25 09:30:24 -07001097
msarett400a93b2016-09-01 18:32:52 -07001098 this->initializeSwizzler(dstInfo, options);
1099 return true;
1100}
1101
msarettc0444612016-09-16 11:45:58 -07001102void SkPngCodec::initializeXformParams() {
1103 switch (fXformMode) {
1104 case kColorOnly_XformMode:
1105 fXformColorFormat = select_xform_format(this->dstInfo().colorType());
1106 fXformAlphaType = select_xform_alpha(this->dstInfo().alphaType(),
1107 this->getInfo().alphaType());
1108 fXformWidth = this->dstInfo().width();
1109 break;
1110 case kSwizzleColor_XformMode:
1111 fXformColorFormat = select_xform_format(this->dstInfo().colorType());
1112 fXformAlphaType = select_xform_alpha(this->dstInfo().alphaType(),
1113 this->getInfo().alphaType());
1114 fXformWidth = this->swizzler()->swizzleWidth();
1115 break;
1116 default:
1117 break;
1118 }
scroggo8e6c7ad2016-09-16 08:20:38 -07001119}
1120
msarett400a93b2016-09-01 18:32:52 -07001121void SkPngCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options) {
1122 SkImageInfo swizzlerInfo = dstInfo;
1123 Options swizzlerOptions = options;
1124 fXformMode = kSwizzleOnly_XformMode;
Matt Sarett313c4632016-10-20 12:35:23 -04001125 if (this->colorXform() &&
1126 apply_xform_on_decode(dstInfo.colorType(), this->getEncodedInfo().color()))
1127 {
Matt Sarett562e6812016-11-08 16:13:43 -05001128 swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType);
msarett400a93b2016-09-01 18:32:52 -07001129 if (kPremul_SkAlphaType == dstInfo.alphaType()) {
1130 swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType);
1131 }
1132
1133 fXformMode = kSwizzleColor_XformMode;
1134
1135 // Here, we swizzle into temporary memory, which is not zero initialized.
1136 // FIXME (msarett):
1137 // Is this a problem?
1138 swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized;
1139 }
1140
msarettac6c7502016-04-25 09:30:24 -07001141 const SkPMColor* colors = get_color_ptr(fColorTable.get());
msarettd1ec89b2016-08-03 12:59:27 -07001142 fSwizzler.reset(SkSwizzler::CreateSwizzler(this->getEncodedInfo(), colors, swizzlerInfo,
msarettd3317422016-08-22 13:00:05 -07001143 swizzlerOptions));
msarettac6c7502016-04-25 09:30:24 -07001144 SkASSERT(fSwizzler);
msarett400a93b2016-09-01 18:32:52 -07001145}
1146
1147SkSampler* SkPngCodec::getSampler(bool createIfNecessary) {
1148 if (fSwizzler || !createIfNecessary) {
Ben Wagner145dbcd2016-11-03 14:40:50 -04001149 return fSwizzler.get();
msarett400a93b2016-09-01 18:32:52 -07001150 }
1151
1152 this->initializeSwizzler(this->dstInfo(), this->options());
Ben Wagner145dbcd2016-11-03 14:40:50 -04001153 return fSwizzler.get();
msarettac6c7502016-04-25 09:30:24 -07001154}
1155
msarettac6c7502016-04-25 09:30:24 -07001156bool SkPngCodec::onRewind() {
scroggo8e6c7ad2016-09-16 08:20:38 -07001157#ifdef SK_GOOGLE3_PNG_HACK
1158 fNeedsToRereadHeader = true;
1159 return true;
1160#else
msarettac6c7502016-04-25 09:30:24 -07001161 // This sets fPng_ptr and fInfo_ptr to nullptr. If read_header
1162 // succeeds, they will be repopulated, and if it fails, they will
1163 // remain nullptr. Any future accesses to fPng_ptr and fInfo_ptr will
1164 // come through this function which will rewind and again attempt
1165 // to reinitialize them.
1166 this->destroyReadStruct();
1167
scroggo46c57472015-09-30 08:57:13 -07001168 png_structp png_ptr;
1169 png_infop info_ptr;
msarettac6c7502016-04-25 09:30:24 -07001170 if (!read_header(this->stream(), fPngChunkReader.get(), nullptr, &png_ptr, &info_ptr)) {
1171 return false;
scroggo05245902015-03-25 11:11:52 -07001172 }
1173
msarettac6c7502016-04-25 09:30:24 -07001174 fPng_ptr = png_ptr;
1175 fInfo_ptr = info_ptr;
1176 return true;
scroggo8e6c7ad2016-09-16 08:20:38 -07001177#endif
msarettac6c7502016-04-25 09:30:24 -07001178}
msarett6a738212016-03-04 13:27:35 -08001179
msarettd1ec89b2016-08-03 12:59:27 -07001180SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
1181 size_t rowBytes, const Options& options,
msarettac6c7502016-04-25 09:30:24 -07001182 SkPMColor ctable[], int* ctableCount,
1183 int* rowsDecoded) {
msarett2ecc35f2016-09-08 11:55:16 -07001184 if (!conversion_possible(dstInfo, this->getInfo()) ||
msarettd1ec89b2016-08-03 12:59:27 -07001185 !this->initializeXforms(dstInfo, options, ctable, ctableCount))
1186 {
msarettac6c7502016-04-25 09:30:24 -07001187 return kInvalidConversion;
1188 }
scroggo8e6c7ad2016-09-16 08:20:38 -07001189#ifdef SK_GOOGLE3_PNG_HACK
1190 // Note that this is done after initializeXforms. Otherwise that method
1191 // would not have png_ptr to use.
1192 if (!this->rereadHeaderIfNecessary()) {
1193 return kCouldNotRewind;
1194 }
1195#endif
msarettd1ec89b2016-08-03 12:59:27 -07001196
msarettac6c7502016-04-25 09:30:24 -07001197 if (options.fSubset) {
msarettac6c7502016-04-25 09:30:24 -07001198 return kUnimplemented;
scroggo05245902015-03-25 11:11:52 -07001199 }
1200
msarett400a93b2016-09-01 18:32:52 -07001201 this->allocateStorage(dstInfo);
msarettc0444612016-09-16 11:45:58 -07001202 this->initializeXformParams();
scroggo8e6c7ad2016-09-16 08:20:38 -07001203 return this->decodeAllRows(dst, rowBytes, rowsDecoded);
1204}
msarettac6c7502016-04-25 09:30:24 -07001205
scroggo8e6c7ad2016-09-16 08:20:38 -07001206SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
1207 void* dst, size_t rowBytes, const SkCodec::Options& options,
1208 SkPMColor* ctable, int* ctableCount) {
1209 if (!conversion_possible(dstInfo, this->getInfo()) ||
1210 !this->initializeXforms(dstInfo, options, ctable, ctableCount))
1211 {
1212 return kInvalidConversion;
1213 }
1214#ifdef SK_GOOGLE3_PNG_HACK
1215 // See note in onGetPixels.
1216 if (!this->rereadHeaderIfNecessary()) {
1217 return kCouldNotRewind;
1218 }
1219#endif
1220
1221 this->allocateStorage(dstInfo);
1222
1223 int firstRow, lastRow;
1224 if (options.fSubset) {
1225 firstRow = options.fSubset->top();
1226 lastRow = options.fSubset->bottom() - 1;
1227 } else {
1228 firstRow = 0;
1229 lastRow = dstInfo.height() - 1;
1230 }
1231 this->setRange(firstRow, lastRow, dst, rowBytes);
scroggod8d68552016-06-06 11:26:17 -07001232 return kSuccess;
scroggo6fb23912016-06-02 14:16:43 -07001233}
1234
scroggo8e6c7ad2016-09-16 08:20:38 -07001235SkCodec::Result SkPngCodec::onIncrementalDecode(int* rowsDecoded) {
1236 // FIXME: Only necessary on the first call.
msarettc0444612016-09-16 11:45:58 -07001237 this->initializeXformParams();
scroggo8e6c7ad2016-09-16 08:20:38 -07001238
1239 return this->decode(rowsDecoded);
1240}
1241
msarettf7eb6fc2016-09-13 09:04:11 -07001242uint64_t SkPngCodec::onGetFillValue(const SkImageInfo& dstInfo) const {
msarettac6c7502016-04-25 09:30:24 -07001243 const SkPMColor* colorPtr = get_color_ptr(fColorTable.get());
1244 if (colorPtr) {
msarettc0444612016-09-16 11:45:58 -07001245 SkAlphaType alphaType = select_xform_alpha(dstInfo.alphaType(),
msarettf7eb6fc2016-09-13 09:04:11 -07001246 this->getInfo().alphaType());
1247 return get_color_table_fill_value(dstInfo.colorType(), alphaType, colorPtr, 0,
Matt Sarett313c4632016-10-20 12:35:23 -04001248 this->colorXform());
msarettac6c7502016-04-25 09:30:24 -07001249 }
msarettf7eb6fc2016-09-13 09:04:11 -07001250 return INHERITED::onGetFillValue(dstInfo);
msarettac6c7502016-04-25 09:30:24 -07001251}
1252
1253SkCodec* SkPngCodec::NewFromStream(SkStream* stream, SkPngChunkReader* chunkReader) {
Ben Wagner145dbcd2016-11-03 14:40:50 -04001254 std::unique_ptr<SkStream> streamDeleter(stream);
msarettac6c7502016-04-25 09:30:24 -07001255
scroggo8e6c7ad2016-09-16 08:20:38 -07001256 SkCodec* outCodec = nullptr;
1257 if (read_header(streamDeleter.get(), chunkReader, &outCodec, nullptr, nullptr)) {
msarettac6c7502016-04-25 09:30:24 -07001258 // Codec has taken ownership of the stream.
1259 SkASSERT(outCodec);
1260 streamDeleter.release();
1261 return outCodec;
1262 }
1263
1264 return nullptr;
scroggo05245902015-03-25 11:11:52 -07001265}