pnm2png.c 16.0 KB
Newer Older
1 2
/*
 *  pnm2png.c --- conversion from PBM/PGM/PPM-file to PNG-file
3
 *  copyright (C) 1999-2019 by Willem van Schaik <willem at schaik dot com>
4
 *
5 6
 *  This software is released under the MIT license. For conditions of
 *  distribution and use, see the LICENSE file part of this package.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 */

#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

23
/* make pnm2png verbose so we can find problems (needs to be before png.h) */
24 25 26 27 28 29 30 31
#ifndef PNG_DEBUG
#define PNG_DEBUG 0
#endif

#include "png.h"

/* function prototypes */

32
int main (int argc, char *argv[]);
33
void usage ();
34 35 36
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);
37 38 39 40 41 42 43
png_uint_32 get_data (FILE *pnm_file, int depth);
png_uint_32 get_value (FILE *pnm_file, int depth);

/*
 *  main
 */

44
int main (int argc, char *argv[])
45 46 47 48 49 50 51 52 53 54
{
  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++)
  {
55
    if (argv[argi][0] == '-')
56
    {
57
      switch (argv[argi][1])
58 59 60 61 62 63 64 65 66 67
      {
        case 'i':
          interlace = TRUE;
          break;
        case 'a':
          alpha = TRUE;
          argi++;
          if ((fp_al = fopen (argv[argi], "rb")) == NULL)
          {
            fprintf (stderr, "PNM2PNG\n");
68
            fprintf (stderr, "Error:  alpha-channel file %s does not exist\n",
69
                     argv[argi]);
70 71 72 73 74
            exit (1);
          }
          break;
        case 'h':
        case '?':
75 76
          usage ();
          exit (0);
77 78 79 80
          break;
        default:
          fprintf (stderr, "PNM2PNG\n");
          fprintf (stderr, "Error:  unknown option %s\n", argv[argi]);
81 82
          usage ();
          exit (1);
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
          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");
100
        fprintf (stderr, "Error:  cannot create PNG-file %s\n", argv[argi]);
101 102 103 104 105 106 107
        exit (1);
      }
    }
    else
    {
      fprintf (stderr, "PNM2PNG\n");
      fprintf (stderr, "Error:  too many parameters\n");
108
      usage ();
109 110 111 112
      exit (1);
    }
  } /* end for */

113 114 115 116
#if defined(O_BINARY) && (O_BINARY != 0)
  /* set stdin/stdout to binary,
   * we're reading the PNM always! in binary format
   */
117
  if (fp_rd == stdin)
118
    setmode (fileno (stdin), O_BINARY);
119
  if (fp_wr == stdout)
120
    setmode (fileno (stdout), O_BINARY);
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
#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
 */

146
void usage ()
147 148 149 150 151 152 153
{
  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");
154 155
  fprintf (stderr,
      "   -a[lpha] <file>.pgm read PNG alpha channel as pgm-file\n");
156 157 158 159 160 161 162
  fprintf (stderr, "   -h | -?  print this help-information\n");
}

/*
 *  pnm2png
 */

163 164
BOOL pnm2png (FILE *pnm_file, FILE *png_file, FILE *alpha_file,
              BOOL interlace, BOOL alpha)
