59ae38984f
a flag to change default. In 1.6.0 when the simplified API was used to produce color-mapped output from an input image with no gamma information the gamma assumed for the input could be different from that assumed for non-color-mapped output. In particular 16-bit depth input files were assumed to be sRGB encoded, whereas in the 'direct' case they were assumed to have linear data. This was an error. The fix makes the simplified API treat all input files the same way and adds a new flag to the png_image::flags member to allow the application/user to specify that 16-bit files contain sRGB data rather than the default linear. Fixed bugs in the pngpixel and makepng test programs.
369 lines
14 KiB
C
369 lines
14 KiB
C
/*- pngpixel
|
|
*
|
|
* 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.
|
|
*
|
|
* Read a single pixel value from a PNG file.
|
|
*
|
|
* This code illustrates basic 'by-row' reading of a PNG file using libpng.
|
|
* Rows are read until a particular pixel is found, the value of this pixel is
|
|
* then printed on stdout.
|
|
*
|
|
* The code illustrates how to do this on interlaced as well as non-interlaced
|
|
* images. Normally you would call png_set_interlace_handling() to have libpng
|
|
* deal with the interlace for you, but that obliges you to buffer half of the
|
|
* image to assemble the interlaced rows. In this code
|
|
* png_set_interlace_handling() is not called and, instead, the code handles the
|
|
* interlace passes directly looking for the required pixel.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <setjmp.h> /* required for error handling */
|
|
|
|
/* Normally use <png.h> here to get the installed libpng, but this is done to
|
|
* ensure the code picks up the local libpng implementation:
|
|
*/
|
|
#include "../../png.h"
|
|
|
|
/* Return component 'c' of pixel 'x' from the given row. */
|
|
static unsigned int
|
|
component(png_const_bytep row, png_uint_32 x, unsigned int c,
|
|
unsigned int bit_depth, unsigned int channels)
|
|
{
|
|
/* PNG images can be up to 2^31 pixels wide, but this means they can be up to
|
|
* 2^37 bits wide (for a 64-bit pixel - the largest possible) and hence 2^34
|
|
* bytes wide. Since the row fitted into memory, however, the following must
|
|
* work:
|
|
*/
|
|
png_uint_32 bit_offset_hi = bit_depth * ((x >> 6) * channels);
|
|
png_uint_32 bit_offset_lo = bit_depth * ((x & 0x3f) * channels + c);
|
|
|
|
row = (png_const_bytep)(((PNG_CONST png_byte (*)[8])row) + bit_offset_hi);
|
|
row += bit_offset_lo >> 3;
|
|
bit_offset_lo &= 0x07;
|
|
|
|
/* PNG pixels are packed into bytes to put the first pixel in the highest
|
|
* bits of the byte and into two bytes for 16-bit values with the high 8 bits
|
|
* first, so:
|
|
*/
|
|
switch (bit_depth)
|
|
{
|
|
case 1: return (row[0] >> (7-bit_offset_lo)) & 0x01;
|
|
case 2: return (row[0] >> (6-bit_offset_lo)) & 0x03;
|
|
case 4: return (row[0] >> (4-bit_offset_lo)) & 0x0f;
|
|
case 8: return row[0];
|
|
case 16: return (row[0] << 8) + row[1];
|
|
default:
|
|
/* This should never happen, it indicates a bug in this program or in
|
|
* libpng itself:
|
|
*/
|
|
fprintf(stderr, "pngpixel: invalid bit depth %u\n", bit_depth);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* Print a pixel from a row returned by libpng; determine the row format, find
|
|
* the pixel, and print the relevant information to stdout.
|
|
*/
|
|
static void
|
|
print_pixel(png_structp png_ptr, png_infop info_ptr, png_const_bytep row,
|
|
png_uint_32 x)
|
|
{
|
|
PNG_CONST unsigned int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
|
|
|
switch (png_get_color_type(png_ptr, info_ptr))
|
|
{
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
printf("GRAY %u\n", component(row, x, 0, bit_depth, 1));
|
|
return;
|
|
|
|
/* The palette case is slightly more difficult - the palette and, if
|
|
* present, the tRNS ('transparency', though the values are really
|
|
* opacity) data must be read to give the full picture:
|
|
*/
|
|
case PNG_COLOR_TYPE_PALETTE:
|
|
{
|
|
PNG_CONST unsigned int index = component(row, x, 0, bit_depth, 1);
|
|
png_colorp palette = NULL;
|
|
int num_palette = 0;
|
|
|
|
if ((png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette) &
|
|
PNG_INFO_PLTE) && num_palette > 0 && palette != NULL)
|
|
{
|
|
png_bytep trans_alpha = NULL;
|
|
int num_trans = 0;
|
|
if ((png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans,
|
|
NULL) & PNG_INFO_tRNS) && num_trans > 0 &&
|
|
trans_alpha != NULL)
|
|
printf("INDEXED %u = %d %d %d %d\n", index,
|
|
palette[index].red, palette[index].green,
|
|
palette[index].blue,
|
|
index < num_trans ? trans_alpha[index] : 255);
|
|
|
|
else /* no transparency */
|
|
printf("INDEXED %u = %d %d %d\n", index,
|
|
palette[index].red, palette[index].green,
|
|
palette[index].blue);
|
|
}
|
|
|
|
else
|
|
printf("INDEXED %u = invalid index\n", index);
|
|
}
|
|
return;
|
|
|
|
case PNG_COLOR_TYPE_RGB:
|
|
printf("RGB %u %u %u\n", component(row, x, 0, bit_depth, 3),
|
|
component(row, x, 1, bit_depth, 3),
|
|
component(row, x, 2, bit_depth, 3));
|
|
return;
|
|
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
printf("GRAY+ALPHA %u %u\n", component(row, x, 0, bit_depth, 2),
|
|
component(row, x, 1, bit_depth, 2));
|
|
return;
|
|
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
printf("RGBA %u %u %u %u\n", component(row, x, 0, bit_depth, 4),
|
|
component(row, x, 1, bit_depth, 4),
|
|
component(row, x, 2, bit_depth, 4),
|
|
component(row, x, 3, bit_depth, 4));
|
|
return;
|
|
|
|
default:
|
|
png_error(png_ptr, "invalid color type");
|
|
}
|
|
}
|
|
|
|
int main(int argc, const char **argv)
|
|
{
|
|
/* This program uses the default, <setjmp.h> based, libpng error handling
|
|
* mechanism, therefore any local variable that exists before the call to
|
|
* setjmp and is changed after the call to setjmp returns successfully must
|
|
* be declared with 'volatile' to ensure that their values don't get
|
|
* destroyed by longjmp:
|
|
*/
|
|
volatile int result = 1/*fail*/;
|
|
|
|
if (argc == 4)
|
|
{
|
|
long x = atol(argv[1]);
|
|
long y = atol(argv[2]);
|
|
FILE *f = fopen(argv[3], "rb");
|
|
volatile png_bytep row = NULL;
|
|
|
|
if (f != NULL)
|
|
{
|
|
/* libpng requires a callback function for handling errors; this
|
|
* callback must not return. The default callback function uses a
|
|
* stored <setjmp.h> style jmp_buf which is held in a png_struct and
|
|
* writes error messages to stderr. Creating the png_struct is a
|
|
* little tricky; just copy the following code.
|
|
*/
|
|
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
|
NULL, NULL, NULL);
|
|
|
|
if (png_ptr != NULL)
|
|
{
|
|
png_infop info_ptr = png_create_info_struct(png_ptr);
|
|
|
|
if (info_ptr != NULL)
|
|
{
|
|
/* Declare stack variables to hold pointers to locally allocated
|
|
* data.
|
|
*/
|
|
|
|
/* Initialize the error control buffer: */
|
|
if (setjmp(png_jmpbuf(png_ptr)) == 0)
|
|
{
|
|
png_uint_32 width, height;
|
|
int bit_depth, color_type, interlace_method,
|
|
compression_method, filter_method;
|
|
png_bytep row_tmp;
|
|
|
|
/* Now associate the recently opened (FILE*) with the default
|
|
* libpng initialization functions. Sometimes libpng is
|
|
* compiled without stdio support (it can be difficult to do
|
|
* in some environments); in that case you will have to write
|
|
* your own read callback to read data from the (FILE*).
|
|
*/
|
|
png_init_io(png_ptr, f);
|
|
|
|
/* And read the first part of the PNG file - the header and
|
|
* all the information up to the first pixel.
|
|
*/
|
|
png_read_info(png_ptr, info_ptr);
|
|
|
|
/* This fills in enough information to tell us the width of
|
|
* each row in bytes, allocate the appropriate amount of
|
|
* space. In this case png_malloc is used - it will not
|
|
* return if memory isn't available.
|
|
*/
|
|
row = png_malloc(png_ptr, png_get_rowbytes(png_ptr,
|
|
info_ptr));
|
|
|
|
/* To avoid the overhead of using a volatile auto copy row_tmp
|
|
* to a local here - just use row for the png_free below.
|
|
*/
|
|
row_tmp = row;
|
|
|
|
/* All the information we need is in the header is returned by
|
|
* png_get_IHDR, if this fails we can now use 'png_error' to
|
|
* signal the error and return control to the setjmp above.
|
|
*/
|
|
if (png_get_IHDR(png_ptr, info_ptr, &width, &height,
|
|
&bit_depth, &color_type, &interlace_method,
|
|
&compression_method, &filter_method))
|
|
{
|
|
int passes, pass;
|
|
|
|
/* png_set_interlace_handling returns the number of
|
|
* passes required as well as turning on libpng's
|
|
* handling, but since we do it ourselves this is
|
|
* necessary:
|
|
*/
|
|
switch (interlace_method)
|
|
{
|
|
case PNG_INTERLACE_NONE:
|
|
passes = 1;
|
|
break;
|
|
|
|
case PNG_INTERLACE_ADAM7:
|
|
passes = PNG_INTERLACE_ADAM7_PASSES;
|
|
break;
|
|
|
|
default:
|
|
png_error(png_ptr, "pngpixel: unknown interlace");
|
|
}
|
|
|
|
/* Now read the pixels, pass-by-pass, row-by-row: */
|
|
png_start_read_image(png_ptr);
|
|
|
|
for (pass=0; pass<passes; ++pass)
|
|
{
|
|
png_uint_32 ystart, xstart, ystep, xstep;
|
|
png_uint_32 py;
|
|
|
|
if (interlace_method == PNG_INTERLACE_ADAM7)
|
|
{
|
|
/* Sometimes the whole pass is empty because the
|
|
* image is too narrow or too short. libpng
|
|
* expects to be called for each row that is
|
|
* present in the pass, so it may be necessary to
|
|
* skip the loop below (over py) if the image is
|
|
* too narrow.
|
|
*/
|
|
if (PNG_PASS_COLS(width, pass) == 0)
|
|
continue;
|
|
|
|
/* We need the starting pixel and the offset
|
|
* between each pixel in this pass; use the macros
|
|
* in png.h:
|
|
*/
|
|
xstart = PNG_PASS_START_COL(pass);
|
|
ystart = PNG_PASS_START_ROW(pass);
|
|
xstep = PNG_PASS_COL_OFFSET(pass);
|
|
ystep = PNG_PASS_ROW_OFFSET(pass);
|
|
}
|
|
|
|
else
|
|
{
|
|
ystart = xstart = 0;
|
|
ystep = xstep = 1;
|
|
}
|
|
|
|
/* To find the pixel, loop over 'py' for each pass
|
|
* reading a row and then checking to see if it
|
|
* contains the pixel.
|
|
*/
|
|
for (py = ystart; py < height; py += ystep)
|
|
{
|
|
png_uint_32 px, ppx;
|
|
|
|
/* png_read_row takes two pointers. When libpng
|
|
* handles the interlace the first is filled in
|
|
* pixel-by-pixel, and the second receives the same
|
|
* pixels but they are replicated across the
|
|
* unwritten pixels so far for each pass. When we
|
|
* do the interlace, however, they just contain
|
|
* the pixels from the interlace pass - giving
|
|
* both is wasteful and pointless, so we pass a
|
|
* NULL pointer.
|
|
*/
|
|
png_read_row(png_ptr, row_tmp, NULL);
|
|
|
|
/* Now find the pixel if it is in this row; there
|
|
* are, of course, much better ways of doing this
|
|
* than using a for loop:
|
|
*/
|
|
if (y == py) for (px = xstart, ppx = 0;
|
|
px < width; px += xstep, ++ppx) if (x == px)
|
|
{
|
|
/* 'ppx' is the index of the pixel in the row
|
|
* buffer.
|
|
*/
|
|
print_pixel(png_ptr, info_ptr, row_tmp, ppx);
|
|
|
|
/* Now terminate the loops early - we have
|
|
* found and handled the required data.
|
|
*/
|
|
goto pass_loop_end;
|
|
} /* x loop */
|
|
} /* y loop */
|
|
} /* pass loop */
|
|
|
|
/* Finally free the temporary buffer: */
|
|
pass_loop_end:
|
|
row = NULL;
|
|
png_free(png_ptr, row_tmp);
|
|
}
|
|
|
|
else
|
|
png_error(png_ptr, "pngpixel: png_get_IHDR failed");
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
/* Else libpng has raised an error. An error message has
|
|
* already been output, so it is only necessary to clean up
|
|
* locally allocated data:
|
|
*/
|
|
if (row != NULL)
|
|
{
|
|
/* The default implementation of png_free never errors out
|
|
* (it just crashes if something goes wrong), but the safe
|
|
* way of using it is still to clear 'row' before calling
|
|
* png_free:
|
|
*/
|
|
png_bytep row_tmp = row;
|
|
row = NULL;
|
|
png_free(png_ptr, row_tmp);
|
|
}
|
|
}
|
|
|
|
png_destroy_info_struct(png_ptr, &info_ptr);
|
|
}
|
|
|
|
else
|
|
fprintf(stderr, "pngpixel: out of memory allocating png_info\n");
|
|
|
|
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
|
}
|
|
|
|
else
|
|
fprintf(stderr, "pngpixel: out of memory allocating png_struct\n");
|
|
}
|
|
|
|
else
|
|
fprintf(stderr, "pngpixel: %s: could not open file\n", argv[3]);
|
|
}
|
|
|
|
else
|
|
/* Wrong number of arguments */
|
|
fprintf(stderr, "pngpixel: usage: pngpixel x y png-file\n");
|
|
|
|
return result;
|
|
}
|