/* babl - dynamically extendable universal pixel conversion library.
 * Copyright (C) 2005, 2014, 2019 Øyvind Kolås.
 * Copyright (C) 2009, Martin Nordholts
 * Copyright (C) 2014, 2019 Elle Stone
 * Copyright (C) 2017, 2018 Red Hat, Inc.
 *
 * 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 <math.h>
#include <stdint.h>
#include <string.h>

#if defined(USE_SSE2)
#include <emmintrin.h>
#endif /* defined(USE_SSE2) */

#include "babl-internal.h"
#include "extensions/util.h"

#define DEGREES_PER_RADIAN (180 / 3.14159265358979323846)
#define RADIANS_PER_DEGREE (1 / DEGREES_PER_RADIAN)
#define DEGREES_PER_RADIANf (180 / 3.14159265358979323846f)
#define RADIANS_PER_DEGREEf (1 / DEGREES_PER_RADIANf)

#define LAB_EPSILON       (216.0 / 24389.0)
#define LAB_EPSILONf      (216.0f / 24389.0f)
#define LAB_KAPPA         (24389.0 / 27.0)
#define LAB_KAPPAf        (24389.0f / 27.0f)

/* The constants below hard-code the D50-adapted sRGB ICC profile
 * reference white, aka the ICC profile D50 illuminant.
 *
 * In a properly ICC profile color-managed application, the profile
 * illuminant values should be retrieved from the image's
 * ICC profile's illuminant.
 *
 * At present, the ICC profile illuminant is always D50. This might
 * change when the next version of the ICC specs is released.
 *
 * As encoded in an actual V2 or V4 ICC profile,
 * the illuminant values are hexadecimal-rounded, as are the following
 * hard-coded D50 ICC profile illuminant values:
 */

#define D50_WHITE_REF_X   0.964202880
#define D50_WHITE_REF_Y   1.000000000
#define D50_WHITE_REF_Z   0.824905400

#define D50_WHITE_REF_Xf  0.964202880f
#define D50_WHITE_REF_Yf  1.000000000f
#define D50_WHITE_REF_Zf  0.824905400f

#define NEAR_ZERO         0.0000000001 
#define NEAR_ZEROf        0.0000000001f
#define near_zero(a)   ((a) < NEAR_ZERO && (a) > -NEAR_ZERO)
#define near_zerof(a)  ((a) < NEAR_ZEROf && (a) > -NEAR_ZEROf)

#define D50_WHITE_REF_x   0.345702921222f
#define D50_WHITE_REF_y   0.358537532290f


static void types (void);
static void components (void);
static void models (void);
static void conversions (void);
static void formats (void);

int init (void);
#include "babl-verify-cpu.inc"

int
init (void)
{
  BABL_VERIFY_CPU();
  types ();
  components ();
  models ();
  formats ();
  conversions ();
  return 0;
}

static void
components (void)
{
  babl_component_new ("CIE L", "doc", "Luminance, range 0.0-100.0 in float", NULL);
  babl_component_new ("CIE a", "chroma", "doc", "chroma component 0.0 is no saturation", NULL);
  babl_component_new ("CIE b", "chroma", "doc", "chroma component 0.0 is no saturation", NULL);
  babl_component_new ("CIE C(ab)", "chroma", "doc", "chrominance/saturation", NULL);
  babl_component_new ("CIE H(ab)", "chroma", "doc", "hue value range 0.0-360.0", NULL);
  babl_component_new ("CIE X", NULL);
  babl_component_new ("CIE Y", NULL);
  babl_component_new ("CIE Z", NULL);
  babl_component_new ("CIE x", NULL);
  babl_component_new ("CIE y", NULL);
  /* CIE 1976 UCS */
  babl_component_new ("CIE u", NULL);
  babl_component_new ("CIE v", NULL);
/*  babl_component_new ("CIE z", NULL);*/
}

static void
models (void)
{
  babl_model_new (
    "name", "CIE Lab",
    "doc", "CIE Lab color model, a perceptually uniform space, euclidian distance in this space represents delta E.",
    babl_component ("CIE L"),
    babl_component ("CIE a"),
    babl_component ("CIE b"),
    "CIE",
    NULL);

  babl_model_new (
    "name", "CIE Lab alpha",
    "doc", "CIE Lab color model, with separate alpha",
    babl_component ("CIE L"),
    babl_component ("CIE a"),
    babl_component ("CIE b"),
    babl_component ("A"),
    "CIE",
    "alpha",
    NULL);

  babl_model_new (
    "name", "CIE LCH(ab)",
    "doc", "CIE LCH color model, using cylindrical coordinates",
    babl_component ("CIE L"),
    babl_component ("CIE C(ab)"),
    babl_component ("CIE H(ab)"),
    "CIE",
    NULL);

  babl_model_new (
    "name", "CIE LCH(ab) alpha",
    "doc", "CIE LCH color model, using cylindrical coordinates, with separate alpha",
    babl_component ("CIE L"),
    babl_component ("CIE C(ab)"),
    babl_component ("CIE H(ab)"),
    babl_component ("A"),
    "CIE",
    "alpha",
    NULL);

  babl_model_new (
    "name", "CIE XYZ",
    babl_component ("CIE X"),
    babl_component ("CIE Y"),
    babl_component ("CIE Z"),
    "CIE",
    NULL);

  babl_model_new (
    "name", "CIE XYZ alpha",
    babl_component ("CIE X"),
    babl_component ("CIE Y"),
    babl_component ("CIE Z"),
    babl_component ("A"),
    "CIE",
    "alpha",
    NULL);

  babl_model_new (
    "name", "CIE xyY",
    "doc", "the coordinate system often used for drawing chromaticity diagrams. Y is luminance.",
    babl_component ("CIE x"),
    babl_component ("CIE y"),
    babl_component ("CIE Y"),
    "CIE",
    NULL);

  babl_model_new (
    "name", "CIE xyY alpha",
    "doc", "the coordinate system often used for drawing chromaticity diagrams. Y is luminance, with separate alpha",
    babl_component ("CIE x"),
    babl_component ("CIE y"),
    babl_component ("CIE Y"),
    babl_component ("A"),
    "CIE",
    "alpha",
    NULL);

/* CIE 1976 UCS */
  babl_model_new (
    "name", "CIE Yuv",
    "doc", "A newer more perceptually uniform space than xyY for chromaticity diagrams.",
    babl_component ("CIE Y"),
    babl_component ("CIE u"),
    babl_component ("CIE v"),
    NULL);

  babl_model_new (
    "name", "CIE Yuv alpha",
    "doc", "A newer more perceptually uniform space than xyY for chromaticity diagrams, with separate alpha.",
    babl_component ("CIE Y"),
    babl_component ("CIE u"),
    babl_component ("CIE v"),
    babl_component ("A"),
    NULL);
}

static void  rgbcie_init (void);

/******** begin double RGB/CIE color space conversions ****************/

static inline void  ab_to_CHab    (double  a,
                                   double  b,
                                   double *to_C,
                                   double *to_H);

static inline void  CHab_to_ab    (double  C,
                                   double  H,
                                   double *to_a,
                                   double *to_b);

static inline void XYZ_to_LAB     (double  X,
                                   double  Y,
                                   double  Z,
                                   double *to_L,
                                   double *to_a,
                                   double *to_b
                                   );

static inline void LAB_to_XYZ     (double  L,
                                   double  a,
                                   double  b,
                                   double *to_X,
                                   double *to_Y,
                                   double *to_Z
                                   );

static inline void XYZ_to_xyY     (double  X,
                                   double  Y,
                                   double  Z,
                                   double *to_x,
                                   double *to_y,
                                   double *to_Y
                                   );

static inline void xyY_to_XYZ     (double  x,
                                   double  y,
                                   double  Y,
                                   double *to_X,
                                   double *to_Y,
                                   double *to_Z
                                   );

/* CIE 1976 UCS */
static inline void XYZ_to_Yuv     (double X,
                                   double Y,
                                   double Z,
                                   double *to_Y,
                                   double *to_u,
                                   double *to_v
                                   );

static inline void Yuv_to_XYZ     (double Y,
                                   double u,
                                   double v,
                                   double *to_X,
                                   double *to_Y,
                                   double *to_Z
                                   );

static inline void
XYZ_to_LAB (double  X,
            double  Y,
            double  Z,
            double *to_L,
            double *to_a,
            double *to_b)
{
  double xr = X / D50_WHITE_REF_X;
  double yr = Y / D50_WHITE_REF_Y;
  double zr = Z / D50_WHITE_REF_Z;

  double fx = xr > LAB_EPSILON ? cbrt (xr) : (LAB_KAPPA * xr + 16.0) / 116.0;
  double fy = yr > LAB_EPSILON ? cbrt (yr) : (LAB_KAPPA * yr + 16.0) / 116.0;
  double fz = zr > LAB_EPSILON ? cbrt (zr) : (LAB_KAPPA * zr + 16.0) / 116.0;

  *to_L = 116.0 * fy - 16.0;
  *to_a = 500.0 * (fx - fy);
  *to_b = 200.0 * (fy - fz);
}

static inline void
LAB_to_XYZ (double  L,
            double  a,
            double  b,
            double *to_X,
            double *to_Y,
            double *to_Z)
{
  double fy = (L + 16.0) / 116.0;
  double fy_cubed = fy * fy * fy;

  double fx = fy + a / 500.0;
  double fx_cubed = fx * fx * fx;

  double fz = fy - b / 200.0;
  double fz_cubed = fz * fz * fz;

  double yr = L > LAB_KAPPA * LAB_EPSILON ? fy_cubed : L / LAB_KAPPA;
  double xr = fx_cubed > LAB_EPSILON ? fx_cubed : (fx * 116.0 - 16.0) / LAB_KAPPA;
  double zr = fz_cubed > LAB_EPSILON ? fz_cubed : (fz * 116.0 - 16.0) / LAB_KAPPA;

  *to_X = xr * D50_WHITE_REF_X;
  *to_Y = yr * D50_WHITE_REF_Y;
  *to_Z = zr * D50_WHITE_REF_Z;
}

/* CIE xyY */
static inline void
XYZ_to_xyY (double  X,
            double  Y,
            double  Z,
            double *to_x,
            double *to_y,
            double *to_Y)
{
   double sum = X + Y + Z;
   if (near_zero (sum))
	{ *to_Y = 0.0;
	  *to_x = D50_WHITE_REF_x;
	  *to_y = D50_WHITE_REF_y;
	}
	else
	{
	*to_x = X / sum;
	*to_y = Y / sum;
	*to_Y = Y;
    }
}

static inline void
xyY_to_XYZ (double  x,
            double  y,
            double  Y,
            double *to_X,
            double *to_Y,
            double *to_Z)
{
  if (near_zero (Y))
    {
	  *to_X = 0.0;
	  *to_Y = 0.0;
	  *to_Z = 0.0;
	}
  else
	{
	*to_X = (x * Y) / y;
	*to_Y = Y;
	*to_Z = ((1 - x - y) * Y) / y;
    }
}


