pnm2png.c 16.7 KB
Newer Older
1 2
/*
 *  pnm2png.c --- conversion from PBM/PGM/PPM-file to PNG-file
3
 *  copyright (C) 1999,2015,2017,2018 by Willem van Schaik <willem at schaik.com>
4 5
 *
 *  version 1.0 - 1999.10.15 - First version.
6 7
 *          1.1 - 2015.07.29 - Fixed leaks (Glenn Randers-Pehrson)
 *          1.2 - 2017.04.22 - Add buffer-size check
8 9
 *          1.3 - 2017.08.24 - Fix potential overflow in buffer-size check
 *                             (Glenn Randers-Pehrson)
10
 *          1.4 - 2017.08.28 - Add PNGMINUS_UNUSED (Christian Hesse)
11
 *          1.5 - 2018.08.05 - Fix buffer overflow in tokenizer (Cosmin Truta)
12
 *          1.6 - 2018.08.05 - Improve portability and fix style (Cosmin Truta)
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 *
 *  Permission to use, copy, modify, and distribute this software and
 *  its documentation for any purpose and without fee is hereby granted,
 *  provided that the above copyright notice appear in all copies and
 *  that both that copyright notice and this permission notice appear in
 *  supporting documentation. This software is provided "as is" without
 *  express or implied warranty.
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#ifndef BOOL
#define BOOL unsigned char
#endif
#ifndef TRUE
#define TRUE (BOOL) 1
#endif
#ifndef FALSE
#define FALSE (BOOL) 0
#endif

36
/* make pnm2png verbose so we can find problems (needs to be before png.h) */
37 38 39 40 41 42 43 44
#ifndef PNG_DEBUG
#define PNG_DEBUG 0
#endif

#include "png.h"

/* function prototypes */

45
int main (int argc, char *argv[]);
46
void usage ();
47 48 49
BOOL pnm2png (FILE *pnm_file, FILE *png_file, FILE *alpha_file,
              BOOL interlace, BOOL alpha);
void get_token (FILE *pnm_file, char *token_buf, size_t token_buf_size);
50 51 52 53 54 55 56
png_uint_32 get_data (FILE *pnm_file, int depth);
png_uint_32 get_value (FILE *pnm_file, int depth);

/*
 *  main
 */

57
int main (int argc, char *argv[])
58 59 60 61 62 63 64 65 66 67
{
  FILE *fp_rd = stdin;
  FILE *fp_al = NULL;
  FILE *fp_wr = stdout;
  BOOL interlace = FALSE;
  BOOL alpha = FALSE;
  int argi;

  for (argi = 1; argi < argc; argi++)
  {
68
    if (argv[argi][0] == '-')
69
    {
70
      switch (argv[argi][1])
71 72 73 74 75 76 77 78 79 80
      {
        case 'i':
          interlace = TRUE;
          break;
        case 'a':
          alpha = TRUE;
          argi++;
          if ((fp_al = fopen (argv[argi], "rb")) == NULL)
          {
            fprintf (stderr, "PNM2PNG\n");
81
            fprintf (stderr, "Error:  alpha-channel file %s does not exist\n",
82
                     argv[argi]);
83 84 85 86 87
            exit (1);
          }
          break;
        case 'h':
        case '?':
88 89
          usage ();
          exit (0);
90 91 92 93
          break;
        default:
          fprintf (stderr, "PNM2PNG\n");
          fprintf (stderr, "Error:  unknown option %s\n", argv[argi]);
94 95
          usage ();
          exit (1);
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
          break;
      } /* end switch */
    }
    else if (fp_rd == stdin)
    {
      if ((fp_rd = fopen (argv[argi], "rb")) == NULL)
      {
        fprintf (stderr, "PNM2PNG\n");
        fprintf (stderr, "Error:  file %s does not exist\n", argv[argi]);
        exit (1);
      }
    }
    else if (fp_wr == stdout)
    {
      if ((fp_wr = fopen (argv[argi], "wb")) == NULL)
      {
        fprintf (stderr, "PNM2PNG\n");
113
        fprintf (stderr, "Error:  cannot create PNG-file %s\n", argv[argi]);
114 115 116 117 118 119 120
        exit (1);
      }
    }
    else
    {
      fprintf (stderr, "PNM2PNG\n");
      fprintf (stderr, "Error:  too many parameters\n");
121
      usage ();
122 123 124 125
      exit (1);
    }
  } /* end for */

126 127 128 129
#if defined(O_BINARY) && (O_BINARY != 0)
  /* set stdin/stdout to binary,
   * we're reading the PNM always! in binary format
   */
130
  if (fp_rd == stdin)
131
    setmode (fileno (stdin), O_BINARY);
132
  if (fp_wr == stdout)
133
    setmode (fileno (stdout), O_BINARY);
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
#endif

  /* call the conversion program itself */
  if (pnm2png (fp_rd, fp_wr, fp_al, interlace, alpha) == FALSE)
  {
    fprintf (stderr, "PNM2PNG\n");
    fprintf (stderr, "Error:  unsuccessful converting to PNG-image\n");
    exit (1);
  }

  /* close input file */
  fclose (fp_rd);
  /* close output file */
  fclose (fp_wr);
  /* close alpha file */
  if (alpha)
    fclose (fp_al);

  return 0;
}

