1 | /* genpng
|
---|
2 | *
|
---|
3 | * COPYRIGHT: Written by John Cunningham Bowler, 2015.
|
---|
4 | * Revised by Glenn Randers-Pehrson, 2017, to add buffer-size check.
|
---|
5 | * To the extent possible under law, the authors have waived all copyright and
|
---|
6 | * related or neighboring rights to this work. This work is published from:
|
---|
7 | * United States.
|
---|
8 | *
|
---|
9 | * Generate a PNG with an alpha channel, correctly.
|
---|
10 | *
|
---|
11 | * This is a test case generator; the resultant PNG files are only of interest
|
---|
12 | * to those of us who care about whether the edges of circles are green, red,
|
---|
13 | * or yellow.
|
---|
14 | *
|
---|
15 | * The program generates an RGB+Alpha PNG of a given size containing the given
|
---|
16 | * shapes on a transparent background:
|
---|
17 | *
|
---|
18 | * genpng width height { shape }
|
---|
19 | * shape ::= color width shape x1 y1 x2 y2
|
---|
20 | *
|
---|
21 | * 'color' is:
|
---|
22 | *
|
---|
23 | * black white red green yellow blue brown purple pink orange gray cyan
|
---|
24 | *
|
---|
25 | * The point is to have colors that are linguistically meaningful plus that old
|
---|
26 | * bugbear of the department store dress murders, Cyan, the only color we argue
|
---|
27 | * about.
|
---|
28 | *
|
---|
29 | * 'shape' is:
|
---|
30 | *
|
---|
31 | * circle: an ellipse
|
---|
32 | * square: a rectangle
|
---|
33 | * line: a straight line
|
---|
34 | *
|
---|
35 | * Each shape is followed by four numbers, these are two points in the output
|
---|
36 | * coordinate space (as real numbers) which describe the circle, square, or
|
---|
37 | * line. The shape is filled if it is preceded by 'filled' (not valid for
|
---|
38 | * 'line') or is drawn with a line, in which case the width of the line must
|
---|
39 | * precede the shape.
|
---|
40 | *
|
---|
41 | * The whole set of information can be repeated as many times as desired:
|
---|
42 | *
|
---|
43 | * shape ::= color width shape x1 y1 x2 y2
|
---|
44 | *
|
---|
45 | * color ::= black|white|red|green|yellow|blue
|
---|
46 | * color ::= brown|purple|pink|orange|gray|cyan
|
---|
47 | * width ::= filled
|
---|
48 | * width ::= <number>
|
---|
49 | * shape ::= circle|square|line
|
---|
50 | * x1 ::= <number>
|
---|
51 | * x2 ::= <number>
|
---|
52 | * y1 ::= <number>
|
---|
53 | * y2 ::= <number>
|
---|
54 | *
|
---|
55 | * The output PNG is generated by down-sampling a 4x supersampled image using
|
---|
56 | * a bi-cubic filter. The bi-cubic has a 2 (output) pixel width, so an 8x8
|
---|
57 | * array of super-sampled points contribute to each output pixel. The value of
|
---|
58 | * a super-sampled point is found using an unfiltered, aliased, infinite
|
---|
59 | * precision image: Each shape from the last to the first is checked to see if
|
---|
60 | * the point is in the drawn area and, if it is, the color of the point is the
|
---|
61 | * color of the shape and the alpha is 1, if not the previous shape is checked.
|
---|
62 | *
|
---|
63 | * This is an aliased algorithm because no filtering is done; a point is either
|
---|
64 | * inside or outside each shape and 'close' points do not contribute to the
|
---|
65 | * sample. The down-sampling is relied on to correct the error of not using
|
---|
66 | * a filter.
|
---|
67 | *
|
---|
68 | * The line end-caps are 'flat'; they go through the points. The square line
|
---|
69 | * joins are mitres; the outside of the lines are continued to the point of
|
---|
70 | * intersection.
|
---|
71 | */
|
---|
72 |
|
---|
73 | #include <stddef.h>
|
---|
74 | #include <stdlib.h>
|
---|
75 | #include <string.h>
|
---|
76 | #include <stdio.h>
|
---|
77 | #include <math.h>
|
---|
78 |
|
---|
79 | /* Normally use <png.h> here to get the installed libpng, but this is done to
|
---|
80 | * ensure the code picks up the local libpng implementation:
|
---|
81 | */
|
---|
82 | #include "../../png.h"
|
---|
83 |
|
---|
84 | #if defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) && defined(PNG_STDIO_SUPPORTED)
|
---|
85 |
|
---|
86 | static const struct color
|
---|
87 | {
|
---|
88 | const char *name;
|
---|
89 | double red;
|
---|
90 | double green;
|
---|
91 | double blue;
|
---|
92 | } colors[] =
|
---|
93 | /* color ::= black|white|red|green|yellow|blue
|
---|
94 | * color ::= brown|purple|pink|orange|gray|cyan
|
---|
95 | */
|
---|
96 | {
|
---|
97 | { "black", 0, 0, 0 },
|
---|
98 | { "white", 1, 1, 1 },
|
---|
99 | { "red", 1, 0, 0 },
|
---|
100 | { "green", 0, 1, 0 },
|
---|
101 | { "yellow", 1, 1, 0 },
|
---|
102 | { "blue", 0, 0, 1 },
|
---|
103 | { "brown", .5, .125, 0 },
|
---|
104 | { "purple", 1, 0, 1 },
|
---|
105 | { "pink", 1, .5, .5 },
|
---|
106 | { "orange", 1, .5, 0 },
|
---|
107 | { "gray", 0, .5, .5 },
|
---|
108 | { "cyan", 0, 1, 1 }
|
---|
109 | };
|
---|
110 | #define color_count ((sizeof colors)/(sizeof colors[0]))
|
---|
111 |
|
---|
112 | static const struct color *
|
---|
113 | color_of(const char *arg)
|
---|
114 | {
|
---|
115 | int icolor = color_count;
|
---|
116 |
|
---|
117 | while (--icolor >= 0)
|
---|
118 | {
|
---|
119 | if (strcmp(colors[icolor].name, arg) == 0)
|
---|
120 | return colors+icolor;
|
---|
121 | }
|
---|
122 |
|
---|
123 | fprintf(stderr, "genpng: invalid color %s\n", arg);
|
---|
124 | exit(1);
|
---|
125 | }
|
---|
126 |
|
---|
127 | static double
|
---|
128 | width_of(const char *arg)
|
---|
129 | {
|
---|
130 | if (strcmp(arg, "filled") == 0)
|
---|
131 | return 0;
|
---|
132 |
|
---|
133 | else
|
---|
134 | {
|
---|
135 | char *ep = NULL;
|
---|
136 | double w = strtod(arg, &ep);
|
---|
137 |
|
---|
138 | if (ep != NULL && *ep == 0 && w > 0)
|
---|
139 | return w;
|
---|
140 | }
|
---|
141 |
|
---|
142 | fprintf(stderr, "genpng: invalid line width %s\n", arg);
|
---|
143 | exit(1);
|
---|
144 | }
|
---|
145 |
|
---|
146 | static double
|
---|
147 | coordinate_of(const char *arg)
|
---|
148 | {
|
---|
149 | char *ep = NULL;
|
---|
150 | double w = strtod(arg, &ep);
|
---|
151 |
|
---|
152 | if (ep != NULL && *ep == 0)
|
---|
153 | return w;
|
---|
154 |
|
---|
155 | fprintf(stderr, "genpng: invalid coordinate value %s\n", arg);
|
---|
156 | exit(1);
|
---|
157 | }
|
---|
158 |
|
---|
159 | struct arg; /* forward declaration */
|
---|
160 |
|
---|
161 | typedef int (*shape_fn_ptr)(const struct arg *arg, double x, double y);
|
---|
162 | /* A function to determine if (x,y) is inside the shape.
|
---|
163 | *
|
---|
164 | * There are two implementations:
|
---|
165 | *
|
---|
166 | * inside_fn: returns true if the point is inside
|
---|
167 | * check_fn: returns;
|
---|
168 | * -1: the point is outside the shape by more than the filter width (2)
|
---|
169 | * 0: the point may be inside the shape
|
---|
170 | * +1: the point is inside the shape by more than the filter width
|
---|
171 | */
|
---|
172 | #define OUTSIDE (-1)
|
---|
173 | #define INSIDE (1)
|
---|
174 |
|
---|
175 | struct arg
|
---|
176 | {
|
---|
177 | const struct color *color;
|
---|
178 | shape_fn_ptr inside_fn;
|
---|
179 | shape_fn_ptr check_fn;
|
---|
180 | double width; /* line width, 0 for 'filled' */
|
---|
181 | double x1, y1, x2, y2;
|
---|
182 | };
|
---|
183 |
|
---|
184 | /* IMPLEMENTATION NOTE:
|
---|
185 | *
|
---|
186 | * We want the contribution of each shape to the sample corresponding to each
|
---|
187 | * pixel. This could be obtained by super sampling the image to infinite
|
---|
188 | * dimensions, finding each point within the shape and assigning that a value
|
---|
189 | * '1' while leaving every point outside the shape with value '0' then
|
---|
190 | * downsampling to the image size with sinc; computationally very expensive.
|
---|
191 | *
|
---|
192 | * Approximations are as follows:
|
---|
193 | *
|
---|
194 | * 1) If the pixel coordinate is within the shape assume the sample has the
|
---|
195 | * shape color and is opaque, else assume there is no contribution from
|
---|
196 | * the shape.
|
---|
197 | *
|
---|
198 | * This is the equivalent of aliased rendering or resampling an image with
|
---|
199 | * a block filter. The maximum error in the calculated alpha (which will
|
---|
200 | * always be 0 or 1) is 0.5.
|
---|
201 | *
|
---|
202 | * 2) If the shape is within a square of size 1x1 centered on the pixel assume
|
---|
203 | * that the shape obscures an amount of the pixel equal to its area within
|
---|
204 | * that square.
|
---|
205 | *
|
---|
206 | * This is the equivalent of 'pixel coverage' alpha calculation or resampling
|
---|
207 | * an image with a bi-linear filter. The maximum error is over 0.2, but the
|
---|
208 | * results are often acceptable.
|
---|
209 | *
|
---|
210 | * This can be approximated by applying (1) to a super-sampled image then
|
---|
211 | * downsampling with a bi-linear filter. The error in the super-sampled
|
---|
212 | * image is 0.5 per sample, but the resampling reduces this.
|
---|
213 | *
|
---|
214 | * 3) Use a better filter with a super-sampled image; in the limit this is the
|
---|
215 | * sinc() approach.
|
---|
216 | *
|
---|
217 | * 4) Do the geometric calculation; a bivariate definite integral across the
|
---|
218 | * shape, unfortunately this means evaluating Si(x), the integral of sinc(x),
|
---|
219 | * which is still a lot of math.
|
---|
220 | *
|
---|
221 | * This code uses approach (3) with a bi-cubic filter and 8x super-sampling
|
---|
222 | * and method (1) for the super-samples. This means that the sample is either
|
---|
223 | * 0 or 1, depending on whether the sub-pixel is within or outside the shape.
|
---|
224 | * The bi-cubic weights are also fixed and the 16 required weights are
|
---|
225 | * pre-computed here (note that the 'scale' setting will need to be changed if
|
---|
226 | * 'super' is increased).
|
---|
227 | *
|
---|
228 | * The code also calculates a sum to the edge of the filter. This is not
|
---|
229 | * currently used by could be used to optimize the calculation.
|
---|
230 | */
|
---|
231 | #if 0 /* bc code */
|
---|
232 | scale=10
|
---|
233 | super=8
|
---|
234 | define bicubic(x) {
|
---|
235 | if (x <= 1) return (1.5*x - 2.5)*x*x + 1;
|
---|
236 | if (x < 2) return (((2.5 - 0.5*x)*x - 4)*x + 2);
|
---|
237 | return 0;
|
---|
238 | }
|
---|
239 | define sum(x) {
|
---|
240 | auto s;
|
---|
241 | s = 0;
|
---|
242 | while (x < 2*super) {
|
---|
243 | s = s + bicubic(x/super);
|
---|
244 | x = x + 1;
|
---|
245 | }
|
---|
246 | return s;
|
---|
247 | }
|
---|
248 | define results(x) {
|
---|
249 | auto b, s;
|
---|
250 | b = bicubic(x/super);
|
---|
251 | s = sum(x);
|
---|
252 |
|
---|
253 | print " /*", x, "*/ { ", b, ", ", s, " }";
|
---|
254 | return 1;
|
---|
255 | }
|
---|
256 | x=0
|
---|
257 | while (x<2*super) {
|
---|
258 | x = x + results(x)
|
---|
259 | if (x < 2*super) print ","
|
---|
260 | print "\n"
|
---|
261 | }
|
---|
262 | quit
|
---|
263 | #endif
|
---|
264 |
|
---|
265 | #define BICUBIC1(x) /* |x| <= 1 */ ((1.5*(x)* - 2.5)*(x)*(x) + 1)
|
---|
266 | #define BICUBIC2(x) /* 1 < |x| < 2 */ (((2.5 - 0.5*(x))*(x) - 4)*(x) + 2)
|
---|
267 | #define FILTER_WEIGHT 9 /* Twice the first sum below */
|
---|
268 | #define FILTER_WIDTH 2 /* Actually half the width; -2..+2 */
|
---|
269 | #define FILTER_STEPS 8 /* steps per filter unit */
|
---|
270 | static const double
|
---|
271 | bicubic[16][2] =
|
---|
272 | {
|
---|
273 | /* These numbers are exact; the weight for the filter is 1/9, but this
|
---|
274 | * would make the numbers inexact, so it is not included here.
|
---|
275 | */
|
---|
276 | /* bicubic sum */
|
---|
277 | /* 0*/ { 1.0000000000, 4.5000000000 },
|
---|
278 | /* 1*/ { .9638671875, 3.5000000000 },
|
---|
279 | /* 2*/ { .8671875000, 2.5361328125 },
|
---|
280 | /* 3*/ { .7275390625, 1.6689453125 },
|
---|
281 | /* 4*/ { .5625000000, .9414062500 },
|
---|
282 | /* 5*/ { .3896484375, .3789062500 },
|
---|
283 | /* 6*/ { .2265625000, -.0107421875 },
|
---|
284 | /* 7*/ { .0908203125, -.2373046875 },
|
---|
285 | /* 8*/ { 0, -.3281250000 },
|
---|
286 | /* 9*/ { -.0478515625, -.3281250000 },
|
---|
287 | /*10*/ { -.0703125000, -.2802734375 },
|
---|
288 | /*11*/ { -.0732421875, -.2099609375 },
|
---|
289 | /*12*/ { -.0625000000, -.1367187500 },
|
---|
290 | /*13*/ { -.0439453125, -.0742187500 },
|
---|
291 | /*14*/ { -.0234375000, -.0302734375 },
|
---|
292 | /*15*/ { -.0068359375, -.0068359375 }
|
---|
293 | };
|
---|
294 |
|
---|
295 | static double
|
---|
296 | alpha_calc(const struct arg *arg, double x, double y)
|
---|
297 | {
|
---|
298 | /* For [x-2..x+2],[y-2,y+2] calculate the weighted bicubic given a function
|
---|
299 | * which tells us whether a point is inside or outside the shape. First
|
---|
300 | * check if we need to do this at all:
|
---|
301 | */
|
---|
302 | switch (arg->check_fn(arg, x, y))
|
---|
303 | {
|
---|
304 | case OUTSIDE:
|
---|
305 | return 0; /* all samples outside the shape */
|
---|
306 |
|
---|
307 | case INSIDE:
|
---|
308 | return 1; /* all samples inside the shape */
|
---|
309 |
|
---|
310 | default:
|
---|
311 | {
|
---|
312 | int dy;
|
---|
313 | double alpha = 0;
|
---|
314 |
|
---|
315 | # define FILTER_D (FILTER_WIDTH*FILTER_STEPS-1)
|
---|
316 | for (dy=-FILTER_D; dy<=FILTER_D; ++dy)
|
---|
317 | {
|
---|
318 | double wy = bicubic[abs(dy)][0];
|
---|
319 |
|
---|
320 | if (wy != 0)
|
---|
321 | {
|
---|
322 | double alphay = 0;
|
---|
323 | int dx;
|
---|
324 |
|
---|
325 | for (dx=-FILTER_D; dx<=FILTER_D; ++dx)
|
---|
326 | {
|
---|
327 | double wx = bicubic[abs(dx)][0];
|
---|
328 |
|
---|
329 | if (wx != 0 && arg->inside_fn(arg, x+dx/16, y+dy/16))
|
---|
330 | alphay += wx;
|
---|
331 | }
|
---|
332 |
|
---|
333 | alpha += wy * alphay;
|
---|
334 | }
|
---|
335 | }
|
---|
336 |
|
---|
337 | /* This needs to be weighted for each dimension: */
|
---|
338 | return alpha / (FILTER_WEIGHT*FILTER_WEIGHT);
|
---|
339 | }
|
---|
340 | }
|
---|
341 | }
|
---|
342 |
|
---|
343 | /* These are the shape functions. */
|
---|
344 | /* "square",
|
---|
345 | * { inside_square_filled, check_square_filled },
|
---|
346 | * { inside_square, check_square }
|
---|
347 | */
|
---|
348 | static int
|
---|
349 | square_check(double x, double y, double x1, double y1, double x2, double y2)
|
---|
350 | /* Is x,y inside the square (x1,y1)..(x2,y2)? */
|
---|
351 | {
|
---|
352 | /* Do a modified Cohen-Sutherland on one point, bit patterns that indicate
|
---|
353 | * 'outside' are:
|
---|
354 | *
|
---|
355 | * x<x1 | x<y1 | x<x2 | x<y2
|
---|
356 | * 0 x 0 x To the right
|
---|
357 | * 1 x 1 x To the left
|
---|
358 | * x 0 x 0 Below
|
---|
359 | * x 1 x 1 Above
|
---|
360 | *
|
---|
361 | * So 'inside' is (x<x1) != (x<x2) && (y<y1) != (y<y2);
|
---|
362 | */
|
---|
363 | return ((x<x1) ^ (x<x2)) & ((y<y1) ^ (y<y2));
|
---|
364 | }
|
---|
365 |
|
---|
366 | static int
|
---|
367 | inside_square_filled(const struct arg *arg, double x, double y)
|
---|
368 | {
|
---|
369 | return square_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2);
|
---|
370 | }
|
---|
371 |
|
---|
372 | static int
|
---|
373 | square_check_line(const struct arg *arg, double x, double y, double w)
|
---|
374 | /* Check for a point being inside the boundaries implied by the given arg
|
---|
375 | * and assuming a width 2*w each side of the boundaries. This returns the
|
---|
376 | * 'check' INSIDE/OUTSIDE/0 result but note the semantics:
|
---|
377 | *
|
---|
378 | * +--------------+
|
---|
379 | * | | OUTSIDE
|
---|
380 | * | INSIDE |
|
---|
381 | * | |
|
---|
382 | * +--------------+
|
---|
383 | *
|
---|
384 | * And '0' means within the line boundaries.
|
---|
385 | */
|
---|
386 | {
|
---|
387 | double cx = (arg->x1+arg->x2)/2;
|
---|
388 | double wx = fabs(arg->x1-arg->x2)/2;
|
---|
389 | double cy = (arg->y1+arg->y2)/2;
|
---|
390 | double wy = fabs(arg->y1-arg->y2)/2;
|
---|
391 |
|
---|
392 | if (square_check(x, y, cx-wx-w, cy-wy-w, cx+wx+w, cy+wy+w))
|
---|
393 | {
|
---|
394 | /* Inside, but maybe too far; check for the redundant case where
|
---|
395 | * the lines overlap:
|
---|
396 | */
|
---|
397 | wx -= w;
|
---|
398 | wy -= w;
|
---|
399 | if (wx > 0 && wy > 0 && square_check(x, y, cx-wx, cy-wy, cx+wx, cy+wy))
|
---|
400 | return INSIDE; /* between (inside) the boundary lines. */
|
---|
401 |
|
---|
402 | return 0; /* inside the lines themselves. */
|
---|
403 | }
|
---|
404 |
|
---|
405 | return OUTSIDE; /* outside the boundary lines. */
|
---|
406 | }
|
---|
407 |
|
---|
408 | static int
|
---|
409 | check_square_filled(const struct arg *arg, double x, double y)
|
---|
410 | {
|
---|
411 | /* The filter extends +/-FILTER_WIDTH each side of each output point, so
|
---|
412 | * the check has to expand and contract the square by that amount; '0'
|
---|
413 | * means close enough to the edge of the square that the bicubic filter has
|
---|
414 | * to be run, OUTSIDE means alpha==0, INSIDE means alpha==1.
|
---|
415 | */
|
---|
416 | return square_check_line(arg, x, y, FILTER_WIDTH);
|
---|
417 | }
|
---|
418 |
|
---|
419 | static int
|
---|
420 | inside_square(const struct arg *arg, double x, double y)
|
---|
421 | {
|
---|
422 | /* Return true if within the drawn lines, else false, no need to distinguish
|
---|
423 | * INSIDE vs OUTSIDE here:
|
---|
424 | */
|
---|
425 | return square_check_line(arg, x, y, arg->width/2) == 0;
|
---|
426 | }
|
---|
427 |
|
---|
428 | static int
|
---|
429 | check_square(const struct arg *arg, double x, double y)
|
---|
430 | {
|
---|
431 | /* So for this function a result of 'INSIDE' means inside the actual lines.
|
---|
432 | */
|
---|
433 | double w = arg->width/2;
|
---|
434 |
|
---|
435 | if (square_check_line(arg, x, y, w+FILTER_WIDTH) == 0)
|
---|
436 | {
|
---|
437 | /* Somewhere close to the boundary lines. If far enough inside one of
|
---|
438 | * them then we can return INSIDE:
|
---|
439 | */
|
---|
440 | w -= FILTER_WIDTH;
|
---|
441 |
|
---|
442 | if (w > 0 && square_check_line(arg, x, y, w) == 0)
|
---|
443 | return INSIDE;
|
---|
444 |
|
---|
445 | /* Point is somewhere in the filter region: */
|
---|
446 | return 0;
|
---|
447 | }
|
---|
448 |
|
---|
449 | else /* Inside or outside the square by more than w+FILTER_WIDTH. */
|
---|
450 | return OUTSIDE;
|
---|
451 | }
|
---|
452 |
|
---|
453 | /* "circle",
|
---|
454 | * { inside_circle_filled, check_circle_filled },
|
---|
455 | * { inside_circle, check_circle }
|
---|
456 | *
|
---|
457 | * The functions here are analogous to the square ones; however, they check
|
---|
458 | * the corresponding ellipse as opposed to the rectangle.
|
---|
459 | */
|
---|
460 | static int
|
---|
461 | circle_check(double x, double y, double x1, double y1, double x2, double y2)
|
---|
462 | {
|
---|
463 | if (square_check(x, y, x1, y1, x2, y2))
|
---|
464 | {
|
---|
465 | /* Inside the square, so maybe inside the circle too: */
|
---|
466 | const double cx = (x1 + x2)/2;
|
---|
467 | const double cy = (y1 + y2)/2;
|
---|
468 | const double dx = x1 - x2;
|
---|
469 | const double dy = y1 - y2;
|
---|
470 |
|
---|
471 | x = (x - cx)/dx;
|
---|
472 | y = (y - cy)/dy;
|
---|
473 |
|
---|
474 | /* It is outside if the distance from the center is more than half the
|
---|
475 | * diameter:
|
---|
476 | */
|
---|
477 | return x*x+y*y < .25;
|
---|
478 | }
|
---|
479 |
|
---|
480 | return 0; /* outside */
|
---|
481 | }
|
---|
482 |
|
---|
483 | static int
|
---|
484 | inside_circle_filled(const struct arg *arg, double x, double y)
|
---|
485 | {
|
---|
486 | return circle_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2);
|
---|
487 | }
|
---|
488 |
|
---|
489 | static int
|
---|
490 | circle_check_line(const struct arg *arg, double x, double y, double w)
|
---|
491 | /* Check for a point being inside the boundaries implied by the given arg
|
---|
492 | * and assuming a width 2*w each side of the boundaries. This function has
|
---|
493 | * the same semantic as square_check_line but tests the circle.
|
---|
494 | */
|
---|
495 | {
|
---|
496 | double cx = (arg->x1+arg->x2)/2;
|
---|
497 | double wx = fabs(arg->x1-arg->x2)/2;
|
---|
498 | double cy = (arg->y1+arg->y2)/2;
|
---|
499 | double wy = fabs(arg->y1-arg->y2)/2;
|
---|
500 |
|
---|
501 | if (circle_check(x, y, cx-wx-w, cy-wy-w, cx+wx+w, cy+wy+w))
|
---|
502 | {
|
---|
503 | /* Inside, but maybe too far; check for the redundant case where
|
---|
504 | * the lines overlap:
|
---|
505 | */
|
---|
506 | wx -= w;
|
---|
507 | wy -= w;
|
---|
508 | if (wx > 0 && wy > 0 && circle_check(x, y, cx-wx, cy-wy, cx+wx, cy+wy))
|
---|
509 | return INSIDE; /* between (inside) the boundary lines. */
|
---|
510 |
|
---|
511 | return 0; /* inside the lines themselves. */
|
---|
512 | }
|
---|
513 |
|
---|
514 | return OUTSIDE; /* outside the boundary lines. */
|
---|
515 | }
|
---|
516 |
|
---|
517 | static int
|
---|
518 | check_circle_filled(const struct arg *arg, double x, double y)
|
---|
519 | {
|
---|
520 | return circle_check_line(arg, x, y, FILTER_WIDTH);
|
---|
521 | }
|
---|
522 |
|
---|
523 | static int
|
---|
524 | inside_circle(const struct arg *arg, double x, double y)
|
---|
525 | {
|
---|
526 | return circle_check_line(arg, x, y, arg->width/2) == 0;
|
---|
527 | }
|
---|
528 |
|
---|
529 | static int
|
---|
530 | check_circle(const struct arg *arg, double x, double y)
|
---|
531 | {
|
---|
532 | /* Exactly as the 'square' code. */
|
---|
533 | double w = arg->width/2;
|
---|
534 |
|
---|
535 | if (circle_check_line(arg, x, y, w+FILTER_WIDTH) == 0)
|
---|
536 | {
|
---|
537 | w -= FILTER_WIDTH;
|
---|
538 |
|
---|
539 | if (w > 0 && circle_check_line(arg, x, y, w) == 0)
|
---|
540 | return INSIDE;
|
---|
541 |
|
---|
542 | /* Point is somewhere in the filter region: */
|
---|
543 | return 0;
|
---|
544 | }
|
---|
545 |
|
---|
546 | else /* Inside or outside the square by more than w+FILTER_WIDTH. */
|
---|
547 | return OUTSIDE;
|
---|
548 | }
|
---|
549 |
|
---|
550 | /* "line",
|
---|
551 | * { NULL, NULL }, There is no 'filled' line.
|
---|
552 | * { inside_line, check_line }
|
---|
553 | */
|
---|
554 | static int
|
---|
555 | line_check(double x, double y, double x1, double y1, double x2, double y2,
|
---|
556 | double w, double expand)
|
---|
557 | {
|
---|
558 | /* Shift all the points to (arg->x1, arg->y1) */
|
---|
559 | double lx = x2 - x1;
|
---|
560 | double ly = y2 - y1;
|
---|
561 | double len2 = lx*lx + ly*ly;
|
---|
562 | double cross, dot;
|
---|
563 |
|
---|
564 | x -= x1;
|
---|
565 | y -= y1;
|
---|
566 |
|
---|
567 | /* The dot product is the distance down the line, the cross product is
|
---|
568 | * the distance away from the line:
|
---|
569 | *
|
---|
570 | * distance = |cross| / sqrt(len2)
|
---|
571 | */
|
---|
572 | cross = x * ly - y * lx;
|
---|
573 |
|
---|
574 | /* If 'distance' is more than w the point is definitely outside the line:
|
---|
575 | *
|
---|
576 | * distance >= w
|
---|
577 | * |cross| >= w * sqrt(len2)
|
---|
578 | * cross^2 >= w^2 * len2:
|
---|
579 | */
|
---|
580 | if (cross*cross >= (w+expand)*(w+expand)*len2)
|
---|
581 | return 0; /* outside */
|
---|
582 |
|
---|
583 | /* Now find the distance *along* the line; this comes from the dot product
|
---|
584 | * lx.x+ly.y. The actual distance (in pixels) is:
|
---|
585 | *
|
---|
586 | * distance = dot / sqrt(len2)
|
---|
587 | */
|
---|
588 | dot = lx * x + ly * y;
|
---|
589 |
|
---|
590 | /* The test for 'outside' is:
|
---|
591 | *
|
---|
592 | * distance < 0 || distance > sqrt(len2)
|
---|
593 | * -> dot / sqrt(len2) > sqrt(len2)
|
---|
594 | * -> dot > len2
|
---|
595 | *
|
---|
596 | * But 'expand' is used for the filter width and needs to be handled too:
|
---|
597 | */
|
---|
598 | return dot > -expand && dot < len2+expand;
|
---|
599 | }
|
---|
600 |
|
---|
601 | static int
|
---|
602 | inside_line(const struct arg *arg, double x, double y)
|
---|
603 | {
|
---|
604 | return line_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2, arg->width/2, 0);
|
---|
605 | }
|
---|
606 |
|
---|
607 | static int
|
---|
608 | check_line(const struct arg *arg, double x, double y)
|
---|
609 | {
|
---|
610 | /* The end caps of the line must be checked too; it's not enough just to
|
---|
611 | * widen the line by FILTER_WIDTH; 'expand' exists for this purpose:
|
---|
612 | */
|
---|
613 | if (line_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2, arg->width/2,
|
---|
614 | FILTER_WIDTH))
|
---|
615 | {
|
---|
616 | /* Inside the line+filter; far enough inside that the filter isn't
|
---|
617 | * required?
|
---|
618 | */
|
---|
619 | if (arg->width > 2*FILTER_WIDTH &&
|
---|
620 | line_check(x, y, arg->x1, arg->y1, arg->x2, arg->y2, arg->width/2,
|
---|
621 | -FILTER_WIDTH))
|
---|
622 | return INSIDE;
|
---|
623 |
|
---|
624 | return 0;
|
---|
625 | }
|
---|
626 |
|
---|
627 | return OUTSIDE;
|
---|
628 | }
|
---|
629 |
|
---|
630 | static const struct
|
---|
631 | {
|
---|
632 | const char *name;
|
---|
633 | shape_fn_ptr function[2/*fill,line*/][2];
|
---|
634 | # define FN_INSIDE 0
|
---|
635 | # define FN_CHECK 1
|
---|
636 | } shape_defs[] =
|
---|
637 | {
|
---|
638 | { "square",
|
---|
639 | { { inside_square_filled, check_square_filled },
|
---|
640 | { inside_square, check_square } }
|
---|
641 | },
|
---|
642 | { "circle",
|
---|
643 | { { inside_circle_filled, check_circle_filled },
|
---|
644 | { inside_circle, check_circle } }
|
---|
645 | },
|
---|
646 | { "line",
|
---|
647 | { { NULL, NULL },
|
---|
648 | { inside_line, check_line } }
|
---|
649 | }
|
---|
650 | };
|
---|
651 |
|
---|
652 | #define shape_count ((sizeof shape_defs)/(sizeof shape_defs[0]))
|
---|
653 |
|
---|
654 | static shape_fn_ptr
|
---|
655 | shape_of(const char *arg, double width, int f)
|
---|
656 | {
|
---|
657 | unsigned int i;
|
---|
658 |
|
---|
659 | for (i=0; i<shape_count; ++i) if (strcmp(shape_defs[i].name, arg) == 0)
|
---|
660 | {
|
---|
661 | shape_fn_ptr fn = shape_defs[i].function[width != 0][f];
|
---|
662 |
|
---|
663 | if (fn != NULL)
|
---|
664 | return fn;
|
---|
665 |
|
---|
666 | fprintf(stderr, "genpng: %s %s not supported\n",
|
---|
667 | width == 0 ? "filled" : "unfilled", arg);
|
---|
668 | exit(1);
|
---|
669 | }
|
---|
670 |
|
---|
671 | fprintf(stderr, "genpng: %s: not a valid shape name\n", arg);
|
---|
672 | exit(1);
|
---|
673 | }
|
---|
674 |
|
---|
675 | static void
|
---|
676 | parse_arg(struct arg *arg, const char **argv/*7 arguments*/)
|
---|
677 | {
|
---|
678 | /* shape ::= color width shape x1 y1 x2 y2 */
|
---|
679 | arg->color = color_of(argv[0]);
|
---|
680 | arg->width = width_of(argv[1]);
|
---|
681 | arg->inside_fn = shape_of(argv[2], arg->width, FN_INSIDE);
|
---|
682 | arg->check_fn = shape_of(argv[2], arg->width, FN_CHECK);
|
---|
683 | arg->x1 = coordinate_of(argv[3]);
|
---|
684 | arg->y1 = coordinate_of(argv[4]);
|
---|
685 | arg->x2 = coordinate_of(argv[5]);
|
---|
686 | arg->y2 = coordinate_of(argv[6]);
|
---|
687 | }
|
---|
688 |
|
---|
689 | static png_uint_32
|
---|
690 | read_wh(const char *name, const char *str)
|
---|
691 | /* read a PNG width or height */
|
---|
692 | {
|
---|
693 | char *ep = NULL;
|
---|
694 | unsigned long ul = strtoul(str, &ep, 10);
|
---|
695 |
|
---|
696 | if (ep != NULL && *ep == 0 && ul > 0 && ul <= 0x7fffffff)
|
---|
697 | return (png_uint_32)/*SAFE*/ul;
|
---|
698 |
|
---|
699 | fprintf(stderr, "genpng: %s: invalid number %s\n", name, str);
|
---|
700 | exit(1);
|
---|
701 | }
|
---|
702 |
|
---|
703 | static void
|
---|
704 | pixel(png_uint_16p p, struct arg *args, int nargs, double x, double y)
|
---|
705 | {
|
---|
706 | /* Fill in the pixel by checking each shape (args[nargs]) for effects on
|
---|
707 | * the corresponding sample:
|
---|
708 | */
|
---|
709 | double r=0, g=0, b=0, a=0;
|
---|
710 |
|
---|
711 | while (--nargs >= 0 && a != 1)
|
---|
712 | {
|
---|
713 | /* NOTE: alpha_calc can return a value outside the range 0..1 with the
|
---|
714 | * bicubic filter.
|
---|
715 | */
|
---|
716 | const double alpha = alpha_calc(args+nargs, x, y) * (1-a);
|
---|
717 |
|
---|
718 | r += alpha * args[nargs].color->red;
|
---|
719 | g += alpha * args[nargs].color->green;
|
---|
720 | b += alpha * args[nargs].color->blue;
|
---|
721 | a += alpha;
|
---|
722 | }
|
---|
723 |
|
---|
724 | /* 'a' may be negative or greater than 1; if it is, negative clamp the
|
---|
725 | * pixel to 0 if >1 clamp r/g/b:
|
---|
726 | */
|
---|
727 | if (a > 0)
|
---|
728 | {
|
---|
729 | if (a > 1)
|
---|
730 | {
|
---|
731 | if (r > 1) r = 1;
|
---|
732 | if (g > 1) g = 1;
|
---|
733 | if (b > 1) b = 1;
|
---|
734 | a = 1;
|
---|
735 | }
|
---|
736 |
|
---|
737 | /* And fill in the pixel: */
|
---|
738 | p[0] = (png_uint_16)/*SAFE*/round(r * 65535);
|
---|
739 | p[1] = (png_uint_16)/*SAFE*/round(g * 65535);
|
---|
740 | p[2] = (png_uint_16)/*SAFE*/round(b * 65535);
|
---|
741 | p[3] = (png_uint_16)/*SAFE*/round(a * 65535);
|
---|
742 | }
|
---|
743 |
|
---|
744 | else
|
---|
745 | p[3] = p[2] = p[1] = p[0] = 0;
|
---|
746 | }
|
---|
747 |
|
---|
748 | int
|
---|
749 | main(int argc, const char **argv)
|
---|
750 | {
|
---|
751 | int convert_to_8bit = 0;
|
---|
752 |
|
---|
753 | /* There is one option: --8bit: */
|
---|
754 | if (argc > 1 && strcmp(argv[1], "--8bit") == 0)
|
---|
755 | --argc, ++argv, convert_to_8bit = 1;
|
---|
756 |
|
---|
757 | if (argc >= 3)
|
---|
758 | {
|
---|
759 | png_uint_16p buffer;
|
---|
760 | int nshapes;
|
---|
761 | png_image image;
|
---|
762 | # define max_shapes 256
|
---|
763 | struct arg arg_list[max_shapes];
|
---|
764 |
|
---|
765 | /* The libpng Simplified API write code requires a fully initialized
|
---|
766 | * structure.
|
---|
767 | */
|
---|
768 | memset(&image, 0, sizeof image);
|
---|
769 | image.version = PNG_IMAGE_VERSION;
|
---|
770 | image.opaque = NULL;
|
---|
771 | image.width = read_wh("width", argv[1]);
|
---|
772 | image.height = read_wh("height", argv[2]);
|
---|
773 | image.format = PNG_FORMAT_LINEAR_RGB_ALPHA;
|
---|
774 | image.flags = 0;
|
---|
775 | image.colormap_entries = 0;
|
---|
776 |
|
---|
777 | /* Check the remainder of the arguments */
|
---|
778 | for (nshapes=0; 3+7*(nshapes+1) <= argc && nshapes < max_shapes;
|
---|
779 | ++nshapes)
|
---|
780 | parse_arg(arg_list+nshapes, argv+3+7*nshapes);
|
---|
781 |
|
---|
782 | if (3+7*nshapes != argc)
|
---|
783 | {
|
---|
784 | fprintf(stderr, "genpng: %s: too many arguments\n", argv[3+7*nshapes]);
|
---|
785 | return 1;
|
---|
786 | }
|
---|
787 |
|
---|
788 | #if 1
|
---|
789 | /* TO do: determine whether this guard against overflow is necessary.
|
---|
790 | * This comment in png.h indicates that it should be safe: "libpng will
|
---|
791 | * refuse to process an image where such an overflow would occur", but
|
---|
792 | * I don't see where the image gets rejected when the buffer is too
|
---|
793 | * large before the malloc is attempted.
|
---|
794 | */
|
---|
795 | if (image.height > ((size_t)(-1))/(8*image.width)) {
|
---|
796 | fprintf(stderr, "genpng: image buffer would be too big");
|
---|
797 | return 1;
|
---|
798 | }
|
---|
799 | #endif
|
---|
800 |
|
---|
801 | /* Create the buffer: */
|
---|
802 | buffer = malloc(PNG_IMAGE_SIZE(image));
|
---|
803 |
|
---|
804 | if (buffer != NULL)
|
---|
805 | {
|
---|
806 | png_uint_32 y;
|
---|
807 |
|
---|
808 | /* Write each row... */
|
---|
809 | for (y=0; y<image.height; ++y)
|
---|
810 | {
|
---|
811 | png_uint_32 x;
|
---|
812 |
|
---|
813 | /* Each pixel in each row: */
|
---|
814 | for (x=0; x<image.width; ++x)
|
---|
815 | pixel(buffer + 4*(x + y*image.width), arg_list, nshapes, x, y);
|
---|
816 | }
|
---|
817 |
|
---|
818 | /* Write the result (to stdout) */
|
---|
819 | if (png_image_write_to_stdio(&image, stdout, convert_to_8bit,
|
---|
820 | buffer, 0/*row_stride*/, NULL/*colormap*/))
|
---|
821 | {
|
---|
822 | free(buffer);
|
---|
823 | return 0; /* success */
|
---|
824 | }
|
---|
825 |
|
---|
826 | else
|
---|
827 | fprintf(stderr, "genpng: write stdout: %s\n", image.message);
|
---|
828 |
|
---|
829 | free(buffer);
|
---|
830 | }
|
---|
831 |
|
---|
832 | else
|
---|
833 | fprintf(stderr, "genpng: out of memory: %lu bytes\n",
|
---|
834 | (unsigned long)PNG_IMAGE_SIZE(image));
|
---|
835 | }
|
---|
836 |
|
---|
837 | else
|
---|
838 | {
|
---|
839 | /* Wrong number of arguments */
|
---|
840 | fprintf(stderr, "genpng: usage: genpng [--8bit] width height {shape}\n"
|
---|
841 | " Generate a transparent PNG in RGBA (truecolor+alpha) format\n"
|
---|
842 | " containing the given shape or shapes. Shapes are defined:\n"
|
---|
843 | "\n"
|
---|
844 | " shape ::= color width shape x1 y1 x2 y2\n"
|
---|
845 | " color ::= black|white|red|green|yellow|blue\n"
|
---|
846 | " color ::= brown|purple|pink|orange|gray|cyan\n"
|
---|
847 | " width ::= filled|<number>\n"
|
---|
848 | " shape ::= circle|square|line\n"
|
---|
849 | " x1,x2 ::= <number>\n"
|
---|
850 | " y1,y2 ::= <number>\n"
|
---|
851 | "\n"
|
---|
852 | " Numbers are floating point numbers describing points relative to\n"
|
---|
853 | " the top left of the output PNG as pixel coordinates. The 'width'\n"
|
---|
854 | " parameter is either the width of the line (in output pixels) used\n"
|
---|
855 | " to draw the shape or 'filled' to indicate that the shape should\n"
|
---|
856 | " be filled with the color.\n"
|
---|
857 | "\n"
|
---|
858 | " Colors are interpreted loosely to give access to the eight full\n"
|
---|
859 | " intensity RGB values:\n"
|
---|
860 | "\n"
|
---|
861 | " black, red, green, blue, yellow, cyan, purple, white,\n"
|
---|
862 | "\n"
|
---|
863 | " Cyan is full intensity blue+green; RGB(0,1,1), plus the following\n"
|
---|
864 | " lower intensity values:\n"
|
---|
865 | "\n"
|
---|
866 | " brown: red+orange: RGB(0.5, 0.125, 0) (dark red+orange)\n"
|
---|
867 | " pink: red+white: RGB(1.0, 0.5, 0.5)\n"
|
---|
868 | " orange: red+yellow: RGB(1.0, 0.5, 0)\n"
|
---|
869 | " gray: black+white: RGB(0.5, 0.5, 0.5)\n"
|
---|
870 | "\n"
|
---|
871 | " The RGB values are selected to make detection of aliasing errors\n"
|
---|
872 | " easy. The names are selected to make the description of errors\n"
|
---|
873 | " easy.\n"
|
---|
874 | "\n"
|
---|
875 | " The PNG is written to stdout, if --8bit is given a 32bpp RGBA sRGB\n"
|
---|
876 | " file is produced, otherwise a 64bpp RGBA linear encoded file is\n"
|
---|
877 | " written.\n");
|
---|
878 | }
|
---|
879 |
|
---|
880 | return 1;
|
---|
881 | }
|
---|
882 | #endif /* SIMPLIFIED_WRITE && STDIO */
|
---|