/* CIE 1976 UCS */

/* Code for near-zero XYZ and RGB for CIE 1976 UCS patterned
 * after code written by and used with kind permission of Graeme Gill.
 * Graeme Gill's code is in "icc.c"
 * downloadable from http://argyllcms.com/ */

static inline void
XYZ_to_Yuv (double X,
            double Y,
            double Z,
            double *to_Y,
            double *to_u,
            double *to_v)
{
  double sum = X + (15.0 * Y) + (3.0 * Z);
  if (near_zero (sum))
    {
        *to_Y = 0.0;
        *to_u = 4.0/19.0;
        *to_v = 9.0/19.0;
    }
        else
    {
        *to_Y = Y;
        *to_u = (4.0 * X) / sum;
        *to_v = (9.0 * Y) / sum;
    }
}

static inline void
Yuv_to_XYZ (double Y,
            double u,
            double v,
            double *to_X,
            double *to_Y,
            double *to_Z)
{
  if (near_zero (v))
    {
      *to_X = 0.0;
      *to_Y = 0.0;
      *to_Z = 0.0;
    }
  else
    {
      *to_X = ((9.0 * u * Y)/(4.0 * v));
      *to_Y = Y;
      *to_Z = -(((20.0 * v + 3.0 * u - 12.0) * Y)/(4.0 * v));
    }
}

/* rgb <-> XYZ */

static void
rgba_to_xyz (const Babl *conversion,
             char       *src,
             char       *dst,
             long        n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      double RGB[3]  = {((double *) src)[0],
                        ((double *) src)[1],
                        ((double *) src)[2] };
      babl_space_to_xyz (space, RGB, (double*)dst);

      src += sizeof (double) * 4;
      dst += sizeof (double) * 3;
    }
}

static void
xyz_to_rgba (const Babl *conversion,
             char       *src,
             char       *dst,
             long        n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      babl_space_from_xyz (space, (double*)src, (double*) dst);
      ((double *) dst)[3] = 1.0;
      src += sizeof (double) * 3;
      dst += sizeof (double) * 4;
    }
}

static void
rgba_to_xyza (const Babl *conversion,char *src,
              char *dst,
              long  n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      babl_space_to_xyz (space, (double*)src, (double*)dst);
      ((double *) dst)[3] = ((double *) src)[3];

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}

static void
xyza_to_rgba (const Babl *conversion,char *src,
              char *dst,
              long  n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      babl_space_from_xyz (space, (double*)src, (double*) dst);
      ((double *) dst)[3] = ((double *) src)[3];

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}


/* rgb -> xyY */
static void
rgba_to_xyY (const Babl *conversion,
             char       *src,
             char       *dst,
             long        n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      double XYZ[3], x, y, Y;

      babl_space_to_xyz (space, (double*)src, XYZ);
      XYZ_to_xyY (XYZ[0], XYZ[1], XYZ[2], &x, &y, &Y);

      ((double *) dst)[0] = x;
      ((double *) dst)[1] = y;
      ((double *) dst)[2] = Y;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 3;
    }
}

static void
rgba_to_xyYa (const Babl *conversion,char *src,
              char *dst,
              long  n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      double alpha = ((double *) src)[3];
      double XYZ[3], x, y, Y;

      //convert RGB to XYZ
      babl_space_to_xyz (space, (double*)src, XYZ);

      //convert XYZ to xyY
      XYZ_to_xyY (XYZ[0], XYZ[1], XYZ[2], &x, &y, &Y);

      ((double *) dst)[0] = x;
      ((double *) dst)[1] = y;
      ((double *) dst)[2] = Y;
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}



/* rgb -> Yuv */
static void
rgba_to_Yuv (const Babl *conversion,char *src,
             char *dst,
             long  n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      double XYZ[3], Y, u, v;

      babl_space_to_xyz (space, (double*)src, XYZ);
      XYZ_to_Yuv (XYZ[0], XYZ[1], XYZ[2], &Y, &u, &v);

      ((double *) dst)[0] = Y;
      ((double *) dst)[1] = u;
      ((double *) dst)[2] = v;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 3;
    }
}

static void
rgba_to_Yuva (const Babl *conversion,char *src,
              char *dst,
              long  n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      double alpha = ((double *) src)[3];
      double XYZ[3], Y, u, v;

      //convert RGB to XYZ
      babl_space_to_xyz (space, (double*)src, XYZ);

      //convert XYZ to Yuv
      XYZ_to_Yuv (XYZ[0], XYZ[1], XYZ[2], &Y, &u, &v);

      ((double *) dst)[0] = Y;
      ((double *) dst)[1] = u;
      ((double *) dst)[2] = v;
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}


/* rgbf <->xyYf */
static void
rgbaf_to_xyYaf (const Babl *conversion,
                float *src,
                float *dst,
                long   samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float x, y, X, Y, Z, r, g, b, a;
      r = src[0];
      g = src[1];
      b = src[2];
      a = src[3];

      if (near_zerof(r) && near_zerof(g) && near_zerof(b))
        {
          Y = 0.0f;
          x = D50_WHITE_REF_x;
          y = D50_WHITE_REF_y;
        }
      else
        {
          X = m_0_0 * r + m_0_1 * g + m_0_2 * b;
          Y = m_1_0 * r + m_1_1 * g + m_1_2 * b;
          Z = m_2_0 * r + m_2_1 * g + m_2_2 * b;

          x = X / (X + Y + Z);
          y = Y / (X + Y + Z);
        }

      dst[0] = x;
      dst[1] = y;
      dst[2] = Y;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}

static void
rgbf_to_xyYf (const Babl *conversion,float *src,
              float *dst,
              long   samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
  float x, y, X, Y, Z, r, g, b;
      r = src[0];
      g = src[1];
      b = src[2];

      if (near_zerof(r) && near_zerof(g) && near_zerof(b))
        {
          Y = 0.0f;
          x = D50_WHITE_REF_x;
          y = D50_WHITE_REF_y;
        }
      else
        {
          X = m_0_0 * r + m_0_1 * g + m_0_2 * b;
          Y = m_1_0 * r + m_1_1 * g + m_1_2 * b;
          Z = m_2_0 * r + m_2_1 * g + m_2_2 * b;

          x = X / (X + Y + Z);
          y = Y / (X + Y + Z);
        }

      dst[0] = x;
      dst[1] = y;
      dst[2] = Y;

      src += 3;
      dst += 3;
    }
}


static void
rgbaf_to_xyYf (const Babl *conversion,
               float      *src,
               float      *dst,
               long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
  float x, y, X, Y, Z, r, g, b;
      r = src[0];
      g = src[1];
      b = src[2];

      if (near_zerof(r) && near_zerof(g) && near_zerof(b))
        {
          Y = 0.0f;
          x = D50_WHITE_REF_x;
          y = D50_WHITE_REF_y;
        }
      else
        {
          X = m_0_0 * r + m_0_1 * g + m_0_2 * b;
          Y = m_1_0 * r + m_1_1 * g + m_1_2 * b;
          Z = m_2_0 * r + m_2_1 * g + m_2_2 * b;

          x = X / (X + Y + Z);
          y = Y / (X + Y + Z);
        }

      dst[0] = x;
      dst[1] = y;
      dst[2] = Y;

      src += 4;
      dst += 3;
    }
}



/* rgbf -> Yuv */
static void
rgbaf_to_Yuvaf (const Babl *conversion,
                float *src,
                float *dst,
                long   samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float u, v, X, Y, Z, r, g, b, a, sum;
      r = src[0];
      g = src[1];
      b = src[2];
      a = src[3];

      if (near_zerof(r) && near_zerof(g) && near_zerof(b))
        {
          Y = 0.0f;
          u = 4.0f/19.0f;
          v = 9.0f/19.0f;
        }
      else
        {
          X = m_0_0 * r + m_0_1 * g + m_0_2 * b;
          Y = m_1_0 * r + m_1_1 * g + m_1_2 * b;
          Z = m_2_0 * r + m_2_1 * g + m_2_2 * b;

          sum = (X + 15.0f * Y + 3.0f * Z);
	  u = (4.0f * X) / sum;
	  v = (9.0f * Y) / sum;
        }

      dst[0] = Y;
      dst[1] = u;
      dst[2] = v;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}

static void
rgbf_to_Yuvf (const Babl *conversion,float *src,
              float *dst,
              long   samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
  float u, v, X, Y, Z, r, g, b, sum;
      r = src[0];
      g = src[1];
      b = src[2];

      if (near_zerof(r) && near_zerof(g) && near_zerof(b))
        {
          Y = 0.0f;
		  u = 4.0/19.0;
		  v = 9.0/19.0;
        }
      else
        {
          X = m_0_0 * r + m_0_1 * g + m_0_2 * b;
          Y = m_1_0 * r + m_1_1 * g + m_1_2 * b;
          Z = m_2_0 * r + m_2_1 * g + m_2_2 * b;

	      sum = (X + 15.0f * Y + 3.0f * Z);
	      u = (4.0f * X) / sum;
	      v = (9.0f * Y) / sum;
        }

      dst[0] = Y;
      dst[1] = u;
      dst[2] = v;

      src += 3;
      dst += 3;
    }
}


static void
rgbaf_to_Yuvf (const Babl *conversion,
               float      *src,
               float      *dst,
               long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
  float u, v, X, Y, Z, r, g, b, sum;
      r = src[0];
      g = src[1];
      b = src[2];

      if (near_zerof(r) && near_zerof(g) && near_zerof(b))
        {
          Y = 0.0f;
          u = 4.0f/19.0f;
          v = 9.0f/19.0f;
        }
      else
        {
          X = m_0_0 * r + m_0_1 * g + m_0_2 * b;
          Y = m_1_0 * r + m_1_1 * g + m_1_2 * b;
          Z = m_2_0 * r + m_2_1 * g + m_2_2 * b;

	      sum = (X + 15.0f * Y + 3.0f * Z);
	      u = (4.0f * X) / sum;
	      v = (9.0f * Y) / sum;
        }

      dst[0] = Y;
      dst[1] = u;
      dst[2] = v;

      src += 4;
      dst += 3;
    }
}




/* xyY -> rgb */

static void
xyY_to_rgba (const Babl *conversion,
             char       *src,
             char       *dst,
             long        n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      double x = ((double *) src)[0];
      double y = ((double *) src)[1];
      double Y = ((double *) src)[2];

      double R, G, B, X, Z;

      //convert xyY to XYZ
      xyY_to_XYZ (x, y, Y, &X, &Y, &Z);

      //convert XYZ to RGB
      {
        double XYZ[3]  = {X,Y,Z};
        double RGB[3];
        babl_space_from_xyz (space, XYZ, RGB);
        R = RGB[0];
        G = RGB[1];
        B = RGB[2];
      }

      ((double *) dst)[0] = R;
      ((double *) dst)[1] = G;
      ((double *) dst)[2] = B;
      ((double *) dst)[3] = 1.0;

      src += sizeof (double) * 3;
      dst += sizeof (double) * 4;
    }
}


