/*
 * This file was part of gggl, it implements a variety of pixel conversion
 * functions that are usable with babl, the file needs more cleanup, and
 * doesn't return the number of samples processed as a long, like it's
 * supposed to.
 *
 *    GGGL 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.
 *
 *    GGGL 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 GGGL; if not, see <https://www.gnu.org/licenses/>.
 *
 *    Rights are granted to use this shared object in libraries covered by
 *    LGPL. (exception added, during import into babl CVS.)
 *
 *  Copyright 2003, 2004, 2005 Øyvind Kolås <pippin@gimp.org>
 */

//#define _POSIX_C_SOURCE 200112L

#include "config.h"
#include <math.h>
#include <string.h>

#include "babl.h"
#include "base/util.h"
#include "extensions/util.h"


/*
 * Implemented according to information read from:
 *
 * http://www.cinenet.net/~spitzak/conversion/sketches_0265.pdf
 *
 * initially ignoring any diffusion, to keep the implementation
 * smaller, and interchangeable with the non optimized version.
 *
 * due to ability to be able to relicence gggl under a different
 * licence than GPL, I avoided the temptation to look at the
 * source files in the same location, in case I was going to
 * need this piece of code for projects where GPL compatibility
 * was a must.
 *
 * TODO: error diffusion,
 *       gamma correction  (not really,. gamma correction belongs in seperate ops,.
 */

/* lookup tables used in conversion */

static float          table_8_F[1 << 8];
static float          table_16_F[1 << 16];
static unsigned char  table_F_8[1 << 16];
static unsigned short table_F_16[1 << 16];


static int table_inited = 0;

static void
table_init (void)
{
  int i;

  if (table_inited)
    return;
  table_inited = 1;

  /* fill tables for conversion from integer to float */
  for (i = 0; i < 1 << 8; i++)
    {
      table_8_F[i] = (i * 1.0) / 255.0;
    }
  for (i = 0; i < 1 << 16; i++)
    {
      table_16_F[i] = (i * 1.0) / 65535.0;
    }
  /* fill tables for conversion from float to integer */
  {
    union
    {
      float          f;
      unsigned short s[2];
    } u;
    u.f = 0.0;

    u.s[0] = 0x8000;

    for (i = 0; i < 1 << 16; i++)
      {
        unsigned char  c;
        unsigned short s;

        u.s[1] = i;

        if (u.f <= 0.0)
          {
            c = 0;
            s = 0;
          }
        else if (u.f >= 1.0)
          {
            c = 255;
            s = 65535;
          }
        else
          {
            c = u.f * 255 + 0.5f;
            s = u.f * 65535 + 0.5f;
          }

        /*fprintf (stderr, "%2.3f=%03i %05i ", f, c, (*hi));
           / if (! ((*hi)%9))
           /         fprintf (stderr, "\n"); */

        table_F_8[u.s[1]]  = c;
        table_F_16[u.s[1]] = s;
      }
  }
  /* fix tables to ensure 1:1 conversions back and forth */
  if (0)
    {                           /*FIXME: probably not the right way to do it,.. must sit down and scribble on paper */
      int i;
      for (i = 0; i < 256; i++)
        {
          float           f  = table_8_F[i];
          unsigned short *hi = ((unsigned short *) (void *) &f);
          unsigned short *lo = ((unsigned short *) (void *) &f);
          *lo              = 0;
          table_F_8[(*hi)] = i;
        }
    }
}

/* function to find the index in table for a float */
static unsigned int
gggl_float_to_index16 (float f)
{
  union
  {
    float          f;
    unsigned short s[2];
  } u;
  u.f = f;
  return u.s[1];
}


static void
conv_F_8 (const Babl    *conversion,
          unsigned char *src, 
          unsigned char *dst, 
          long           samples)
{
  long n = samples;

  if (!table_inited)
    table_init ();
  while (n--)
    {
      register float f = (*(float *) src);
      *(unsigned char *) dst = table_F_8[gggl_float_to_index16 (f)];
      dst                   += 1;
      src                   += 4;
    }
}

static void
conv_F_16 (const Babl    *conversion,
          unsigned char *src, 
          unsigned char *dst, 
          long           samples)
{
  long n = samples;

  if (!table_inited)
    table_init ();
  while (n--)
    {
      register float f = (*(float *) src);
      *(unsigned short *) dst = table_F_16[gggl_float_to_index16 (f)];
      dst                    += 2;
      src                    += 4;
    }
}

static void
conv_8_F (const Babl    *conversion,
          unsigned char *src, 
          unsigned char *dst, 
          long           samples)
{
  long n = samples;

  if (!table_inited)
    table_init ();
  while (n--)
    {
      (*(float *) dst) = table_8_F[*(unsigned char *) src];
      dst             += 4;
      src             += 1;
    }
}

