/src/libwebp/imageio/pngdec.c
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2012 Google Inc. All Rights Reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style license |
4 | | // that can be found in the COPYING file in the root of the source |
5 | | // tree. An additional intellectual property rights grant can be found |
6 | | // in the file PATENTS. All contributing project authors may |
7 | | // be found in the AUTHORS file in the root of the source tree. |
8 | | // ----------------------------------------------------------------------------- |
9 | | // |
10 | | // PNG decode. |
11 | | |
12 | | #include "./pngdec.h" |
13 | | |
14 | | #ifdef HAVE_CONFIG_H |
15 | | #include "webp/config.h" |
16 | | #endif |
17 | | |
18 | | #include <stdio.h> |
19 | | |
20 | | #ifdef WEBP_HAVE_PNG |
21 | | #ifndef PNG_USER_MEM_SUPPORTED |
22 | | #define PNG_USER_MEM_SUPPORTED // for png_create_read_struct_2 |
23 | | #endif |
24 | | #include <png.h> |
25 | | |
26 | | #include <setjmp.h> // note: this must be included *after* png.h |
27 | | #include <stdlib.h> |
28 | | #include <string.h> |
29 | | |
30 | | #include "./imageio_util.h" |
31 | | #include "./metadata.h" |
32 | | #include "webp/encode.h" |
33 | | #include "webp/types.h" |
34 | | |
35 | | #define LOCAL_PNG_VERSION ((PNG_LIBPNG_VER_MAJOR << 8) | PNG_LIBPNG_VER_MINOR) |
36 | | #define LOCAL_PNG_PREREQ(maj, min) \ |
37 | | (LOCAL_PNG_VERSION >= (((maj) << 8) | (min))) |
38 | | |
39 | 117 | static void PNGAPI error_function(png_structp png, png_const_charp error) { |
40 | 117 | if (error != NULL) fprintf(stderr, "libpng error: %s\n", error); |
41 | 117 | longjmp(png_jmpbuf(png), 1); |
42 | 117 | } |
43 | | |
44 | | #if LOCAL_PNG_PREREQ(1,4) |
45 | | typedef png_alloc_size_t LocalPngAllocSize; |
46 | | #else |
47 | | typedef png_size_t LocalPngAllocSize; |
48 | | #endif |
49 | | |
50 | 351 | static png_voidp MallocFunc(png_structp png_ptr, LocalPngAllocSize size) { |
51 | 351 | (void)png_ptr; |
52 | 351 | if (size != (size_t)size) return NULL; |
53 | 351 | if (!ImgIoUtilCheckSizeArgumentsOverflow(size, 1)) return NULL; |
54 | 351 | return (png_voidp)malloc((size_t)size); |
55 | 351 | } |
56 | | |
57 | 351 | static void FreeFunc(png_structp png_ptr, png_voidp ptr) { |
58 | 351 | (void)png_ptr; |
59 | 351 | free(ptr); |
60 | 351 | } |
61 | | |
62 | | // Converts the NULL terminated 'hexstring' which contains 2-byte character |
63 | | // representations of hex values to raw data. |
64 | | // 'hexstring' may contain values consisting of [A-F][a-f][0-9] in pairs, |
65 | | // e.g., 7af2..., separated by any number of newlines. |
66 | | // 'expected_length' is the anticipated processed size. |
67 | | // On success the raw buffer is returned with its length equivalent to |
68 | | // 'expected_length'. NULL is returned if the processed length is less than |
69 | | // 'expected_length' or any character aside from those above is encountered. |
70 | | // The returned buffer must be freed by the caller. |
71 | | static uint8_t* HexStringToBytes(const char* hexstring, |
72 | 0 | size_t expected_length) { |
73 | 0 | const char* src = hexstring; |
74 | 0 | size_t actual_length = 0; |
75 | 0 | uint8_t* const raw_data = (uint8_t*)malloc(expected_length); |
76 | 0 | uint8_t* dst; |
77 | |
|
78 | 0 | if (raw_data == NULL) return NULL; |
79 | | |
80 | 0 | for (dst = raw_data; actual_length < expected_length && *src != '\0'; ++src) { |
81 | 0 | char* end; |
82 | 0 | char val[3]; |
83 | 0 | if (*src == '\n') continue; |
84 | 0 | val[0] = *src++; |
85 | 0 | val[1] = *src; |
86 | 0 | val[2] = '\0'; |
87 | 0 | *dst++ = (uint8_t)strtol(val, &end, 16); |
88 | 0 | if (end != val + 2) break; |
89 | 0 | ++actual_length; |
90 | 0 | } |
91 | |
|
92 | 0 | if (actual_length != expected_length) { |
93 | 0 | free(raw_data); |
94 | 0 | return NULL; |
95 | 0 | } |
96 | 0 | return raw_data; |
97 | 0 | } |
98 | | |
99 | | static int ProcessRawProfile(const char* profile, size_t profile_len, |
100 | 0 | MetadataPayload* const payload) { |
101 | 0 | const char* src = profile; |
102 | 0 | char* end; |
103 | 0 | int expected_length; |
104 | |
|
105 | 0 | if (profile == NULL || profile_len == 0) return 0; |
106 | | |
107 | | // ImageMagick formats 'raw profiles' as |
108 | | // '\n<name>\n<length>(%8lu)\n<hex payload>\n'. |
109 | 0 | if (*src != '\n') { |
110 | 0 | fprintf(stderr, "Malformed raw profile, expected '\\n' got '\\x%.2X'\n", |
111 | 0 | *src); |
112 | 0 | return 0; |
113 | 0 | } |
114 | 0 | ++src; |
115 | | // skip the profile name and extract the length. |
116 | 0 | while (*src != '\0' && *src++ != '\n') {} |
117 | 0 | expected_length = (int)strtol(src, &end, 10); |
118 | 0 | if (*end != '\n') { |
119 | 0 | fprintf(stderr, "Malformed raw profile, expected '\\n' got '\\x%.2X'\n", |
120 | 0 | *end); |
121 | 0 | return 0; |
122 | 0 | } |
123 | 0 | ++end; |
124 | | |
125 | | // 'end' now points to the profile payload. |
126 | 0 | payload->bytes = HexStringToBytes(end, expected_length); |
127 | 0 | if (payload->bytes == NULL) return 0; |
128 | 0 | payload->size = expected_length; |
129 | 0 | return 1; |
130 | 0 | } |
131 | | |
132 | | static const struct { |
133 | | const char* name; |
134 | | int (*process)(const char* profile, size_t profile_len, |
135 | | MetadataPayload* const payload); |
136 | | size_t storage_offset; |
137 | | } kPNGMetadataMap[] = { |
138 | | // https://exiftool.org/TagNames/PNG.html#TextualData |
139 | | // See also: ExifTool on CPAN. |
140 | | { "Raw profile type exif", ProcessRawProfile, METADATA_OFFSET(exif) }, |
141 | | { "Raw profile type xmp", ProcessRawProfile, METADATA_OFFSET(xmp) }, |
142 | | // Exiftool puts exif data in APP1 chunk, too. |
143 | | { "Raw profile type APP1", ProcessRawProfile, METADATA_OFFSET(exif) }, |
144 | | // ImageMagick uses lowercase app1. |
145 | | { "Raw profile type app1", ProcessRawProfile, METADATA_OFFSET(exif) }, |
146 | | // XMP Specification Part 3, Section 3 #PNG |
147 | | { "XML:com.adobe.xmp", MetadataCopy, METADATA_OFFSET(xmp) }, |
148 | | { NULL, NULL, 0 }, |
149 | | }; |
150 | | |
151 | | // Looks for metadata at both the beginning and end of the PNG file, giving |
152 | | // preference to the head. |
153 | | // Returns true on success. The caller must use MetadataFree() on 'metadata' in |
154 | | // all cases. |
155 | | static int ExtractMetadataFromPNG(png_structp png, |
156 | | png_infop const head_info, |
157 | | png_infop const end_info, |
158 | 0 | Metadata* const metadata) { |
159 | 0 | int p; |
160 | |
|
161 | 0 | for (p = 0; p < 2; ++p) { |
162 | 0 | png_infop const info = (p == 0) ? head_info : end_info; |
163 | 0 | png_textp text = NULL; |
164 | 0 | const png_uint_32 num = png_get_text(png, info, &text, NULL); |
165 | 0 | png_uint_32 i; |
166 | |
|
167 | 0 | #ifdef PNG_eXIf_SUPPORTED |
168 | | // Look for an 'eXIf' tag. Preference is given to this tag as it's newer |
169 | | // than the TextualData tags. |
170 | 0 | { |
171 | 0 | png_bytep exif; |
172 | 0 | png_uint_32 len; |
173 | |
|
174 | 0 | if (png_get_eXIf_1(png, info, &len, &exif) == PNG_INFO_eXIf) { |
175 | 0 | if (!MetadataCopy((const char*)exif, len, &metadata->exif)) return 0; |
176 | 0 | } |
177 | 0 | } |
178 | 0 | #endif // PNG_eXIf_SUPPORTED |
179 | | |
180 | | // Look for EXIF / XMP metadata. |
181 | 0 | for (i = 0; i < num; ++i, ++text) { |
182 | 0 | int j; |
183 | 0 | for (j = 0; kPNGMetadataMap[j].name != NULL; ++j) { |
184 | 0 | if (!strcmp(text->key, kPNGMetadataMap[j].name)) { |
185 | 0 | MetadataPayload* const payload = |
186 | 0 | (MetadataPayload*)((uint8_t*)metadata + |
187 | 0 | kPNGMetadataMap[j].storage_offset); |
188 | 0 | png_size_t text_length; |
189 | 0 | switch (text->compression) { |
190 | 0 | #ifdef PNG_iTXt_SUPPORTED |
191 | 0 | case PNG_ITXT_COMPRESSION_NONE: |
192 | 0 | case PNG_ITXT_COMPRESSION_zTXt: |
193 | 0 | text_length = text->itxt_length; |
194 | 0 | break; |
195 | 0 | #endif |
196 | 0 | case PNG_TEXT_COMPRESSION_NONE: |
197 | 0 | case PNG_TEXT_COMPRESSION_zTXt: |
198 | 0 | default: |
199 | 0 | text_length = text->text_length; |
200 | 0 | break; |
201 | 0 | } |
202 | 0 | if (payload->bytes != NULL) { |
203 | 0 | fprintf(stderr, "Ignoring additional '%s'\n", text->key); |
204 | 0 | } else if (!kPNGMetadataMap[j].process(text->text, text_length, |
205 | 0 | payload)) { |
206 | 0 | fprintf(stderr, "Failed to process: '%s'\n", text->key); |
207 | 0 | return 0; |
208 | 0 | } |
209 | 0 | break; |
210 | 0 | } |
211 | 0 | } |
212 | 0 | } |
213 | 0 | #ifdef PNG_iCCP_SUPPORTED |
214 | | // Look for an ICC profile. |
215 | 0 | { |
216 | 0 | png_charp name; |
217 | 0 | int comp_type; |
218 | 0 | #if LOCAL_PNG_PREREQ(1,5) |
219 | 0 | png_bytep profile; |
220 | | #else |
221 | | png_charp profile; |
222 | | #endif |
223 | 0 | png_uint_32 len; |
224 | |
|
225 | 0 | if (png_get_iCCP(png, info, |
226 | 0 | &name, &comp_type, &profile, &len) == PNG_INFO_iCCP) { |
227 | 0 | if (!MetadataCopy((const char*)profile, len, &metadata->iccp)) return 0; |
228 | 0 | } |
229 | 0 | } |
230 | 0 | #endif // PNG_iCCP_SUPPORTED |
231 | 0 | } |
232 | 0 | return 1; |
233 | 0 | } |
234 | | |
235 | | typedef struct { |
236 | | const uint8_t* data; |
237 | | size_t data_size; |
238 | | png_size_t offset; |
239 | | } PNGReadContext; |
240 | | |
241 | 470 | static void ReadFunc(png_structp png_ptr, png_bytep data, png_size_t length) { |
242 | 470 | PNGReadContext* const ctx = (PNGReadContext*)png_get_io_ptr(png_ptr); |
243 | 470 | if (ctx->data_size - ctx->offset < length) { |
244 | 16 | png_error(png_ptr, "ReadFunc: invalid read length (overflow)!"); |
245 | 16 | } |
246 | 454 | memcpy(data, ctx->data + ctx->offset, length); |
247 | 454 | ctx->offset += length; |
248 | 454 | } |
249 | | |
250 | | int ReadPNG(const uint8_t* const data, size_t data_size, |
251 | | struct WebPPicture* const pic, |
252 | 121 | int keep_alpha, struct Metadata* const metadata) { |
253 | 121 | volatile png_structp png = NULL; |
254 | 121 | volatile png_infop info = NULL; |
255 | 121 | volatile png_infop end_info = NULL; |
256 | 121 | PNGReadContext context = { NULL, 0, 0 }; |
257 | 121 | int color_type, bit_depth, interlaced; |
258 | 121 | int num_channels; |
259 | 121 | int num_passes; |
260 | 121 | int p; |
261 | 121 | volatile int ok = 0; |
262 | 121 | png_uint_32 width, height, y; |
263 | 121 | int64_t stride; |
264 | 121 | uint8_t* volatile rgb = NULL; |
265 | | |
266 | 121 | if (data == NULL || data_size == 0 || pic == NULL) return 0; |
267 | | |
268 | 117 | context.data = data; |
269 | 117 | context.data_size = data_size; |
270 | | |
271 | 117 | png = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, |
272 | 117 | NULL, MallocFunc, FreeFunc); |
273 | 117 | if (png == NULL) goto End; |
274 | | |
275 | 117 | png_set_error_fn(png, 0, error_function, NULL); |
276 | 117 | if (setjmp(png_jmpbuf(png))) { |
277 | 117 | Error: |
278 | 117 | MetadataFree(metadata); |
279 | 117 | goto End; |
280 | 117 | } |
281 | | |
282 | 0 | #if LOCAL_PNG_PREREQ(1,5) || \ |
283 | 0 | (LOCAL_PNG_PREREQ(1,4) && PNG_LIBPNG_VER_RELEASE >= 1) |
284 | | // If it looks like the bitstream is going to need more memory than libpng's |
285 | | // internal limit (default: 8M), try to (reasonably) raise it. |
286 | 0 | if (data_size > png_get_chunk_malloc_max(png) && data_size < (1u << 24)) { |
287 | 0 | png_set_chunk_malloc_max(png, data_size); |
288 | 0 | } |
289 | 0 | #endif |
290 | |
|
291 | 0 | info = png_create_info_struct(png); |
292 | 0 | if (info == NULL) goto Error; |
293 | 0 | end_info = png_create_info_struct(png); |
294 | 0 | if (end_info == NULL) goto Error; |
295 | | |
296 | 0 | png_set_read_fn(png, &context, ReadFunc); |
297 | 0 | png_read_info(png, info); |
298 | 0 | if (!png_get_IHDR(png, info, |
299 | 0 | &width, &height, &bit_depth, &color_type, &interlaced, |
300 | 0 | NULL, NULL)) goto Error; |
301 | | |
302 | 0 | png_set_strip_16(png); |
303 | 0 | png_set_packing(png); |
304 | 0 | if (color_type == PNG_COLOR_TYPE_PALETTE) { |
305 | 0 | png_set_palette_to_rgb(png); |
306 | 0 | } |
307 | 0 | if (color_type == PNG_COLOR_TYPE_GRAY || |
308 | 0 | color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { |
309 | 0 | if (bit_depth < 8) { |
310 | 0 | png_set_expand_gray_1_2_4_to_8(png); |
311 | 0 | } |
312 | 0 | png_set_gray_to_rgb(png); |
313 | 0 | } |
314 | 0 | if (png_get_valid(png, info, PNG_INFO_tRNS)) { |
315 | 0 | png_set_tRNS_to_alpha(png); |
316 | 0 | } |
317 | | |
318 | | // Apply gamma correction if needed. |
319 | 0 | { |
320 | 0 | double image_gamma = 1 / 2.2, screen_gamma = 2.2; |
321 | 0 | int srgb_intent; |
322 | 0 | if (png_get_sRGB(png, info, &srgb_intent) || |
323 | 0 | png_get_gAMA(png, info, &image_gamma)) { |
324 | 0 | png_set_gamma(png, screen_gamma, image_gamma); |
325 | 0 | } |
326 | 0 | } |
327 | |
|
328 | 0 | if (!keep_alpha) { |
329 | 0 | png_set_strip_alpha(png); |
330 | 0 | } |
331 | |
|
332 | 0 | num_passes = png_set_interlace_handling(png); |
333 | 0 | png_read_update_info(png, info); |
334 | |
|
335 | 0 | num_channels = png_get_channels(png, info); |
336 | 0 | if (num_channels != 3 && num_channels != 4) { |
337 | 0 | goto Error; |
338 | 0 | } |
339 | 0 | stride = (int64_t)num_channels * width * sizeof(*rgb); |
340 | 0 | if (stride != (int)stride || |
341 | 0 | !ImgIoUtilCheckSizeArgumentsOverflow(stride, height)) { |
342 | 0 | goto Error; |
343 | 0 | } |
344 | | |
345 | 0 | rgb = (uint8_t*)malloc((size_t)stride * height); |
346 | 0 | if (rgb == NULL) goto Error; |
347 | 0 | for (p = 0; p < num_passes; ++p) { |
348 | 0 | png_bytep row = rgb; |
349 | 0 | for (y = 0; y < height; ++y) { |
350 | 0 | png_read_rows(png, &row, NULL, 1); |
351 | 0 | row += stride; |
352 | 0 | } |
353 | 0 | } |
354 | 0 | png_read_end(png, end_info); |
355 | |
|
356 | 0 | if (metadata != NULL && |
357 | 0 | !ExtractMetadataFromPNG(png, info, end_info, metadata)) { |
358 | 0 | fprintf(stderr, "Error extracting PNG metadata!\n"); |
359 | 0 | goto Error; |
360 | 0 | } |
361 | | |
362 | 0 | pic->width = (int)width; |
363 | 0 | pic->height = (int)height; |
364 | 0 | ok = (num_channels == 4) ? WebPPictureImportRGBA(pic, rgb, (int)stride) |
365 | 0 | : WebPPictureImportRGB(pic, rgb, (int)stride); |
366 | |
|
367 | 0 | if (!ok) { |
368 | 0 | goto Error; |
369 | 0 | } |
370 | | |
371 | 117 | End: |
372 | 117 | if (png != NULL) { |
373 | 117 | png_destroy_read_struct((png_structpp)&png, |
374 | 117 | (png_infopp)&info, (png_infopp)&end_info); |
375 | 117 | } |
376 | 117 | free(rgb); |
377 | 117 | return ok; |
378 | 0 | } |
379 | | #else // !WEBP_HAVE_PNG |
380 | | int ReadPNG(const uint8_t* const data, size_t data_size, |
381 | | struct WebPPicture* const pic, |
382 | | int keep_alpha, struct Metadata* const metadata) { |
383 | | (void)data; |
384 | | (void)data_size; |
385 | | (void)pic; |
386 | | (void)keep_alpha; |
387 | | (void)metadata; |
388 | | fprintf(stderr, "PNG support not compiled. Please install the libpng " |
389 | | "development package before building.\n"); |
390 | | return 0; |
391 | | } |
392 | | #endif // WEBP_HAVE_PNG |
393 | | |
394 | | // ----------------------------------------------------------------------------- |