static void
xyYa_to_rgba (const Babl *conversion,char *src,
              char *dst,
              long  n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      double x     = ((double *) src)[0];
      double y     = ((double *) src)[1];
      double Y     = ((double *) src)[2];
      double alpha = ((double *) src)[3];

      double X, Z;

      //convert xyY to XYZ
      xyY_to_XYZ (x, y, Y, &X, &Y, &Z);

      {
        //convert XYZ to RGB
        double XYZ[3]  = {X,Y,Z};
        babl_space_from_xyz (space, XYZ, (double*)dst);
      }
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}



/* Yuv -> rgb */

static void
Yuv_to_rgba (const Babl *conversion,char *src,
             char *dst,
             long  n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      double Y = ((double *) src)[0];
      double u = ((double *) src)[1];
      double v = ((double *) src)[2];

      double R, G, B, X, Z;

      //convert Yuv to XYZ
      Yuv_to_XYZ (Y, u, v, &X, &Y, &Z);

      //convert XYZ to RGB
      {
        double XYZ[3]  = {X,Y,Z};
        double RGB[3];
        babl_space_from_xyz (space, XYZ, RGB);
        R = RGB[0];
        G = RGB[1];
        B = RGB[2];
      }

      ((double *) dst)[0] = R;
      ((double *) dst)[1] = G;
      ((double *) dst)[2] = B;
      ((double *) dst)[3] = 1.0;

      src += sizeof (double) * 3;
      dst += sizeof (double) * 4;
    }
}


static void
Yuva_to_rgba (const Babl *conversion,char *src,
              char *dst,
              long  n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      double Y     = ((double *) src)[0];
      double u     = ((double *) src)[1];
      double v     = ((double *) src)[2];
      double alpha = ((double *) src)[3];

      double X, Z;

      //convert Yuv to XYZ
      Yuv_to_XYZ (Y, u, v, &X, &Y, &Z);

      {
        //convert XYZ to RGB
        double XYZ[3]  = {X,Y,Z};
        babl_space_from_xyz (space, XYZ, (double*)dst);
      }
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}


/* xyYf -> rgbf */

static void
xyYf_to_rgbf (const Babl *conversion,float *src,
                float *dst,
                long   samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float X, Z, r, g, b;
      float x = src[0];
      float y = src[1];
      float Y = src[2];

      if (near_zerof (y))
        {
          X = 0.0f;
          Y = 0.0f;
          Z = 0.0f;
        }
      else
        {
          X = (x * Y) / y;
          //Y = Y;
          Z = ((1 - x - y) * Y) / y;
        }

      r = m_0_0 * X + m_0_1 * Y + m_0_2 * Z;
      g = m_1_0 * X + m_1_1 * Y + m_1_2 * Z;
      b = m_2_0 * X + m_2_1 * Y + m_2_2 * Z;

      dst[0] = r;
      dst[1] = g;
      dst[2] = b;

      src += 3;
      dst += 3;
    }
}



static void
xyYf_to_rgbaf (const Babl *conversion,
               float      *src,
               float      *dst,
               long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float X, Z, r, g, b;
      float x = src[0];
      float y = src[1];
      float Y = src[2];


      if (near_zerof (Y))
        {
          X = 0.0f;
          Y = 0.0f;
          Z = 0.0f;
        }
      else
        {
          X = (x * Y) / y;
          //Y = Y;
          Z = ((1 - x - y) * Y) / y;
        }

      r = m_0_0 * X + m_0_1 * Y + m_0_2 * Z;
      g = m_1_0 * X + m_1_1 * Y + m_1_2 * Z;
      b = m_2_0 * X + m_2_1 * Y + m_2_2 * Z;

      dst[0] =    r;
      dst[1] =    g;
      dst[2] =    b;
      dst[3] = 1.0f;

      src += 3;
      dst += 4;
    }
}

static void
xyYaf_to_rgbaf (const Babl *conversion,
                float      *src,
                float      *dst,
                long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float X, Z, r, g, b;
      float x = src[0];
      float y = src[1];
      float Y = src[2];
      float a = src[3];

      if (near_zerof (Y))
        {
          X = 0.0f;
          Y = 0.0f;
          Z = 0.0f;
        }
      else
        {
          X = (x * Y) / y;
          //Y = Y;
          Z = ((1 - x - y) * Y) / y;
        }

      r = m_0_0 * X + m_0_1 * Y + m_0_2 * Z;
      g = m_1_0 * X + m_1_1 * Y + m_1_2 * Z;
      b = m_2_0 * X + m_2_1 * Y + m_2_2 * Z;

      dst[0] = r;
      dst[1] = g;
      dst[2] = b;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}



/* Yuvf -> rgbf */

static void
Yuvf_to_rgbf (const Babl *conversion,float *src,
              float *dst,
              long   samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float X, Z, r, g, b;
      float Y = src[0];
      float u = src[1];
      float v = src[2];

      if (near_zerof (v))
        {
          X = 0.0f;
          Y = 0.0f;
          Z = 0.0f;
        }
      else
        {
          X = ((9.0f * u * Y)/(4.0f * v));
          //Y = Y;
          Z = -(((20.0f * v + 3.0f * u - 12.0f) * Y)/(4.0f * v));
        }

      r = m_0_0 * X + m_0_1 * Y + m_0_2 * Z;
      g = m_1_0 * X + m_1_1 * Y + m_1_2 * Z;
      b = m_2_0 * X + m_2_1 * Y + m_2_2 * Z;

      dst[0] = r;
      dst[1] = g;
      dst[2] = b;

      src += 3;
      dst += 3;
    }
}



static void
Yuvf_to_rgbaf (const Babl *conversion,
               float      *src,
               float      *dst,
               long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float X, Z, r, g, b;
      float Y = src[0];
      float u = src[1];
      float v = src[2];

      if (near_zerof (v))
        {
          X = 0.0f;
          Y = 0.0f;
          Z = 0.0f;
        }
      else
        {
          X = ((9.0f * u * Y)/(4.0f * v));
          //Y = Y;
          Z = -(((20.0f * v + 3.0f * u - 12.0f) * Y)/(4.0f * v));
        }

      r = m_0_0 * X + m_0_1 * Y + m_0_2 * Z;
      g = m_1_0 * X + m_1_1 * Y + m_1_2 * Z;
      b = m_2_0 * X + m_2_1 * Y + m_2_2 * Z;

      dst[0] =    r;
      dst[1] =    g;
      dst[2] =    b;
      dst[3] = 1.0f;

      src += 3;
      dst += 4;
    }
}

static void
Yuvaf_to_rgbaf (const Babl *conversion,
                float      *src,
                float      *dst,
                long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float X, Z, r, g, b;
      float Y = src[0];
      float u = src[1];
      float v = src[2];
      float a = src[3];

      if (near_zerof (v))
        {
          X = 0.0f;
          Y = 0.0f;
          Z = 0.0f;
        }
      else
        {
          X = ((9.0f * u * Y)/(4.0f * v));
          //Y = Y;
          Z = -(((20.0f * v + 3.0f * u - 12.0f) * Y)/(4.0f * v));
        }

      r = m_0_0 * X + m_0_1 * Y + m_0_2 * Z;
      g = m_1_0 * X + m_1_1 * Y + m_1_2 * Z;
      b = m_2_0 * X + m_2_1 * Y + m_2_2 * Z;

      dst[0] = r;
      dst[1] = g;
      dst[2] = b;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}


/* rgb <-> LAB */

static void
rgba_to_lab (const Babl *conversion,
             char       *src,
             char       *dst,
             long        n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      double XYZ[3], L, a, b;

      babl_space_to_xyz (space, (double*)src, XYZ);
      XYZ_to_LAB (XYZ[0], XYZ[1], XYZ[2], &L, &a, &b);

      ((double *) dst)[0] = L;
      ((double *) dst)[1] = a;
      ((double *) dst)[2] = b;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 3;
    }
}


static void
lab_to_rgba (const Babl *conversion,
             char       *src,
             char       *dst,
             long        n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      double L = ((double *) src)[0];
      double a = ((double *) src)[1];
      double b = ((double *) src)[2];

      double X, Y, Z, R, G, B;

      //convert Lab to XYZ
      LAB_to_XYZ (L, a, b, &X, &Y, &Z);

      //convert XYZ to RGB
      {
        double XYZ[3]  = {X,Y,Z};
        double RGB[3];
        babl_space_from_xyz (space, XYZ, RGB);
        R = RGB[0];
        G = RGB[1];
        B = RGB[2];
      }

      ((double *) dst)[0] = R;
      ((double *) dst)[1] = G;
      ((double *) dst)[2] = B;
      ((double *) dst)[3] = 1.0;

      src += sizeof (double) * 3;
      dst += sizeof (double) * 4;
    }
}

static void
rgba_to_laba (const Babl *conversion,
              char       *src,
              char       *dst,
              long        n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  while (n--)
    {
      double alpha = ((double *) src)[3];
      double XYZ[3], L, a, b;

      //convert RGB to XYZ
      babl_space_to_xyz (space, (double*)src, XYZ);

      //convert XYZ to Lab
      XYZ_to_LAB (XYZ[0], XYZ[1], XYZ[2], &L, &a, &b);

      ((double *) dst)[0] = L;
      ((double *) dst)[1] = a;
      ((double *) dst)[2] = b;
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}

static void
laba_to_rgba (const Babl *conversion,
              char       *src,
              char       *dst,
              long        n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      double L     = ((double *) src)[0];
      double a     = ((double *) src)[1];
      double b     = ((double *) src)[2];
      double alpha = ((double *) src)[3];

      double X, Y, Z;

      //convert Lab to XYZ
      LAB_to_XYZ (L, a, b, &X, &Y, &Z);

      {
        //convert XYZ to RGB
        double XYZ[3]  = {X,Y,Z};
        babl_space_from_xyz (space, XYZ, (double*)dst);
      }
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}


/* rgb <-> LCh */

static inline void
CHab_to_ab (double  C,
            double  H,
            double *to_a,
            double *to_b)
{
  *to_a = cos (H * RADIANS_PER_DEGREE) * C;
  *to_b = sin (H * RADIANS_PER_DEGREE) * C;
}

static inline void
ab_to_CHab (double  a,
            double  b,
            double *to_C,
            double *to_H)
{
  *to_C = sqrt ( (a * a) + (b * b) );
  *to_H = atan2 (b, a) * DEGREES_PER_RADIAN;

  // Keep H within the range 0-360
  if (*to_H < 0.0)
      *to_H += 360;
}

static void
rgba_to_lchab (const Babl *conversion,
               char       *src,
               char       *dst,
               long        n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);

  while (n--)
    {
      double XYZ[3], L, a, b, C, H;

      //convert RGB to XYZ
      babl_space_to_xyz (space, (double *)src, XYZ);

      //convert XYZ to Lab
      XYZ_to_LAB (XYZ[0], XYZ[1], XYZ[2], &L, &a, &b);


      //convert Lab to LCH(ab)
      ab_to_CHab (a, b, &C, &H);

      ((double *) dst)[0] = L;
      ((double *) dst)[1] = C;
      ((double *) dst)[2] = H;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 3;
    }
}

