libpng/contrib/pngminus/pnm2png.c
Cosmin Truta bdbbcaa457 pngminus: Improve and modernize the PNG processing
Improve and modernize png2pnm.c:

 * Remove the explicit reading of the input PNG file signature.
   Libpng is now able to read it, check it, and issue an appropriate
   error message in case of magic number mismatch or file corruption.
   (See the function `png_read_sig`.)

 * Remove the explicit allocation and dealocation of the image data.
   Libpng is now able to manage all the image data automatically.
   (See the function `png_read_png`.)

 * Specify the needed image transformations without a-priori checking
   the image type for applicability.

 * Use the `png_set_expand_gray_1_2_4_to_8` transformation.
   Since libpng version 1.2.9, this transformation (if needed) must
   be enabled separately from `png_set_expand`.

Improve and modernize pnm2png.c:

 * Modify the allocation of image data, in order to match libpng's
   internal allocation model.

 * Transfer the ownership of the image data from the `pnm2png` function
   to libpng, which will manage and dealocate it at the right time.
   (See the functions `png_set_image_rows` and `png_data_freer`.)

Refactor, clean up, etc.
2024-01-08 20:31:18 +02:00

672 lines
18 KiB
C

/*
* pnm2png.c --- conversion from PBM/PGM/PPM-file to PNG-file
* copyright (C) 1999-2019 by Willem van Schaik <willem at schaik dot com>
*
* This software is released under the MIT license. For conditions of
* distribution and use, see the LICENSE file part of this package.
*/
#include <limits.h>
#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
#include "png.h"
/* function prototypes */
int main (int argc, char *argv[]);
void usage ();
BOOL pnm2png (FILE *pnm_file, FILE *png_file, FILE *alpha_file,
BOOL interlace, BOOL alpha);
BOOL pnm2png_internal (png_struct *png_ptr, png_info *info_ptr,
FILE *pnm_file, FILE *alpha_file,
BOOL interlace, BOOL alpha);
int fscan_pnm_magic (FILE *pnm_file, char *magic_buf, size_t magic_buf_size);
int fscan_pnm_token (FILE *pnm_file, char *token_buf, size_t token_buf_size);
int fscan_pnm_uint_32 (FILE *pnm_file, png_uint_32 *num_ptr);
png_uint_32 get_pnm_data (FILE *pnm_file, int depth);
png_uint_32 get_pnm_value (FILE *pnm_file, int depth);
/*
* main
*/
int main (int argc, char *argv[])
{
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++)
{
if (argv[argi][0] == '-')
{
switch (argv[argi][1])
{
case 'i':
interlace = TRUE;
break;
case 'a':
alpha = TRUE;
argi++;
if ((fp_al = fopen (argv[argi], "rb")) == NULL)
{
fprintf (stderr, "PNM2PNG\n");
fprintf (stderr, "Error: alpha-channel file %s does not exist\n",
argv[argi]);
exit (1);
}
break;
case 'h':
case '?':
usage ();
exit (0);
break;
default:
fprintf (stderr, "PNM2PNG\n");
fprintf (stderr, "Error: unknown option %s\n", argv[argi]);
usage ();
exit (1);
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");
fprintf (stderr, "Error: cannot create PNG-file %s\n", argv[argi]);
exit (1);
}
}
else
{
fprintf (stderr, "PNM2PNG\n");
fprintf (stderr, "Error: too many parameters\n");
usage ();
exit (1);
}
} /* end for */
#if defined(O_BINARY) && (O_BINARY != 0)
/* set stdin/stdout to binary,
* we're reading the PNM always! in binary format
*/
if (fp_rd == stdin)
setmode (fileno (stdin), O_BINARY);
if (fp_wr == stdout)
setmode (fileno (stdout), O_BINARY);
#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
*/
void usage ()
{
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");
fprintf (stderr,
" -a[lpha] <file>.pgm read PNG alpha channel as pgm-file\n");
fprintf (stderr, " -h | -? print this help-information\n");
}
/*
* pnm2png
*/
BOOL pnm2png (FILE *pnm_file, FILE *png_file, FILE *alpha_file,
BOOL interlace, BOOL alpha)
{
png_struct *png_ptr;
png_info *info_ptr;
BOOL ret;
/* initialize the libpng structures for writing to png_file */
png_ptr = png_create_write_struct (png_get_libpng_ver(NULL),
NULL, NULL, NULL);
if (!png_ptr)
return FALSE; /* out of memory */
info_ptr = png_create_info_struct (png_ptr);
if (!info_ptr)
{
png_destroy_write_struct (&png_ptr, NULL);
return FALSE; /* out of memory */
}
if (setjmp (png_jmpbuf (png_ptr)))
{
png_destroy_write_struct (&png_ptr, &info_ptr);
return FALSE; /* generic libpng error */
}
png_init_io (png_ptr, png_file);
/* do the actual conversion */
ret = pnm2png_internal (png_ptr, info_ptr,
pnm_file, alpha_file, interlace, alpha);
/* clean up the libpng structures and their internally-managed data */
png_destroy_write_struct (&png_ptr, &info_ptr);
return ret;
}
/*
* pnm2png_internal
*/
BOOL pnm2png_internal (png_struct *png_ptr, png_info *info_ptr,
FILE *pnm_file, FILE *alpha_file,
BOOL interlace, BOOL alpha)
{
png_byte **row_pointers;
png_byte *pix_ptr;
int bit_depth;
int color_type;
int channels;
char magic_token[4];
BOOL raw;
png_uint_32 width, height, maxval;
png_uint_32 row_bytes;
png_uint_32 row, col;
png_uint_32 val16, i;
png_uint_32 alpha_width = 0, alpha_height = 0;
int alpha_depth = 0, alpha_present = 0;
BOOL alpha_raw = FALSE;
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
BOOL packed_bitmap = FALSE;
#endif
/* read header of PNM file */
if (fscan_pnm_magic (pnm_file, magic_token, sizeof (magic_token)) != 1)
return FALSE; /* not a PNM file */
if ((magic_token[1] == '1') || (magic_token[1] == '4'))
{
if ((fscan_pnm_uint_32 (pnm_file, &width) != 1) ||
(fscan_pnm_uint_32 (pnm_file, &height) != 1))
return FALSE; /* bad PBM file header */
} else if ((magic_token[1] == '2') || (magic_token[1] == '5') ||
(magic_token[1] == '3') || (magic_token[1] == '6'))
{
if ((fscan_pnm_uint_32 (pnm_file, &width) != 1) ||
(fscan_pnm_uint_32 (pnm_file, &height) != 1) ||
(fscan_pnm_uint_32 (pnm_file, &maxval) != 1))
return FALSE; /* bad PGM/PPM file header */
}
if ((magic_token[1] == '1') || (magic_token[1] == '4'))
{
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
raw = (magic_token[1] == '4');
bit_depth = 1;
color_type = PNG_COLOR_TYPE_GRAY;
packed_bitmap = TRUE;
#else
fprintf (stderr, "PNM2PNG built without PNG_WRITE_INVERT_SUPPORTED and\n");
fprintf (stderr, "PNG_WRITE_PACK_SUPPORTED can't read PBM (P1,P4) files\n");
return FALSE;
#endif
}
else if ((magic_token[1] == '2') || (magic_token[1] == '5'))
{
raw = (magic_token[1] == '5');
color_type = PNG_COLOR_TYPE_GRAY;
if (maxval == 0)
return FALSE;
else 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;
else if (maxval <= 65535U)
bit_depth = 16;
else /* maxval > 65535U */
return FALSE;
}
else if ((magic_token[1] == '3') || (magic_token[1] == '6'))
{
raw = (magic_token[1] == '6');
color_type = PNG_COLOR_TYPE_RGB;
if (maxval == 0)
return FALSE;
else 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;
else if (maxval <= 65535U)
bit_depth = 16;
else /* maxval > 65535U */
return FALSE;
}
else if (magic_token[1] == '7')
{
fprintf (stderr, "PNM2PNG can't read PAM (P7) files\n");
return FALSE;
}
else
{
return FALSE;
}
/* read header of PGM file with alpha channel */
if (alpha)
{
if ((fscan_pnm_magic (alpha_file, magic_token, sizeof (magic_token)) != 1)
|| ((magic_token[1] != '2') && (magic_token[1] != '5')))
return FALSE; /* not a PGM file */
if ((fscan_pnm_uint_32 (alpha_file, &alpha_width) != 1) ||
(fscan_pnm_uint_32 (alpha_file, &alpha_height) != 1) ||
(fscan_pnm_uint_32 (alpha_file, &maxval) != 1))
return FALSE; /* bad PGM file header */
if ((alpha_width != width) || (alpha_height != height))
return FALSE; /* mismatched PGM dimensions */
alpha_raw = (magic_token[1] == '5');
color_type |= PNG_COLOR_MASK_ALPHA;
if (maxval == 0)
return FALSE;
else 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;
else if (maxval <= 65535U)
alpha_depth = 16;
else /* maxval > 65535U */
return FALSE;
if (alpha_depth != bit_depth)
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;
else
return FALSE; /* NOTREACHED */
alpha_present = (channels - 1) % 2;
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
if (packed_bitmap)
{
/* row data is as many bytes as can fit width x channels x bit_depth */
row_bytes = (width * channels * bit_depth + 7) / 8;
}
else
#endif
{
/* row_bytes is the width x number of channels x (bit-depth / 8) */
row_bytes = width * channels * ((bit_depth <= 8) ? 1 : 2);
}
if ((row_bytes == 0) ||
((size_t) height > (size_t) (-1) / (size_t) row_bytes))
{
/* too big */
return FALSE;
}
/* allocate the rows using the same memory layout as libpng, and transfer
* their ownership to libpng, with the responsibility to clean everything up;
* please note the use of png_calloc instead of png_malloc */
row_pointers = (png_byte **)
png_calloc (png_ptr, height * sizeof (png_byte *));
png_set_rows (png_ptr, info_ptr, row_pointers);
png_data_freer (png_ptr, info_ptr, PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_ALL);
for (row = 0; row < height; row++)
{
/* the individual rows should only be allocated after all the previous
* steps completed successfully, because libpng must handle correctly
* any image allocation left incomplete after an out-of-memory error */
row_pointers[row] = (png_byte *) png_malloc (png_ptr, row_bytes);
}
/* read the data from PNM file */
for (row = 0; row < height; row++)
{
pix_ptr = row_pointers[row];
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
if (packed_bitmap)
{
for (i = 0; i < row_bytes; i++)
{
/* png supports this format natively so no conversion is needed */
*pix_ptr++ = get_pnm_data (pnm_file, 8);
}
}
else
#endif
{
for (col = 0; col < width; col++)
{
for (i = 0; i < (png_uint_32) (channels - alpha_present); i++)
{
if (raw)
{
*pix_ptr++ = get_pnm_data (pnm_file, bit_depth);
if (bit_depth == 16)
*pix_ptr++ = get_pnm_data (pnm_file, bit_depth);
}
else
{
if (bit_depth <= 8)
{
*pix_ptr++ = get_pnm_value (pnm_file, bit_depth);
}
else
{
val16 = get_pnm_value (pnm_file, bit_depth);
*pix_ptr = (png_byte) ((val16 >> 8) & 0xFF);
pix_ptr++;
*pix_ptr = (png_byte) (val16 & 0xFF);
pix_ptr++;
}
}
}
if (alpha) /* read alpha-channel from pgm file */
{
if (alpha_raw)
{
*pix_ptr++ = get_pnm_data (alpha_file, alpha_depth);
if (alpha_depth == 16)
*pix_ptr++ = get_pnm_data (alpha_file, alpha_depth);
}
else
{
if (alpha_depth <= 8)
{
*pix_ptr++ = get_pnm_value (alpha_file, bit_depth);
}
else
{
val16 = get_pnm_value (alpha_file, bit_depth);
*pix_ptr++ = (png_byte) ((val16 >> 8) & 0xFF);
*pix_ptr++ = (png_byte) (val16 & 0xFF);
}
}
} /* end if alpha */
} /* end if packed_bitmap */
} /* end for col */
} /* end for row */
/* 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,
(!interlace) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
#if defined(PNG_WRITE_INVERT_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
if (packed_bitmap == TRUE)
{
png_set_packing (png_ptr);
png_set_invert_mono (png_ptr);
}
#endif
/* write the file header information */
png_write_info (png_ptr, info_ptr);
/* write out the entire image data in one call */
png_write_image (png_ptr, row_pointers);
/* write the additional chunks to the PNG file (not really needed) */
png_write_end (png_ptr, info_ptr);
return TRUE;
} /* end of pnm2png */
/*
* fscan_pnm_magic - like fscan_pnm_token below, but expects the magic string
* to start immediately, without any comment or whitespace,
* and to match the regex /^P[1-9]$/
*/
int fscan_pnm_magic (FILE *pnm_file, char *magic_buf, size_t magic_buf_size)
{
int ret;
ret = fgetc (pnm_file);
if (ret == EOF) return 0;
ungetc (ret, pnm_file);
if (ret != 'P') return 0;
/* the string buffer must be at least four bytes long, i.e., the capacity
* required for strings of at least three characters long, i.e., the minimum
* required for ensuring that our magic string is exactly two characters long
*/
if (magic_buf_size < 4) return -1;
ret = fscan_pnm_token (pnm_file, magic_buf, magic_buf_size);
if (ret < 1) return ret;
if ((magic_buf[1] < '1') || (magic_buf[1] > '9')) return 0;
if (magic_buf[2] != '\0') return 0;
return 1;
}
/*
* fscan_pnm_token - extracts the first string token after whitespace,
* and (like fscanf) returns the number of successful
* extractions, which can be either 0 or 1
*/
int fscan_pnm_token (FILE *pnm_file, char *token_buf, size_t token_buf_size)
{
size_t i = 0;
int ret;
/* remove white-space and comment lines */
do
{
ret = fgetc (pnm_file);
if (ret == '#')
{
/* the rest of this line is a comment */
do
{
ret = fgetc (pnm_file);
}
while ((ret != '\n') && (ret != '\r') && (ret != EOF));
}
if (ret == EOF) break;
token_buf[i] = (char) ret;
}
while ((ret == '\n') || (ret == '\r') || (ret == ' '));
/* read string */
do
{
ret = fgetc (pnm_file);
if (ret == EOF) break;
if (ret == '0')
{
/* avoid storing more than one leading '0' in the token buffer,
* to ensure that all valid (in-range) numeric inputs can fit in. */
if ((i == 0) && (token_buf[i] == '0')) continue;
}
if (++i == token_buf_size - 1) break;
token_buf[i] = (char) ret;
}
while ((ret != '\n') && (ret != '\r') && (ret != ' '));
token_buf[i] = '\0';
return (i > 0) ? 1 : 0;
}
/*
* fscan_pnm_uint_32 - like fscan_token above, but expects the extracted token
* to be numeric, and converts it to an unsigned 32-bit int
*/
int fscan_pnm_uint_32 (FILE *pnm_file, png_uint_32 *num_ptr)
{
char token[16];
unsigned long token_value;
int ret;
ret = fscan_pnm_token (pnm_file, token, sizeof (token));
if (ret < 1) return ret;
if ((token[0] < '0') && (token[0] > '9'))
return 0; /* the token starts with junk, or a +/- sign, which is invalid */
ret = sscanf (token, "%lu%*c", &token_value);
if (ret != 1)
return 0; /* the token ends with junk */
*num_ptr = (png_uint_32) token_value;
#if ULONG_MAX > 0xFFFFFFFFUL
/* saturate the converted number, following the fscanf convention */
if (token_value > 0xFFFFFFFFUL)
*num_ptr = 0xFFFFFFFFUL;
#endif
return 1;
}
/*
* get_pnm_data - takes first byte and converts into next pixel value,
* taking as many bits as defined by bit-depth and
* using the bit-depth to fill up a byte (0x0A -> 0xAA)
*/
png_uint_32 get_pnm_data (FILE *pnm_file, int depth)
{
static int bits_left = 0;
static int old_value = 0;
static int mask = 0;
png_uint_32 ret_value;
int i;
if (mask == 0)
for (i = 0; i < depth; i++)
mask = (mask >> 1) | 0x80;
if (bits_left <= 0)
{
/* FIXME:
* signal the premature end of file, instead of pretending to read zeroes
*/
old_value = fgetc (pnm_file);
if (old_value == EOF) return 0;
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;
}
/*
* get_pnm_value - takes first (numeric) string and converts into number,
* using the bit-depth to fill up a byte (0x0A -> 0xAA)
*/
png_uint_32 get_pnm_value (FILE *pnm_file, int depth)
{
static png_uint_32 mask = 0;
png_uint_32 ret_value;
int i;
if (mask == 0)
for (i = 0; i < depth; i++)
mask = (mask << 1) | 0x01;
if (fscan_pnm_uint_32 (pnm_file, &ret_value) != 1)
{
/* FIXME:
* signal the invalid numeric tokens or the premature end of file,
* instead of pretending to read zeroes
*/
return 0;
}
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 */