pngdat.c

hint

browsers eat the greater than and less than characters in a <pre> section. Have the browser show source to see them.

/* cc -o pngdat pngdat.c -lpng */

/*
Copyright (c) 2020-2023 Peter C. Capasso

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
SOFTWARE.
*/

/* includes */
#include 
#include 
#include 
#ifndef strcmp
  #include 
#endif
#include 
#include 
#include 

/* What do you do when the compiler finds an error in an include
   file, for a include that is thirty years old and very much verified?
   You hope that "png_FILE_p" is really just "FILE *" and kludge
   accordingly with a define.  That seems to work, but WHY did this
   happen, after *decades*, in the first place?  Do this before the
   include of png.h */
#ifndef png_FILE_p
  #define png_FILE_p FILE *
#endif

#include 
#include 


/* defines */
#define neq !=
#define eq ==
#define OKDOKEY 0
#define DOOPS 20
#define mod %


/* global variables */
png_structp peanut_butter = NULL;
png_infop jelly = NULL;


/* forward references */
void usage_exit(void);
FILE *BHopen(char *, char *);
size_t int_square_root(size_t);
size_t calculate_size_in_pixels(size_t);
void calc_output_dimensions(size_t, size_t *, size_t *);
int write_output_png_header(FILE *, size_t, size_t);
int write_output_png_data(FILE *, FILE *, long, size_t, size_t);
int copy_data_in(FILE *, FILE *);
int data_to_png(char *, char *);
int is_file_a_png(FILE *);
int check_png_header(FILE *, png_uint_32 *, png_uint_32 *, int *, int *);
int copy_data_out(FILE *, png_uint_32,png_uint_32);
int png_to_data(char *, char *);


/* start of actual program */
void usage_exit(void)
{
  fprintf(stderr, "\
usage: pngdat -e  \n\
       pngdat -d  \n\
where input png must be 24-bit non-progresive\n");
  exit(DOOPS);
}


FILE *BHopen(Fname, Fmode)
char *Fname;
char *Fmode;
{
  FILE *temp;

  temp = fopen(Fname, Fmode);
  if (temp eq NULL) {
    fprintf(
      stderr,
      "error opening %s mode file \"%s\"\n",
      Fmode, Fname
    );
    exit(DOOPS);  /* no messing with return, just exit on out NOW */
  }
  if (setvbuf(temp, NULL, _IOFBF, 32768)) {
    fprintf(stderr, "BHopen: setvbuf error\n");
    exit(DOOPS);
  }
  return(temp);
}


/* why size_t?  because it is the largest possibly unsigned integer,
   whereas offset_t is the largest possibly signed integet, I think.
   As to how large, that varies with compiler and architecture.
   Values in png data structures do not exceed 32-bit values, so
   size_t should be enough overhead for multiplication and such.
*/
size_t int_square_root(n)
size_t n;
{
  size_t mid, low, high, low_work, high_work, mid_work;

  switch (n) {
    case 0 :   /* well, zero times zero equals zero, right? */
    case 1 :
      return(n);
    case 2 :
    case 3 :
      return(1);
    case 4 :
      return(2);
    default :
      low = 1;
      high = n;
      while (low < high) {
        low_work = low * low;
        high_work = high * high;
        if (low_work eq n)  return(low);
        if (high_work <= n)  return(high);
        /* if we reached this point, low is too low and high is too high */
        mid = (high + low) / 2;
        mid_work = mid * mid;
        if (mid_work eq n)  return(mid);
        if (mid_work > n) {  /* mid is too high */
          high = mid;        /* mid is too high, set it to be the new high,
                                which will be lower than the old high */
        } else {
          if (low eq mid)  return(mid);
          low = mid;         /* mid is too low, set it to be the new low,
                                which will be higher than the old low */
        }
      } /* end while */
  } /* end switch */
  /* should never ever reach this point */
  return(0);
}


size_t calculate_size_in_pixels(size_in_bytes)
size_t size_in_bytes;
{
  size_t number_of_pixels;

  number_of_pixels = size_in_bytes / 3;  /* three bytes per 24-bit pixel */
  if ((size_in_bytes mod 3)) {
    number_of_pixels++;
  }
  return(number_of_pixels);
}


void calc_output_dimensions(pixels, w, h)
size_t pixels;
size_t *w;
size_t *h;
{
  size_t x, y, extra, gap;

  x = int_square_root(pixels);
  if ((x * x) eq pixels) {
    /* possible, but of low probability */
    *w = x;
    *h = x;
  } else {
    /* more likely */
    y = x;
    while ((x * y) < pixels) {
      y++;
    }
    *w = x;
    *h = y;
  }
}