static void
conv_16_F (const Babl    *conversion,
          unsigned char *src, 
          unsigned char *dst, 
          long           samples)
{
  long n = samples;

  if (!table_inited)
    table_init ();
  while (n--)
    {
      (*(float *) dst) = table_16_F[*(unsigned short *) src];
      dst             += 4;
      src             += 2;
    }
}

/*********/
static void
conv_rgbaF_rgba8 (const Babl    *conversion,
                  unsigned char *src, 
                  unsigned char *dst, 
                  long           samples)
{
  conv_F_8 (conversion, src, dst, samples * 4);
}

#define conv_rgbaF_rgbP8    conv_rgbaF_rgba8

static void
conv_rgbF_rgb8 (const Babl    *conversion,
                unsigned char *src, 
                unsigned char *dst, 
                long           samples)
{
  conv_F_8 (conversion, src, dst, samples * 3);
}

static void
conv_gaF_ga8 (const Babl    *conversion,
              unsigned char *src, 
              unsigned char *dst, 
              long           samples)
{
  conv_F_8 (conversion, src, dst, samples * 2);
}

#define conv_rgbAF_rgbA8    conv_rgbaF_rgba8
#define conv_gF_g8          conv_F_8
#define conv_gAF_gA8        conv_gaF_ga8

static void
conv_rgbaF_rgba16 (const Babl    *conversion,
                   unsigned char *src, 
                   unsigned char *dst, 
                   long           samples)
{
  conv_F_16 (conversion, src, dst, samples * 4);
}

static void
conv_rgbF_rgb16 (const Babl    *conversion,
                 unsigned char *src, 
                 unsigned char *dst, 
                 long           samples)
{
  conv_F_16 (conversion, src, dst, samples * 3);
}

static void
conv_gaF_ga16 (const Babl    *conversion,
               unsigned char *src, 
               unsigned char *dst, 
               long           samples)
{
  conv_F_16 (conversion, src, dst, samples * 2);
}

#define conv_rgbAF_rgbA16    conv_rgbaF_rgba16
#define conv_gF_g16          conv_F_16
#define conv_gAF_gA16        conv_gaF_ga16

static void
conv_rgba8_rgbaF (const Babl    *conversion,
                  unsigned char *src, 
                  unsigned char *dst, 
                  long           samples)
{
  conv_8_F (conversion, src, dst, samples * 4);
}

static void
conv_rgb8_rgbF (const Babl    *conversion,
                unsigned char *src, 
                unsigned char *dst, 
                long           samples)
{
  conv_8_F (conversion, src, dst, samples * 3);
}

static void
conv_ga8_gaF (const Babl    *conversion,
              unsigned char *src, 
              unsigned char *dst, 
              long           samples)
{
  conv_8_F (conversion, src, dst, samples * 2);
}

#define conv_rgbA8_rgbAF    conv_rgba8_rgbaF
#define conv_gA8_gAF        conv_ga8_gaF
#define conv_g8_gF          conv_8_F

static void
conv_rgba16_rgbaF (const Babl    *conversion,
                   unsigned char *src, 
                   unsigned char *dst, 
                   long           samples)
{
  conv_16_F (conversion, src, dst, samples * 4);
}

static void
conv_rgb16_rgbF (const Babl    *conversion,
                 unsigned char *src, 
                 unsigned char *dst, 
                 long           samples)
{
  conv_16_F (conversion, src, dst, samples * 3);
}

static void
conv_ga16_gaF (const Babl    *conversion,
               unsigned char *src, 
               unsigned char *dst, 
               long           samples)
{
  conv_16_F (conversion, src, dst, samples * 2);
}

#define conv_rgbA16_rgbAF    conv_rgba16_rgbaF
#define conv_gA16_gAF        conv_ga16_gaF
#define conv_g16_gF          conv_16_F

static void
conv_rgbafloat_linear_cairo32_le (const Babl    *conversion,
                                  unsigned char *src_char,
                                  unsigned char *dst,
                                  long           samples)
{
  long   n    = samples;
  float *src  = (float*)src_char;

  while (n--)
    {
      float alpha = src[3] * 255;

      if (alpha <= BABL_ALPHA_FLOOR)
        {
          *(int *)dst = 0;
        }
      else
        {
          if (alpha > 255) alpha = 255;
#define div_255(a) ((((a)+128)+(((a)+128)>>8))>>8)
          dst[0] = src[2] * alpha + 0.5f;
          dst[1] = src[1] * alpha + 0.5f;
          dst[2] = src[0] * alpha + 0.5f;
          dst[3] = alpha + 0.5f;
        }
      src += 4;
      dst += 4;
    }
}


int init (void);

