/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * Gimp image compositing
 * Copyright (C) 2003  Helvetix Victorinox, a pseudonym, <helvetix@gimp.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <stdlib.h>

#include <glib-object.h>

#include "base/base-types.h"

#include "gimp-composite.h"

#include "gimp-composite-generic.h"

#ifdef ARCH_X86
#include "gimp-composite-mmx.h"
#include "gimp-composite-sse.h"
#endif


/*
 * Details about pixel formats, bits-per-pixel alpha and non alpha
 * versions of pixel formats.
 */
/*
 * Report on the number of bytes a particular pixel format consumes per pixel.
 */
const guchar gimp_composite_pixel_bpp[] =
  {
    1, /* GIMP_PIXELFORMAT_V8      */
    2, /* GIMP_PIXELFORMAT_VA8     */
    3, /* GIMP_PIXELFORMAT_RGB8    */
    4, /* GIMP_PIXELFORMAT_RGBA8   */
#if GIMP_COMPOSITE_16BIT
    2, /* GIMP_PIXELFORMAT_V16     */
    4, /* GIMP_PIXELFORMAT_VA16    */
    6, /* GIMP_PIXELFORMAT_RGB16   */
    8, /* GIMP_PIXELFORMAT_RGBA16  */
#endif
#if GIMP_COMPOSITE_32BIT
    2, /* GIMP_PIXELFORMAT_V32     */
    4, /* GIMP_PIXELFORMAT_VA32    */
    6, /* GIMP_PIXELFORMAT_RGB32   */
    8, /* GIMP_PIXELFORMAT_RGBA32  */
#endif
    0, /* GIMP_PIXELFORMAT_ANY */
  };

/*
 * Report true (non-zero) if a pixel format has alpha.
 */
const guchar gimp_composite_pixel_alphap[] =
  {
    0, /* GIMP_PIXELFORMAT_V8      */
    1, /* GIMP_PIXELFORMAT_VA8     */
    0, /* GIMP_PIXELFORMAT_RGB8    */
    1, /* GIMP_PIXELFORMAT_RGBA8   */
#if GIMP_COMPOSITE_16BIT
    0, /* GIMP_PIXELFORMAT_V16     */
    1, /* GIMP_PIXELFORMAT_VA16    */
    0, /* GIMP_PIXELFORMAT_RGB16   */
    1, /* GIMP_PIXELFORMAT_RGBA16  */
#endif
#if GIMP_COMPOSITE_32BIT
    0, /* GIMP_PIXELFORMAT_V32     */
    1, /* GIMP_PIXELFORMAT_VA32    */
    0, /* GIMP_PIXELFORMAT_RGB32   */
    1, /* GIMP_PIXELFORMAT_RGBA32  */
#endif
    0, /* GIMP_PIXELFORMAT_UNKNOWN */
  };

/*
 * Convert to/from pixel formats with/without alpha.
 */
const GimpPixelFormat gimp_composite_pixel_alpha[] =
  {
    GIMP_PIXELFORMAT_VA8,         /* GIMP_PIXELFORMAT_V8      */
    GIMP_PIXELFORMAT_V8,          /* GIMP_PIXELFORMAT_VA8     */
    GIMP_PIXELFORMAT_RGBA8,       /* GIMP_PIXELFORMAT_RGB8    */
    GIMP_PIXELFORMAT_RGB8,        /* GIMP_PIXELFORMAT_RGBA8   */
#if GIMP_COMPOSITE_16BIT
    GIMP_PIXELFORMAT_VA16,
    GIMP_PIXELFORMAT_V16,
    GIMP_PIXELFORMAT_RGBA16,
    GIMP_PIXELFORMAT_RGB16
#endif
#if GIMP_COMPOSITE_32BIT
    GIMP_PIXELFORMAT_VA32,
    GIMP_PIXELFORMAT_V32,
    GIMP_PIXELFORMAT_RGBA32,
    GIMP_PIXELFORMAT_RGB32
#endif
    GIMP_PIXELFORMAT_ANY,         /* GIMP_PIXELFORMAT_ANY */
  };


/*
 * XXX I don't like to put this here.  I think this information,
 * specific to the functions, ought to be with the function.
 */
