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