Blame imageio/pnmdec.c

Packit 9c6abc
// Copyright 2017 Google Inc. All Rights Reserved.
Packit 9c6abc
//
Packit 9c6abc
// Use of this source code is governed by a BSD-style license
Packit 9c6abc
// that can be found in the COPYING file in the root of the source
Packit 9c6abc
// tree. An additional intellectual property rights grant can be found
Packit 9c6abc
// in the file PATENTS. All contributing project authors may
Packit 9c6abc
// be found in the AUTHORS file in the root of the source tree.
Packit 9c6abc
// -----------------------------------------------------------------------------
Packit 9c6abc
//
Packit 9c6abc
// (limited) PNM decoder
Packit 9c6abc
Packit 9c6abc
#include "./pnmdec.h"
Packit 9c6abc
Packit 9c6abc
#include <assert.h>
Packit 9c6abc
#include <ctype.h>
Packit 9c6abc
#include <stdio.h>
Packit 9c6abc
#include <stdlib.h>
Packit 9c6abc
#include <string.h>
Packit 9c6abc
Packit 9c6abc
#include "webp/encode.h"
Packit 9c6abc
#include "./imageio_util.h"
Packit 9c6abc
Packit 9c6abc
typedef enum {
Packit 9c6abc
  WIDTH_FLAG      = 1 << 0,
Packit 9c6abc
  HEIGHT_FLAG     = 1 << 1,
Packit 9c6abc
  DEPTH_FLAG      = 1 << 2,
Packit 9c6abc
  MAXVAL_FLAG     = 1 << 3,
Packit 9c6abc
  TUPLE_FLAG      = 1 << 4,
Packit 9c6abc
  ALL_NEEDED_FLAGS = 0x1f
Packit 9c6abc
} PNMFlags;
Packit 9c6abc
Packit 9c6abc
typedef struct {
Packit 9c6abc
  const uint8_t* data;
Packit 9c6abc
  size_t data_size;
Packit 9c6abc
  int width, height;
Packit 9c6abc
  int bytes_per_px;   // 1, 3, 4
Packit 9c6abc
  int depth;
Packit 9c6abc
  int max_value;
Packit 9c6abc
  int type;           // 5, 6 or 7
Packit 9c6abc
  int seen_flags;
Packit 9c6abc
} PNMInfo;
Packit 9c6abc
Packit 9c6abc
// -----------------------------------------------------------------------------
Packit 9c6abc
// PNM decoding
Packit 9c6abc
Packit 9c6abc
#define MAX_LINE_SIZE 1024
Packit 9c6abc
static const size_t kMinPNMHeaderSize = 3;
Packit 9c6abc
Packit 9c6abc
static size_t ReadLine(const uint8_t* const data, size_t off, size_t data_size,
Packit 9c6abc
                       char out[MAX_LINE_SIZE + 1], size_t* const out_size) {
Packit 9c6abc
  size_t i = 0;
Packit 9c6abc
  *out_size = 0;
Packit 9c6abc
 redo:
Packit 9c6abc
  for (i = 0; i < MAX_LINE_SIZE && off < data_size; ++i) {
Packit 9c6abc
    out[i] = data[off++];
Packit 9c6abc
    if (out[i] == '\n') break;
Packit 9c6abc
  }
Packit 9c6abc
  if (off < data_size) {
Packit 9c6abc
    if (i == 0) goto redo;         // empty line
Packit 9c6abc
    if (out[0] == '#') goto redo;  // skip comment
Packit 9c6abc
  }
Packit 9c6abc
  out[i] = 0;   // safety sentinel
Packit 9c6abc
  *out_size = i;
Packit 9c6abc
  return off;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static size_t FlagError(const char flag[]) {
Packit 9c6abc
  fprintf(stderr, "PAM header error: flags '%s' already seen.\n", flag);
Packit 9c6abc
  return 0;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
// inspired from http://netpbm.sourceforge.net/doc/pam.html
Packit 9c6abc
static size_t ReadPAMFields(PNMInfo* const info, size_t off) {
Packit 9c6abc
  char out[MAX_LINE_SIZE + 1];
Packit 9c6abc
  size_t out_size;
Packit 9c6abc
  int tmp;
Packit 9c6abc
  assert(info != NULL);
Packit 9c6abc
  while (1) {
Packit 9c6abc
    off = ReadLine(info->data, off, info->data_size, out, &out_size);
Packit 9c6abc
    if (off == 0) return 0;
Packit 9c6abc
    if (sscanf(out, "WIDTH %d", &tmp) == 1) {
Packit 9c6abc
      if (info->seen_flags & WIDTH_FLAG) return FlagError("WIDTH");
Packit 9c6abc
      info->seen_flags |= WIDTH_FLAG;
Packit 9c6abc
      info->width = tmp;
Packit 9c6abc
    } else if (sscanf(out, "HEIGHT %d", &tmp) == 1) {
Packit 9c6abc
      if (info->seen_flags & HEIGHT_FLAG) return FlagError("HEIGHT");
Packit 9c6abc
      info->seen_flags |= HEIGHT_FLAG;
Packit 9c6abc
      info->height = tmp;
Packit 9c6abc
    } else if (sscanf(out, "DEPTH %d", &tmp) == 1) {
Packit 9c6abc
      if (info->seen_flags & DEPTH_FLAG) return FlagError("DEPTH");
Packit 9c6abc
      info->seen_flags |= DEPTH_FLAG;
Packit 9c6abc
      info->depth = tmp;
Packit 9c6abc
    } else if (sscanf(out, "MAXVAL %d", &tmp) == 1) {
Packit 9c6abc
      if (info->seen_flags & MAXVAL_FLAG) return FlagError("MAXVAL");
Packit 9c6abc
      info->seen_flags |= MAXVAL_FLAG;
Packit 9c6abc
      info->max_value = tmp;
Packit 9c6abc
    } else if (!strcmp(out, "TUPLTYPE RGB_ALPHA")) {
Packit 9c6abc
      info->bytes_per_px = 4;
Packit 9c6abc
      info->seen_flags |= TUPLE_FLAG;
Packit 9c6abc
    } else if (!strcmp(out, "TUPLTYPE RGB")) {
Packit 9c6abc
      info->bytes_per_px = 3;
Packit 9c6abc
      info->seen_flags |= TUPLE_FLAG;
Packit 9c6abc
    } else if (!strcmp(out, "TUPLTYPE GRAYSCALE")) {
Packit 9c6abc
      info->bytes_per_px = 1;
Packit 9c6abc
      info->seen_flags |= TUPLE_FLAG;
Packit 9c6abc
    } else if (!strcmp(out, "ENDHDR")) {
Packit 9c6abc
      break;
Packit 9c6abc
    } else {
Packit 9c6abc
      static const char kEllipsis[] = " ...";
Packit 9c6abc
      int i;
Packit 9c6abc
      if (out_size > 20) sprintf(out + 20 - strlen(kEllipsis), kEllipsis);
Packit 9c6abc
      for (i = 0; i < (int)strlen(out); ++i) {
Packit 9c6abc
        if (!isprint(out[i])) out[i] = ' ';
Packit 9c6abc
      }
Packit 9c6abc
      fprintf(stderr, "PAM header error: unrecognized entry [%s]\n", out);
Packit 9c6abc
      return 0;
Packit 9c6abc
    }
Packit 9c6abc
  }
Packit 9c6abc
  if (!(info->seen_flags & TUPLE_FLAG)) {
Packit 9c6abc
    if (info->depth > 0 && info->depth <= 4 && info->depth != 2) {
Packit 9c6abc
      info->seen_flags |= TUPLE_FLAG;
Packit 9c6abc
      info->bytes_per_px = info->depth * (info->max_value > 255 ? 2 : 1);
Packit 9c6abc
    } else {
Packit 9c6abc
      fprintf(stderr, "PAM: invalid bitdepth (%d).\n", info->depth);
Packit 9c6abc
      return 0;
Packit 9c6abc
    }
Packit 9c6abc
  }
Packit 9c6abc
  if (info->seen_flags != ALL_NEEDED_FLAGS) {
Packit 9c6abc
    fprintf(stderr, "PAM: incomplete header.\n");
Packit 9c6abc
    return 0;
Packit 9c6abc
  }
Packit 9c6abc
  return off;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
static size_t ReadHeader(PNMInfo* const info) {
Packit 9c6abc
  size_t off = 0;
Packit 9c6abc
  char out[MAX_LINE_SIZE + 1];
Packit 9c6abc
  size_t out_size;
Packit 9c6abc
  if (info == NULL) return 0;
Packit 9c6abc
  if (info->data == NULL || info->data_size < kMinPNMHeaderSize) return 0;
Packit 9c6abc
Packit 9c6abc
  info->width = info->height = 0;
Packit 9c6abc
  info->type = -1;
Packit 9c6abc
  info->seen_flags = 0;
Packit 9c6abc
  info->bytes_per_px = 0;
Packit 9c6abc
  info->depth = 0;
Packit 9c6abc
  info->max_value = 0;
Packit 9c6abc
Packit 9c6abc
  off = ReadLine(info->data, off, info->data_size, out, &out_size);
Packit 9c6abc
  if (off == 0 || sscanf(out, "P%d", &info->type) != 1) return 0;
Packit 9c6abc
  if (info->type == 7) {
Packit 9c6abc
    off = ReadPAMFields(info, off);
Packit 9c6abc
  } else {
Packit 9c6abc
    off = ReadLine(info->data, off, info->data_size, out, &out_size);
Packit 9c6abc
    if (off == 0 || sscanf(out, "%d %d", &info->width, &info->height) != 2) {
Packit 9c6abc
      return 0;
Packit 9c6abc
    }
Packit 9c6abc
    off = ReadLine(info->data, off, info->data_size, out, &out_size);
Packit 9c6abc
    if (off == 0 || sscanf(out, "%d", &info->max_value) != 1) return 0;
Packit 9c6abc
Packit 9c6abc
    // finish initializing missing fields
Packit 9c6abc
    info->depth = (info->type == 5) ? 1 : 3;
Packit 9c6abc
    info->bytes_per_px = info->depth * (info->max_value > 255 ? 2 : 1);
Packit 9c6abc
  }
Packit 9c6abc
  // perform some basic numerical validation
Packit 9c6abc
  if (info->width <= 0 || info->height <= 0 ||
Packit 9c6abc
      info->type <= 0 || info->type >= 9 ||
Packit 9c6abc
      info->depth <= 0 || info->depth == 2 || info->depth > 4 ||
Packit 9c6abc
      info->bytes_per_px < info->depth ||
Packit 9c6abc
      info->max_value <= 0 || info->max_value >= 65536) {
Packit 9c6abc
    return 0;
Packit 9c6abc
  }
Packit 9c6abc
  return off;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
int ReadPNM(const uint8_t* const data, size_t data_size,
Packit 9c6abc
            WebPPicture* const pic, int keep_alpha,
Packit 9c6abc
            struct Metadata* const metadata) {
Packit 9c6abc
  int ok = 0;
Packit 9c6abc
  int i, j;
Packit 9c6abc
  uint64_t stride, pixel_bytes;
Packit 9c6abc
  uint8_t* rgb = NULL, *tmp_rgb;
Packit 9c6abc
  size_t offset;
Packit 9c6abc
  PNMInfo info;
Packit 9c6abc
Packit 9c6abc
  info.data = data;
Packit 9c6abc
  info.data_size = data_size;
Packit 9c6abc
  offset = ReadHeader(&info;;
Packit 9c6abc
  if (offset == 0) {
Packit 9c6abc
    fprintf(stderr, "Error parsing PNM header.\n");
Packit 9c6abc
    goto End;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  if (info.type < 5 || info.type > 7) {
Packit 9c6abc
    fprintf(stderr, "Unsupported P%d PNM format.\n", info.type);
Packit 9c6abc
    goto End;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  // Some basic validations.
Packit 9c6abc
  if (pic == NULL) goto End;
Packit 9c6abc
  if (info.width > WEBP_MAX_DIMENSION || info.height > WEBP_MAX_DIMENSION) {
Packit 9c6abc
    fprintf(stderr, "Invalid %dx%d dimension for PNM\n",
Packit 9c6abc
                    info.width, info.height);
Packit 9c6abc
    goto End;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  pixel_bytes = (uint64_t)info.width * info.height * info.bytes_per_px;
Packit 9c6abc
  if (data_size < offset + pixel_bytes) {
Packit 9c6abc
    fprintf(stderr, "Truncated PNM file (P%d).\n", info.type);
Packit 9c6abc
    goto End;
Packit 9c6abc
  }
Packit 9c6abc
  stride =
Packit 9c6abc
      (uint64_t)(info.bytes_per_px < 3 ? 3 : info.bytes_per_px) * info.width;
Packit 9c6abc
  if (stride != (size_t)stride ||
Packit 9c6abc
      !ImgIoUtilCheckSizeArgumentsOverflow(stride, info.height)) {
Packit 9c6abc
    goto End;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  rgb = (uint8_t*)malloc((size_t)stride * info.height);
Packit 9c6abc
  if (rgb == NULL) goto End;
Packit 9c6abc
Packit 9c6abc
  // Convert input
Packit 9c6abc
  tmp_rgb = rgb;
Packit 9c6abc
  for (j = 0; j < info.height; ++j) {
Packit 9c6abc
    assert(offset + info.bytes_per_px * info.width <= data_size);
Packit 9c6abc
    if (info.depth == 1) {
Packit 9c6abc
      // convert grayscale -> RGB
Packit 9c6abc
      for (i = 0; i < info.width; ++i) {
Packit 9c6abc
        const uint8_t v = data[offset + i];
Packit 9c6abc
        tmp_rgb[3 * i + 0] = tmp_rgb[3 * i + 1] = tmp_rgb[3 * i + 2] = v;
Packit 9c6abc
      }
Packit 9c6abc
    } else if (info.depth == 3) {   // RGB
Packit 9c6abc
      memcpy(tmp_rgb, data + offset, 3 * info.width * sizeof(*data));
Packit 9c6abc
    } else if (info.depth == 4) {   // RGBA
Packit 9c6abc
      memcpy(tmp_rgb, data + offset, 4 * info.width * sizeof(*data));
Packit 9c6abc
    }
Packit 9c6abc
    offset += info.bytes_per_px * info.width;
Packit 9c6abc
    tmp_rgb += stride;
Packit 9c6abc
  }
Packit 9c6abc
Packit 9c6abc
  // WebP conversion.
Packit 9c6abc
  pic->width = info.width;
Packit 9c6abc
  pic->height = info.height;
Packit 9c6abc
  ok = (info.depth == 4) ? WebPPictureImportRGBA(pic, rgb, (int)stride)
Packit 9c6abc
                         : WebPPictureImportRGB(pic, rgb, (int)stride);
Packit 9c6abc
  if (!ok) goto End;
Packit 9c6abc
Packit 9c6abc
  ok = 1;
Packit 9c6abc
 End:
Packit 9c6abc
  free((void*)rgb);
Packit 9c6abc
Packit 9c6abc
  (void)metadata;
Packit 9c6abc
  (void)keep_alpha;
Packit 9c6abc
  return ok;
Packit 9c6abc
}
Packit 9c6abc
Packit 9c6abc
// -----------------------------------------------------------------------------