static void
lchab_to_rgba (const Babl *conversion,
               char       *src,
               char       *dst,
               long        n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);

  while (n--)
    {
      double L = ((double *) src)[0];
      double C = ((double *) src)[1];
      double H = ((double *) src)[2];
      double a, b, X, Y, Z;

      //Convert LCH(ab) to Lab
      CHab_to_ab (C, H, &a, &b);

      //Convert LAB to XYZ
      LAB_to_XYZ (L, a, b, &X, &Y, &Z);

      //Convert XYZ to RGB
      {
        double XYZ[3]  = {X,Y,Z};
        babl_space_from_xyz (space, XYZ, (double*)dst);
      }

      ((double *) dst)[3] = 1.0;

      src += sizeof (double) * 3;
      dst += sizeof (double) * 4;
    }
}

static void
rgba_to_lchaba (const Babl *conversion,
                char       *src,
                char       *dst,
                long        n)
{
  const Babl *space = babl_conversion_get_source_space (conversion);

  while (n--)
    {
      double alpha = ((double *) src)[3];
      double XYZ[3], L, a, b, C, H;

      //convert RGB to XYZ
      babl_space_to_xyz (space, (double*)src, XYZ);

      //convert XYZ to Lab
      XYZ_to_LAB (XYZ[0], XYZ[1], XYZ[2], &L, &a, &b);

      //convert Lab to LCH(ab)
      ab_to_CHab (a, b, &C, &H);

      ((double *) dst)[0] = L;
      ((double *) dst)[1] = C;
      ((double *) dst)[2] = H;
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}

static void
lchaba_to_rgba (const Babl *conversion,
                char       *src,
                char       *dst,
                long        n)
{
  const Babl *space = babl_conversion_get_destination_space (conversion);
  while (n--)
    {
      double L     = ((double *) src)[0];
      double C     = ((double *) src)[1];
      double H     = ((double *) src)[2];
      double alpha = ((double *) src)[3];
      double a, b, X, Y, Z;

      //Convert LCH(ab) to Lab
      CHab_to_ab (C, H, &a, &b);

      //Convert Lab to XYZ
      LAB_to_XYZ (L, a, b, &X, &Y, &Z);

      //Convert XYZ to RGB
      {
        double XYZ[3]  = {X,Y,Z};
        babl_space_from_xyz (space, XYZ, (double*)dst);
      }
      ((double *) dst)[3] = alpha;

      src += sizeof (double) * 4;
      dst += sizeof (double) * 4;
    }
}


/******** end double RGB/CIE color space conversions ******************/

/******** begin floating point RGB/CIE color space conversions ********/

/* origin: http://www.hackersdelight.org/hdcodetxt/acbrt.c.txt
 * permissions: http://www.hackersdelight.org/permissions.htm
 */
/* _cbrtf(x)
 * Return cube root of x
 */

static inline float
_cbrtf (float x)
{
  union { float f; uint32_t i; } u = { x };

  u.i = u.i / 4 + u.i / 16;
  u.i = u.i + u.i / 16;
  u.i = u.i + u.i / 256;
  u.i = 0x2a5137a0 + u.i;
  u.f = 0.33333333f * (2.0f * u.f + x / (u.f * u.f));
  u.f = 0.33333333f * (2.0f * u.f + x / (u.f * u.f));

  return u.f;
}

static inline float
cubef (float f)
{
  return f * f * f;
}

static void
Yf_to_Lf (const Babl *conversion,
          float      *src,
          float      *dst,
          long        samples)
{
  long n = samples;

  while (n--)
    {
      float yr = src[0];
      float L  = yr > LAB_EPSILONf ? 116.0f * _cbrtf (yr) - 16 : LAB_KAPPAf * yr;

      dst[0] = L;

      src++;
      dst++;
    }
}

static void
Yaf_to_Lf (const Babl *conversion,
           float      *src,
           float      *dst,
           long        samples)
{
  long n = samples;

  while (n--)
    {
      float yr = src[0];
      float L  = yr > LAB_EPSILONf ? 116.0f * _cbrtf (yr) - 16 : LAB_KAPPAf * yr;

      dst[0] = L;

      src += 2;
      dst += 1;
    }
}

static void
Yaf_to_Laf (const Babl *conversion,
            float      *src,
            float      *dst,
            long        samples)
{
  long n = samples;

  while (n--)
    {
      float yr = src[0];
      float a  = src[1];
      float L  = yr > LAB_EPSILONf ? 116.0f * _cbrtf (yr) - 16 : LAB_KAPPAf * yr;

      dst[0] = L;
      dst[1] = a;

      src += 2;
      dst += 2;
    }
}

static void
rgbf_to_Labf (const Babl *conversion,
              float      *src,
              float      *dst,
              long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float r = src[0];
      float g = src[1];
      float b = src[2];

      float xr = m_0_0 * r + m_0_1 * g + m_0_2 * b;
      float yr = m_1_0 * r + m_1_1 * g + m_1_2 * b;
      float zr = m_2_0 * r + m_2_1 * g + m_2_2 * b;

      float fx = xr > LAB_EPSILONf ? _cbrtf (xr) : (LAB_KAPPAf * xr + 16.0f) / 116.0f;
      float fy = yr > LAB_EPSILONf ? _cbrtf (yr) : (LAB_KAPPAf * yr + 16.0f) / 116.0f;
      float fz = zr > LAB_EPSILONf ? _cbrtf (zr) : (LAB_KAPPAf * zr + 16.0f) / 116.0f;

      float L = 116.0f * fy - 16.0f;
      float A = 500.0f * (fx - fy);
      float B = 200.0f * (fy - fz);

      dst[0] = L;
      dst[1] = A;
      dst[2] = B;

      src += 3;
      dst += 3;
    }
}

static void
rgbaf_to_Lf (const Babl *conversion,
             float      *src,
             float      *dst,
             long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  long n = samples;

  while (n--)
    {
      float r = src[0];
      float g = src[1];
      float b = src[2];

      float yr = m_1_0 * r + m_1_1 * g + m_1_2 * b;
      float L = yr > LAB_EPSILONf ? 116.0f * _cbrtf (yr) - 16 : LAB_KAPPAf * yr;

      dst[0] = L;

      src += 4;
      dst += 1;
    }
}

static void
rgbaf_to_Labf (const Babl *conversion,
               float      *src,
               float      *dst,
               long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float r = src[0];
      float g = src[1];
      float b = src[2];

      float xr = m_0_0 * r + m_0_1 * g + m_0_2 * b;
      float yr = m_1_0 * r + m_1_1 * g + m_1_2 * b;
      float zr = m_2_0 * r + m_2_1 * g + m_2_2 * b;

      float fx = xr > LAB_EPSILONf ? _cbrtf (xr) : (LAB_KAPPAf * xr + 16.0f) / 116.0f;
      float fy = yr > LAB_EPSILONf ? _cbrtf (yr) : (LAB_KAPPAf * yr + 16.0f) / 116.0f;
      float fz = zr > LAB_EPSILONf ? _cbrtf (zr) : (LAB_KAPPAf * zr + 16.0f) / 116.0f;

      float L = 116.0f * fy - 16.0f;
      float A = 500.0f * (fx - fy);
      float B = 200.0f * (fy - fz);

      dst[0] = L;
      dst[1] = A;
      dst[2] = B;

      src += 4;
      dst += 3;
    }
}

static void
rgbaf_to_Labaf (const Babl *conversion,
                float      *src,
                float      *dst,
                long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float r = src[0];
      float g = src[1];
      float b = src[2];
      float a = src[3];

      float xr = m_0_0 * r + m_0_1 * g + m_0_2 * b;
      float yr = m_1_0 * r + m_1_1 * g + m_1_2 * b;
      float zr = m_2_0 * r + m_2_1 * g + m_2_2 * b;

      float fx = xr > LAB_EPSILONf ? _cbrtf (xr) : (LAB_KAPPAf * xr + 16.0f) / 116.0f;
      float fy = yr > LAB_EPSILONf ? _cbrtf (yr) : (LAB_KAPPAf * yr + 16.0f) / 116.0f;
      float fz = zr > LAB_EPSILONf ? _cbrtf (zr) : (LAB_KAPPAf * zr + 16.0f) / 116.0f;

      float L = 116.0f * fy - 16.0f;
      float A = 500.0f * (fx - fy);
      float B = 200.0f * (fy - fz);

      dst[0] = L;
      dst[1] = A;
      dst[2] = B;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}

static void
Labf_to_Lf (const Babl *conversion,
            float      *src,
            float      *dst,
            long        samples)
{
  long n = samples;

  while (n--)
    {
      dst[0] = src[0];

      src += 3;
      dst += 1;
    }
}

static void
Labaf_to_Lf (const Babl *conversion,
             float      *src,
             float      *dst,
             long        samples)
{
  long n = samples;

  while (n--)
    {
      dst[0] = src[0];

      src += 4;
      dst += 1;
    }
}

static void
Labf_to_rgbf (const Babl *conversion,
              float      *src,
              float      *dst,
              long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float L = src[0];
      float A = src[1];
      float B = src[2];

      float fy = (L + 16.0f) / 116.0f;
      float fx = fy + A / 500.0f;
      float fz = fy - B / 200.0f;

      float yr = L > LAB_KAPPAf * LAB_EPSILONf ? cubef (fy) : L / LAB_KAPPAf;
      float xr = cubef (fx) > LAB_EPSILONf ? cubef (fx) : (fx * 116.0f - 16.0f) / LAB_KAPPAf;
      float zr = cubef (fz) > LAB_EPSILONf ? cubef (fz) : (fz * 116.0f - 16.0f) / LAB_KAPPAf;

      float r = m_0_0 * xr + m_0_1 * yr + m_0_2 * zr;
      float g = m_1_0 * xr + m_1_1 * yr + m_1_2 * zr;
      float b = m_2_0 * xr + m_2_1 * yr + m_2_2 * zr;

      dst[0] = r;
      dst[1] = g;
      dst[2] = b;

      src += 3;
      dst += 3;
    }
}


