aboutsummaryrefslogtreecommitdiffstats
path: root/src/libclut.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libclut.c')
-rw-r--r--src/libclut.c214
1 files changed, 214 insertions, 0 deletions
diff --git a/src/libclut.c b/src/libclut.c
index 8963a4d..93d0ac0 100644
--- a/src/libclut.c
+++ b/src/libclut.c
@@ -16,6 +16,8 @@
*/
#include "libclut.h"
+#include <errno.h>
+
/**
@@ -199,3 +201,215 @@ void (libclut_model_cielab_to_ciexyz)(double L, double a, double b, double* X, d
libclut_model_cielab_to_ciexyz(L, a, b, X, Y, Z);
}
+
+
+/**
+ * Divide all values in a row by a divisor.
+ *
+ * @param m The first part of the row.
+ * @param a The second part of the row.
+ * @param d The divisor.
+ */
+static void divrow(double m[3], double a[3], double d)
+{
+ m[0] /= d, m[1] /= d, m[2] /= d;
+ a[0] /= d, a[1] /= d, a[2] /= d;
+}
+
+/**
+ * Subtract all values in a row by corresponding value
+ * in another row multiplied by a common value.
+ *
+ * @param a1 The first part of the minuend/difference row.
+ * @param a2 The second part of the minuend/difference row.
+ * @param b1 The first part of the subtrahend row.
+ * @param b2 The second part of the subtrahend row.
+ * @param m The multiplier.
+ */
+static void subrow(double a1[3], double a2[3], double b1[3], double b2[3], double m)
+{
+ a1[0] -= b1[0] * m, a1[1] -= b1[1] * m, a1[2] -= b1[2] * m;
+ a2[0] -= b2[0] * m, a2[1] -= b2[1] * m, a2[2] -= b2[2] * m;
+}
+
+
+/**
+ * Invert a matrix.
+ *
+ * @param M The matrix to invert, will be modified to an
+ * identity matrix, possibly with reordered rows.
+ * @param A The inversion of M (as input).
+ * @return 1 on success, 0 if the matrix is not invertible.
+ */
+static int invert(libclut_colourspace_conversion_matrix_t M, libclut_colourspace_conversion_matrix_t A)
+{
+ int r0 = 0, r1 = 1, r2 = 2, t, swapped = 0;
+ libclut_colourspace_conversion_matrix_t T;
+
+ A[0][0] = A[1][1] = A[2][2] = 1;
+ A[0][1] = A[0][2] = A[1][0] = A[1][2] = A[2][0] = A[2][1] = 0;
+
+ if (libclut_0__(M[r0][0]))
+ {
+ if (libclut_0__(M[r1][0]))
+ {
+ if (libclut_0__(M[r2][0]))
+ return 0;
+ t = r0, r0 = r2, r2 = t;
+ }
+ else
+ t = r0, r0 = r1, r1 = t;
+ swapped = 1;
+ }
+
+ divrow(M[r0], A[r0], M[r0][0]);
+ subrow(M[r1], A[r1], M[r0], A[r0], M[r1][0]);
+ subrow(M[r2], A[r2], M[r0], A[r0], M[r2][0]);
+
+ if (libclut_0__(M[r1][1]))
+ {
+ if (libclut_0__(M[r2][1]))
+ return 0;
+ t = r1, r1 = r2, r2 = t;
+ swapped = 1;
+ }
+
+ divrow(M[r1], A[r1], M[r1][1]);
+ subrow(M[r2], A[r2], M[r1], A[r1], M[r2][1]);
+
+ if (libclut_0__(M[r2][2]))
+ return 0;
+
+ divrow(M[r2], A[r2], M[r2][2]);
+
+ subrow(M[r1], A[r1], M[r2], A[r2], M[r1][2]);
+ subrow(M[r0], A[r0], M[r2], A[r2], M[r0][2]);
+
+ subrow(M[r0], A[r0], M[r1], A[r1], M[r0][1]);
+
+ if (swapped)
+ {
+ memcpy(T, A, sizeof(T));
+ memcpy(A[0], T[r0], sizeof(*T));
+ memcpy(A[1], T[r1], sizeof(*T));
+ memcpy(A[2], T[r2], sizeof(*T));
+ }
+
+ return 1;
+}
+
+
+/**
+ * Create an RGB to CIE XYZ conversion matrix.
+ *
+ * @param cs The colour space.
+ * @param M The output matrix.
+ * @return Zero on success, -1 on error.
+ *
+ * @throws EINVAL The colourspace cannot be used.
+ */
+static int get_conversion_matrix(const libclut_rgb_colourspace_t* cs, libclut_colourspace_conversion_matrix_t M)
+{
+#define XYY_TO_XYZ(x, y, Y, Xp, Yp, Zp) \
+ (libclut_0__(Y)) ? \
+ (*(Xp) = *(Zp) = 0, *(Yp) = (Y)) : \
+ (*(Xp) = (x) * (Y) / (y), \
+ *(Yp) = (Y), \
+ *(Zp) = (1 - (x) - (y)) * (Y) / (y))
+
+ double Xr, Yr, Zr, Xg, Yg, Zg, Xb, Yb, Zb, Xw, Yw, Zw, Sr, Sg, Sb;
+ libclut_colourspace_conversion_matrix_t M2;
+
+ XYY_TO_XYZ(cs->red_x, cs->red_y, 1, &Xr, &Yr, &Zr);
+ XYY_TO_XYZ(cs->green_x, cs->green_y, 1, &Xg, &Yg, &Zg);
+ XYY_TO_XYZ(cs->blue_x, cs->blue_y, 1, &Xb, &Yb, &Zb);
+ XYY_TO_XYZ(cs->white_x, cs->white_y, cs->white_Y, &Xw, &Yw, &Zw);
+
+ M2[0][0] = Xr, M2[0][1] = Xg, M2[0][2] = Xb;
+ M2[1][0] = Yr, M2[1][1] = Yg, M2[1][2] = Yb;
+ M2[2][0] = Zr, M2[2][1] = Zg, M2[2][2] = Zb;
+
+ if (!invert(M2, M))
+ return errno = EINVAL, -1;
+
+ Sr = M[0][0] * Xw + M[0][1] * Yw + M[0][2] * Zw;
+ Sg = M[1][0] * Xw + M[1][1] * Yw + M[1][2] * Zw;
+ Sb = M[2][0] * Xw + M[2][1] * Yw + M[2][2] * Zw;
+
+ M[0][0] = Sr * Xr, M[0][1] = Sg * Xg, M[0][2] = Sb * Xb;
+ M[1][0] = Sr * Yr, M[1][1] = Sg * Yg, M[1][2] = Sb * Yb;
+ M[2][0] = Sr * Zr, M[2][1] = Sg * Zg, M[2][2] = Sb * Zb;
+
+ return 0;
+
+#undef XYY_TO_XYZ
+}
+
+
+/**
+ * Create a matrix for converting values between
+ * two RGB colourspaces.
+ *
+ * @param from The input colourspace, the Y-component is only necessary for the whitepoint.
+ * @param to The output colourspace, the Y-component is only necessary for the whitepoint.
+ * @param M Output matrix for conversion from `from` to `to`.
+ * @param Minv Output matrix for conversion from `to` to `from`, may be `NULL`.
+ * @return Zero on success, -1 on error.
+ *
+ * @throws EINVAL The colourspace cannot be used.
+ */
+int libclut_model_get_rgb_conversion_matrix(const libclut_rgb_colourspace_t* from,
+ const libclut_rgb_colourspace_t* to,
+ libclut_colourspace_conversion_matrix_t M,
+ libclut_colourspace_conversion_matrix_t Minv)
+{
+ libclut_colourspace_conversion_matrix_t A, B;
+
+ if (get_conversion_matrix(from, A))
+ return -1;
+ if (get_conversion_matrix(to, M))
+ return -1;
+ if (!invert(M, B))
+ return errno = EINVAL, -1;
+
+ M[0][0] = A[0][0] * B[0][0] + A[0][1] * B[1][0] + A[0][2] * B[2][0];
+ M[0][1] = A[0][0] * B[0][1] + A[0][1] * B[1][1] + A[0][2] * B[2][1];
+ M[0][2] = A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2] * B[2][2];
+
+ M[1][0] = A[1][0] * B[0][0] + A[1][1] * B[1][0] + A[1][2] * B[2][0];
+ M[1][1] = A[1][0] * B[0][1] + A[1][1] * B[1][1] + A[1][2] * B[2][1];
+ M[1][2] = A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2] * B[2][2];
+
+ M[2][0] = A[2][0] * B[0][0] + A[2][1] * B[1][0] + A[2][2] * B[2][0];
+ M[2][1] = A[2][0] * B[0][1] + A[2][1] * B[1][1] + A[2][2] * B[2][1];
+ M[2][2] = A[2][0] * B[0][2] + A[2][1] * B[1][2] + A[2][2] * B[2][2];
+
+ if (Minv != NULL)
+ {
+ memcpy(A, M, sizeof(A));
+ if (!invert(A, Minv))
+ return errno = EINVAL, -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Convert an RGB colour into another RGB colourspace.
+ * None of the parameter may have side-effects.
+ *
+ * @param r The red component of the colour to convert.
+ * @param g The green component of the colour to convert.
+ * @param b The blue component of the colour to convert.
+ * @param M Conversion matrix, create with `libclut_model_get_rgb_conversion_matrix`.
+ * @param out_r Output parameter for the new red component.
+ * @param out_g Output parameter for the new green component.
+ * @param out_b Output parameter for the new blue component.
+ */
+void (libclut_model_convert_rgb)(double r, double g, double b, libclut_colourspace_conversion_matrix_t M,
+ double *out_r, double *out_g, double *out_b)
+{
+ libclut_model_convert_rgb(r, g, b, M, out_r, out_g, out_b);
+}
+