int write_output_png_header(f, w, h)
FILE *f;
size_t w;
size_t h;
{
  peanut_butter = png_create_write_struct(
    PNG_LIBPNG_VER_STRING,
    NULL, /* user_error_ptr, */
    NULL, /* user_error_fn, */
    NULL  /* user_warning_fn */
  );
  if (NULL eq peanut_butter) {
    fprintf(stderr, "write_png_header: error creating png write structure\n");
    return(DOOPS);
  }

  jelly = png_create_info_struct(peanut_butter);
  if (NULL eq jelly) {
    fprintf(stderr, "write_png_header: error creating png info structure\n");
    return(DOOPS);
  }

  /* Call setjmp in each subroutine that uses png, and call
     it right after the two structure allocations. */
  if (setjmp(png_jmpbuf(peanut_butter))) {
    png_destroy_write_struct(
      &peanut_butter,
      &jelly
    );
    fprintf(stderr, "write_png_header: setjmp error\n");
    return(DOOPS);
  }

  /* begin to write the first part(s) of the output png header */
  png_init_io(peanut_butter, f);

  /* set image parameters within the header */
  png_set_IHDR(
    peanut_butter,
    jelly,
    w,            /* image width */
    h,            /* image height */
    8,            /* 24-bit, so 8 bits per component */
    PNG_COLOR_TYPE_RGB,   /* rgb, not indexed nor grayscale, no alpha */
    PNG_INTERLACE_NONE,   /* not interlaced! */
    PNG_COMPRESSION_TYPE_DEFAULT,  /* never change */
    PNG_FILTER_TYPE_DEFAULT        /* never change */
  );

  /* write the (rest of the) header */
  png_write_info(peanut_butter, jelly);
  /* next line is OPTIONAL, in theory can take it out */
  png_write_flush(peanut_butter);

  return(OKDOKEY);
}


int write_output_png_data(f1, f2, input_file_size, w, h)
FILE *f1;  /* input raw binary data */
FILE *f2;  /* output png (header already written) */
long input_file_size;
size_t w;  /* output width, in pixels */
size_t h;  /* output height, in pixels */
{
  size_t y, last_line, bytes_per_line, actual_read;
  unsigned char *line_buffer;

  /* note: last_line will usually need padding, since most
     of the time the input data will not exactly fit the
     required png output size */
  last_line = h - 1;
  bytes_per_line = 3 * w;

  line_buffer = malloc(bytes_per_line);
  if (NULL eq line_buffer) {
    fprintf(stderr, "write_output_png_data: memory failure\n");
    png_destroy_write_struct(&peanut_butter, &jelly);
    return(DOOPS);
  }

  /* Call setjmp in each subroutine that uses png, and call
     it right after the two structure allocation. */
  if (setjmp(png_jmpbuf(peanut_butter))) {
    png_destroy_write_struct(&peanut_butter, &jelly);
    fprintf(stderr, "write_output_png_data: setjmp error\n");
    return(DOOPS);
  }

  for (y = 0; y neq h; y++) {
    actual_read = fread(line_buffer, 1, bytes_per_line, f1);
    if (bytes_per_line neq actual_read) {
      if (y eq last_line) {
        /* pad the rest of the line with zeroes */
        memset(
          &line_buffer[actual_read],
          0,
          (bytes_per_line - actual_read)
        );
      } else {
        /* can detect read error on all but the last line/row */
        fprintf(
          stderr,
          "write_output_png_data: error reading from input file\n"
        );
        return(DOOPS);
      }
    }
    png_write_row(
      peanut_butter,
      (png_bytep) line_buffer
    );
  } /* next y */

  /* flush/finish the file */
  png_write_end(peanut_butter, NULL);

  /* clean up */
  png_destroy_write_struct(&peanut_butter, &jelly);
  free(line_buffer);

  /* done */
  return(OKDOKEY);
}


