/* pngstrip:  rewrite a PNG file with all extraneous information removed

   A quick and dirty utility to rewrite a (possibly interlaced) PNG image file
   as a non-interlaced PNG image file with all ancillary chunks beyond

       bKGD - background color
       gAMA - image gamma setting
       pHYs - physical pixel dimensions
       sRGB - srgb color space indicator
       tRNS - transparency

   being removed.

   Copyright (C) 2004-2026 by Brian Lindholm.  This file is part of the
   littleutils utility set.

   The pngstrip utility is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 3, or (at your option) any later
   version.

   The pngstrip utility is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   more details.

   You should have received a copy of the GNU General Public License along with
   the littleutils.  If not, see <https://www.gnu.org/licenses/>. */

#include <config.h>

#ifdef HAVE_STDIO_H
# include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
# define OPTEND -1
#else
# define OPTEND EOF
#endif
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#endif

#ifdef HAVE_ZLIB_H
# include <zlib.h>
#endif

#include <png.h>

#ifdef __MINGW32__
extern int getopt(int argc, char * const *argv, const char *optstring);
extern char *optarg;
extern int optind;
#endif

#ifndef PNG_TRANSFORM_GRAYSCALE
# define PNG_TRANSFORM_GRAYSCALE 0x1000 /* on reads only */
#endif

typedef struct
{
  png_uint_32 width, height;
  int bit_depth, color_type, interlace_type, compression_type, filter_type;
}
ihdr_t;

/* help function */
static void help(FILE *where)
{
  fprintf(where, "pngstrip " PACKAGE_VERSION "\n"
    "usage: pngstrip [-8(bit_strip_to)] [-a(lpha_strip)] [-e(xpand_to_rgb)]\n"
    "  [-g(rayscale_convert)] [-r DPM] [-h(elp)] infile outfile\n");
}

