go-libwebp

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

pnmdec.c (9770B)


      1 // Copyright 2017 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 // (limited) PNM decoder
     11 
     12 #include "./pnmdec.h"
     13 
     14 #include <assert.h>
     15 #include <ctype.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 
     20 #include "./imageio_util.h"
     21 #include "webp/encode.h"
     22 #include "webp/types.h"
     23 
     24 #if defined(_MSC_VER) && _MSC_VER < 1900
     25 #define snprintf _snprintf
     26 #endif
     27 
     28 typedef enum {
     29   WIDTH_FLAG      = 1 << 0,
     30   HEIGHT_FLAG     = 1 << 1,
     31   DEPTH_FLAG      = 1 << 2,
     32   MAXVAL_FLAG     = 1 << 3,
     33   TUPLE_FLAG      = 1 << 4,
     34   ALL_NEEDED_FLAGS = WIDTH_FLAG | HEIGHT_FLAG | DEPTH_FLAG | MAXVAL_FLAG
     35 } PNMFlags;
     36 
     37 typedef struct {
     38   const uint8_t* data;
     39   size_t data_size;
     40   int width, height;
     41   int bytes_per_px;
     42   int depth;          // 1 (grayscale), 2 (grayscale + alpha), 3 (rgb), 4 (rgba)
     43   int max_value;
     44   int type;           // 5, 6 or 7
     45   int seen_flags;
     46 } PNMInfo;
     47 
     48 // -----------------------------------------------------------------------------
     49 // PNM decoding
     50 
     51 #define MAX_LINE_SIZE 1024
     52 static const size_t kMinPNMHeaderSize = 3;
     53 
     54 static size_t ReadLine(const uint8_t* const data, size_t off, size_t data_size,
     55                        char out[MAX_LINE_SIZE + 1], size_t* const out_size) {
     56   size_t i = 0;
     57   *out_size = 0;
     58  redo:
     59   for (i = 0; i < MAX_LINE_SIZE && off < data_size; ++i) {
     60     out[i] = data[off++];
     61     if (out[i] == '\n') break;
     62   }
     63   if (off < data_size) {
     64     if (i == 0) goto redo;         // empty line
     65     if (out[0] == '#') goto redo;  // skip comment
     66   }
     67   out[i] = 0;   // safety sentinel
     68   *out_size = i;
     69   return off;
     70 }
     71 
     72 static size_t FlagError(const char flag[]) {
     73   fprintf(stderr, "PAM header error: flags '%s' already seen.\n", flag);
     74   return 0;
     75 }
     76 
     77 // inspired from http://netpbm.sourceforge.net/doc/pam.html
     78 static size_t ReadPAMFields(PNMInfo* const info, size_t off) {
     79   char out[MAX_LINE_SIZE + 1];
     80   size_t out_size;
     81   int tmp;
     82   int expected_depth = -1;
     83   assert(info != NULL);
     84   while (1) {
     85     off = ReadLine(info->data, off, info->data_size, out, &out_size);
     86     if (off == 0) return 0;
     87     if (sscanf(out, "WIDTH %d", &tmp) == 1) {
     88       if (info->seen_flags & WIDTH_FLAG) return FlagError("WIDTH");
     89       info->seen_flags |= WIDTH_FLAG;
     90       info->width = tmp;
     91     } else if (sscanf(out, "HEIGHT %d", &tmp) == 1) {
     92       if (info->seen_flags & HEIGHT_FLAG) return FlagError("HEIGHT");
     93       info->seen_flags |= HEIGHT_FLAG;
     94       info->height = tmp;
     95     } else if (sscanf(out, "DEPTH %d", &tmp) == 1) {
     96       if (info->seen_flags & DEPTH_FLAG) return FlagError("DEPTH");
     97       info->seen_flags |= DEPTH_FLAG;
     98       info->depth = tmp;
     99     } else if (sscanf(out, "MAXVAL %d", &tmp) == 1) {
    100       if (info->seen_flags & MAXVAL_FLAG) return FlagError("MAXVAL");
    101       info->seen_flags |= MAXVAL_FLAG;
    102       info->max_value = tmp;
    103     } else if (!strcmp(out, "TUPLTYPE RGB_ALPHA")) {
    104       expected_depth = 4;
    105       info->seen_flags |= TUPLE_FLAG;
    106     } else if (!strcmp(out, "TUPLTYPE RGB")) {
    107       expected_depth = 3;
    108       info->seen_flags |= TUPLE_FLAG;
    109     } else if (!strcmp(out, "TUPLTYPE GRAYSCALE_ALPHA")) {
    110       expected_depth = 2;
    111       info->seen_flags |= TUPLE_FLAG;
    112     } else if (!strcmp(out, "TUPLTYPE GRAYSCALE")) {
    113       expected_depth = 1;
    114       info->seen_flags |= TUPLE_FLAG;
    115     } else if (!strcmp(out, "ENDHDR")) {
    116       break;
    117     } else {
    118       static const char kEllipsis[] = " ...";
    119       const size_t kLen = strlen(kEllipsis) + 1;  // +1 = trailing \0
    120       int i;
    121       if (out_size > 20) snprintf(out + 20 - kLen, kLen, kEllipsis);
    122       for (i = 0; i < (int)strlen(out); ++i) {
    123         // isprint() might trigger a "char-subscripts" warning if given a char.
    124         if (!isprint((int)out[i])) out[i] = ' ';
    125       }
    126       fprintf(stderr, "PAM header error: unrecognized entry [%s]\n", out);
    127       return 0;
    128     }
    129   }
    130   if (!(info->seen_flags & ALL_NEEDED_FLAGS)) {
    131     fprintf(stderr, "PAM header error: missing tags%s%s%s%s\n",
    132             (info->seen_flags & WIDTH_FLAG) ? "" : " WIDTH",
    133             (info->seen_flags & HEIGHT_FLAG) ? "" : " HEIGHT",
    134             (info->seen_flags & DEPTH_FLAG) ? "" : " DEPTH",
    135             (info->seen_flags & MAXVAL_FLAG) ? "" : " MAXVAL");
    136     return 0;
    137   }
    138   if (expected_depth != -1 && info->depth != expected_depth) {
    139     fprintf(stderr, "PAM header error: expected DEPTH %d but got DEPTH %d\n",
    140             expected_depth, info->depth);
    141     return 0;
    142   }
    143   return off;
    144 }
    145 
    146 static size_t ReadHeader(PNMInfo* const info) {
    147   size_t off = 0;
    148   char out[MAX_LINE_SIZE + 1];
    149   size_t out_size;
    150   if (info == NULL) return 0;
    151   if (info->data == NULL || info->data_size < kMinPNMHeaderSize) return 0;
    152 
    153   info->width = info->height = 0;
    154   info->type = -1;
    155   info->seen_flags = 0;
    156   info->bytes_per_px = 0;
    157   info->depth = 0;
    158   info->max_value = 0;
    159 
    160   off = ReadLine(info->data, off, info->data_size, out, &out_size);
    161   if (off == 0 || sscanf(out, "P%d", &info->type) != 1) return 0;
    162   if (info->type == 7) {
    163     off = ReadPAMFields(info, off);
    164   } else {
    165     off = ReadLine(info->data, off, info->data_size, out, &out_size);
    166     if (off == 0 || sscanf(out, "%d %d", &info->width, &info->height) != 2) {
    167       return 0;
    168     }
    169     off = ReadLine(info->data, off, info->data_size, out, &out_size);
    170     if (off == 0 || sscanf(out, "%d", &info->max_value) != 1) return 0;
    171 
    172     // finish initializing missing fields
    173     info->depth = (info->type == 5) ? 1 : 3;
    174   }
    175   // perform some basic numerical validation
    176   if (info->width <= 0 || info->height <= 0 ||
    177       info->type <= 0 || info->type >= 9 ||
    178       info->depth <= 0 || info->depth > 4 ||
    179       info->max_value <= 0 || info->max_value >= 65536) {
    180     return 0;
    181   }
    182   info->bytes_per_px = info->depth * (info->max_value > 255 ? 2 : 1);
    183   return off;
    184 }
    185 
    186 int ReadPNM(const uint8_t* const data, size_t data_size,
    187             WebPPicture* const pic, int keep_alpha,
    188             struct Metadata* const metadata) {
    189   int ok = 0;
    190   int i, j;
    191   uint64_t stride, pixel_bytes, sample_size, depth;
    192   uint8_t* rgb = NULL, *tmp_rgb;
    193   size_t offset;
    194   PNMInfo info;
    195 
    196   info.data = data;
    197   info.data_size = data_size;
    198   offset = ReadHeader(&info);
    199   if (offset == 0) {
    200     fprintf(stderr, "Error parsing PNM header.\n");
    201     goto End;
    202   }
    203 
    204   if (info.type < 5 || info.type > 7) {
    205     fprintf(stderr, "Unsupported P%d PNM format.\n", info.type);
    206     goto End;
    207   }
    208 
    209   // Some basic validations.
    210   if (pic == NULL) goto End;
    211   if (info.width > WEBP_MAX_DIMENSION || info.height > WEBP_MAX_DIMENSION) {
    212     fprintf(stderr, "Invalid %dx%d dimension for PNM\n",
    213                     info.width, info.height);
    214     goto End;
    215   }
    216 
    217   pixel_bytes = (uint64_t)info.width * info.height * info.bytes_per_px;
    218   if (data_size < offset + pixel_bytes) {
    219     fprintf(stderr, "Truncated PNM file (P%d).\n", info.type);
    220     goto End;
    221   }
    222   sample_size = (info.max_value > 255) ? 2 : 1;
    223   // final depth
    224   depth = (info.depth == 1 || info.depth == 3 || !keep_alpha) ? 3 : 4;
    225   stride = depth * info.width;
    226   if (stride != (size_t)stride ||
    227       !ImgIoUtilCheckSizeArgumentsOverflow(stride, info.height)) {
    228     goto End;
    229   }
    230 
    231   rgb = (uint8_t*)malloc((size_t)stride * info.height);
    232   if (rgb == NULL) goto End;
    233 
    234   // Convert input.
    235   // We only optimize for the sample_size=1, max_value=255, depth=1 case.
    236   tmp_rgb = rgb;
    237   for (j = 0; j < info.height; ++j) {
    238     const uint8_t* in = data + offset;
    239     offset += info.bytes_per_px * info.width;
    240     assert(offset <= data_size);
    241     if (info.max_value == 255 && info.depth >= 3) {
    242       // RGB or RGBA
    243       if (info.depth == 3 || keep_alpha) {
    244         memcpy(tmp_rgb, in, info.depth * info.width * sizeof(*in));
    245       } else {
    246         assert(info.depth == 4 && !keep_alpha);
    247         for (i = 0; i < info.width; ++i) {
    248           tmp_rgb[3 * i + 0] = in[4 * i + 0];
    249           tmp_rgb[3 * i + 1] = in[4 * i + 1];
    250           tmp_rgb[3 * i + 2] = in[4 * i + 2];
    251         }
    252       }
    253     } else {
    254       // Unoptimized case, we need to handle non-trivial operations:
    255       //   * convert 16b to 8b (if max_value > 255)
    256       //   * rescale to [0..255] range (if max_value != 255)
    257       //   * drop the alpha channel (if keep_alpha is false)
    258       const uint32_t round = info.max_value / 2;
    259       int k = 0;
    260       for (i = 0; i < info.width * info.depth; ++i) {
    261         uint32_t v = (sample_size == 2) ? 256u * in[2 * i + 0] + in[2 * i + 1]
    262                    : in[i];
    263         if (info.max_value != 255) v = (v * 255u + round) / info.max_value;
    264         if (v > 255u) v = 255u;
    265         if (info.depth > 2) {
    266           if (!keep_alpha && info.depth == 4 && (i % 4) == 3) {
    267             // skip alpha
    268           } else {
    269             tmp_rgb[k] = v;
    270             k += 1;
    271           }
    272         } else if (info.depth == 1 || (i % 2) == 0) {
    273           tmp_rgb[k + 0] = tmp_rgb[k + 1] = tmp_rgb[k + 2] = v;
    274           k += 3;
    275         } else if (keep_alpha && info.depth == 2) {
    276           tmp_rgb[k] = v;
    277           k += 1;
    278         } else {
    279           // skip alpha
    280         }
    281       }
    282     }
    283     tmp_rgb += stride;
    284   }
    285 
    286   // WebP conversion.
    287   pic->width = info.width;
    288   pic->height = info.height;
    289   ok = (depth == 4) ? WebPPictureImportRGBA(pic, rgb, (int)stride)
    290                     : WebPPictureImportRGB(pic, rgb, (int)stride);
    291   if (!ok) goto End;
    292 
    293   ok = 1;
    294  End:
    295   free((void*)rgb);
    296 
    297   (void)metadata;
    298   (void)keep_alpha;
    299   return ok;
    300 }
    301 
    302 // -----------------------------------------------------------------------------