int copy_data_in(f1, f2)
FILE *f1;  /* input data file, any format, to be packed inside a png */
FILE *f2;  /* output png file */
{
  long input_file_size;
  size_t number_of_pixels, w, h;

  /* ascertain input (data) file size, in bytes */
  if (fseek(f1, 0, SEEK_END)) {  /* seek to end of file */
    fprintf(stderr, "copy_data_in: first fseek error\n");
    return(DOOPS);
  }
  input_file_size = ftell(f1);   /* get actual input size, in bytes */
  if (fseek(f1, 0, SEEK_SET)) {  /* rewind to start of file */
    fprintf(stderr, "copy_data_in: second fseek error\n");
    return(DOOPS);
  }
  fprintf(stderr, "debug: input file size: %lu\n", input_file_size);
  if (0 eq input_file_size) {
    fprintf(stderr, "copy_data_in: error: zero-length input file\n");
    return(DOOPS);
  }

  /* from the size, calculate the number of pixels needed to store
     the data */
  number_of_pixels = calculate_size_in_pixels(input_file_size);
  fprintf(stderr, "debug: pixels:%lu\n", number_of_pixels);

  /* from the number of pixels needed, calculate the width and
     height of the output image */
  calc_output_dimensions(
    number_of_pixels,
    &w,
    &h
  );
  fprintf(stderr, "debug: w:%lu h:%lu\n", w, h);

  /* At this point we know the exact width and height of the output
     image.  We also know that it will be 24-bit RGB.  Time to
     actually do some png header writing */
  if (OKDOKEY neq write_output_png_header(f2, w, h)) {
    fprintf(stderr, "copy_data_in: error writing png header\n");
    return(DOOPS);
  }

  /* If we reached this point, the png header write to the output file
     completed without errors. */
  /* Write the data part.  Don't forget to pad with zero bytes, when needed */
  if (OKDOKEY neq write_output_png_data(f1, f2, input_file_size, w, h)) {
    fprintf(stderr, "copy_data_in: error writing data to png\n");
    return(DOOPS);
  }

  /* if that has all worked, we can return (don't close the files, the
     calling subroutine handles that) */
  return(OKDOKEY);
}


int data_to_png(data_file_in, png_file_out)
char *data_file_in;
char *png_file_out;
{
  FILE *f1, *f2;

  f1 = BHopen(data_file_in, "rb");
  f2 = BHopen(png_file_out, "wb");
  if (OKDOKEY neq copy_data_in(f1, f2)) {
    fprintf(stderr, "data_to_png: copy_data_in() error\n");
    return(DOOPS);
  }
  fclose(f2);
  fclose(f1);
  return(OKDOKEY);
}


int is_file_a_png(f)
FILE *f;  /* already opened and STILL AT THE BEGINNING */
{
  unsigned char work[8];

  if (1 neq fread(&work[0], 8, 1, f)) {
    /* a file read error is interpreted as ... not a png */
    return(0);   /* not a png */
  }
  if (137 neq work[0])  return(0);
  if (strncmp("PNG", &work[1], 3))  return(0);
  if (13 neq work[4])  return(0);
  if (10 neq work[5])  return(0);
  if (26 neq work[6])  return(0);
  if (10 neq work[7])  return(0);
  return(1);   /* yes, it is a png */
}


int check_png_header(f, width, height, bit_depth, color_type)
FILE *f;
png_uint_32 *width;
png_uint_32 *height;
int *bit_depth;
int *color_type;
{
  int interlace_type;

  peanut_butter = png_create_read_struct(
    PNG_LIBPNG_VER_STRING,
    NULL,   /* user error pointer */
    NULL,   /* user error function */
    NULL    /* user warning function */
  );
  if (NULL eq peanut_butter) {
    fprintf(stderr, "check_png_header: png_create_read_struct failure; \
possibly out of memory\n");
    return(DOOPS);
  }

  jelly = png_create_info_struct(peanut_butter);
  if (NULL eq jelly) {
    fprintf(stderr, "check_png_header: png_create_info_struct failure; \
possibly out of memory\n");
    png_destroy_read_struct(&peanut_butter, NULL, NULL);
    return(DOOPS);
  }

  /* this setjmp stuff really confuses me,
     just remember that it must be done in every subroutine that
     calls png library functions, and it must occur immediately
     after the png_create_info_structure call */
  if (setjmp(png_jmpbuf(peanut_butter))) {
    png_destroy_read_struct(&peanut_butter, &jelly, NULL);
    fprintf(stderr, "check_png_header: setjmp error\n");
    return(DOOPS);
  }

  /* read the rest of the png header, keeping in mind that the
     first eight bytes have already been read and verified */
  png_init_io(peanut_butter, f);
  png_set_sig_bytes(peanut_butter, 8); /* yes we read those first eight bytes */
  png_read_info(peanut_butter, jelly); /* actually read & parse the png header */

  /* get the info we want from the now-in-ram header structure */
  png_get_IHDR(
    peanut_butter,
    jelly,
    width,
    height,
    bit_depth,  /* for RGB 24, this would be 8: 8 bytes per component */
    color_type,
    &interlace_type,
    NULL, NULL
  );

  if (PNG_INTERLACE_NONE neq interlace_type) {
    fprintf(stderr, "check_png_header: error: interlaced input \
is not allowed\n");
    png_destroy_read_struct(&peanut_butter, &jelly, NULL);
    return(DOOPS);
  }

  return(OKDOKEY);
}


