/*- simpleover
 *
 * COPYRIGHT: Written by John Cunningham Bowler, 2015.
 * To the extent possible under law, the author has waived all copyright and
 * related or neighboring rights to this work.  This work is published from:
 * United States.
 *
 * Read several PNG files, which should have an alpha channel or transparency
 * information, and composite them together to produce one or more 16-bit
 * linear RGBA intermediates.  This involves doing the 'over' compositing
 * operation to combine the alpha channels and corresponding data.
 *
 * Finally read an output (background) PNG using the 24-bit RGB format (the
 * PNG will be composited on green (#00ff00) by default if it has an alpha
 * channel), and apply the intermediate image generated above to specified
 * locations in the image.
 *
 * The command line has the general format:
 *
 *    simpleover <background.png> [output.png]
 *        {--sprite=width,height,name {[--at=x,y] {sprite.png}}}
 *        {--add=name {x,y}}
 *
 * The --sprite and --add options may occur multiple times. They are executed
 * in order.  --add may refer to any sprite already read.
 *
 * This code is intended to show how to composite multiple images together
 * correctly.  Apart from the libpng Simplified API the only work done in here
 * is to combine multiple input PNG images into a single sprite; this involves
 * a Porter-Duff 'over' operation and the input PNG images may, as a result,
 * be regarded as being layered one on top of the other with the first
 * (leftmost on the command line) being at the bottom and the last on the top.
 */
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Normally use <png.h> here to get the installed libpng, but this is done to
 * ensure the code picks up the local libpng implementation, so long as this
 * file is linked against a sufficiently recent libpng (1.6+) it is ok to
 * change this to <png.h>:
 */
#include "../../png.h"

#if !defined(PNG_SIMPLIFIED_READ_SUPPORTED)
#error This program requires libpng supporting the simplified read API
#endif

#define sprite_name_chars 15
struct sprite
{
   FILE *file;
   png_uint_16p buffer;
   unsigned int width;
   unsigned int height;
   char name[sprite_name_chars + 1];
};

#if 0 /* div by 65535 test program */
#include <math.h>
#include <stdio.h>

int
main(void)
{
   double err = 0;
   unsigned int xerr = 0;
   unsigned int r = 32769;
   unsigned int x = 0;

   do
   {
      unsigned int t = x + (x >> 16) /*+ (x >> 31)*/ + r;
      double v = x, errtest;

      if (t < x)
      {
         fprintf(stderr, "overflow: %u+%u -> %u\n", x, r, t);
         return 1;
      }

      v /= 65535;
      errtest = v;
      t >>= 16;
      errtest -= t;

      if (errtest > err)
      {
         err = errtest;
         xerr = x;

         if (errtest >= .5)
         {
            fprintf(stderr, "error: %u/65535 = %f, not %u, error %f\n",
                    x, v, t, errtest);
            return 0;
         }
      }
   } while (++x <= 65535U * 65535U);

   printf("error %f @ %u\n", err, xerr);

   return 0;
}
#endif /* div by 65535 test program */

static void
sprite_op(const struct sprite *sprite, int x_offset, int y_offset,
          png_imagep image, const png_uint_16 *buffer)
{
   /* This is where the Porter-Duff 'Over' operator is evaluated; change this
    * code to change the operator (this could be parameterized).  Any other
    * image processing operation could be used here.
    */


   /* Check for an x or y offset that pushes any part of the image beyond the
    * right or bottom of the sprite:
    */
   if ((y_offset < 0 || /*SAFE*/ (unsigned)y_offset < sprite->height) &&
       (x_offset < 0 || /*SAFE*/ (unsigned)x_offset < sprite->width))
   {
      unsigned int y = 0;

      if (y_offset < 0)
         y = -y_offset; /* Skip to first visible row */

      do
      {
         unsigned int x = 0;

         if (x_offset < 0)
            x = -x_offset;

         do
         {
            /* In and out are RGBA values, so: */
            const png_uint_16 *in_pixel = buffer + (y * image->width + x) * 4;
            png_uint_32 in_alpha = in_pixel[3];

            /* This is the optimized Porter-Duff 'Over' operation, when the
             * input alpha is 0 the output is not changed.
             */
            if (in_alpha > 0)
            {
               png_uint_16 *out_pixel = sprite->buffer +
                  ((y + y_offset) * sprite->width + (x + x_offset)) * 4;

               /* This is the weight to apply to the output: */
               in_alpha = 65535 - in_alpha;

               if (in_alpha > 0)
               {
                  /* The input must be composed onto the output. This means
                   * multiplying the current output pixel value by the inverse
                   * of the input alpha (1-alpha). A division is required but
                   * it is by the constant 65535.  Approximate this as:
                   *
                   *     (x + (x >> 16) + 32769) >> 16;
                   *
                   * This is exact (and does not overflow) for all values of
                   * x in the range 0..65535*65535.  (Note that the calculation
                   * produces the closest integer; the maximum error is <0.5).
                   */
                  png_uint_32 tmp;

#                 define compose(c) \
                     tmp = out_pixel[c] * in_alpha; \
                     tmp = (tmp + (tmp >> 16) + 32769) >> 16; \
                     out_pixel[c] = tmp + in_pixel[c]

                  /* The following is very vectorizable... */
                  compose(0);
                  compose(1);
                  compose(2);
                  compose(3);
               }

               else
               {
                  out_pixel[0] = in_pixel[0];
                  out_pixel[1] = in_pixel[1];
                  out_pixel[2] = in_pixel[2];
                  out_pixel[3] = in_pixel[3];
               }
            }
         } while (++x < image->width);
      } while (++y < image->height);
   }
}

