1 /*- pngpixel
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  * Read a single pixel value from a PNG file.
9  *
10  * This code illustrates basic 'by-row' reading of a PNG file using libpng.
11  * Rows are read until a particular pixel is found; the value of this pixel is
12  * then printed on stdout.
13  *
14  * The code illustrates how to do this on interlaced as well as non-interlaced
15  * images.  Normally you would call png_set_interlace_handling() to have libpng
16  * deal with the interlace for you, but that obliges you to buffer half of the
17  * image to assemble the interlaced rows.  In this code
18  * png_set_interlace_handling() is not called and, instead, the code handles the
19  * interlace passes directly looking for the required pixel.
20  */
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <setjmp.h> /* required for error handling */
24 
25 /* Normally use <png.h> here to get the installed libpng, but this is done to
26  * ensure the code picks up the local libpng implementation:
27  */
28 #include "../../png.h"
29 
30 #if defined(PNG_READ_SUPPORTED) && defined(PNG_SEQUENTIAL_READ_SUPPORTED)
31 
32 /* Return component 'c' of pixel 'x' from the given row. */
33 static unsigned int
component(png_const_bytep row,png_uint_32 x,unsigned int c,unsigned int bit_depth,unsigned int channels)34 component(png_const_bytep row, png_uint_32 x, unsigned int c,
35    unsigned int bit_depth, unsigned int channels)
36 {
37    /* PNG images can be up to 2^31 pixels wide, but this means they can be up to
38     * 2^37 bits wide (for a 64-bit pixel - the largest possible) and hence 2^34
39     * bytes wide.  Since the row fitted into memory, however, the following must
40     * work:
41     */
42    png_uint_32 bit_offset_hi = bit_depth * ((x >> 6) * channels);
43    png_uint_32 bit_offset_lo = bit_depth * ((x & 0x3f) * channels + c);
44 
45    row = (png_const_bytep)(((const png_byte (*)[8])row) + bit_offset_hi);
46    row += bit_offset_lo >> 3;
47    bit_offset_lo &= 0x07;
48 
49    /* PNG pixels are packed into bytes to put the first pixel in the highest
50     * bits of the byte and into two bytes for 16-bit values with the high 8 bits
51     * first, so:
52     */
53    switch (bit_depth)
54    {
55       case 1: return (row[0] >> (7-bit_offset_lo)) & 0x01;
56       case 2: return (row[0] >> (6-bit_offset_lo)) & 0x03;
57       case 4: return (row[0] >> (4-bit_offset_lo)) & 0x0f;
58       case 8: return row[0];
59       case 16: return (row[0] << 8) + row[1];
60       default:
61          /* This should never happen; it indicates a bug in this program or in
62           * libpng itself:
63           */
64          fprintf(stderr, "pngpixel: invalid bit depth %u\n", bit_depth);
65          exit(1);
66    }
67 }
68 
69 /* Print a pixel from a row returned by libpng; determine the row format, find
70  * the pixel, and print the relevant information to stdout.
71  */
72 static void
print_pixel(png_structp png_ptr,png_infop info_ptr,png_const_bytep row,png_uint_32 x)73 print_pixel(png_structp png_ptr, png_infop info_ptr, png_const_bytep row,
74    png_uint_32 x)
75 {
76    unsigned int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
77 
78    switch (png_get_color_type(png_ptr, info_ptr))
79    {
80       case PNG_COLOR_TYPE_GRAY:
81          printf("GRAY %u\n", component(row, x, 0, bit_depth, 1));
82          return;
83 
84       /* The palette case is slightly more difficult - the palette and, if
85        * present, the tRNS ('transparency', though the values are really
86        * opacity) data must be read to give the full picture:
87        */
88       case PNG_COLOR_TYPE_PALETTE:
89          {
90             int index = component(row, x, 0, bit_depth, 1);
91             png_colorp palette = NULL;
92             int num_palette = 0;
93 
94             if ((png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette) &
95                PNG_INFO_PLTE) && num_palette > 0 && palette != NULL)
96             {
97                png_bytep trans_alpha = NULL;
98                int num_trans = 0;
99                if ((png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans,
100                   NULL) & PNG_INFO_tRNS) && num_trans > 0 &&
101                   trans_alpha != NULL)
102                   printf("INDEXED %u = %d %d %d %d\n", index,
103                      palette[index].red, palette[index].green,
104                      palette[index].blue,
105                      index < num_trans ? trans_alpha[index] : 255);
106 
107                else /* no transparency */
108                   printf("INDEXED %u = %d %d %d\n", index,
109                      palette[index].red, palette[index].green,
110                      palette[index].blue);
111             }
112 
113             else
114                printf("INDEXED %u = invalid index\n", index);
115          }
116          return;
117 
118       case PNG_COLOR_TYPE_RGB:
119          printf("RGB %u %u %u\n", component(row, x, 0, bit_depth, 3),
120             component(row, x, 1, bit_depth, 3),
121             component(row, x, 2, bit_depth, 3));
122          return;
123 
124       case PNG_COLOR_TYPE_GRAY_ALPHA:
125          printf("GRAY+ALPHA %u %u\n", component(row, x, 0, bit_depth, 2),
126             component(row, x, 1, bit_depth, 2));
127          return;
128 
129       case PNG_COLOR_TYPE_RGB_ALPHA:
130          printf("RGBA %u %u %u %u\n", component(row, x, 0, bit_depth, 4),
131             component(row, x, 1, bit_depth, 4),
132             component(row, x, 2, bit_depth, 4),
133             component(row, x, 3, bit_depth, 4));
134          return;
135 
136       default:
137          png_error(png_ptr, "pngpixel: invalid color type");
138    }
139 }
140 
main(int argc,const char ** argv)141 int main(int argc, const char **argv)
142 {
143    /* This program uses the default, <setjmp.h> based, libpng error handling
144     * mechanism, therefore any local variable that exists before the call to
145     * setjmp and is changed after the call to setjmp returns successfully must
146     * be declared with 'volatile' to ensure that their values don't get
147     * destroyed by longjmp:
148     */
149    volatile int result = 1/*fail*/;
150 
151    if (argc == 4)
152    {
153       long x = atol(argv[1]);
154       long y = atol(argv[2]);
155       FILE *f = fopen(argv[3], "rb");
156       volatile png_bytep row = NULL;
157 
158       if (f != NULL)
159       {
160          /* libpng requires a callback function for handling errors; this
161           * callback must not return.  The default callback function uses a
162           * stored <setjmp.h> style jmp_buf which is held in a png_struct and
163           * writes error messages to stderr.  Creating the png_struct is a
164           * little tricky; just copy the following code.
165           */
166          png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
167             NULL, NULL, NULL);
168 
169          if (png_ptr != NULL)
170          {
171             png_infop info_ptr = png_create_info_struct(png_ptr);
172 
173             if (info_ptr != NULL)
174             {
175                /* Declare stack variables to hold pointers to locally allocated
176                 * data.
177                 */
178 
179                /* Initialize the error control buffer: */
180                if (setjmp(png_jmpbuf(png_ptr)) == 0)
181                {
182                   png_uint_32 width, height;
183                   int bit_depth, color_type, interlace_method,
184                      compression_method, filter_method;
185                   png_bytep row_tmp;
186 
187                   /* Now associate the recently opened (FILE*) with the default
188                    * libpng initialization functions.  Sometimes libpng is
189                    * compiled without stdio support (it can be difficult to do
190                    * in some environments); in that case you will have to write
191                    * your own read callback to read data from the (FILE*).
192                    */
193                   png_init_io(png_ptr, f);
194 
195                   /* And read the first part of the PNG file - the header and
196                    * all the information up to the first pixel.
197                    */
198                   png_read_info(png_ptr, info_ptr);
199 
200                   /* This fills in enough information to tell us the width of
201                    * each row in bytes, allocate the appropriate amount of
202                    * space.  In this case png_malloc is used - it will not
203                    * return if memory isn't available.
204                    */
205                   row = png_malloc(png_ptr, png_get_rowbytes(png_ptr,
206                      info_ptr));
207 
208                   /* To avoid the overhead of using a volatile auto copy row_tmp
209                    * to a local here - just use row for the png_free below.
210                    */
211                   row_tmp = row;
212 
213                   /* All the information we need is in the header is returned by
214                    * png_get_IHDR, if this fails we can now use 'png_error' to
215                    * signal the error and return control to the setjmp above.
216                    */
217                   if (png_get_IHDR(png_ptr, info_ptr, &width, &height,
218                      &bit_depth, &color_type, &interlace_method,
219                      &compression_method, &filter_method))
220                   {
221                      int passes, pass;
222 
223                      /* png_set_interlace_handling returns the number of
224                       * passes required as well as turning on libpng's
225                       * handling, but since we do it ourselves this is
226                       * necessary:
227                       */
228                      switch (interlace_method)
229                      {
230                         case PNG_INTERLACE_NONE:
231                            passes = 1;
232                            break;
233 
234                         case PNG_INTERLACE_ADAM7:
235                            passes = PNG_INTERLACE_ADAM7_PASSES;
236                            break;
237 
238                         default:
239                            png_error(png_ptr, "pngpixel: unknown interlace");
240                      }
241 
242                      /* Now read the pixels, pass-by-pass, row-by-row: */
243                      png_start_read_image(png_ptr);
244 
245                      for (pass=0; pass<passes; ++pass)
246                      {
247                         png_uint_32 ystart, xstart, ystep, xstep;
248                         png_uint_32 py;
249 
250                         if (interlace_method == PNG_INTERLACE_ADAM7)
251                         {
252                            /* Sometimes the whole pass is empty because the
253                             * image is too narrow or too short.  libpng
254                             * expects to be called for each row that is
255                             * present in the pass, so it may be necessary to
256                             * skip the loop below (over py) if the image is
257                             * too narrow.
258                             */
259                            if (PNG_PASS_COLS(width, pass) == 0)
260                               continue;
261 
262                            /* We need the starting pixel and the offset
263                             * between each pixel in this pass; use the macros
264                             * in png.h:
265                             */
266                            xstart = PNG_PASS_START_COL(pass);
267                            ystart = PNG_PASS_START_ROW(pass);
268                            xstep = PNG_PASS_COL_OFFSET(pass);
269                            ystep = PNG_PASS_ROW_OFFSET(pass);
270                         }
271 
272                         else
273                         {
274                            ystart = xstart = 0;
275                            ystep = xstep = 1;
276                         }
277 
278                         /* To find the pixel, loop over 'py' for each pass
279                          * reading a row and then checking to see if it
280                          * contains the pixel.
281                          */
282                         for (py = ystart; py < height; py += ystep)
283                         {
284                            png_uint_32 px, ppx;
285 
286                            /* png_read_row takes two pointers.  When libpng
287                             * handles the interlace the first is filled in
288                             * pixel-by-pixel, and the second receives the same
289                             * pixels but they are replicated across the
290                             * unwritten pixels so far for each pass.  When we
291                             * do the interlace, however, they just contain
292                             * the pixels from the interlace pass - giving
293                             * both is wasteful and pointless, so we pass a
294                             * NULL pointer.
295                             */
296                            png_read_row(png_ptr, row_tmp, NULL);
297 
298                            /* Now find the pixel if it is in this row; there
299                             * are, of course, much better ways of doing this
300                             * than using a for loop:
301                             */
302                            if (y == py) for (px = xstart, ppx = 0;
303                               px < width; px += xstep, ++ppx) if (x == px)
304                            {
305                               /* 'ppx' is the index of the pixel in the row
306                                * buffer.
307                                */
308                               print_pixel(png_ptr, info_ptr, row_tmp, ppx);
309 
310                               /* Now terminate the loops early - we have
311                                * found and handled the required data.
312                                */
313                               goto pass_loop_end;
314                            } /* x loop */
315                         } /* y loop */
316                      } /* pass loop */
317 
318                      /* Finally free the temporary buffer: */
319                   pass_loop_end:
320                      row = NULL;
321                      png_free(png_ptr, row_tmp);
322                   }
323 
324                   else
325                      png_error(png_ptr, "pngpixel: png_get_IHDR failed");
326 
327                }
328 
329                else
330                {
331                   /* Else libpng has raised an error.  An error message has
332                    * already been output, so it is only necessary to clean up
333                    * locally allocated data:
334                    */
335                   if (row != NULL)
336                   {
337                      /* The default implementation of png_free never errors out
338                       * (it just crashes if something goes wrong), but the safe
339                       * way of using it is still to clear 'row' before calling
340                       * png_free:
341                       */
342                      png_bytep row_tmp = row;
343                      row = NULL;
344                      png_free(png_ptr, row_tmp);
345                   }
346                }
347 
348                png_destroy_info_struct(png_ptr, &info_ptr);
349             }
350 
351             else
352                fprintf(stderr, "pngpixel: out of memory allocating png_info\n");
353 
354             png_destroy_read_struct(&png_ptr, NULL, NULL);
355          }
356 
357          else
358             fprintf(stderr, "pngpixel: out of memory allocating png_struct\n");
359       }
360 
361       else
362          fprintf(stderr, "pngpixel: %s: could not open file\n", argv[3]);
363    }
364 
365    else
366       /* Wrong number of arguments */
367       fprintf(stderr, "pngpixel: usage: pngpixel x y png-file\n");
368 
369    return result;
370 }
371 #endif /* READ && SEQUENTIAL_READ */
372