From 047d3248896d375a8fbc80dbbf573b81a3e5a927 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 20 Mar 2009 04:06:39 +0000 Subject: Revamp color handling. --- libdimension/Makefile.am | 2 +- libdimension/canvas.c | 166 +----------------------- libdimension/color.c | 259 ++++++++++++++++++++++++++++++++++++++ libdimension/dimension.h | 4 + libdimension/dimension/canvas.h | 11 +- libdimension/dimension/color.h | 51 +++++++- libdimension/dimension/geometry.h | 16 +-- libdimension/inlines.c | 22 ++++ 8 files changed, 343 insertions(+), 188 deletions(-) create mode 100644 libdimension/color.c create mode 100644 libdimension/inlines.c (limited to 'libdimension') diff --git a/libdimension/Makefile.am b/libdimension/Makefile.am index 293093c..9338b38 100644 --- a/libdimension/Makefile.am +++ b/libdimension/Makefile.am @@ -21,5 +21,5 @@ nobase_include_HEADERS = dimension.h dimension/geometry.h dimension/color.h dime lib_LTLIBRARIES = libdimension.la -libdimension_la_SOURCES = dimension.h dimension/geometry.h dimension/color.h dimension/canvas.h canvas.c +libdimension_la_SOURCES = dimension.h dimension/geometry.h dimension/color.h dimension/canvas.h canvas.c color.c inlines.c libdimension_la_LDFLAGS = -version-info 0:0:0 diff --git a/libdimension/canvas.c b/libdimension/canvas.c index 4e3f89f..21d6d86 100644 --- a/libdimension/canvas.c +++ b/libdimension/canvas.c @@ -19,169 +19,7 @@ *************************************************************************/ #include "dimension.h" -#include -#include - -/* Conversions between CIE 1931 XYZ and sRGB color. */ - -dmnsn_pixel -dmnsn_pixel_from_color(dmnsn_color color) -{ - double X, Y, Z; /* CIE XYZ values */ - double Rlinear, Glinear, Blinear; /* Linear RGB values - no gamma */ - double R, G, B; /* sRGB values */ - dmnsn_pixel pixel; - - /* Convert from CIE xyY to CIE XYZ */ - Y = color.Y; - X = Y*color.x/color.y; - Z = Y*(1.0 - color.x - color.y)/color.y; - - /* - * First, the linear conversion. Expressed as matrix multiplication, it looks - * like this: - * - * [Rlinear] [ 3.2410 -1.5374 -0.4986] [X] - * [Glinear] = [-0.9692 1.8760 0.0416]*[Y] - * [Blinear] [ 0.0556 -0.2040 1.0570] [Z] - */ - Rlinear = 3.2410*X - 1.5374*Y - 0.4986*Z; - Glinear = -0.9692*X + 1.8760*Y + 0.0416*Z; - Blinear = 0.0556*X - 0.2040*Y + 1.0570*Z; - - /* - * If C represents R, G, and B, then the sRGB values are now found as follows: - * - * { 12.92*Clinear, Clinear <= 0.0031308 - * Csrgb = { 1/2.4 - * { (1.055)*Clinear - 0.055, Clinear > 0.0031308 - */ - - if (Rlinear <= 0.0031308) { - R = 12.92*Rlinear; - } else { - R = 1.055*pow(Rlinear, 1.0/2.4) - 0.055; - } - - if (Glinear <= 0.0031308) { - G = 12.92*Glinear; - } else { - G = 1.055*pow(Glinear, 1.0/2.4) - 0.055; - } - - if (Blinear <= 0.0031308) { - B = 12.92*Blinear; - } else { - B = 1.055*pow(Blinear, 1.0/2.4) - 0.055; - } - - /* Now we go from unlimited to limited light, saturating at UINT16_MAX */ - - if (R < 0.0) { - pixel.r = 0.0; - } else if (R > 1.0) { - pixel.r = UINT16_MAX; - } else { - pixel.r = UINT16_MAX*R; - } - - if (G < 0.0) { - pixel.g = 0.0; - } else if (G > 1.0) { - pixel.g = UINT16_MAX; - } else { - pixel.g = UINT16_MAX*G; - } - - if (B < 0.0) { - pixel.b = 0.0; - } else if (B > 1.0) { - pixel.b = UINT16_MAX; - } else { - pixel.b = UINT16_MAX*B; - } - - if (color.filter < 0.0) { - pixel.a = 0.0; - } else if (color.filter > 1.0) { - pixel.a = UINT16_MAX; - } else { - pixel.a = UINT16_MAX*color.filter; - } - - if (color.trans < 0.0) { - pixel.t = 0.0; - } else if (color.trans > 1.0) { - pixel.t = UINT16_MAX; - } else { - pixel.t = UINT16_MAX*color.trans; - } - - return pixel; -} - -dmnsn_color -dmnsn_color_from_pixel(dmnsn_pixel pixel) -{ - double R, G, B; /* sRGB values */ - double Rlinear, Glinear, Blinear; /* Linear RGB values - no gamma */ - double X, Y, Z; /* CIE XYZ values */ - dmnsn_color color; - - /* Conversion back to unlimited light */ - R = ((double)pixel.r)/UINT16_MAX; - G = ((double)pixel.g)/UINT16_MAX; - B = ((double)pixel.b)/UINT16_MAX; - - /* - * If C represents R, G, and B, then the Clinear values are now found as - * follows: - * - * { Csrgb/12.92, Csrgb <= 0.04045 - * Clinear = { 1/2.4 - * { ((Csrgb + 0.055)/1.055) , Csrgb > 0.04045 - */ - - if (R <= 0.04045) { - Rlinear = R/19.92; - } else { - Rlinear = pow((R + 0.055)/1.055, 2.4); - } - - if (G <= 0.04045) { - Glinear = G/19.92; - } else { - Glinear = pow((G + 0.055)/1.055, 2.4); - } - - if (B <= 0.04045) { - Blinear = B/19.92; - } else { - Blinear = pow((B + 0.055)/1.055, 2.4); - } - - /* - * Now, the linear conversion. Expressed as matrix multiplication, it looks - * like this: - * - * [X] [0.4124 0.3576 0.1805] [Rlinear] - * [Y] = [0.2126 0.7152 0.0722]*[Glinear] - * [X] [0.0193 0.1192 0.9505] [Blinear] - */ - - X = 0.4124*Rlinear + 0.3576*Glinear + 0.1805*Blinear; - Y = 0.2126*Rlinear + 0.7152*Glinear + 0.0722*Blinear; - Z = 0.0193*Rlinear + 0.1192*Glinear + 0.9505*Blinear; - - /* Now convert to CIE xyY colorspace */ - color.x = X/(X + Y + Z); - color.y = Y/(X + Y + Z); - color.Y = Y; - color.filter = ((double)pixel.a)/UINT16_MAX; - color.trans = ((double)pixel.t)/UINT16_MAX; - - return color; -} +#include /* For malloc(), free() */ dmnsn_canvas * dmnsn_new_canvas(unsigned int x, unsigned int y) @@ -191,7 +29,7 @@ dmnsn_new_canvas(unsigned int x, unsigned int y) if (canvas) { canvas->x = x; canvas->y = y; - canvas->pixels = malloc(sizeof(dmnsn_pixel)*x*y); + canvas->pixels = malloc(sizeof(dmnsn_color)*x*y); if (canvas->pixels) { return canvas; diff --git a/libdimension/color.c b/libdimension/color.c new file mode 100644 index 0000000..bbde48d --- /dev/null +++ b/libdimension/color.c @@ -0,0 +1,259 @@ +/************************************************************************* + * Copyright (C) 2008 Tavian Barnes * + * * + * This file is part of Dimension. * + * * + * Dimension 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. * + * * + * Dimension 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 program. If not, see * + * . * + *************************************************************************/ + +#include "dimension.h" +#include /* For pow() */ + +dmnsn_CIE_XYZ whitepoint = { 0.9505, 1, 1.089 }; + +dmnsn_color +dmnsn_color_from_XYZ(dmnsn_CIE_XYZ XYZ) +{ + dmnsn_color ret = { XYZ.X, XYZ.Y, XYZ.Z, 0.0, 0.0 }; + return ret; +} + +dmnsn_color +dmnsn_color_from_xyY(dmnsn_CIE_xyY xyY) +{ + dmnsn_color ret = { xyY.Y*xyY.x/xyY.y, + xyY.Y, + xyY.Y*(1.0 - xyY.x - xyY.Y)/xyY.y, + 0.0, 0.0 }; + return ret; +} + +static double Lab_finv(double t) { + if (t > 6.0/29.0) { + return t*t*t; + } else { + return 108.0*(t - 16.0/116.0)/841.0; + } +} + +dmnsn_color +dmnsn_color_from_Lab(dmnsn_CIE_Lab Lab, dmnsn_CIE_XYZ white) +{ + double fx, fy, fz; + dmnsn_color ret; + + fy = (Lab.L + 16.0)/116.0; + fx = fy + Lab.a/500.0; + fz = fy - Lab.b/200.0; + + ret.X = white.X*Lab_finv(fx); + ret.Y = white.Y*Lab_finv(fy); + ret.Z = white.Z*Lab_finv(fz); + + return ret; +} + +dmnsn_color +dmnsn_color_from_Luv(dmnsn_CIE_Luv Luv, dmnsn_CIE_XYZ white) +{ + double fy; + double uprime, unprime, vprime, vnprime; + dmnsn_color ret; + + fy = (Luv.L + 16.0)/116.0; + + unprime = 4.0*white.X/(white.X + 15.0*white.Y + 3.0*white.Z); + uprime = Luv.u/Luv.L/13.0 + unprime; + vnprime = 9.0*white.Y/(white.X + 15.0*white.Y + 3.0*white.Z); + vprime = Luv.v/Luv.L/13.0 + vnprime; + + ret.Y = white.Y*Lab_finv(fy); + ret.X = ret.Y*9.0*uprime/vprime/4.0; + ret.Z = ret.Y*(12.0 - 3*uprime - 20*vprime)/vprime/4.0; + + return ret; +} + +dmnsn_color +dmnsn_color_from_sRGB(dmnsn_sRGB sRGB) +{ + double Rlinear, Glinear, Blinear; /* Linear RGB values - no gamma */ + dmnsn_color ret; + + /* + * If C represents R, G, and B, then the Clinear values are now found as + * follows: + * + * { Csrgb/12.92, Csrgb <= 0.04045 + * Clinear = { 1/2.4 + * { ((Csrgb + 0.055)/1.055) , Csrgb > 0.04045 + */ + + if (sRGB.R <= 0.04045) { + Rlinear = sRGB.R/19.92; + } else { + Rlinear = pow((sRGB.R + 0.055)/1.055, 2.4); + } + + if (sRGB.G <= 0.04045) { + Glinear = sRGB.G/19.92; + } else { + Glinear = pow((sRGB.G + 0.055)/1.055, 2.4); + } + + if (sRGB.B <= 0.04045) { + Blinear = sRGB.B/19.92; + } else { + Blinear = pow((sRGB.B + 0.055)/1.055, 2.4); + } + + /* + * Now, the linear conversion. Expressed as matrix multiplication, it looks + * like this: + * + * [X] [0.4124 0.3576 0.1805] [Rlinear] + * [Y] = [0.2126 0.7152 0.0722]*[Glinear] + * [X] [0.0193 0.1192 0.9505] [Blinear] + */ + + ret.X = 0.4124*Rlinear + 0.3576*Glinear + 0.1805*Blinear; + ret.Y = 0.2126*Rlinear + 0.7152*Glinear + 0.0722*Blinear; + ret.Z = 0.0193*Rlinear + 0.1192*Glinear + 0.9505*Blinear; + ret.filter = 0.0; + ret.trans = 0.0; + + return ret; +} + +dmnsn_CIE_XYZ +dmnsn_XYZ_from_color(dmnsn_color color) +{ + dmnsn_CIE_XYZ ret = { color.X, color.Y, color.Z }; + return ret; +} + +dmnsn_CIE_xyY +dmnsn_xyY_from_color(dmnsn_color color) +{ + dmnsn_CIE_xyY ret = { color.X/(color.X + color.Y + color.Z), + color.Y/(color.X + color.Y + color.Z), + color.Y }; + return ret; +} + +static double Lab_f(double t) { + if (t > 216.0/24389.0) { + return pow(t, 1.0/3.0); + } else { + return 841.0*t/108.0 + 4.0/29.0; + } +} + +dmnsn_CIE_Lab +dmnsn_Lab_from_color(dmnsn_color color, dmnsn_CIE_XYZ white) +{ + dmnsn_CIE_Lab ret; + + ret.L = 116.0*Lab_f(color.Y/white.Y) - 16.0; + ret.a = 500.0*(Lab_f(color.X/white.X) - Lab_f(color.Y/white.Y)); + ret.b = 200.0*(Lab_f(color.Y/white.Y) - Lab_f(color.Z/white.Z)); + + return ret; +} + +dmnsn_CIE_Luv +dmnsn_Luv_from_color(dmnsn_color color, dmnsn_CIE_XYZ white) +{ + double uprime, unprime, vprime, vnprime; + dmnsn_CIE_Luv ret; + + uprime = 4.0*color.X/(color.X + 15.0*color.Y + 3.0*color.Z); + unprime = 4.0*white.X/(white.X + 15.0*white.Y + 3.0*white.Z); + vprime = 9.0*color.Y/(color.X + 15.0*color.Y + 3.0*color.Z); + vnprime = 9.0*white.Y/(white.X + 15.0*white.Y + 3.0*white.Z); + + ret.L = 116.0*Lab_f(color.Y/white.Y) - 16.0; + ret.u = 13.0*ret.L*(uprime - unprime); + ret.v = 13.0*ret.L*(vprime - vnprime); + + return ret; +} + +dmnsn_sRGB +dmnsn_sRGB_from_color(dmnsn_color color) +{ + double Rlinear, Glinear, Blinear; /* Linear RGB values - no gamma */ + dmnsn_sRGB ret; + + /* + * First, the linear conversion. Expressed as matrix multiplication, it looks + * like this: + * + * [Rlinear] [ 3.2410 -1.5374 -0.4986] [X] + * [Glinear] = [-0.9692 1.8760 0.0416]*[Y] + * [Blinear] [ 0.0556 -0.2040 1.0570] [Z] + */ + Rlinear = 3.2410*color.X - 1.5374*color.Y - 0.4986*color.Z; + Glinear = -0.9692*color.X + 1.8760*color.Y + 0.0416*color.Z; + Blinear = 0.0556*color.X - 0.2040*color.Y + 1.0570*color.Z; + + /* + * If C represents R, G, and B, then the sRGB values are now found as follows: + * + * { 12.92*Clinear, Clinear <= 0.0031308 + * Csrgb = { 1/2.4 + * { (1.055)*Clinear - 0.055, Clinear > 0.0031308 + */ + + if (Rlinear <= 0.0031308) { + ret.R = 12.92*Rlinear; + } else { + ret.R = 1.055*pow(Rlinear, 1.0/2.4) - 0.055; + } + + if (Glinear <= 0.0031308) { + ret.G = 12.92*Glinear; + } else { + ret.G = 1.055*pow(Glinear, 1.0/2.4) - 0.055; + } + + if (Blinear <= 0.0031308) { + ret.B = 12.92*Blinear; + } else { + ret.B = 1.055*pow(Blinear, 1.0/2.4) - 0.055; + } + + return ret; +} + +dmnsn_color +dmnsn_color_add(dmnsn_color color1, dmnsn_color color2) +{ + dmnsn_CIE_Lab Lab, Lab1, Lab2; + dmnsn_color ret; + + Lab1 = dmnsn_Lab_from_color(color1, whitepoint); + Lab2 = dmnsn_Lab_from_color(color2, whitepoint); + + Lab.L = Lab1.L + Lab2.L; + Lab.a = (Lab1.L*Lab1.a + Lab2.L*Lab2.a)/Lab.L; + Lab.b = (Lab1.L*Lab1.b + Lab2.L*Lab2.b)/Lab.L; + + ret = dmnsn_color_from_Lab(Lab, whitepoint); + ret.filter = (Lab1.L*color1.filter + Lab2.L*color2.filter)/Lab.L; + ret.trans = (Lab1.L*color1.trans + Lab2.L*color2.trans)/Lab.L; + + return ret; +} diff --git a/libdimension/dimension.h b/libdimension/dimension.h index 558c9ed..c52ae31 100644 --- a/libdimension/dimension.h +++ b/libdimension/dimension.h @@ -28,6 +28,10 @@ extern "C" { #endif +#ifndef DMNSN_INLINE +#define DMNSN_INLINE extern inline +#endif + #include #include #include diff --git a/libdimension/dimension/canvas.h b/libdimension/dimension/canvas.h index 8959e12..21d56e4 100644 --- a/libdimension/dimension/canvas.h +++ b/libdimension/dimension/canvas.h @@ -25,15 +25,6 @@ #ifndef DIMENSION_CANVAS_H #define DIMENSION_CANVAS_H -/* 48-bit sRGB color for pixels. */ -typedef struct { - uint16_t r, g, b; /* Red, green, blue */ - uint16_t a, t; /* Filtered transparancy, normal transparancy */ -} dmnsn_pixel; - -dmnsn_pixel dmnsn_pixel_from_color(dmnsn_color color); -dmnsn_color dmnsn_color_from_pixel(dmnsn_pixel pixel); - typedef struct { unsigned int x, y; @@ -41,7 +32,7 @@ typedef struct { * Stored in first-quadrant representation (origin is bottom-left). The pixel * at (a,b) is accessible as pixels[b*x + a]. */ - dmnsn_pixel *pixels; + dmnsn_color *pixels; } dmnsn_canvas; dmnsn_canvas *dmnsn_new_canvas(unsigned int x, unsigned int y); diff --git a/libdimension/dimension/color.h b/libdimension/dimension/color.h index 34668e3..d637a81 100644 --- a/libdimension/dimension/color.h +++ b/libdimension/dimension/color.h @@ -29,16 +29,57 @@ extern "C" { #endif -/* CIE 1931 xyY color. */ +/* Internally, we use CIE 1931 XYZ color. */ +typedef struct { + double X, Y, Z; + double filter, trans; /* Filter transparancy only lets light of this color + through; regular transparancy lets all colors + through. filter + trans should be <= 1.0. */ +} dmnsn_color; + +typedef struct { + double X, Y, Z; /* X, Y, and Z are tristimulus values, unbounded above zero. + Diffuse white is (0.9505, 1, 1.089). */ +} dmnsn_CIE_XYZ; + typedef struct { double x, y, Y; /* x and y are chromaticity coordinates, and Y is luminance, in the CIE 1931 xyZ color space. We use an unlimited light model, so x,y in [0, 1] and Y >= 0, with 1 = diffuse white */ - double filter, trans; /* Filter transparancy only lets light of this color - through; regular transparancy lets all colors - through */ -} dmnsn_color; +} dmnsn_CIE_xyY; + +typedef struct { + double L, a, b; /* L is luminence (100 = diffuse white); a and b are color- + opponent dimensions. This color space is used for color + arithmetic. */ +} dmnsn_CIE_Lab; + +typedef struct { + double L, u, v; /* L is luminence (100 = diffuse white); u and v are + chromaticity coordinates. */ +} dmnsn_CIE_Luv; + +typedef struct { + double R, G, B; /* sRGB R, G, and B values */ +} dmnsn_sRGB; + +/* Standard whitepoint, determined by the conversion of sRGB white to XYZ */ +extern dmnsn_CIE_XYZ whitepoint; + +dmnsn_color dmnsn_color_from_XYZ(dmnsn_CIE_XYZ XYZ); +dmnsn_color dmnsn_color_from_xyY(dmnsn_CIE_xyY xyY); +dmnsn_color dmnsn_color_from_Lab(dmnsn_CIE_Lab Lab, dmnsn_CIE_XYZ white); +dmnsn_color dmnsn_color_from_Luv(dmnsn_CIE_Luv Luv, dmnsn_CIE_XYZ white); +dmnsn_color dmnsn_color_from_sRGB(dmnsn_sRGB sRGB); + +dmnsn_CIE_XYZ dmnsn_XYZ_from_color(dmnsn_color color); +dmnsn_CIE_xyY dmnsn_xyY_from_color(dmnsn_color color); +dmnsn_CIE_Lab dmnsn_Lab_from_color(dmnsn_color color, dmnsn_CIE_XYZ white); +dmnsn_CIE_Luv dmnsn_Luv_from_color(dmnsn_color color, dmnsn_CIE_XYZ white); +dmnsn_sRGB dmnsn_sRGB_from_color(dmnsn_color color); + +dmnsn_color dmnsn_color_add(dmnsn_color color1, dmnsn_color color2); #ifdef __cplusplus } diff --git a/libdimension/dimension/geometry.h b/libdimension/dimension/geometry.h index 2c0c204..11a6daf 100644 --- a/libdimension/dimension/geometry.h +++ b/libdimension/dimension/geometry.h @@ -31,44 +31,44 @@ typedef struct { dmnsn_scalar x, y, z; } dmnsn_vector; /* Vector arithmetic */ -inline dmnsn_vector +DMNSN_INLINE dmnsn_vector dmnsn_vector_construct(dmnsn_scalar x, dmnsn_scalar y, dmnsn_scalar z) { dmnsn_vector v = { .x = x, .y = y, .z = z }; return v; } -inline dmnsn_vector +DMNSN_INLINE dmnsn_vector dmnsn_vector_add(dmnsn_vector lhs, dmnsn_vector rhs) { return dmnsn_vector_construct(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); } -inline dmnsn_vector +DMNSN_INLINE dmnsn_vector dmnsn_vector_sub(dmnsn_vector lhs, dmnsn_vector rhs) { return dmnsn_vector_construct(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); } -inline dmnsn_vector +DMNSN_INLINE dmnsn_vector dmnsn_vector_mul(dmnsn_scalar lhs, dmnsn_vector rhs) { return dmnsn_vector_construct(lhs*rhs.x, lhs*rhs.y, lhs*rhs.z); } -inline dmnsn_vector +DMNSN_INLINE dmnsn_vector dmnsn_vector_div(dmnsn_vector lhs, dmnsn_scalar rhs) { return dmnsn_vector_construct(lhs.x/rhs, lhs.y/rhs, lhs.z/rhs); } -inline dmnsn_scalar +DMNSN_INLINE dmnsn_scalar dmnsn_vector_dot(dmnsn_vector lhs, dmnsn_vector rhs) { return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z; } -inline dmnsn_vector +DMNSN_INLINE dmnsn_vector dmnsn_vector_cross(dmnsn_vector lhs, dmnsn_vector rhs) { return dmnsn_vector_construct(lhs.y*rhs.z - lhs.z*rhs.y, @@ -83,7 +83,7 @@ typedef struct { } dmnsn_line; /* A point on a line, defined by x0 + t*n */ -inline dmnsn_vector +DMNSN_INLINE dmnsn_vector dmnsn_line_point(dmnsn_line l, dmnsn_scalar t) { return dmnsn_vector_add(l.x0, dmnsn_vector_mul(t, l.n)); diff --git a/libdimension/inlines.c b/libdimension/inlines.c new file mode 100644 index 0000000..51348fa --- /dev/null +++ b/libdimension/inlines.c @@ -0,0 +1,22 @@ +/************************************************************************* + * Copyright (C) 2008 Tavian Barnes * + * * + * This file is part of Dimension. * + * * + * Dimension 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. * + * * + * Dimension 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 program. If not, see * + * . * + *************************************************************************/ + +#define DMNSN_INLINE +#include "dimension.h" -- cgit v1.2.3