int
init (void)
{
  const Babl *rgbaF = babl_format_new (
    babl_model ("RGBA"),
    babl_type ("float"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    babl_component ("A"),
    NULL);
  const Babl *rgba16 = babl_format_new (
    babl_model ("RGBA"),
    babl_type ("u16"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    babl_component ("A"),
    NULL);
  const Babl *rgba8 = babl_format_new (
    babl_model ("RGBA"),
    babl_type ("u8"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    babl_component ("A"),
    NULL);
  const Babl *rgbAF = babl_format_new (
    babl_model ("RaGaBaA"),
    babl_type ("float"),
    babl_component ("Ra"),
    babl_component ("Ga"),
    babl_component ("Ba"),
    babl_component ("A"),
    NULL);
  const Babl *rgbA16 = babl_format_new (
    babl_model ("RaGaBaA"),
    babl_type ("u16"),
    babl_component ("Ra"),
    babl_component ("Ga"),
    babl_component ("Ba"),
    babl_component ("A"),
    NULL);
  const Babl *rgbA8 = babl_format_new (
    babl_model ("RaGaBaA"),
    babl_type ("u8"),
    babl_component ("Ra"),
    babl_component ("Ga"),
    babl_component ("Ba"),
    babl_component ("A"),
    NULL);
  const Babl *rgbF = babl_format_new (
    babl_model ("RGB"),
    babl_type ("float"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    NULL);
  const Babl *rgb16 = babl_format_new (
    babl_model ("RGB"),
    babl_type ("u16"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    NULL);
  const Babl *rgb8 = babl_format_new (
    babl_model ("RGB"),
    babl_type ("u8"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    NULL);
  const Babl *gaF = babl_format_new (
    babl_model ("YA"),
    babl_type ("float"),
    babl_component ("Y"),
    babl_component ("A"),
    NULL);
  const Babl *gAF = babl_format_new (
    babl_model ("YaA"),
    babl_type ("float"),
    babl_component ("Ya"),
    babl_component ("A"),
    NULL);
  const Babl *gF = babl_format_new (
    babl_model ("Y"),
    babl_type ("float"),
    babl_component ("Y"),
    NULL);
  const Babl *ga16 = babl_format_new (
    babl_model ("YA"),
    babl_type ("u16"),
    babl_component ("Y"),
    babl_component ("A"),
    NULL);
  const Babl *gA16 = babl_format_new (
    babl_model ("YaA"),
    babl_type ("u16"),
    babl_component ("Ya"),
    babl_component ("A"),
    NULL);
  const Babl *g16 = babl_format_new (
    babl_model ("Y"),
    babl_type ("u16"),
    babl_component ("Y"),
    NULL);
  const Babl *ga8 = babl_format_new (
    babl_model ("YA"),
    babl_type ("u8"),
    babl_component ("Y"),
    babl_component ("A"),
    NULL);
  const Babl *gA8 = babl_format_new (
    babl_model ("YaA"),
    babl_type ("u8"),
    babl_component ("Ya"),
    babl_component ("A"),
    NULL);
  const Babl *g8 = babl_format_new (
    babl_model ("Y"),
    babl_type ("u8"),
    babl_component ("Y"),
    NULL);

  int   testint  = 23;
  char *testchar = (char*) &testint;
  int   littleendian = (testchar[0] == 23);

  if (littleendian)
    {
      const Babl *f32 = babl_format_new (
        "name", "cairo-ARGB32",
        babl_model ("R'aG'aB'aA"),
        babl_type ("u8"),
        babl_component ("B'a"),
        babl_component ("G'a"),
        babl_component ("R'a"),
        babl_component ("A"),
        NULL
      );
      babl_conversion_new (babl_format ("RGBA float"), f32, "linear",
                           conv_rgbafloat_linear_cairo32_le, NULL);
  }

#define o(src, dst) \
  babl_conversion_new (src, dst, "linear", conv_ ## src ## _ ## dst, NULL)

  o (rgbaF, rgba8);
  o (rgba8, rgbaF);
  o (rgbaF, rgba16);
  o (rgba16, rgbaF);
  o (rgbAF, rgbA8);
  o (rgbA8, rgbAF);
  o (rgbAF, rgbA16);
  o (rgbA16, rgbAF);
  o (rgbF, rgb8);
  o (rgb8, rgbF);
  o (rgbF, rgb16);
  o (rgb16, rgbF);
  o (gaF, ga8);
  o (gAF, gA8);
  o (gF, g8);
  o (ga8, gaF);
  o (gA8, gAF);
  o (g8, gF);
  o (gaF, ga16);
  o (gAF, gA16);
  o (gF, g16);
  o (ga16, gaF);
  o (gA16, gAF);
  o (g16, gF);

  if (!table_inited)
    table_init ();

  return 0;
}