static int
create_sprite(struct sprite *sprite, int *argc, const char ***argv)
{
   /* Read the arguments and create this sprite. The sprite buffer has already
    * been allocated. This reads the input PNGs one by one in linear format,
    * composes them onto the sprite buffer (the code in the function above)
    * then saves the result, converting it on the fly to PNG RGBA 8-bit format.
    */
   while (*argc > 0)
   {
      char tombstone;
      int x = 0, y = 0;

      if ((*argv)[0][0] == '-' && (*argv)[0][1] == '-')
      {
         /* The only supported option is --at. */
         if (sscanf((*argv)[0], "--at=%d,%d%c", &x, &y, &tombstone) != 2)
            break; /* success; caller will parse this option */

         ++*argv, --*argc;
      }

      else
      {
         /* The argument has to be a file name */
         png_image image;

         image.version = PNG_IMAGE_VERSION;
         image.opaque = NULL;

         if (png_image_begin_read_from_file(&image, (*argv)[0]))
         {
            png_uint_16p buffer;

            image.format = PNG_FORMAT_LINEAR_RGB_ALPHA;

            buffer = malloc(PNG_IMAGE_SIZE(image));

            if (buffer != NULL)
            {
               if (png_image_finish_read(&image, NULL /*background*/, buffer,
                                         0 /*row_stride*/, NULL /*colormap*/))
               {
                  /* This is the place where the Porter-Duff 'Over' operator
                   * needs to be done by this code.  In fact, any image
                   * processing required can be done here; the data is in
                   * the correct format (linear, 16-bit) and source and
                   * destination are in memory.
                   */
                  sprite_op(sprite, x, y, &image, buffer);
                  free(buffer);
                  ++*argv, --*argc;
                  /* And continue to the next argument */
                  continue;
               }

               else
               {
                  free(buffer);
                  fprintf(stderr, "simpleover: read %s: %s\n", (*argv)[0],
                          image.message);
               }
            }

            else
            {
               fprintf(stderr, "simpleover: out of memory: %lu bytes\n",
                       (unsigned long)PNG_IMAGE_SIZE(image));

               /* png_image_free must be called if we abort the Simplified API
                * read because of a problem detected in this code.  If problems
                * are detected in the Simplified API it cleans up itself.
                */
               png_image_free(&image);
            }
         }

         else
         {
            /* Failed to read the first argument: */
            fprintf(stderr, "simpleover: %s: %s\n", (*argv)[0], image.message);
         }

         return 0; /* failure */
      }
   }

   /* All the sprite operations have completed successfully. Save the RGBA
    * buffer as a PNG using the simplified write API.
    */
   sprite->file = tmpfile();

   if (sprite->file != NULL)
   {
      png_image save;

      memset(&save, 0, sizeof save);
      save.version = PNG_IMAGE_VERSION;
      save.opaque = NULL;
      save.width = sprite->width;
      save.height = sprite->height;
      save.format = PNG_FORMAT_LINEAR_RGB_ALPHA;
      save.flags = PNG_IMAGE_FLAG_FAST;
      save.colormap_entries = 0;

      if (png_image_write_to_stdio(&save, sprite->file, 1 /*convert_to_8_bit*/,
                                   sprite->buffer, 0 /*row_stride*/,
                                   NULL /*colormap*/))
      {
         /* Success; the buffer is no longer needed: */
         free(sprite->buffer);
         sprite->buffer = NULL;
         return 1; /* ok */
      }

      else
         fprintf(stderr, "simpleover: write sprite %s: %s\n", sprite->name,
                 save.message);
   }

   else
      fprintf(stderr,
              "simpleover: sprite %s: could not allocate tmpfile: %s\n",
              sprite->name, strerror(errno));

   return 0; /* fail */
}

