/*- genpng | |

* | |

* COPYRIGHT: Written by John Cunningham Bowler, 2015. | |

* Revised by Glenn Randers-Pehrson, 2017, to add buffer-size check. | |

* To the extent possible under law, the authors have 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; | |

} | |

#if 1 | |

/* TO do: determine whether this guard against overflow is necessary. | |

* This comment in png.h indicates that it should be safe: "libpng will | |

* refuse to process an image where such an overflow would occur", but | |

* I don't see where the image gets rejected when the buffer is too | |

* large before the malloc is attempted. | |

*/ | |

if (image.height > ((size_t)(-1))/(8*image.width)) { | |

fprintf(stderr, "genpng: image buffer would be too big"); | |

return 1; | |

} | |

#endif | |

/* 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 */ |