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