165 166 167 168 169 170
{
  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;
171
  volatile png_uint_32 row_bytes;
172 173 174 175 176

  char          type_token[16];
  char          width_token[16];
  char          height_token[16];
  char          maxval_token[16];
177 178 179 180 181 182
  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;
183
  png_uint_32   maxval;
184 185
  volatile int  bit_depth = 0;
  int           channels = 0;
186
  int           alpha_depth = 0;
187
  int           alpha_present = 0;
188 189
  int           row, col;
  BOOL          raw, alpha_raw = FALSE;
190
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
191
  BOOL          packed_bitmap = FALSE;
192
#endif
193 194 195 196 197
  png_uint_32   tmp16;
  int           i;

  /* read header of PNM file */

198
  get_token (pnm_file, type_token, sizeof (type_token));
199 200 201 202 203 204
  if (type_token[0] != 'P')
  {
    return FALSE;
  }
  else if ((type_token[1] == '1') || (type_token[1] == '4'))
  {
205
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
206 207
    raw = (type_token[1] == '4');
    color_type = PNG_COLOR_TYPE_GRAY;
208
    get_token (pnm_file, width_token, sizeof (width_token));
209 210
    sscanf (width_token, "%lu", &ul_width);
    width = (png_uint_32) ul_width;
211
    get_token (pnm_file, height_token, sizeof (height_token));
212 213
    sscanf (height_token, "%lu", &ul_height);
    height = (png_uint_32) ul_height;
214
    bit_depth = 1;
215
    packed_bitmap = TRUE;
216
#else
217
    fprintf (stderr, "PNM2PNG built without PNG_WRITE_INVERT_SUPPORTED and\n");
218
    fprintf (stderr, "PNG_WRITE_PACK_SUPPORTED can't read PBM (P1,P4) files\n");
219
    return FALSE;
220
#endif
221 222 223 224 225
  }
  else if ((type_token[1] == '2') || (type_token[1] == '5'))
  {
    raw = (type_token[1] == '5');
    color_type = PNG_COLOR_TYPE_GRAY;
226
    get_token (pnm_file, width_token, sizeof (width_token));
227 228
    sscanf (width_token, "%lu", &ul_width);
    width = (png_uint_32) ul_width;
229
    get_token (pnm_file, height_token, sizeof (height_token));
230 231
    sscanf (height_token, "%lu", &ul_height);
    height = (png_uint_32) ul_height;
232
    get_token (pnm_file, maxval_token, sizeof (maxval_token));
233 234
    sscanf (maxval_token, "%lu", &ul_maxval);
    maxval = (png_uint_32) ul_maxval;
235

236 237 238 239 240 241 242 243
    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;
244
    else if (maxval <= 65535U)
245
      bit_depth = 16;
246 247
    else /* maxval > 65535U */
      return FALSE;
248 249 250 251 252
  }
  else if ((type_token[1] == '3') || (type_token[1] == '6'))
  {
    raw = (type_token[1] == '6');
    color_type = PNG_COLOR_TYPE_RGB;
253
    get_token (pnm_file, width_token, sizeof (width_token));
254 255
    sscanf (width_token, "%lu", &ul_width);
    width = (png_uint_32) ul_width;
256
    get_token (pnm_file, height_token, sizeof (height_token));
257 258
    sscanf (height_token, "%lu", &ul_height);
    height = (png_uint_32) ul_height;
259
    get_token (pnm_file, maxval_token, sizeof (maxval_token));
260 261
    sscanf (maxval_token, "%lu", &ul_maxval);
    maxval = (png_uint_32) ul_maxval;
262 263 264 265 266 267 268 269
    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;
270
    else if (maxval <= 65535U)
271
      bit_depth = 16;
272 273
    else /* maxval > 65535U */
      return FALSE;
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
  }
  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;

289
    get_token (alpha_file, type_token, sizeof (type_token));
290 291 292 293 294 295 296
    if (type_token[0] != 'P')
    {
      return FALSE;
    }
    else if ((type_token[1] == '2') || (type_token[1] == '5'))
    {
      alpha_raw = (type_token[1] == '5');
297
      get_token (alpha_file, width_token, sizeof (width_token));
298
      sscanf (width_token, "%lu", &ul_alpha_width);
299
      alpha_width = (png_uint_32) ul_alpha_width;
300 301
      if (alpha_width != width)
        return FALSE;
302
      get_token (alpha_file, height_token, sizeof (height_token));
303 304
      sscanf (height_token, "%lu", &ul_alpha_height);
      alpha_height = (png_uint_32) ul_alpha_height;
305 306
      if (alpha_height != height)
        return FALSE;
307
      get_token (alpha_file, maxval_token, sizeof (maxval_token));
308 309
      sscanf (maxval_token, "%lu", &ul_maxval);
      maxval = (png_uint_32) ul_maxval;
310 311 312 313 314 315 316 317
      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;
318
      else if (maxval <= 65535U)
319
        alpha_depth = 16;
320 321
      else /* maxval > 65535U */
        return FALSE;
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
      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;
340
#if 0
341
  else
342 343
    channels = 0; /* cannot happen */
#endif
344 345 346

  alpha_present = (channels - 1) % 2;

347
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
348
  if (packed_bitmap)
349
  {
350 351
    /* row data is as many bytes as can fit width x channels x bit_depth */
    row_bytes = (width * channels * bit_depth + 7) / 8;
352
  }
353
  else
354
#endif
355 356
  {
    /* row_bytes is the width x number of channels x (bit-depth / 8) */
357
    row_bytes = width * channels * ((bit_depth <= 8) ? 1 : 2);
358
  }
359

360 361
  if ((row_bytes == 0) ||
      ((size_t) height > (size_t) (-1) / (size_t) row_bytes))
362
  {
363
    /* too big */
364 365
    return FALSE;
  }
366
  if ((png_pixels = (png_byte *)
367 368 369
       malloc ((size_t) row_bytes * (size_t) height)) == NULL)
  {
    /* out of memory */
370
    return FALSE;
371
  }
372 373 374 375

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

376
  for (row = 0; row < (int) height; row++)
377
  {
378
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
379 380
    if (packed_bitmap)
    {
381
      for (i = 0; i < (int) row_bytes; i++)
382
      {
383 384
        /* png supports this format natively so no conversion is needed */
        *pix_ptr++ = get_data (pnm_file, 8);
385 386 387
      }
    }
    else
388 389
#endif
    {
390
      for (col = 0; col < (int) width; col++)
391
      {
392 393 394
        for (i = 0; i < (channels - alpha_present); i++)
        {
          if (raw)
395
          {
396
            *pix_ptr++ = get_data (pnm_file, bit_depth);
397
          }
398
          else
399
          {
400
            if (bit_depth <= 8)
401
            {
402
              *pix_ptr++ = get_value (pnm_file, bit_depth);
403
            }
404 405 406 407 408 409 410 411
            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++;
            }
412
          }
413 414 415 416 417
        }

        if (alpha) /* read alpha-channel from pgm file */
        {
          if (alpha_raw)
418
          {
419
            *pix_ptr++ = get_data (alpha_file, alpha_depth);
420
          }
421
          else
422
          {
423
            if (alpha_depth <= 8)
424
            {
425
              *pix_ptr++ = get_value (alpha_file, bit_depth);
426
            }
427 428 429 430 431 432
            else
            {
              tmp16 = get_value (alpha_file, bit_depth);
              *pix_ptr++ = (png_byte) ((tmp16 >> 8) & 0xFF);
              *pix_ptr++ = (png_byte) (tmp16 & 0xFF);
            }
433 434 435
          }
        } /* end if alpha */
      } /* end if packed_bitmap */
436 437 438 439
    } /* end for col */
  } /* end for row */

  /* prepare the standard PNG structures */
440 441
  png_ptr = png_create_write_struct (png_get_libpng_ver(NULL),
                                     NULL, NULL, NULL);
442 443
  if (!png_ptr)
  {
444
    free (png_pixels);
445 446 447 448 449
    return FALSE;
  }
  info_ptr = png_create_info_struct (png_ptr);
  if (!info_ptr)
  {
450
    png_destroy_write_struct (&png_ptr, NULL);
451
    free (png_pixels);
452 453 454
    return FALSE;
  }

455
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
456 457 458 459 460
  if (packed_bitmap == TRUE)
  {
    png_set_packing (png_ptr);
    png_set_invert_mono (png_ptr);
  }
461
#endif
462

463
  if (setjmp (png_jmpbuf (png_ptr)))
464
  {
465 466
    png_destroy_write_struct (&png_ptr, &info_ptr);
    free (png_pixels);
467 468 469 470 471 472 473 474
    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,
475 476
                (!interlace) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
                PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
477 478 479 480 481

  /* 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 */
482
  if (row_pointers == NULL)
483
  {
484
    if ((row_pointers = (png_byte **)
485
         malloc (height * sizeof (png_byte *))) == NULL)
486
    {
487 488
      png_destroy_write_struct (&png_ptr, &info_ptr);
      free (png_pixels);
489 490 491 492 493
      return FALSE;
    }
  }

  /* set the individual row_pointers to point at the correct offsets */
494
  for (i = 0; i < (int) height; i++)
495 496 497 498
    row_pointers[i] = png_pixels + i * row_bytes;

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

500
  /* write the additional chunks to the PNG file (not really needed) */
501
  png_write_end (png_ptr, info_ptr);
502 503

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

506
  if (row_pointers != NULL)
507
    free (row_pointers);
508
  if (png_pixels != NULL)
509 510 511 512 513 514
    free (png_pixels);

  return TRUE;
} /* end of pnm2png */

/*
515
 * get_token - gets the first string after whitespace
516 517
 */

518
void get_token (FILE *pnm_file, char *token_buf, size_t token_buf_size)
519
{
520
  size_t i = 0;
521
  int ret;
522

523
  /* remove white-space and comment lines */
524 525
  do
  {
526
    ret = fgetc (pnm_file);
527 528
    if (ret == '#')
    {
529 530 531
      /* the rest of this line is a comment */
      do
      {
532
        ret = fgetc (pnm_file);
533 534 535 536
      }
      while ((ret != '\n') && (ret != '\r') && (ret != EOF));
    }
    if (ret == EOF) break;
537
    token_buf[i] = (char) ret;
538
  }
539
  while ((ret == '\n') || (ret == '\r') || (ret == ' '));
540 541 542 543

  /* read string */
  do
  {
544
    ret = fgetc (pnm_file);
545
    if (ret == EOF) break;
546 547
    if (++i == token_buf_size - 1) break;
    token_buf[i] = (char) ret;
548
  }
549
  while ((ret != '\n') && (ret != '\r') && (ret != ' '));
550

551
  token_buf[i] = '\0';
552 553 554 555 556

  return;
}

/*
557 558 559
 *  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)
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
 */

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;
}

/*
591 592
 *  get_value - takes first (numeric) string and converts into number,
 *              using the bit-depth to fill up a byte (0Ah -> AAh)
593 594 595 596 597
 */

png_uint_32 get_value (FILE *pnm_file, int depth)
{
  static png_uint_32 mask = 0;
598
  char token[16];
599
  unsigned long ul_ret_value;
600 601 602 603 604 605 606
  png_uint_32 ret_value;
  int i = 0;

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

607 608
  get_token (pnm_file, token, sizeof (token));
  sscanf (token, "%lu", &ul_ret_value);
609
  ret_value = (png_uint_32) ul_ret_value;
610 611 612 613 614 615 616 617 618 619 620

  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 */