static void
Labf_to_rgbaf (const Babl *conversion,float *src,
               float *dst,
               long   samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float L = src[0];
      float A = src[1];
      float B = src[2];

      float fy = (L + 16.0f) / 116.0f;
      float fx = fy + A / 500.0f;
      float fz = fy - B / 200.0f;

      float yr = L > LAB_KAPPAf * LAB_EPSILONf ? cubef (fy) : L / LAB_KAPPAf;
      float xr = cubef (fx) > LAB_EPSILONf ? cubef (fx) : (fx * 116.0f - 16.0f) / LAB_KAPPAf;
      float zr = cubef (fz) > LAB_EPSILONf ? cubef (fz) : (fz * 116.0f - 16.0f) / LAB_KAPPAf;

      float r = m_0_0 * xr + m_0_1 * yr + m_0_2 * zr;
      float g = m_1_0 * xr + m_1_1 * yr + m_1_2 * zr;
      float b = m_2_0 * xr + m_2_1 * yr + m_2_2 * zr;

      dst[0] = r;
      dst[1] = g;
      dst[2] = b;
      dst[3] = 1.0f;

      src += 3;
      dst += 4;
    }
}

static void
Labaf_to_rgbaf (const Babl *conversion,
                float      *src,
                float      *dst,
                long        samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  float m_0_0 = space->space.XYZtoRGBf[0] * D50_WHITE_REF_Xf;
  float m_0_1 = space->space.XYZtoRGBf[1] * D50_WHITE_REF_Yf;
  float m_0_2 = space->space.XYZtoRGBf[2] * D50_WHITE_REF_Zf;
  float m_1_0 = space->space.XYZtoRGBf[3] * D50_WHITE_REF_Xf;
  float m_1_1 = space->space.XYZtoRGBf[4] * D50_WHITE_REF_Yf;
  float m_1_2 = space->space.XYZtoRGBf[5] * D50_WHITE_REF_Zf;
  float m_2_0 = space->space.XYZtoRGBf[6] * D50_WHITE_REF_Xf;
  float m_2_1 = space->space.XYZtoRGBf[7] * D50_WHITE_REF_Yf;
  float m_2_2 = space->space.XYZtoRGBf[8] * D50_WHITE_REF_Zf;
  long n = samples;

  while (n--)
    {
      float L = src[0];
      float A = src[1];
      float B = src[2];
      float a = src[3];

      float fy = (L + 16.0f) / 116.0f;
      float fx = fy + A / 500.0f;
      float fz = fy - B / 200.0f;

      float yr = L > LAB_KAPPAf * LAB_EPSILONf ? cubef (fy) : L / LAB_KAPPAf;
      float xr = cubef (fx) > LAB_EPSILONf ? cubef (fx) : (fx * 116.0f - 16.0f) / LAB_KAPPAf;
      float zr = cubef (fz) > LAB_EPSILONf ? cubef (fz) : (fz * 116.0f - 16.0f) / LAB_KAPPAf;

      float r = m_0_0 * xr + m_0_1 * yr + m_0_2 * zr;
      float g = m_1_0 * xr + m_1_1 * yr + m_1_2 * zr;
      float b = m_2_0 * xr + m_2_1 * yr + m_2_2 * zr;

      dst[0] = r;
      dst[1] = g;
      dst[2] = b;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}

static void
Labf_to_Lchabf (const Babl *conversion,
                float      *src,
                float      *dst,
                long        samples)
{
  long n = samples;

  while (n--)
    {
      float L = src[0];
      float A = src[1];
      float B = src[2];

      float C = sqrtf (A * A + B * B);
      float H = atan2f (B, A) * DEGREES_PER_RADIANf;

      // Keep H within the range 0-360
      if (H < 0.0f)
        H += 360.0f;

      dst[0] = L;
      dst[1] = C;
      dst[2] = H;

      src += 3;
      dst += 3;
    }
}

static void
Lchabf_to_Labf (const Babl *conversion,
                float      *src,
                float      *dst,
                long        samples)
{
  long n = samples;

  while (n--)
    {
      float L = src[0];
      float C = src[1];
      float H = src[2];

      float A = C * cosf (H * RADIANS_PER_DEGREEf);
      float B = C * sinf (H * RADIANS_PER_DEGREEf);

      dst[0] = L;
      dst[1] = A;
      dst[2] = B;

      src += 3;
      dst += 3;
    }
}

static void
Labaf_to_Lchabaf (const Babl *conversion,
                  float      *src,
                  float      *dst,
                  long        samples)
{
  long n = samples;

  while (n--)
    {
      float L = src[0];
      float A = src[1];
      float B = src[2];
      float a = src[3];

      float C = sqrtf (A * A + B * B);
      float H = atan2f (B, A) * DEGREES_PER_RADIANf;

      // Keep H within the range 0-360
      if (H < 0.0f)
        H += 360.0f;

      dst[0] = L;
      dst[1] = C;
      dst[2] = H;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}

static void
Lchabaf_to_Labaf (const Babl *conversion,
                  float      *src,
                  float      *dst,
                  long        samples)
{
  long n = samples;

  while (n--)
    {
      float L = src[0];
      float C = src[1];
      float H = src[2];
      float a = src[3];

      float A = C * cosf (H * RADIANS_PER_DEGREEf);
      float B = C * sinf (H * RADIANS_PER_DEGREEf);

      dst[0] = L;
      dst[1] = A;
      dst[2] = B;
      dst[3] = a;

      src += 4;
      dst += 4;
    }
}

#if defined(USE_SSE2)

/* This is an SSE2 version of Halley's method for approximating the
 * cube root of an IEEE float implementation.
 *
 * The scalar version is as follows:
 *
 * static inline float
 * _cbrt_5f (float x)
 * {
 *   union { float f; uint32_t i; } u = { x };
 *
 *   u.i = u.i / 3 + 709921077;
 *   return u.f;
 * }
 *
 * static inline float
 * _cbrta_halleyf (float a, float R)
 * {
 *   float a3 = a * a * a;
 *   float b = a * (a3 + R + R) / (a3 + a3 + R);
 *   return b;
 * }
 *
 * static inline float
 * _cbrtf (float x)
 * {
 *   float a;
 *
 *   a = _cbrt_5f (x);
 *   a = _cbrta_halleyf (a, x);
 *   a = _cbrta_halleyf (a, x);
 *   return a;
 * }
 *
 * The above scalar version seems to have originated from
 * http://metamerist.com/cbrt/cbrt.htm but that's not accessible
 * anymore. At present there's a copy in CubeRoot.cpp in the Skia
 * sources that's licensed under a BSD-style license. There's some
 * discussion on the implementation at
 * http://www.voidcn.com/article/p-gpwztojr-wt.html.
 *
 * Note that Darktable also has an SSE2 version of the same algorithm,
 * but uses only a single iteration of Halley's method, which is too
 * coarse.
 */
/* Return cube roots of the four single-precision floating point
 * components of x.
 */
static inline __m128
_cbrtf_ps_sse2 (__m128 x)
{
  const __m128i magic = _mm_set1_epi32 (709921077);

  __m128i xi = _mm_castps_si128 (x);
  __m128 xi_3 = _mm_div_ps (_mm_cvtepi32_ps (xi), _mm_set1_ps (3.0f));
  __m128i ai = _mm_add_epi32 (_mm_cvtps_epi32 (xi_3), magic);
  __m128 a = _mm_castsi128_ps (ai);

  __m128 a3 = _mm_mul_ps (_mm_mul_ps (a, a), a);
  __m128 divisor = _mm_add_ps (_mm_add_ps (a3, a3), x);
  a = _mm_div_ps (_mm_mul_ps (a, _mm_add_ps (a3, _mm_add_ps (x, x))), divisor);

  a3 = _mm_mul_ps (_mm_mul_ps (a, a), a);
  divisor = _mm_add_ps (_mm_add_ps (a3, a3), x);
  a = _mm_div_ps (_mm_mul_ps (a, _mm_add_ps (a3, _mm_add_ps (x, x))), divisor);

  return a;
}

static inline __m128
lab_r_to_f_sse2 (__m128 r)
{
  const __m128 epsilon = _mm_set1_ps (LAB_EPSILON);
  const __m128 kappa = _mm_set1_ps (LAB_KAPPA);

  const __m128 f_big = _cbrtf_ps_sse2 (r);

  const __m128 f_small = _mm_div_ps (_mm_add_ps (_mm_mul_ps (kappa, r), _mm_set1_ps (16.0f)),
                                     _mm_set1_ps (116.0f));

  const __m128 mask = _mm_cmpgt_ps (r, epsilon);
  const __m128 f = _mm_or_ps (_mm_and_ps (mask, f_big), _mm_andnot_ps (mask, f_small));
  return f;
}

static void
Yf_to_Lf_sse2 (const Babl  *conversion, 
               const float *src, 
               float       *dst, 
               long         samples)
{
  long i = 0;
  long remainder;

  if (((uintptr_t) src % 16) + ((uintptr_t) dst % 16) == 0)
    {
      const long n = (samples / 4) * 4;

      for ( ; i < n; i += 4)
        {
          __m128 Y = _mm_load_ps (src);

          __m128 fy = lab_r_to_f_sse2 (Y);

          __m128 L = _mm_sub_ps (_mm_mul_ps (_mm_set1_ps (116.0f), fy), _mm_set1_ps (16.0f));

          _mm_store_ps (dst, L);

          src += 4;
          dst += 4;
        }
    }

  remainder = samples - i;
  while (remainder--)
    {
      float yr = src[0];
      float L  = yr > LAB_EPSILONf ? 116.0f * _cbrtf (yr) - 16 : LAB_KAPPAf * yr;

      dst[0] = L;

      src++;
      dst++;
    }
}

static void
Yaf_to_Lf_sse2 (const Babl  *conversion, 
                const float *src, 
                float       *dst, 
                long         samples)
{
  long i = 0;
  long remainder;

  if (((uintptr_t) src % 16) + ((uintptr_t) dst % 16) == 0)
    {
      const long n = (samples / 4) * 4;

      for ( ; i < n; i += 4)
        {
          __m128 YaYa0 = _mm_load_ps (src);
          __m128 YaYa1 = _mm_load_ps (src + 4);

          __m128 Y = _mm_shuffle_ps (YaYa0, YaYa1, _MM_SHUFFLE (2, 0, 2, 0));

          __m128 fy = lab_r_to_f_sse2 (Y);

          __m128 L = _mm_sub_ps (_mm_mul_ps (_mm_set1_ps (116.0f), fy), _mm_set1_ps (16.0f));

          _mm_store_ps (dst, L);

          src += 8;
          dst += 4;
        }
    }

  remainder = samples - i;
  while (remainder--)
    {
      float yr = src[0];
      float L  = yr > LAB_EPSILONf ? 116.0f * _cbrtf (yr) - 16 : LAB_KAPPAf * yr;

      dst[0] = L;

      src += 2;
      dst += 1;
    }
}

static void
rgbaf_to_Lf_sse2 (const Babl  *conversion, 
                  const float *src, 
                  float       *dst, 
                  long         samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  const float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  const float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  const float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  long i = 0;
  long remainder;

  if (((uintptr_t) src % 16) + ((uintptr_t) dst % 16) == 0)
    {
      const long    n = (samples / 4) * 4;
      const __m128 m_1_0_v = _mm_set1_ps (m_1_0);
      const __m128 m_1_1_v = _mm_set1_ps (m_1_1);
      const __m128 m_1_2_v = _mm_set1_ps (m_1_2);

      for ( ; i < n; i += 4)
        {
          __m128 rgba0 = _mm_load_ps (src);
          __m128 rgba1 = _mm_load_ps (src + 4);
          __m128 rgba2 = _mm_load_ps (src + 8);
          __m128 rgba3 = _mm_load_ps (src + 12);

          __m128 r = rgba0;
          __m128 g = rgba1;
          __m128 b = rgba2;
          __m128 a = rgba3;
          _MM_TRANSPOSE4_PS (r, g, b, a);

          {
            __m128 yr = _mm_add_ps (_mm_add_ps (_mm_mul_ps (m_1_0_v, r), _mm_mul_ps (m_1_1_v, g)),
                                    _mm_mul_ps (m_1_2_v, b));

            __m128 fy = lab_r_to_f_sse2 (yr);

            __m128 L = _mm_sub_ps (_mm_mul_ps (_mm_set1_ps (116.0f), fy), _mm_set1_ps (16.0f));

            _mm_store_ps (dst, L);
          }

          src += 16;
          dst += 4;
        }
    }

  remainder = samples - i;
  while (remainder--)
    {
      float r = src[0];
      float g = src[1];
      float b = src[2];

      float yr = m_1_0 * r + m_1_1 * g + m_1_2 * b;
      float L = yr > LAB_EPSILONf ? 116.0f * _cbrtf (yr) - 16 : LAB_KAPPAf * yr;

      dst[0] = L;

      src += 4;
      dst += 1;
    }
}

static void
rgbaf_to_Labaf_sse2_aligned_4mult (const Babl  *conversion,
                                   const float *src,
                                   float       *dst,
                                   long         samples)
{
  const Babl *space = babl_conversion_get_source_space (conversion);
  const float m_0_0 = space->space.RGBtoXYZf[0] / D50_WHITE_REF_Xf;
  const float m_0_1 = space->space.RGBtoXYZf[1] / D50_WHITE_REF_Xf;
  const float m_0_2 = space->space.RGBtoXYZf[2] / D50_WHITE_REF_Xf;
  const float m_1_0 = space->space.RGBtoXYZf[3] / D50_WHITE_REF_Yf;
  const float m_1_1 = space->space.RGBtoXYZf[4] / D50_WHITE_REF_Yf;
  const float m_1_2 = space->space.RGBtoXYZf[5] / D50_WHITE_REF_Yf;
  const float m_2_0 = space->space.RGBtoXYZf[6] / D50_WHITE_REF_Zf;
  const float m_2_1 = space->space.RGBtoXYZf[7] / D50_WHITE_REF_Zf;
  const float m_2_2 = space->space.RGBtoXYZf[8] / D50_WHITE_REF_Zf;
  long        i = 0;

  const __m128 m_0_0_v = _mm_set1_ps (m_0_0);
  const __m128 m_0_1_v = _mm_set1_ps (m_0_1);
  const __m128 m_0_2_v = _mm_set1_ps (m_0_2);
  const __m128 m_1_0_v = _mm_set1_ps (m_1_0);
  const __m128 m_1_1_v = _mm_set1_ps (m_1_1);
  const __m128 m_1_2_v = _mm_set1_ps (m_1_2);
  const __m128 m_2_0_v = _mm_set1_ps (m_2_0);
  const __m128 m_2_1_v = _mm_set1_ps (m_2_1);
  const __m128 m_2_2_v = _mm_set1_ps (m_2_2);

  assert (((uintptr_t) src % 16) + ((uintptr_t) dst % 16) == 0);
  assert (samples % 4 == 0);

  for ( ; i < samples; i += 4)
    {
      __m128 Laba0;
      __m128 Laba1;
      __m128 Laba2;
      __m128 Laba3;

      __m128 rgba0 = _mm_load_ps (src);
      __m128 rgba1 = _mm_load_ps (src + 4);
      __m128 rgba2 = _mm_load_ps (src + 8);
      __m128 rgba3 = _mm_load_ps (src + 12);

      __m128 r = rgba0;
      __m128 g = rgba1;
      __m128 b = rgba2;
      __m128 a = rgba3;
      _MM_TRANSPOSE4_PS (r, g, b, a);

        {
          __m128 xr = _mm_add_ps (_mm_add_ps (_mm_mul_ps (m_0_0_v, r), _mm_mul_ps (m_0_1_v, g)),
                                  _mm_mul_ps (m_0_2_v, b));
          __m128 yr = _mm_add_ps (_mm_add_ps (_mm_mul_ps (m_1_0_v, r), _mm_mul_ps (m_1_1_v, g)),
                                  _mm_mul_ps (m_1_2_v, b));
          __m128 zr = _mm_add_ps (_mm_add_ps (_mm_mul_ps (m_2_0_v, r), _mm_mul_ps (m_2_1_v, g)),
                                  _mm_mul_ps (m_2_2_v, b));

          __m128 fx = lab_r_to_f_sse2 (xr);
          __m128 fy = lab_r_to_f_sse2 (yr);
          __m128 fz = lab_r_to_f_sse2 (zr);

          __m128 L = _mm_sub_ps (_mm_mul_ps (_mm_set1_ps (116.0f), fy), _mm_set1_ps (16.0f));
          __m128 A = _mm_mul_ps (_mm_set1_ps (500.0f), _mm_sub_ps (fx, fy));
          __m128 B = _mm_mul_ps (_mm_set1_ps (200.0f), _mm_sub_ps (fy, fz));

          Laba0 = L;
          Laba1 = A;
          Laba2 = B;
          Laba3 = a;
          _MM_TRANSPOSE4_PS (Laba0, Laba1, Laba2, Laba3);
        }

      _mm_store_ps (dst, Laba0);
      _mm_store_ps (dst + 4, Laba1);
      _mm_store_ps (dst + 8, Laba2);
      _mm_store_ps (dst + 12, Laba3);

      src += 16;
      dst += 16;
    }
}

static void
rgbaf_to_Labaf_sse2 (const Babl  *conversion,
                     const float *src,
                     float       *dst,
                     long         samples)
{
  if (((uintptr_t) src % 16) + ((uintptr_t) dst % 16) == 0 ||
      samples < 4)
    {
      long first_samples = samples / 4 * 4;
      long remainder;

      rgbaf_to_Labaf_sse2_aligned_4mult (conversion, src, dst, first_samples);
      remainder = samples - first_samples;

      if (remainder)
        {
          float __attribute__ ((aligned (16))) aligned_src[16];
          float __attribute__ ((aligned (16))) aligned_dest[16];

          memcpy (aligned_src, src + first_samples * 4, remainder * 16);
          memset (aligned_src + remainder * 4, 0, 4 * 16 - (remainder * 16));
          rgbaf_to_Labaf_sse2_aligned_4mult (conversion, (const float *) aligned_src, aligned_dest, 4);
          memcpy (dst + first_samples * 4, aligned_dest, remainder * 16);
        }
    }
  else
    {
      float __attribute__ ((aligned (16))) _aligned_src[4 * samples];
      float __attribute__ ((aligned (16))) _aligned_dst[4 * samples];
      float                               *aligned_src;
      float                               *aligned_dst;

      if (((uintptr_t) src % 16) != 0)
        {
          aligned_src = _aligned_src;
          memcpy (aligned_src, src, samples * 16);
        }
      else
        {
          aligned_src = (float *) src;
        }

      if (((uintptr_t) dst % 16) != 0)
        aligned_dst = _aligned_dst;
      else
        aligned_dst = dst;

      rgbaf_to_Labaf_sse2 (conversion, aligned_src, aligned_dst, samples);

      if (((uintptr_t) dst % 16) != 0)
        memcpy (dst, aligned_dst, samples * 16);
    }
}

#endif /* defined(USE_SSE2) */

static void
conversions (void)
{
  /* babl_model */

  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE Lab"),
    "linear", rgba_to_lab,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE Lab"),
    babl_model ("RGBA"),
    "linear", lab_to_rgba,
    NULL
  );
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE Lab alpha"),
    "linear", rgba_to_laba,
    NULL
  );
    babl_conversion_new (
    babl_model ("CIE Lab alpha"),
    babl_model ("RGBA"),
    "linear", laba_to_rgba,
    NULL
  );
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE LCH(ab)"),
    "linear", rgba_to_lchab,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE LCH(ab)"),
    babl_model ("RGBA"),
    "linear", lchab_to_rgba,
    NULL
  );
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE LCH(ab) alpha"),
    "linear", rgba_to_lchaba,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE LCH(ab) alpha"),
    babl_model ("RGBA"),
    "linear", lchaba_to_rgba,
    NULL
  );
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE XYZ"),
    "linear", rgba_to_xyz,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE XYZ"),
    babl_model ("RGBA"),
    "linear", xyz_to_rgba,
    NULL
  );
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE XYZ alpha"),
    "linear", rgba_to_xyza,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE XYZ alpha"),
    babl_model ("RGBA"),
    "linear", xyza_to_rgba,
    NULL
  );

  /* CIE xyY */
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE xyY"),
    "linear", rgba_to_xyY,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE xyY"),
    babl_model ("RGBA"),
    "linear", xyY_to_rgba,
    NULL
  );
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE xyY alpha"),
    "linear", rgba_to_xyYa,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE xyY alpha"),
    babl_model ("RGBA"),
    "linear", xyYa_to_rgba,
    NULL
  );

  /* CIE 1976 UCS */
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE Yuv"),
    "linear", rgba_to_Yuv,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE Yuv"),
    babl_model ("RGBA"),
    "linear", Yuv_to_rgba,
    NULL
  );
  babl_conversion_new (
    babl_model ("RGBA"),
    babl_model ("CIE Yuv alpha"),
    "linear", rgba_to_Yuva,
    NULL
  );
  babl_conversion_new (
    babl_model ("CIE Yuv alpha"),
    babl_model ("RGBA"),
    "linear", Yuva_to_rgba,
    NULL
  );

  /* babl_format */

  babl_conversion_new (
    babl_format ("RGB float"),
    babl_format ("CIE Lab float"),
    "linear", rgbf_to_Labf,
    NULL
  );
  babl_conversion_new (
    babl_format ("RGBA float"),
    babl_format ("CIE Lab float"),
    "linear", rgbaf_to_Labf,
    NULL
  );
  babl_conversion_new (
    babl_format ("RGBA float"),
    babl_format ("CIE Lab alpha float"),
    "linear", rgbaf_to_Labaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Lab float"),
    babl_format ("RGB float"),
    "linear", Labf_to_rgbf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Lab float"),
    babl_format ("RGBA float"),
    "linear", Labf_to_rgbaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Lab alpha float"),
    babl_format ("RGBA float"),
    "linear", Labaf_to_rgbaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("Y float"),
    babl_format ("CIE L float"),
    "linear", Yf_to_Lf,
    NULL
  );
  babl_conversion_new (
    babl_format ("YA float"),
    babl_format ("CIE L float"),
    "linear", Yaf_to_Lf,
    NULL
  );
  babl_conversion_new (
    babl_format ("YA float"),
    babl_format ("CIE L alpha float"),
    "linear", Yaf_to_Laf,
    NULL
  );
  babl_conversion_new (
    babl_format ("RGBA float"),
    babl_format ("CIE L float"),
    "linear", rgbaf_to_Lf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Lab float"),
    babl_format ("CIE L float"),
    "linear", Labf_to_Lf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Lab alpha float"),
    babl_format ("CIE L float"),
    "linear", Labaf_to_Lf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Lab float"),
    babl_format ("CIE LCH(ab) float"),
    "linear", Labf_to_Lchabf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE LCH(ab) float"),
    babl_format ("CIE Lab float"),
    "linear", Lchabf_to_Labf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Lab alpha float"),
    babl_format ("CIE LCH(ab) alpha float"),
    "linear", Labaf_to_Lchabaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE LCH(ab) alpha float"),
    babl_format ("CIE Lab alpha float"),
    "linear", Lchabaf_to_Labaf,
    NULL
  );

  /* CIE xyY */
  babl_conversion_new (
    babl_format ("RGB float"),
    babl_format ("CIE xyY float"),
    "linear", rgbf_to_xyYf,
    NULL
  );
  babl_conversion_new (
    babl_format ("RGBA float"),
    babl_format ("CIE xyY alpha float"),
    "linear", rgbaf_to_xyYaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("RGBA float"),
    babl_format ("CIE xyY float"),
    "linear", rgbaf_to_xyYf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE xyY float"),
    babl_format ("RGB float"),
    "linear", xyYf_to_rgbf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE xyY float"),
    babl_format ("RGBA float"),
    "linear", xyYf_to_rgbaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE xyY alpha float"),
    babl_format ("RGBA float"),
    "linear", xyYaf_to_rgbaf,
    NULL
  );
  /* CIE 1976 UCS */
  babl_conversion_new (
    babl_format ("RGB float"),
    babl_format ("CIE Yuv float"),
    "linear", rgbf_to_Yuvf,
    NULL
  );
  babl_conversion_new (
    babl_format ("RGBA float"),
    babl_format ("CIE Yuv alpha float"),
    "linear", rgbaf_to_Yuvaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("RGBA float"),
    babl_format ("CIE Yuv float"),
    "linear", rgbaf_to_Yuvf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Yuv float"),
    babl_format ("RGB float"),
    "linear", Yuvf_to_rgbf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Yuv float"),
    babl_format ("RGBA float"),
    "linear", Yuvf_to_rgbaf,
    NULL
  );
  babl_conversion_new (
    babl_format ("CIE Yuv alpha float"),
    babl_format ("RGBA float"),
    "linear", Yuvaf_to_rgbaf,
    NULL
  );