int copy_data_out(f2, w, h)
FILE *f2;
png_uint_32 w;
png_uint_32 h;
{
  char *line_buffer;
  unsigned int y, size_of_one_row;

  /* size_of_one_row = 3 * w; */

  /* another subroutine that does png things, so another
     setjmp call */
  if (setjmp(png_jmpbuf(peanut_butter))) {
    png_destroy_read_struct(&peanut_butter, &jelly, NULL);
    fprintf(stderr, "copy_data_out: setjmp error\n");
    return(DOOPS);
  }

  /* Before calling this subroutine, we confirmed that the input png
     file is 24 bit: RGB with one byte per color component within
     each pixel.  This makes thing simpler. */

  /* Just Ignore gamma.  Really.  The raw as-is pixel data *must* be
     left as-is.  */

  /* get ready to read the image data */
  png_read_update_info(peanut_butter, jelly);

  size_of_one_row = png_get_rowbytes(peanut_butter, jelly);
  /* allocate space for one line (aka row) */
  line_buffer = malloc(size_of_one_row);
  if (NULL eq line_buffer) {
    fprintf(stderr, "copy_data_out: error allocating line buffer\n");
    png_destroy_read_struct(&peanut_butter, &jelly, NULL);
    return(DOOPS);
  }

  /* finally read and write from png to raw data, a line at a time */
  for (y = 0; y < h; y++) {
    png_read_row(peanut_butter, line_buffer, NULL);
    if (1 neq fwrite(line_buffer, size_of_one_row, 1, f2)) {
      fprintf(stderr, "copy_data_out: file write error\n");
      png_destroy_read_struct(&peanut_butter, &jelly, NULL);
      return(DOOPS);
    }
  }

  fflush(f2);
  free(line_buffer);
  return(OKDOKEY);
}


int png_to_data(png_in_name, data_out_name)
char *png_in_name;
char *data_out_name;
{
  FILE *f1, *f2;
  png_uint_32 width;
  png_uint_32 height;
  int bit_depth;
  int color_type;
  int status, file_in, file_out;

  /* set up input */
  if (strcmp("-", png_in_name)) {
    /* not a pipe */
    file_in = 1;
    f1 = BHopen(png_in_name, "rb");
  } else {
    /* a pipe */
    file_in = 0;
    f1 = stdin;
  }

  /* set up output */
  if (strcmp("-", data_out_name)) {
    /* not a pipe */
    file_out = 1;
    f2 = BHopen(data_out_name, "wb");
  } else {
    /* a pipe */
    file_out = 0;
    f2 = stdout;
  }

  if (0 eq is_file_a_png(f1)) {
    fprintf(stderr, "error: file is corrupt or is not png\n");
    return(DOOPS);
  }
  status = check_png_header(f1, &width, &height, &bit_depth, &color_type);
  if (OKDOKEY neq status) {
    fprintf(stderr, "error: error while checking png header\n");
    return(DOOPS);
  }
  fprintf(
    stderr,
    "width %lu, height %lu, bit depth %d, color type %d\n",
    width, height,
    bit_depth, color_type
  );

  if ((8 eq bit_depth) && (PNG_COLOR_TYPE_RGB eq color_type)) {
    /* 24 bit rgb, which is what we want */
    if (OKDOKEY neq copy_data_out(f2, width, height)) {
      fprintf(stderr, "copy_data_out error\n");
      return(DOOPS);
    }
  } else {
    fprintf(stderr, "This program only accepts 24 bit pngs\n");
    return(DOOPS);
  }

  if (file_out)  fclose(f2);
  if (file_in)  fclose(f1);
  return(OKDOKEY);
}


int main(argc, argv)
int argc;
char *argv[];
{
  if (argc < 4)  usage_exit();
  if (0 eq strcmp("-d", argv[1])) {
    if (OKDOKEY neq png_to_data(argv[2], argv[3])) {
      fprintf(stderr, "error getting data out of png\n");
      exit(DOOPS);
    }
  } else {
    if (0 eq strcmp("-e", argv[1])) {
      if (OKDOKEY neq data_to_png(argv[2], argv[3])) {
        fprintf(stderr, "error getting data into png\n");
        exit(DOOPS);
      }
    } else {
      fprintf(stderr, "error: invalid option\n");
      usage_exit();
    }
  }
  exit(OKDOKEY);
}


/* actual end of this file */