#undef LOCAL_DEBUG
-#include "sherlock/sherlock.h"
-#include "lib/math.h"
+#include "ucw/lib.h"
#include "images/images.h"
#include "images/color.h"
+#include "images/error.h"
+#include "images/math.h"
+
+#include <string.h>
+#include <math.h>
+
+uns color_space_channels[COLOR_SPACE_MAX] = {
+ [COLOR_SPACE_UNKNOWN] = 0,
+ [COLOR_SPACE_UNKNOWN_1] = 1,
+ [COLOR_SPACE_UNKNOWN_2] = 2,
+ [COLOR_SPACE_UNKNOWN_3] = 3,
+ [COLOR_SPACE_UNKNOWN_4] = 4,
+ [COLOR_SPACE_GRAYSCALE] = 1,
+ [COLOR_SPACE_RGB] = 3,
+ [COLOR_SPACE_XYZ] = 3,
+ [COLOR_SPACE_LAB] = 3,
+ [COLOR_SPACE_YCBCR] = 3,
+ [COLOR_SPACE_CMYK] = 4,
+ [COLOR_SPACE_YCCK] = 4,
+};
+
+byte *color_space_name[COLOR_SPACE_MAX] = {
+ [COLOR_SPACE_UNKNOWN] = "Unknown",
+ [COLOR_SPACE_UNKNOWN_1] = "1-channel",
+ [COLOR_SPACE_UNKNOWN_2] = "2-channels",
+ [COLOR_SPACE_UNKNOWN_3] = "3-channels",
+ [COLOR_SPACE_UNKNOWN_4] = "4-channels",
+ [COLOR_SPACE_GRAYSCALE] = "Grayscale",
+ [COLOR_SPACE_RGB] = "RGB",
+ [COLOR_SPACE_XYZ] = "XYZ",
+ [COLOR_SPACE_LAB] = "LAB",
+ [COLOR_SPACE_YCBCR] = "YCbCr",
+ [COLOR_SPACE_CMYK] = "CMYK",
+ [COLOR_SPACE_YCCK] = "YCCK",
+};
+
+byte *
+color_space_id_to_name(uns id)
+{
+ ASSERT(id < COLOR_SPACE_MAX);
+ return color_space_name[id];
+}
+
+uns
+color_space_name_to_id(byte *name)
+{
+ for (uns i = 1; i < COLOR_SPACE_MAX; i++)
+ if (color_space_name[i] && !strcasecmp(name, color_space_name[i]))
+ return i;
+ return 0;
+}
struct color color_black = { .color_space = COLOR_SPACE_GRAYSCALE };
struct color color_white = { .c = { 255 }, .color_space = COLOR_SPACE_GRAYSCALE };
-inline void
-color_put_grayscale(byte *dest, struct color *color)
+int
+color_get(struct color *color, byte *src, uns src_space)
+{
+ color->color_space = src_space;
+ memcpy(color->c, src, color_space_channels[src_space]);
+ return 1;
+}
+
+int
+color_put(struct image_context *ctx, struct color *color, byte *dest, uns dest_space)
{
- switch (color->color_space)
+ switch (dest_space)
{
case COLOR_SPACE_GRAYSCALE:
- dest[0] = color->c[0];
+ switch (color->color_space)
+ {
+ case COLOR_SPACE_GRAYSCALE:
+ dest[0] = color->c[0];
+ return 1;
+ case COLOR_SPACE_RGB:
+ dest[0] = rgb_to_gray_func(color->c[0], color->c[1], color->c[2]);
+ return 1;
+ }
break;
case COLOR_SPACE_RGB:
- dest[0] = rgb_to_gray_func(color->c[0], color->c[1], color->c[2]);
+ switch (color->color_space)
+ {
+ case COLOR_SPACE_GRAYSCALE:
+ dest[0] = dest[1] = dest[2] = color->c[0];
+ return 1;
+ case COLOR_SPACE_RGB:
+ dest[0] = color->c[0];
+ dest[1] = color->c[1];
+ dest[2] = color->c[2];
+ return 1;
+ case COLOR_SPACE_CMYK:
+ {
+ double rgb[3], cmyk[4];
+ for (uns i = 0; i < 4; i++)
+ cmyk[i] = color->c[i] * (1.0 / 255);
+ cmyk_to_rgb_exact(rgb, cmyk);
+ for (uns i = 0; i < 3; i++)
+ dest[i] = CLAMP(rgb[i] * 255, 0, 255);
+ }
+ return 1;
+ }
+ break;
+ case COLOR_SPACE_CMYK:
+ switch (color->color_space)
+ {
+ case COLOR_SPACE_GRAYSCALE:
+ dest[0] = dest[1] = dest[2] = 0;
+ dest[3] = 255 - color->c[0];
+ return 1;
+ case COLOR_SPACE_RGB:
+ {
+ double rgb[3], cmyk[4];
+ for (uns i = 0; i < 3; i++)
+ rgb[i] = color->c[i] * (1.0 / 255);
+ rgb_to_cmyk_exact(cmyk, rgb);
+ for (uns i = 0; i < 4; i++)
+ dest[i] = CLAMP(cmyk[i] * 255, 0, 255);
+ }
+ return 1;
+ }
break;
- default:
- ASSERT(0);
}
+ if (dest_space != COLOR_SPACE_RGB )
+ {
+ /* Try to convert the color via RGB */
+ struct color rgb;
+ if (!color_put(ctx, color, rgb.c, COLOR_SPACE_RGB))
+ return 0;
+ rgb.color_space = COLOR_SPACE_RGB;
+ return color_put(ctx, &rgb, dest, dest_space);
+ }
+ IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Conversion from %s to %s is not supported",
+ color_space_id_to_name(color->color_space), color_space_id_to_name(color->color_space));
+ return 0;
}
-inline void
-color_put_rgb(byte *dest, struct color *color)
+
+/********************* IMAGE CONVERSION ROUTINES **********************/
+
+struct image_conv_options image_conv_defaults = {
+ .flags = IMAGE_CONV_COPY_ALPHA | IMAGE_CONV_FILL_ALPHA | IMAGE_CONV_APPLY_ALPHA,
+ .background = { .color_space = COLOR_SPACE_GRAYSCALE } };
+
+/* Grayscale <-> RGB */
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_gray_1_to_rgb_n
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_SEC_COL_STEP 1
+#define IMAGE_WALK_UNROLL 4
+#define IMAGE_WALK_DO_STEP do{ walk_pos[0] = walk_pos[1] = walk_pos[2] = walk_sec_pos[0]; }while(0)
+#include "images/image-walk.h"
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_rgb_n_to_gray_1
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_COL_STEP 1
+#define IMAGE_WALK_UNROLL 2
+#define IMAGE_WALK_DO_STEP do{ walk_pos[0] = rgb_to_gray_func(walk_sec_pos[0], walk_sec_pos[1], walk_sec_pos[2]); }while(0)
+#include "images/image-walk.h"
+
+/* YCbCr <-> RGB */
+
+static inline void
+pixel_conv_ycbcr_to_rgb(byte *dest, byte *src)
+{
+ /* R = Y + 1.40200 * Cr
+ * G = Y - 0.34414 * Cb - 0.71414 * Cr
+ * B = Y + 1.77200 * Cb */
+ int y = src[0], cb = src[1] - 128, cr = src[2] - 128;
+ dest[0] = CLAMP(y + (91881 * cr) / 0x10000, 0, 255);
+ dest[1] = CLAMP(y - (22553 * cb + 46801 * cr) / 0x10000, 0, 255);
+ dest[2] = CLAMP(y + (116129 * cb) / 0x10000, 0, 255);
+}
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_ycbcr_n_to_rgb_n
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_DO_STEP do{ pixel_conv_ycbcr_to_rgb(walk_pos, walk_sec_pos); }while(0)
+#include "images/image-walk.h"
+
+static inline void
+pixel_conv_rgb_to_ycbcr(byte *dest, byte *src)
+{
+ /* Y = 0.29900 * R + 0.58700 * G + 0.11400 * B
+ * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTER
+ * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTER */
+ uns r = src[0], g = src[1], b = src[2];
+ dest[0] = (19595 * r + 38470 * g + 7471 * b) / 0x10000;
+ dest[1] = (0x800000 + 0x8000 * b - 11058 * r - 21710 * g) / 0x10000;
+ dest[2] = (0x800000 + 0x8000 * r - 27439 * g - 5329 * b) / 0x10000;
+}
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_rgb_n_to_ycbcr_n
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_DO_STEP do{ pixel_conv_rgb_to_ycbcr(walk_pos, walk_sec_pos); }while(0)
+#include "images/image-walk.h"
+
+/* CMYK <-> RGB */
+
+static inline void
+pixel_conv_cmyk_to_rgb(byte *dest, byte *src)
{
- switch (color->color_space)
+ uns d = (255 - src[3]) * (0xffffffffU / 255 /255);
+ dest[0] = d * (255 - src[0]) >> 24;
+ dest[1] = d * (255 - src[1]) >> 24;
+ dest[2] = d * (255 - src[2]) >> 24;
+}
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_cmyk_4_to_rgb_n
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_SEC_COL_STEP 4
+#define IMAGE_WALK_DO_STEP do{ pixel_conv_cmyk_to_rgb(walk_pos, walk_sec_pos); }while(0)
+#include "images/image-walk.h"
+
+static inline void
+pixel_conv_rgb_to_cmyk(byte *dest, byte *src)
+{
+ uns k = MAX(src[0], src[1]);
+ k = MAX(k, src[2]);
+ uns d = fast_div_u32_u8(0x7fffffffU, k); /* == 0 for zero K */
+ dest[0] = (d * (k - src[0])) >> 23;
+ dest[1] = (d * (k - src[1])) >> 23;
+ dest[2] = (d * (k - src[2])) >> 23;
+ dest[3] = 255 - k;
+}
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_rgb_n_to_cmyk_4
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_COL_STEP 4
+#define IMAGE_WALK_DO_STEP do{ pixel_conv_rgb_to_cmyk(walk_pos, walk_sec_pos); }while(0)
+#include "images/image-walk.h"
+
+/* YCCK <-> RGB */
+
+static inline void
+pixel_conv_ycck_to_rgb(byte *dest, byte *src)
+{
+ int y = src[0], cb = src[1] - 128, cr = src[2] - 128;
+ uns d = (255 - src[3]) * (0xffffffffU / 255 /255);
+ dest[0] = (d * CLAMP(y + (91881 * cr) / 0x10000, 0, 255) >> 24);
+ dest[1] = (d * CLAMP(y - (22553 * cb + 46801 * cr) / 0x10000, 0, 255) >> 24);
+ dest[2] = (d * CLAMP(y + (116129 * cb) / 0x10000, 0, 255) >> 24);
+}
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_ycck_4_to_rgb_n
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_SEC_COL_STEP 4
+#define IMAGE_WALK_DO_STEP do{ pixel_conv_ycck_to_rgb(walk_pos, walk_sec_pos); }while(0)
+#include "images/image-walk.h"
+
+static inline void
+pixel_conv_rgb_to_ycck(byte *dest, byte *src)
+{
+ uns k = MAX(src[0], src[1]);
+ k = MAX(k, src[2]);
+ uns d = fast_div_u32_u8(0x7fffffffU, k); /* == 0 for zero K */
+ uns r = 255 - ((d * (k - src[0])) >> 23);
+ uns g = 255 - ((d * (k - src[1])) >> 23);
+ uns b = 255 - ((d * (k - src[2])) >> 23);
+ dest[0] = (19595 * r + 38470 * g + 7471 * b) / 0x10000;
+ dest[1] = (0x800000 + 0x8000 * b - 11058 * r - 21710 * g) / 0x10000;
+ dest[2] = (0x800000 + 0x8000 * r - 27439 * g - 5329 * b) / 0x10000;
+ dest[3] = 255 - k;
+}
+
+#define IMAGE_WALK_PREFIX(x) walk_##x
+#define IMAGE_WALK_FUNC_NAME image_conv_rgb_n_to_ycck_4
+#define IMAGE_WALK_DOUBLE
+#define IMAGE_WALK_COL_STEP 4
+#define IMAGE_WALK_DO_STEP do{ pixel_conv_rgb_to_ycck(walk_pos, walk_sec_pos); }while(0)
+#include "images/image-walk.h"
+
+/* Main functions */
+
+static int
+image_conv_color_space(struct image_context *ctx UNUSED, struct image *dest, struct image *src, struct image_conv_options *opt UNUSED)
+{
+ switch (dest->flags & IMAGE_COLOR_SPACE)
{
case COLOR_SPACE_GRAYSCALE:
- dest[0] = dest[1] = dest[2] = color->c[0];
+ switch (src->flags & IMAGE_COLOR_SPACE)
+ {
+ case COLOR_SPACE_RGB:
+ if (dest->pixel_size == 1)
+ {
+ image_conv_rgb_n_to_gray_1(dest, src);
+ return 1;
+ }
+ break;
+ }
break;
case COLOR_SPACE_RGB:
- dest[0] = color->c[0];
- dest[1] = color->c[1];
- dest[2] = color->c[2];
+ switch (src->flags & IMAGE_CHANNELS_FORMAT)
+ {
+ case COLOR_SPACE_GRAYSCALE:
+ if (src->pixel_size == 1)
+ {
+ image_conv_gray_1_to_rgb_n(dest, src);
+ return 1;
+ }
+ break;
+ case COLOR_SPACE_YCBCR:
+ image_conv_ycbcr_n_to_rgb_n(dest, src);
+ return 1;
+ case COLOR_SPACE_CMYK:
+ if (src->pixel_size == 4)
+ {
+ image_conv_cmyk_4_to_rgb_n(dest, src);
+ return 1;
+ }
+ break;
+ case COLOR_SPACE_YCCK:
+ if (src->pixel_size == 4)
+ {
+ image_conv_ycck_4_to_rgb_n(dest, src);
+ return 1;
+ }
+ break;
+ }
+ break;
+ case COLOR_SPACE_YCBCR:
+ switch (src->flags & IMAGE_CHANNELS_FORMAT)
+ {
+ case COLOR_SPACE_RGB:
+ image_conv_rgb_n_to_ycbcr_n(dest, src);
+ return 1;
+ }
+ break;
+ case COLOR_SPACE_CMYK:
+ switch (src->flags & IMAGE_CHANNELS_FORMAT)
+ {
+ case COLOR_SPACE_RGB:
+ if (dest->pixel_size == 4)
+ {
+ image_conv_rgb_n_to_cmyk_4(dest, src);
+ return 1;
+ }
+ break;
+ }
+ break;
+ case COLOR_SPACE_YCCK:
+ switch (src->flags & IMAGE_CHANNELS_FORMAT)
+ {
+ case COLOR_SPACE_RGB:
+ if (dest->pixel_size == 4)
+ {
+ image_conv_rgb_n_to_ycck_4(dest, src);
+ return 1;
+ }
+ break;
+ }
break;
- default:
- ASSERT(0);
}
+ return 0;
}
-void
-color_put_color_space(byte *dest, struct color *color, enum color_space color_space)
+static void
+image_conv_copy(struct image *dest, struct image *src)
{
- switch (color_space)
+ if (dest->pixels == src->pixels)
+ return;
+ else if (dest->pixel_size != src->pixel_size)
{
- case COLOR_SPACE_GRAYSCALE:
- color_put_grayscale(dest, color);
- break;
- case COLOR_SPACE_RGB:
- color_put_rgb(dest, color);
+ uns channels = MIN(dest->channels, src->channels);
+ switch (channels)
+ {
+ case 1:
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_UNROLL 4
+# define IMAGE_WALK_DO_STEP do{ walk_pos[0] = walk_sec_pos[0]; }while(0)
+# include "images/image-walk.h"
+ }
+ return;
+ case 2:
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_UNROLL 4
+# define IMAGE_WALK_DO_STEP do{ walk_pos[0] = walk_sec_pos[0]; walk_pos[1] = walk_sec_pos[1]; }while(0)
+# include "images/image-walk.h"
+ return;
+ case 3:
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_UNROLL 2
+# define IMAGE_WALK_DO_STEP do{ walk_pos[0] = walk_sec_pos[0]; walk_pos[1] = walk_sec_pos[1]; walk_pos[2] = walk_sec_pos[2]; }while(0)
+# include "images/image-walk.h"
+ return;
+ case 4:
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_UNROLL 2
+# define IMAGE_WALK_DO_STEP do{ walk_pos[0] = walk_sec_pos[0]; walk_pos[1] = walk_sec_pos[1]; walk_pos[2] = walk_sec_pos[2]; walk_pos[3] = walk_sec_pos[3]; }while(0)
+# include "images/image-walk.h"
+ return;
+ default:
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_DO_STEP do{ for (uns i = 0; i < channels; i++) walk_pos[i] = walk_sec_pos[i]; }while(0)
+# include "images/image-walk.h"
+ return;
+ }
+ }
+ else if (dest->row_size != src->row_size || ((dest->flags | src->flags) & IMAGE_GAPS_PROTECTED))
+ {
+ byte *s = src->pixels;
+ byte *d = dest->pixels;
+ for (uns row = src->rows; row--; )
+ {
+ memcpy(d, s, src->row_pixels_size);
+ d += dest->row_size;
+ s += src->row_size;
+ }
+ }
+ else if (dest->pixels != src->pixels)
+ memcpy(dest->pixels, src->pixels, src->image_size);
+}
+
+static void
+image_conv_fill_alpha(struct image *dest)
+{
+ switch (dest->channels)
+ {
+ case 2:
+ if (dest->pixel_size == 2)
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_COL_STEP 2
+# define IMAGE_WALK_UNROLL 4
+# define IMAGE_WALK_DO_STEP do{ walk_pos[1] = 255; }while(0)
+# include "images/image-walk.h"
+ return;
+ }
+ break;
+ case 4:
+ if (dest->pixel_size == 4)
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_COL_STEP 4
+# define IMAGE_WALK_UNROLL 4
+# define IMAGE_WALK_DO_STEP do{ walk_pos[3] = 255; }while(0)
+# include "images/image-walk.h"
+ return;
+ }
break;
- default:
- ASSERT(0);
+ }
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_UNROLL 4
+# define IMAGE_WALK_DO_STEP do{ walk_pos[dest->channels - 1] = 255; }while(0)
+# include "images/image-walk.h"
+ }
+}
+
+static void
+image_conv_copy_alpha(struct image *dest, struct image *src)
+{
+ if (dest->pixels != src->pixels || dest->channels != src->channels)
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_UNROLL 4
+# define IMAGE_WALK_DO_STEP do{ walk_pos[dest->channels - 1] = walk_sec_pos[src->channels - 1]; }while(0)
+# include "images/image-walk.h"
}
}
+static inline uns
+image_conv_alpha_func(uns value, uns alpha, uns acoef, uns bcoef)
+{
+ return ((uns)(acoef + (int)alpha * (int)(value - bcoef)) * (0xffffffffU / 255 / 255)) >> 24;
+}
+
+static int
+image_conv_apply_alpha_from(struct image_context *ctx, struct image *dest, struct image *src, struct image_conv_options *opt)
+{
+ if (!opt->background.color_space)
+ return 1;
+ byte background[IMAGE_MAX_CHANNELS];
+ if (unlikely(!color_put(ctx, &opt->background, background, dest->flags & IMAGE_COLOR_SPACE)))
+ return 0;
+ uns a[IMAGE_MAX_CHANNELS], b[IMAGE_MAX_CHANNELS];
+ for (uns i = 0; i < dest->channels; i++)
+ a[i] = 255 * (b[i] = background[i]);
+ switch (dest->channels)
+ {
+ case 1:
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_UNROLL 2
+# define IMAGE_WALK_DO_STEP do{ \
+ walk_pos[0] = image_conv_alpha_func(walk_pos[0], walk_sec_pos[src->channels - 1], a[0], b[0]); }while(0)
+# include "images/image-walk.h"
+ }
+ return 1;
+ case 3:
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_DO_STEP do{ \
+ walk_pos[0] = image_conv_alpha_func(walk_pos[0], walk_sec_pos[src->channels - 1], a[0], b[0]); \
+ walk_pos[1] = image_conv_alpha_func(walk_pos[1], walk_sec_pos[src->channels - 1], a[1], b[1]); \
+ walk_pos[2] = image_conv_alpha_func(walk_pos[2], walk_sec_pos[src->channels - 1], a[2], b[2]); }while(0)
+# include "images/image-walk.h"
+ }
+ return 1;
+ }
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_DO_STEP do{ for (uns i = 0; i < dest->channels; i++) \
+ walk_pos[i] = image_conv_alpha_func(walk_pos[i], walk_sec_pos[src->channels - 1], a[i], b[i]); }while(0)
+# include "images/image-walk.h"
+ }
+ return 1;
+}
+
+static int
+image_conv_apply_alpha_to(struct image_context *ctx, struct image *dest, struct image *src, struct image_conv_options *opt)
+{
+ if (!opt->background.color_space)
+ {
+ image_conv_copy(dest, src);
+ return 1;
+ }
+ byte background[IMAGE_MAX_CHANNELS];
+ if (unlikely(!color_put(ctx, &opt->background, background, dest->flags & IMAGE_COLOR_SPACE)))
+ return 0;
+ uns a[IMAGE_MAX_CHANNELS], b[IMAGE_MAX_CHANNELS];
+ for (uns i = 0; i < dest->channels; i++)
+ a[i] = 255 * (b[i] = background[i]);
+ switch (dest->channels)
+ {
+ case 1:
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_UNROLL 2
+# define IMAGE_WALK_DO_STEP do{ \
+ walk_pos[0] = image_conv_alpha_func(walk_sec_pos[0], walk_sec_pos[src->channels - 1], a[0], b[0]); }while(0)
+# include "images/image-walk.h"
+ }
+ return 1;
+ case 3:
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_DO_STEP do{ \
+ walk_pos[0] = image_conv_alpha_func(walk_sec_pos[0], walk_sec_pos[src->channels - 1], a[0], b[0]); \
+ walk_pos[1] = image_conv_alpha_func(walk_sec_pos[1], walk_sec_pos[src->channels - 1], a[1], b[1]); \
+ walk_pos[2] = image_conv_alpha_func(walk_sec_pos[2], walk_sec_pos[src->channels - 1], a[2], b[2]); }while(0)
+# include "images/image-walk.h"
+ }
+ return 1;
+ }
+ {
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_SEC_IMAGE src
+# define IMAGE_WALK_DOUBLE
+# define IMAGE_WALK_DO_STEP do{ for (uns i = 0; i < dest->channels; i++) \
+ walk_pos[i] = image_conv_alpha_func(walk_sec_pos[i], walk_sec_pos[src->channels - 1], a[i], b[i]); }while(0)
+# include "images/image-walk.h"
+ }
+ return 1;
+}
+
+int
+image_conv(struct image_context *ctx, struct image *dest, struct image *src, struct image_conv_options *opt)
+{
+ ASSERT(dest->cols == src->cols && dest->rows == src->rows);
+ if (!((dest->flags ^ src->flags) & IMAGE_COLOR_SPACE))
+ {
+ if (!(src->flags & IMAGE_ALPHA) || (dest->flags & IMAGE_ALPHA))
+ image_conv_copy(dest, src);
+ else if (unlikely(!image_conv_apply_alpha_to(ctx, dest, src, opt)))
+ return 0;
+ }
+ else
+ {
+ if (!(src->flags & IMAGE_ALPHA))
+ {
+ if (unlikely(!image_conv_color_space(ctx, dest, src, opt)))
+ goto error;
+ if ((dest->flags & IMAGE_ALPHA) && (opt->flags & IMAGE_CONV_FILL_ALPHA))
+ image_conv_fill_alpha(dest);
+ }
+ else
+ {
+ if (dest->flags & IMAGE_ALPHA)
+ {
+ if (dest->channels <= src->channels)
+ {
+ if (unlikely(!image_conv_color_space(ctx, dest, src, opt)))
+ goto error;
+ if (opt->flags & IMAGE_CONV_COPY_ALPHA)
+ image_conv_copy_alpha(dest, src);
+ else if (opt->flags & IMAGE_CONV_FILL_ALPHA)
+ image_conv_fill_alpha(dest);
+ }
+ else
+ {
+ if (opt->flags & IMAGE_CONV_COPY_ALPHA)
+ image_conv_copy_alpha(dest, src);
+ else
+ image_conv_fill_alpha(dest);
+ if (unlikely(!image_conv_color_space(ctx, dest, src, opt)))
+ goto error;
+ }
+ }
+ else
+ {
+ if (dest->channels <= src->channels)
+ {
+ if (unlikely(!image_conv_color_space(ctx, dest, src, opt)))
+ goto error;
+ if (unlikely(!image_conv_apply_alpha_from(ctx, dest, src, opt)))
+ return 0;
+ }
+ else
+ {
+ if (unlikely(!image_conv_apply_alpha_to(ctx, dest, src, opt)))
+ return 0;
+ if (unlikely(!image_conv_color_space(ctx, dest, dest, opt)))
+ goto error;
+ }
+ }
+ }
+ }
+ return 1;
+error:
+ IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Image conversion not supported for such pixel formats");
+ return 0;
+}
+
/********************* EXACT CONVERSION ROUTINES **********************/
-/* sRGB to XYZ */
-void
-srgb_to_xyz_slow(double xyz[3], double srgb[3])
+/* Reference whites */
+#define COLOR_ILLUMINANT_A 0.44757, 0.40744
+#define COLOR_ILLUMINANT_B 0.34840, 0.35160
+#define COLOR_ILLUMINANT_C 0.31006, 0.31615
+#define COLOR_ILLUMINANT_D50 0.34567, 0.35850
+#define COLOR_ILLUMINANT_D55 0.33242, 0.34743
+#define COLOR_ILLUMINANT_D65 0.31273, 0.32902
+#define COLOR_ILLUMINANT_D75 0.29902, 0.31485
+#define COLOR_ILLUMINANT_9300K 0.28480, 0.29320
+#define COLOR_ILLUMINANT_E (1./3.), (1./3.)
+#define COLOR_ILLUMINANT_F2 0.37207, 0.37512
+#define COLOR_ILLUMINANT_F7 0.31285, 0.32918
+#define COLOR_ILLUMINANT_F11 0.38054, 0.37691
+
+const double
+ color_illuminant_d50[2] = {COLOR_ILLUMINANT_D50},
+ color_illuminant_d65[2] = {COLOR_ILLUMINANT_D65},
+ color_illuminant_e[2] = {COLOR_ILLUMINANT_E};
+
+/* RGB profiles (many missing) */
+const struct color_space_info
+ color_adobe_rgb_info = {"Adobe RGB", {{0.6400, 0.3300}, {0.2100, 0.7100}, {0.1500, 0.0600}, {COLOR_ILLUMINANT_D65}}, {0.45, 0.45, 0, 0, 0}},
+ color_apple_rgb_info = {"Apple RGB", {{0.6250, 0.3400}, {0.2800, 0.5950}, {0.1550, 0.0700}, {COLOR_ILLUMINANT_D65}}, {0.56, 0.56, 0, 0, 0}},
+ color_cie_rgb_info = {"CIE RGB", {{0.7350, 0.2650}, {0.2740, 0.7170}, {0.1670, 0.0090}, {COLOR_ILLUMINANT_E}}, {0.45, 0.45, 0, 0, 0}},
+ color_color_match_rgb_info = {"ColorMatch RGB", {{0.6300, 0.3400}, {0.2950, 0.6050}, {0.1500, 0.0750}, {COLOR_ILLUMINANT_D50}}, {0.56, 0.56, 0, 0, 0}},
+ color_srgb_info = {"sRGB", {{0.6400, 0.3300}, {0.3000, 0.6000}, {0.1500, 0.0600}, {COLOR_ILLUMINANT_D65}}, {0.45, 0.42, 0.055, 0.003, 12.92}};
+
+#define CLIP(x, min, max) (((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x))
+
+static inline void
+clip(double a[3])
+{
+ a[0] = CLIP(a[0], 0, 1);
+ a[1] = CLIP(a[1], 0, 1);
+ a[2] = CLIP(a[2], 0, 1);
+}
+
+static inline void
+correct_gamma_simple(double dest[3], double src[3], const struct color_space_gamma_info *info)
+{
+ dest[0] = pow(src[0], info->simple_gamma);
+ dest[1] = pow(src[1], info->simple_gamma);
+ dest[2] = pow(src[2], info->simple_gamma);
+}
+
+static inline void
+invert_gamma_simple(double dest[3], double src[3], const struct color_space_gamma_info *info)
+{
+ dest[0] = pow(src[0], 1 / info->simple_gamma);
+ dest[1] = pow(src[1], 1 / info->simple_gamma);
+ dest[2] = pow(src[2], 1 / info->simple_gamma);
+}
+
+static inline void
+correct_gamma_detailed(double dest[3], double src[3], const struct color_space_gamma_info *info)
{
- double a[3];
for (uns i = 0; i < 3; i++)
- if (srgb[i] > 0.04045)
- a[i] = pow((srgb[i] + 0.055) * (1 / 1.055), 2.4);
+ if (src[i] > info->transition)
+ dest[i] = (1 + info->offset) * pow(src[i], info->detailed_gamma) - info->offset;
else
- a[i] = srgb[i] * (1 / 12.92);
- xyz[0] = SRGB_XYZ_XR * a[0] + SRGB_XYZ_XG * a[1] + SRGB_XYZ_XB * a[2];
- xyz[1] = SRGB_XYZ_YR * a[0] + SRGB_XYZ_YG * a[1] + SRGB_XYZ_YB * a[2];
- xyz[2] = SRGB_XYZ_ZR * a[0] + SRGB_XYZ_ZG * a[1] + SRGB_XYZ_ZB * a[2];
+ dest[i] = info->slope * src[i];
}
-/* XYZ to sRGB */
-void
-xyz_to_srgb_slow(double srgb[3], double xyz[3])
+static inline void
+invert_gamma_detailed(double dest[3], double src[3], const struct color_space_gamma_info *info)
{
- double a[3];
- a[0] = 3.2406 * xyz[0] + -1.5372 * xyz[1] + -0.4986 * xyz[2];
- a[1] = -0.9689 * xyz[0] + 1.8758 * xyz[1] + 0.0415 * xyz[2];
- a[2] = 0.0557 * xyz[0] + -0.2040 * xyz[1] + 1.0570 * xyz[2];
for (uns i = 0; i < 3; i++)
- if (a[i] > 0.0031308)
- srgb[i] = 1.055 * pow(a[i], 1 / 2.4) - 0.055;
+ if (src[i] > info->transition * info->slope)
+ dest[i] = pow((src[i] + info->offset) / (1 + info->offset), 1 / info->detailed_gamma);
else
- srgb[i] = 12.92 * a[i];
+ dest[i] = src[i] / info->slope;
+}
+
+static inline void
+apply_matrix(double dest[3], double src[3], double matrix[9])
+{
+ dest[0] = src[0] * matrix[0] + src[1] * matrix[1] + src[2] * matrix[2];
+ dest[1] = src[0] * matrix[3] + src[1] * matrix[4] + src[2] * matrix[5];
+ dest[2] = src[0] * matrix[6] + src[1] * matrix[7] + src[2] * matrix[8];
+}
+
+void
+color_invert_matrix(double dest[9], double matrix[9])
+{
+ double *i = dest, *m = matrix;
+ double a0 = m[4] * m[8] - m[5] * m[7];
+ double a1 = m[3] * m[8] - m[5] * m[6];
+ double a2 = m[3] * m[7] - m[4] * m[6];
+ double d = 1 / (m[0] * a0 - m[1] * a1 + m[2] * a2);
+ i[0] = d * a0;
+ i[3] = -d * a1;
+ i[6] = d * a2;
+ i[1] = -d * (m[1] * m[8] - m[2] * m[7]);
+ i[4] = d * (m[0] * m[8] - m[2] * m[6]);
+ i[7] = -d * (m[0] * m[7] - m[1] * m[6]);
+ i[2] = d * (m[1] * m[5] - m[2] * m[4]);
+ i[5] = -d * (m[0] * m[5] - m[2] * m[3]);
+ i[8] = d * (m[0] * m[4] - m[1] * m[3]);
+}
+
+static void
+mul_matrices(double r[9], double a[9], double b[9])
+{
+ r[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
+ r[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
+ r[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
+ r[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
+ r[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
+ r[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
+ r[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
+ r[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
+ r[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
+}
+
+/* computes conversion matrix from a given color space to CIE XYZ */
+void
+color_compute_color_space_to_xyz_matrix(double matrix[9], const struct color_space_chromacity_info *space)
+{
+ double wX = space->white[0] / space->white[1];
+ double wZ = (1 - space->white[0] - space->white[1]) / space->white[1];
+ double a[9], b[9];
+ a[0] = space->prim1[0]; a[3] = space->prim1[1]; a[6] = 1 - a[0] - a[3];
+ a[1] = space->prim2[0]; a[4] = space->prim2[1]; a[7] = 1 - a[1] - a[4];
+ a[2] = space->prim3[0]; a[5] = space->prim3[1]; a[8] = 1 - a[2] - a[5];
+ color_invert_matrix(b, a);
+ double ra = wX * b[0] + b[1] + wZ * b[2];
+ double rb = wX * b[3] + b[4] + wZ * b[5];
+ double rc = wX * b[6] + b[7] + wZ * b[8];
+ matrix[0] = a[0] * ra;
+ matrix[1] = a[1] * rb;
+ matrix[2] = a[2] * rc;
+ matrix[3] = a[3] * ra;
+ matrix[4] = a[4] * rb;
+ matrix[5] = a[5] * rc;
+ matrix[6] = a[6] * ra;
+ matrix[7] = a[7] * rb;
+ matrix[8] = a[8] * rc;
+}
+
+/* computes matrix to join transformations with different reference whites */
+void
+color_compute_bradford_matrix(double matrix[9], const double source[2], const double dest[2])
+{
+ /* cone response matrix and its inversion */
+ static double r[9] = {
+ 0.8951, 0.2664, -0.1614,
+ -0.7502, 1.7135, 0.0367,
+ 0.0389, -0.0685, 1.0296};
+ //static double i[9] = {0.9870, -0.1471, 0.1600, 0.4323, 0.5184, 0.0493, -0.0085, 0.0400, 0.9685};
+ double i[9];
+ color_invert_matrix(i, r);
+ double aX = source[0] / source[1];
+ double aZ = (1 - source[0] - source[1]) / source[1];
+ double bX = dest[0] / dest[1];
+ double bZ = (1 - dest[0] - dest[1]) / dest[1];
+ double x = (r[0] * bX + r[1] + r[2] * bZ) / (r[0] * aX + r[1] + r[2] * aZ);
+ double y = (r[3] * bX + r[4] + r[5] * bZ) / (r[3] * aX + r[4] + r[5] * aZ);
+ double z = (r[6] * bX + r[7] + r[8] * bZ) / (r[6] * aX + r[7] + r[8] * aZ);
+ double m[9];
+ m[0] = i[0] * x; m[1] = i[1] * y; m[2] = i[2] * z;
+ m[3] = i[3] * x; m[4] = i[4] * y; m[5] = i[5] * z;
+ m[6] = i[6] * x; m[7] = i[7] * y; m[8] = i[8] * z;
+ mul_matrices(matrix, m, r);
+}
+
+void
+color_compute_color_spaces_conversion_matrix(double matrix[9], const struct color_space_chromacity_info *src, const struct color_space_chromacity_info *dest)
+{
+ double a_to_xyz[9], b_to_xyz[9], xyz_to_b[9], bradford[9], m[9];
+ color_compute_color_space_to_xyz_matrix(a_to_xyz, src);
+ color_compute_color_space_to_xyz_matrix(b_to_xyz, dest);
+ color_invert_matrix(xyz_to_b, b_to_xyz);
+ if (src->white[0] == dest->white[0] && src->white[1] == dest->white[1])
+ mul_matrices(matrix, a_to_xyz, xyz_to_b);
+ else
+ {
+ color_compute_bradford_matrix(bradford, src->white, dest->white);
+ mul_matrices(m, a_to_xyz, bradford);
+ mul_matrices(matrix, m, xyz_to_b);
+ }
+}
+
+/* sRGB to XYZ */
+void
+srgb_to_xyz_exact(double xyz[3], double srgb[3])
+{
+ static double matrix[9] = {
+ 0.41248031, 0.35756952, 0.18043951,
+ 0.21268516, 0.71513904, 0.07217580,
+ 0.01933501, 0.11918984, 0.95031473};
+ double srgb_lin[3];
+ invert_gamma_detailed(srgb_lin, srgb, &color_srgb_info.gamma);
+ apply_matrix(xyz, srgb_lin, matrix);
+ xyz_to_srgb_exact(srgb_lin, xyz);
+}
+
+/* XYZ to sRGB */
+void
+xyz_to_srgb_exact(double srgb[3], double xyz[3])
+{
+ static double matrix[9] = {
+ 3.24026666, -1.53704957, -0.49850256,
+ -0.96928381, 1.87604525, 0.04155678,
+ 0.05564281, -0.20402363, 1.05721334};
+ double srgb_lin[3];
+ apply_matrix(srgb_lin, xyz, matrix);
+ clip(srgb_lin);
+ correct_gamma_detailed(srgb, srgb_lin, &color_srgb_info.gamma);
}
/* XYZ to CIE-Luv */
void
-xyz_to_luv_slow(double luv[3], double xyz[3])
+xyz_to_luv_exact(double luv[3], double xyz[3])
{
double sum = xyz[0] + 15 * xyz[1] + 3 * xyz[2];
if (sum < 0.000001)
/* CIE-Luv to XYZ */
void
-luv_to_xyz_slow(double xyz[3], double luv[3])
+luv_to_xyz_exact(double xyz[3], double luv[3])
{
double var_u = luv[1] / (13 * luv[0]) + (4 * REF_WHITE_X / (REF_WHITE_X + 15 * REF_WHITE_Y + 3 * REF_WHITE_Z));
double var_v = luv[2] / (13 * luv[0]) + (9 * REF_WHITE_Y / (REF_WHITE_X + 15 * REF_WHITE_Y + 3 * REF_WHITE_Z));
xyz[2] = (9 * xyz[1] - 15 * var_v * xyz[1] - var_v * xyz[0]) / (3 * var_v);
}
+/* RGB to CMYK - a very simple version, not too accureate */
+void
+rgb_to_cmyk_exact(double cmyk[4], double rgb[3])
+{
+ cmyk[0] = 1 - rgb[0];
+ cmyk[1] = 1 - rgb[1];
+ cmyk[2] = 1 - rgb[2];
+ cmyk[3] = MIN(cmyk[0], cmyk[1]);
+ cmyk[3] = MIN(cmyk[3], cmyk[2]);
+ if (cmyk[3] > 0.9999)
+ {
+ cmyk[3] = 1;
+ cmyk[0] = cmyk[1] = cmyk[2] = 0;
+ }
+ else
+ {
+ double d = 1 / (1 - cmyk[3]);
+ for (uns i = 0; i < 3; i++)
+ cmyk[i] = d * (cmyk[i] - cmyk[3]);
+ }
+}
+
+/* CMYK to RGB */
+void
+cmyk_to_rgb_exact(double rgb[3], double cmyk[4])
+{
+ double d = 1 - cmyk[1];
+ for (uns i = 0; i < 3; i++)
+ rgb[i] = d * (1 - cmyk[i]);
+}
/***************** OPTIMIZED SRGB -> LUV CONVERSION *********************/
for (p[0] = 0; p[0] < (1 << COLOR_CONV_OFS); p[0]++)
{
uns index;
- static const uns tetrahedrons[5][4] = {
+ static const uns tetrahedra[5][4] = {
{0000, 0001, 0010, 0100},
{0110, 0111, 0100, 0010},
{0101, 0100, 0111, 0001},
index = 3;
else
index = 4;
- interpolate_tetrahedron(n, p, tetrahedrons[index]);
+ interpolate_tetrahedron(n, p, tetrahedra[index]);
n++;
}
}
srgb[0] = src[0] / 255.;
srgb[1] = src[1] / 255.;
srgb[2] = src[2] / 255.;
- srgb_to_xyz_slow(xyz, srgb);
- xyz_to_luv_slow(luv, xyz);
+ srgb_to_xyz_exact(xyz, srgb);
+ xyz_to_luv_exact(luv, xyz);
dest[0] = luv[0] * 2.55;
dest[1] = luv[1] * (2.55 / 4) + 128;
dest[2] = luv[2] * (2.55 / 4) + 128;
byte *a = xmalloc(3 * CNT), *b = xmalloc(3 * CNT);
for (uns i = 0; i < 3 * CNT; i++)
a[i] = random_max(256);
- init_timer();
+ timestamp_t timer;
+ init_timer(&timer);
for (uns i = 0; i < TESTS; i++)
memcpy(b, a, CNT * 3);
- DBG("memcpy time=%d", (uns)get_timer());
- init_timer();
+ DBG("memcpy time=%d", get_timer(&timer));
+ init_timer(&timer);
for (uns i = 0; i < TESTS; i++)
srgb_to_luv_pixels(b, a, CNT);
- DBG("direct time=%d", (uns)get_timer());
- init_timer();
+ DBG("direct time=%d", get_timer(&timer));
+ init_timer(&timer);
for (uns i = 0; i < TESTS; i++)
color_conv_pixels(b, a, CNT, srgb_to_luv_grid);
- DBG("grid time=%d", (uns)get_timer());
+ DBG("grid time=%d", get_timer(&timer));
#endif
return 0;
}