#if defined(USE_SSE2)

  if (babl_cpu_accel_get_support () & BABL_CPU_ACCEL_X86_SSE2)
    {
      babl_conversion_new (
        babl_format ("RGBA float"),
        babl_format ("CIE Lab alpha float"),
        "linear", rgbaf_to_Labaf_sse2,
        NULL
      );
      babl_conversion_new (
        babl_format ("Y float"),
        babl_format ("CIE L float"),
        "linear", Yf_to_Lf_sse2,
        NULL
      );
      babl_conversion_new (
        babl_format ("YA float"),
        babl_format ("CIE L float"),
        "linear", Yaf_to_Lf_sse2,
        NULL
      );
      babl_conversion_new (
        babl_format ("RGBA float"),
        babl_format ("CIE L float"),
        "linear", rgbaf_to_Lf_sse2,
        NULL
      );
    }

#endif /* defined(USE_SSE2) */

  rgbcie_init ();
}

static void
formats (void)
{
  babl_format_new (
    "name", "CIE Lab float",
    babl_model ("CIE Lab"),

    babl_type ("float"),
    babl_component ("CIE L"),
    babl_component ("CIE a"),
    babl_component ("CIE b"),
    NULL);

  babl_format_new (
    "name", "CIE XYZ float",
    babl_model ("CIE XYZ"),

    babl_type ("float"),
    babl_component ("CIE X"),
    babl_component ("CIE Y"),
    babl_component ("CIE Z"),
    NULL);

  babl_format_new (
    "name", "CIE XYZ alpha float",
    babl_model ("CIE XYZ"),

    babl_type ("float"),
    babl_component ("CIE X"),
    babl_component ("CIE Y"),
    babl_component ("CIE Z"),
    babl_component ("A"),
    NULL);

  babl_format_new (
    "name", "CIE Lab alpha float",
    babl_model ("CIE Lab alpha"),

    babl_type ("float"),
    babl_component ("CIE L"),
    babl_component ("CIE a"),
    babl_component ("CIE b"),
    babl_component ("A"),
    NULL);

  babl_format_new (
    "name", "CIE LCH(ab) float",
    babl_model ("CIE LCH(ab)"),

    babl_type ("float"),
    babl_component ("CIE L"),
    babl_component ("CIE C(ab)"),
    babl_component ("CIE H(ab)"),
    NULL);

  babl_format_new (
    "name", "CIE LCH(ab) alpha float",
    babl_model ("CIE LCH(ab) alpha"),

    babl_type ("float"),
    babl_component ("CIE L"),
    babl_component ("CIE C(ab)"),
    babl_component ("CIE H(ab)"),
    babl_component ("A"),
    NULL);

  babl_format_new (
    "name", "CIE L float",
    babl_model ("CIE Lab"),
    babl_type ("float"),
    babl_component ("CIE L"),
    NULL);

  babl_format_new (
    "name", "CIE L alpha float",
    babl_model ("CIE Lab alpha"),
    babl_type ("float"),
    babl_component ("CIE L"),
    babl_component ("A"),
    NULL);

  babl_format_new (
    "name", "CIE Lab u8",
    babl_model ("CIE Lab"),

    babl_type ("CIE u8 L"),
    babl_component ("CIE L"),
    babl_type ("CIE u8 ab"),
    babl_component ("CIE a"),
    babl_type ("CIE u8 ab"),
    babl_component ("CIE b"),
    NULL);

  babl_format_new (
    "name", "CIE Lab alpha u8",
    babl_model ("CIE Lab alpha"),

    babl_type ("CIE u8 L"),
    babl_component ("CIE L"),
    babl_type ("CIE u8 ab"),
    babl_component ("CIE a"),
    babl_type ("CIE u8 ab"),
    babl_component ("CIE b"),
    babl_type ("u8"),
    babl_component ("A"),
    NULL);

  babl_format_new (
    "name", "CIE Lab u16",
    babl_model ("CIE Lab"),

    babl_type ("CIE u16 L"),
    babl_component ("CIE L"),
    babl_type ("CIE u16 ab"),
    babl_component ("CIE a"),
    babl_type ("CIE u16 ab"),
    babl_component ("CIE b"),
    NULL);

  babl_format_new (
    "name", "CIE Lab alpha u16",
    babl_model ("CIE Lab alpha"),

    babl_type ("CIE u16 L"),
    babl_component ("CIE L"),
    babl_type ("CIE u16 ab"),
    babl_component ("CIE a"),
    babl_type ("CIE u16 ab"),
    babl_component ("CIE b"),
    babl_type ("u16"),
    babl_component ("A"),
    NULL);

  babl_format_new (
    "name", "CIE xyY float",
    babl_model ("CIE xyY"),

    babl_type ("float"),
    babl_component ("CIE x"),
    babl_component ("CIE y"),
    babl_component ("CIE Y"),
    NULL);

  babl_format_new (
    "name", "CIE xyY alpha float",
    babl_model ("CIE xyY alpha"),

    babl_type ("float"),
    babl_component ("CIE x"),
    babl_component ("CIE y"),
    babl_component ("CIE Y"),
    babl_component ("A"),
    NULL);

  /* CIE 1976 UCS */
  babl_format_new (
    "name", "CIE Yuv float",
    babl_model ("CIE Yuv"),

    babl_type ("float"),
    babl_component ("CIE Y"),
    babl_component ("CIE u"),
    babl_component ("CIE v"),
    NULL);

  babl_format_new (
    "name", "CIE Yuv alpha float",
    babl_model ("CIE Yuv alpha"),

    babl_type ("float"),
    babl_component ("CIE Y"),
    babl_component ("CIE u"),
    babl_component ("CIE v"),
    babl_component ("A"),
    NULL);
}


