diff options
Diffstat (limited to 'libpng/contrib/tools')
-rw-r--r-- | libpng/contrib/tools/README.txt | 3 | ||||
-rwxr-xr-x | libpng/contrib/tools/chkfmt | 7 | ||||
-rw-r--r-- | libpng/contrib/tools/genpng.c | 867 | ||||
-rw-r--r-- | libpng/contrib/tools/png-fix-itxt.c | 69 | ||||
-rw-r--r-- | libpng/contrib/tools/pngcp.c | 2453 | ||||
-rw-r--r-- | libpng/contrib/tools/pngfix.c | 81 | ||||
-rwxr-xr-x | libpng/contrib/tools/reindent | 25 |
7 files changed, 3437 insertions, 68 deletions
diff --git a/libpng/contrib/tools/README.txt b/libpng/contrib/tools/README.txt index 5ddae02ce..f53be6df9 100644 --- a/libpng/contrib/tools/README.txt +++ b/libpng/contrib/tools/README.txt @@ -23,4 +23,5 @@ ORIGINAL AUTHORS of the people below claim any rights with regard to the contents of this directory. - John Bowler <jbowler@acm.org> + John Bowler <jbowler at acm.org> + Glenn Randers-Pehrson <glennrp at users.sourceforge.net> diff --git a/libpng/contrib/tools/chkfmt b/libpng/contrib/tools/chkfmt index 9da6475fd..a1b88ccbf 100755 --- a/libpng/contrib/tools/chkfmt +++ b/libpng/contrib/tools/chkfmt @@ -1,4 +1,11 @@ #!/bin/sh + +# chkfmt +# +# COPYRIGHT: Written by John Cunningham Bowler, 2010. +# 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. # # Check the format of the source files in the current directory - checks for a # line length of 80 characters max and no tab characters. diff --git a/libpng/contrib/tools/genpng.c b/libpng/contrib/tools/genpng.c new file mode 100644 index 000000000..ce43260cb --- /dev/null +++ b/libpng/contrib/tools/genpng.c @@ -0,0 +1,867 @@ +/*- genpng + * + * COPYRIGHT: Written by John Cunningham Bowler, 2015. + * 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. + * + * Generate a PNG with an alpha channel, correctly. + * + * This is a test case generator; the resultant PNG files are only of interest + * to those of us who care about whether the edges of circles are green, red, + * or yellow. + * + * The program generates an RGB+Alpha PNG of a given size containing the given + * shapes on a transparent background: + * + * genpng width height { shape } + * shape ::= color width shape x1 y1 x2 y2 + * + * 'color' is: + * + * black white red green yellow blue brown purple pink orange gray cyan + * + * The point is to have colors that are linguistically meaningful plus that old + * bugbear of the department store dress murders, Cyan, the only color we argue + * about. + * + * 'shape' is: + * + * circle: an ellipse + * square: a rectangle + * line: a straight line + * + * Each shape is followed by four numbers, these are two points in the output + * coordinate space (as real numbers) which describe the circle, square, or + * line. The shape is filled if it is preceded by 'filled' (not valid for + * 'line') or is drawn with a line, in which case the width of the line must + * precede the shape. + * + * The whole set of information can be repeated as many times as desired: + * + * shape ::= color width shape x1 y1 x2 y2 + * + * color ::= black|white|red|green|yellow|blue + * color ::= brown|purple|pink|orange|gray|cyan + * width ::= filled + * width ::= <number> + * shape ::= circle|square|line + * x1 ::= <number> + * x2 ::= <number> + * y1 ::= <number> + * y2 ::= <number> + * + * The output PNG is generated by down-sampling a 4x supersampled image using + * a bi-cubic filter. The bi-cubic has a 2 (output) pixel width, so an 8x8 + * array of super-sampled points contribute to each output pixel. The value of + * a super-sampled point is found using an unfiltered, aliased, infinite + * precision image: Each shape from the last to the first is checked to see if + * the point is in the drawn area and, if it is, the color of the point is the + * color of the shape and the alpha is 1, if not the previous shape is checked. + * + * This is an aliased algorithm because no filtering is done; a point is either + * inside or outside each shape and 'close' points do not contribute to the + * sample. The down-sampling is relied on to correct the error of not using + * a filter. + * + * The line end-caps are 'flat'; they go through the points. The square line + * joins are mitres; the outside of the lines are continued to the point of + * intersection. + */ +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <math.h> + +/* 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" + +#if defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) && defined(PNG_STDIO_SUPPORTED) + +static const struct color +{ + const char *name; + double red; + double green; + double blue; +} colors[] = +/* color ::= black|white|red|green|yellow|blue + * color ::= brown|purple|pink|orange|gray|cyan + */ +{ + { "black", 0, 0, 0 }, + { "white", 1, 1, 1 }, + { "red", 1, 0, 0 }, + { "green", 0, 1, 0 }, + { "yellow", 1, 1, 0 }, + { "blue", 0, 0, 1 }, + { "brown", .5, .125, 0 }, + { "purple", 1, 0, 1 }, + { "pink", 1, .5, .5 }, + { "orange", 1, .5, 0 }, + { "gray", 0, .5, .5 }, + { "cyan", 0, 1, 1 } +}; +#define color_count ((sizeof colors)/(sizeof colors[0])) + +static const struct color * +color_of(const char *arg) +{ + int icolor = color_count; + + while (--icolor >= 0) + { + if (strcmp(colors[icolor].name, arg) == 0) + return colors+icolor; + } + + fprintf(stderr, "genpng: invalid color %s\n", arg); + exit(1); +} + +static double +width_of(const char *arg) +{ + if (strcmp(arg, "filled") == 0) + return 0; + + else + { + char *ep = NULL; + double w = strtod(arg, &ep); + + if (ep != NULL && *ep == 0 && w > 0) + return w; + } + + fprintf(stderr, "genpng: invalid line width %s\n", arg); + exit(1); +} + +static double +coordinate_of(const char *arg) +{ + char *ep = NULL; + double w = strtod(arg, &ep); + + if (ep != NULL && *ep == 0) + return w; + + fprintf(stderr, "genpng: invalid coordinate value %s\n", arg); + exit(1); +} + +struct arg; /* forward declaration */ + +typedef int (*shape_fn_ptr)(const struct arg *arg, double x, double y); + /* A function to determine if (x,y) is inside the shape. + * + * There are two implementations: + * + * inside_fn: returns true if the point is inside + * check_fn: returns; + * -1: the point is outside the shape by more than the filter width (2) + * 0: the point may be inside the shape + * +1: the point is inside the shape by more than the filter width + */ +#define OUTSIDE (-1) +#define INSIDE (1) + +struct arg +{ + const struct color *color; + shape_fn_ptr inside_fn; + shape_fn_ptr check_fn; + double width; /* line width, 0 for 'filled' */ + double x1, y1, x2, y2; +}; + +/* IMPLEMENTATION NOTE: + * + * We want the contribution of each shape to the sample corresponding to each + * pixel. This could be obtained by super sampling the image to infinite + * dimensions, finding each point within the shape and assigning that a value + * '1' while leaving every point outside the shape with value '0' then + * downsampling to the image size with sinc; computationally very expensive. + * + * Approximations are as follows: + * + * 1) If the pixel coordinate is within the shape assume the sample has the + * shape color and is opaque, else assume there is no contribution from + * the shape. + * + * This is the equivalent of aliased rendering or resampling an image with + * a block filter. The maximum error in the calculated alpha (which will + * always be 0 or 1) is 0.5. + * + * 2) If the shape is within a square of size 1x1 centered on the pixel assume + * that the shape obscures an amount of the pixel equal to its area within + * that square. + * + * This is the equivalent of 'pixel coverage' alpha calculation or resampling + * an image with a bi-linear filter. The maximum error is over 0.2, but the + * results are often acceptable. + * + * This can be approximated by applying (1) to a super-sampled image then + * downsampling with a bi-linear filter. The error in the super-sampled + * image is 0.5 per sample, but the resampling reduces this. + * + * 3) Use a better filter with a super-sampled image; in the limit this is the + * sinc() approach. + * + * 4) Do the geometric calculation; a bivariate definite integral across the + * shape, unfortunately this means evaluating Si(x), the integral of sinc(x), + * which is still a lot of math. + * + * This code uses approach (3) with a bi-cubic filter and 8x super-sampling + * and method (1) for the super-samples. This means that the sample is either + * 0 or 1, depending on whether the sub-pixel is within or outside the shape. + * The bi-cubic weights are also fixed and the 16 required weights are + * pre-computed here (note that the 'scale' setting will need to be changed if + * 'super' is increased). + * + * The code also calculates a sum to the edge of the filter. This is not + * currently used by could be used to optimize the calculation. + */ +#if 0 /* bc code */ +scale=10 +super=8 +define bicubic(x) { + if (x <= 1) return (1.5*x - 2.5)*x*x + 1; + if (x < 2) return (((2.5 - 0.5*x)*x - 4)*x + 2); + return 0; +} +define sum(x) { + auto s; + s = 0; + while (x < 2*super) { + s = s + bicubic(x/super); + x = x + 1; + } + return s; +} +define results(x) { + auto b, s; + b = bicubic(x/super); + s = sum(x); + + print " /*", x, "*/ { ", b, ", ", s, " }"; + return 1; +} +x=0 +while (x<2*super) { + x = x + results(x) + if (x < 2*super) print "," + print "\n" +} +quit +#endif + +#define BICUBIC1(x) /* |x| <= 1 */ ((1.5*(x)* - 2.5)*(x)*(x) + 1) +#define BICUBIC2(x) /* 1 < |x| < 2 */ (((2.5 - 0.5*(x))*(x) - 4)*(x) + 2) +#define FILTER_WEIGHT 9 /* Twice the first sum below */ +#define FILTER_WIDTH 2 /* Actually half the width; -2..+2 */ +#define FILTER_STEPS 8 /* steps per filter unit */ +static const double +bicubic[16][2] = +{ + /* These numbers are exact; the weight for the filter is 1/9, but this + * would make the numbers inexact, so it is not included here. + */ + /* bicubic sum */ + /* 0*/ { 1.0000000000, 4.5000000000 }, + /* 1*/ { .9638671875, 3.5000000000 }, + /* 2*/ { .8671875000, 2.5361328125 }, + /* 3*/ { .7275390625, 1.6689453125 }, + /* 4*/ { .5625000000, .9414062500 }, + /* 5*/ { .3896484375, .3789062500 }, + /* 6*/ { .2265625000, -.0107421875 }, + /* 7*/ { .0908203125, -.2373046875 }, + /* 8*/ { 0, -.3281250000 }, + /* 9*/ { -.0478515625, -.3281250000 }, + /*10*/ { -.0703125000, -.2802734375 }, + /*11*/ { -.0732421875, -.2099609375 }, + /*12*/ { -.0625000000, -.1367187500 }, + /*13*/ { -.0439453125, -.0742187500 }, + /*14*/ { -.0234375000, -.0302734375 }, + /*15*/ { -.0068359375, -.0068359375 } +}; + +static double +alpha_calc(const struct arg *arg, double x, double y) +{ + /* For [x-2..x+2],[y-2,y+2] calculate the weighted bicubic given a function + * which tells us whether a point is inside or outside the shape. First + * check if we need to do this at all: + */ + switch (arg->check_fn(arg, x, y)) + { + case OUTSIDE: + return 0; /* all samples outside the shape */ + + case INSIDE: + return 1; /* all samples inside the shape */ + + default: + { + int dy; + double alpha = 0; + +# define FILTER_D (FILTER_WIDTH*FILTER_STEPS-1) + for (dy=-FILTER_D; dy<=FILTER_D; ++dy) + { + double wy = bicubic[abs(dy)][0]; + + if (wy != 0) + { + double alphay = 0; + int dx; + + for (dx=-FILTER_D; dx<=FILTER_D; ++dx) + { + double wx = bicubic[abs(dx)][0]; + + if (wx != 0 && arg->inside_fn(arg, x+dx/16, y+dy/16)) + alphay += wx; + } + + alpha += wy * alphay; + } + } + + /* This needs to be weighted for each dimension: */ + return alpha / (FILTER_WEIGHT*FILTER_WEIGHT); + } + } +} + +/* These are the shape functions. */ +/* "square", + * { inside_square_filled, check_square_filled }, + * { inside_square, check_square } + */ +static int +square_check(double x, double y, double x1, double y1, double x2, double y2) + /* Is x,y inside the square (x1,y1)..(x2,y2)? */ +{ + /* Do a modified Cohen-Sutherland on one point, bit patterns that indicate + * 'outside' are: + * + * x<x1 | x<y1 | x<x2 | x<y2 + * 0 x 0 x To the right + * 1 x 1 x To the left + * x 0 x 0 Below + * x 1 x 1 Above + * + * So 'inside' is (x<x1) != (x<x2) && (y<y1) != (y<y2); + */ + return ((x<x1) ^ (x<x2)) & ((y<y1) ^ (y<y2)); +} + +static int +inside_square_filled(const struct arg *arg, double x, double y) +{ + return square_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2); +} + +static int +square_check_line(const struct arg *arg, double x, double y, double w) + /* Check for a point being inside the boundaries implied by the given arg + * and assuming a width 2*w each side of the boundaries. This returns the + * 'check' INSIDE/OUTSIDE/0 result but note the semantics: + * + * +--------------+ + * | | OUTSIDE + * | INSIDE | + * | | + * +--------------+ + * + * And '0' means within the line boundaries. + */ +{ + double cx = (arg->x1+arg->x2)/2; + double wx = fabs(arg->x1-arg->x2)/2; + double cy = (arg->y1+arg->y2)/2; + double wy = fabs(arg->y1-arg->y2)/2; + + if (square_check(x, y, cx-wx-w, cy-wy-w, cx+wx+w, cy+wy+w)) + { + /* Inside, but maybe too far; check for the redundant case where + * the lines overlap: + */ + wx -= w; + wy -= w; + if (wx > 0 && wy > 0 && square_check(x, y, cx-wx, cy-wy, cx+wx, cy+wy)) + return INSIDE; /* between (inside) the boundary lines. */ + + return 0; /* inside the lines themselves. */ + } + + return OUTSIDE; /* outside the boundary lines. */ +} + +static int +check_square_filled(const struct arg *arg, double x, double y) +{ + /* The filter extends +/-FILTER_WIDTH each side of each output point, so + * the check has to expand and contract the square by that amount; '0' + * means close enough to the edge of the square that the bicubic filter has + * to be run, OUTSIDE means alpha==0, INSIDE means alpha==1. + */ + return square_check_line(arg, x, y, FILTER_WIDTH); +} + +static int +inside_square(const struct arg *arg, double x, double y) +{ + /* Return true if within the drawn lines, else false, no need to distinguish + * INSIDE vs OUTSIDE here: + */ + return square_check_line(arg, x, y, arg->width/2) == 0; +} + +static int +check_square(const struct arg *arg, double x, double y) +{ + /* So for this function a result of 'INSIDE' means inside the actual lines. + */ + double w = arg->width/2; + + if (square_check_line(arg, x, y, w+FILTER_WIDTH) == 0) + { + /* Somewhere close to the boundary lines. If far enough inside one of + * them then we can return INSIDE: + */ + w -= FILTER_WIDTH; + + if (w > 0 && square_check_line(arg, x, y, w) == 0) + return INSIDE; + + /* Point is somewhere in the filter region: */ + return 0; + } + + else /* Inside or outside the square by more than w+FILTER_WIDTH. */ + return OUTSIDE; +} + +/* "circle", + * { inside_circle_filled, check_circle_filled }, + * { inside_circle, check_circle } + * + * The functions here are analoguous to the square ones; however, they check + * the corresponding ellipse as opposed to the rectangle. + */ +static int +circle_check(double x, double y, double x1, double y1, double x2, double y2) +{ + if (square_check(x, y, x1, y1, x2, y2)) + { + /* Inside the square, so maybe inside the circle too: */ + const double cx = (x1 + x2)/2; + const double cy = (y1 + y2)/2; + const double dx = x1 - x2; + const double dy = y1 - y2; + + x = (x - cx)/dx; + y = (y - cy)/dy; + + /* It is outside if the distance from the center is more than half the + * diameter: + */ + return x*x+y*y < .25; + } + + return 0; /* outside */ +} + +static int +inside_circle_filled(const struct arg *arg, double x, double y) +{ + return circle_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2); +} + +static int +circle_check_line(const struct arg *arg, double x, double y, double w) + /* Check for a point being inside the boundaries implied by the given arg + * and assuming a width 2*w each side of the boundaries. This function has + * the same semantic as square_check_line but tests the circle. + */ +{ + double cx = (arg->x1+arg->x2)/2; + double wx = fabs(arg->x1-arg->x2)/2; + double cy = (arg->y1+arg->y2)/2; + double wy = fabs(arg->y1-arg->y2)/2; + + if (circle_check(x, y, cx-wx-w, cy-wy-w, cx+wx+w, cy+wy+w)) + { + /* Inside, but maybe too far; check for the redundant case where + * the lines overlap: + */ + wx -= w; + wy -= w; + if (wx > 0 && wy > 0 && circle_check(x, y, cx-wx, cy-wy, cx+wx, cy+wy)) + return INSIDE; /* between (inside) the boundary lines. */ + + return 0; /* inside the lines themselves. */ + } + + return OUTSIDE; /* outside the boundary lines. */ +} + +static int +check_circle_filled(const struct arg *arg, double x, double y) +{ + return circle_check_line(arg, x, y, FILTER_WIDTH); +} + +static int +inside_circle(const struct arg *arg, double x, double y) +{ + return circle_check_line(arg, x, y, arg->width/2) == 0; +} + +static int +check_circle(const struct arg *arg, double x, double y) +{ + /* Exactly as the 'square' code. */ + double w = arg->width/2; + + if (circle_check_line(arg, x, y, w+FILTER_WIDTH) == 0) + { + w -= FILTER_WIDTH; + + if (w > 0 && circle_check_line(arg, x, y, w) == 0) + return INSIDE; + + /* Point is somewhere in the filter region: */ + return 0; + } + + else /* Inside or outside the square by more than w+FILTER_WIDTH. */ + return OUTSIDE; +} + +/* "line", + * { NULL, NULL }, There is no 'filled' line. + * { inside_line, check_line } + */ +static int +line_check(double x, double y, double x1, double y1, double x2, double y2, + double w, double expand) +{ + /* Shift all the points to (arg->x1, arg->y1) */ + double lx = x2 - x1; + double ly = y2 - y1; + double len2 = lx*lx + ly*ly; + double cross, dot; + + x -= x1; + y -= y1; + + /* The dot product is the distance down the line, the cross product is + * the distance away from the line: + * + * distance = |cross| / sqrt(len2) + */ + cross = x * ly - y * lx; + + /* If 'distance' is more than w the point is definitely outside the line: + * + * distance >= w + * |cross| >= w * sqrt(len2) + * cross^2 >= w^2 * len2: + */ + if (cross*cross >= (w+expand)*(w+expand)*len2) + return 0; /* outside */ + + /* Now find the distance *along* the line; this comes from the dot product + * lx.x+ly.y. The actual distance (in pixels) is: + * + * distance = dot / sqrt(len2) + */ + dot = lx * x + ly * y; + + /* The test for 'outside' is: + * + * distance < 0 || distance > sqrt(len2) + * -> dot / sqrt(len2) > sqrt(len2) + * -> dot > len2 + * + * But 'expand' is used for the filter width and needs to be handled too: + */ + return dot > -expand && dot < len2+expand; +} + +static int +inside_line(const struct arg *arg, double x, double y) +{ + return line_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2, arg->width/2, 0); +} + +static int +check_line(const struct arg *arg, double x, double y) +{ + /* The end caps of the line must be checked too; it's not enough just to + * widen the line by FILTER_WIDTH; 'expand' exists for this purpose: + */ + if (line_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2, arg->width/2, + FILTER_WIDTH)) + { + /* Inside the line+filter; far enough inside that the filter isn't + * required? + */ + if (arg->width > 2*FILTER_WIDTH && + line_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2, arg->width/2, + -FILTER_WIDTH)) + return INSIDE; + + return 0; + } + + return OUTSIDE; +} + +static const struct +{ + const char *name; + shape_fn_ptr function[2/*fill,line*/][2]; +# define FN_INSIDE 0 +# define FN_CHECK 1 +} shape_defs[] = +{ + { "square", + { { inside_square_filled, check_square_filled }, + { inside_square, check_square } } + }, + { "circle", + { { inside_circle_filled, check_circle_filled }, + { inside_circle, check_circle } } + }, + { "line", + { { NULL, NULL }, + { inside_line, check_line } } + } +}; + +#define shape_count ((sizeof shape_defs)/(sizeof shape_defs[0])) + +static shape_fn_ptr +shape_of(const char *arg, double width, int f) +{ + unsigned int i; + + for (i=0; i<shape_count; ++i) if (strcmp(shape_defs[i].name, arg) == 0) + { + shape_fn_ptr fn = shape_defs[i].function[width != 0][f]; + + if (fn != NULL) + return fn; + + fprintf(stderr, "genpng: %s %s not supported\n", + width == 0 ? "filled" : "unfilled", arg); + exit(1); + } + + fprintf(stderr, "genpng: %s: not a valid shape name\n", arg); + exit(1); +} + +static void +parse_arg(struct arg *arg, const char **argv/*7 arguments*/) +{ + /* shape ::= color width shape x1 y1 x2 y2 */ + arg->color = color_of(argv[0]); + arg->width = width_of(argv[1]); + arg->inside_fn = shape_of(argv[2], arg->width, FN_INSIDE); + arg->check_fn = shape_of(argv[2], arg->width, FN_CHECK); + arg->x1 = coordinate_of(argv[3]); + arg->y1 = coordinate_of(argv[4]); + arg->x2 = coordinate_of(argv[5]); + arg->y2 = coordinate_of(argv[6]); +} + +static png_uint_32 +read_wh(const char *name, const char *str) + /* read a PNG width or height */ +{ + char *ep = NULL; + unsigned long ul = strtoul(str, &ep, 10); + + if (ep != NULL && *ep == 0 && ul > 0 && ul <= 0x7fffffff) + return (png_uint_32)/*SAFE*/ul; + + fprintf(stderr, "genpng: %s: invalid number %s\n", name, str); + exit(1); +} + +static void +pixel(png_uint_16p p, struct arg *args, int nargs, double x, double y) +{ + /* Fill in the pixel by checking each shape (args[nargs]) for effects on + * the corresponding sample: + */ + double r=0, g=0, b=0, a=0; + + while (--nargs >= 0 && a != 1) + { + /* NOTE: alpha_calc can return a value outside the range 0..1 with the + * bicubic filter. + */ + const double alpha = alpha_calc(args+nargs, x, y) * (1-a); + + r += alpha * args[nargs].color->red; + g += alpha * args[nargs].color->green; + b += alpha * args[nargs].color->blue; + a += alpha; + } + + /* 'a' may be negative or greater than 1; if it is, negative clamp the + * pixel to 0 if >1 clamp r/g/b: + */ + if (a > 0) + { + if (a > 1) + { + if (r > 1) r = 1; + if (g > 1) g = 1; + if (b > 1) b = 1; + a = 1; + } + + /* And fill in the pixel: */ + p[0] = (png_uint_16)/*SAFE*/round(r * 65535); + p[1] = (png_uint_16)/*SAFE*/round(g * 65535); + p[2] = (png_uint_16)/*SAFE*/round(b * 65535); + p[3] = (png_uint_16)/*SAFE*/round(a * 65535); + } + + else + p[3] = p[2] = p[1] = p[0] = 0; +} + +int +main(int argc, const char **argv) +{ + int convert_to_8bit = 0; + + /* There is one option: --8bit: */ + if (argc > 1 && strcmp(argv[1], "--8bit") == 0) + --argc, ++argv, convert_to_8bit = 1; + + if (argc >= 3) + { + png_uint_16p buffer; + int nshapes; + png_image image; +# define max_shapes 256 + struct arg arg_list[max_shapes]; + + /* The libpng Simplified API write code requires a fully initialized + * structure. + */ + memset(&image, 0, sizeof image); + image.version = PNG_IMAGE_VERSION; + image.opaque = NULL; + image.width = read_wh("width", argv[1]); + image.height = read_wh("height", argv[2]); + image.format = PNG_FORMAT_LINEAR_RGB_ALPHA; + image.flags = 0; + image.colormap_entries = 0; + + /* Check the remainder of the arguments */ + for (nshapes=0; 3+7*(nshapes+1) <= argc && nshapes < max_shapes; + ++nshapes) + parse_arg(arg_list+nshapes, argv+3+7*nshapes); + + if (3+7*nshapes != argc) + { + fprintf(stderr, "genpng: %s: too many arguments\n", argv[3+7*nshapes]); + return 1; + } + + /* Create the buffer: */ + buffer = malloc(PNG_IMAGE_SIZE(image)); + + if (buffer != NULL) + { + png_uint_32 y; + + /* Write each row... */ + for (y=0; y<image.height; ++y) + { + png_uint_32 x; + + /* Each pixel in each row: */ + for (x=0; x<image.width; ++x) + pixel(buffer + 4*(x + y*image.width), arg_list, nshapes, x, y); + } + + /* Write the result (to stdout) */ + if (png_image_write_to_stdio(&image, stdout, convert_to_8bit, + buffer, 0/*row_stride*/, NULL/*colormap*/)) + { + free(buffer); + return 0; /* success */ + } + + else + fprintf(stderr, "genpng: write stdout: %s\n", image.message); + + free(buffer); + } + + else + fprintf(stderr, "genpng: out of memory: %lu bytes\n", + (unsigned long)PNG_IMAGE_SIZE(image)); + } + + else + { + /* Wrong number of arguments */ + fprintf(stderr, "genpng: usage: genpng [--8bit] width height {shape}\n" + " Generate a transparent PNG in RGBA (truecolor+alpha) format\n" + " containing the given shape or shapes. Shapes are defined:\n" + "\n" + " shape ::= color width shape x1 y1 x2 y2\n" + " color ::= black|white|red|green|yellow|blue\n" + " color ::= brown|purple|pink|orange|gray|cyan\n" + " width ::= filled|<number>\n" + " shape ::= circle|square|line\n" + " x1,x2 ::= <number>\n" + " y1,y2 ::= <number>\n" + "\n" + " Numbers are floating point numbers describing points relative to\n" + " the top left of the output PNG as pixel coordinates. The 'width'\n" + " parameter is either the width of the line (in output pixels) used\n" + " to draw the shape or 'filled' to indicate that the shape should\n" + " be filled with the color.\n" + "\n" + " Colors are interpreted loosely to give access to the eight full\n" + " intensity RGB values:\n" + "\n" + " black, red, green, blue, yellow, cyan, purple, white,\n" + "\n" + " Cyan is full intensity blue+green; RGB(0,1,1), plus the following\n" + " lower intensity values:\n" + "\n" + " brown: red+orange: RGB(0.5, 0.125, 0) (dark red+orange)\n" + " pink: red+white: RGB(1.0, 0.5, 0.5)\n" + " orange: red+yellow: RGB(1.0, 0.5, 0)\n" + " gray: black+white: RGB(0.5, 0.5, 0.5)\n" + "\n" + " The RGB values are selected to make detection of aliasing errors\n" + " easy. The names are selected to make the description of errors\n" + " easy.\n" + "\n" + " The PNG is written to stdout, if --8bit is given a 32bpp RGBA sRGB\n" + " file is produced, otherwise a 64bpp RGBA linear encoded file is\n" + " written.\n"); + } + + return 1; +} +#endif /* SIMPLIFIED_WRITE && STDIO */ diff --git a/libpng/contrib/tools/png-fix-itxt.c b/libpng/contrib/tools/png-fix-itxt.c index 1210bd9c8..c7654c113 100644 --- a/libpng/contrib/tools/png-fix-itxt.c +++ b/libpng/contrib/tools/png-fix-itxt.c @@ -1,14 +1,14 @@ /* png-fix-itxt version 1.0.0 * - * Copyright 2013 Glenn Randers-Pehrson - * Last changed in libpng 1.6.3 [July 18, 2013] + * Copyright 2015 Glenn Randers-Pehrson + * Last changed in libpng 1.6.18 [July 23, 2015] * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h * - * Usage: + * Usage: * * png-fix-itxt.exe < bad.png > good.png * @@ -34,8 +34,10 @@ #define MAX_LENGTH 500000 -#define GETBREAK ((unsigned char)(inchar=getchar())); if (inchar == EOF) break - +/* Read one character (inchar), also return octet (c), break if EOF */ +#define GETBREAK inchar=getchar(); \ + c=(inchar & 0xffU);\ + if (inchar != c) break int main(void) { @@ -48,25 +50,25 @@ main(void) /* Skip 8-byte signature */ for (i=8; i; i--) { - c=GETBREAK; + GETBREAK; putchar(c); } -if (inchar != EOF) +if (inchar == c) /* !EOF */ for (;;) { /* Read the length */ unsigned long length; /* must be 32 bits! */ - c=GETBREAK; buf[0] = c; length = c; length <<= 8; - c=GETBREAK; buf[1] = c; length += c; length <<= 8; - c=GETBREAK; buf[2] = c; length += c; length <<= 8; - c=GETBREAK; buf[3] = c; length += c; + GETBREAK; buf[0] = c; length = c; length <<= 8; + GETBREAK; buf[1] = c; length += c; length <<= 8; + GETBREAK; buf[2] = c; length += c; length <<= 8; + GETBREAK; buf[3] = c; length += c; /* Read the chunkname */ - c=GETBREAK; buf[4] = c; - c=GETBREAK; buf[5] = c; - c=GETBREAK; buf[6] = c; - c=GETBREAK; buf[7] = c; + GETBREAK; buf[4] = c; + GETBREAK; buf[5] = c; + GETBREAK; buf[6] = c; + GETBREAK; buf[7] = c; /* The iTXt chunk type expressed as integers is (105, 84, 88, 116) */ @@ -81,19 +83,22 @@ for (;;) /* Copy the data bytes */ for (i=8; i < length + 12; i++) { - c=GETBREAK; buf[i] = c; + GETBREAK; buf[i] = c; } + if (inchar != c) /* EOF */ + break; + /* Calculate the CRC */ crc = crc32(crc, buf+4, (uInt)length+4); for (;;) { /* Check the CRC */ - if (((crc >> 24) & 0xff) == buf[length+8] && - ((crc >> 16) & 0xff) == buf[length+9] && - ((crc >> 8) & 0xff) == buf[length+10] && - ((crc ) & 0xff) == buf[length+11]) + if (((crc >> 24) & 0xffU) == buf[length+8] && + ((crc >> 16) & 0xffU) == buf[length+9] && + ((crc >> 8) & 0xffU) == buf[length+10] && + ((crc ) & 0xffU) == buf[length+11]) break; length++; @@ -101,18 +106,21 @@ for (;;) if (length >= MAX_LENGTH-12) break; - c=GETBREAK; - buf[length+11]=c; + GETBREAK; + buf[length+11] = c; /* Update the CRC */ crc = crc32(crc, buf+7+length, 1); } + if (inchar != c) /* EOF */ + break; + /* Update length bytes */ - buf[0] = (unsigned char)((length << 24) & 0xff); - buf[1] = (unsigned char)((length << 16) & 0xff); - buf[2] = (unsigned char)((length << 8) & 0xff); - buf[3] = (unsigned char)((length ) & 0xff); + buf[0] = (unsigned char)((length >> 24) & 0xffU); + buf[1] = (unsigned char)((length >> 16) & 0xffU); + buf[2] = (unsigned char)((length >> 8) & 0xffU); + buf[3] = (unsigned char)((length ) & 0xffU); /* Write the fixed iTXt chunk (length, name, data, crc) */ for (i=0; i<length+12; i++) @@ -121,6 +129,9 @@ for (;;) else { + if (inchar != c) /* EOF */ + break; + /* Copy bytes that were already read (length and chunk name) */ for (i=0; i<8; i++) putchar(buf[i]); @@ -128,11 +139,11 @@ for (;;) /* Copy data bytes and CRC */ for (i=8; i< length+12; i++) { - c=GETBREAK; + GETBREAK; putchar(c); } - if (inchar == EOF) + if (inchar != c) /* EOF */ { break; } @@ -142,7 +153,7 @@ for (;;) break; } - if (inchar == EOF) + if (inchar != c) /* EOF */ break; if (buf[4] == 73 && buf[5] == 69 && buf[6] == 78 && buf[7] == 68) diff --git a/libpng/contrib/tools/pngcp.c b/libpng/contrib/tools/pngcp.c new file mode 100644 index 000000000..e82dddccc --- /dev/null +++ b/libpng/contrib/tools/pngcp.c @@ -0,0 +1,2453 @@ +/* pngcp.c + * + * Copyright (c) 2016 John Cunningham Bowler + * + * Last changed in libpng 1.6.24 [August 4, 2016] + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This is an example of copying a PNG without changes using the png_read_png + * and png_write_png interfaces. A considerable number of options are provided + * to manipulate the compression of the PNG data and other compressed chunks. + * + * For a more extensive example that uses the transforms see + * contrib/libtests/pngimage.c in the libpng distribution. + */ +#include "pnglibconf.h" /* To find how libpng was configured. */ + +#ifdef PNG_PNGCP_TIMING_SUPPORTED + /* WARNING: + * + * This test is here to allow POSIX.1b extensions to be used if enabled in + * the compile; specifically the code requires_POSIX_C_SOURCE support of + * 199309L or later to enable clock_gettime use. + * + * IF this causes problems THEN compile with a strict ANSI C compiler and let + * this code turn on the POSIX features that it minimally requires. + * + * IF this does not work there is probably a bug in your ANSI C compiler or + * your POSIX implementation. + */ +# define _POSIX_C_SOURCE 199309L +#else /* No timing support required */ +# define _POSIX_SOURCE 1 +#endif + +#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) +# include <config.h> +#endif + +#include <stdio.h> + +/* Define the following to use this test against your installed libpng, rather + * than the one being built here: + */ +#ifdef PNG_FREESTANDING_TESTS +# include <png.h> +#else +# include "../../png.h" +#endif + +#if PNG_LIBPNG_VER < 10700 + /* READ_PNG and WRITE_PNG were not defined, so: */ +# ifdef PNG_INFO_IMAGE_SUPPORTED +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED +# define PNG_READ_PNG_SUPPORTED +# endif /* SEQUENTIAL_READ */ +# ifdef PNG_WRITE_SUPPORTED +# define PNG_WRITE_PNG_SUPPORTED +# endif /* WRITE */ +# endif /* INFO_IMAGE */ +#endif /* pre 1.7.0 */ + +#if (defined(PNG_READ_PNG_SUPPORTED)) && (defined(PNG_WRITE_PNG_SUPPORTED)) +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <assert.h> + +#include <unistd.h> +#include <sys/stat.h> + +#include <zlib.h> + +#ifndef PNG_SETJMP_SUPPORTED +# include <setjmp.h> /* because png.h did *not* include this */ +#endif + +#ifdef __cplusplus +# define voidcast(type, value) static_cast<type>(value) +#else +# define voidcast(type, value) (value) +#endif /* __cplusplus */ + +#ifdef __GNUC__ + /* Many versions of GCC erroneously report that local variables unmodified + * within the scope of a setjmp may be clobbered. This hacks round the + * problem (sometimes) without harming other compilers. + */ +# define gv volatile +#else +# define gv +#endif + +/* 'CLOCK_PROCESS_CPUTIME_ID' is one of the clock timers for clock_gettime. It + * need not be supported even when clock_gettime is available. It returns the + * 'CPU' time the process has consumed. 'CPU' time is assumed to include time + * when the CPU is actually blocked by a pending cache fill but not time + * waiting for page faults. The attempt is to get a measure of the actual time + * the implementation takes to read a PNG ignoring the potentially very large IO + * overhead. + */ +#ifdef PNG_PNGCP_TIMING_SUPPORTED +# include <time.h> /* clock_gettime and associated definitions */ +# ifndef CLOCK_PROCESS_CPUTIME_ID + /* Prevent inclusion of the spurious code: */ +# undef PNG_PNGCP_TIMING_SUPPORTED +# endif +#endif /* PNGCP_TIMING */ + +/* So if the timing feature has been activated: */ + +/* This structure is used to control the test of a single file. */ +typedef enum +{ + VERBOSE, /* switches on all messages */ + INFORMATION, + WARNINGS, /* switches on warnings */ + LIBPNG_WARNING, + APP_WARNING, + ERRORS, /* just errors */ + APP_FAIL, /* continuable error - no need to longjmp */ + LIBPNG_ERROR, /* this and higher cause a longjmp */ + LIBPNG_BUG, /* erroneous behavior in libpng */ + APP_ERROR, /* such as out-of-memory in a callback */ + QUIET, /* no normal messages */ + USER_ERROR, /* such as file-not-found */ + INTERNAL_ERROR +} error_level; +#define LEVEL_MASK 0xf /* where the level is in 'options' */ + +#define STRICT 0x010 /* Fail on warnings as well as errors */ +#define LOG 0x020 /* Log pass/fail to stdout */ +#define CONTINUE 0x040 /* Continue on APP_FAIL errors */ +#define SIZES 0x080 /* Report input and output sizes */ +#define SEARCH 0x100 /* Search IDAT compression options */ +#define NOWRITE 0x200 /* Do not write an output file */ +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +# define IGNORE_INDEX 0x400 /* Ignore out of range palette indices (BAD!) */ +# ifdef PNG_GET_PALETTE_MAX_SUPPORTED +# define FIX_INDEX 0x800 /* 'Fix' out of range palette indices (OK) */ +# endif /* GET_PALETTE_MAX */ +#endif /* CHECK_FOR_INVALID_INDEX */ +#define OPTION 0x80000000 /* Used for handling options */ +#define LIST 0x80000001 /* Used for handling options */ + +/* Result masks apply to the result bits in the 'results' field below; these + * bits are simple 1U<<error_level. A pass requires either nothing worse than + * warnings (--relaxes) or nothing worse than information (--strict) + */ +#define RESULT_STRICT(r) (((r) & ~((1U<<WARNINGS)-1)) == 0) +#define RESULT_RELAXED(r) (((r) & ~((1U<<ERRORS)-1)) == 0) + +/* OPTION DEFINITIONS */ +static const char range_lo[] = "low"; +static const char range_hi[] = "high"; +static const char all[] = "all"; +#define RANGE(lo,hi) { range_lo, lo }, { range_hi, hi } +typedef struct value_list +{ + const char *name; /* the command line name of the value */ + int value; /* the actual value to use */ +} value_list; + +static const value_list +#ifdef PNG_SW_COMPRESS_png_level +vl_compression[] = +{ + /* Overall compression control. The order controls the search order for + * 'all'. Since the search is for the smallest the order used is low memory + * then high speed. + */ + { "low-memory", PNG_COMPRESSION_LOW_MEMORY }, + { "high-speed", PNG_COMPRESSION_HIGH_SPEED }, + { "high-read-speed", PNG_COMPRESSION_HIGH_READ_SPEED }, + { "low", PNG_COMPRESSION_LOW }, + { "medium", PNG_COMPRESSION_MEDIUM }, + { "old", PNG_COMPRESSION_COMPAT }, + { "high", PNG_COMPRESSION_HIGH }, + { all, 0 } +}, +#endif /* SW_COMPRESS_png_level */ + +#if defined(PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED) ||\ + defined(PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED) +vl_strategy[] = +{ + /* This controls the order of search. */ + { "huffman", Z_HUFFMAN_ONLY }, + { "RLE", Z_RLE }, + { "fixed", Z_FIXED }, /* the remainder do window searchs */ + { "filtered", Z_FILTERED }, + { "default", Z_DEFAULT_STRATEGY }, + { all, 0 } +}, +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +vl_windowBits_text[] = +{ + { "default", MAX_WBITS/*from zlib*/ }, + { "minimum", 8 }, + RANGE(8, MAX_WBITS/*from zlib*/), + { all, 0 } +}, +#endif /* text compression */ +vl_level[] = +{ + { "default", Z_DEFAULT_COMPRESSION /* this is -1 */ }, + { "none", Z_NO_COMPRESSION }, + { "speed", Z_BEST_SPEED }, + { "best", Z_BEST_COMPRESSION }, + { "0", Z_NO_COMPRESSION }, + RANGE(1, 9), /* this deliberately excludes '0' */ + { all, 0 } +}, +vl_memLevel[] = +{ + { "max", MAX_MEM_LEVEL }, /* zlib maximum */ + { "1", 1 }, /* zlib minimum */ + { "default", 8 }, /* zlib default */ + { "2", 2 }, + { "3", 3 }, + { "4", 4 }, + { "5", 5 }, /* for explicit testing */ + RANGE(6, MAX_MEM_LEVEL/*zlib*/), /* exclude 5 and below: zlib bugs */ + { all, 0 } +}, +#endif /* WRITE_CUSTOMIZE_*COMPRESSION */ +#ifdef PNG_WRITE_FILTER_SUPPORTED +vl_filter[] = +{ + { all, PNG_ALL_FILTERS }, + { "off", PNG_NO_FILTERS }, + { "none", PNG_FILTER_NONE }, + { "sub", PNG_FILTER_SUB }, + { "up", PNG_FILTER_UP }, + { "avg", PNG_FILTER_AVG }, + { "paeth", PNG_FILTER_PAETH } +}, +#endif /* WRITE_FILTER */ +#ifdef PNG_PNGCP_TIMING_SUPPORTED +# define PNGCP_TIME_READ 1 +# define PNGCP_TIME_WRITE 2 +vl_time[] = +{ + { "both", PNGCP_TIME_READ+PNGCP_TIME_WRITE }, + { "off", 0 }, + { "read", PNGCP_TIME_READ }, + { "write", PNGCP_TIME_WRITE } +}, +#endif /* PNGCP_TIMING */ +vl_IDAT_size[] = /* for png_set_IDAT_size */ +{ + { "default", 0x7FFFFFFF }, + { "minimal", 1 }, + RANGE(1, 0x7FFFFFFF) +}, +#ifndef PNG_SW_IDAT_size + /* Pre 1.7 API: */ +# define png_set_IDAT_size(p,v) png_set_compression_buffer_size(p, v) +#endif /* !SW_IDAT_size */ +#define SL 8 /* stack limit in display, below */ +vl_log_depth[] = { { "on", 1 }, { "off", 0 }, RANGE(0, SL) }, +vl_on_off[] = { { "on", 1 }, { "off", 0 } }; + +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +static value_list +vl_windowBits_IDAT[] = +{ + { "default", MAX_WBITS }, + { "small", 9 }, + RANGE(8, MAX_WBITS), /* modified by set_windowBits_hi */ + { all, 0 } +}; +#endif /* IDAT compression */ + +typedef struct option +{ + const char *name; /* name of the option */ + png_uint_32 opt; /* an option, or OPTION or LIST */ + png_byte search; /* Search on --search */ + png_byte value_count; /* length of the list of values: */ + const value_list *values; /* values for OPTION or LIST */ +} option; + +static const option options[] = +{ + /* struct display options, these are set when the command line is read */ +# define S(n,v) { #n, v, 0, 2, vl_on_off }, + S(verbose, VERBOSE) + S(warnings, WARNINGS) + S(errors, ERRORS) + S(quiet, QUIET) + S(strict, STRICT) + S(log, LOG) + S(continue, CONTINUE) + S(sizes, SIZES) + S(search, SEARCH) + S(nowrite, NOWRITE) +# ifdef IGNORE_INDEX + S(ignore-palette-index, IGNORE_INDEX) +# endif /* IGNORE_INDEX */ +# ifdef FIX_INDEX + S(fix-palette-index, FIX_INDEX) +# endif /* FIX_INDEX */ +# undef S + + /* OPTION settings, these and LIST settings are read on demand */ +# define VLNAME(name) vl_ ## name +# define VLSIZE(name) voidcast(png_byte,\ + (sizeof VLNAME(name))/(sizeof VLNAME(name)[0])) +# define VL(oname, name, type, search)\ + { oname, type, search, VLSIZE(name), VLNAME(name) }, +# define VLO(oname, name, search) VL(oname, name, OPTION, search) + +# ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +# define VLCIDAT(name) VLO(#name, name, 1/*search*/) +# ifdef PNG_SW_COMPRESS_level +# define VLCiCCP(name) VLO("ICC-profile-" #name, name, 0/*search*/) +# else +# define VLCiCCP(name) +# endif +# else +# define VLCIDAT(name) +# define VLCiCCP(name) +# endif /* WRITE_CUSTOMIZE_COMPRESSION */ + +# ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +# define VLCzTXt(name) VLO("text-" #name, name, 0/*search*/) +# else +# define VLCzTXt(name) +# endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */ + +# define VLC(name) VLCIDAT(name) VLCiCCP(name) VLCzTXt(name) + +# ifdef PNG_SW_COMPRESS_png_level + /* The libpng compression level isn't searched beause it justs sets the + * other things that are searched! + */ + VLO("compression", compression, 0) + VLO("text-compression", compression, 0) + VLO("ICC-profile-compression", compression, 0) +# endif /* SW_COMPRESS_png_level */ + VLC(strategy) + VLO("windowBits", windowBits_IDAT, 1) +# ifdef PNG_SW_COMPRESS_windowBits + VLO("ICC-profile-windowBits", windowBits_text/*sic*/, 0) +# endif + VLO("text-windowBits", windowBits_text, 0) + VLC(level) + VLC(memLevel) + VLO("IDAT-size", IDAT_size, 0) + VLO("log-depth", log_depth, 0) + +# undef VLO + + /* LIST settings */ +# define VLL(name, search) VL(#name, name, LIST, search) +#ifdef PNG_WRITE_FILTER_SUPPORTED + VLL(filter, 0) +#endif /* WRITE_FILTER */ +#ifdef PNG_PNGCP_TIMING_SUPPORTED + VLL(time, 0) +#endif /* PNGCP_TIMING */ +# undef VLL +# undef VL +}; + +#ifdef __cplusplus + static const size_t option_count((sizeof options)/(sizeof options[0])); +#else /* !__cplusplus */ +# define option_count ((sizeof options)/(sizeof options[0])) +#endif /* !__cplusplus */ + +static const char * +cts(int ct) +{ + switch (ct) + { + case PNG_COLOR_TYPE_PALETTE: return "P"; + case PNG_COLOR_TYPE_GRAY: return "G"; + case PNG_COLOR_TYPE_GRAY_ALPHA: return "GA"; + case PNG_COLOR_TYPE_RGB: return "RGB"; + case PNG_COLOR_TYPE_RGB_ALPHA: return "RGBA"; + default: return "INVALID"; + } +} + +struct display +{ + jmp_buf error_return; /* Where to go to on error */ + unsigned int errset; /* error_return is set */ + + const char *operation; /* What is happening */ + const char *filename; /* The name of the original file */ + const char *output_file; /* The name of the output file */ + + /* Used on both read and write: */ + FILE *fp; + + /* Used on a read, both the original read and when validating a written + * image. + */ + png_alloc_size_t read_size; + png_structp read_pp; + png_infop ip; +# if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED + png_textp text_ptr; /* stash of text chunks */ + int num_text; + int text_stashed; +# endif /* pre 1.7 */ + +# ifdef PNG_PNGCP_TIMING_SUPPORTED + struct timespec read_time; + struct timespec read_time_total; + struct timespec write_time; + struct timespec write_time_total; +# endif /* PNGCP_TIMING */ + + /* Used to write a new image (the original info_ptr is used) */ +# define MAX_SIZE ((png_alloc_size_t)(-1)) + png_alloc_size_t write_size; + png_alloc_size_t best_size; + png_structp write_pp; + + /* Base file information */ + png_alloc_size_t size; + png_uint_32 w; + png_uint_32 h; + int bpp; + png_byte ct; + int no_warnings; /* Do not output libpng warnings */ + int min_windowBits; /* The windowBits range is 8..8 */ + + /* Options handling */ + png_uint_32 results; /* A mask of errors seen */ + png_uint_32 options; /* See display_log below */ + png_byte entry[option_count]; /* The selected entry+1 of an option + * that appears on the command line, or + * 0 if it was not given. */ + int value[option_count]; /* Corresponding value */ + + /* Compression exhaustive testing */ + /* Temporary variables used only while testing a single collection of + * settings: + */ + unsigned int csp; /* next stack entry to use */ + unsigned int nsp; /* highest active entry+1 found so far */ + + /* Values used while iterating through all the combinations of settings for a + * single file: + */ + unsigned int tsp; /* nsp from the last run; this is the + * index+1 of the highest active entry on + * this run; this entry will be advanced. + */ + int opt_string_start; /* Position in buffer for the first + * searched option; non-zero if earlier + * options were set on the command line. + */ + struct stack + { + png_alloc_size_t best_size; /* Best so far for this option */ + png_alloc_size_t lo_size; + png_alloc_size_t hi_size; + int lo, hi; /* For binary chop of a range */ + int best_val; /* Best value found so far */ + int opt_string_end; /* End of the option string in 'curr' */ + png_byte opt; /* The option being tested */ + png_byte entry; /* The next value entry to be tested */ + png_byte end; /* This is the last entry */ + } stack[SL]; /* Stack of entries being tested */ + char curr[32*SL]; /* current options being tested */ + char best[32*SL]; /* best options */ + + char namebuf[FILENAME_MAX]; /* output file name */ +}; + +static void +display_init(struct display *dp) + /* Call this only once right at the start to initialize the control + * structure, the (struct buffer) lists are maintained across calls - the + * memory is not freed. + */ +{ + memset(dp, 0, sizeof *dp); + dp->operation = "internal error"; + dp->filename = "command line"; + dp->output_file = "no output file"; + dp->options = WARNINGS; /* default to !verbose, !quiet */ + dp->fp = NULL; + dp->read_pp = NULL; + dp->ip = NULL; + dp->write_pp = NULL; + dp->min_windowBits = -1; /* this is an OPTIND, so -1 won't match anything */ +# if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED + dp->text_ptr = NULL; + dp->num_text = 0; + dp->text_stashed = 0; +# endif /* pre 1.7 */ +} + +static void +display_clean_read(struct display *dp) +{ + if (dp->read_pp != NULL) + png_destroy_read_struct(&dp->read_pp, NULL, NULL); + + if (dp->fp != NULL) + { + FILE *fp = dp->fp; + dp->fp = NULL; + (void)fclose(fp); + } +} + +static void +display_clean_write(struct display *dp) +{ + if (dp->fp != NULL) + { + FILE *fp = dp->fp; + dp->fp = NULL; + (void)fclose(fp); + } + + if (dp->write_pp != NULL) + png_destroy_write_struct(&dp->write_pp, dp->tsp > 0 ? NULL : &dp->ip); +} + +static void +display_clean(struct display *dp) +{ + display_clean_read(dp); + display_clean_write(dp); + dp->output_file = NULL; + +# if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED + /* This is actually created and used by the write code, but only + * once; it has to be retained for subsequent writes of the same file. + */ + if (dp->text_stashed) + { + dp->text_stashed = 0; + dp->num_text = 0; + free(dp->text_ptr); + dp->text_ptr = NULL; + } +# endif /* pre 1.7 */ + + /* leave the filename for error detection */ + dp->results = 0; /* reset for next time */ +} + +static void +display_destroy(struct display *dp) +{ + /* Release any memory held in the display. */ + display_clean(dp); +} + +static struct display * +get_dp(png_structp pp) + /* The display pointer is always stored in the png_struct error pointer */ +{ + struct display *dp = (struct display*)png_get_error_ptr(pp); + + if (dp == NULL) + { + fprintf(stderr, "pngcp: internal error (no display)\n"); + exit(99); /* prevents a crash */ + } + + return dp; +} + +/* error handling */ +#ifdef __GNUC__ +# define VGATTR __attribute__((__format__ (__printf__,3,4))) + /* Required to quiet GNUC warnings when the compiler sees a stdarg function + * that calls one of the stdio v APIs. + */ +#else +# define VGATTR +#endif +static void VGATTR +display_log(struct display *dp, error_level level, const char *fmt, ...) + /* 'level' is as above, fmt is a stdio style format string. This routine + * does not return if level is above LIBPNG_WARNING + */ +{ + dp->results |= 1U << level; + + if (level > (error_level)(dp->options & LEVEL_MASK)) + { + const char *lp; + va_list ap; + + switch (level) + { + case INFORMATION: lp = "information"; break; + case LIBPNG_WARNING: lp = "warning(libpng)"; break; + case APP_WARNING: lp = "warning(pngcp)"; break; + case APP_FAIL: lp = "error(continuable)"; break; + case LIBPNG_ERROR: lp = "error(libpng)"; break; + case LIBPNG_BUG: lp = "bug(libpng)"; break; + case APP_ERROR: lp = "error(pngcp)"; break; + case USER_ERROR: lp = "error(user)"; break; + + case INTERNAL_ERROR: /* anything unexpected is an internal error: */ + case VERBOSE: case WARNINGS: case ERRORS: case QUIET: + default: lp = "bug(pngcp)"; break; + } + + fprintf(stderr, "%s: %s: %s", + dp->filename != NULL ? dp->filename : "<stdin>", lp, dp->operation); + + fprintf(stderr, ": "); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputc('\n', stderr); + } + /* else do not output any message */ + + /* Errors cause this routine to exit to the fail code */ + if (level > APP_FAIL || (level > ERRORS && !(dp->options & CONTINUE))) + { + if (dp->errset) + longjmp(dp->error_return, level); + + else + exit(99); + } +} + +#if PNG_LIBPNG_VER < 10700 && defined PNG_TEXT_SUPPORTED +static void +text_stash(struct display *dp) +{ + /* libpng 1.6 and earlier fixed a bug whereby text chunks were written + * multiple times by png_write_png; the issue was that png_write_png passed + * the same png_info to both png_write_info and png_write_end. Rather than + * fixing it by recording the information in the png_struct, or by recording + * where to write the chunks, the fix made was to change the 'compression' + * field of the chunk to invalid values, rendering the png_info somewhat + * useless. + * + * The only fix for this given that we use the png_info more than once is to + * make a copy of the text chunks and png_set_text it each time. This adds a + * text chunks, so they get replicated, but only the new set gets written + * each time. This uses memory like crazy but there is no way to delete the + * useless chunks from the png_info. + * + * To make this slightly more efficient only the top level structure is + * copied; since the old strings are actually preserved (in 1.6 and earlier) + * this happens to work. + */ + png_textp chunks = NULL; + + dp->num_text = png_get_text(dp->write_pp, dp->ip, &chunks, NULL); + + if (dp->num_text > 0) + { + dp->text_ptr = voidcast(png_textp, malloc(dp->num_text * sizeof *chunks)); + + if (dp->text_ptr == NULL) + display_log(dp, APP_ERROR, "text chunks: stash malloc failed"); + + else + memcpy(dp->text_ptr, chunks, dp->num_text * sizeof *chunks); + } + + dp->text_stashed = 1; /* regardless of whether there are chunks or not */ +} + +#define text_stash(dp) if (!dp->text_stashed) text_stash(dp) + +static void +text_restore(struct display *dp) +{ + /* libpng makes a copy, so this is fine: */ + if (dp->text_ptr != NULL) + png_set_text(dp->write_pp, dp->ip, dp->text_ptr, dp->num_text); +} + +#define text_restore(dp) if (dp->text_stashed) text_restore(dp) + +#else +#define text_stash(dp) ((void)0) +#define text_restore(dp) ((void)0) +#endif /* pre 1.7 */ + +/* OPTIONS: + * + * The command handles options of the forms: + * + * --option + * Turn an option on (Option) + * --no-option + * Turn an option off (Option) + * --option=value + * Set an option to a value (Value) + * --option=val1,val2,val3 + * Set an option to a bitmask constructed from the values (List) + */ +static png_byte +option_index(struct display *dp, const char *opt, size_t len) + /* Return the index (in options[]) of the given option, outputs an error if + * it does not exist. Takes the name of the option and a length (number of + * characters in the name). + */ +{ + png_byte j; + + for (j=0; j<option_count; ++j) + if (strncmp(options[j].name, opt, len) == 0 && options[j].name[len] == 0) + return j; + + /* If the setjmp buffer is set the code is asking for an option index; this + * is bad. Otherwise this is the command line option parsing. + */ + display_log(dp, dp->errset ? INTERNAL_ERROR : USER_ERROR, + "%.*s: unknown option", (int)/*SAFE*/len, opt); + abort(); /* NOT REACHED */ +} + +/* This works for an option name (no quotes): */ +#define OPTIND(dp, name) option_index(dp, #name, (sizeof #name)-1) + +static int +get_option(struct display *dp, const char *opt, int *value) +{ + const png_byte i = option_index(dp, opt, strlen(opt)); + + if (dp->entry[i]) /* option was set on command line */ + { + *value = dp->value[i]; + return 1; + } + + else + return 0; +} + +static int +set_opt_string_(struct display *dp, unsigned int sp, png_byte opt, + const char *entry_name) + /* Add the appropriate option string to dp->curr. */ +{ + int offset, add; + + if (sp > 0) + offset = dp->stack[sp-1].opt_string_end; + + else + offset = dp->opt_string_start; + + if (entry_name == range_lo) + add = sprintf(dp->curr+offset, " --%s=%d", options[opt].name, + dp->value[opt]); + + else + add = sprintf(dp->curr+offset, " --%s=%s", options[opt].name, entry_name); + + if (add < 0) + display_log(dp, INTERNAL_ERROR, "sprintf failed"); + + assert(offset+add < (int)/*SAFE*/sizeof dp->curr); + return offset+add; +} + +static void +set_opt_string(struct display *dp, unsigned int sp) + /* Add the appropriate option string to dp->curr. */ +{ + dp->stack[sp].opt_string_end = set_opt_string_(dp, sp, dp->stack[sp].opt, + options[dp->stack[sp].opt].values[dp->stack[sp].entry].name); +} + +static void +record_opt(struct display *dp, png_byte opt, const char *entry_name) + /* Record this option in dp->curr; called for an option not being searched, + * the caller passes in the name of the value, or range_lo to use the + * numerical value. + */ +{ + const unsigned int sp = dp->csp; /* stack entry of next searched option */ + + if (sp >= dp->tsp) + { + /* At top of stack; add the opt string for this entry to the previous + * searched entry or the start of the dp->curr buffer if there is nothing + * on the stack yet (sp == 0). + */ + const int offset = set_opt_string_(dp, sp, opt, entry_name); + + if (sp > 0) + dp->stack[sp-1].opt_string_end = offset; + + else + dp->opt_string_start = offset; + } + + /* else do nothing: option already recorded */ +} + +static int +opt_list_end(struct display *dp, png_byte opt, png_byte entry) +{ + if (options[opt].values[entry].name == range_lo) + return entry+1U >= options[opt].value_count /* missing range_hi */ || + options[opt].values[entry+1U].name != range_hi /* likewise */ || + options[opt].values[entry+1U].value <= dp->value[opt] /* range end */; + + else + return entry+1U >= options[opt].value_count /* missing 'all' */ || + options[opt].values[entry+1U].name == all /* last entry */; +} + +static void +push_opt(struct display *dp, unsigned int sp, png_byte opt, int search) + /* Push a new option onto the stack, initializing the new stack entry + * appropriately; this does all the work of next_opt (setting end/nsp) for + * the first entry in the list. + */ +{ + png_byte entry; + const char *entry_name; + + assert(sp == dp->tsp && sp < SL); + + /* The starting entry is entry 0 unless there is a range in which case it is + * the entry corresponding to range_lo: + */ + entry = options[opt].value_count; + assert(entry > 0U); + + do + { + entry_name = options[opt].values[--entry].name; + if (entry_name == range_lo) + break; + } + while (entry > 0U); + + dp->tsp = sp+1U; + dp->stack[sp].best_size = + dp->stack[sp].lo_size = + dp->stack[sp].hi_size = MAX_SIZE; + + if (search && entry_name == range_lo) /* search this range */ + { + dp->stack[sp].lo = options[opt].values[entry].value; + /* check for a mal-formed RANGE above: */ + assert(entry+1 < options[opt].value_count && + options[opt].values[entry+1].name == range_hi); + dp->stack[sp].hi = options[opt].values[entry+1].value; + } + + else + { + /* next_opt will just iterate over the range. */ + dp->stack[sp].lo = INT_MAX; + dp->stack[sp].hi = INT_MIN; /* Prevent range chop */ + } + + dp->stack[sp].opt = opt; + dp->stack[sp].entry = entry; + dp->stack[sp].best_val = dp->value[opt] = options[opt].values[entry].value; + + set_opt_string(dp, sp); + + /* This works for the search case too; if the range has only one entry 'end' + * will be marked here. + */ + if (opt_list_end(dp, opt, entry)) + { + dp->stack[sp].end = 1; + /* Skip the warning if pngcp did this itself. See the code in + * set_windowBits_hi. + */ + if (opt != dp->min_windowBits) + display_log(dp, APP_WARNING, "%s: only testing one value", + options[opt].name); + } + + else + { + dp->stack[sp].end = 0; + dp->nsp = dp->tsp; + } + + /* Do a lazy cache of the text chunks for libpng 1.6 and earlier; this is + * because they can only be written once(!) so if we are going to re-use the + * png_info we need a copy. + */ + text_stash(dp); +} + +static void +next_opt(struct display *dp, unsigned int sp) + /* Return the next value for this option. When called 'sp' is expected to be + * the topmost stack entry - only the topmost entry changes each time round - + * and there must be a valid entry to return. next_opt will set dp->nsp to + * sp+1 if more entries are available, otherwise it will not change it and + * set dp->stack[s].end to true. + */ +{ + int search = 0; + png_byte entry, opt; + const char *entry_name; + + /* dp->stack[sp] must be the top stack entry and it must be active: */ + assert(sp+1U == dp->tsp && !dp->stack[sp].end); + + opt = dp->stack[sp].opt; + entry = dp->stack[sp].entry; + assert(entry+1U < options[opt].value_count); + entry_name = options[opt].values[entry].name; + assert(entry_name != NULL); + + /* For ranges increment the value but don't change the entry, for all other + * cases move to the next entry and load its value: + */ + if (entry_name == range_lo) /* a range */ + { + /* A range can be iterated over or searched. The default iteration option + * is indicated by hi < lo on the stack, otherwise the range being search + * is [lo..hi] (inclusive). + */ + if (dp->stack[sp].lo > dp->stack[sp].hi) + dp->value[opt]++; + + else + { + /* This is the best size found for this option value: */ + png_alloc_size_t best_size = dp->stack[sp].best_size; + int lo = dp->stack[sp].lo; + int hi = dp->stack[sp].hi; + int val = dp->value[opt]; + + search = 1; /* end is determined here */ + assert(best_size < MAX_SIZE); + + if (val == lo) + { + /* Finding the best for the low end of the range: */ + dp->stack[sp].lo_size = best_size; + assert(hi > val); + + if (hi == val+1) /* only 2 entries */ + dp->stack[sp].end = 1; + + val = hi; + } + + else if (val == hi) + { + dp->stack[sp].hi_size = best_size; + assert(val > lo+1); /* else 'end' set above */ + + if (val == lo+2) /* only three entries to test */ + dp->stack[sp].end = 1; + + val = (lo + val)/2; + } + + else + { + png_alloc_size_t lo_size = dp->stack[sp].lo_size; + png_alloc_size_t hi_size = dp->stack[sp].hi_size; + + /* lo and hi should have been tested. */ + assert(lo_size < MAX_SIZE && hi_size < MAX_SIZE); + + /* These cases arise with the 'probe' handling below when there is a + * dip or peak in the size curve. + */ + if (val < lo) /* probing a new lo */ + { + /* Swap lo and val: */ + dp->stack[sp].lo = val; + dp->stack[sp].lo_size = best_size; + val = lo; + best_size = lo_size; + lo = dp->stack[sp].lo; + lo_size = dp->stack[sp].lo_size; + } + + else if (val > hi) /* probing a new hi */ + { + /* Swap hi and val: */ + dp->stack[sp].hi = val; + dp->stack[sp].hi_size = best_size; + val = hi; + best_size = hi_size; + hi = dp->stack[sp].hi; + hi_size = dp->stack[sp].hi_size; + } + + /* The following should be true or something got messed up above. */ + assert(lo < val && val < hi); + + /* If there are only four entries (lo, val, hi plus one more) just + * test the remaining entry. + */ + if (hi == lo+3) + { + /* Because of the 'probe' code val can either be lo+1 or hi-1; we + * need to test the other. + */ + val = lo + ((val == lo+1) ? 2 : 1); + assert(lo < val && val < hi); + dp->stack[sp].end = 1; + } + + else + { + /* There are at least 2 entries still untested between lo and hi, + * i.e. hi >= lo+4. 'val' is the midpoint +/- 0.5 + * + * Separate out the four easy cases when lo..val..hi are + * monotonically decreased or (more weird) increasing: + */ + assert(hi > lo+3); + + if (lo_size <= best_size && best_size <= hi_size) + { + /* Select the low range; testing this first favours the low + * range over the high range when everything comes out equal. + * Because of the probing 'val' may be lo+1. In that case end + * the search and set 'val' to lo+2. + */ + if (val == lo+1) + { + ++val; + dp->stack[sp].end = 1; + } + + else + { + dp->stack[sp].hi = hi = val; + dp->stack[sp].hi_size = best_size; + val = (lo + val) / 2; + } + } + + else if (lo_size >= best_size && best_size >= hi_size) + { + /* Monotonically decreasing size; this is the expected case. + * Select the high end of the range. As above, val may be + * hi-1. + */ + if (val == hi-1) + { + --val; + dp->stack[sp].end = 1; + } + + else + { + dp->stack[sp].lo = lo = val; + dp->stack[sp].lo_size = best_size; + val = (val + hi) / 2; + } + } + + /* If both those tests failed 'best_size' is either greater than + * or less than both lo_size and hi_size. There is a peak or dip + * in the curve of sizes from lo to hi and val is on the peak or + * dip. + * + * Because the ranges being searched as so small (level is 1..9, + * windowBits 8..15, memLevel 1..9) there will only be at most + * three untested values between lo..val and val..hi, so solve + * the problem by probing down from hi or up from lo, whichever + * is the higher. + * + * This is the place where 'val' is set to outside the range + * lo..hi, described as 'probing', though maybe 'narrowing' would + * be more accurate. + */ + else if (lo_size <= hi_size) /* down from hi */ + { + dp->stack[sp].hi = val; + dp->stack[sp].hi_size = best_size; + val = --hi; + } + + else /* up from low */ + { + dp->stack[sp].lo = val; + dp->stack[sp].lo_size = best_size; + val = ++lo; + } + + /* lo and hi are still the true range limits, check for the end + * condition. + */ + assert(hi > lo+1); + if (hi <= lo+2) + dp->stack[sp].end = 1; + } + } + + assert(val != dp->stack[sp].best_val); /* should be a new value */ + dp->value[opt] = val; + dp->stack[sp].best_size = MAX_SIZE; + } + } + + else + { + /* Increment 'entry' */ + dp->value[opt] = options[opt].values[++entry].value; + dp->stack[sp].entry = entry; + } + + set_opt_string(dp, sp); + + if (!search && opt_list_end(dp, opt, entry)) /* end of list */ + dp->stack[sp].end = 1; + + else if (!dp->stack[sp].end) /* still active after all these tests */ + dp->nsp = dp->tsp; +} + +static int +compare_option(const struct display *dp, unsigned int sp) +{ + int opt = dp->stack[sp].opt; + + /* If the best so far is numerically less than the current value the + * current set of options is invariably worse. + */ + if (dp->stack[sp].best_val < dp->value[opt]) + return -1; + + /* Lists of options are searched out of numerical order (currently only + * strategy), so only return +1 here when a range is being searched. + */ + else if (dp->stack[sp].best_val > dp->value[opt]) + { + if (dp->stack[sp].lo <= dp->stack[sp].hi /*searching*/) + return 1; + + else + return -1; + } + + else + return 0; /* match; current value is the best one */ +} + +static int +advance_opt(struct display *dp, png_byte opt, int search) +{ + unsigned int sp = dp->csp++; /* my stack entry */ + + assert(sp >= dp->nsp); /* nsp starts off zero */ + + /* If the entry was active in the previous run dp->stack[sp] is already + * set up and dp->tsp will be greater than sp, otherwise a new entry + * needs to be created. + * + * dp->nsp is handled this way: + * + * 1) When an option is pushed onto the stack dp->nsp and dp->tsp are + * both set (by push_opt) to the next stack entry *unless* there is + * only one entry in the new list, in which case dp->stack[sp].end + * is set. + * + * 2) For the top stack entry next_opt is called. The entry must be + * active (dp->stack[sp].end is not set) and either 'nsp' or 'end' + * will be updated as appropriate. + * + * 3) For lower stack entries nsp is set unless the stack entry is + * already at the end. This means that when all the higher entries + * are popped this entry will be too. + */ + if (sp >= dp->tsp) + { + push_opt(dp, sp, opt, search); /* This sets tsp to sp+1 */ + return 1; /* initialized */ + } + + else + { + int ret = 0; /* unchanged */ + + /* An option that is already on the stack; update best_size and best_val + * if appropriate. On the first run there are no previous values and + * dp->write_size will be MAX_SIZE, however on the first run dp->tsp + * starts off as 0. + */ + assert(dp->write_size > 0U && dp->write_size < MAX_SIZE); + + if (dp->stack[sp].best_size > dp->write_size || + (dp->stack[sp].best_size == dp->write_size && + compare_option(dp, sp) > 0)) + { + dp->stack[sp].best_size = dp->write_size; + dp->stack[sp].best_val = dp->value[opt]; + } + + if (sp+1U >= dp->tsp) + { + next_opt(dp, sp); + ret = 1; /* advanced */ + } + + else if (!dp->stack[sp].end) /* Active, not at top of stack */ + dp->nsp = sp+1U; + + return ret; /* advanced || unchanged */ + } +} + +static int +getallopts_(struct display *dp, const png_byte opt, int *value, int record) + /* Like getop but iterate over all the values if the option was set to "all". + */ +{ + if (dp->entry[opt]) /* option was set on command line */ + { + /* Simple, single value, entries don't have a stack frame and have a fixed + * value (it doesn't change once set on the command line). Otherwise the + * value (entry) selected from the command line is 'all': + */ + const char *entry_name = options[opt].values[dp->entry[opt]-1].name; + + if (entry_name == all) + (void)advance_opt(dp, opt, 0/*do not search; iterate*/); + + else if (record) + record_opt(dp, opt, entry_name); + + *value = dp->value[opt]; + return 1; /* set */ + } + + else + return 0; /* not set */ +} + +static int +getallopts(struct display *dp, const char *opt_str, int *value) +{ + return getallopts_(dp, option_index(dp, opt_str, strlen(opt_str)), value, 0); +} + +static int +getsearchopts(struct display *dp, const char *opt_str, int *value) + /* As above except that if the option was not set try a search */ +{ + png_byte istrat; + const png_byte opt = option_index(dp, opt_str, strlen(opt_str)); + int record = options[opt].search; + const char *entry_name; + + /* If it was set on the command line honour the setting, including 'all' + * which will override the built in search: + */ + if (getallopts_(dp, opt, value, record)) + return 1; + + else if (!record) /* not a search option */ + return 0; /* unset and not searched */ + + /* Otherwise decide what to do here. */ + istrat = OPTIND(dp, strategy); + entry_name = range_lo; /* record the value, not the name */ + + if (opt == istrat) /* search all strategies */ + (void)advance_opt(dp, opt, 0/*iterate*/), record=0; + + else if (opt == OPTIND(dp, level)) + { + /* Both RLE and HUFFMAN don't benefit from level increases */ + if (dp->value[istrat] == Z_RLE || dp->value[istrat] == Z_HUFFMAN_ONLY) + dp->value[opt] = 1; + + else /* fixed, filtered or default */ + (void)advance_opt(dp, opt, 1/*search*/), record=0; + } + + else if (opt == OPTIND(dp, windowBits)) + { + /* Changing windowBits for strategies that do not search the window is + * pointless. Huffman-only does not search, RLE only searches backwards + * one byte, so given that the maximum string length is 258, a windowBits + * of 9 is always sufficient. + */ + if (dp->value[istrat] == Z_HUFFMAN_ONLY) + dp->value[opt] = 8; + + else if (dp->value[istrat] == Z_RLE) + dp->value[opt] = 9; + + else /* fixed, filtered or default */ + (void)advance_opt(dp, opt, 1/*search*/), record=0; + } + + else if (opt == OPTIND(dp, memLevel)) + { +# if 0 + (void)advance_opt(dp, opt, 0/*all*/), record=0; +# else + dp->value[opt] = MAX_MEM_LEVEL; +# endif + } + + else /* something else */ + assert(0=="reached"); + + if (record) + record_opt(dp, opt, entry_name); + + /* One of the above searched options: */ + *value = dp->value[opt]; + return 1; +} + +static int +find_val(struct display *dp, png_byte opt, const char *str, size_t len) + /* Like option_index but sets (index+i) of the entry in options[opt] that + * matches str[0..len-1] into dp->entry[opt] as well as returning the actual + * value. + */ +{ + int rlo = INT_MAX, rhi = INT_MIN; + png_byte j, irange = 0; + + for (j=1U; j<=options[opt].value_count; ++j) + { + if (strncmp(options[opt].values[j-1U].name, str, len) == 0 && + options[opt].values[j-1U].name[len] == 0) + { + dp->entry[opt] = j; + return options[opt].values[j-1U].value; + } + else if (options[opt].values[j-1U].name == range_lo) + rlo = options[opt].values[j-1U].value, irange = j; + else if (options[opt].values[j-1U].name == range_hi) + rhi = options[opt].values[j-1U].value; + } + + /* No match on the name, but there may be a range. */ + if (irange > 0) + { + char *ep = NULL; + long l = strtol(str, &ep, 0); + + if (ep == str+len && l >= rlo && l <= rhi) + { + dp->entry[opt] = irange; /* range_lo */ + return (int)/*SAFE*/l; + } + } + + display_log(dp, dp->errset ? INTERNAL_ERROR : USER_ERROR, + "%s: unknown value setting '%.*s'", options[opt].name, + (int)/*SAFE*/len, str); + abort(); /* NOT REACHED */ +} + +static int +opt_check(struct display *dp, const char *arg) +{ + assert(dp->errset == 0); + + if (arg != NULL && arg[0] == '-' && arg[1] == '-') + { + int i = 0, negate = (strncmp(arg+2, "no-", 3) == 0), val; + png_byte j; + + if (negate) + arg += 5; /* --no- */ + + else + arg += 2; /* -- */ + + /* Find the length (expect arg\0 or arg=) */ + while (arg[i] != 0 && arg[i] != '=') ++i; + + /* So arg[0..i-1] is the argument name, this does not return if this isn't + * a valid option name. + */ + j = option_index(dp, arg, i); + + /* It matcheth an option; check the remainder. */ + if (arg[i] == 0) /* no specified value, use the default */ + { + val = options[j].values[negate].value; + dp->entry[j] = (png_byte)/*SAFE*/(negate + 1U); + } + + else + { + const char *list = arg + (i+1); + + /* Expect a single value here unless this is a list, in which case + * multiple values are combined. + */ + if (options[j].opt != LIST) + { + /* find_val sets 'dp->entry[j]' to a non-zero value: */ + val = find_val(dp, j, list, strlen(list)); + + if (negate) + { + if (options[j].opt < OPTION) + val = !val; + + else + { + display_log(dp, USER_ERROR, + "%.*s: option=arg cannot be negated", i, arg); + abort(); /* NOT REACHED */ + } + } + } + + else /* multiple options separated by ',' characters */ + { + /* --no-option negates list values from the default, which should + * therefore be 'all'. Notice that if the option list is empty in + * this case nothing will be removed and therefore --no-option= is + * the same as --option. + */ + if (negate) + val = options[j].values[0].value; + + else + val = 0; + + while (*list != 0) /* allows option= which sets 0 */ + { + /* A value is terminated by the end of the list or a ',' + * character. + */ + int v, iv; + + iv = 0; /* an index into 'list' */ + while (list[++iv] != 0 && list[iv] != ',') {} + + v = find_val(dp, j, list, iv); + + if (negate) + val &= ~v; + + else + val |= v; + + list += iv; + if (*list != 0) + ++list; /* skip the ',' */ + } + } + } + + /* 'val' is the new value, store it for use later and debugging: */ + dp->value[j] = val; + + if (options[j].opt < LEVEL_MASK) + { + /* The handling for error levels is to set the level. */ + if (val) /* Set this level */ + dp->options = (dp->options & ~LEVEL_MASK) | options[j].opt; + + else + display_log(dp, USER_ERROR, + "%.*s: messages cannot be turned off individually; set a message level", + i, arg); + } + + else if (options[j].opt < OPTION) + { + if (val) + dp->options |= options[j].opt; + + else + dp->options &= ~options[j].opt; + } + + return 1; /* this is an option */ + } + + else + return 0; /* not an option */ +} + +#ifdef PNG_PNGCP_TIMING_SUPPORTED +static void +set_timer(struct display *dp, struct timespec *timer) +{ + /* Do the timing using clock_gettime and the per-process timer. */ + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, timer)) + { + display_log(dp, APP_ERROR, + "CLOCK_PROCESS_CPUTIME_ID: %s: timing disabled\n", strerror(errno)); + dp->value[OPTIND(dp,time)] = 0; /* i.e. off */ + } +} + +static void +start_timer(struct display *dp, int what) +{ + if ((dp->value[OPTIND(dp,time)] & what) != 0) + set_timer(dp, what == PNGCP_TIME_READ ? &dp->read_time : &dp->write_time); +} + +static void +end_timer(struct display *dp, int what) +{ + if ((dp->value[OPTIND(dp,time)] & what) != 0) + { + struct timespec t, tmp; + + set_timer(dp, &t); + + if (what == PNGCP_TIME_READ) + tmp = dp->read_time; + + else + tmp = dp->write_time; + + t.tv_sec -= tmp.tv_sec; + t.tv_nsec -= tmp.tv_nsec; + + if (t.tv_nsec < 0) + { + --(t.tv_sec); + t.tv_nsec += 1000000000L; + } + + if (what == PNGCP_TIME_READ) + dp->read_time = t, tmp = dp->read_time_total; + + else + dp->write_time = t, tmp = dp->write_time_total; + + tmp.tv_sec += t.tv_sec; + tmp.tv_nsec += t.tv_nsec; + + if (tmp.tv_nsec >= 1000000000L) + { + ++(tmp.tv_sec); + tmp.tv_nsec -= 1000000000L; + } + + if (what == PNGCP_TIME_READ) + dp->read_time_total = tmp; + + else + dp->write_time_total = tmp; + } +} + +static void +print_time(const char *what, struct timespec t) +{ + printf("%s %.2lu.%.9ld", what, (unsigned long)t.tv_sec, t.tv_nsec); +} +#else /* !PNGCP_TIMING */ +#define start_timer(dp, what) ((void)0) +#define end_timer(dp, what) ((void)0) +#endif /* !PNGCP_TIMING */ + +/* The following is used in main to verify that the final argument is a + * directory: + */ +static int +checkdir(const char *pathname) +{ + struct stat buf; + return stat(pathname, &buf) == 0 && S_ISDIR(buf.st_mode); +} + +/* Work out whether a path is valid (if not a display_log occurs), a directory + * (1 is returned) or a file *or* non-existent (0 is returned). + * + * Used for a write path. + */ +static int +isdir(struct display *dp, const char *pathname) +{ + if (pathname == NULL) + return 0; /* stdout */ + + else if (pathname[0] == 0) + return 1; /* empty string */ + + else + { + struct stat buf; + int ret = stat(pathname, &buf); + + if (ret == 0) /* the entry exists */ + { + if (S_ISDIR(buf.st_mode)) + return 1; + + /* Else expect an object that exists and can be written: */ + if (access(pathname, W_OK) != 0) + display_log(dp, USER_ERROR, "%s: cannot be written (%s)", pathname, + strerror(errno)); + + return 0; /* file (exists, can be written) */ + } + + else /* an error */ + { + /* Non-existence is fine, other errors are not: */ + if (errno != ENOENT) + display_log(dp, USER_ERROR, "%s: invalid output name (%s)", + pathname, strerror(errno)); + + return 0; /* file (does not exist) */ + } + } +} + +static void +makename(struct display *dp, const char *dir, const char *infile) +{ + /* Make a name for an output file (and check it). */ + dp->namebuf[0] = 0; + + if (dir == NULL || infile == NULL) + display_log(dp, INTERNAL_ERROR, "NULL name to makename"); + + else + { + size_t dsize = strlen(dir); + + if (dsize <= (sizeof dp->namebuf)-2) /* Allow for name + '/' + '\0' */ + { + size_t isize = strlen(infile); + size_t istart = isize-1; + + /* This should fail before here: */ + if (infile[istart] == '/') + display_log(dp, INTERNAL_ERROR, "infile with trailing /"); + + memcpy(dp->namebuf, dir, dsize); + if (dsize > 0 && dp->namebuf[dsize-1] != '/') + dp->namebuf[dsize++] = '/'; + + /* Find the rightmost non-/ character: */ + while (istart > 0 && infile[istart-1] != '/') + --istart; + + isize -= istart; + infile += istart; + + if (dsize+isize < (sizeof dp->namebuf)) /* dsize + infile + '\0' */ + { + memcpy(dp->namebuf+dsize, infile, isize+1); + + if (isdir(dp, dp->namebuf)) + display_log(dp, USER_ERROR, "%s: output file is a directory", + dp->namebuf); + } + + else + { + dp->namebuf[dsize] = 0; /* allowed for: -2 at start */ + display_log(dp, USER_ERROR, "%s%s: output file name too long", + dp->namebuf, infile); + } + } + + else + display_log(dp, USER_ERROR, "%s: output directory name too long", dir); + } +} + +/* error handler callbacks for libpng */ +static void PNGCBAPI +display_warning(png_structp pp, png_const_charp warning) +{ + struct display *dp = get_dp(pp); + + /* This is used to prevent repeated warnings while searching */ + if (!dp->no_warnings) + display_log(get_dp(pp), LIBPNG_WARNING, "%s", warning); +} + +static void PNGCBAPI +display_error(png_structp pp, png_const_charp error) +{ + struct display *dp = get_dp(pp); + + display_log(dp, LIBPNG_ERROR, "%s", error); +} + +static void +display_start_read(struct display *dp, const char *filename) +{ + if (filename != NULL) + { + dp->filename = filename; + dp->fp = fopen(filename, "rb"); + } + + else + { + dp->filename = "<stdin>"; + dp->fp = stdin; + } + + dp->w = dp->h = 0U; + dp->bpp = 0U; + dp->size = 0U; + dp->read_size = 0U; + + if (dp->fp == NULL) + display_log(dp, USER_ERROR, "file open failed (%s)", strerror(errno)); +} + +static void PNGCBAPI +read_function(png_structp pp, png_bytep data, png_size_t size) +{ + struct display *dp = get_dp(pp); + + if (size == 0U || fread(data, size, 1U, dp->fp) == 1U) + dp->read_size += size; + + else + { + if (feof(dp->fp)) + display_log(dp, LIBPNG_ERROR, "PNG file truncated"); + else + display_log(dp, LIBPNG_ERROR, "PNG file read failed (%s)", + strerror(errno)); + } +} + +static void +read_png(struct display *dp, const char *filename) +{ + display_clean_read(dp); /* safety */ + display_start_read(dp, filename); + + dp->read_pp = png_create_read_struct(PNG_LIBPNG_VER_STRING, dp, + display_error, display_warning); + if (dp->read_pp == NULL) + display_log(dp, LIBPNG_ERROR, "failed to create read struct"); + +# ifdef PNG_BENIGN_ERRORS_SUPPORTED + png_set_benign_errors(dp->read_pp, 1/*allowed*/); +# endif /* BENIGN_ERRORS */ + +# ifdef FIX_INDEX + if ((dp->options & FIX_INDEX) != 0) + png_set_check_for_invalid_index(dp->read_pp, 1/*on, no warning*/); +# ifdef IGNORE_INDEX + else +# endif /* IGNORE_INDEX */ +# endif /* FIX_INDEX */ +# ifdef IGNORE_INDEX + if ((dp->options & IGNORE_INDEX) != 0) /* DANGEROUS */ + png_set_check_for_invalid_index(dp->read_pp, -1/*off completely*/); +# endif /* IGNORE_INDEX */ + + /* The png_read_png API requires us to make the info struct, but it does the + * call to png_read_info. + */ + dp->ip = png_create_info_struct(dp->read_pp); + if (dp->ip == NULL) + png_error(dp->read_pp, "failed to create info struct"); + + /* Set the IO handling */ + png_set_read_fn(dp->read_pp, dp, read_function); + +# ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + png_set_keep_unknown_chunks(dp->read_pp, PNG_HANDLE_CHUNK_ALWAYS, NULL, + 0); +# endif /* HANDLE_AS_UNKNOWN */ + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + /* Remove the user limits, if any */ + png_set_user_limits(dp->read_pp, 0x7fffffff, 0x7fffffff); +# endif /* SET_USER_LIMITS */ + + /* Now read the PNG. */ + start_timer(dp, PNGCP_TIME_READ); + png_read_png(dp->read_pp, dp->ip, 0U/*transforms*/, NULL/*params*/); + end_timer(dp, PNGCP_TIME_READ); + dp->w = png_get_image_width(dp->read_pp, dp->ip); + dp->h = png_get_image_height(dp->read_pp, dp->ip); + dp->ct = png_get_color_type(dp->read_pp, dp->ip); + dp->bpp = png_get_bit_depth(dp->read_pp, dp->ip) * + png_get_channels(dp->read_pp, dp->ip); + { + /* png_get_rowbytes should never return 0 because the value is set by the + * first call to png_set_IHDR, which should have happened by now, but just + * in case: + */ + png_alloc_size_t rb = png_get_rowbytes(dp->read_pp, dp->ip); + + if (rb == 0) + png_error(dp->read_pp, "invalid row byte count from libpng"); + + /* The size calc can overflow. */ + if ((MAX_SIZE-dp->h)/rb < dp->h) + png_error(dp->read_pp, "image too large"); + + dp->size = rb * dp->h + dp->h/*filter byte*/; + } + +#ifdef FIX_INDEX + if (dp->ct == PNG_COLOR_TYPE_PALETTE && (dp->options & FIX_INDEX) != 0) + { + int max = png_get_palette_max(dp->read_pp, dp->ip); + png_colorp palette = NULL; + int num = -1; + + if (png_get_PLTE(dp->read_pp, dp->ip, &palette, &num) != PNG_INFO_PLTE + || max < 0 || num <= 0 || palette == NULL) + display_log(dp, LIBPNG_ERROR, "invalid png_get_PLTE result"); + + if (max >= num) + { + /* 'Fix' the palette. */ + int i; + png_color newpal[256]; + + for (i=0; i<num; ++i) + newpal[i] = palette[i]; + + /* Fill in any remainder with a warning color: */ + for (; i<=max; ++i) + { + newpal[i].red = 0xbe; + newpal[i].green = 0xad; + newpal[i].blue = 0xed; + } + + png_set_PLTE(dp->read_pp, dp->ip, newpal, i); + } + } +#endif /* FIX_INDEX */ + + display_clean_read(dp); + dp->operation = "none"; +} + +static void +display_start_write(struct display *dp, const char *filename) +{ + assert(dp->fp == NULL); + + if ((dp->options & NOWRITE) != 0) + dp->output_file = "<no write>"; + + else + { + if (filename != NULL) + { + dp->output_file = filename; + dp->fp = fopen(filename, "wb"); + } + + else + { + dp->output_file = "<stdout>"; + dp->fp = stdout; + } + + if (dp->fp == NULL) + display_log(dp, USER_ERROR, "%s: file open failed (%s)", + dp->output_file, strerror(errno)); + } +} + +static void PNGCBAPI +write_function(png_structp pp, png_bytep data, png_size_t size) +{ + struct display *dp = get_dp(pp); + + /* The write fail is classed as a USER_ERROR, so --quiet does not turn it + * off, this seems more likely to be correct. + */ + if (dp->fp == NULL || fwrite(data, size, 1U, dp->fp) == 1U) + { + dp->write_size += size; + if (dp->write_size < size || dp->write_size == MAX_SIZE) + png_error(pp, "IDAT size overflow"); + } + + else + display_log(dp, USER_ERROR, "%s: PNG file write failed (%s)", + dp->output_file, strerror(errno)); +} + +/* Compression option, 'method' is never set: there is no choice. + * + * IMPORTANT: the order of the entries in this macro determines the preference + * order when two different combos of two of these options produce an IDAT of + * the same size. The logic here is to put the things that affect the decoding + * of the PNG image ahead of those that are relevant only to the encoding. + */ +#define SET_COMPRESSION\ + SET(strategy, strategy);\ + SET(windowBits, window_bits);\ + SET(level, level);\ + SET(memLevel, mem_level); + +#ifdef PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +static void +search_compression(struct display *dp) +{ + /* Like set_compression below but use a more restricted search than 'all' */ + int val; + +# define SET(name, func) if (getsearchopts(dp, #name, &val))\ + png_set_compression_ ## func(dp->write_pp, val); + SET_COMPRESSION +# undef SET +} + +static void +set_compression(struct display *dp) +{ + int val; + +# define SET(name, func) if (getallopts(dp, #name, &val))\ + png_set_compression_ ## func(dp->write_pp, val); + SET_COMPRESSION +# undef SET +} + +#ifdef PNG_SW_COMPRESS_level /* 1.7.0+ */ +static void +set_ICC_profile_compression(struct display *dp) +{ + int val; + +# define SET(name, func) if (getallopts(dp, "ICC-profile-" #name, &val))\ + png_set_ICC_profile_compression_ ## func(dp->write_pp, val); + SET_COMPRESSION +# undef SET +} +#else +# define set_ICC_profile_compression(dp) ((void)0) +#endif +#else +# define search_compression(dp) ((void)0) +# define set_compression(dp) ((void)0) +# define set_ICC_profile_compression(dp) ((void)0) +#endif /* WRITE_CUSTOMIZE_COMPRESSION */ + +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +static void +set_text_compression(struct display *dp) +{ + int val; + +# define SET(name, func) if (getallopts(dp, "text-" #name, &val))\ + png_set_text_compression_ ## func(dp->write_pp, val); + SET_COMPRESSION +# undef SET +} +#else +# define set_text_compression(dp) ((void)0) +#endif /* WRITE_CUSTOMIZE_ZTXT_COMPRESSION */ + +static void +write_png(struct display *dp, const char *destname) +{ + display_clean_write(dp); /* safety */ + display_start_write(dp, destname); + + dp->write_pp = png_create_write_struct(PNG_LIBPNG_VER_STRING, dp, + display_error, display_warning); + + if (dp->write_pp == NULL) + display_log(dp, LIBPNG_ERROR, "failed to create write png_struct"); + +# ifdef PNG_BENIGN_ERRORS_SUPPORTED + png_set_benign_errors(dp->write_pp, 1/*allowed*/); +# endif /* BENIGN_ERRORS */ + + png_set_write_fn(dp->write_pp, dp, write_function, NULL/*flush*/); + +#ifdef IGNORE_INDEX + if ((dp->options & IGNORE_INDEX) != 0) /* DANGEROUS */ + png_set_check_for_invalid_index(dp->write_pp, -1/*off completely*/); +#endif /* IGNORE_INDEX */ + + /* Restore the text chunks when using libpng 1.6 or less; this is a macro + * which expands to nothing in 1.7+ In earlier versions it tests + * dp->text_stashed, which is only set (below) *after* the first write. + */ + text_restore(dp); + +# ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + png_set_keep_unknown_chunks(dp->write_pp, PNG_HANDLE_CHUNK_ALWAYS, NULL, + 0); +# endif /* HANDLE_AS_UNKNOWN */ + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + /* Remove the user limits, if any */ + png_set_user_limits(dp->write_pp, 0x7fffffff, 0x7fffffff); +# endif + + /* OPTION HANDLING */ + /* compression outputs, IDAT and zTXt/iTXt: */ + dp->tsp = dp->nsp; + dp->nsp = dp->csp = 0; +# ifdef PNG_SW_COMPRESS_png_level + { + int val; + + /* This sets everything, but then the following options just override + * the specific settings for ICC profiles and text. + */ + if (getallopts(dp, "compression", &val)) + png_set_compression(dp->write_pp, val); + + if (getallopts(dp, "ICC-profile-compression", &val)) + png_set_ICC_profile_compression(dp->write_pp, val); + + if (getallopts(dp, "text-compression", &val)) + png_set_text_compression(dp->write_pp, val); + } +# endif /* png_level support */ + if (dp->options & SEARCH) + search_compression(dp); + else + set_compression(dp); + set_ICC_profile_compression(dp); + set_text_compression(dp); + + { + int val; + + /* The permitted range is 1..0x7FFFFFFF, so the cast is safe */ + if (get_option(dp, "IDAT-size", &val)) + png_set_IDAT_size(dp->write_pp, val); + } + + /* filter handling */ +# ifdef PNG_WRITE_FILTER_SUPPORTED + { + int val; + + if (get_option(dp, "filter", &val)) + png_set_filter(dp->write_pp, PNG_FILTER_TYPE_BASE, val); + } +# endif /* WRITE_FILTER */ + + /* This just uses the 'read' info_struct directly, it contains the image. */ + dp->write_size = 0U; + start_timer(dp, PNGCP_TIME_WRITE); + png_write_png(dp->write_pp, dp->ip, 0U/*transforms*/, NULL/*params*/); + end_timer(dp, PNGCP_TIME_WRITE); + + /* Make sure the file was written ok: */ + if (dp->fp != NULL) + { + FILE *fp = dp->fp; + dp->fp = NULL; + if (fclose(fp)) + display_log(dp, APP_ERROR, "%s: write failed (%s)", + destname == NULL ? "stdout" : destname, strerror(errno)); + } + + /* Clean it on the way out - if control returns to the caller then the + * written_file contains the required data. + */ + display_clean_write(dp); + dp->operation = "none"; +} + +static void +set_windowBits_hi(struct display *dp) +{ + /* windowBits is in the range 8..15 but zlib maps '8' to '9' so it is only + * worth using if the data size is 256 byte or less. + */ + int wb = MAX_WBITS; /* for large images */ + int i = VLSIZE(windowBits_IDAT); + + while (wb > 8 && dp->size <= 1U<<(wb-1)) --wb; + + while (--i >= 0) if (VLNAME(windowBits_IDAT)[i].name == range_hi) break; + + assert(i > 1); /* vl_windowBits_IDAT always has a RANGE() */ + VLNAME(windowBits_IDAT)[i].value = wb; + + assert(VLNAME(windowBits_IDAT)[--i].name == range_lo); + VLNAME(windowBits_IDAT)[i].value = wb > 8 ? 9 : 8; + + /* If wb == 8 then any search has been restricted to just one windowBits + * entry. Record that here to avoid producing a spurious app-level warning + * above. + */ + if (wb == 8) + dp->min_windowBits = OPTIND(dp, windowBits); +} + +static int +better_options(const struct display *dp) +{ + /* Are these options better than the best found so far? Normally the + * options are tested in preference order, best first, however when doing a + * search operation on a range the range values are tested out of order. In + * that case preferable options will get tested later. + * + * This function looks through the stack from the bottom up looking for an + * option that does not match the current best value. When it finds one it + * checks to see if it is more or less desireable and returns true or false + * as appropriate. + * + * Notice that this means that the order options are pushed onto the stack + * conveys a priority; lower/earlier options are more important than later + * ones. + */ + unsigned int sp; + + for (sp=0; sp<dp->csp; ++sp) + { + int c = compare_option(dp, sp); + + if (c < 0) + return 0; /* worse */ + + else if (c > 0) + return 1; /* better */ + } + + assert(0 && "unreached"); +} + +static void +print_search_results(struct display *dp) +{ + assert(dp->filename != NULL); + printf("%s [%ld x %ld %d bpp %s, %lu bytes] %lu -> %lu with '%s'\n", + dp->filename, (unsigned long)dp->w, (unsigned long)dp->h, dp->bpp, + cts(dp->ct), (unsigned long)dp->size, (unsigned long)dp->read_size, + (unsigned long)dp->best_size, dp->best); + fflush(stdout); +} + +static void +log_search(struct display *dp, unsigned int log_depth) +{ + /* Log, and reset, the search so far: */ + if (dp->nsp/*next entry to change*/ <= log_depth) + { + print_search_results(dp); + /* Start again with this entry: */ + dp->best_size = MAX_SIZE; + } +} + +static void +cp_one_file(struct display *dp, const char *filename, const char *destname) +{ + unsigned int log_depth; + + dp->filename = filename; + dp->operation = "read"; + dp->no_warnings = 0; + + /* Read it then write it: */ + if (filename != NULL && access(filename, R_OK) != 0) + display_log(dp, USER_ERROR, "%s: invalid file name (%s)", + filename, strerror(errno)); + + read_png(dp, filename); + + /* But 'destname' may be a directory. */ + dp->operation = "write"; + + /* Limit the upper end of the windowBits range for this file */ + set_windowBits_hi(dp); + + /* For logging, depth to log: */ + { + int val; + + if (get_option(dp, "log-depth", &val) && val >= 0) + log_depth = (unsigned int)/*SAFE*/val; + + else + log_depth = 0U; + } + + if (destname != NULL) /* else stdout */ + { + if (isdir(dp, destname)) + { + makename(dp, destname, filename); + destname = dp->namebuf; + } + + else if (access(destname, W_OK) != 0 && errno != ENOENT) + display_log(dp, USER_ERROR, "%s: invalid output name (%s)", destname, + strerror(errno)); + } + + dp->nsp = 0; + dp->curr[0] = 0; /* acts as a flag for the caller */ + dp->opt_string_start = 0; + dp->best[0] = 0; /* safety */ + dp->best_size = MAX_SIZE; + write_png(dp, destname); + + /* Initialize the 'best' fields: */ + strcpy(dp->best, dp->curr); + dp->best_size = dp->write_size; + + if (dp->nsp > 0) /* interating over lists */ + { + char *tmpname, tmpbuf[(sizeof dp->namebuf) + 4]; + assert(dp->curr[0] == ' ' && dp->tsp > 0); + + /* Cancel warnings on subsequent writes */ + log_search(dp, log_depth); + dp->no_warnings = 1; + + /* Make a temporary name for the subsequent tests: */ + if (destname != NULL) + { + strcpy(tmpbuf, destname); + strcat(tmpbuf, ".tmp"); /* space for .tmp allocated above */ + tmpname = tmpbuf; + } + + else + tmpname = NULL; /* stdout */ + + /* Loop to find the best option. */ + do + { + write_png(dp, tmpname); + + /* And compare the sizes (the write function makes sure write_size + * doesn't overflow.) + */ + assert(dp->csp > 0); + + if (dp->write_size < dp->best_size || + (dp->write_size == dp->best_size && better_options(dp))) + { + if (destname != NULL && rename(tmpname, destname) != 0) + display_log(dp, APP_ERROR, "rename %s %s failed (%s)", tmpname, + destname, strerror(errno)); + + strcpy(dp->best, dp->curr); + dp->best_size = dp->write_size; + } + + else if (tmpname != NULL && unlink(tmpname) != 0) + display_log(dp, APP_WARNING, "unlink %s failed (%s)", tmpname, + strerror(errno)); + + log_search(dp, log_depth); + } + while (dp->nsp > 0); + + /* Do this for the 'sizes' option so that it reports the correct size. */ + dp->write_size = dp->best_size; + } +} + +static int +cppng(struct display *dp, const char *file, const char *gv dest) + /* Exists solely to isolate the setjmp clobbers which some versions of GCC + * erroneously generate. + */ +{ + int ret = setjmp(dp->error_return); + + if (ret == 0) + { + dp->errset = 1; + cp_one_file(dp, file, dest); + dp->errset = 0; + return 0; + } + + else + { + dp->errset = 0; + + if (ret < ERRORS) /* shouldn't longjmp on warnings */ + display_log(dp, INTERNAL_ERROR, "unexpected return code %d", ret); + + return ret; + } +} + +int +main(const int argc, const char * const * const argv) +{ + /* For each file on the command line test it with a range of transforms */ + int option_end; + struct display d; + + display_init(&d); + + d.operation = "options"; + for (option_end = 1; + option_end < argc && opt_check(&d, argv[option_end]); + ++option_end) + { + } + + /* Do a quick check on the directory target case; when there are more than + * two arguments the last one must be a directory. + */ + if (!(d.options & NOWRITE) && option_end+2 < argc && !checkdir(argv[argc-1])) + { + fprintf(stderr, + "pngcp: %s: directory required with more than two arguments\n", + argv[argc-1]); + return 99; + } + + { + int errors = 0; + int i = option_end; + + /* Do this at least once; if there are no arguments stdin/stdout are used. + */ + d.operation = "files"; + do + { + const char *infile = NULL; + const char *outfile = NULL; + int ret; + + if (i < argc) + { + infile = argv[i++]; + if (!(d.options & NOWRITE) && i < argc) + outfile = argv[argc-1]; + } + + ret = cppng(&d, infile, outfile); + + if (ret) + { + if (ret > QUIET) /* abort on user or internal error */ + return 99; + + /* An error: the output is meaningless */ + } + + else if (d.best[0] != 0) + { + /* This result may already have been output, in which case best_size + * has been reset. + */ + if (d.best_size < MAX_SIZE) + print_search_results(&d); + } + + else if (d.options & SIZES) + { + printf("%s [%ld x %ld %d bpp %s, %lu bytes] %lu -> %lu [0x%lx]\n", + infile, (unsigned long)d.w, (unsigned long)d.h, d.bpp, + cts(d.ct), (unsigned long)d.size, (unsigned long)d.read_size, + (unsigned long)d.write_size, (unsigned long)d.results); + fflush(stdout); + } + + /* Here on any return, including failures, except user/internal issues + */ + { + const int pass = (d.options & STRICT) ? + RESULT_STRICT(d.results) : RESULT_RELAXED(d.results); + + if (!pass) + ++errors; + + if (d.options & LOG) + { + int j; + + printf("%s: pngcp", pass ? "PASS" : "FAIL"); + + for (j=1; j<option_end; ++j) + printf(" %s", argv[j]); + + if (infile != NULL) + printf(" %s", infile); + +# ifdef PNG_PNGCP_TIMING_SUPPORTED + /* When logging output the files for each file, if enabled. */ + if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_READ) != 0) + print_time(" read", d.read_time); + + if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_WRITE) != 0) + print_time(" write", d.write_time); +# endif /* PNGCP_TIMING */ + + printf("\n"); + fflush(stdout); + } + } + + display_clean(&d); + } + while (i+!(d.options & NOWRITE) < argc); + /* I.e. for write cases after the first time through the loop require + * there to be at least two arguments left and for the last one to be a + * directory (this was checked above). + */ + + /* Release allocated memory */ + display_destroy(&d); + +# ifdef PNG_PNGCP_TIMING_SUPPORTED + { + int output = 0; + + if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_READ) != 0) + print_time("read", d.read_time_total), output = 1; + + if ((d.value[OPTIND(&d,time)] & PNGCP_TIME_WRITE) != 0) + { + if (output) putchar(' '); + print_time("write", d.write_time_total); + output = 1; + } + + if (output) putchar('\n'); + } +# endif /* PNGCP_TIMING */ + + return errors != 0; + } +} +#else /* !READ_PNG || !WRITE_PNG */ +int +main(void) +{ + fprintf(stderr, "pngcp: no support for png_read/write_image\n"); + return 77; +} +#endif /* !READ_PNG || !WRITE_PNG */ diff --git a/libpng/contrib/tools/pngfix.c b/libpng/contrib/tools/pngfix.c index 67bab6aec..6da970ccb 100644 --- a/libpng/contrib/tools/pngfix.c +++ b/libpng/contrib/tools/pngfix.c @@ -1,8 +1,8 @@ /* pngfix.c * - * Copyright (c) 2014-2015 John Cunningham Bowler + * Copyright (c) 2014-2016 John Cunningham Bowler * - * Last changed in libpng 1.6.17 [March 26, 2015] + * Last changed in libpng 1.6.26 [October 20, 2016] * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer @@ -52,7 +52,10 @@ #ifdef PNG_SETJMP_SUPPORTED #include <setjmp.h> -#if defined(PNG_READ_SUPPORTED) && defined(PNG_EASY_ACCESS_SUPPORTED) +#if defined(PNG_READ_SUPPORTED) && defined(PNG_EASY_ACCESS_SUPPORTED) &&\ + (defined(PNG_READ_DEINTERLACE_SUPPORTED) ||\ + defined(PNG_READ_INTERLACING_SUPPORTED)) + /* zlib.h defines the structure z_stream, an instance of which is included * in this structure and is required for decompressing the LZ compressed * data in PNG files. @@ -71,8 +74,8 @@ * with older builds. */ #if ZLIB_VERNUM < 0x1260 -# define PNGZ_MSG_CAST(s) png_constcast(char*,s) -# define PNGZ_INPUT_CAST(b) png_constcast(png_bytep,b) +# define PNGZ_MSG_CAST(s) constcast(char*,s) +# define PNGZ_INPUT_CAST(b) constcast(png_bytep,b) #else # define PNGZ_MSG_CAST(s) (s) # define PNGZ_INPUT_CAST(b) (b) @@ -86,17 +89,17 @@ /* Copied from pngpriv.h */ #ifdef __cplusplus -# define png_voidcast(type, value) static_cast<type>(value) -# define png_constcast(type, value) const_cast<type>(value) -# define png_aligncast(type, value) \ +# define voidcast(type, value) static_cast<type>(value) +# define constcast(type, value) const_cast<type>(value) +# define aligncast(type, value) \ static_cast<type>(static_cast<void*>(value)) -# define png_aligncastconst(type, value) \ +# define aligncastconst(type, value) \ static_cast<type>(static_cast<const void*>(value)) #else -# define png_voidcast(type, value) (value) -# define png_constcast(type, value) ((type)(value)) -# define png_aligncast(type, value) ((void*)(value)) -# define png_aligncastconst(type, value) ((const void*)(value)) +# define voidcast(type, value) (value) +# define constcast(type, value) ((type)(value)) +# define aligncast(type, value) ((void*)(value)) +# define aligncastconst(type, value) ((const void*)(value)) #endif /* __cplusplus */ #if PNG_LIBPNG_VER < 10700 @@ -134,7 +137,7 @@ #define png_zTXt PNG_U32(122, 84, 88, 116) #endif -/* The 8 byte signature as a pair of 32 bit quantities */ +/* The 8-byte signature as a pair of 32-bit quantities */ #define sig1 PNG_U32(137, 80, 78, 71) #define sig2 PNG_U32( 13, 10, 26, 10) @@ -156,7 +159,7 @@ */ #define UNREACHED 0 -/* 80-bit number handling - a PNG image can be up to (2^31-1)x(2^31-1) 8 byte +/* 80-bit number handling - a PNG image can be up to (2^31-1)x(2^31-1) 8-byte * (16-bit RGBA) pixels in size; that's less than 2^65 bytes or 2^68 bits, so * arithmetic of 80-bit numbers is sufficient. This representation uses an * arbitrary length array of png_uint_16 digits (0..65535). The representation @@ -316,13 +319,13 @@ uarb_mult32(uarb acc, int a_digits, uarb num, int n_digits, png_uint_32 val) a_digits = uarb_mult_digit(acc, a_digits, num, n_digits, (png_uint_16)(val & 0xffff)); - /* Because n_digits and val are >0 the following must be true: */ - assert(a_digits > 0); - val >>= 16; if (val > 0) a_digits = uarb_mult_digit(acc+1, a_digits-1, num, n_digits, (png_uint_16)val) + 1; + + /* Because n_digits and val are >0 the following must be true: */ + assert(a_digits > 0); } return a_digits; @@ -446,7 +449,7 @@ static void make_random_bytes(png_uint_32* seed, void* pv, size_t size) { png_uint_32 u0 = seed[0], u1 = seed[1]; - png_bytep bytes = png_voidcast(png_bytep, pv); + png_bytep bytes = voidcast(png_bytep, pv); /* There are thirty-three bits; the next bit in the sequence is bit-33 XOR * bit-20. The top 1 bit is in u1, the bottom 32 are in u0. @@ -584,7 +587,7 @@ chunk_type_valid(png_uint_32 c) c &= ~PNG_U32(32,32,0,32); t = (c & ~0x1f1f1f1f) ^ 0x40404040; - /* Subtract 65 for each 8 bit quantity, this must not overflow + /* Subtract 65 for each 8-bit quantity, this must not overflow * and each byte must then be in the range 0-25. */ c -= PNG_U32(65,65,65,65); @@ -667,8 +670,8 @@ IDAT_list_extend(struct IDAT_list *tail) if (length < tail->length) /* arithmetic overflow */ length = tail->length; - - next = png_voidcast(IDAT_list*, malloc(IDAT_list_size(NULL, length))); + + next = voidcast(IDAT_list*, malloc(IDAT_list_size(NULL, length))); CLEAR(*next); /* The caller must handle this: */ @@ -921,7 +924,7 @@ emit_string(const char *str, FILE *out) else if (isspace(UCHAR_MAX & *str)) putc('_', out); - + else fprintf(out, "\\%.3o", *str); } @@ -1821,7 +1824,7 @@ IDAT_init(struct IDAT * const idat, struct file * const file) } static png_uint_32 -rechunk_length(struct IDAT *idat) +rechunk_length(struct IDAT *idat, int start) /* Return the length for the next IDAT chunk, taking into account * rechunking. */ @@ -1833,7 +1836,7 @@ rechunk_length(struct IDAT *idat) const struct IDAT_list *cur; unsigned int count; - if (idat->idat_index == 0) /* at the new chunk (first time) */ + if (start) return idat->idat_length; /* use the cache */ /* Otherwise rechunk_length is called at the end of a chunk for the length @@ -1945,7 +1948,7 @@ process_IDAT(struct file *file) list->count = 0; file->idat->idat_list_tail = list; } - + /* And fill in the next IDAT information buffer. */ list->lengths[(list->count)++] = file->chunk->chunk_length; @@ -1992,7 +1995,7 @@ process_IDAT(struct file *file) idat->idat_index = 0; /* Index into chunk data */ /* Update the chunk length to the correct value for the IDAT chunk: */ - file->chunk->chunk_length = rechunk_length(idat); + file->chunk->chunk_length = rechunk_length(idat, 1/*start*/); /* Change the state to writing IDAT chunks */ file->state = STATE_IDAT; @@ -2218,7 +2221,7 @@ zlib_init(struct zlib *zlib, struct IDAT *idat, struct chunk *chunk, /* These values are sticky across reset (in addition to the stuff in the * first block, which is actually constant.) */ - zlib->file_bits = 16; + zlib->file_bits = 24; zlib->ok_bits = 16; /* unset */ zlib->cksum = 0; /* set when a checksum error is detected */ @@ -2301,10 +2304,12 @@ zlib_advance(struct zlib *zlib, png_uint_32 nbytes) zlib->file_bits = file_bits; /* Check against the existing value - it may not need to be - * changed. + * changed. Note that a bogus file_bits is allowed through once, + * to see if it works, but the window_bits value is set to 15, + * the maximum. */ if (new_bits == 0) /* no change */ - zlib->window_bits = file_bits; + zlib->window_bits = ((file_bits > 15) ? 15 : file_bits); else if (new_bits != file_bits) /* rewrite required */ bIn = (png_byte)((bIn & 0xf) + ((new_bits-8) << 4)); @@ -2325,8 +2330,7 @@ zlib_advance(struct zlib *zlib, png_uint_32 nbytes) if (bIn != b2) { /* If the first byte wasn't changed this indicates an error in - * the checksum calculation; signal this by setting file_bits - * (not window_bits) to 0. + * the checksum calculation; signal this by setting 'cksum'. */ if (zlib->file_bits == zlib->window_bits) zlib->cksum = 1; @@ -2585,7 +2589,7 @@ zlib_run(struct zlib *zlib) { struct chunk *chunk = zlib->chunk; int rc; - + assert(zlib->rewrite_offset < chunk->chunk_length); rc = zlib_advance(zlib, chunk->chunk_length - zlib->rewrite_offset); @@ -3469,7 +3473,8 @@ read_callback(png_structp png_ptr, png_bytep buffer, size_t count) /* Write another IDAT chunk. Call rechunk_length to * calculate the length required. */ - length = chunk->chunk_length = rechunk_length(file->idat); + length = chunk->chunk_length = + rechunk_length(file->idat, 0/*end*/); assert(type == png_IDAT); file->write_count = 0; /* for the new chunk */ --(file->write_count); /* fake out the increment below */ @@ -3535,7 +3540,7 @@ get_control(png_const_structrp png_ptr) /* This just returns the (file*). The chunk and idat control structures * don't always exist. */ - struct control *control = png_voidcast(struct control*, + struct control *control = voidcast(struct control*, png_get_error_ptr(png_ptr)); return &control->file; } @@ -3543,7 +3548,7 @@ get_control(png_const_structrp png_ptr) static void allocate(struct file *file, int allocate_idat) { - struct control *control = png_voidcast(struct control*, file->alloc_ptr); + struct control *control = voidcast(struct control*, file->alloc_ptr); if (allocate_idat) { @@ -3853,6 +3858,7 @@ usage(const char *prog) int main(int argc, const char **argv) { + char temp_name[FILENAME_MAX+1]; const char * prog = *argv; const char * outfile = NULL; const char * suffix = NULL; @@ -3955,7 +3961,6 @@ main(int argc, const char **argv) else { size_t outlen = strlen(*argv); - char temp_name[FILENAME_MAX+1]; if (outfile == NULL) /* else this takes precedence */ { @@ -4030,7 +4035,7 @@ main(void) int main(void) { - fprintf(stderr, "pngfix does not work without read support\n"); + fprintf(stderr, "pngfix does not work without read deinterlace support\n"); return 77; } #endif /* PNG_READ_SUPPORTED && PNG_EASY_ACCESS_SUPPORTED */ diff --git a/libpng/contrib/tools/reindent b/libpng/contrib/tools/reindent new file mode 100755 index 000000000..f4df309b6 --- /dev/null +++ b/libpng/contrib/tools/reindent @@ -0,0 +1,25 @@ +#!/bin/sh + +# reindent a libpng C source + +# COPYRIGHT: Written by Glenn Randers-Pehrson, 2016. +# 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. + +# Usage: +# reindent inputtabsize outputtabsize inputcontinuestring outputcontinuestring +# +# Assumes that continued lines begin with indentation plus one space, and +# that continued comments begin with indentation plus " *". +# +# eg, to change libpng coding style from 3-space indentation with 4-space +# continuations to 4-space indentation with 2-space continuations: +# +# reindent 3 4 "\t " " " < example.c > example.c_4_2 +# and to restore the file back to libpng coding style +# reindent 4 3 " " " " < example.c_4_2 > example.c_3_4 + +unexpand --first-only --t $1 | \ + sed -e "/^ *$3[^\*]/{s/$3/$4/}" | \ + expand -t $2 |