/* rewrite PNG function */
static void rewrite_png(char *read_file, char *write_file, png_uint_32 trnsfrm, png_uint_32 dpm)
{
  png_bytep header, trans;
  png_bytepp row_pointers;
  png_colorp palette;
  png_color_16p trans_values, background;
  png_infop read_info_ptr, write_info_ptr, read_end_info;
  png_structp read_png_ptr, write_png_ptr;
  png_uint_32 has_bkgd, has_gama, has_phys, has_plte, has_srgb, has_trns, row, res_x, res_y;
  FILE *fp, *fp2;
  double gamma;
  ihdr_t ihdr;
  int srgb_intent, num_palette, num_trans, is_png, unit_type;

  /* open file for reading */
  if ((fp = fopen (read_file, "rb")) == NULL) {
    fprintf(stderr, "pngstrip error: can't open file %s\n", read_file);
    exit(1);
  }
  /* check for PNG signature */
  header = (png_bytep) calloc ((size_t) 8, (size_t) 1);
  (void) fread(header, (size_t) 1, (size_t) 8, fp);
  is_png = !png_sig_cmp(header, (png_size_t) 0, (png_size_t) 8);
  free(header);
  if (!is_png) {
    fprintf(stderr, "pngstrip error: %s is not a PNG file\n", read_file);
    (void) fclose(fp);
    exit(2);
  }
  /* allocate various png memory structures */
  read_png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (read_png_ptr == NULL) {
    fprintf(stderr, "pngstrip error: can't create read_struct\n");
    (void) fclose(fp);
    exit(3);
  }
  read_info_ptr = png_create_info_struct(read_png_ptr);
  if (read_info_ptr == NULL) {
    fprintf(stderr, "pngstrip error: can't create read_info_struct\n");
    png_destroy_read_struct(&read_png_ptr, (png_infopp) NULL, (png_infopp) NULL);
    (void) fclose(fp);
    exit(3);
  }
  read_end_info = png_create_info_struct(read_png_ptr);
  if (read_end_info == NULL) {
    fprintf(stderr, "pngstrip error: can't create read_end_info\n");
    png_destroy_read_struct(&read_png_ptr, &read_info_ptr, (png_infopp) NULL);
    (void) fclose(fp);
    exit(3);
  }
  if (setjmp(png_jmpbuf(read_png_ptr))) {
    fprintf(stderr, "bizzare pngstrip error: unknown problem on read\n");
    png_destroy_read_struct(&read_png_ptr, &read_info_ptr, &read_end_info);
    (void) fclose(fp);
    exit(3);
  }
  /* read the file the simple way */
  /* png_init_io(read_png_ptr, fp);
  png_set_sig_bytes(read_png_ptr, 8);
  png_read_png(read_png_ptr, read_info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
  (void) fclose(fp); */
  /* This was dropped because it didn't allow grayscale transforms. */
  /* begin reading the file the complicated way */
  png_init_io(read_png_ptr, fp);
  png_set_sig_bytes(read_png_ptr, 8);
  png_read_info(read_png_ptr, read_info_ptr);
  /* extract image header */
  png_get_IHDR(read_png_ptr, read_info_ptr, &ihdr.width, &ihdr.height, &ihdr.bit_depth,
    &ihdr.color_type, &ihdr.interlace_type, &ihdr.compression_type, &ihdr.filter_type);
  /* activate requested transformations */
  if ((trnsfrm & PNG_TRANSFORM_STRIP_16) && (ihdr.bit_depth == 16)) {
    png_set_strip_16(read_png_ptr);
    ihdr.bit_depth = 8;
  }
  if (trnsfrm & PNG_TRANSFORM_STRIP_ALPHA) {
    if (ihdr.color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
      png_set_strip_alpha(read_png_ptr);
      ihdr.color_type = PNG_COLOR_TYPE_RGB;
    }
    else if (ihdr.color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
      png_set_strip_alpha(read_png_ptr);
      ihdr.color_type = PNG_COLOR_TYPE_GRAY;
    }
  }
  if (((trnsfrm & PNG_TRANSFORM_EXPAND) || (trnsfrm & PNG_TRANSFORM_GRAYSCALE)) &&
      (ihdr.color_type == PNG_COLOR_TYPE_PALETTE)) {
    png_set_palette_to_rgb(read_png_ptr);
    ihdr.bit_depth = 8;
    ihdr.color_type = PNG_COLOR_TYPE_RGB;
    if (((trnsfrm & PNG_TRANSFORM_STRIP_ALPHA) == 0) && png_get_valid(read_png_ptr, read_info_ptr, PNG_INFO_tRNS)) {
      png_set_tRNS_to_alpha(read_png_ptr);
      ihdr.color_type = PNG_COLOR_TYPE_RGB_ALPHA;
    }
  }
  if (trnsfrm & PNG_TRANSFORM_GRAYSCALE) {
    if (ihdr.color_type == PNG_COLOR_TYPE_RGB) {
      png_set_rgb_to_gray_fixed(read_png_ptr, 1, 21268, 71514);
      ihdr.color_type = PNG_COLOR_TYPE_GRAY;
    }
    else if (ihdr.color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
      png_set_rgb_to_gray_fixed(read_png_ptr, 1, 21268, 71514);
      ihdr.color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
    }
  }
  if (trnsfrm != 0) png_read_update_info(read_png_ptr, read_info_ptr);
  /* allocate memory for image and finish reading in the image */
  row_pointers = png_malloc(read_png_ptr, ihdr.height * sizeof(png_bytep));
  for (row = 0; row < ihdr.height; row++)
    row_pointers[row] = png_malloc(read_png_ptr, png_get_rowbytes(read_png_ptr, read_info_ptr));
  png_read_image(read_png_ptr, row_pointers);
  png_read_end(read_png_ptr, read_info_ptr);
  (void) fclose(fp);
  /* extract additional information to variables */
  has_bkgd = png_get_bKGD(read_png_ptr, read_info_ptr, &background);
  has_gama = png_get_gAMA(read_png_ptr, read_info_ptr, &gamma);
  if (dpm > 0) {
    has_phys = PNG_INFO_pHYs;
    res_x = dpm;
    res_y = dpm;
    unit_type = PNG_RESOLUTION_METER;
  }
  else
    has_phys = png_get_pHYs(read_png_ptr, read_info_ptr, &res_x, &res_y, &unit_type);
  has_plte = png_get_PLTE(read_png_ptr, read_info_ptr, &palette, &num_palette);
  has_srgb = png_get_sRGB(read_png_ptr, read_info_ptr, &srgb_intent);
  has_trns = png_get_tRNS(read_png_ptr, read_info_ptr, &trans, &num_trans, &trans_values);
  /* open file for writing */
  fp2 = fopen(write_file, "wb");
  if (!fp2) {
    fprintf(stderr, "pngstrip error: can't open file %s for writing\n", write_file);
    png_destroy_read_struct(&read_png_ptr, &read_info_ptr, &read_end_info);
    exit(1);
  }
  /* allocate various png memory structures */
  write_png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (!write_png_ptr) {
    fprintf(stderr, "pngstrip error: can't create write struct\n");
    png_destroy_read_struct(&read_png_ptr, &read_info_ptr, &read_end_info);
    (void) fclose(fp);
    exit(3);
  }
  write_info_ptr = png_create_info_struct(write_png_ptr);
  if (!write_info_ptr) {
    fprintf(stderr, "pngstrip error: can't create write info struct\n");
    png_destroy_write_struct(&write_png_ptr, NULL);
    png_destroy_read_struct(&read_png_ptr, &read_info_ptr, &read_end_info);
    (void) fclose(fp);
    exit(3);
  }
  if (setjmp(png_jmpbuf(write_png_ptr))) {
    fprintf(stderr, "bizzare pngstrip error: unknown problem on write\n");
    png_destroy_write_struct(&write_png_ptr, &write_info_ptr);
    png_destroy_read_struct(&read_png_ptr, &read_info_ptr, &read_end_info);
    (void) fclose(fp);
    exit(3);
  }
  png_init_io(write_png_ptr, fp2);
  /* assign various pointers and pieces of information */
  png_set_IHDR(write_png_ptr, write_info_ptr, ihdr.width, ihdr.height, ihdr.bit_depth, ihdr.color_type,
    PNG_INTERLACE_NONE, ihdr.compression_type, ihdr.filter_type);
  if (has_bkgd) png_set_bKGD(write_png_ptr, write_info_ptr, background);
  if (has_gama) png_set_gAMA(write_png_ptr, write_info_ptr, gamma);
  if (has_phys) png_set_pHYs(write_png_ptr, write_info_ptr, res_x, res_y, unit_type);
  if (has_plte && (ihdr.color_type == PNG_COLOR_TYPE_PALETTE)) png_set_PLTE(write_png_ptr, write_info_ptr, palette,
    num_palette);
  if (has_srgb) png_set_sRGB(write_png_ptr, write_info_ptr, srgb_intent);
  if (has_trns && ((trnsfrm & PNG_TRANSFORM_STRIP_ALPHA) == 0)) png_set_tRNS(write_png_ptr, write_info_ptr, trans,
    num_trans, trans_values);
  png_set_rows(write_png_ptr, write_info_ptr, row_pointers);
  png_set_compression_level(write_png_ptr, Z_BEST_COMPRESSION);
  /* write file */
  png_write_png(write_png_ptr, write_info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
  (void) fclose(fp2);
  /* free allocated memory structures */
  png_destroy_write_struct(&write_png_ptr, &write_info_ptr);
  png_destroy_read_struct(&read_png_ptr, &read_info_ptr, &read_end_info);
}

/* main program */
int main(int argc, char **argv)
{
  int opt;
  png_uint_32 dpm, trnsfrm;

  /* parse options */
  trnsfrm = 0;
  dpm = 0;
  while ((opt = getopt (argc, argv, "8aeghr:")) != OPTEND) switch (opt) {
    case '8':
      trnsfrm = trnsfrm | PNG_TRANSFORM_STRIP_16;
      break;
    case 'a':
      trnsfrm = trnsfrm | PNG_TRANSFORM_STRIP_ALPHA;
      break;
    case 'e':
      trnsfrm = trnsfrm | PNG_TRANSFORM_EXPAND;
      break;
    case 'g':
      trnsfrm = trnsfrm | PNG_TRANSFORM_GRAYSCALE;
      break;
    case 'h':
      help(stdout);
      return(0);
    case 'r':
      dpm = (png_uint_32) atoi(optarg);
      break;
    case '?':
      help(stderr);
      return(1);
  }
  /* Ensure that two files were given as input. */
  if ((argc - optind) != 2) {
    fprintf(stderr, "pngstrip error: missing input or output file\n");
    help(stderr);
    return(1);
  }
  /* Rewrite the image without interlacing and unwanted chunks removed and finish. */
  rewrite_png(argv[optind], argv[optind + 1], trnsfrm, dpm);
  return(0);
}
