/*- iccfrompng
 *
 * COPYRIGHT: Written by John Cunningham Bowler, 2011.
 * To the extent possible under law, the author has waived all copyright and
 * related or neighboring rights to this work.  This work is published from:
 * United States.
 *
 * Extract any icc profiles found in the given PNG files.  This is a simple
 * example of a program that extracts information from the header of a PNG file
 * without processing the image.  Notice that some header information may occur
 * after the image data. Textual data and comments are an example; the approach
 * in this file won't work reliably for such data because it only looks for the
 * information in the section of the file that precedes the image data.
 *
 * Compile and link against libpng and zlib, plus anything else required on the
 * system you use.
 *
 * To use supply a list of PNG files containing iCCP chunks, the chunks will be
 * extracted to a similarly named file with the extension replaced by 'icc',
 * which will be overwritten without warning.
 */
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
#include <stdio.h>

#include <png.h>

#if !defined(PNG_iCCP_SUPPORTED) || !defined(PNG_READ_SUPPORTED)
#error This program requires libpng supporting the iCCP chunk and the read API
#endif


static int verbose = 1;
static png_byte no_profile[] = "no profile";

static png_bytep
extract(FILE *fp, png_uint_32 *proflen)
{
   png_structp png_ptr =
      png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
   png_infop info_ptr = NULL;
   png_bytep result = NULL;

   /* Initialize for error or no profile: */
   *proflen = 0;

   if (png_ptr == NULL)
   {
      fprintf(stderr, "iccfrompng: version library mismatch?\n");
      return 0;
   }

   if (setjmp(png_jmpbuf(png_ptr)))
   {
      png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
      return 0;
   }

   png_init_io(png_ptr, fp);

   info_ptr = png_create_info_struct(png_ptr);
   if (info_ptr == NULL)
      png_error(png_ptr, "OOM allocating info structure");

   png_read_info(png_ptr, info_ptr);

   {
      png_charp name;
      int compression_type;
      png_bytep profile;

      if (png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile,
                       proflen) & PNG_INFO_iCCP)
      {
         result = malloc(*proflen);
         if (result != NULL)
            memcpy(result, profile, *proflen);

         else
            png_error(png_ptr, "OOM allocating profile buffer");
      }

      else
         result = no_profile;
   }

   png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
   return result;
}

static int
extract_one_file(const char *filename)
{
   int result = 0;
   FILE *fp = fopen(filename, "rb");

   if (fp != NULL)
   {
      png_uint_32 proflen = 0;
      png_bytep profile = extract(fp, &proflen);

      if (profile != NULL && profile != no_profile)
      {
         size_t len;
         char *output;

         {
            const char *ep = strrchr(filename, '.');

            if (ep != NULL)
               len = ep - filename;

            else
               len = strlen(filename);
         }

         output = malloc(len + 5);
         if (output != NULL)
         {
            FILE *of;

            memcpy(output, filename, len);
            strcpy(output + len, ".icc");

            of = fopen(output, "wb");
            if (of != NULL)
            {
               if (fwrite(profile, proflen, 1, of) == 1 &&
                   fflush(of) == 0 &&
                   fclose(of) == 0)
               {
                  if (verbose)
                     printf("%s -> %s\n", filename, output);
                  /* Success return */
                  result = 1;
               }

               else
               {
                  fprintf(stderr, "%s: error writing profile\n", output);
                  if (remove(output))
                     fprintf(stderr, "%s: could not remove file\n", output);
               }
            }

            else
               fprintf(stderr, "%s: failed to open output file\n", output);

            free(output);
         }

         else
            fprintf(stderr, "%s: OOM allocating string!\n", filename);

         free(profile);
      }

      else if (verbose && profile == no_profile)
         printf("%s has no profile\n", filename);
   }

   else
      fprintf(stderr, "%s: could not open file\n", filename);

   if (fp != NULL)
      fclose(fp);

   return result;
}

int
main(int argc, char **argv)
{
   int i;
   int extracted = 0;

   for (i = 1; i < argc; ++i)
   {
      if (strcmp(argv[i], "-q") == 0)
         verbose = 0;

      else if (extract_one_file(argv[i]))
         extracted = 1;
   }

   /* Exit code is true if any extract succeeds */
   return extracted == 0;
}