static int
add_sprite(png_imagep output, png_bytep out_buf, struct sprite *sprite,
           int *argc, const char ***argv)
{
   /* Given a --add argument naming this sprite, perform the operations listed
    * in the following arguments.  The arguments are expected to have the form
    * (x,y), which is just an offset at which to add the sprite to the
    * output.
    */
   while (*argc > 0)
   {
      char tombstone;
      int x, y;

      if ((*argv)[0][0] == '-' && (*argv)[0][1] == '-')
         return 1; /* success */

      if (sscanf((*argv)[0], "%d,%d%c", &x, &y, &tombstone) == 2)
      {
         /* Now add the new image into the sprite data, but only if it
          * will fit.
          */
         if (x < 0 || y < 0 ||
             /*SAFE*/ (unsigned)x >= output->width ||
             /*SAFE*/ (unsigned)y >= output->height ||
             sprite->width > output->width - x ||
             sprite->height > output->height - y)
         {
            fprintf(stderr, "simpleover: sprite %s @ (%d,%d) outside image\n",
                    sprite->name, x, y);
            /* Could just skip this, but for the moment it is an error */
            return 0; /* error */
         }

         else
         {
            /* Since we know the sprite fits we can just read it into the
             * output using the simplified API.
             */
            png_image in;

            in.version = PNG_IMAGE_VERSION;
            rewind(sprite->file);

            if (png_image_begin_read_from_stdio(&in, sprite->file))
            {
               in.format = PNG_FORMAT_RGB; /* force compose */

               if (png_image_finish_read(
                      &in, NULL /*background*/,
                      out_buf + (y * output->width + x) * 3 /*RGB*/,
                      output->width * 3 /*row_stride*/, NULL /*colormap*/))
               {
                  ++*argv, --*argc;
                  continue;
               }
            }

            /* The read failed: */
            fprintf(stderr, "simpleover: add sprite %s: %s\n", sprite->name,
                    in.message);
            return 0; /* error */
         }
      }

      else
      {
         fprintf(stderr, "simpleover: --add='%s': invalid position %s\n",
                 sprite->name, (*argv)[0]);
         return 0; /* error */
      }
   }

   return 1; /* ok */
}

static int
simpleover_process(png_imagep output, png_bytep out_buf, int argc,
                   const char **argv)
{
   int result = 1; /* success */
#  define csprites 10 /*limit*/
#  define str(a) #a
   int nsprites = 0;
   struct sprite sprites[csprites];

   while (argc > 0)
   {
      result = 0; /* fail */

      if (strncmp(argv[0], "--sprite=", 9) == 0)
      {
         char tombstone;

         if (nsprites < csprites)
         {
            int n;

            sprites[nsprites].width = sprites[nsprites].height = 0;
            sprites[nsprites].name[0] = 0;

            n = sscanf(argv[0],
                       "--sprite=%u,%u,%" str(sprite_name_chars) "s%c",
                       &sprites[nsprites].width, &sprites[nsprites].height,
                       sprites[nsprites].name, &tombstone);

            if ((n == 2 || n == 3) &&
                (sprites[nsprites].width > 0) &&
                (sprites[nsprites].height > 0))
            {
               size_t buf_size, tmp;

               /* Default a name if not given. */
               if (sprites[nsprites].name[0] == 0)
                  sprintf(sprites[nsprites].name, "sprite-%d", nsprites + 1);

               /* Allocate a buffer for the sprite and calculate the buffer
                * size:
                */
               buf_size = sizeof(png_uint_16[4]);
               buf_size *= sprites[nsprites].width;
               buf_size *= sprites[nsprites].height;

               /* This can overflow a (size_t); check for this: */
               tmp = buf_size;
               tmp /= sprites[nsprites].width;
               tmp /= sprites[nsprites].height;

               if (tmp == sizeof(png_uint_16[4]))
               {
                  sprites[nsprites].buffer = malloc(buf_size);
                  /* This buffer must be initialized to transparent: */
                  memset(sprites[nsprites].buffer, 0, buf_size);

                  if (sprites[nsprites].buffer != NULL)
                  {
                     sprites[nsprites].file = NULL;
                     ++argv, --argc;

                     if (create_sprite(sprites + nsprites++, &argc, &argv))
                     {
                        result = 1; /* still ok */
                        continue;
                     }

                     break; /* error */
                  }
               }

               /* Overflow, or OOM */
               fprintf(stderr, "simpleover: %s: sprite too large\n", argv[0]);
               break;
            }

            else
            {
               fprintf(stderr, "simpleover: %s: invalid sprite (%u,%u)\n",
                       argv[0],
                       sprites[nsprites].width, sprites[nsprites].height);
               break;
            }
         }

         else
         {
            fprintf(stderr, "simpleover: %s: too many sprites\n", argv[0]);
            break;
         }
      }

      else if (strncmp(argv[0], "--add=", 6) == 0)
      {
         const char *name = argv[0] + 6;
         int isprite = nsprites;

         ++argv, --argc;

         while (--isprite >= 0)
         {
            if (strcmp(sprites[isprite].name, name) == 0)
            {
               if (!add_sprite(output, out_buf, sprites + isprite,
                               &argc, &argv))
                  goto out; /* error in add_sprite */

               break;
            }
         }

         if (isprite < 0) /* sprite not found */
         {
            fprintf(stderr, "simpleover: --add='%s': sprite not found\n",
                    name);
            break;
         }
      }

      else
      {
         fprintf(stderr, "simpleover: %s: unrecognized operation\n", argv[0]);
         break;
      }

      result = 1; /* ok  */
   }

   /* Clean up the cache of sprites: */
out:
   while (--nsprites >= 0)
   {
      if (sprites[nsprites].buffer != NULL)
         free(sprites[nsprites].buffer);

      if (sprites[nsprites].file != NULL)
         (void)fclose(sprites[nsprites].file);
   }

   return result;
}

