• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
50 struct 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 
62 int 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 
102 static void
sprite_op(const struct sprite * sprite,int x_offset,int y_offset,png_imagep image,const png_uint_16 * buffer)103 sprite_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 
187 static int
create_sprite(struct sprite * sprite,int * argc,const char *** argv)188 create_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 
314 static int
add_sprite(png_imagep output,png_bytep out_buf,struct sprite * sprite,int * argc,const char *** argv)315 add_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 
390 static int
simpleover_process(png_imagep output,png_bytep out_buf,int argc,const char ** argv)391 simpleover_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: */
516 out:
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 
main(int argc,const char ** argv)529 int 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 */
649