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