/******** end floating point RGB/CIE color space conversions **********/

/******** begin  integer RGB/CIE color space conversions **************/

static inline void
convert_double_u8_scaled (const Babl   *conversion,
                          double        min_val,
                          double        max_val,
                          unsigned char min,
                          unsigned char max,
                          char         *src,
                          char         *dst,
                          int           src_pitch,
                          int           dst_pitch,
                          long          n)
{
  while (n--)
    {
      double        dval = *(double *) src;
      unsigned char u8val;

      if (dval < min_val)
        u8val = min;
      else if (dval > max_val)
        u8val = max;
      else
        u8val = ((dval - min_val) / (max_val - min_val) * (max - min) + min) + 0.5;

      *(unsigned char *) dst = u8val;
      src                   += src_pitch;
      dst                   += dst_pitch;
    }
}

static inline void
convert_u8_double_scaled (const Babl   *conversion,
                          double        min_val,
                          double        max_val,
                          unsigned char min,
                          unsigned char max,
                          char         *src,
                          char         *dst,
                          int           src_pitch,
                          int           dst_pitch,
                          long          n)
{
  while (n--)
    {
      int    u8val = *(unsigned char *) src;
      double dval;

      if (u8val < min)
        dval = min_val;
      else if (u8val > max)
        dval = max_val;
      else
        dval = (u8val - min) / (double) (max - min) * (max_val - min_val) + min_val;

      (*(double *) dst) = dval;

      dst += dst_pitch;
      src += src_pitch;
    }
}

#define MAKE_CONVERSIONS(name, min_val, max_val, min, max) \
  static void \
  convert_ ## name ## _double (const Babl *c, char *src, \
                               char *dst, \
                               int src_pitch, \
                               int dst_pitch, \
                               long n)        \
  { \
    convert_u8_double_scaled (c, min_val, max_val, min, max, \
                              src, dst, src_pitch, dst_pitch, n); \
  }                                                               \
  static void \
  convert_double_ ## name (const Babl *c, char *src, \
                           char *dst, \
                           int src_pitch, \
                           int dst_pitch, \
                           long n)        \
  { \
    convert_double_u8_scaled (c, min_val, max_val, min, max, \
                              src, dst, src_pitch, dst_pitch, n); \
  }

/* source ICC.1:2004-10 */

MAKE_CONVERSIONS (u8_l, 0.0, 100.0, 0x00, 0xff)
MAKE_CONVERSIONS (u8_ab, -128.0, 127.0, 0x00, 0xff)