struct GimpCompositeOperationEffects gimp_composite_operation_effects[] =
  {
    { TRUE,  TRUE,  FALSE, },     /*  GIMP_NORMAL_MODE        */
    { TRUE,  TRUE,  FALSE, },     /*  GIMP_DISSOLVE_MODE      */
    { TRUE,  TRUE,  FALSE, },     /*  GIMP_BEHIND_MODE        */
    { FALSE, FALSE, FALSE, },     /*  GIMP_MULTIPLY_MODE      */
    { FALSE, FALSE, FALSE, },     /*  GIMP_SCREEN_MODE        */
    { FALSE, FALSE, FALSE, },     /*  GIMP_OVERLAY_MODE       */
    { FALSE, FALSE, FALSE, },     /*  GIMP_DIFFERENCE_MODE    */
    { FALSE, FALSE, FALSE, },     /*  GIMP_ADDITION_MODE      */
    { FALSE, FALSE, FALSE, },     /*  GIMP_SUBTRACT_MODE      */
    { FALSE, FALSE, FALSE, },     /*  GIMP_DARKEN_ONLY_MODE   */
    { FALSE, FALSE, FALSE, },     /*  GIMP_LIGHTEN_ONLY_MODE  */
    { FALSE, FALSE, FALSE, },     /*  GIMP_HUE_MODE           */
    { FALSE, FALSE, FALSE, },     /*  GIMP_SATURATION_MODE    */
    { FALSE, FALSE, FALSE, },     /*  GIMP_COLOR_MODE         */
    { FALSE, FALSE, FALSE, },     /*  GIMP_VALUE_MODE         */
    { FALSE, FALSE, FALSE, },     /*  GIMP_DIVIDE_MODE        */
    { FALSE, FALSE, FALSE, },     /*  GIMP_DODGE_MODE         */
    { FALSE, FALSE, FALSE, },     /*  GIMP_BURN_MODE          */
    { FALSE, FALSE, FALSE, },     /*  GIMP_HARDLIGHT_MODE     */
    { FALSE, FALSE, FALSE, },     /*  GIMP_SOFTLIGHT_MODE     */
    { FALSE, FALSE, FALSE, },     /*  GIMP_GRAIN_EXTRACT_MODE */
    { FALSE, FALSE, FALSE, },     /*  GIMP_GRAIN_MERGE_MODE   */
    { TRUE,  FALSE, TRUE,  },     /*  GIMP_COLOR_ERASE_MODE   */
    { TRUE,  FALSE, TRUE,  },     /*  GIMP_ERASE_MODE         */
    { TRUE,  TRUE,  TRUE,  },     /*  GIMP_REPLACE_MODE       */
    { TRUE,  TRUE,  FALSE, },     /*  GIMP_ANTI_ERASE_MODE    */

    { FALSE, FALSE, FALSE },      /*  GIMP_SWAP */
    { FALSE, FALSE, FALSE },      /*  GIMP_SCALE */
    { FALSE, FALSE, FALSE },      /*  GIMP_CONVERT */
  };

struct GimpCompositeOptions gimp_composite_options =
  {
    0
  };

const gchar * gimp_composite_function_name[GIMP_COMPOSITE_N][GIMP_PIXELFORMAT_N][GIMP_PIXELFORMAT_N][GIMP_PIXELFORMAT_N];

void (* gimp_composite_function[GIMP_COMPOSITE_N][GIMP_PIXELFORMAT_N][GIMP_PIXELFORMAT_N][GIMP_PIXELFORMAT_N])(GimpCompositeContext *);

/**
 * gimp_composite_dispatch:
 * @ctx: The compositing context
 *
 * Given a compositing context, perform the compositing function
 * dictated by the compositing context operation.  There is no return
 * value, all results are in the compositing context.
 **/
void
gimp_composite_dispatch (GimpCompositeContext *ctx)
{
  void (* function) (GimpCompositeContext *);

  function = gimp_composite_function[ctx->op][ctx->pixelformat_A][ctx->pixelformat_B][ctx->pixelformat_D];

  if (function)
    {
      if (gimp_composite_options.bits & GIMP_COMPOSITE_OPTION_VERBOSE)
        {
          g_print ("%s %s %s %s = %p\n",
                   gimp_composite_mode_astext (ctx->op),
                   gimp_composite_pixelformat_astext (ctx->pixelformat_A),
                   gimp_composite_pixelformat_astext (ctx->pixelformat_B),
                   gimp_composite_pixelformat_astext (ctx->pixelformat_D),
                   function);
        }

      function (ctx);
    }
  else
    {
      g_print ("gimp_composite: unsupported operation: %s %s %s %s\n",
              gimp_composite_mode_astext (ctx->op),
              gimp_composite_pixelformat_astext (ctx->pixelformat_A),
              gimp_composite_pixelformat_astext (ctx->pixelformat_B),
              gimp_composite_pixelformat_astext (ctx->pixelformat_D));
    }
}

/**
 * gimp_composite_context_print:
 * @ctx: The context to print
 *
 * Print a human readable form of a GimpCompositeContext on stdout.
 **/