/*
 *  usage
 */

159
void usage ()
160 161 162 163 164 165 166
{
  fprintf (stderr, "PNM2PNG\n");
  fprintf (stderr, "   by Willem van Schaik, 1999\n");
  fprintf (stderr, "Usage:  pnm2png [options] <file>.<pnm> [<file>.png]\n");
  fprintf (stderr, "   or:  ... | pnm2png [options]\n");
  fprintf (stderr, "Options:\n");
  fprintf (stderr, "   -i[nterlace]   write png-file with interlacing on\n");
167 168
  fprintf (stderr,
      "   -a[lpha] <file>.pgm read PNG alpha channel as pgm-file\n");
169 170 171 172 173 174 175
  fprintf (stderr, "   -h | -?  print this help-information\n");
}

/*
 *  pnm2png
 */

176 177
BOOL pnm2png (FILE *pnm_file, FILE *png_file, FILE *alpha_file,
              BOOL interlace, BOOL alpha)
178 179 180 181 182 183
{
  png_struct    *png_ptr = NULL;
  png_info      *info_ptr = NULL;
  png_byte      *png_pixels = NULL;
  png_byte      **row_pointers = NULL;
  png_byte      *pix_ptr = NULL;
184
  volatile png_uint_32 row_bytes;
185 186 187 188 189

  char          type_token[16];
  char          width_token[16];
  char          height_token[16];
  char          maxval_token[16];
190 191 192 193 194 195
  volatile int  color_type = 1;
  unsigned long ul_width = 0, ul_alpha_width = 0;
  unsigned long ul_height = 0, ul_alpha_height = 0;
  unsigned long ul_maxval = 0;
  volatile png_uint_32 width = 0, height = 0;
  volatile png_uint_32 alpha_width = 0, alpha_height = 0;
196
  png_uint_32   maxval;
197 198
  volatile int  bit_depth = 0;
  int           channels = 0;
199
  int           alpha_depth = 0;
200
  int           alpha_present = 0;
201 202
  int           row, col;
  BOOL          raw, alpha_raw = FALSE;
203
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
204
  BOOL          packed_bitmap = FALSE;
205
#endif
206 207 208 209 210
  png_uint_32   tmp16;
  int           i;

  /* read header of PNM file */

211
  get_token (pnm_file, type_token, sizeof (type_token));
212 213 214 215 216 217
  if (type_token[0] != 'P')
  {
    return FALSE;
  }
  else if ((type_token[1] == '1') || (type_token[1] == '4'))
  {
218
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
219 220
    raw = (type_token[1] == '4');
    color_type = PNG_COLOR_TYPE_GRAY;
221
    get_token (pnm_file, width_token, sizeof (width_token));
222 223
    sscanf (width_token, "%lu", &ul_width);
    width = (png_uint_32) ul_width;
224
    get_token (pnm_file, height_token, sizeof (height_token));
225 226
    sscanf (height_token, "%lu", &ul_height);
    height = (png_uint_32) ul_height;
227
    bit_depth = 1;
228
    packed_bitmap = TRUE;
229
#else
230
    fprintf (stderr, "PNM2PNG built without PNG_WRITE_INVERT_SUPPORTED and\n");
231
    fprintf (stderr, "PNG_WRITE_PACK_SUPPORTED can't read PBM (P1,P4) files\n");
232
    return FALSE;
233
#endif
234 235 236 237 238
  }
  else if ((type_token[1] == '2') || (type_token[1] == '5'))
  {
    raw = (type_token[1] == '5');
    color_type = PNG_COLOR_TYPE_GRAY;
239
    get_token (pnm_file, width_token, sizeof (width_token));
240 241
    sscanf (width_token, "%lu", &ul_width);
    width = (png_uint_32) ul_width;
242
    get_token (pnm_file, height_token, sizeof (height_token));
243 244
    sscanf (height_token, "%lu", &ul_height);
    height = (png_uint_32) ul_height;
245
    get_token (pnm_file, maxval_token, sizeof (maxval_token));
246 247
    sscanf (maxval_token, "%lu", &ul_maxval);
    maxval = (png_uint_32) ul_maxval;
248

249 250 251 252 253 254 255 256
    if (maxval <= 1)
      bit_depth = 1;
    else if (maxval <= 3)
      bit_depth = 2;
    else if (maxval <= 15)
      bit_depth = 4;
    else if (maxval <= 255)
      bit_depth = 8;
257
    else if (maxval <= 65535U)
258
      bit_depth = 16;
259 260
    else /* maxval > 65535U */
      return FALSE;
261 262 263 264 265
  }
  else if ((type_token[1] == '3') || (type_token[1] == '6'))
  {
    raw = (type_token[1] == '6');
    color_type = PNG_COLOR_TYPE_RGB;
266
    get_token (pnm_file, width_token, sizeof (width_token));
267 268
    sscanf (width_token, "%lu", &ul_width);
    width = (png_uint_32) ul_width;
269
    get_token (pnm_file, height_token, sizeof (height_token));
270 271
    sscanf (height_token, "%lu", &ul_height);
    height = (png_uint_32) ul_height;
272
    get_token (pnm_file, maxval_token, sizeof (maxval_token));
273 274
    sscanf (maxval_token, "%lu", &ul_maxval);
    maxval = (png_uint_32) ul_maxval;
275 276 277 278 279 280 281 282
    if (maxval <= 1)
      bit_depth = 1;
    else if (maxval <= 3)
      bit_depth = 2;
    else if (maxval <= 15)
      bit_depth = 4;
    else if (maxval <= 255)
      bit_depth = 8;
283
    else if (maxval <= 65535U)
284
      bit_depth = 16;
285 286
    else /* maxval > 65535U */
      return FALSE;
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
  }
  else
  {
    return FALSE;
  }

  /* read header of PGM file with alpha channel */

  if (alpha)
  {
    if (color_type == PNG_COLOR_TYPE_GRAY)
      color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
    if (color_type == PNG_COLOR_TYPE_RGB)
      color_type = PNG_COLOR_TYPE_RGB_ALPHA;

302
    get_token (alpha_file, type_token, sizeof (type_token));
303 304 305 306 307 308 309
    if (type_token[0] != 'P')
    {
      return FALSE;
    }
    else if ((type_token[1] == '2') || (type_token[1] == '5'))
    {
      alpha_raw = (type_token[1] == '5');
310
      get_token (alpha_file, width_token, sizeof (width_token));
311
      sscanf (width_token, "%lu", &ul_alpha_width);
312
      alpha_width = (png_uint_32) ul_alpha_width;
313 314
      if (alpha_width != width)
        return FALSE;
315
      get_token (alpha_file, height_token, sizeof (height_token));
316 317
      sscanf (height_token, "%lu", &ul_alpha_height);
      alpha_height = (png_uint_32) ul_alpha_height;
318 319
      if (alpha_height != height)
        return FALSE;
320
      get_token (alpha_file, maxval_token, sizeof (maxval_token));
321 322
      sscanf (maxval_token, "%lu", &ul_maxval);
      maxval = (png_uint_32) ul_maxval;
323 324 325 326 327 328 329 330
      if (maxval <= 1)
        alpha_depth = 1;
      else if (maxval <= 3)
        alpha_depth = 2;
      else if (maxval <= 15)
        alpha_depth = 4;
      else if (maxval <= 255)
        alpha_depth = 8;
331
      else if (maxval <= 65535U)
332
        alpha_depth = 16;
333 334
      else /* maxval > 65535U */
        return FALSE;
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
      if (alpha_depth != bit_depth)
        return FALSE;
    }
    else
    {
      return FALSE;
    }
  } /* end if alpha */

  /* calculate the number of channels and store alpha-presence */
  if (color_type == PNG_COLOR_TYPE_GRAY)
    channels = 1;
  else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
    channels = 2;
  else if (color_type == PNG_COLOR_TYPE_RGB)
    channels = 3;
  else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
    channels = 4;
353
#if 0
354
  else
355 356
    channels = 0; /* cannot happen */
#endif
357 358 359

  alpha_present = (channels - 1) % 2;

360
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
361
  if (packed_bitmap)
362
  {
363 364
    /* row data is as many bytes as can fit width x channels x bit_depth */
    row_bytes = (width * channels * bit_depth + 7) / 8;
365
  }
366
  else
367
#endif
368 369
  {
    /* row_bytes is the width x number of channels x (bit-depth / 8) */
370
    row_bytes = width * channels * ((bit_depth <= 8) ? 1 : 2);
371
  }
372

373 374
  if ((row_bytes == 0) ||
      ((size_t) height > (size_t) (-1) / (size_t) row_bytes))
375
  {
376
    /* too big */
377 378
    return FALSE;
  }
379
  if ((png_pixels = (png_byte *)
380 381 382
       malloc ((size_t) row_bytes * (size_t) height)) == NULL)
  {
    /* out of memory */
383
    return FALSE;
384
  }
385 386 387 388

  /* read data from PNM file */
  pix_ptr = png_pixels;

389
  for (row = 0; row < (int) height; row++)
390
  {
391
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
392 393
    if (packed_bitmap)
    {
394
      for (i = 0; i < (int) row_bytes; i++)
395
      {
396 397
        /* png supports this format natively so no conversion is needed */
        *pix_ptr++ = get_data (pnm_file, 8);
398 399 400
      }
    }
    else
401 402
#endif
    {
403
      for (col = 0; col < (int) width; col++)
404
      {
405 406 407
        for (i = 0; i < (channels - alpha_present); i++)
        {
          if (raw)
408
          {
409
            *pix_ptr++ = get_data (pnm_file, bit_depth);
410
          }
411
          else
412
          {
413
            if (bit_depth <= 8)
414
            {
415
              *pix_ptr++ = get_value (pnm_file, bit_depth);
416
            }
417 418 419 420 421 422 423 424
            else
            {
              tmp16 = get_value (pnm_file, bit_depth);
              *pix_ptr = (png_byte) ((tmp16 >> 8) & 0xFF);
              pix_ptr++;
              *pix_ptr = (png_byte) (tmp16 & 0xFF);
              pix_ptr++;
            }
425
          }
426 427 428 429 430
        }

        if (alpha) /* read alpha-channel from pgm file */
        {
          if (alpha_raw)
431
          {
432
            *pix_ptr++ = get_data (alpha_file, alpha_depth);
433
          }
434
          else
435
          {
436
            if (alpha_depth <= 8)
437
            {
438
              *pix_ptr++ = get_value (alpha_file, bit_depth);
439
            }
440 441 442 443 444 445
            else
            {
              tmp16 = get_value (alpha_file, bit_depth);
              *pix_ptr++ = (png_byte) ((tmp16 >> 8) & 0xFF);
              *pix_ptr++ = (png_byte) (tmp16 & 0xFF);
            }
446 447 448
          }
        } /* end if alpha */
      } /* end if packed_bitmap */
449 450 451 452
    } /* end for col */
  } /* end for row */

  /* prepare the standard PNG structures */
453 454
  png_ptr = png_create_write_struct (png_get_libpng_ver(NULL),
                                     NULL, NULL, NULL);
455 456
  if (!png_ptr)
  {
457
    free (png_pixels);
458 459 460 461 462
    return FALSE;
  }
  info_ptr = png_create_info_struct (png_ptr);
  if (!info_ptr)
  {
463
    png_destroy_write_struct (&png_ptr, NULL);
464
    free (png_pixels);
465 466 467
    return FALSE;
  }

468
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
469 470 471 472 473
  if (packed_bitmap == TRUE)
  {
    png_set_packing (png_ptr);
    png_set_invert_mono (png_ptr);
  }
474
#endif
475

476
  if (setjmp (png_jmpbuf (png_ptr)))
477
  {
478 479
    png_destroy_write_struct (&png_ptr, &info_ptr);
    free (png_pixels);
480 481 482 483 484 485 486 487
    return FALSE;
  }

  /* initialize the png structure */
  png_init_io (png_ptr, png_file);

  /* we're going to write more or less the same PNG as the input file */
  png_set_IHDR (png_ptr, info_ptr, width, height, bit_depth, color_type,
488 489
                (!interlace) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
                PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
490 491 492 493 494

  /* write the file header information */
  png_write_info (png_ptr, info_ptr);

  /* if needed we will allocate memory for an new array of row-pointers */
495
  if (row_pointers == NULL)
496
  {
497
    if ((row_pointers = (png_byte **)
498
         malloc (height * sizeof (png_byte *))) == NULL)
499
    {
500 501
      png_destroy_write_struct (&png_ptr, &info_ptr);
      free (png_pixels);
502 503 504 505 506
      return FALSE;
    }
  }

  /* set the individual row_pointers to point at the correct offsets */
507
  for (i = 0; i < (int) height; i++)
508 509 510 511
    row_pointers[i] = png_pixels + i * row_bytes;

  /* write out the entire image data in one call */
  png_write_image (png_ptr, row_pointers);
512

513
  /* write the additional chunks to the PNG file (not really needed) */
514
  png_write_end (png_ptr, info_ptr);
515 516

  /* clean up after the write, and free any memory allocated */
517
  png_destroy_write_struct (&png_ptr, &info_ptr);
518

519
  if (row_pointers != NULL)
520
    free (row_pointers);
521
  if (png_pixels != NULL)
522 523 524 525 526 527
    free (png_pixels);

  return TRUE;
} /* end of pnm2png */

/*
528
 * get_token - gets the first string after whitespace
529 530
 */

531
void get_token (FILE *pnm_file, char *token_buf, size_t token_buf_size)
532
{
533
  size_t i = 0;
534
  int ret;
535

536
  /* remove white-space and comment lines */
537 538
  do
  {
539
    ret = fgetc (pnm_file);
540 541
    if (ret == '#')
    {
542 543 544
      /* the rest of this line is a comment */
      do
      {
545
        ret = fgetc (pnm_file);
546 547 548 549
      }
      while ((ret != '\n') && (ret != '\r') && (ret != EOF));
    }
    if (ret == EOF) break;
550
    token_buf[i] = (char) ret;
551
  }
552
  while ((ret == '\n') || (ret == '\r') || (ret == ' '));
553 554 555 556

  /* read string */
  do
  {
557
    ret = fgetc (pnm_file);
558
    if (ret == EOF) break;
559 560
    if (++i == token_buf_size - 1) break;
    token_buf[i] = (char) ret;
561
  }
562
  while ((ret != '\n') && (ret != '\r') && (ret != ' '));
563

564
  token_buf[i] = '\0';
565 566 567 568 569

  return;
}

/*
570 571 572
 *  get_data - takes first byte and converts into next pixel value,
 *             taking as much bits as defined by bit-depth and
 *             using the bit-depth to fill up a byte (0Ah -> AAh)
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
 */

png_uint_32 get_data (FILE *pnm_file, int depth)
{
  static int bits_left = 0;
  static int old_value = 0;
  static int mask = 0;
  int i;
  png_uint_32 ret_value;

  if (mask == 0)
    for (i = 0; i < depth; i++)
      mask = (mask >> 1) | 0x80;

  if (bits_left <= 0)
  {
    old_value = fgetc (pnm_file);
    bits_left = 8;
  }

  ret_value = old_value & mask;
  for (i = 1; i < (8 / depth); i++)
    ret_value = ret_value || (ret_value >> depth);

  old_value = (old_value << depth) & 0xFF;
  bits_left -= depth;

  return ret_value;
}

/*
604 605
 *  get_value - takes first (numeric) string and converts into number,
 *              using the bit-depth to fill up a byte (0Ah -> AAh)
606 607 608 609 610
 */

png_uint_32 get_value (FILE *pnm_file, int depth)
{
  static png_uint_32 mask = 0;
611
  char token[16];
612
  unsigned long ul_ret_value;
613 614 615 616 617 618 619
  png_uint_32 ret_value;
  int i = 0;

  if (mask == 0)
    for (i = 0; i < depth; i++)
      mask = (mask << 1) | 0x01;

620 621
  get_token (pnm_file, token, sizeof (token));
  sscanf (token, "%lu", &ul_ret_value);
622
  ret_value = (png_uint_32) ul_ret_value;
623 624 625 626 627 628 629 630 631 632 633

  ret_value &= mask;

  if (depth < 8)
    for (i = 0; i < (8 / depth); i++)
      ret_value = (ret_value << depth) || ret_value;

  return ret_value;
}

/* end of source */