/* babl - dynamically extendable universal pixel conversion library.
 * Copyright (C) 2005, Øyvind Kolås.
 *
 * Optimized 8bit conversion routines as used by legacy GIMP code.
 * Copyright (C) 2008  Sven Neumann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"
#include <stdio.h>

#include "babl-internal.h"

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


/* lookup tables used in conversion */

#define MAX_SPACES 32
static const Babl *spaces[MAX_SPACES]={NULL,};

static float lut_linear[1 << 8];
static float lut_gamma_2_2[MAX_SPACES][1 << 8];


static int
tables_init (const Babl *space)
{
  int i, j;

  for (j = 0; spaces[j]; j++)
  {
    if (spaces[j] == space)
      return j;
  }
  spaces[j] = space;

  /* fill tables for conversion from 8 bit integer to float */
  if (j == 0)
  for (i = 0; i < 1 << 8; i++)
    {
      double value = i / 255.0;
      lut_linear[i]    = value;
    }

  /* fill tables for conversion from 8 bit integer to float */
  for (i = 0; i < 1 << 8; i++)
    {
      double value = i / 255.0;
      lut_gamma_2_2[j][i] = babl_trc_to_linear (space->space.trc[0], value);
    }

  return j;
}

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

  while (n--)
    *d++ = lut_linear[*src++];
}


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

  while (n--)
    {
      float alpha = lut_linear[src[3]];
      d[0] = lut_linear[src[0]] * alpha;
      d[1] = lut_linear[src[1]] * alpha;
      d[2] = lut_linear[src[2]] * alpha;
      d[3] = alpha;
      src += 4;
      d += 4;
    }
}

static inline void
u8_gamma_2_2_to_float_linear (const Babl    *conversion,
                              unsigned char *src,
                              unsigned char *dst,
                              long           samples)
{
  int   space_no = tables_init (conversion->conversion.source->format.space);
  float *d = (float *) dst;
  long   n = samples;

  while (n--)
    *d++ = lut_gamma_2_2[space_no][*src++];
}

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

static void
conv_rgba8_linear_ragabaaF_linear (const Babl    *conversion,
                                   unsigned char *src,
                                   unsigned char *dst,
                                   long           samples)
{
  u8_linear_to_float_linear_assoc (conversion, src, dst, samples);
}


static void
conv_rgba8_gamma_2_2_rgbaF_linear (const Babl    *conversion,
                                   unsigned char *src,
                                   unsigned char *dst,
                                   long           samples)
{
  int   space_no = tables_init (conversion->conversion.source->format.space);
  float *d = (float *) dst;
  long   n = samples;

  while (n--)
    {
      *d++ = lut_gamma_2_2[space_no][*src++];
      *d++ = lut_gamma_2_2[space_no][*src++];
      *d++ = lut_gamma_2_2[space_no][*src++];
      *d++ = lut_linear[*src++];
    }
}

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

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

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

  while (n--)
    {
      *d++ = lut_linear[*src++];
      *d++ = lut_linear[*src++];
      *d++ = lut_linear[*src++];
      *d++ = 1.0;
    }
}

#define conv_rgb8_linear_ragabaaF_linear conv_rgb8_linear_rgbaF_linear

static void
conv_rgb8_gamma_2_2_rgbaF_linear (const Babl    *conversion,
                                  unsigned char *src,
                                  unsigned char *dst,
                                  long           samples)
{
  int   space_no = tables_init (conversion->conversion.source->format.space);
  float *d = (float *) dst;
  long   n = samples;

  while (n--)
    {
      *d++ = lut_gamma_2_2[space_no][*src++];
      *d++ = lut_gamma_2_2[space_no][*src++];
      *d++ = lut_gamma_2_2[space_no][*src++];
      *d++ = 1.0;
    }
}

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

static void
conv_ga8_gamma_2_2_gaF_linear (const Babl    *conversion,
                               unsigned char *src,
                               unsigned char *dst,
                               long           samples)
{
  int   space_no = tables_init (conversion->conversion.source->format.space);
  float *d = (float *) dst;
  long   n = samples;

  while (n--)
    {
      *d++ = lut_gamma_2_2[space_no][*src++];
      *d++ = lut_linear[*src++];
    }
}

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

  while (n--)
    {
      *dst++ = *src;
      *dst++ = *src;
      *dst++ = *src++;
      *dst++ = *src++;
    }
}

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

  while (n--)
    {
      float value = lut_linear[*src++];

      *d++ = value;
      *d++ = value;
      *d++ = value;
      *d++ = lut_linear[*src++];
    }
}

static void
conv_ga8_gamma_2_2_rgbaF_linear (const Babl    *conversion,
                                 unsigned char *src,
                                 unsigned char *dst,
                                 long           samples)
{
  int   space_no = tables_init (conversion->conversion.source->format.space);
  float *d = (float *) dst;
  long   n = samples;

  while (n--)
    {
      float value = lut_gamma_2_2[space_no][*src++];

      *d++ = value;
      *d++ = value;
      *d++ = value;
      *d++ = lut_linear[*src++];
    }
}

static void
conv_g8_linear_gF_linear (const Babl    *conversion,
                          unsigned char *src,
                          unsigned char *dst,
                          long           samples)
{
  u8_linear_to_float_linear (conversion, src, dst, samples);
}

static void
conv_g8_gamma_2_2_gF_linear (const Babl    *conversion,
                             unsigned char *src,
                             unsigned char *dst,
                             long           samples)
{
  u8_gamma_2_2_to_float_linear (conversion, src, dst, samples);
}

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

  while (n--)
    {
      float value = lut_linear[*src++];

      *d++ = value;
      *d++ = value;
      *d++ = value;
      *d++ = 1.0;
    }
}