void
gimp_composite_context_print (GimpCompositeContext *ctx)
{
  g_print ("%p: op=%s\n  A=%s(%d):%p\n  B=%s(%d):%p\n  D=%s(%d):%p\n  M=%s(%d):%p\n  n_pixels=%lu\n",
           ctx,
           gimp_composite_mode_astext (ctx->op),
           gimp_composite_pixelformat_astext (ctx->pixelformat_A),
           ctx->pixelformat_A, ctx->A,
           gimp_composite_pixelformat_astext (ctx->pixelformat_B),
           ctx->pixelformat_B, ctx->B,
           gimp_composite_pixelformat_astext (ctx->pixelformat_D),
           ctx->pixelformat_D, ctx->D,
           gimp_composite_pixelformat_astext (ctx->pixelformat_M),
           ctx->pixelformat_M, ctx->M,
           ctx->n_pixels);
}

/**
 * gimp_composite_mode_astext:
 * @op:
 *
 * Given a GimpCompositeOperatin, return a string representation of the name
 * of that operation.
 *
 * Returns: gchar *, the name
 **/
const gchar *
gimp_composite_mode_astext (GimpCompositeOperation op)
{
  switch (op)
    {
    case GIMP_COMPOSITE_NORMAL:        return ("GIMP_COMPOSITE_NORMAL");
    case GIMP_COMPOSITE_DISSOLVE:      return ("GIMP_COMPOSITE_DISSOLVE");
    case GIMP_COMPOSITE_BEHIND:        return ("GIMP_COMPOSITE_BEHIND");
    case GIMP_COMPOSITE_MULTIPLY:      return ("GIMP_COMPOSITE_MULTIPLY");
    case GIMP_COMPOSITE_SCREEN:        return ("GIMP_COMPOSITE_SCREEN");
    case GIMP_COMPOSITE_OVERLAY:       return ("GIMP_COMPOSITE_OVERLAY");
    case GIMP_COMPOSITE_DIFFERENCE:    return ("GIMP_COMPOSITE_DIFFERENCE");
    case GIMP_COMPOSITE_ADDITION:      return ("GIMP_COMPOSITE_ADDITION");
    case GIMP_COMPOSITE_SUBTRACT:      return ("GIMP_COMPOSITE_SUBTRACT");
    case GIMP_COMPOSITE_DARKEN:        return ("GIMP_COMPOSITE_DARKEN");
    case GIMP_COMPOSITE_LIGHTEN:       return ("GIMP_COMPOSITE_LIGHTEN");
    case GIMP_COMPOSITE_HUE:           return ("GIMP_COMPOSITE_HUE");
    case GIMP_COMPOSITE_SATURATION:    return ("GIMP_COMPOSITE_SATURATION");
    case GIMP_COMPOSITE_COLOR_ONLY:    return ("GIMP_COMPOSITE_COLOR_ONLY");
    case GIMP_COMPOSITE_VALUE:         return ("GIMP_COMPOSITE_VALUE");
    case GIMP_COMPOSITE_DIVIDE:        return ("GIMP_COMPOSITE_DIVIDE");
    case GIMP_COMPOSITE_DODGE:         return ("GIMP_COMPOSITE_DODGE");
    case GIMP_COMPOSITE_BURN:          return ("GIMP_COMPOSITE_BURN");
    case GIMP_COMPOSITE_HARDLIGHT:     return ("GIMP_COMPOSITE_HARDLIGHT");
    case GIMP_COMPOSITE_SOFTLIGHT:     return ("GIMP_COMPOSITE_SOFTLIGHT");
    case GIMP_COMPOSITE_GRAIN_EXTRACT: return ("GIMP_COMPOSITE_GRAIN_EXTRACT");
    case GIMP_COMPOSITE_GRAIN_MERGE:   return ("GIMP_COMPOSITE_GRAIN_MERGE");
    case GIMP_COMPOSITE_COLOR_ERASE:   return ("GIMP_COMPOSITE_COLOR_ERASE");
    case GIMP_COMPOSITE_ERASE:         return ("GIMP_COMPOSITE_ERASE");
    case GIMP_COMPOSITE_REPLACE:       return ("GIMP_COMPOSITE_REPLACE");
    case GIMP_COMPOSITE_ANTI_ERASE:    return ("GIMP_COMPOSITE_ANTI_ERASE");
    case GIMP_COMPOSITE_BLEND:         return ("GIMP_COMPOSITE_BLEND");
    case GIMP_COMPOSITE_SHADE:         return ("GIMP_COMPOSITE_SHADE");
    case GIMP_COMPOSITE_SWAP:          return ("GIMP_COMPOSITE_SWAP");
    case GIMP_COMPOSITE_SCALE:         return ("GIMP_COMPOSITE_SCALE");
    case GIMP_COMPOSITE_CONVERT:       return ("GIMP_COMPOSITE_CONVERT");
    case GIMP_COMPOSITE_XOR:           return ("GIMP_COMPOSITE_XOR");
    default:
      break;
    }

  return ("bad mode");
}

