src/third-party/libpng/contrib/examples/simpleover.c (view raw)
1/*- simpleover
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 * Read several PNG files, which should have an alpha channel or transparency
9 * information, and composite them together to produce one or more 16-bit linear
10 * RGBA intermediates. This involves doing the correct 'over' composition to
11 * combine the alpha channels and corresponding data.
12 *
13 * Finally read an output (background) PNG using the 24-bit RGB format (the
14 * PNG will be composited on green (#00ff00) by default if it has an alpha
15 * channel), and apply the intermediate image generated above to specified
16 * locations in the image.
17 *
18 * The command line has the general format:
19 *
20 * simpleover <background.png> [output.png]
21 * {--sprite=width,height,name {[--at=x,y] {sprite.png}}}
22 * {--add=name {x,y}}
23 *
24 * The --sprite and --add options may occur multiple times. They are executed
25 * in order. --add may refer to any sprite already read.
26 *
27 * This code is intended to show how to composite multiple images together
28 * correctly. Apart from the libpng Simplified API the only work done in here
29 * is to combine multiple input PNG images into a single sprite; this involves
30 * a Porter-Duff 'over' operation and the input PNG images may, as a result,
31 * be regarded as being layered one on top of the other with the first (leftmost
32 * on the command line) being at the bottom and the last on the top.
33 */
34#include <stddef.h>
35#include <stdlib.h>
36#include <string.h>
37#include <stdio.h>
38#include <errno.h>
39
40/* Normally use <png.h> here to get the installed libpng, but this is done to
41 * ensure the code picks up the local libpng implementation, so long as this
42 * file is linked against a sufficiently recent libpng (1.6+) it is ok to
43 * change this to <png.h>:
44 */
45#include "../../png.h"
46
47#ifdef PNG_SIMPLIFIED_READ_SUPPORTED
48
49#define sprite_name_chars 15
50struct sprite {
51 FILE *file;
52 png_uint_16p buffer;
53 unsigned int width;
54 unsigned int height;
55 char name[sprite_name_chars+1];
56};
57
58#if 0 /* div by 65535 test program */
59#include <math.h>
60#include <stdio.h>
61
62int main(void) {
63 double err = 0;
64 unsigned int xerr = 0;
65 unsigned int r = 32769;
66 {
67 unsigned int x = 0;
68
69 do {
70 unsigned int t = x + (x >> 16) /*+ (x >> 31)*/ + r;
71 double v = x, errtest;
72
73 if (t < x) {
74 fprintf(stderr, "overflow: %u+%u -> %u\n", x, r, t);
75 return 1;
76 }
77
78 v /= 65535;
79 errtest = v;
80 t >>= 16;
81 errtest -= t;
82
83 if (errtest > err) {
84 err = errtest;
85 xerr = x;
86
87 if (errtest >= .5) {
88 fprintf(stderr, "error: %u/65535 = %f, not %u, error %f\n",
89 x, v, t, errtest);
90 return 0;
91 }
92 }
93 } while (++x <= 65535U*65535U);
94 }
95
96 printf("error %f @ %u\n", err, xerr);
97
98 return 0;
99}
100#endif /* div by 65535 test program */
101
102static void
103sprite_op(const struct sprite *sprite, int x_offset, int y_offset,
104 png_imagep image, const png_uint_16 *buffer)
105{
106 /* This is where the Porter-Duff 'Over' operator is evaluated; change this
107 * code to change the operator (this could be parameterized). Any other
108 * image processing operation could be used here.
109 */
110
111
112 /* Check for an x or y offset that pushes any part of the image beyond the
113 * right or bottom of the sprite:
114 */
115 if ((y_offset < 0 || (unsigned)/*SAFE*/y_offset < sprite->height) &&
116 (x_offset < 0 || (unsigned)/*SAFE*/x_offset < sprite->width))
117 {
118 unsigned int y = 0;
119
120 if (y_offset < 0)
121 y = -y_offset; /* Skip to first visible row */
122
123 do
124 {
125 unsigned int x = 0;
126
127 if (x_offset < 0)
128 x = -x_offset;
129
130 do
131 {
132 /* In and out are RGBA values, so: */
133 const png_uint_16 *in_pixel = buffer + (y * image->width + x)*4;
134 png_uint_32 in_alpha = in_pixel[3];
135
136 /* This is the optimized Porter-Duff 'Over' operation, when the
137 * input alpha is 0 the output is not changed.
138 */
139 if (in_alpha > 0)
140 {
141 png_uint_16 *out_pixel = sprite->buffer +
142 ((y+y_offset) * sprite->width + (x+x_offset))*4;
143
144 /* This is the weight to apply to the output: */
145 in_alpha = 65535-in_alpha;
146
147 if (in_alpha > 0)
148 {
149 /* The input must be composed onto the output. This means
150 * multiplying the current output pixel value by the inverse
151 * of the input alpha (1-alpha). A division is required but
152 * it is by the constant 65535. Approximate this as:
153 *
154 * (x + (x >> 16) + 32769) >> 16;
155 *
156 * This is exact (and does not overflow) for all values of
157 * x in the range 0..65535*65535. (Note that the calculation
158 * produces the closest integer; the maximum error is <0.5).
159 */
160 png_uint_32 tmp;
161
162# define compose(c)\
163 tmp = out_pixel[c] * in_alpha;\
164 tmp = (tmp + (tmp >> 16) + 32769) >> 16;\
165 out_pixel[c] = tmp + in_pixel[c]
166
167 /* The following is very vectorizable... */
168 compose(0);
169 compose(1);
170 compose(2);
171 compose(3);
172 }
173
174 else
175 out_pixel[0] = in_pixel[0],
176 out_pixel[1] = in_pixel[1],
177 out_pixel[2] = in_pixel[2],
178 out_pixel[3] = in_pixel[3];
179 }
180 }
181 while (++x < image->width);
182 }
183 while (++y < image->height);
184 }
185}
186
187static int
188create_sprite(struct sprite *sprite, int *argc, const char ***argv)
189{
190 /* Read the arguments and create this sprite. The sprite buffer has already
191 * been allocated. This reads the input PNGs one by one in linear format,
192 * composes them onto the sprite buffer (the code in the function above)
193 * then saves the result, converting it on the fly to PNG RGBA 8-bit format.
194 */
195 while (*argc > 0)
196 {
197 char tombstone;
198 int x = 0, y = 0;
199
200 if ((*argv)[0][0] == '-' && (*argv)[0][1] == '-')
201 {
202 /* The only supported option is --at. */
203 if (sscanf((*argv)[0], "--at=%d,%d%c", &x, &y, &tombstone) != 2)
204 break; /* success; caller will parse this option */
205
206 ++*argv, --*argc;
207 }
208
209 else
210 {
211 /* The argument has to be a file name */
212 png_image image;
213
214 image.version = PNG_IMAGE_VERSION;
215 image.opaque = NULL;
216
217 if (png_image_begin_read_from_file(&image, (*argv)[0]))
218 {
219 png_uint_16p buffer;
220
221 image.format = PNG_FORMAT_LINEAR_RGB_ALPHA;
222
223 buffer = malloc(PNG_IMAGE_SIZE(image));
224
225 if (buffer != NULL)
226 {
227 if (png_image_finish_read(&image, NULL/*background*/, buffer,
228 0/*row_stride*/,
229 NULL/*colormap for PNG_FORMAT_FLAG_COLORMAP*/))
230 {
231 /* This is the place where the Porter-Duff 'Over' operator
232 * needs to be done by this code. In fact, any image
233 * processing required can be done here; the data is in
234 * the correct format (linear, 16-bit) and source and
235 * destination are in memory.
236 */
237 sprite_op(sprite, x, y, &image, buffer);
238 free(buffer);
239 ++*argv, --*argc;
240 /* And continue to the next argument */
241 continue;
242 }
243
244 else
245 {
246 free(buffer);
247 fprintf(stderr, "simpleover: read %s: %s\n", (*argv)[0],
248 image.message);
249 }
250 }
251
252 else
253 {
254 fprintf(stderr, "simpleover: out of memory: %lu bytes\n",
255 (unsigned long)PNG_IMAGE_SIZE(image));
256
257 /* png_image_free must be called if we abort the Simplified API
258 * read because of a problem detected in this code. If problems
259 * are detected in the Simplified API it cleans up itself.
260 */
261 png_image_free(&image);
262 }
263 }
264
265 else
266 {
267 /* Failed to read the first argument: */
268 fprintf(stderr, "simpleover: %s: %s\n", (*argv)[0], image.message);
269 }
270
271 return 0; /* failure */
272 }
273 }
274
275 /* All the sprite operations have completed successfully. Save the RGBA
276 * buffer as a PNG using the simplified write API.
277 */
278 sprite->file = tmpfile();
279
280 if (sprite->file != NULL)
281 {
282 png_image save;
283
284 memset(&save, 0, sizeof save);
285 save.version = PNG_IMAGE_VERSION;
286 save.opaque = NULL;
287 save.width = sprite->width;
288 save.height = sprite->height;
289 save.format = PNG_FORMAT_LINEAR_RGB_ALPHA;
290 save.flags = PNG_IMAGE_FLAG_FAST;
291 save.colormap_entries = 0;
292
293 if (png_image_write_to_stdio(&save, sprite->file, 1/*convert_to_8_bit*/,
294 sprite->buffer, 0/*row_stride*/, NULL/*colormap*/))
295 {
296 /* Success; the buffer is no longer needed: */
297 free(sprite->buffer);
298 sprite->buffer = NULL;
299 return 1; /* ok */
300 }
301
302 else
303 fprintf(stderr, "simpleover: write sprite %s: %s\n", sprite->name,
304 save.message);
305 }
306
307 else
308 fprintf(stderr, "simpleover: sprite %s: could not allocate tmpfile: %s\n",
309 sprite->name, strerror(errno));
310
311 return 0; /* fail */
312}
313
314static int
315add_sprite(png_imagep output, png_bytep out_buf, struct sprite *sprite,
316 int *argc, const char ***argv)
317{
318 /* Given a --add argument naming this sprite, perform the operations listed
319 * in the following arguments. The arguments are expected to have the form
320 * (x,y), which is just an offset at which to add the sprite to the
321 * output.
322 */
323 while (*argc > 0)
324 {
325 char tombstone;
326 int x, y;
327
328 if ((*argv)[0][0] == '-' && (*argv)[0][1] == '-')
329 return 1; /* success */
330
331 if (sscanf((*argv)[0], "%d,%d%c", &x, &y, &tombstone) == 2)
332 {
333 /* Now add the new image into the sprite data, but only if it
334 * will fit.
335 */
336 if (x < 0 || y < 0 ||
337 (unsigned)/*SAFE*/x >= output->width ||
338 (unsigned)/*SAFE*/y >= output->height ||
339 sprite->width > output->width-x ||
340 sprite->height > output->height-y)
341 {
342 fprintf(stderr, "simpleover: sprite %s @ (%d,%d) outside image\n",
343 sprite->name, x, y);
344 /* Could just skip this, but for the moment it is an error */
345 return 0; /* error */
346 }
347
348 else
349 {
350 /* Since we know the sprite fits we can just read it into the
351 * output using the simplified API.
352 */
353 png_image in;
354
355 in.version = PNG_IMAGE_VERSION;
356 rewind(sprite->file);
357
358 if (png_image_begin_read_from_stdio(&in, sprite->file))
359 {
360 in.format = PNG_FORMAT_RGB; /* force compose */
361
362 if (png_image_finish_read(&in, NULL/*background*/,
363 out_buf + (y*output->width + x)*3/*RGB*/,
364 output->width*3/*row_stride*/,
365 NULL/*colormap for PNG_FORMAT_FLAG_COLORMAP*/))
366 {
367 ++*argv, --*argc;
368 continue;
369 }
370 }
371
372 /* The read failed: */
373 fprintf(stderr, "simpleover: add sprite %s: %s\n", sprite->name,
374 in.message);
375 return 0; /* error */
376 }
377 }
378
379 else
380 {
381 fprintf(stderr, "simpleover: --add='%s': invalid position %s\n",
382 sprite->name, (*argv)[0]);
383 return 0; /* error */
384 }
385 }
386
387 return 1; /* ok */
388}
389
390static int
391simpleover_process(png_imagep output, png_bytep out_buf, int argc,
392 const char **argv)
393{
394 int result = 1; /* success */
395# define csprites 10/*limit*/
396# define str(a) #a
397 int nsprites = 0;
398 struct sprite sprites[csprites];
399
400 while (argc > 0)
401 {
402 result = 0; /* fail */
403
404 if (strncmp(argv[0], "--sprite=", 9) == 0)
405 {
406 char tombstone;
407
408 if (nsprites < csprites)
409 {
410 int n;
411
412 sprites[nsprites].width = sprites[nsprites].height = 0;
413 sprites[nsprites].name[0] = 0;
414
415 n = sscanf(argv[0], "--sprite=%u,%u,%" str(sprite_name_chars) "s%c",
416 &sprites[nsprites].width, &sprites[nsprites].height,
417 sprites[nsprites].name, &tombstone);
418
419 if ((n == 2 || n == 3) &&
420 sprites[nsprites].width > 0 && sprites[nsprites].height > 0)
421 {
422 size_t buf_size, tmp;
423
424 /* Default a name if not given. */
425 if (sprites[nsprites].name[0] == 0)
426 sprintf(sprites[nsprites].name, "sprite-%d", nsprites+1);
427
428 /* Allocate a buffer for the sprite and calculate the buffer
429 * size:
430 */
431 buf_size = sizeof (png_uint_16 [4]);
432 buf_size *= sprites[nsprites].width;
433 buf_size *= sprites[nsprites].height;
434
435 /* This can overflow a (size_t); check for this: */
436 tmp = buf_size;
437 tmp /= sprites[nsprites].width;
438 tmp /= sprites[nsprites].height;
439
440 if (tmp == sizeof (png_uint_16 [4]))
441 {
442 sprites[nsprites].buffer = malloc(buf_size);
443 /* This buffer must be initialized to transparent: */
444 memset(sprites[nsprites].buffer, 0, buf_size);
445
446 if (sprites[nsprites].buffer != NULL)
447 {
448 sprites[nsprites].file = NULL;
449 ++argv, --argc;
450
451 if (create_sprite(sprites+nsprites++, &argc, &argv))
452 {
453 result = 1; /* still ok */
454 continue;
455 }
456
457 break; /* error */
458 }
459 }
460
461 /* Overflow, or OOM */
462 fprintf(stderr, "simpleover: %s: sprite too large\n", argv[0]);
463 break;
464 }
465
466 else
467 {
468 fprintf(stderr, "simpleover: %s: invalid sprite (%u,%u)\n",
469 argv[0], sprites[nsprites].width, sprites[nsprites].height);
470 break;
471 }
472 }
473
474 else
475 {
476 fprintf(stderr, "simpleover: %s: too many sprites\n", argv[0]);
477 break;
478 }
479 }
480
481 else if (strncmp(argv[0], "--add=", 6) == 0)
482 {
483 const char *name = argv[0]+6;
484 int isprite = nsprites;
485
486 ++argv, --argc;
487
488 while (--isprite >= 0)
489 {
490 if (strcmp(sprites[isprite].name, name) == 0)
491 {
492 if (!add_sprite(output, out_buf, sprites+isprite, &argc, &argv))
493 goto out; /* error in add_sprite */
494
495 break;
496 }
497 }
498
499 if (isprite < 0) /* sprite not found */
500 {
501 fprintf(stderr, "simpleover: --add='%s': sprite not found\n", name);
502 break;
503 }
504 }
505
506 else
507 {
508 fprintf(stderr, "simpleover: %s: unrecognized operation\n", argv[0]);
509 break;
510 }
511
512 result = 1; /* ok */
513 }
514
515 /* Clean up the cache of sprites: */
516out:
517 while (--nsprites >= 0)
518 {
519 if (sprites[nsprites].buffer != NULL)
520 free(sprites[nsprites].buffer);
521
522 if (sprites[nsprites].file != NULL)
523 (void)fclose(sprites[nsprites].file);
524 }
525
526 return result;
527}
528
529int main(int argc, const char **argv)
530{
531 int result = 1; /* default to fail */
532
533 if (argc >= 2)
534 {
535 int argi = 2;
536 const char *output = NULL;
537 png_image image;
538
539 if (argc > 2 && argv[2][0] != '-'/*an operation*/)
540 {
541 output = argv[2];
542 argi = 3;
543 }
544
545 image.version = PNG_IMAGE_VERSION;
546 image.opaque = NULL;
547
548 if (png_image_begin_read_from_file(&image, argv[1]))
549 {
550 png_bytep buffer;
551
552 image.format = PNG_FORMAT_RGB; /* 24-bit RGB */
553
554 buffer = malloc(PNG_IMAGE_SIZE(image));
555
556 if (buffer != NULL)
557 {
558 png_color background = {0, 0xff, 0}; /* fully saturated green */
559
560 if (png_image_finish_read(&image, &background, buffer,
561 0/*row_stride*/, NULL/*colormap for PNG_FORMAT_FLAG_COLORMAP */))
562 {
563 /* At this point png_image_finish_read has cleaned up the
564 * allocated data in png_image, and only the buffer needs to be
565 * freed.
566 *
567 * Perform the remaining operations:
568 */
569 if (simpleover_process(&image, buffer, argc-argi, argv+argi))
570 {
571 /* Write the output: */
572 if ((output != NULL &&
573 png_image_write_to_file(&image, output,
574 0/*convert_to_8bit*/, buffer, 0/*row_stride*/,
575 NULL/*colormap*/)) ||
576 (output == NULL &&
577 png_image_write_to_stdio(&image, stdout,
578 0/*convert_to_8bit*/, buffer, 0/*row_stride*/,
579 NULL/*colormap*/)))
580 result = 0;
581
582 else
583 fprintf(stderr, "simpleover: write %s: %s\n",
584 output == NULL ? "stdout" : output, image.message);
585 }
586
587 /* else simpleover_process writes an error message */
588 }
589
590 else
591 fprintf(stderr, "simpleover: read %s: %s\n", argv[1],
592 image.message);
593
594 free(buffer);
595 }
596
597 else
598 {
599 fprintf(stderr, "simpleover: out of memory: %lu bytes\n",
600 (unsigned long)PNG_IMAGE_SIZE(image));
601
602 /* This is the only place where a 'free' is required; libpng does
603 * the cleanup on error and success, but in this case we couldn't
604 * complete the read because of running out of memory.
605 */
606 png_image_free(&image);
607 }
608 }
609
610 else
611 {
612 /* Failed to read the first argument: */
613 fprintf(stderr, "simpleover: %s: %s\n", argv[1], image.message);
614 }
615 }
616
617 else
618 {
619 /* Usage message */
620 fprintf(stderr,
621 "simpleover: usage: simpleover background.png [output.png]\n"
622 " Output 'background.png' as a 24-bit RGB PNG file in 'output.png'\n"
623 " or, if not given, stdout. 'background.png' will be composited\n"
624 " on fully saturated green.\n"
625 "\n"
626 " Optionally, before output, process additional PNG files:\n"
627 "\n"
628 " --sprite=width,height,name {[--at=x,y] {sprite.png}}\n"
629 " Produce a transparent sprite of size (width,height) and with\n"
630 " name 'name'.\n"
631 " For each sprite.png composite it using a Porter-Duff 'Over'\n"
632 " operation at offset (x,y) in the sprite (defaulting to (0,0)).\n"
633 " Input PNGs will be truncated to the area of the sprite.\n"
634 "\n"
635 " --add='name' {x,y}\n"
636 " Optionally, before output, composite a sprite, 'name', which\n"
637 " must have been previously produced using --sprite, at each\n"
638 " offset (x,y) in the output image. Each sprite must fit\n"
639 " completely within the output image.\n"
640 "\n"
641 " PNG files are processed in the order they occur on the command\n"
642 " line and thus the first PNG processed appears as the bottommost\n"
643 " in the output image.\n");
644 }
645
646 return result;
647}
648#endif /* SIMPLIFIED_READ */