/* LIBGIMP - The GIMP Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * 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 2 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 * Library 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, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include #include "libgimpmath/gimpmath.h" #include "gimpcolortypes.h" #include "gimpcolorspace.h" #include "gimprgb.h" #include "gimphsv.h" #define GIMP_HSV_UNDEFINED -1.0 #define GIMP_HSL_UNDEFINED -1.0 /********************************* * color conversion routines * *********************************/ /* GimpRGB functions */ void gimp_rgb_to_hsv (const GimpRGB *rgb, GimpHSV *hsv) { gdouble max, min, delta; g_return_if_fail (rgb != NULL); g_return_if_fail (hsv != NULL); max = gimp_rgb_max (rgb); min = gimp_rgb_min (rgb); hsv->v = max; delta = max - min; if (delta > 0.0001) { hsv->s = delta / max; if (rgb->r == max) { hsv->h = (rgb->g - rgb->b) / delta; } else if (rgb->g == max) { hsv->h = 2.0 + (rgb->b - rgb->r) / delta; } else if (rgb->b == max) { hsv->h = 4.0 + (rgb->r - rgb->g) / delta; } hsv->h /= 6.0; if (hsv->h < 0.0) hsv->h += 1.0; else if (hsv->h > 1.0) hsv->h -= 1.0; } else { hsv->s = 0.0; hsv->h = 0.0; } hsv->a = rgb->a; } void gimp_hsv_to_rgb (const GimpHSV *hsv, GimpRGB *rgb) { gint i; gdouble f, w, q, t; gdouble hue; g_return_if_fail (rgb != NULL); g_return_if_fail (hsv != NULL); if (hsv->s == 0.0) { rgb->r = hsv->v; rgb->g = hsv->v; rgb->b = hsv->v; } else { hue = hsv->h; if (hue == 1.0) hue = 0.0; hue *= 6.0; i = (gint) hue; f = hue - i; w = hsv->v * (1.0 - hsv->s); q = hsv->v * (1.0 - (hsv->s * f)); t = hsv->v * (1.0 - (hsv->s * (1.0 - f))); switch (i) { case 0: rgb->r = hsv->v; rgb->g = t; rgb->b = w; break; case 1: rgb->r = q; rgb->g = hsv->v; rgb->b = w; break; case 2: rgb->r = w; rgb->g = hsv->v; rgb->b = t; break; case 3: rgb->r = w; rgb->g = q; rgb->b = hsv->v; break; case 4: rgb->r = t; rgb->g = w; rgb->b = hsv->v; break; case 5: rgb->r = hsv->v; rgb->g = w; rgb->b = q; break; } } rgb->a = hsv->a; } void gimp_rgb_to_hsl (const GimpRGB *rgb, GimpHSL *hsl) { gdouble max, min, delta; g_return_if_fail (rgb != NULL); g_return_if_fail (hsl != NULL); max = gimp_rgb_max (rgb); min = gimp_rgb_min (rgb); hsl->l = (max + min) / 2.0; if (max == min) { hsl->s = 0.0; hsl->h = GIMP_HSL_UNDEFINED; } else { if (hsl->l <= 0.5) hsl->s = (max - min) / (max + min); else hsl->s = (max - min) / (2.0 - max - min); delta = max - min; if (delta == 0.0) delta = 1.0; if (rgb->r == max) { hsl->h = (rgb->g - rgb->b) / delta; } else if (rgb->g == max) { hsl->h = 2.0 + (rgb->b - rgb->r) / delta; } else if (rgb->b == max) { hsl->h = 4.0 + (rgb->r - rgb->g) / delta; } hsl->h /= 6.0; if (hsl->h < 0.0) hsl->h += 1.0; } hsl->a = rgb->a; } static gdouble gimp_hsl_value (gdouble n1, gdouble n2, gdouble hue) { gdouble val; if (hue > 6.0) hue -= 6.0; else if (hue < 0.0) hue += 6.0; if (hue < 1.0) val = n1 + (n2 - n1) * hue; else if (hue < 3.0) val = n2; else if (hue < 4.0) val = n1 + (n2 - n1) * (4.0 - hue); else val = n1; return val; } void gimp_hsl_to_rgb (const GimpHSL *hsl, GimpRGB *rgb) { g_return_if_fail (hsl != NULL); g_return_if_fail (rgb != NULL); if (hsl->s == 0) { /* achromatic case */ rgb->r = hsl->l; rgb->g = hsl->l; rgb->b = hsl->l; } else { gdouble m1, m2; if (hsl->l <= 0.5) m2 = hsl->l * (1.0 + hsl->s); else m2 = hsl->l + hsl->s - hsl->l * hsl->s; m1 = 2.0 * hsl->l - m2; rgb->r = gimp_hsl_value (m1, m2, hsl->h * 6.0 + 2.0); rgb->g = gimp_hsl_value (m1, m2, hsl->h * 6.0); rgb->b = gimp_hsl_value (m1, m2, hsl->h * 6.0 - 2.0); } rgb->a = hsl->a; } void gimp_rgb_to_cmyk (const GimpRGB *rgb, gdouble pullout, GimpCMYK *cmyk) { gdouble c, m, y, k; g_return_if_fail (rgb != NULL); g_return_if_fail (cmyk != NULL); c = 1.0 - rgb->r; m = 1.0 - rgb->g; y = 1.0 - rgb->b; k = 1.0; if (c < k) k = c; if (m < k) k = m; if (y < k) k = y; k *= pullout; if (k < 1.0) { cmyk->c = (c - k) / (1.0 - k); cmyk->m = (m - k) / (1.0 - k); cmyk->y = (y - k) / (1.0 - k); } else { cmyk->c = 0.0; cmyk->m = 0.0; cmyk->y = 0.0; } cmyk->k = k; cmyk->a = rgb->a; } void gimp_cmyk_to_rgb (const GimpCMYK *cmyk, GimpRGB *rgb) { gdouble c, m, y, k; g_return_if_fail (cmyk != NULL); g_return_if_fail (rgb != NULL); k = cmyk->k; if (k < 1.0) { c = cmyk->c * (1.0 - k) + k; m = cmyk->m * (1.0 - k) + k; y = cmyk->y * (1.0 - k) + k; } else { c = 1.0; m = 1.0; y = 1.0; } rgb->r = 1.0 - c; rgb->g = 1.0 - m; rgb->b = 1.0 - y; rgb->a = cmyk->a; } #define GIMP_RETURN_RGB(x, y, z) { rgb->r = x; rgb->g = y; rgb->b = z; return; } /***************************************************************************** * Theoretically, hue 0 (pure red) is identical to hue 6 in these transforms. * Pure red always maps to 6 in this implementation. Therefore UNDEFINED can * be defined as 0 in situations where only unsigned numbers are desired. *****************************************************************************/ void gimp_rgb_to_hwb (const GimpRGB *rgb, gdouble *hue, gdouble *whiteness, gdouble *blackness) { /* RGB are each on [0, 1]. W and B are returned on [0, 1] and H is */ /* returned on [0, 6]. Exception: H is returned UNDEFINED if W == 1 - B. */ /* ====================================================================== */ gdouble R = rgb->r, G = rgb->g, B = rgb->b, w, v, b, f; gint i; w = gimp_rgb_min (rgb); v = gimp_rgb_max (rgb); b = 1.0 - v; if (v == w) { *hue = GIMP_HSV_UNDEFINED; *whiteness = w; *blackness = b; } else { f = (R == w) ? G - B : ((G == w) ? B - R : R - G); i = (R == w) ? 3.0 : ((G == w) ? 5.0 : 1.0); *hue = (360.0 / 6.0) * (i - f / (v - w)); *whiteness = w; *blackness = b; } } void gimp_hwb_to_rgb (gdouble hue, gdouble whiteness, gdouble blackness, GimpRGB *rgb) { /* H is given on [0, 6] or UNDEFINED. whiteness and * blackness are given on [0, 1]. * RGB are each returned on [0, 1]. */ gdouble h = hue, w = whiteness, b = blackness, v, n, f; gint i; h = 6.0 * h/ 360.0; v = 1.0 - b; if (h == GIMP_HSV_UNDEFINED) { rgb->r = v; rgb->g = v; rgb->b = v; } else { i = floor (h); f = h - i; if (i & 1) f = 1.0 - f; /* if i is odd */ n = w + f * (v - w); /* linear interpolation between w and v */ switch (i) { case 6: case 0: GIMP_RETURN_RGB (v, n, w); break; case 1: GIMP_RETURN_RGB (n, v, w); break; case 2: GIMP_RETURN_RGB (w, v, n); break; case 3: GIMP_RETURN_RGB (w, n, v); break; case 4: GIMP_RETURN_RGB (n, w, v); break; case 5: GIMP_RETURN_RGB (v, w, n); break; } } } /* gint functions */ void gimp_rgb_to_hsv_int (gint *red, gint *green, gint *blue) { gdouble r, g, b; gdouble h, s, v; gint min; gdouble delta; r = *red; g = *green; b = *blue; if (r > g) { v = MAX (r, b); min = MIN (g, b); } else { v = MAX (g, b); min = MIN (r, b); } delta = v - min; if (v == 0.0) s = 0.0; else s = delta / v; if (s == 0.0) h = 0.0; else { if (r == v) h = 60.0 * (g - b) / delta; else if (g == v) h = 120 + 60.0 * (b - r) / delta; else h = 240 + 60.0 * (r - g) / delta; if (h < 0.0) h += 360.0; if (h > 360.0) h -= 360.0; } *red = ROUND (h); *green = ROUND (s * 255.0); *blue = ROUND (v); } void gimp_hsv_to_rgb_int (gint *hue, gint *saturation, gint *value) { gdouble h, s, v, h_temp; gdouble f, p, q, t; gint i; if (*saturation == 0) { *hue = *value; *saturation = *value; *value = *value; } else { h = *hue; s = *saturation / 255.0; v = *value / 255.0; if (h == 360) h_temp = 0; else h_temp = h; h_temp = h_temp / 60.0; i = floor (h_temp); f = h_temp - i; p = v * (1.0 - s); q = v * (1.0 - (s * f)); t = v * (1.0 - (s * (1.0 - f))); switch (i) { case 0: *hue = ROUND (v * 255.0); *saturation = ROUND (t * 255.0); *value = ROUND (p * 255.0); break; case 1: *hue = ROUND (q * 255.0); *saturation = ROUND (v * 255.0); *value = ROUND (p * 255.0); break; case 2: *hue = ROUND (p * 255.0); *saturation = ROUND (v * 255.0); *value = ROUND (t * 255.0); break; case 3: *hue = ROUND (p * 255.0); *saturation = ROUND (q * 255.0); *value = ROUND (v * 255.0); break; case 4: *hue = ROUND (t * 255.0); *saturation = ROUND (p * 255.0); *value = ROUND (v * 255.0); break; case 5: *hue = ROUND (v * 255.0); *saturation = ROUND (p * 255.0); *value = ROUND (q * 255.0); break; } } } void gimp_rgb_to_hls_int (gint *red, gint *green, gint *blue) { gint r, g, b; gdouble h, l, s; gint min, max; gint delta; r = *red; g = *green; b = *blue; if (r > g) { max = MAX (r, b); min = MIN (g, b); } else { max = MAX (g, b); min = MIN (r, b); } l = (max + min) / 2.0; if (max == min) { s = 0.0; h = 0.0; } else { delta = (max - min); if (l < 128) s = 255 * (gdouble) delta / (gdouble) (max + min); else s = 255 * (gdouble) delta / (gdouble) (511 - max - min); if (r == max) h = (g - b) / (gdouble) delta; else if (g == max) h = 2 + (b - r) / (gdouble) delta; else h = 4 + (r - g) / (gdouble) delta; h = h * 42.5; if (h < 0) h += 255; else if (h > 255) h -= 255; } *red = ROUND (h); *green = ROUND (l); *blue = ROUND (s); } gint gimp_rgb_to_l_int (gint red, gint green, gint blue) { gint min, max; if (red > green) { max = MAX (red, blue); min = MIN (green, blue); } else { max = MAX (green, blue); min = MIN (red, blue); } return ROUND ((max + min) / 2.0); } static gint gimp_hls_value (gdouble n1, gdouble n2, gdouble hue) { gdouble value; if (hue > 255) hue -= 255; else if (hue < 0) hue += 255; if (hue < 42.5) value = n1 + (n2 - n1) * (hue / 42.5); else if (hue < 127.5) value = n2; else if (hue < 170) value = n1 + (n2 - n1) * ((170 - hue) / 42.5); else value = n1; return ROUND (value * 255.0); } void gimp_hls_to_rgb_int (gint *hue, gint *lightness, gint *saturation) { gdouble h, l, s; h = *hue; l = *lightness; s = *saturation; if (s == 0) { /* achromatic case */ *hue = l; *lightness = l; *saturation = l; } else { gdouble m1, m2; if (l < 128) m2 = (l * (255 + s)) / 65025.0; else m2 = (l + s - (l * s) / 255.0) / 255.0; m1 = (l / 127.5) - m2; /* chromatic case */ *hue = gimp_hls_value (m1, m2, h + 85); *lightness = gimp_hls_value (m1, m2, h); *saturation = gimp_hls_value (m1, m2, h - 85); } } /** * gimp_rgb_to_cmyk_int: * @red: the red channel; returns the cyan value (0-255) * @green: the green channel; returns the magenta value (0-255) * @blue: the blue channel; returns the yellow value (0-255) * @pullout: the percentage of black to pull out (0-100); returns * the black value (0-255) * * Does a naive conversion from RGB to CMYK colorspace. A simple * formula that doesn't take any color-profiles into account is used. * The amount of black pullout how can be controlled via the @pullout * parameter. A @pullout value of 0 makes this a conversion to CMY. * A value of 100 causes the maximum amount of black to be pulled out. **/ void gimp_rgb_to_cmyk_int (gint *red, gint *green, gint *blue, gint *pullout) { gint c, m, y; c = 255 - *red; m = 255 - *green; y = 255 - *blue; if (*pullout == 0) { *red = c; *green = m; *blue = y; } else { gint k = 255; if (c < k) k = c; if (m < k) k = m; if (y < k) k = y; k = (k * CLAMP (*pullout, 0, 100)) / 100; *red = ((c - k) << 8) / (256 - k); *green = ((m - k) << 8) / (256 - k); *blue = ((y - k) << 8) / (256 - k); *pullout = k; } } void gimp_cmyk_to_rgb_int (gint *cyan, gint *magenta, gint *yellow, gint *black) { gint c, m, y, k; c = *cyan; m = *magenta; y = *yellow; k = *black; if (k) { c = ((c * (256 - k)) >> 8) + k; m = ((m * (256 - k)) >> 8) + k; y = ((y * (256 - k)) >> 8) + k; } *cyan = 255 - c; *magenta = 255 - m; *yellow = 255 - y; } void gimp_rgb_to_hsv4 (guchar *rgb, gdouble *hue, gdouble *saturation, gdouble *value) { gdouble red, green, blue; gdouble h, s, v; gdouble min, max; gdouble delta; red = rgb[0] / 255.0; green = rgb[1] / 255.0; blue = rgb[2] / 255.0; h = 0.0; /* Shut up -Wall */ if (red > green) { max = MAX (red, blue); min = MIN (green, blue); } else { max = MAX (green, blue); min = MIN (red, blue); } v = max; if (max != 0.0) s = (max - min) / max; else s = 0.0; if (s == 0.0) h = 0.0; else { delta = max - min; if (delta == 0.0) delta = 1.0; if (red == max) h = (green - blue) / delta; else if (green == max) h = 2 + (blue - red) / delta; else if (blue == max) h = 4 + (red - green) / delta; h /= 6.0; if (h < 0.0) h += 1.0; else if (h > 1.0) h -= 1.0; } *hue = h; *saturation = s; *value = v; } void gimp_hsv_to_rgb4 (guchar *rgb, gdouble hue, gdouble saturation, gdouble value) { gdouble h, s, v; gdouble f, p, q, t; if (saturation == 0.0) { hue = value; saturation = value; value = value; } else { h = hue * 6.0; s = saturation; v = value; if (h == 6.0) h = 0.0; f = h - (gint) h; p = v * (1.0 - s); q = v * (1.0 - s * f); t = v * (1.0 - s * (1.0 - f)); switch ((int) h) { case 0: hue = v; saturation = t; value = p; break; case 1: hue = q; saturation = v; value = p; break; case 2: hue = p; saturation = v; value = t; break; case 3: hue = p; saturation = q; value = v; break; case 4: hue = t; saturation = p; value = v; break; case 5: hue = v; saturation = p; value = q; break; } } rgb[0] = ROUND (hue * 255.0); rgb[1] = ROUND (saturation * 255.0); rgb[2] = ROUND (value * 255.0); }