/**
 * gimp_composite_pixelformat_astext:
 * @format:  The format.
 *
 * Given a GimpPixelFormat, return a string representation of the name
 * of that format.
 *
 * Returns: gchar *, the name
 **/
const gchar *
gimp_composite_pixelformat_astext (GimpPixelFormat format)
{
  switch (format) {
  case GIMP_PIXELFORMAT_V8:     return ("V8");
  case GIMP_PIXELFORMAT_VA8:    return ("VA8");
  case GIMP_PIXELFORMAT_RGB8:   return ("RGB8");
  case GIMP_PIXELFORMAT_RGBA8:  return ("RGBA8");
#if GIMP_COMPOSITE_16BIT
  case GIMP_PIXELFORMAT_V16:    return ("V16");
  case GIMP_PIXELFORMAT_VA16:   return ("VA16");
  case GIMP_PIXELFORMAT_RGB16:  return ("RGB16");
  case GIMP_PIXELFORMAT_RGBA16: return ("RGBA16");
#endif
#if GIMP_COMPOSITE_32BIT
  case GIMP_PIXELFORMAT_V32:    return ("V32");
  case GIMP_PIXELFORMAT_VA32:   return ("VA32");
  case GIMP_PIXELFORMAT_RGB32:  return ("RGB32");
  case GIMP_PIXELFORMAT_RGBA32: return ("RGBA32");
#endif
  case GIMP_PIXELFORMAT_ANY:    return ("ANY");
  default:
    break;
  }

  return ("bad format");
}


/**
 * gimp_composite_init:
 * @be_verbose:    whether to be verbose on stdout
 * @use_cpu_accel: whether to use accelerated routines like MMX
 *
 * Initialise the Gimp Compositing subsystem.  This includes checking
 * for user options and environment, installing the generic set of
 * compositing operation handlers, followed by overloading those which
 * are supported by the current cpu/hardware.
 **/
void
gimp_composite_init (gboolean  be_verbose,
                     gboolean  use_cpu_accel)
{
  const gchar *p;

  if ((p = g_getenv ("GIMP_COMPOSITE")))
    {
      gimp_composite_options.bits = strtoul(p, NULL, 16);
    }

  if (! use_cpu_accel)
    gimp_composite_options.bits |= GIMP_COMPOSITE_OPTION_NOEXTENSIONS;

  if (be_verbose)
    g_printerr ("gimp_composite: verbose=%s\n",
                (gimp_composite_options.bits & GIMP_COMPOSITE_OPTION_VERBOSE) ?
                "yes" : "no");

  gimp_composite_generic_install ();

  /*
   * Here is where you "glue" in the initialisation of your
   * optimisations.
   *
   * Declare the install() function external, and then call it.  A
   * return value of TRUE from the install function means the
   * installer was successful in instantiating itself.  For example,
   * it succeeded in hooking in the functions with the special
   * optimisation instructions, or hardware, or whatever.
   */
  if (! (gimp_composite_options.bits & GIMP_COMPOSITE_OPTION_NOEXTENSIONS))
    {
      extern gboolean gimp_composite_mmx_install (void);
      extern gboolean gimp_composite_sse_install (void);
      extern gboolean gimp_composite_sse2_install (void);
      extern gboolean gimp_composite_3dnow_install (void);
      extern gboolean gimp_composite_altivec_install (void);
      extern gboolean gimp_composite_vis_install (void);

      gboolean can_use_mmx     = gimp_composite_mmx_install ();
      gboolean can_use_sse     = gimp_composite_sse_install ();
      gboolean can_use_sse2    = gimp_composite_sse2_install ();
      gboolean can_use_3dnow   = gimp_composite_3dnow_install ();
      gboolean can_use_altivec = gimp_composite_altivec_install ();
      gboolean can_use_vis     = gimp_composite_vis_install ();

      if (be_verbose)
        g_printerr ("Processor instruction sets: "
                    "%cmmx %csse %csse2 %c3dnow %caltivec %cvis\n",
                    can_use_mmx     ? '+' : '-',
                    can_use_sse     ? '+' : '-',
                    can_use_sse2    ? '+' : '-',
                    can_use_3dnow   ? '+' : '-',
                    can_use_altivec ? '+' : '-',
                    can_use_vis     ? '+' : '-');
    }
}

gboolean
gimp_composite_use_cpu_accel (void)
{
  return ! (gimp_composite_options.bits & GIMP_COMPOSITE_OPTION_NOEXTENSIONS);
}