int
main(int argc, const char **argv)
{
   int result = 1; /* default to fail */

   if (argc >= 2)
   {
      int argi = 2;
      const char *output = NULL;
      png_image image;

      if (argc > 2 && argv[2][0] != '-' /*an operation*/)
      {
         output = argv[2];
         argi = 3;
      }

      image.version = PNG_IMAGE_VERSION;
      image.opaque = NULL;

      if (png_image_begin_read_from_file(&image, argv[1]))
      {
         png_bytep buffer;

         image.format = PNG_FORMAT_RGB; /* 24-bit RGB */

         buffer = malloc(PNG_IMAGE_SIZE(image));

         if (buffer != NULL)
         {
            png_color background = {0, 0xff, 0}; /* fully saturated green */

            if (png_image_finish_read(&image, &background, buffer,
                                      0 /*row_stride*/, NULL /*colormap*/))
            {
               /* At this point png_image_finish_read has cleaned up the
                * allocated data in png_image, and only the buffer needs to be
                * freed.
                *
                * Perform the remaining operations:
                */
               if (simpleover_process(&image, buffer,
                                      argc - argi, argv + argi))
               {
                  /* Write the output: */
                  if ((output != NULL &&
                       png_image_write_to_file(
                          &image, output, 0 /*convert_to_8bit*/, buffer,
                          0 /*row_stride*/, NULL /*colormap*/)) ||
                      (output == NULL &&
                       png_image_write_to_stdio(
                          &image, stdout, 0 /*convert_to_8bit*/, buffer,
                          0 /*row_stride*/, NULL /*colormap*/)))
                     result = 0;

                  else
                     fprintf(stderr, "simpleover: write %s: %s\n",
                             output == NULL ? "stdout" : output,
                             image.message);
               }

               /* else simpleover_process writes an error message */
            }

            else
               fprintf(stderr, "simpleover: read %s: %s\n", argv[1],
                       image.message);

            free(buffer);
         }

         else
         {
            fprintf(stderr, "simpleover: out of memory: %lu bytes\n",
                    (unsigned long)PNG_IMAGE_SIZE(image));

            /* This is the only place where a 'free' is required; libpng does
             * the cleanup on error and success, but in this case we couldn't
             * complete the read because of running out of memory.
             */
            png_image_free(&image);
         }
      }

      else
      {
         /* Failed to read the first argument: */
         fprintf(stderr, "simpleover: %s: %s\n", argv[1], image.message);
      }
   }

   else
   {
      /* Usage message */
      fprintf(
         stderr,
         "simpleover: usage: simpleover background.png [output.png]\n"
         "  Output 'background.png' as a 24-bit RGB PNG file in 'output.png'\n"
         "   or, if not given, stdout.  'background.png' will be composited\n"
         "   on fully saturated green.\n"
         "\n"
         "  Optionally, before output, process additional PNG files:\n"
         "\n"
         "   --sprite=width,height,name {[--at=x,y] {sprite.png}}\n"
         "    Produce a transparent sprite of size (width,height) and with\n"
         "     name 'name'.\n"
         "    For each sprite.png composite it is using a Porter-Duff 'Over'\n"
         "     operation at offset (x,y) in the sprite, defaulting to (0,0).\n"
         "     Input PNGs will be truncated to the area of the sprite.\n"
         "\n"
         "   --add='name' {x,y}\n"
         "    Optionally, before output, composite a sprite, 'name', which\n"
         "     must have been previously produced using --sprite at each\n"
         "     offset (x,y) in the output image.  Each sprite must fit\n"
         "     completely within the output image.\n"
         "\n"
         "  PNG files are processed in the order they occur on the command\n"
         "  line and thus the first PNG processed appears as the bottommost\n"
         "  in the output image.\n");
   }

   return result;
}
