0f12df19a1
simpleover. Also added a comment to png.h pointing out that the pointer must be set to NULL! (simpleover crashes with any slightly complex command lines without this fix.)
865 lines
25 KiB
C
865 lines
25 KiB
C
/*- 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"
|
|
|
|
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;
|
|
}
|