#undef MAKE_CONVERSIONS

static inline void
convert_float_u8_scaled (const Babl     *conversion,
                          float          min_val,
                          float          max_val,
                          unsigned char  min,
                          unsigned char  max,
                          char          *src,
                          char          *dst,
                          int            src_pitch,
                          int            dst_pitch,
                          long           n)
{
  while (n--)
    {
      float        dval = *(float *) src;
      unsigned char u8val;

      if (dval < min_val)
        u8val = min;
      else if (dval > max_val)
        u8val = max;
      else
        u8val = ((dval - min_val) / (max_val - min_val) * (max - min) + min) + 0.5f;

      *(unsigned char *) dst = u8val;
      src                   += src_pitch;
      dst                   += dst_pitch;
    }
}

static inline void
convert_u8_float_scaled (const Babl    *conversion,
                          float         min_val,
                          float         max_val,
                          unsigned char min,
                          unsigned char max,
                          char         *src,
                          char         *dst,
                          int           src_pitch,
                          int           dst_pitch,
                          long          n)
{
  while (n--)
    {
      int    u8val = *(unsigned char *) src;
      float dval;

      if (u8val < min)
        dval = min_val;
      else if (u8val > max)
        dval = max_val;
      else
        dval = (u8val - min) / (float) (max - min) * (max_val - min_val) + min_val;

      (*(float *) dst) = dval;

      dst += dst_pitch;
      src += src_pitch;
    }
}

#define MAKE_CONVERSIONS(name, min_val, max_val, min, max) \
  static void \
  convert_ ## name ## _float (const Babl *c, char *src, \
                               char *dst, \
                               int src_pitch, \
                               int dst_pitch, \
                               long n)        \
  { \
    convert_u8_float_scaled (c, min_val, max_val, min, max, \
                              src, dst, src_pitch, dst_pitch, n); \
  }                                                               \
  static void \
  convert_float_ ## name (const Babl *c, char *src, \
                           char *dst, \
                           int src_pitch, \
                           int dst_pitch, \
                           long n)        \
  { \
    convert_float_u8_scaled (c, min_val, max_val, min, max, \
                              src, dst, src_pitch, dst_pitch, n); \
  }

/* source ICC.1:2004-10 */

MAKE_CONVERSIONS (u8_l, 0.0, 100.0, 0x00, 0xff)
MAKE_CONVERSIONS (u8_ab, -128.0, 127.0, 0x00, 0xff)

#undef MAKE_CONVERSIONS

static void
types_u8 (void)
{
  babl_type_new (
    "CIE u8 L",
    "integer",
    "unsigned",
    "bits", 8,
    "min_val", 0.0,
    "max_val", 100.0,
    NULL
  );

  babl_type_new (
    "CIE u8 ab",
    "integer",
    "unsigned",
    "bits", 8,
    "min_val", -128.0,
    "max_val", 127.0,
    NULL
  );

  babl_conversion_new (
    babl_type ("CIE u8 L"),
    babl_type ("double"),
    "plane", convert_u8_l_double,
    NULL
  );
  babl_conversion_new (
    babl_type ("double"),
    babl_type ("CIE u8 L"),
    "plane", convert_double_u8_l,
    NULL
  );

  babl_conversion_new (
    babl_type ("CIE u8 ab"),
    babl_type ("double"),
    "plane", convert_u8_ab_double,
    NULL
  );
  babl_conversion_new (
    babl_type ("double"),
    babl_type ("CIE u8 ab"),
    "plane", convert_double_u8_ab,
    NULL
  );

  babl_conversion_new (
    babl_type ("CIE u8 L"),
    babl_type ("float"),
    "plane", convert_u8_l_float,
    NULL
  );
  babl_conversion_new (
    babl_type ("float"),
    babl_type ("CIE u8 L"),
    "plane", convert_float_u8_l,
    NULL
  );

  babl_conversion_new (
    babl_type ("CIE u8 ab"),
    babl_type ("float"),
    "plane", convert_u8_ab_float,
    NULL
  );
  babl_conversion_new (
    babl_type ("float"),
    babl_type ("CIE u8 ab"),
    "plane", convert_float_u8_ab,
    NULL
  );
}

static inline void
convert_double_u16_scaled (const Babl    *conversion,
                           double         min_val,
                           double         max_val,
                           unsigned short min,
                           unsigned short max,
                           char          *src,
                           char          *dst,
                           int            src_pitch,
                           int            dst_pitch,
                           long           n)
{
  while (n--)
    {
      double         dval = *(double *) src;
      unsigned short u16val;

      if (dval < min_val)
        u16val = min;
      else if (dval > max_val)
        u16val = max;
      else
        u16val = ((dval - min_val) / (max_val - min_val) * (max - min) + min) + 0.5f;

      *(unsigned short *) dst = u16val;
      dst                    += dst_pitch;
      src                    += src_pitch;
    }
}

static inline void
convert_u16_double_scaled (const Babl    *conversion,
                           double         min_val,
                           double         max_val,
                           unsigned short min,
                           unsigned short max,
                           char          *src,
                           char          *dst,
                           int            src_pitch,
                           int            dst_pitch,
                           long           n)
{
  while (n--)
    {
      int    u16val = *(unsigned short *) src;
      double dval;

      if (u16val < min)
        dval = min_val;
      else if (u16val > max)
        dval = max_val;
      else
        dval = (u16val - min) / (double) (max - min) * (max_val - min_val) + min_val;

      (*(double *) dst) = dval;
      dst              += dst_pitch;
      src              += src_pitch;
    }
}

#define MAKE_CONVERSIONS(name, min_val, max_val, min, max)      \
  static void \
  convert_ ## name ## _double (const Babl *c, char *src, \
                               char *dst, \
                               int src_pitch, \
                               int dst_pitch, \
                               long n)        \
  { \
    convert_u16_double_scaled (c, min_val, max_val, min, max, \
                               src, dst, src_pitch, dst_pitch, n); \
  }                                                               \
  static void \
  convert_double_ ## name (const Babl *c, char *src, \
                           char *dst, \
                           int src_pitch, \
                           int dst_pitch, \
                           long n)        \
  { \
    convert_double_u16_scaled (c, min_val, max_val, min, max, \
                               src, dst, src_pitch, dst_pitch, n); \
  }

MAKE_CONVERSIONS (u16_l, 0.0, 100.0, 0x00, 0xffff)
MAKE_CONVERSIONS (u16_ab, -128.0, 127.0, 0x00, 0xffff)

#undef MAKE_CONVERSIONS


static inline void
convert_float_u16_scaled (const Babl     *conversion,
                           float          min_val,
                           float          max_val,
                           unsigned short min,
                           unsigned short max,
                           char          *src,
                           char          *dst,
                           int            src_pitch,
                           int            dst_pitch,
                           long           n)
{
  while (n--)
    {
      float         dval = *(float *) src;
      unsigned short u16val;

      if (dval < min_val)
        u16val = min;
      else if (dval > max_val)
        u16val = max;
      else
        u16val = ((dval - min_val) / (max_val - min_val) * (max - min) + min) + 0.5f;

      *(unsigned short *) dst = u16val;
      dst                    += dst_pitch;
      src                    += src_pitch;
    }
}

static inline void
convert_u16_float_scaled (const Babl     *conversion,
                           float          min_val,
                           float          max_val,
                           unsigned short min,
                           unsigned short max,
                           char          *src,
                           char          *dst,
                           int            src_pitch,
                           int            dst_pitch,
                           long           n)
{
  while (n--)
    {
      int    u16val = *(unsigned short *) src;
      float dval;

      if (u16val < min)
        dval = min_val;
      else if (u16val > max)
        dval = max_val;
      else
        dval = (u16val - min) / (float) (max - min) * (max_val - min_val) + min_val;

      (*(float *) dst) = dval;
      dst              += dst_pitch;
      src              += src_pitch;
    }
}

#define MAKE_CONVERSIONS(name, min_val, max_val, min, max)      \
  static void \
  convert_ ## name ## _float (const Babl *c, char *src, \
                               char *dst, \
                               int src_pitch, \
                               int dst_pitch, \
                               long n)        \
  { \
    convert_u16_float_scaled (c, min_val, max_val, min, max, \
                               src, dst, src_pitch, dst_pitch, n); \
  }                                                               \
  static void \
  convert_float_ ## name (const Babl *c, char *src, \
                           char *dst, \
                           int src_pitch, \
                           int dst_pitch, \
                           long n)        \
  { \
    convert_float_u16_scaled (c, min_val, max_val, min, max, \
                               src, dst, src_pitch, dst_pitch, n); \
  }

MAKE_CONVERSIONS (u16_l, 0.0, 100.0, 0x00, 0xffff)
MAKE_CONVERSIONS (u16_ab, -128.0, 127.0, 0x00, 0xffff)

#undef MAKE_CONVERSIONS

static void
types_u16 (void)
{
  babl_type_new (
    "CIE u16 L",
    "integer",
    "unsigned",
    "bits", 16,
    "min_val", 0.0,
    "max_val", 100.0,
    NULL
  );

  babl_type_new (
    "CIE u16 ab",
    "integer",
    "unsigned",
    "bits", 16,
    "min_val", -128.0,
    "max_val", 127.0,
    NULL
  );


  babl_conversion_new (
    babl_type ("CIE u16 L"),
    babl_type ("double"),
    "plane", convert_u16_l_double,
    NULL
  );
  babl_conversion_new (
    babl_type ("double"),
    babl_type ("CIE u16 L"),
    "plane", convert_double_u16_l,
    NULL
  );

  babl_conversion_new (
    babl_type ("CIE u16 ab"),
    babl_type ("double"),
    "plane", convert_u16_ab_double,
    NULL
  );
  babl_conversion_new (
    babl_type ("double"),
    babl_type ("CIE u16 ab"),
    "plane", convert_double_u16_ab,
    NULL
  );

  babl_conversion_new (
    babl_type ("CIE u16 L"),
    babl_type ("float"),
    "plane", convert_u16_l_float,
    NULL
  );
  babl_conversion_new (
    babl_type ("float"),
    babl_type ("CIE u16 L"),
    "plane", convert_float_u16_l,
    NULL
  );

  babl_conversion_new (
    babl_type ("CIE u16 ab"),
    babl_type ("float"),
    "plane", convert_u16_ab_float,
    NULL
  );
  babl_conversion_new (
    babl_type ("float"),
    babl_type ("CIE u16 ab"),
    "plane", convert_float_u16_ab,
    NULL
  );
}

static void
types (void)
{
  types_u8 ();
  types_u16 ();
}

/******** end  integer RGB/CIE color space conversions ****************/

static void
rgbxyzrgb_init (void)
{
}

static void
rgbcie_init (void)
{
  static int initialized = 0;

  if (!initialized)
    {
      rgbxyzrgb_init ();
      initialized = 1;
    }
}
