diff options
-rw-r--r-- | TODO | 3 | ||||
-rw-r--r-- | src/libcolour.c | 89 | ||||
-rw-r--r-- | src/test.c | 151 |
3 files changed, 215 insertions, 28 deletions
@@ -5,7 +5,8 @@ Colour spaces: YCbCr TSL Y'UV - Hunter 1948 color space + Hunter 1948 color space (see Wikipedia article on CIELAB) + CIELCh (CIEHLC) (see Wikipedia article on CIELAB) Support for generic additive colour spaces (3 channels and more) Support for generic subtractive colour spaces (3 channels and more) diff --git a/src/libcolour.c b/src/libcolour.c index d60c44f..dd170af 100644 --- a/src/libcolour.c +++ b/src/libcolour.c @@ -16,6 +16,7 @@ */ #include "libcolour.h" +#include <alloca.h> #include <errno.h> #include <math.h> #include <stdlib.h> @@ -330,10 +331,9 @@ static void ciexyy_to_ciexyz(const libcolour_ciexyy_t* restrict from, libcolour_ } } -static inline double cielab_finv(double x) +static inline double cielab_finv(double t) { - double x3 = x * x * x; - return (x3 > 0.00885642) ? x3 : (x - 0.1379310) / (7.78 + 703 / 99900); + return (t > 6. / 29.) ? t * t * t : (t - 4. / 29.) * 108 / 841; } static void cielab_to_ciexyz(const libcolour_cielab_t* restrict from, libcolour_ciexyz_t* restrict to) @@ -341,10 +341,10 @@ static void cielab_to_ciexyz(const libcolour_cielab_t* restrict from, libcolour_ double L = from->L, a = from->a, b = from->b; to->Y = (L + 16) / 116; to->X = to->Y + a / 500; - to->Z = to->Y - b / 300; + to->Z = to->Y - b / 200; to->X = cielab_finv(to->X) * 0.95047; - to->Y = cielab_finv(to->Y); - to->Z = cielab_finv(to->Z) * 1.08883; + to->Y = cielab_finv(to->Y) * 1.08883; + to->Z = cielab_finv(to->Z); } static void cieluv_to_ciexyz(const libcolour_cieluv_t* restrict from, libcolour_ciexyz_t* restrict to) @@ -446,9 +446,9 @@ static void to_ciexyz(const libcolour_colour_t* restrict from, libcolour_ciexyz_ } -static inline double cielab_f(double x) +static inline double cielab_f(double t) { - return (x > 0.00885642) ? cbrt(x) : x * (7.78 + 703 / 99900) + 0.1379310; + return (t > 216. / 24389.) ? cbrt(t) : t * 841. / 108. + 4. / 29.; } static void ciexyz_to_cielab(const libcolour_ciexyz_t* restrict from, libcolour_cielab_t* restrict to) @@ -492,14 +492,14 @@ static void ciexyz_to_cieluv(const libcolour_ciexyz_t* restrict from, libcolour_ t = to->white.X + 15 * to->white.Y + 3 * to->white.Z; u = 4 * to->white.X / t; v = 9 * to->white.Y / t; - t = from->X + 15 * from->Y + 4 * from->Z; + t = from->X + 15 * from->Y + 3 * from->Z; u = 4 * from->X / t - u; v = 9 * from->Y / t - v; y = from->Y / to->white.Y; y2 = y * 24389; y = y2 <= 216 ? y2 / 27 : cbrt(y) * 116 - 16; to->L = y; - y *= 13; + y *= 13; to->u = u * y; to->v = v * y; } @@ -535,6 +535,9 @@ static void other_to_cieluv(const libcolour_colour_t* restrict from, libcolour_c static void to_cieluv(const libcolour_colour_t* restrict from, libcolour_cieluv_t* restrict to) { switch (from->model) { + case LIBCOLOUR_CIEXYZ: + ciexyz_to_cieluv(&from->ciexyz, to); + return; case LIBCOLOUR_CIELCH: cielch_to_cieluv(&from->cielch, to); return; @@ -563,7 +566,7 @@ static void cieluv_to_cielch(const libcolour_cieluv_t* restrict from, libcolour_ to_cieluv((const libcolour_colour_t*)from, &tmp); L = tmp.L, u = tmp.u, v = tmp.v; } else { - L = from->L, u = from->u, v = from->u; + L = from->L, u = from->u, v = from->v; } to->L = L; to->C = sqrt(u * u + v * v); @@ -611,9 +614,14 @@ static void to_yiq(const libcolour_colour_t* restrict from, libcolour_yiq_t* res return; default: tmp.model = LIBCOLOUR_SRGB; + tmp.srgb.with_gamma = 0; to_srgb(from, &tmp.srgb); /* fall through */ case LIBCOLOUR_SRGB: + if (tmp.srgb.with_gamma) { + tmp.srgb.with_gamma = 0; + to_srgb(from, &tmp.srgb); + } r = tmp.srgb.R; g = tmp.srgb.G; b = tmp.srgb.B; @@ -641,9 +649,14 @@ static void to_ydbdr(const libcolour_colour_t* restrict from, libcolour_ydbdr_t* return; default: tmp.model = LIBCOLOUR_SRGB; + tmp.srgb.with_gamma = 0; to_srgb(from, &tmp.srgb); /* fall through */ case LIBCOLOUR_SRGB: + if (tmp.srgb.with_gamma) { + tmp.srgb.with_gamma = 0; + to_srgb(from, &tmp.srgb); + } r = tmp.srgb.R; g = tmp.srgb.G; b = tmp.srgb.B; @@ -686,9 +699,14 @@ static void to_ypbpr(const libcolour_colour_t* restrict from, libcolour_ypbpr_t* return; default: tmp.model = LIBCOLOUR_SRGB; + tmp.srgb.with_gamma = 0; to_srgb(from, &tmp.srgb); /* fall through */ case LIBCOLOUR_SRGB: + if (tmp.srgb.with_gamma) { + tmp.srgb.with_gamma = 0; + to_srgb(from, &tmp.srgb); + } to->Y = tmp.srgb.R * 0.2126 + tmp.srgb.G * 0.7152 + tmp.srgb.B * 0.0722; to->Pb = tmp.srgb.B - to->Y; to->Pr = tmp.srgb.R - to->Y; @@ -707,9 +725,14 @@ static void to_ycgco(const libcolour_colour_t* restrict from, libcolour_ycgco_t* return; default: tmp.model = LIBCOLOUR_SRGB; + tmp.srgb.with_gamma = 0; to_srgb(from, &tmp.srgb); /* fall through */ case LIBCOLOUR_SRGB: + if (tmp.srgb.with_gamma) { + tmp.srgb.with_gamma = 0; + to_srgb(from, &tmp.srgb); + } to->Y = tmp.srgb.R / 4 + tmp.srgb.G / 2 + tmp.srgb.B / 4; to->Cg = -tmp.srgb.R / 4 + tmp.srgb.G / 2 - tmp.srgb.B / 4; to->Co = tmp.srgb.R / 2 - tmp.srgb.B / 2; @@ -766,7 +789,7 @@ static void to_cieuvw(const libcolour_colour_t* restrict from, libcolour_cieuvw_ to_cie1960ucs(from, &tmp.cie1960ucs); /* fall through */ case LIBCOLOUR_CIE1960UCS: - U = from->cie1960ucs.u, V = from->cie1960ucs.v, W = from->cie1960ucs.Y; + U = tmp.cie1960ucs.u, V = tmp.cie1960ucs.v, W = tmp.cie1960ucs.Y; W = 25 * cbrt(W) - 17; w = W * 13; to->U = w * (U - to->u0); @@ -780,7 +803,9 @@ static void to_cieuvw(const libcolour_colour_t* restrict from, libcolour_cieuvw_ int libcolour_convert(const libcolour_colour_t* restrict from, libcolour_colour_t* restrict to) { - if (from->model < 0 || from->model > LIBCOLOUR_CIEUVW) { +#define X(C, T) 1 + + if (from->model < 0 || from->model > LIBCOLOUR_LIST_MODELS 0) { +#undef X errno = EINVAL; return -1; } @@ -862,8 +887,10 @@ int libcolour_delta_e(const libcolour_colour_t* a, const libcolour_colour_t* b, } -static int eliminate(double** M, size_t n, size_t m) +#define eliminate(M, n, m) eliminate_(n, m, &(M)) +static int eliminate_(size_t n, size_t m, double (*Mp)[n][m]) { +#define M (*Mp) size_t r1, r2, c; double d; double* R1; @@ -894,7 +921,7 @@ static int eliminate(double** M, size_t n, size_t m) } for (r1 = n; --r1;) { R1 = M[r1]; - for (r2 = r1; --r2;) { + for (r2 = r1; r2--;) { R2 = M[r2]; d = R2[r1]; for (c = 0; c < m; c++) @@ -902,6 +929,7 @@ static int eliminate(double** M, size_t n, size_t m) } } return 0; +#undef M } @@ -932,7 +960,7 @@ int libcolour_proper(libcolour_colour_t* colour) m[0][0] = r.ciexyz.X, m[0][1] = g.ciexyz.X, m[0][2] = b.ciexyz.X, m[0][3] = w.ciexyz.X; m[1][0] = r.ciexyz.Y, m[1][1] = g.ciexyz.Y, m[1][2] = b.ciexyz.Y, m[1][3] = w.ciexyz.Y; m[2][0] = r.ciexyz.Z, m[2][1] = g.ciexyz.Z, m[2][2] = b.ciexyz.Z, m[2][3] = w.ciexyz.Z; - if (eliminate((double**)m, 3, 4)) + if (eliminate(m, 3, 4)) return -1; colour->rgb.red.Y = m[0][3]; colour->rgb.green.Y = m[1][3]; @@ -968,14 +996,14 @@ static int get_primaries(libcolour_rgb_t* cs) M[1][0] = r.ciexyz.Y, M[1][1] = g.ciexyz.Y, M[1][2] = b.ciexyz.Y, M[1][3] = 0, M[1][4] = 1, M[1][5] = 0; M[2][0] = r.ciexyz.Z, M[2][1] = g.ciexyz.Z, M[2][2] = b.ciexyz.Z, M[2][3] = 0, M[2][4] = 0, M[2][5] = 1; - if (eliminate((double**)M, 3, 6)) + if (eliminate(M, 3, 6)) return -1; memcpy(M[0], M[0] + 3, 3 * sizeof(double)), M[0][3] = Sr; memcpy(M[1], M[1] + 3, 3 * sizeof(double)), M[1][3] = Sg; memcpy(M[2], M[2] + 3, 3 * sizeof(double)), M[2][3] = Sb; - if (eliminate((double**)M, 3, 4)) + if (eliminate(M, 3, 4)) return -1; w.ciexyz.X = M[0][3]; @@ -1014,7 +1042,7 @@ static int get_matrices(libcolour_rgb_t* cs) M[1][0] = r.ciexyz.Y, M[1][1] = g.ciexyz.Y, M[1][2] = b.ciexyz.Y, M[1][3] = 0, M[1][4] = 1, M[1][5] = 0; M[2][0] = r.ciexyz.Z, M[2][1] = g.ciexyz.Z, M[2][2] = b.ciexyz.Z, M[2][3] = 0, M[2][4] = 0, M[2][5] = 1; - if (eliminate((double**)M, 3, 6)) + if (eliminate(M, 3, 6)) return -1; Sr = M[0][3] * w.ciexyz.X + M[0][4] * w.ciexyz.Y + M[0][5] * w.ciexyz.Z; @@ -1033,7 +1061,7 @@ static int get_matrices(libcolour_rgb_t* cs) memcpy(M[1], cs->M[1], 3 * sizeof(double)), M[1][3] = 0, M[1][4] = 1, M[1][5] = 0; memcpy(M[2], cs->M[2], 3 * sizeof(double)), M[2][3] = 0, M[2][4] = 0, M[2][5] = 1; - if (eliminate((double**)M, 3, 6)) + if (eliminate(M, 3, 6)) return -1; memcpy(cs->Minv[0], M[0] + 3, 3 * sizeof(double)); @@ -1044,20 +1072,27 @@ static int get_matrices(libcolour_rgb_t* cs) } -static int invert(double **Minv, double **M, size_t n) +#define invert(Minv, M, n) invert_(n, &(Minv), &(M)) +static int invert_(size_t n, double (*Minvp)[n][n], double (*Mp)[n][n]) { - double J[3][6]; +#define Minv (*Minvp) +#define M (*Mp) +#define J (*Jp) + double J[n][2 * n] = alloca(sizeof(double[n][2 * n])); size_t i; for (i = 0; i < n; i++) { memcpy(J[i], M[i], n * sizeof(double)); memset(J[i] + n, 0, n * sizeof(double)); J[i][n + i] = 1; } - if (eliminate((double**)J, n, 2 * n)) + if (eliminate(J, n, 2 * n)) return -1; for (i = 0; i < n; i++) - memcpy(M[i], J[i] + n, n * sizeof(double)); + memcpy(Minv[i], J[i] + n, n * sizeof(double)); return 0; +#undef J +#undef M +#undef Minv } @@ -1082,7 +1117,7 @@ static void get_transfer_function(libcolour_colour_t* cs) } -int libcolour_rgb_colour_space(libcolour_rgb_t* cs, libcolour_rgb_colour_space_t space) +int libcolour_get_rgb_colour_space(libcolour_rgb_t* cs, libcolour_rgb_colour_space_t space) { #define XYY(XVALUE, YVALUE) (libcolour_ciexyy_t){ .model = LIBCOLOUR_CIEXYY, .x = XVALUE, .y = YVALUE, .Y = 1} @@ -1093,12 +1128,12 @@ int libcolour_rgb_colour_space(libcolour_rgb_t* cs, libcolour_rgb_colour_space_t return 0; case LIBCOLOUR_RGB_COLOUR_SPACE_CUSTOM_FROM_MATRIX: - if (invert((double**)(cs->Minv), (double**)(cs->M), 3) || get_primaries(cs)) + if (invert(cs->Minv, cs->M, 3) || get_primaries(cs)) return -1; return 0; case LIBCOLOUR_RGB_COLOUR_SPACE_CUSTOM_FROM_INV_MATRIX: - if (invert((double**)(cs->M), (double**)(cs->Minv), 3) || get_primaries(cs)) + if (invert(cs->M, cs->Minv, 3) || get_primaries(cs)) return -1; return 0; diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..13f4e91 --- /dev/null +++ b/src/test.c @@ -0,0 +1,151 @@ +/** + * Copyright © 2016 Mattias Andrée <maandree@kth.se> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "libcolour.h" + +#include <stdio.h> + + +int test_convert_(libcolour_colour_t* c1, libcolour_colour_t* c2, libcolour_colour_t* c3) +{ + double ch1, ch2, ch3; + if (libcolour_convert(c1, c2)) + return -1; + if (libcolour_convert(c2, c3)) + return -1; + ch1 = c1->srgb.R - c3->srgb.R; + ch2 = c1->srgb.G - c3->srgb.G; + ch3 = c1->srgb.B - c3->srgb.B; + ch1 *= ch1; + ch2 *= ch2; + ch3 *= ch3; + if (ch1 > 0.0000001 || + ch2 > 0.0000001 || + ch3 > 0.0000001) + return 0; + return 1; +} + + +int test_convert(libcolour_colour_t* c1, libcolour_model_t model) +{ + libcolour_colour_t c2, c3; + int r1, r2; + c3 = *c1; + c2.model = model; + switch (model) { + case LIBCOLOUR_RGB: + c2.rgb.with_gamma = 0; + if (libcolour_get_rgb_colour_space(&c2.rgb, LIBCOLOUR_RGB_COLOUR_SPACE_SRGB) < 0) + return -1; + return test_convert_(c1, &c2, &c3); + case LIBCOLOUR_SRGB: + c2.srgb.with_gamma = 0; + if (r1 = test_convert_(c1, &c2, &c3), r1 < 0) + return -1; + c2.srgb.with_gamma = 1; + if (r2 = test_convert_(c1, &c2, &c3), r2 < 0) + return -1; + return r1 & r2; + case LIBCOLOUR_CIELUV: + case LIBCOLOUR_CIELCH: + c2.cielch.white.model = LIBCOLOUR_CIEXYZ; + c2.cielch.white.X = 1.0294; + c2.cielch.white.Y = 1; + c2.cielch.white.Z = 0.9118; + return test_convert_(c1, &c2, &c3); + case LIBCOLOUR_CIEUVW: + c2.cieuvw.u0 = 0.37; + c2.cieuvw.v0 = 0.30; + return test_convert_(c1, &c2, &c3); + default: + return test_convert_(c1, &c2, &c3); + } +} + + +int test_convert_all(libcolour_model_t model, const char* model_name) +{ + libcolour_colour_t c1; + int r, rc = 1; + + c1.model = model; + c1.srgb.R = 0.2, c1.srgb.G = 0.5, c1.srgb.B = 0.9; + + switch (model) { + case LIBCOLOUR_RGB: + c1.rgb.with_gamma = 0; + if (libcolour_get_rgb_colour_space(&c1.rgb, LIBCOLOUR_RGB_COLOUR_SPACE_SRGB) < 0) + return -1; + break; + case LIBCOLOUR_SRGB: + c1.srgb.with_gamma = 0; + break; + case LIBCOLOUR_CIELUV: + case LIBCOLOUR_CIELCH: + c1.cielch.white.model = LIBCOLOUR_CIEXYZ; + c1.cielch.white.X = 1.0294; + c1.cielch.white.Y = 1; + c1.cielch.white.Z = 0.9118; + break; + case LIBCOLOUR_CIEUVW: + c1.cieuvw.u0 = 0.37; + c1.cieuvw.v0 = 0.30; + break; + default: + break; + } + + c1.srgb.with_gamma = 0; +#define X(ENUM, TYPE)\ + r = test_convert(&c1, ENUM);\ + if (r < 0)\ + return -1;\ + if (!r)\ + printf("%s -> %s -> %s failed\n", model_name, #ENUM, model_name), rc = 0; + LIBCOLOUR_LIST_MODELS; +#undef X + + return rc; +} + + +/** + * Test libcolour + * + * @return 0: All tests passed + * 1: At least one test fail + * 2: An error occurred + */ +int main(int argc, char* argv[]) +{ + int r, rc = 0; + +#define X(ENUM, TYPE)\ + r = test_convert_all(ENUM, #ENUM);\ + if (r < 0)\ + goto fail;\ + if (!r)\ + rc = 1; + LIBCOLOUR_LIST_MODELS; +#undef X + + return rc; + fail: + perror(*argv); + return 2; + (void) argc; +} |