static void
conv_g8_gamma_2_2_rgbaF_linear (const Babl    *conversion,
                                unsigned char *src,
                                unsigned char *dst,
                                long           samples)
{
  int   space_no = tables_init (conversion->conversion.source->format.space);
  float *d = (float *) dst;
  long   n = samples;

  while (n--)
    {
      float value = lut_gamma_2_2[space_no][*src++];

      *d++ = value;
      *d++ = value;
      *d++ = value;
      *d++ = 1.0;
    }
}

static void
conv_rgbaF_linear_rgb8_linear (const Babl    *conversion,
                               unsigned char *src,
                               unsigned char *dst,
                               long           samples)
{
  float *fsrc = (float *) src;
  long n = samples;
  long int v;

  while (n--)
    {
      v = *fsrc++ * 255.0f + 0.5f;
      *dst++ = (v < 0) ? 0 : ((v > 255) ? 255 : v);

      v = *fsrc++ * 255.0f + 0.5f;
      *dst++ = (v < 0) ? 0 : ((v > 255) ? 255 : v);

      v = *fsrc++ * 255.0f + 0.5f;
      *dst++ = (v < 0) ? 0 : ((v > 255) ? 255 : v);

      fsrc++;
    }
}

static void
conv_rgbaF_linear_rgba8_linear (const Babl    *conversion,
                                unsigned char *src,
                                unsigned char *dst,
                                long           samples)
{
  float *fsrc = (float *) src;
  long n = samples;
  long int v;

  while (n--)
    {
      v = *fsrc++ * 255.0f + 0.5f;
      *dst++ = (v < 0) ? 0 : ((v > 255) ? 255 : v);

      v = *fsrc++ * 255.0f + 0.5f;
      *dst++ = (v < 0) ? 0 : ((v > 255) ? 255 : v);

      v = *fsrc++ * 255.0f + 0.5f;
      *dst++ = (v < 0) ? 0 : ((v > 255) ? 255 : v);

      v = *fsrc++ * 255.0f + 0.5f;
      *dst++ = (v < 0) ? 0 : ((v > 255) ? 255 : v);
    }
}

int init (void);

int
init (void)
{
  const Babl *ragabaaF_linear = babl_format_new (
    babl_model ("RaGaBaA"),
    babl_type ("float"),
    babl_component ("Ra"),
    babl_component ("Ga"),
    babl_component ("Ba"),
    babl_component ("A"),
    NULL);
  const Babl *rgbaF_linear = babl_format_new (
    babl_model ("RGBA"),
    babl_type ("float"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    babl_component ("A"),
    NULL);
  const Babl *rgba8_linear = babl_format_new (
    babl_model ("RGBA"),
    babl_type ("u8"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    babl_component ("A"),
    NULL);
  const Babl *rgba8_gamma_2_2 = babl_format_new (
    babl_model ("R'G'B'A"),
    babl_type ("u8"),
    babl_component ("R'"),
    babl_component ("G'"),
    babl_component ("B'"),
    babl_component ("A"),
    NULL);
  const Babl *rgbF_linear = babl_format_new (
    babl_model ("RGB"),
    babl_type ("float"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    NULL);
  const Babl *rgb8_linear = babl_format_new (
    babl_model ("RGB"),
    babl_type ("u8"),
    babl_component ("R"),
    babl_component ("G"),
    babl_component ("B"),
    NULL);
  const Babl *rgb8_gamma_2_2 = babl_format_new (
    babl_model ("R'G'B'"),
    babl_type ("u8"),
    babl_component ("R'"),
    babl_component ("G'"),
    babl_component ("B'"),
    NULL);
  const Babl *gaF_linear = babl_format_new (
    babl_model ("YA"),
    babl_type ("float"),
    babl_component ("Y"),
    babl_component ("A"),
    NULL);
  const Babl *ga8_linear = babl_format_new (
    babl_model ("YA"),
    babl_type ("u8"),
    babl_component ("Y"),
    babl_component ("A"),
    NULL);
  const Babl *ga8_gamma_2_2 = babl_format_new (
    babl_model ("Y'A"),
    babl_type ("u8"),
    babl_component ("Y'"),
    babl_component ("A"),
    NULL);
  const Babl *gF_linear = babl_format_new (
    babl_model ("Y"),
    babl_type ("float"),
    babl_component ("Y"),
    NULL);
  const Babl *g8_linear = babl_format_new (
    babl_model ("Y"),
    babl_type ("u8"),
    babl_component ("Y"),
    NULL);
  const Babl *g8_gamma_2_2 = babl_format_new (
    babl_model ("Y'"),
    babl_type ("u8"),
    babl_component ("Y'"),
    NULL);

  tables_init (babl_space("sRGB"));

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

  o (rgba8_linear, ragabaaF_linear);
  o (rgba8_linear, rgbaF_linear);
  o (rgba8_gamma_2_2, rgbaF_linear);

  o (rgb8_linear, rgbF_linear);
  o (rgb8_gamma_2_2, rgbF_linear);
  o (rgb8_linear, rgbaF_linear);
  o (rgb8_linear, ragabaaF_linear);
  o (rgb8_gamma_2_2, rgbaF_linear);

  o (ga8_linear, gaF_linear);
  o (ga8_gamma_2_2, gaF_linear);
  o (ga8_linear, rgbaF_linear);
  o (ga8_gamma_2_2, rgbaF_linear);

  o (ga8_gamma_2_2, rgba8_gamma_2_2);

  o (g8_linear, gF_linear);
  o (g8_gamma_2_2, gF_linear);
  o (g8_linear, rgbaF_linear);
  o (g8_gamma_2_2, rgbaF_linear);

  o (rgbaF_linear, rgb8_linear);
  o (rgbaF_linear, rgba8_linear);

  return 0;
}
