1/*- iccfrompng
2 *
3 * COPYRIGHT: Written by John Cunningham Bowler, 2011.
4 * To the extent possible under law, the author has waived all copyright and
5 * related or neighboring rights to this work.  This work is published from:
6 * United States.
7 *
8 * Extract any icc profiles found in the given PNG files.  This is a simple
9 * example of a program that extracts information from the header of a PNG file
10 * without processing the image.  Notice that some header information may occur
11 * after the image data. Textual data and comments are an example; the approach
12 * in this file won't work reliably for such data because it only looks for the
13 * information in the section of the file that preceeds the image data.
14 *
15 * Compile and link against libpng and zlib, plus anything else required on the
16 * system you use.
17 *
18 * To use supply a list of PNG files containing iCCP chunks, the chunks will be
19 * extracted to a similarly named file with the extension replaced by 'icc',
20 * which will be overwritten without warning.
21 */
22#include <stdlib.h>
23#include <setjmp.h>
24#include <string.h>
25#include <stdio.h>
26
27#include <png.h>
28
29static int verbose = 1;
30static png_byte no_profile[] = "no profile";
31
32static png_bytep
33extract(FILE *fp, png_uint_32 *proflen)
34{
35   png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);
36   png_infop info_ptr = NULL;
37   png_bytep result = NULL;
38
39   /* Initialize for error or no profile: */
40   *proflen = 0;
41
42   if (png_ptr == NULL)
43   {
44      fprintf(stderr, "iccfrompng: version library mismatch?\n");
45      return 0;
46   }
47
48   if (setjmp(png_jmpbuf(png_ptr)))
49   {
50      png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
51      return 0;
52   }
53
54   png_init_io(png_ptr, fp);
55
56   info_ptr = png_create_info_struct(png_ptr);
57   if (info_ptr == NULL)
58      png_error(png_ptr, "OOM allocating info structure");
59
60   png_read_info(png_ptr, info_ptr);
61
62   {
63      png_charp name;
64      int compression_type;
65      png_bytep profile;
66
67      if (png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile,
68         proflen) & PNG_INFO_iCCP)
69      {
70         result = malloc(*proflen);
71         if (result != NULL)
72            memcpy(result, profile, *proflen);
73
74         else
75            png_error(png_ptr, "OOM allocating profile buffer");
76      }
77
78      else
79	result = no_profile;
80   }
81
82   png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
83   return result;
84}
85
86static int
87extract_one_file(const char *filename)
88{
89   int result = 0;
90   FILE *fp = fopen(filename, "rb");
91
92   if (fp != NULL)
93   {
94      png_uint_32 proflen = 0;
95      png_bytep profile = extract(fp, &proflen);
96
97      if (profile != NULL && profile != no_profile)
98      {
99         size_t len;
100         char *output;
101
102         {
103            const char *ep = strrchr(filename, '.');
104
105            if (ep != NULL)
106               len = ep-filename;
107
108            else
109               len = strlen(filename);
110         }
111
112         output = malloc(len + 5);
113         if (output != NULL)
114         {
115            FILE *of;
116
117            memcpy(output, filename, len);
118            strcpy(output+len, ".icc");
119
120            of = fopen(output, "wb");
121            if (of != NULL)
122            {
123               if (fwrite(profile, proflen, 1, of) == 1 &&
124                  fflush(of) == 0 &&
125                  fclose(of) == 0)
126               {
127                  if (verbose)
128                     printf("%s -> %s\n", filename, output);
129                  /* Success return */
130                  result = 1;
131               }
132
133               else
134               {
135                  fprintf(stderr, "%s: error writing profile\n", output);
136                  if (remove(output))
137                     fprintf(stderr, "%s: could not remove file\n", output);
138               }
139            }
140
141            else
142               fprintf(stderr, "%s: failed to open output file\n", output);
143
144            free(output);
145         }
146
147         else
148            fprintf(stderr, "%s: OOM allocating string!\n", filename);
149
150         free(profile);
151      }
152
153      else if (verbose && profile == no_profile)
154	printf("%s has no profile\n", filename);
155   }
156
157   else
158      fprintf(stderr, "%s: could not open file\n", filename);
159
160   return result;
161}
162
163int
164main(int argc, char **argv)
165{
166   int i;
167   int extracted = 0;
168
169   for (i=1; i<argc; ++i)
170   {
171      if (strcmp(argv[i], "-q") == 0)
172         verbose = 0;
173
174      else if (extract_one_file(argv[i]))
175         extracted = 1;
176   }
177
178   /* Exit code is true if any extract succeeds */
179   return extracted == 0;
180}
181