go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

pngdec.c (12143B)


      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 static void PNGAPI error_function(png_structp png, png_const_charp error) {
     40   if (error != NULL) fprintf(stderr, "libpng error: %s\n", error);
     41   longjmp(png_jmpbuf(png), 1);
     42 }
     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 static png_voidp MallocFunc(png_structp png_ptr, LocalPngAllocSize size) {
     51   (void)png_ptr;
     52   if (size != (size_t)size) return NULL;
     53   if (!ImgIoUtilCheckSizeArgumentsOverflow(size, 1)) return NULL;
     54   return (png_voidp)malloc((size_t)size);
     55 }
     56 
     57 static void FreeFunc(png_structp png_ptr, png_voidp ptr) {
     58   (void)png_ptr;
     59   free(ptr);
     60 }
     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                                  size_t expected_length) {
     73   const char* src = hexstring;
     74   size_t actual_length = 0;
     75   uint8_t* const raw_data = (uint8_t*)malloc(expected_length);
     76   uint8_t* dst;
     77 
     78   if (raw_data == NULL) return NULL;
     79 
     80   for (dst = raw_data; actual_length < expected_length && *src != '\0'; ++src) {
     81     char* end;
     82     char val[3];
     83     if (*src == '\n') continue;
     84     val[0] = *src++;
     85     val[1] = *src;
     86     val[2] = '\0';
     87     *dst++ = (uint8_t)strtol(val, &end, 16);
     88     if (end != val + 2) break;
     89     ++actual_length;
     90   }
     91 
     92   if (actual_length != expected_length) {
     93     free(raw_data);
     94     return NULL;
     95   }
     96   return raw_data;
     97 }
     98 
     99 static int ProcessRawProfile(const char* profile, size_t profile_len,
    100                              MetadataPayload* const payload) {
    101   const char* src = profile;
    102   char* end;
    103   int expected_length;
    104 
    105   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   if (*src != '\n') {
    110     fprintf(stderr, "Malformed raw profile, expected '\\n' got '\\x%.2X'\n",
    111             *src);
    112     return 0;
    113   }
    114   ++src;
    115   // skip the profile name and extract the length.
    116   while (*src != '\0' && *src++ != '\n') {}
    117   expected_length = (int)strtol(src, &end, 10);
    118   if (*end != '\n') {
    119     fprintf(stderr, "Malformed raw profile, expected '\\n' got '\\x%.2X'\n",
    120             *end);
    121     return 0;
    122   }
    123   ++end;
    124 
    125   // 'end' now points to the profile payload.
    126   payload->bytes = HexStringToBytes(end, expected_length);
    127   if (payload->bytes == NULL) return 0;
    128   payload->size = expected_length;
    129   return 1;
    130 }
    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                                   Metadata* const metadata) {
    159   int p;
    160 
    161   for (p = 0; p < 2; ++p)  {
    162     png_infop const info = (p == 0) ? head_info : end_info;
    163     png_textp text = NULL;
    164     const png_uint_32 num = png_get_text(png, info, &text, NULL);
    165     png_uint_32 i;
    166 
    167 #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     {
    171       png_bytep exif;
    172       png_uint_32 len;
    173 
    174       if (png_get_eXIf_1(png, info, &len, &exif) == PNG_INFO_eXIf) {
    175         if (!MetadataCopy((const char*)exif, len, &metadata->exif)) return 0;
    176       }
    177     }
    178 #endif  // PNG_eXIf_SUPPORTED
    179 
    180     // Look for EXIF / XMP metadata.
    181     for (i = 0; i < num; ++i, ++text) {
    182       int j;
    183       for (j = 0; kPNGMetadataMap[j].name != NULL; ++j) {
    184         if (!strcmp(text->key, kPNGMetadataMap[j].name)) {
    185           MetadataPayload* const payload =
    186               (MetadataPayload*)((uint8_t*)metadata +
    187                                  kPNGMetadataMap[j].storage_offset);
    188           png_size_t text_length;
    189           switch (text->compression) {
    190 #ifdef PNG_iTXt_SUPPORTED
    191             case PNG_ITXT_COMPRESSION_NONE:
    192             case PNG_ITXT_COMPRESSION_zTXt:
    193               text_length = text->itxt_length;
    194               break;
    195 #endif
    196             case PNG_TEXT_COMPRESSION_NONE:
    197             case PNG_TEXT_COMPRESSION_zTXt:
    198             default:
    199               text_length = text->text_length;
    200               break;
    201           }
    202           if (payload->bytes != NULL) {
    203             fprintf(stderr, "Ignoring additional '%s'\n", text->key);
    204           } else if (!kPNGMetadataMap[j].process(text->text, text_length,
    205                                                  payload)) {
    206             fprintf(stderr, "Failed to process: '%s'\n", text->key);
    207             return 0;
    208           }
    209           break;
    210         }
    211       }
    212     }
    213 #ifdef PNG_iCCP_SUPPORTED
    214     // Look for an ICC profile.
    215     {
    216       png_charp name;
    217       int comp_type;
    218 #if LOCAL_PNG_PREREQ(1,5)
    219       png_bytep profile;
    220 #else
    221       png_charp profile;
    222 #endif
    223       png_uint_32 len;
    224 
    225       if (png_get_iCCP(png, info,
    226                        &name, &comp_type, &profile, &len) == PNG_INFO_iCCP) {
    227         if (!MetadataCopy((const char*)profile, len, &metadata->iccp)) return 0;
    228       }
    229     }
    230 #endif  // PNG_iCCP_SUPPORTED
    231   }
    232   return 1;
    233 }
    234 
    235 typedef struct {
    236   const uint8_t* data;
    237   size_t data_size;
    238   png_size_t offset;
    239 } PNGReadContext;
    240 
    241 static void ReadFunc(png_structp png_ptr, png_bytep data, png_size_t length) {
    242   PNGReadContext* const ctx = (PNGReadContext*)png_get_io_ptr(png_ptr);
    243   if (ctx->data_size - ctx->offset < length) {
    244     png_error(png_ptr, "ReadFunc: invalid read length (overflow)!");
    245   }
    246   memcpy(data, ctx->data + ctx->offset, length);
    247   ctx->offset += length;
    248 }
    249 
    250 int ReadPNG(const uint8_t* const data, size_t data_size,
    251             struct WebPPicture* const pic,
    252             int keep_alpha, struct Metadata* const metadata) {
    253   volatile png_structp png = NULL;
    254   volatile png_infop info = NULL;
    255   volatile png_infop end_info = NULL;
    256   PNGReadContext context = { NULL, 0, 0 };
    257   int color_type, bit_depth, interlaced;
    258   int num_channels;
    259   int num_passes;
    260   int p;
    261   volatile int ok = 0;
    262   png_uint_32 width, height, y;
    263   int64_t stride;
    264   uint8_t* volatile rgb = NULL;
    265 
    266   if (data == NULL || data_size == 0 || pic == NULL) return 0;
    267 
    268   context.data = data;
    269   context.data_size = data_size;
    270 
    271   png = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL,
    272                                  NULL, MallocFunc, FreeFunc);
    273   if (png == NULL) goto End;
    274 
    275   png_set_error_fn(png, 0, error_function, NULL);
    276   if (setjmp(png_jmpbuf(png))) {
    277  Error:
    278     MetadataFree(metadata);
    279     goto End;
    280   }
    281 
    282 #if LOCAL_PNG_PREREQ(1,5) || \
    283     (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   if (data_size > png_get_chunk_malloc_max(png) && data_size < (1u << 24)) {
    287     png_set_chunk_malloc_max(png, data_size);
    288   }
    289 #endif
    290 
    291   info = png_create_info_struct(png);
    292   if (info == NULL) goto Error;
    293   end_info = png_create_info_struct(png);
    294   if (end_info == NULL) goto Error;
    295 
    296   png_set_read_fn(png, &context, ReadFunc);
    297   png_read_info(png, info);
    298   if (!png_get_IHDR(png, info,
    299                     &width, &height, &bit_depth, &color_type, &interlaced,
    300                     NULL, NULL)) goto Error;
    301 
    302   png_set_strip_16(png);
    303   png_set_packing(png);
    304   if (color_type == PNG_COLOR_TYPE_PALETTE) {
    305     png_set_palette_to_rgb(png);
    306   }
    307   if (color_type == PNG_COLOR_TYPE_GRAY ||
    308       color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
    309     if (bit_depth < 8) {
    310       png_set_expand_gray_1_2_4_to_8(png);
    311     }
    312     png_set_gray_to_rgb(png);
    313   }
    314   if (png_get_valid(png, info, PNG_INFO_tRNS)) {
    315     png_set_tRNS_to_alpha(png);
    316   }
    317 
    318   // Apply gamma correction if needed.
    319   {
    320     double image_gamma = 1 / 2.2, screen_gamma = 2.2;
    321     int srgb_intent;
    322     if (png_get_sRGB(png, info, &srgb_intent) ||
    323         png_get_gAMA(png, info, &image_gamma)) {
    324       png_set_gamma(png, screen_gamma, image_gamma);
    325     }
    326   }
    327 
    328   if (!keep_alpha) {
    329     png_set_strip_alpha(png);
    330   }
    331 
    332   num_passes = png_set_interlace_handling(png);
    333   png_read_update_info(png, info);
    334 
    335   num_channels = png_get_channels(png, info);
    336   if (num_channels != 3 && num_channels != 4) {
    337     goto Error;
    338   }
    339   stride = (int64_t)num_channels * width * sizeof(*rgb);
    340   if (stride != (int)stride ||
    341       !ImgIoUtilCheckSizeArgumentsOverflow(stride, height)) {
    342     goto Error;
    343   }
    344 
    345   rgb = (uint8_t*)malloc((size_t)stride * height);
    346   if (rgb == NULL) goto Error;
    347   for (p = 0; p < num_passes; ++p) {
    348     png_bytep row = rgb;
    349     for (y = 0; y < height; ++y) {
    350       png_read_rows(png, &row, NULL, 1);
    351       row += stride;
    352     }
    353   }
    354   png_read_end(png, end_info);
    355 
    356   if (metadata != NULL &&
    357       !ExtractMetadataFromPNG(png, info, end_info, metadata)) {
    358     fprintf(stderr, "Error extracting PNG metadata!\n");
    359     goto Error;
    360   }
    361 
    362   pic->width = (int)width;
    363   pic->height = (int)height;
    364   ok = (num_channels == 4) ? WebPPictureImportRGBA(pic, rgb, (int)stride)
    365                            : WebPPictureImportRGB(pic, rgb, (int)stride);
    366 
    367   if (!ok) {
    368     goto Error;
    369   }
    370 
    371  End:
    372   if (png != NULL) {
    373     png_destroy_read_struct((png_structpp)&png,
    374                             (png_infopp)&info, (png_infopp)&end_info);
    375   }
    376   free(rgb);
    377   return ok;
    378 }
    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 // -----------------------------------------------------------------------------