]> mj.ucw.cz Git - libucw.git/commitdiff
Partial merge with dev-img - added libimages.
authorPavel Charvat <pavel.charvat@netcentrum.cz>
Thu, 19 Oct 2006 08:13:19 +0000 (10:13 +0200)
committerPavel Charvat <pavel.charvat@netcentrum.cz>
Thu, 19 Oct 2006 08:13:19 +0000 (10:13 +0200)
42 files changed:
cf/images [new file with mode: 0644]
images/Makefile [new file with mode: 0644]
images/alpha.c [new file with mode: 0644]
images/color-tool.c [new file with mode: 0644]
images/color.c [new file with mode: 0644]
images/color.h [new file with mode: 0644]
images/color.t [new file with mode: 0644]
images/config.c [new file with mode: 0644]
images/context.c [new file with mode: 0644]
images/dup-cmp.c [new file with mode: 0644]
images/dup-init.c [new file with mode: 0644]
images/duplicates.h [new file with mode: 0644]
images/error.h [new file with mode: 0644]
images/hilbert-test.c [new file with mode: 0644]
images/hilbert-test.t [new file with mode: 0644]
images/hilbert.h [new file with mode: 0644]
images/image-dup-test.c [new file with mode: 0644]
images/image-sim-test.c [new file with mode: 0644]
images/image-test.c [new file with mode: 0644]
images/image-tool.c [new file with mode: 0644]
images/image-walk.h [new file with mode: 0644]
images/image.c [new file with mode: 0644]
images/images.h [new file with mode: 0644]
images/io-libjpeg.c [new file with mode: 0644]
images/io-libmagick.c [new file with mode: 0644]
images/io-libpng.c [new file with mode: 0644]
images/io-libungif.c [new file with mode: 0644]
images/io-main.c [new file with mode: 0644]
images/io-main.h [new file with mode: 0644]
images/math.c [new file with mode: 0644]
images/math.h [new file with mode: 0644]
images/object.c [new file with mode: 0644]
images/object.h [new file with mode: 0644]
images/scale-gen.h [new file with mode: 0644]
images/scale.c [new file with mode: 0644]
images/sig-cmp-gen.h [new file with mode: 0644]
images/sig-cmp.c [new file with mode: 0644]
images/sig-dump.c [new file with mode: 0644]
images/sig-init.c [new file with mode: 0644]
images/sig-seg.c [new file with mode: 0644]
images/sig-txt.c [new file with mode: 0644]
images/signature.h [new file with mode: 0644]

diff --git a/cf/images b/cf/images
new file mode 100644 (file)
index 0000000..0f4a331
--- /dev/null
+++ b/cf/images
@@ -0,0 +1,88 @@
+# Configuration of the image library (included by cf/sherlock)
+
+######## General parameters #####################################################
+
+ImageLib {
+
+# Default tracing level (0 to disable)
+Trace                  0
+
+# Limits for image allocation
+ImageMaxDim            0xffff          # Maximum width/height (at most 64k-1)
+ImageMaxBytes          256M            # Maximum size in bytes
+
+}
+
+######## Image signatures #######################################################
+
+ImageSig {
+
+# To find similar images, Sherlock uses comparison based on regions.
+# First of all, the imagesim analyser extracts various region features.
+# Sets of these features are called "image signatures" and they are stored
+# in the `H' attribute of image objects.
+
+# Signatures are later processed by the indexer to build an effective
+# search structure finally used by the search server. See Indexer and Search
+# sections for more options.
+
+# Minimum image size to apply segmentation. Smaller images are always
+# compared by the simple "average" method (see ImageSig.CompareMethod).
+MinWidth               16
+MinHeight              16
+
+# List of subdivision thresholds in the first phase of segmentation.
+# Lower the values to increase the average number of regions and vice versa.
+PreQuantThresholds     6 12 15 20 25 25 30 30 40 40 50 50 60 60 60
+
+# Settings for the second phase of segmentation -- usually not so important.
+# We use an iterative algorithm to improve the average error from the first phase.
+# We stop the process after PostQuantMaxSteps or if we get only PostQuantThreshold
+# percentual improvement over the previous step.
+PostQuantMinSteps      2
+PostQuantMaxSteps      10
+PostQuantThreshold     1
+
+# BorderBonus and BorderSize parameters can increase or decrease the weight
+# of image pixels near the borders. Weight of all pixels more than BorderSize * MIN(cols, rows)
+# pixels far from the edges is 128. Then this value continuously decreses/increses up to 128+BorderSize.
+BorderSize             0.4
+BorderBonus            -50
+
+# Scaling constants for computation of normalized i-th order inertia features (I1, I2, I3).
+InertiaScale           2 0.5 0.05
+
+# Threshold for detecting textured images (see images/sig-txt.c for details).
+# Decrease the threshold if you want less detected textures,
+# set it to zero to disable the algorithm completely.
+TexturedThreshold      0.32
+
+# Signature comparison method:
+#
+#   integrated
+#      based on: James Z. Wang, Jia Li and Gio Wiederhold,
+#      "SIMPLIcity: Semantics-Sensitive Integrated Matching for Picture Libraries",
+#      IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 23, no. 9, pp. 947-963, 2001.
+#
+#   fuzzy (unstable and unbalanced parameters)
+#      based on: Yixin Chen and James Z. Wang,
+#      "A Region-Based Fuzzy Feature Matching Approach to Content-Based Image Retrieval",
+#      IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 24, no. 9, pp. 1252-1267, 2002.
+#
+#   average
+#      Simple distance of image features averages (ignores segmentation).
+#
+CompareMethod          integrated
+
+# Array of multiplicative constants in feature vector distance computation
+# (L, u, v, LH, HL, HH, I1, I2, I3, X, Y). Each one must be an integer in range 0..15, default is 4.
+CompareFeaturesWeights 4 6 6 4 4 4 4 4 4 4 4
+
+}
+
+######## Duplicate finder #######################################################
+
+ImageDup {
+# Detection of image duplicates does not work yet.
+}
+
diff --git a/images/Makefile b/images/Makefile
new file mode 100644 (file)
index 0000000..0b4b27d
--- /dev/null
@@ -0,0 +1,77 @@
+# Testing dir... code will be moved somewhere else... maybe to trash :-)
+
+DIRS+=images
+
+PROGS+=$(o)/images/image-tool $(o)/images/color-tool
+CONFIGS+=images
+LIBIMAGES_MODS=math config context image scale color alpha io-main object
+
+ifdef CONFIG_IMAGES_DUP
+PROGS+=$(o)/images/image-dup-test
+LIBIMAGES_MODS+=dup-init dup-cmp
+endif
+ifdef CONFIG_IMAGES_SIM
+PROGS+=$(o)/images/image-sim-test
+LIBIMAGES_MODS+=sig-cmp
+endif
+ifneq ($(CONFIG_IMAGES_DUP)$(CONFIG_IMAGES_SIM),)
+LIBIMAGES_MODS+=sig-dump sig-init sig-seg sig-txt
+endif
+
+LIBIMAGES_LIBS=-lm -lpthread
+
+ifdef CONFIG_IMAGES_LIBJPEG
+LIBIMAGES_MODS+=io-libjpeg
+LIBIMAGES_LIBS+=-ljpeg
+endif
+
+ifdef CONFIG_IMAGES_LIBPNG
+LIBIMAGES_MODS+=io-libpng
+LIBIMAGES_LIBS+=-lpng
+endif
+
+ifdef CONFIG_IMAGES_LIBUNGIF
+LIBIMAGES_MODS+=io-libungif
+LIBIMAGES_LIBS+=-lungif
+else
+ifdef CONFIG_IMAGES_LIBGIF
+LIBIMAGES_MODS+=io-libungif
+LIBIMAGES_LIBS+=-lgif
+endif
+endif
+
+ifdef CONFIG_IMAGES_LIBMAGICK
+LIBIMAGES_MODS+=io-libmagick
+MAGICK_LIBS:=$(shell GraphicsMagick-config --libs)
+MAGICK_CPPFLAGS:=$(shell GraphicsMagick-config --cppflags)
+LIBIMAGES_LIBS+=$(MAGICK_LIBS)
+$(o)/images/io-libmagick.o: CFLAGS+=$(MAGICK_CPPFLAGS)
+endif
+
+$(o)/images/libimages.a: $(addsuffix .o,$(addprefix $(o)/images/,$(LIBIMAGES_MODS)))
+$(o)/images/libimages.so: $(addsuffix .oo,$(addprefix $(o)/images/,$(LIBIMAGES_MODS)))
+
+$(o)/images/image-tool: $(o)/images/image-tool.o $(LIBIMAGES) $(LIBUCW)
+$(o)/images/image-tool: LIBS+=$(LIBIMAGES_LIBS)
+
+$(o)/images/color-tool: $(o)/images/color-tool.o $(LIBIMAGES) $(LIBUCW)
+$(o)/images/color-tool: LIBS+=$(LIBIMAGES_LIBS)
+
+$(o)/images/image-dup-test: $(o)/images/image-dup-test.o $(LIBIMAGES) $(LIBUCW)
+$(o)/images/image-dup-test: LIBS+=$(LIBIMAGES_LIBS)
+
+$(o)/images/image-sim-test: $(o)/images/image-sim-test.o $(LIBIMAGES) $(LIBUCW)
+$(o)/images/image-sim-test: LIBS+=$(LIBIMAGES_LIBS)
+
+TESTS+=$(o)/images/image-test.test
+$(o)/images/image-test: $(o)/images/image-test.o $(LIBIMAGES) $(LIBUCW)
+$(o)/images/image-test: LIBS+=$(LIBIMAGES_LIBS)
+$(o)/images/image-test.test: $(o)/images/image-test
+
+TESTS+=$(o)/images/hilbert-test.test
+$(o)/images/hilbert-test: LIBS+=-lm $(LIBSH)
+$(o)/images/hilbert-test.test: $(o)/images/hilbert-test
+
+TESTS+=$(o)/images/color.test
+$(o)/images/color-t: LIBS+=-lm
+$(o)/images/color.test: $(o)/images/color-t
diff --git a/images/alpha.c b/images/alpha.c
new file mode 100644 (file)
index 0000000..d97207e
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ *     Image Library -- Alpha channels
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "images/images.h"
+#include "images/color.h"
+
+static inline uns
+merge_func(uns value, uns alpha, uns acoef, uns bcoef)
+{
+  return ((uns)(acoef + (int)alpha * (int)(value - bcoef)) * (0xffffffffU / 255 / 255)) >> 24;
+}
+
+int
+image_apply_background(struct image_context *ctx UNUSED, struct image *dest, struct image *src, struct color *background)
+{
+  DBG("image_apply_background()");
+
+  /* Grayscale */
+  if (src->pixel_size == 2)
+    {
+      ASSERT(dest->pixel_size == 1);
+      byte bg;
+      if (background->color_space)
+        color_put_grayscale(&bg, background);
+      else
+       bg = 0;
+      uns a = 255 * bg, b = bg;
+#     define IMAGE_WALK_PREFIX(x) walk_##x
+#     define IMAGE_WALK_INLINE
+#     define IMAGE_WALK_DOUBLE
+#     define IMAGE_WALK_UNROLL 4
+#     define IMAGE_WALK_IMAGE dest
+#     define IMAGE_WALK_SEC_IMAGE src
+#     define IMAGE_WALK_COL_STEP 1
+#     define IMAGE_WALK_SEC_COL_STEP 2
+#     define IMAGE_WALK_DO_STEP do{ walk_pos[0] = merge_func(walk_sec_pos[0], walk_sec_pos[1], a, b); }while(0)
+#     include "images/image-walk.h"
+    }
+
+  /* RGBA to RGB or aligned RGB */
+  else if (src->pixel_size == 4)
+    {
+      ASSERT((src->flags & IMAGE_ALPHA) && dest->pixel_size >= 3 && !(dest->flags & IMAGE_ALPHA));
+      byte bg[3];
+      if (background->color_space)
+        color_put_rgb(bg, background);
+      else
+       bg[0] = bg[1] = bg[2] = 0;
+      uns a0 = 255 * bg[0], b0 = bg[0];
+      uns a1 = 255 * bg[1], b1 = bg[1];
+      uns a2 = 255 * bg[2], b2 = bg[2];
+#     define IMAGE_WALK_PREFIX(x) walk_##x
+#     define IMAGE_WALK_INLINE
+#     define IMAGE_WALK_DOUBLE
+#     define IMAGE_WALK_UNROLL 2
+#     define IMAGE_WALK_IMAGE dest
+#     define IMAGE_WALK_SEC_IMAGE src
+#     define IMAGE_WALK_SEC_COL_STEP 4
+#     define IMAGE_WALK_DO_STEP do{ \
+         walk_pos[0] = merge_func(walk_sec_pos[0], walk_sec_pos[3], a0, b0); \
+         walk_pos[1] = merge_func(walk_sec_pos[1], walk_sec_pos[3], a1, b1); \
+         walk_pos[2] = merge_func(walk_sec_pos[2], walk_sec_pos[3], a2, b2); \
+       }while(0)
+#     include "images/image-walk.h"
+    }
+  else
+    ASSERT(0);
+  return 1;
+}
diff --git a/images/color-tool.c b/images/color-tool.c
new file mode 100644 (file)
index 0000000..112f738
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ *     Color spaces tool
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU General Public License.
+ */
+
+#include "lib/lib.h"
+#include "images/images.h"
+#include "images/color.h"
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+
+static void NONRET
+usage(void)
+{
+  fputs("\
+Usage: color-tool input-color-space output-color-space\n\
+", stderr);
+  exit(1);
+}
+
+static char *shortopts = "";
+static struct option longopts[] =
+{
+  { NULL,                      0, 0, 0 }
+};
+
+static const struct color_space_info *
+parse_color_space(byte *s)
+{
+  if (!strcasecmp(s, "sRGB"))
+    return &color_srgb_info;
+  else if (!strcasecmp(s, "AdobeRGB") || !strcasecmp(s, "Adobe RGB"))
+    return &color_adobe_rgb_info;
+  else if (!strcasecmp(s, "CIERGB") || strcasecmp(s, "CIE RGB"))
+    return &color_cie_rgb_info;
+  else
+    die("Unknown color space");
+}
+
+static void
+print_matrix(double m[9])
+{
+  for (uns j = 0; j < 3; j++)
+    {
+      for (uns i = 0; i < 3; i++)
+       printf(" %12.8f", m[i + j * 3]);
+      printf("\n");
+    }
+}
+
+int
+main(int argc, char **argv)
+{
+  log_init(argv[0]);
+  int opt;
+  while ((opt = getopt_long(argc, argv, shortopts, longopts, NULL)) >= 0)
+    switch (opt)
+      {
+       default:
+         usage();
+      }
+
+  if (argc == optind + 1)
+    {
+      const struct color_space_info *a = parse_color_space(argv[optind]);
+      double a_to_xyz[9], xyz_to_a[9];
+      color_compute_color_space_to_xyz_matrix(a_to_xyz, &a->chromacity);
+      color_invert_matrix(xyz_to_a, a_to_xyz);
+      printf("linear %s -> XYZ:\n", a->name);
+      print_matrix(a_to_xyz);
+      printf("XYZ -> linear %s:\n", a->name);
+      print_matrix(xyz_to_a);
+      printf("Simple gamma: %.8f\n", a->gamma.simple_gamma);
+      printf("Detailed gamma: g=%.8f o=%.8f t=%.8f s=%.8f\n", a->gamma.detailed_gamma, a->gamma.offset, a->gamma.transition, a->gamma.slope);
+    }
+  else if (argc == optind + 2)
+    {
+      const struct color_space_info *a = parse_color_space(argv[optind++]);
+      const struct color_space_info *b = parse_color_space(argv[optind]);
+      double a_to_b[9];
+      color_compute_color_spaces_conversion_matrix(a_to_b, &a->chromacity, &b->chromacity);
+      printf("linear %s -> linear %s:\n", a->name, b->name);
+      print_matrix(a_to_b);
+    }
+  else
+    usage();
+
+  return 0;
+}
diff --git a/images/color.c b/images/color.c
new file mode 100644 (file)
index 0000000..8ea6022
--- /dev/null
@@ -0,0 +1,618 @@
+/*
+ *     Image Library -- Color Spaces
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "sherlock/sherlock.h"
+#include "lib/math.h"
+#include "images/images.h"
+#include "images/color.h"
+
+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)
+{
+  switch (color->color_space)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       dest[0] = color->c[0];
+       break;
+      case COLOR_SPACE_RGB:
+       dest[0] = rgb_to_gray_func(color->c[0], color->c[1], color->c[2]);
+       break;
+      default:
+       ASSERT(0);
+    }
+}
+
+inline void
+color_put_rgb(byte *dest, struct color *color)
+{
+  switch (color->color_space)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       dest[0] = dest[1] = dest[2] = color->c[0];
+       break;
+      case COLOR_SPACE_RGB:
+       dest[0] = color->c[0];
+       dest[1] = color->c[1];
+       dest[2] = color->c[2];
+       break;
+      default:
+       ASSERT(0);
+    }
+}
+
+void
+color_put_color_space(byte *dest, struct color *color, uns color_space)
+{
+  switch (color_space)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       color_put_grayscale(dest, color);
+       break;
+      case COLOR_SPACE_RGB:
+       color_put_rgb(dest, color);
+       break;
+      default:
+       ASSERT(0);
+    }
+}
+
+/********************* EXACT CONVERSION ROUTINES **********************/
+
+/* 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)
+{
+  for (uns i = 0; i < 3; i++)
+    if (src[i] > info->transition)
+      dest[i] = (1 + info->offset) * pow(src[i], info->detailed_gamma) - info->offset;
+    else
+      dest[i] = info->slope * src[i];
+}
+
+static inline void
+invert_gamma_detailed(double dest[3], double src[3], const struct color_space_gamma_info *info)
+{
+  for (uns i = 0; i < 3; i++)
+    if (src[i] > info->transition * info->slope)
+      dest[i] = pow((src[i] + info->offset) / (1 + info->offset), 1 / info->detailed_gamma);
+    else
+      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_exact(double luv[3], double xyz[3])
+{
+  double sum = xyz[0] + 15 * xyz[1] + 3 * xyz[2];
+  if (sum < 0.000001)
+    luv[0] = luv[1] = luv[2] = 0;
+  else
+    {
+      double var_u = 4 * xyz[0] / sum;
+      double var_v = 9 * xyz[1] / sum;
+      if (xyz[1] > 0.008856)
+        luv[0] = 116 * pow(xyz[1], 1 / 3.) - 16;
+      else
+        luv[0] = (116 * 7.787) * xyz[1];
+     luv[1] = luv[0] * (13 * (var_u - 4 * REF_WHITE_X / (REF_WHITE_X + 15 * REF_WHITE_Y + 3 * REF_WHITE_Z)));
+     luv[2] = luv[0] * (13 * (var_v - 9 * REF_WHITE_Y / (REF_WHITE_X + 15 * REF_WHITE_Y + 3 * REF_WHITE_Z)));
+     /* intervals [0..100], [-134..220], [-140..122] */
+   }
+}
+
+/* CIE-Luv to XYZ */
+void
+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));
+  double var_y = (luv[0] + 16) / 116;
+  double pow_y = var_y * var_y * var_y;
+  if (pow_y > 0.008856)
+    var_y = pow_y;
+  else
+    var_y = (var_y - 16 / 116) / 7.787;
+  xyz[1] = var_y;
+  xyz[0] = -(9 * xyz[1] * var_u) / ((var_u - 4) * var_v - var_u * var_v);
+  xyz[2] = (9 * xyz[1] - 15 * var_v * xyz[1] - var_v * xyz[0]) / (3 * var_v);
+}
+
+
+/***************** OPTIMIZED SRGB -> LUV CONVERSION *********************/
+
+u16 srgb_to_luv_tab1[256];
+u16 srgb_to_luv_tab2[9 << SRGB_TO_LUV_TAB2_SIZE];
+u32 srgb_to_luv_tab3[20 << SRGB_TO_LUV_TAB3_SIZE];
+
+void
+srgb_to_luv_init(void)
+{
+  DBG("Initializing sRGB -> Luv table");
+  for (uns i = 0; i < 256; i++)
+    {
+      double t = i / 255.;
+      if (t > 0.04045)
+        t = pow((t + 0.055) * (1 / 1.055), 2.4);
+      else
+        t = t * (1 / 12.92);
+      srgb_to_luv_tab1[i] = CLAMP(t * 0xfff + 0.5, 0, 0xfff);
+    }
+  for (uns i = 0; i < (9 << SRGB_TO_LUV_TAB2_SIZE); i++)
+    {
+      double t = i / (double)((9 << SRGB_TO_LUV_TAB2_SIZE) - 1);
+      if (t > 0.008856)
+        t = 1.16 * pow(t, 1 / 3.) - 0.16;
+      else
+        t = (1.16 * 7.787) * t;
+      srgb_to_luv_tab2[i] =
+       CLAMP(t * ((1 << SRGB_TO_LUV_TAB2_SCALE) - 1) + 0.5,
+          0, (1 << SRGB_TO_LUV_TAB2_SCALE) - 1);
+    }
+  for (uns i = 0; i < (20 << SRGB_TO_LUV_TAB3_SIZE); i++)
+    {
+      srgb_to_luv_tab3[i] = i ? (13 << (SRGB_TO_LUV_TAB3_SCALE + SRGB_TO_LUV_TAB3_SIZE)) / i : 0;
+    }
+}
+
+void
+srgb_to_luv_pixels(byte *dest, byte *src, uns count)
+{
+  while (count--)
+    {
+      srgb_to_luv_pixel(dest, src);
+      dest += 3;
+      src += 3;
+    }
+}
+
+
+/************************ GRID INTERPOLATION ALGORITHM ************************/
+
+struct color_grid_node *srgb_to_luv_grid;
+struct color_interpolation_node *color_interpolation_table;
+
+/* Returns volume of a given tetrahedron multiplied by 6 */
+static inline uns
+tetrahedron_volume(uns *v1, uns *v2, uns *v3, uns *v4)
+{
+  int a[3], b[3], c[3];
+  for (uns i = 0; i < 3; i++)
+    {
+      a[i] = v2[i] - v1[i];
+      b[i] = v3[i] - v1[i];
+      c[i] = v4[i] - v1[i];
+    }
+  int result =
+    a[0] * (b[1] * c[2] - b[2] * c[1]) -
+    a[1] * (b[0] * c[2] - b[2] * c[0]) +
+    a[2] * (b[0] * c[1] - b[1] * c[0]);
+  return (result > 0) ? result : -result;
+}
+
+static void
+interpolate_tetrahedron(struct color_interpolation_node *n, uns *p, const uns *c)
+{
+  uns v[4][3];
+  for (uns i = 0; i < 4; i++)
+    {
+      v[i][0] = (c[i] & 0001) ? (1 << COLOR_CONV_OFS) : 0;
+      v[i][1] = (c[i] & 0010) ? (1 << COLOR_CONV_OFS) : 0;
+      v[i][2] = (c[i] & 0100) ? (1 << COLOR_CONV_OFS) : 0;
+      n->ofs[i] =
+       ((c[i] & 0001) ? 1 : 0) +
+       ((c[i] & 0010) ? (1 << COLOR_CONV_SIZE) : 0) +
+       ((c[i] & 0100) ? (1 << (COLOR_CONV_SIZE * 2)) : 0);
+    }
+  uns vol = tetrahedron_volume(v[0], v[1], v[2], v[3]);
+  n->mul[0] = ((tetrahedron_volume(p, v[1], v[2], v[3]) << 8) + (vol >> 1)) / vol;
+  n->mul[1] = ((tetrahedron_volume(v[0], p, v[2], v[3]) << 8) + (vol >> 1)) / vol;
+  n->mul[2] = ((tetrahedron_volume(v[0], v[1], p, v[3]) << 8) + (vol >> 1)) / vol;
+  n->mul[3] = ((tetrahedron_volume(v[0], v[1], v[2], p) << 8) + (vol >> 1)) / vol;
+  uns j;
+  for (j = 0; j < 4; j++)
+    if (n->mul[j])
+      break;
+  for (uns i = 0; i < 4; i++)
+    if (n->mul[i] == 0)
+      n->ofs[i] = n->ofs[j];
+}
+
+static void
+interpolation_table_init(void)
+{
+  DBG("Initializing color interpolation table");
+  struct color_interpolation_node *n = color_interpolation_table =
+    xmalloc(sizeof(struct color_interpolation_node) << (COLOR_CONV_OFS * 3));
+  uns p[3];
+  for (p[2] = 0; p[2] < (1 << COLOR_CONV_OFS); p[2]++)
+    for (p[1] = 0; p[1] < (1 << COLOR_CONV_OFS); p[1]++)
+      for (p[0] = 0; p[0] < (1 << COLOR_CONV_OFS); p[0]++)
+        {
+         uns index;
+          static const uns tetrahedra[5][4] = {
+            {0000, 0001, 0010, 0100},
+            {0110, 0111, 0100, 0010},
+            {0101, 0100, 0111, 0001},
+            {0011, 0010, 0001, 0111},
+            {0111, 0001, 0010, 0100}};
+         if (p[0] + p[1] + p[2] <= (1 << COLOR_CONV_OFS))
+           index = 0;
+         else if ((1 << COLOR_CONV_OFS) + p[0] <= p[1] + p[2])
+           index = 1;
+         else if ((1 << COLOR_CONV_OFS) + p[1] <= p[0] + p[2])
+           index = 2;
+         else if ((1 << COLOR_CONV_OFS) + p[2] <= p[0] + p[1])
+           index = 3;
+         else
+           index = 4;
+         interpolate_tetrahedron(n, p, tetrahedra[index]);
+         n++;
+       }
+}
+
+typedef void color_conv_func(double dest[3], double src[3]);
+
+static void
+conv_grid_init(struct color_grid_node **grid, color_conv_func func)
+{
+  if (*grid)
+    return;
+  struct color_grid_node *g = *grid = xmalloc((sizeof(struct color_grid_node)) << (COLOR_CONV_SIZE * 3));
+  double src[3], dest[3];
+  for (uns k = 0; k < (1 << COLOR_CONV_SIZE); k++)
+    {
+      src[2] = k * (255 / (double)((1 << COLOR_CONV_SIZE) - 1));
+      for (uns j = 0; j < (1 << COLOR_CONV_SIZE); j++)
+        {
+          src[1] = j * (255/ (double)((1 << COLOR_CONV_SIZE) - 1));
+          for (uns i = 0; i < (1 << COLOR_CONV_SIZE); i++)
+            {
+              src[0] = i * (255 / (double)((1 << COLOR_CONV_SIZE) - 1));
+             func(dest, src);
+             g->val[0] = CLAMP(dest[0] + 0.5, 0, 255);
+             g->val[1] = CLAMP(dest[1] + 0.5, 0, 255);
+             g->val[2] = CLAMP(dest[2] + 0.5, 0, 255);
+             g++;
+           }
+       }
+    }
+}
+
+static void
+srgb_to_luv_func(double dest[3], double src[3])
+{
+  double srgb[3], xyz[3], luv[3];
+  srgb[0] = src[0] / 255.;
+  srgb[1] = src[1] / 255.;
+  srgb[2] = src[2] / 255.;
+  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;
+}
+
+void
+color_conv_init(void)
+{
+  interpolation_table_init();
+  conv_grid_init(&srgb_to_luv_grid, srgb_to_luv_func);
+}
+
+void
+color_conv_pixels(byte *dest, byte *src, uns count, struct color_grid_node *grid)
+{
+  while (count--)
+    {
+      color_conv_pixel(dest, src, grid);
+      dest += 3;
+      src += 3;
+    }
+}
+
+
+/**************************** TESTS *******************************/
+
+#ifdef TEST
+#include <string.h>
+
+static double
+conv_error(u32 color, struct color_grid_node *grid, color_conv_func func)
+{
+  byte src[3], dest[3];
+  src[0] = color & 255;
+  src[1] = (color >> 8) & 255;
+  src[2] = (color >> 16) & 255;
+  color_conv_pixel(dest, src, grid);
+  double src2[3], dest2[3];
+  for (uns i = 0; i < 3; i++)
+    src2[i] = src[i];
+  func(dest2, src2);
+  double err = 0;
+  for (uns i = 0; i < 3; i++)
+    err += (dest[i] - dest2[i]) * (dest[i] - dest2[i]);
+  return err;
+}
+
+typedef void test_fn(byte *dest, byte *src);
+
+static double
+func_error(u32 color, test_fn test, color_conv_func func)
+{
+  byte src[3], dest[3];
+  src[0] = color & 255;
+  src[1] = (color >> 8) & 255;
+  src[2] = (color >> 16) & 255;
+  test(dest, src);
+  double src2[3], dest2[3];
+  for (uns i = 0; i < 3; i++)
+    src2[i] = src[i];
+  func(dest2, src2);
+  double err = 0;
+  for (uns i = 0; i < 3; i++)
+    err += (dest[i] - dest2[i]) * (dest[i] - dest2[i]);
+  return err;
+}
+
+static void
+test_grid(byte *name, struct color_grid_node *grid, color_conv_func func)
+{
+  double max_err = 0, sum_err = 0;
+  uns count = 100000;
+  for (uns i = 0; i < count; i++)
+    {
+      double err = conv_error(random_max(0x1000000), grid, func);
+      max_err = MAX(err, max_err);
+      sum_err += err;
+    }
+  DBG("%s: error max=%f avg=%f", name, max_err, sum_err / count);
+  if (max_err > 12)
+    die("Too large error in %s conversion", name);
+}
+
+static void
+test_func(byte *name, test_fn test, color_conv_func func)
+{
+  double max_err = 0, sum_err = 0;
+  uns count = 100000;
+  for (uns i = 0; i < count; i++)
+    {
+      double err = func_error(random_max(0x1000000), test, func);
+      max_err = MAX(err, max_err);
+      sum_err += err;
+    }
+  DBG("%s: error max=%f avg=%f", name, max_err, sum_err / count);
+  if (max_err > 12)
+    die("Too large error in %s conversion", name);
+}
+
+int
+main(void)
+{
+  srgb_to_luv_init();
+  test_func("func sRGB -> Luv", srgb_to_luv_pixel, srgb_to_luv_func);
+  color_conv_init();
+  test_grid("grid sRGB -> Luv", srgb_to_luv_grid, srgb_to_luv_func);
+#ifdef LOCAL_DEBUG
+#define CNT 1000000
+#define TESTS 10
+  byte *a = xmalloc(3 * CNT), *b = xmalloc(3 * CNT);
+  for (uns i = 0; i < 3 * CNT; i++)
+    a[i] = random_max(256);
+  init_timer();
+  for (uns i = 0; i < TESTS; i++)
+    memcpy(b, a, CNT * 3);
+  DBG("memcpy time=%d", (uns)get_timer());
+  init_timer();
+  for (uns i = 0; i < TESTS; i++)
+    srgb_to_luv_pixels(b, a, CNT);
+  DBG("direct time=%d", (uns)get_timer());
+  init_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());
+#endif
+  return 0;
+}
+#endif
+
diff --git a/images/color.h b/images/color.h
new file mode 100644 (file)
index 0000000..8c87e87
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ *     Image Library -- Color Spaces
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ *
+ *
+ *     References:
+ *     - A Review of RGB Color Spaces, Danny Pascale (2003)
+ *     - http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
+ *     - http://www.tecgraf.puc-rio.br/~mgattass/color/ColorIndex.html
+ *
+ *     FIXME:
+ *     - fix theoretical problems with rounding errors in srgb_to_luv_pixel()
+ *     - SIMD should help to speed up conversion of large arrays
+ *     - maybe try to generate a long switch in color_conv_pixel()
+ *       with optimized entries instead of access to interpolation table
+ *     - most of multiplications in srgb_to_luv_pixels can be replaced
+ *       with tables lookup... tests shows almost the same speed for random
+ *       input and cca 40% gain when input colors fit in CPU chache
+ */
+
+#ifndef _IMAGES_COLOR_H
+#define _IMAGES_COLOR_H
+
+#include "images/images.h"
+
+// A comparison of four multimedia RGB spaces, Danny Pascale
+
+enum {
+  COLOR_SPACE_UNKNOWN = 0,
+  COLOR_SPACE_GRAYSCALE,
+  COLOR_SPACE_RGB,
+  COLOR_SPACE_XYZ,
+  COLOR_SPACE_LAB,
+  COLOR_SPACE_LUV,
+  COLOR_SPACE_YCBCR,
+  COLOR_SPACE_MAX
+};
+
+/* Color spaces in the CIE 1931 chromacity diagram */
+
+struct color_space_chromacity_info {
+  double prim1[2];
+  double prim2[2];
+  double prim3[2];
+  double white[2];
+};
+
+struct color_space_gamma_info {
+  double simple_gamma;
+  double detailed_gamma;
+  double offset;
+  double transition;
+  double slope;
+};
+
+struct color_space_info {
+  byte *name;
+  struct color_space_chromacity_info chromacity;
+  struct color_space_gamma_info gamma;
+};
+
+extern const double
+  color_illuminant_d50[2],
+  color_illuminant_d65[2],
+  color_illuminant_e[2];
+
+extern const struct color_space_info
+  color_adobe_rgb_info,                /* Adobe RGB (1998) */
+  color_apple_rgb_info,                /* Apple RGB */
+  color_cie_rgb_info,          /* CIE RGB */
+  color_color_match_rgb_info,  /* ColorMatch RGB */
+  color_srgb_info;             /* sRGB */
+
+/* These routines do not check numeric errors! */
+void color_compute_color_space_to_xyz_matrix(double matrix[9], const struct color_space_chromacity_info *space);
+void color_compute_bradford_matrix(double matrix[9], const double src[2], const double dest[2]);
+void color_compute_color_spaces_conversion_matrix(double matrix[9], const struct color_space_chromacity_info *src, const struct color_space_chromacity_info *dest);
+void color_invert_matrix(double dest[9], double matrix[9]);
+
+static inline uns
+rgb_to_gray_func(uns r, uns g, uns b)
+{
+  return (r * 19660 + g * 38666 + b * 7210) >> 16;
+}
+
+extern struct color color_black, color_white;
+
+static inline void
+color_make_gray(struct color *color, uns gray)
+{
+  color->c[0] = gray;
+  color->color_space = COLOR_SPACE_GRAYSCALE;
+}
+
+static inline void
+color_make_rgb(struct color *color, uns r, uns g, uns b)
+{
+  color->c[0] = r;
+  color->c[1] = g;
+  color->c[2] = b;
+  color->color_space = COLOR_SPACE_RGB;
+}
+
+void color_put_color_space(byte *dest, struct color *color, uns color_space);
+void color_put_grayscale(byte *dest, struct color *color);
+void color_put_rgb(byte *dest, struct color *color);
+
+/* Exact slow conversion routines */
+void srgb_to_xyz_exact(double dest[3], double src[3]);
+void xyz_to_srgb_exact(double dest[3], double src[3]);
+void xyz_to_luv_exact(double dest[3], double src[3]);
+void luv_to_xyz_exact(double dest[3], double src[3]);
+
+/* Reference white */
+#define REF_WHITE_X 0.96422
+#define REF_WHITE_Y 1.
+#define REF_WHITE_Z 0.82521
+
+/* sRGB -> XYZ matrix */
+#define SRGB_XYZ_XR 0.412424
+#define SRGB_XYZ_XG 0.357579
+#define SRGB_XYZ_XB 0.180464
+#define SRGB_XYZ_YR 0.212656
+#define SRGB_XYZ_YG 0.715158
+#define SRGB_XYZ_YB 0.072186
+#define SRGB_XYZ_ZR 0.019332
+#define SRGB_XYZ_ZG 0.119193
+#define SRGB_XYZ_ZB 0.950444
+
+
+/*********************** OPTIMIZED CONVERSION ROUTINES **********************/
+
+/* sRGB -> Luv parameters */
+#define SRGB_TO_LUV_TAB2_SIZE 9
+#define SRGB_TO_LUV_TAB2_SCALE 11
+#define SRGB_TO_LUV_TAB3_SIZE 8
+#define SRGB_TO_LUV_TAB3_SCALE (39 - SRGB_TO_LUV_TAB2_SCALE - SRGB_TO_LUV_TAB3_SIZE)
+
+extern u16 srgb_to_luv_tab1[256];
+extern u16 srgb_to_luv_tab2[9 << SRGB_TO_LUV_TAB2_SIZE];
+extern u32 srgb_to_luv_tab3[20 << SRGB_TO_LUV_TAB3_SIZE];
+
+void srgb_to_luv_init(void);
+void srgb_to_luv_pixels(byte *dest, byte *src, uns count);
+
+/* L covers the interval [0..255]; u and v are centered to 128 and scaled by 1/4 in respect of L */
+static inline void
+srgb_to_luv_pixel(byte *dest, byte *src)
+{
+  uns r = srgb_to_luv_tab1[src[0]];
+  uns g = srgb_to_luv_tab1[src[1]];
+  uns b = srgb_to_luv_tab1[src[2]];
+  uns x =
+    (uns)(4 * SRGB_XYZ_XR * 0xffff) * r +
+    (uns)(4 * SRGB_XYZ_XG * 0xffff) * g +
+    (uns)(4 * SRGB_XYZ_XB * 0xffff) * b;
+  uns y =
+    (uns)(9 * SRGB_XYZ_YR * 0xffff) * r +
+    (uns)(9 * SRGB_XYZ_YG * 0xffff) * g +
+    (uns)(9 * SRGB_XYZ_YB * 0xffff) * b;
+  uns l = srgb_to_luv_tab2[y >> (28 - SRGB_TO_LUV_TAB2_SIZE)];
+    dest[0] = l >> (SRGB_TO_LUV_TAB2_SCALE - 8);
+  uns sum =
+    (uns)((SRGB_XYZ_XR + 15 * SRGB_XYZ_YR + 3 * SRGB_XYZ_ZR) * 0x7fff) * r +
+    (uns)((SRGB_XYZ_XG + 15 * SRGB_XYZ_YG + 3 * SRGB_XYZ_ZG) * 0x7fff) * g +
+    (uns)((SRGB_XYZ_XB + 15 * SRGB_XYZ_YB + 3 * SRGB_XYZ_ZB) * 0x7fff) * b;
+  uns s = srgb_to_luv_tab3[sum >> (27 - SRGB_TO_LUV_TAB3_SIZE)];
+  int xs = ((u64)x * s) >> 32;
+  int ys = ((u64)y * s) >> 32;
+  int xw = ((4 * 13) << (SRGB_TO_LUV_TAB3_SCALE - 4)) *
+    REF_WHITE_X / (REF_WHITE_X + 15 * REF_WHITE_Y + 3 * REF_WHITE_Z);
+  int yw = ((9 * 13) << (SRGB_TO_LUV_TAB3_SCALE - 4)) *
+    REF_WHITE_Y / (REF_WHITE_X + 15 * REF_WHITE_Y + 3 * REF_WHITE_Z);
+  int u = (int)(l) * (xs - xw);
+  int v = (int)(l) * (ys - yw);
+  dest[1] = 128 + (u >> (SRGB_TO_LUV_TAB3_SCALE + SRGB_TO_LUV_TAB2_SCALE - 10));
+  dest[2] = 128 + (v >> (SRGB_TO_LUV_TAB3_SCALE + SRGB_TO_LUV_TAB2_SCALE - 10));
+}
+
+
+/****************** GENERAL INTERPOLATION IN 3D GRID ********************/
+
+#define COLOR_CONV_SIZE        5  /* 128K conversion grid size */
+#define COLOR_CONV_OFS 3  /* 8K interpolation table size */
+
+struct color_grid_node {
+  byte val[4];
+};
+
+struct color_interpolation_node {
+  u16 ofs[4];
+  u16 mul[4];
+};
+
+extern struct color_grid_node *srgb_to_luv_grid;
+extern struct color_interpolation_node *color_interpolation_table;
+
+void color_conv_init(void);
+void color_conv_pixels(byte *dest, byte *src, uns count, struct color_grid_node *grid);
+
+#define COLOR_CONV_SCALE_CONST (((((1 << COLOR_CONV_SIZE) - 1) << 16) + (1 << (16 - COLOR_CONV_OFS))) / 255)
+
+static inline void
+color_conv_pixel(byte *dest, byte *src, struct color_grid_node *grid)
+{
+  uns s0 = src[0] * COLOR_CONV_SCALE_CONST;
+  uns s1 = src[1] * COLOR_CONV_SCALE_CONST;
+  uns s2 = src[2] * COLOR_CONV_SCALE_CONST;
+  struct color_grid_node *g0, *g1, *g2, *g3, *g = grid +
+    ((s0 >> 16) + ((s1 >> 16) << COLOR_CONV_SIZE) + ((s2 >> 16) << (2 * COLOR_CONV_SIZE)));
+  struct color_interpolation_node *n = color_interpolation_table +
+    (((s0 & (0x10000 - (0x10000 >> COLOR_CONV_OFS))) >> (16 - COLOR_CONV_OFS)) +
+    ((s1 & (0x10000 - (0x10000 >> COLOR_CONV_OFS))) >> (16 - 2 * COLOR_CONV_OFS)) +
+    ((s2 & (0x10000 - (0x10000 >> COLOR_CONV_OFS))) >> (16 - 3 * COLOR_CONV_OFS)));
+  g0 = g + n->ofs[0];
+  g1 = g + n->ofs[1];
+  g2 = g + n->ofs[2];
+  g3 = g + n->ofs[3];
+  dest[0] = (g0->val[0] * n->mul[0] + g1->val[0] * n->mul[1] +
+             g2->val[0] * n->mul[2] + g3->val[0] * n->mul[3] + 128) >> 8;
+  dest[1] = (g0->val[1] * n->mul[0] + g1->val[1] * n->mul[1] +
+             g2->val[1] * n->mul[2] + g3->val[1] * n->mul[3] + 128) >> 8;
+  dest[2] = (g0->val[2] * n->mul[0] + g1->val[2] * n->mul[1] +
+             g2->val[2] * n->mul[2] + g3->val[2] * n->mul[3] + 128) >> 8;
+}
+
+#endif
diff --git a/images/color.t b/images/color.t
new file mode 100644 (file)
index 0000000..acae30f
--- /dev/null
@@ -0,0 +1,3 @@
+# Tests for color conversion module
+
+Run:   obj/images/color-t
diff --git a/images/config.c b/images/config.c
new file mode 100644 (file)
index 0000000..a268525
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *     Image Library -- Configuration
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "images/images.h"
+#include "images/signature.h"
+
+#include <string.h>
+
+/* ImageLib section */
+uns image_trace;
+uns image_max_dim = 0xffff;
+uns image_max_bytes = ~0U;
+
+/* ImageSig section */
+uns image_sig_min_width;
+uns image_sig_min_height;
+uns *image_sig_prequant_thresholds;
+uns image_sig_postquant_min_steps;
+uns image_sig_postquant_max_steps;
+uns image_sig_postquant_threshold;
+double image_sig_border_size;
+int image_sig_border_bonus;
+double image_sig_inertia_scale[3];
+double image_sig_textured_threshold;
+int image_sig_compare_method;
+uns image_sig_cmp_features_weights[IMAGE_REG_F + IMAGE_REG_H];
+
+static struct cf_section image_lib_config = {
+  CF_ITEMS{
+    CF_UNS("Trace", &image_trace),
+    CF_UNS("ImageMaxDim", &image_max_dim),
+    CF_UNS("ImageMaxBytes", &image_max_bytes),
+    CF_END
+  }
+};
+
+static struct cf_section image_sig_config = {
+  CF_ITEMS{
+    CF_UNS("MinWidth", &image_sig_min_width),
+    CF_UNS("MinHeight", &image_sig_min_height),
+    CF_UNS_DYN("PreQuantThresholds", &image_sig_prequant_thresholds, CF_ANY_NUM),
+    CF_UNS("PostQuantMinSteps", &image_sig_postquant_min_steps),
+    CF_UNS("PostQuantMaxSteps", &image_sig_postquant_max_steps),
+    CF_UNS("PostQuantThreshold", &image_sig_postquant_threshold),
+    CF_DOUBLE("BorderSize", &image_sig_border_size),
+    CF_INT("BorderBonus", &image_sig_border_bonus),
+    CF_DOUBLE_ARY("InertiaScale", image_sig_inertia_scale, 3),
+    CF_DOUBLE("TexturedThreshold", &image_sig_textured_threshold),
+    CF_LOOKUP("CompareMethod", &image_sig_compare_method, ((byte *[]){"integrated", "fuzzy", "average", NULL})),
+    CF_UNS_ARY("CompareFeaturesWeights", image_sig_cmp_features_weights, IMAGE_REG_F + IMAGE_REG_H),
+    CF_END
+  }
+};
+
+static void CONSTRUCTOR
+images_init_config(void)
+{
+  cf_declare_section("ImageLib", &image_lib_config, 0);
+  cf_declare_section("ImageSig", &image_sig_config, 0);
+}
diff --git a/images/context.c b/images/context.c
new file mode 100644 (file)
index 0000000..6281ba3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *     Image Library -- Image contexts
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/bbuf.h"
+#include "images/images.h"
+#include "images/error.h"
+
+#include <string.h>
+
+void
+image_context_init(struct image_context *ctx)
+{
+  bzero(ctx, sizeof(*ctx));
+  bb_init(&ctx->msg_buf);
+  ctx->tracing_level = image_trace;
+  ctx->msg_callback = image_context_msg_default;
+}
+
+void
+image_context_cleanup(struct image_context *ctx)
+{
+  IMAGE_TRACE(ctx, 10, "Destroying image thread");
+  bb_done(&ctx->msg_buf);
+}
+
+void
+image_context_msg_default(struct image_context *ctx)
+{
+  log(ctx->msg_code >> 24, "%s", ctx->msg);
+}
+
+void
+image_context_msg_silent(struct image_context *ctx UNUSED)
+{
+}
+
+void
+image_context_msg(struct image_context *ctx, uns code, char *msg, ...)
+{
+  va_list args;
+  va_start(args, msg);
+  image_context_vmsg(ctx, code, msg, args);
+  va_end(args);
+}
+
+void
+image_context_vmsg(struct image_context *ctx, uns code, char *msg, va_list args)
+{
+  ctx->msg_code = code;
+  ctx->msg = bb_vprintf(&ctx->msg_buf, msg, args);
+  ctx->msg_callback(ctx);
+}
diff --git a/images/dup-cmp.c b/images/dup-cmp.c
new file mode 100644 (file)
index 0000000..5fec176
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ *      Image Library -- Duplicates Comparison
+ *
+ *      (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *      This software may be freely distributed and used according to the terms
+ *      of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "sherlock/sherlock.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/duplicates.h"
+
+#include <fcntl.h>
+
+static uns image_dup_ratio_threshold = 140;
+static uns image_dup_error_threshold = 100;
+
+static inline uns
+err (int a, int b)
+{
+  a -= b;
+  return a * a;
+}
+
+static inline u64
+err_sum(byte *pos1, byte *pos2, uns count)
+{
+  uns e64 = 0;
+  while (count--)
+    {
+      uns e = err(*pos1++, *pos2++);
+      e += err(*pos1++, *pos2++);
+      e += err(*pos1++, *pos2++);
+      e64 += e;
+    }
+  return e64;
+}
+
+static inline u64
+err_sum_transformed(byte *pos1, byte *pos2, uns cols, uns rows, int row_step_1, int col_step_2, int row_step_2)
+{
+  DBG("err_sum_transformed(pos1=%p pos2=%p cols=%u rows=%u row_step_1=%d col_step_2=%d row_step_2=%d)",
+      pos1, pos2, cols, rows, row_step_1, col_step_2, row_step_2);
+  u64 e64 = 0;
+  for (uns j = rows; j--; )
+    {
+      byte *p1 = pos1;
+      byte *p2 = pos2;
+      uns e = 0;
+      for (uns i = cols; i--; )
+      {
+       e += err(p1[0], p2[0]);
+       e += err(p1[1], p2[1]);
+       e += err(p1[2], p2[2]);
+       p1 += 3;
+       p2 += col_step_2;
+      }
+      pos1 += row_step_1;
+      pos2 += row_step_2;
+      e64 += e;
+    }
+  return e64;
+}
+
+static inline int
+aspect_ratio_test(uns cols1, uns rows1, uns cols2, uns rows2)
+{
+  DBG("aspect_ratio_test(cols1=%u rows1=%u cols2=%u rows2=%u)", cols1, rows1, cols2, rows2);
+  uns r1 = cols1 * rows2;
+  uns r2 = rows1 * cols2;
+  return
+    r1 <= ((r2 * image_dup_ratio_threshold) >> 7) &&
+    r2 <= ((r1 * image_dup_ratio_threshold) >> 7);
+}
+
+static inline int
+average_compare(struct image_dup *dup1, struct image_dup *dup2)
+{
+  byte *block1 = image_dup_block(dup1, 0, 0);
+  byte *block2 = image_dup_block(dup2, 0, 0);
+  uns e =
+    err(block1[0], block2[0]) +
+    err(block1[1], block2[1]) +
+    err(block1[2], block2[2]);
+  return e <= image_dup_error_threshold;
+}
+
+static int
+blocks_compare(struct image_dup *dup1, struct image_dup *dup2, uns tab_col, uns tab_row, uns trans)
+{
+  DBG("blocks_compare(tab_col=%d tab_row=%d trans=%d)", tab_col, tab_row, trans);
+  byte *block1 = image_dup_block(dup1, tab_col, tab_row);
+  byte *block2;
+  int col_step, row_step;
+  if (trans < 4)
+    block2 = image_dup_block(dup2, tab_col, tab_row);
+  else
+    block2 = image_dup_block(dup2, tab_row, tab_col);
+  switch (trans)
+    {
+      case 0: ;
+       uns err = (err_sum(block1, block2, 1 << (tab_col + tab_row)) >> (tab_col + tab_row));
+       DBG("average error=%d", err);
+       return err <= image_dup_error_threshold;
+      case 1:
+       col_step = -3;
+       row_step = (3 << tab_col);
+       block2 += row_step - 3;
+       break;
+      case 2:
+       col_step = 3;
+       row_step = -(3 << tab_col);
+       block2 += (3 << (tab_col + tab_row)) + row_step;
+       break;
+      case 3:
+       col_step = -3;
+       row_step = -(3 << tab_col);
+       block2 += (3 << (tab_col + tab_row)) - 3;
+       break;
+      case 4:
+       col_step = (3 << tab_row);
+       row_step = 3;
+       break;
+      case 5:
+       col_step = -(3 << tab_row);
+       row_step = 3;
+       block2 += (3 << (tab_col + tab_row)) + col_step;
+       break;
+      case 6:
+       col_step = (3 << tab_row);
+       row_step = -3;
+       block2 += col_step - 3;
+       break;
+      case 7:
+       col_step = -(3 << tab_row);
+       row_step = -3;
+       block2 += (3 << (tab_col + tab_row)) - 3;
+       break;
+      default:
+       ASSERT(0);
+    }
+  uns err = (err_sum_transformed(block1, block2, (1 << tab_col), (1 << tab_row), (3 << tab_col), col_step, row_step) >> (tab_col + tab_row));
+  DBG("average error=%d", err);
+  return err <= image_dup_error_threshold;
+}
+
+static int
+same_size_compare(struct image_dup *dup1, struct image_dup *dup2, uns trans)
+{
+  struct image *img1 = dup1->image;
+  struct image *img2 = dup2->image;
+  byte *block1 = img1->pixels;
+  byte *block2 = img2->pixels;
+  int col_step, row_step;
+  DBG("same_size_compare(trans=%d)",  trans);
+  switch (trans)
+    {
+      case 0: ;
+       col_step = 3;
+       row_step = img2->row_size;
+       break;
+      case 1:
+       col_step = -3;
+       row_step = img2->row_size;
+       block2 += 3 * (img2->cols - 1);
+       break;
+      case 2:
+       col_step = 3;
+       row_step = -img2->row_size;
+       block2 += img2->row_size * (img2->rows - 1);
+       break;
+      case 3:
+       col_step = -3;
+       row_step = -img2->row_size;
+       block2 += img2->row_size * (img2->rows - 1) + 3 * (img2->cols - 1);
+       break;
+      case 4:
+       col_step = img2->row_size;
+       row_step = 3;
+       break;
+      case 5:
+       col_step = -img2->row_size;
+       row_step = 3;
+       block2 += img2->row_size * (img2->rows - 1);
+       break;
+      case 6:
+       col_step = img2->row_size;
+       row_step = -3;
+       block2 += 3 * (img2->cols - 1);
+       break;
+      case 7:
+       col_step = -img2->row_size;
+       row_step = -3;
+       block2 += img2->row_size * (img2->rows - 1) + 3 * (img2->cols - 1);
+       break;
+      default:
+       ASSERT(0);
+    }
+  uns err = (err_sum_transformed(block1, block2, img1->cols, img1->rows, img1->row_size, col_step, row_step) / ((u64)img1->cols * img1->rows));
+  DBG("average error=%d", err);
+  return err <= image_dup_error_threshold;
+}
+
+uns
+image_dup_compare(struct image_dup *dup1, struct image_dup *dup2, uns flags)
+{
+  DBG("image_dup_compare()");
+  if (!average_compare(dup1, dup2))
+    return 0;
+  struct image *img1 = dup1->image;
+  struct image *img2 = dup2->image;
+  if (flags & IMAGE_DUP_SCALE)
+    {
+      DBG("Scale support");
+      if (!aspect_ratio_test(img1->cols, img1->rows, img2->cols, img2->rows))
+       flags &= ~0x0f;
+      if (!aspect_ratio_test(img1->cols, img1->rows, img2->rows, img2->cols))
+       flags &= ~0xf0;
+    }
+  else
+    {
+      DBG("No scale support");
+      if (!(img1->cols == img2->cols && img1->rows == img2->rows))
+       flags &= ~0x0f;
+      if (!(img1->cols == img2->rows && img1->rows == img2->cols))
+       flags &= ~0xf0;
+    }
+  if (!(flags & 0xff))
+    return 0;
+  uns result = 0;
+  if (flags & 0x0f)
+    {
+      uns cols = MIN(dup1->tab_cols, dup2->tab_cols);
+      uns rows = MIN(dup1->tab_rows, dup2->tab_rows);
+      for (uns t = 0; t < 4; t++)
+       if (flags & (1 << t))
+         {
+           DBG("Testing trans %d", t);
+            for (uns i = MAX(cols, rows); i--; )
+              {
+               uns col = MAX(0, (int)(cols - i));
+               uns row = MAX(0, (int)(rows - i));
+               if (!blocks_compare(dup1, dup2, col, row, t))
+                 break;
+               if (!i &&
+                   (img1->cols != img2->cols || img1->rows != img2->rows ||
+                   same_size_compare(dup1, dup2, t)))
+                 {
+                   result |= 1 << t;
+                   if (!(flags & IMAGE_DUP_WANT_ALL))
+                     return result;
+                   else
+                     break;
+                 }
+             }
+         }
+    }
+  if (flags & 0xf0)
+    {
+      uns cols = MIN(dup1->tab_cols, dup2->tab_rows);
+      uns rows = MIN(dup1->tab_rows, dup2->tab_cols);
+      for (uns t = 4; t < 8; t++)
+       if (flags & (1 << t))
+         {
+           DBG("Testing trans %d", t);
+            for (uns i = MAX(cols, rows); i--; )
+              {
+               uns col = MAX(0, (int)(cols - i));
+               uns row = MAX(0, (int)(rows - i));
+               if (!blocks_compare(dup1, dup2, col, row, t))
+                 break;
+               if (!i &&
+                   (img1->cols != img2->rows || img1->rows != img2->cols ||
+                   same_size_compare(dup1, dup2, t)) )
+                 {
+                   result |= 1 << t;
+                   if (!(flags & IMAGE_DUP_WANT_ALL))
+                     return result;
+                   else
+                     break;
+                 }
+             }
+         }
+    }
+  return result;
+}
diff --git a/images/dup-init.c b/images/dup-init.c
new file mode 100644 (file)
index 0000000..1dda196
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ *      Image Library -- Duplicates Comparison
+ *
+ *      (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *      This software may be freely distributed and used according to the terms
+ *      of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "sherlock/sherlock.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/color.h"
+#include "images/duplicates.h"
+
+#include <fcntl.h>
+
+static uns image_dup_tab_limit = 8;
+
+static inline struct image *
+image_dup_subimage(struct image_context *ctx, struct image_dup *dup, struct image *block, uns tab_col, uns tab_row)
+{
+  return image_init_matrix(ctx, block, image_dup_block(dup, tab_col, tab_row),
+      1 << tab_col, 1 << tab_row, 3 << tab_col, COLOR_SPACE_RGB);
+}
+
+static inline void
+pixels_average(byte *dest, byte *src1, byte *src2)
+{
+  dest[0] = ((uns)src1[0] + (uns)src2[0]) >> 1;
+  dest[1] = ((uns)src1[1] + (uns)src2[1]) >> 1;
+  dest[2] = ((uns)src1[2] + (uns)src2[2]) >> 1;
+}
+
+uns
+image_dup_estimate_size(uns cols, uns rows)
+{
+  uns tab_cols, tab_rows;
+  for (tab_cols = 0; (uns)(2 << tab_cols) < cols && tab_cols < image_dup_tab_limit; tab_cols++);
+  for (tab_rows = 0; (uns)(2 << tab_rows) < rows && tab_rows < image_dup_tab_limit; tab_rows++);
+  return sizeof(struct image) + cols * rows * 3 + sizeof(struct image_dup) + (12 << (tab_cols + tab_rows)) + 64;
+}
+
+uns
+image_dup_init(struct image_context *ctx, struct image_dup *dup, struct image *img, struct mempool *pool)
+{
+  DBG("image_dup_init()");
+
+  ASSERT((img->flags & IMAGE_PIXEL_FORMAT) == COLOR_SPACE_RGB);
+
+  dup->image = img;
+  for (dup->tab_cols = 0; (uns)(2 << dup->tab_cols) < img->cols && dup->tab_cols < image_dup_tab_limit; dup->tab_cols++);
+  for (dup->tab_rows = 0; (uns)(2 << dup->tab_rows) < img->rows && dup->tab_rows < image_dup_tab_limit; dup->tab_rows++);
+  dup->tab_pixels = mp_alloc(pool, dup->tab_size = (12 << (dup->tab_cols + dup->tab_rows)));
+  dup->tab_row_size = 6 << dup->tab_cols;
+
+  /* Scale original image to right bottom block */
+  {
+    struct image block;
+    if (!image_dup_subimage(ctx, dup, &block, dup->tab_cols, dup->tab_rows))
+      return 0;
+    if (!image_scale(ctx, &block, img))
+      return 0;
+  }
+
+  /* Complete bottom row */
+  for (uns i = dup->tab_cols; i--; )
+    {
+      byte *d = image_dup_block(dup, i, dup->tab_rows);
+      byte *s = image_dup_block(dup, i + 1, dup->tab_rows);
+      for (uns y = 0; y < (uns)(1 << dup->tab_rows); y++)
+       for (uns x = 0; x < (uns)(1 << i); x++)
+         {
+           pixels_average(d, s, s + 3);
+           d += 3;
+           s += 6;
+         }
+    }
+
+  /* Complete remaining blocks */
+  for (uns i = 0; i <= dup->tab_cols; i++)
+    {
+      uns line_size = (3 << i);
+      for (uns j = dup->tab_rows; j--; )
+        {
+          byte *d = image_dup_block(dup, i, j);
+          byte *s = image_dup_block(dup, i, j + 1);
+          for (uns y = 0; y < (uns)(1 << j); y++)
+            {
+              for (uns x = 0; x < (uns)(1 << i); x++)
+                {
+                 pixels_average(d, s, s + line_size);
+                 d += 3;
+                 s += 3;
+               }
+             s += line_size;
+           }
+        }
+    }
+
+  return 1;
+}
diff --git a/images/duplicates.h b/images/duplicates.h
new file mode 100644 (file)
index 0000000..dd07b76
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef _IMAGES_DUP_CMP_H
+#define _IMAGES_DUP_CMP_H
+
+struct image_dup {
+  struct image *image;
+  byte *tab_pixels;
+  u32 tab_cols;
+  u32 tab_rows;
+  u32 tab_row_size;
+  u32 tab_size;
+};
+
+#define IMAGE_DUP_TRANS_ID     0x01
+#define IMAGE_DUP_FLIP_X       0x02
+#define IMAGE_DUP_FLIP_Y       0x04
+#define IMAGE_DUP_ROT_180      0x08
+#define IMAGE_DUP_FLIP_BACK    0x10
+#define IMAGE_DUP_ROT_CCW      0x20
+#define IMAGE_DUP_ROT_CW       0x40
+#define IMAGE_DUP_FLIP_SLASH   0x80
+#define IMAGE_DUP_TRANS_ALL    0xff
+#define IMAGE_DUP_SCALE                0x100
+#define IMAGE_DUP_WANT_ALL     0x200
+
+/* dup-init.c */
+
+uns image_dup_init(struct image_context *ctx, struct image_dup *dup, struct image *image, struct mempool *pool);
+uns image_dup_estimate_size(uns cols, uns rows);
+
+/* dup-cmp.c */
+
+uns image_dup_compare(struct image_dup *dup1, struct image_dup *dup2, uns flags);
+
+/* internals */
+
+static inline byte *
+image_dup_block(struct image_dup *dup, uns tab_col, uns tab_row)
+{
+  return dup->tab_pixels + (dup->tab_row_size << tab_row) + (3 << (tab_row + tab_col));
+}
+
+
+#endif
diff --git a/images/error.h b/images/error.h
new file mode 100644 (file)
index 0000000..92f3766
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef _IMAGES_ERROR_H
+#define _IMAGES_ERROR_H
+
+extern uns image_trace; /* ImageLib.Trace */ 
+
+/* Error codes */
+
+enum image_msg_code {
+  IMAGE_MSG_TYPE = 0xff000000,
+  IMAGE_MSG_TRACE = (L_DEBUG << 24),
+  IMAGE_MSG_WARN = (L_WARN << 24),
+  IMAGE_MSG_ERROR = (L_ERROR << 24),
+  IMAGE_TRACE_LEVEL = 0x0000ffff,
+  IMAGE_WARN_TYPE = 0x0000ffff,
+  IMAGE_WARN_SUBTYPE = 0x00ff0000,
+  IMAGE_ERROR_TYPE = 0x0000ffff,
+  IMAGE_ERROR_SUBTYPE = 0x00ff0000,
+  IMAGE_ERROR_NOT_IMPLEMENTED = 1,
+  IMAGE_ERROR_INVALID_DIMENSIONS = 2,
+  IMAGE_ERROR_INVALID_FILE_FORMAT = 3,
+  IMAGE_ERROR_INVALID_PIXEL_FORMAT = 4,
+  IMAGE_ERROR_READ_FAILED = 5,
+  IMAGE_ERROR_WRITE_FAILED = 6,
+};
+
+/* Useful macros */
+
+#define IMAGE_WARN(ctx, type, msg...) image_context_msg((ctx), IMAGE_MSG_WARN | (type), msg)
+#define IMAGE_ERROR(ctx, type, msg...) image_context_msg((ctx), IMAGE_MSG_ERROR | (type), msg)
+
+#define IMAGE_TRACE(ctx, level, msg...) do { \
+       struct image_context *_ctx = (ctx); uns _level = (level); \
+       if (_level < _ctx->tracing_level) image_context_msg(_ctx, IMAGE_MSG_TRACE | _level, msg); } while (0)
+
+#endif
diff --git a/images/hilbert-test.c b/images/hilbert-test.c
new file mode 100644 (file)
index 0000000..9824f09
--- /dev/null
@@ -0,0 +1,124 @@
+/* Tests for multidimensional Hilbert curves */
+
+#define LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "lib/math.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+static struct mempool *pool;
+
+static uns dim;
+static uns order;
+
+static inline void
+rand_vec(uns *vec)
+{
+  for (uns i = 0; i < dim; i++)
+    vec[i] = (uns)rand() >> (32 - order);
+}
+
+static byte *
+print_vec(uns *vec)
+{
+  byte *s = mp_alloc(pool, dim * 16), *res = s;
+  *s++ = '(';
+  for (uns i = 0; i < dim; i++)
+    {
+      if (i)
+       *s++ = ' ';
+      s += sprintf(s, "%x", vec[i]);
+    }
+  *s++ = ')';
+  *s = 0;
+  return res;
+}
+
+static inline int
+cmp_vec(uns *vec1, uns *vec2)
+{
+  for (uns i = dim; i--; )
+    if (vec1[i] < vec2[i])
+      return -1;
+    else if (vec1[i] > vec2[i])
+      return 1;
+  return 0;
+}
+
+#if 0
+static long double
+param_dist(uns *vec1, uns *vec2)
+{
+  long double d1 = 0, d2 = 0;
+  for (uns i = 0; i < dim; i++)
+    {
+      d1 = (d1 + vec1[i]) / ((u64)1 << order);
+      d2 = (d2 + vec2[i]) / ((u64)1 << order);
+    }
+  return fabsl(d1 - d2);
+}
+
+static long double
+vec_dist(uns *vec1, uns *vec2)
+{
+  long double d = 0;
+  for (uns i = 0; i < dim; i++)
+    {
+      long double x = fabsl(vec1[i] - vec2[i]) / ((u64)1 << order);
+      d += x * x;
+    }
+  return sqrtl(d);
+}
+#endif
+
+#define HILBERT_PREFIX(x) test1_##x
+#define HILBERT_DIM dim
+#define HILBERT_ORDER order
+#define HILBERT_WANT_DECODE
+#define HILBERT_WANT_ENCODE
+#include "images/hilbert.h"
+
+static void
+test1(void)
+{
+  uns a[32], b[32], c[32];
+  for (dim = 2; dim <= 8; dim++)
+    for (order = 8; order <= 32; order++)
+      for (uns i = 0; i < 1000; i++)
+        {
+         rand_vec(a);
+          test1_encode(b, a);
+          test1_decode(c, b);
+         if (cmp_vec(a, c))
+           die("Error... dim=%d order=%d testnum=%d ... %s -> %s -> %s", 
+               dim, order, i, print_vec(a), print_vec(b), print_vec(c));
+        }
+}
+
+#if 0
+#include "images/hilbert-origin.h"
+static void
+test_origin(void)
+{
+  Hcode code;
+  Point pt, pt2;
+  pt.hcode[0] = 0x12345678;
+  pt.hcode[1] = 0x654321;
+  pt.hcode[2] = 0x11122233;
+  code = H_encode(pt);
+  pt2 = H_decode(code);
+  DBG("origin: [%08x, %08x, %08x] --> [%08x, %08x %08x] --> [%08x, %08x %08x]", 
+    pt.hcode[0], pt.hcode[1], pt.hcode[2], code.hcode[0], code.hcode[1], code.hcode[2], pt2.hcode[0], pt2.hcode[1], pt2.hcode[2]);
+}
+#endif
+
+int
+main(int argc UNUSED, char **argv UNUSED)
+{
+  pool = mp_new(1 << 16);
+  test1();
+  //test_origin();
+  return 0;
+}
diff --git a/images/hilbert-test.t b/images/hilbert-test.t
new file mode 100644 (file)
index 0000000..f8794e3
--- /dev/null
@@ -0,0 +1,3 @@
+# Tests for multidimensional Hilbert curves
+
+Run:   obj/images/hilbert-test
diff --git a/images/hilbert.h b/images/hilbert.h
new file mode 100644 (file)
index 0000000..4971826
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ *     Image Library -- multidimensional Hilbert curves
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ *
+ *
+ *     References:
+ *     - http://www.dcs.bbk.ac.uk/~jkl/mapping.c
+ *       (c) 2002 J.K.Lawder
+ *     - J.K. Lawder. Calculation of Mappings between One and n-dimensional Values
+ *       Using the Hilbert Space-Filling Curve. Technical Report JL1/00, Birkbeck
+ *       College, University of London, 2000.
+ *
+ *     FIXME:
+ *     - the algorithm fails for some combinations of HILBERT_DIM and HILBERT_ORDER,
+ *       but it should be safe for HILBERT_DIM = 2..8, HILBERT_ORDER = 8..32
+ *     - clean and optimize the code
+ */
+
+#ifndef HILBERT_PREFIX
+#  error Undefined HILBERT_PREFIX
+#endif
+
+#define P(x) HILBERT_PREFIX(x)
+
+/*
+ * HILBERT_DIM is the number of dimensions in space through which the
+ * Hilbert Curve passes.
+ * Don't use this implementation with values for HILBERT_DIM of > 31!
+ * Also, make sure you use a 32 bit compiler!
+ */
+#ifndef HILBERT_DIM
+#  define HILBERT_DIM 2
+#endif
+
+#ifndef HILBERT_TYPE
+#  define HILBERT_TYPE u32
+#endif
+
+#ifndef HILBERT_ORDER
+#  define HILBERT_ORDER (8 * sizeof(HILBERT_TYPE))
+#endif
+
+typedef HILBERT_TYPE P(t);
+
+/*
+ * retained for historical reasons: the number of bits in an attribute value:
+ * effectively the order of a curve
+ */
+#define NUMBITS HILBERT_ORDER
+
+/*
+ * the number of bits in a word used to store an hcode (or in an element of
+ * an array that's used)
+ */
+#define WORDBITS HILBERT_ORDER
+
+#ifdef HILBERT_WANT_ENCODE
+/*
+ * given the coordinates of a point, it finds the sequence number of the point
+ * on the Hilbert Curve
+ */
+static void
+P(encode) (P(t) *dest, P(t) *src)
+{
+  P(t) mask = (P(t))1 << WORDBITS - 1, element, temp1, temp2,
+    A, W = 0, S, tS, T, tT, J, P = 0, xJ;
+  uns i = NUMBITS * HILBERT_DIM - HILBERT_DIM, j;
+
+  for (j = 0; j < HILBERT_DIM; j++)
+    dest[j] = 0;
+  for (j = A = 0; j < HILBERT_DIM; j++)
+    if (src[j] & mask)
+      A |= (1 << HILBERT_DIM - 1 - j);
+
+  S = tS = A;
+
+  P |= S & (1 << HILBERT_DIM - 1);
+  for (j = 1; j < HILBERT_DIM; j++)
+    if( S & (1 << HILBERT_DIM - 1 - j) ^ (P >> 1) & (1 << HILBERT_DIM - 1 - j))
+      P |= (1 << HILBERT_DIM - 1 - j);
+
+  /* add in HILBERT_DIM bits to hcode */
+  element = i / WORDBITS;
+  if (i % WORDBITS > WORDBITS - HILBERT_DIM)
+    {
+      dest[element] |= P << i % WORDBITS;
+      dest[element + 1] |= P >> WORDBITS - i % WORDBITS;
+    }
+  else
+    dest[element] |= P << i - element * WORDBITS;
+
+  J = HILBERT_DIM;
+  for (j = 1; j < HILBERT_DIM; j++)
+    if ((P >> j & 1) == (P & 1))
+      continue;
+    else
+      break;
+  if (j != HILBERT_DIM)
+    J -= j;
+  xJ = J - 1;
+
+  if (P < 3)
+    T = 0;
+  else
+    if (P % 2)
+      T = (P - 1) ^ (P - 1) / 2;
+    else
+      T = (P - 2) ^ (P - 2) / 2;
+  tT = T;
+
+  for (i -= HILBERT_DIM, mask >>= 1; (int)i >= 0; i -= HILBERT_DIM, mask >>= 1)
+    {
+      for (j = A = 0; j < HILBERT_DIM; j++)
+        if (src[j] & mask)
+          A |= (1 << HILBERT_DIM - 1 - j);
+
+      W ^= tT;
+      tS = A ^ W;
+      if (xJ % HILBERT_DIM != 0)
+        {
+          temp1 = tS << xJ % HILBERT_DIM;
+          temp2 = tS >> HILBERT_DIM - xJ % HILBERT_DIM;
+         S = temp1 | temp2;
+         S &= ((P(t))1 << HILBERT_DIM) - 1;
+       }
+      else
+       S = tS;
+
+      P = S & (1 << HILBERT_DIM - 1);
+      for (j = 1; j < HILBERT_DIM; j++)
+       if( S & (1 << HILBERT_DIM - 1 - j) ^ (P >> 1) & (1 << HILBERT_DIM - 1 - j))
+         P |= (1 << HILBERT_DIM - 1 - j);
+
+      /* add in HILBERT_DIM bits to hcode */
+      element = i / WORDBITS;
+      if (i % WORDBITS > WORDBITS - HILBERT_DIM)
+       {
+         dest[element] |= P << i % WORDBITS;
+         dest[element + 1] |= P >> WORDBITS - i % WORDBITS;
+       }
+      else
+       dest[element] |= P << i - element * WORDBITS;
+
+      if (i > 0)
+       {
+         if (P < 3)
+           T = 0;
+         else
+           if (P % 2)
+             T = (P - 1) ^ (P - 1) / 2;
+           else
+             T = (P - 2) ^ (P - 2) / 2;
+
+         if (xJ % HILBERT_DIM != 0)
+           {
+             temp1 = T >> xJ % HILBERT_DIM;
+             temp2 = T << HILBERT_DIM - xJ % HILBERT_DIM;
+             tT = temp1 | temp2;
+             tT &= ((P(t))1 << HILBERT_DIM) - 1;
+           }
+         else
+           tT = T;
+
+         J = HILBERT_DIM;
+         for (j = 1; j < HILBERT_DIM; j++)
+           if ((P >> j & 1) == (P & 1))
+             continue;
+           else
+             break;
+         if (j != HILBERT_DIM)
+           J -= j;
+
+         xJ += J - 1;
+          /* J %= HILBERT_DIM; */
+       }
+    }
+  for (j = 0; j < HILBERT_DIM; j++)
+    dest[j] &= ~(P(t))0 >> (8 * sizeof(P(t)) - WORDBITS);
+}
+#endif
+
+#ifdef HILBERT_WANT_DECODE
+/*
+ * given the sequence number of a point, it finds the coordinates of the point
+ * on the Hilbert Curve
+ */
+static void
+P(decode) (P(t) *dest, P(t) *src)
+{
+  P(t) mask = (P(t))1 << WORDBITS - 1, element, temp1, temp2,
+    A, W = 0, S, tS, T, tT, J, P = 0, xJ;
+  uns i = NUMBITS * HILBERT_DIM - HILBERT_DIM, j;
+
+  for (j = 0; j < HILBERT_DIM; j++)
+    dest[j] = 0;
+
+  /*--- P ---*/
+  element = i / WORDBITS;
+  P = src[element];
+  if (i % WORDBITS > WORDBITS - HILBERT_DIM)
+    {
+      temp1 = src[element + 1];
+      P >>= i % WORDBITS;
+      temp1 <<= WORDBITS - i % WORDBITS;
+      P |= temp1;
+    }
+  else
+    P >>= i % WORDBITS;        /* P is a HILBERT_DIM bit hcode */
+
+  /* the & masks out spurious highbit values */
+  if (HILBERT_DIM < WORDBITS)
+    P &= (1 << HILBERT_DIM) -1;
+
+  /*--- xJ ---*/
+  J = HILBERT_DIM;
+  for (j = 1; j < HILBERT_DIM; j++)
+    if ((P >> j & 1) == (P & 1))
+      continue;
+    else
+      break;
+  if (j != HILBERT_DIM)
+    J -= j;
+  xJ = J - 1;
+
+  /*--- S, tS, A ---*/
+  A = S = tS = P ^ P / 2;
+
+
+  /*--- T ---*/
+  if (P < 3)
+    T = 0;
+  else
+    if (P % 2)
+      T = (P - 1) ^ (P - 1) / 2;
+    else
+      T = (P - 2) ^ (P - 2) / 2;
+
+  /*--- tT ---*/
+  tT = T;
+
+  /*--- distrib bits to coords ---*/
+  for (j = HILBERT_DIM - 1; P > 0; P >>=1, j--)
+    if (P & 1)
+      dest[j] |= mask;
+
+
+  for (i -= HILBERT_DIM, mask >>= 1; (int)i >= 0; i -= HILBERT_DIM, mask >>= 1)
+    {
+      /*--- P ---*/
+      element = i / WORDBITS;
+      P = src[element];
+      if (i % WORDBITS > WORDBITS - HILBERT_DIM)
+       {
+         temp1 = src[element + 1];
+         P >>= i % WORDBITS;
+         temp1 <<= WORDBITS - i % WORDBITS;
+         P |= temp1;
+       }
+      else
+       P >>= i % WORDBITS;     /* P is a HILBERT_DIM bit hcode */
+
+      /* the & masks out spurious highbit values */
+      if (HILBERT_DIM < WORDBITS)
+        P &= (1 << HILBERT_DIM) -1;
+
+      /*--- S ---*/
+      S = P ^ P / 2;
+
+      /*--- tS ---*/
+      if (xJ % HILBERT_DIM != 0)
+       {
+         temp1 = S >> xJ % HILBERT_DIM;
+         temp2 = S << HILBERT_DIM - xJ % HILBERT_DIM;
+         tS = temp1 | temp2;
+         tS &= ((P(t))1 << HILBERT_DIM) - 1;
+       }
+      else
+       tS = S;
+
+      /*--- W ---*/
+      W ^= tT;
+
+      /*--- A ---*/
+      A = W ^ tS;
+
+      /*--- distrib bits to coords ---*/
+      for (j = HILBERT_DIM - 1; A > 0; A >>=1, j--)
+       if (A & 1)
+         dest[j] |= mask;
+
+      if (i > 0)
+       {
+         /*--- T ---*/
+         if (P < 3)
+           T = 0;
+         else
+           if (P % 2)
+             T = (P - 1) ^ (P - 1) / 2;
+           else
+             T = (P - 2) ^ (P - 2) / 2;
+
+         /*--- tT ---*/
+         if (xJ % HILBERT_DIM != 0)
+           {
+             temp1 = T >> xJ % HILBERT_DIM;
+             temp2 = T << HILBERT_DIM - xJ % HILBERT_DIM;
+             tT = temp1 | temp2;
+             tT &= ((P(t))1 << HILBERT_DIM) - 1;
+           }
+         else
+           tT = T;
+
+         /*--- xJ ---*/
+         J = HILBERT_DIM;
+         for (j = 1; j < HILBERT_DIM; j++)
+           if ((P >> j & 1) == (P & 1))
+             continue;
+           else
+             break;
+         if (j != HILBERT_DIM)
+           J -= j;
+         xJ += J - 1;
+       }
+    }
+}
+#endif
+
+#undef P
+#undef HILBERT_PREFIX
+#undef HILBERT_DIM
+#undef HILBERT_TYPE
+#undef HILBERT_ORDER
+#undef HILBERT_WANT_DECODE
+#undef HILBERT_WANT_ENCODE
+#undef NUMBITS
+#undef WORDBITS
diff --git a/images/image-dup-test.c b/images/image-dup-test.c
new file mode 100644 (file)
index 0000000..cbd37e5
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ *     Image duplicates testing
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/getopt.h"
+#include "lib/fastbuf.h"
+#include "lib/mempool.h"
+#include "images/images.h"
+#include "images/color.h"
+#include "images/duplicates.h"
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+
+static void NONRET
+usage(void)
+{
+  fputs("\
+Usage: image-dup-test [options] image1 image2 \n\
+\n\
+-q --quiet           no progress messages\n\
+-f --format-1        image1 format (jpeg, gif, png)\n\
+-F --format-2        image2 format\n\
+-g --background      background color (hexadecimal RRGGBB)\n\
+-t --transformations hexadecimal value of allowed transformtion (1=identity, FF=all)\n\
+", stderr);
+  exit(1);
+}
+
+static char *shortopts = "qf:F:g:t:" CF_SHORT_OPTS;
+static struct option longopts[] =
+{
+  CF_LONG_OPTS
+  { "quiet",           0, 0, 'q' },
+  { "format-1",                0, 0, 'f' },
+  { "format-2",                0, 0, 'F' },
+  { "background",      0, 0, 'g' },
+  { "transormations",  0, 0, 't' },
+  { NULL,              0, 0, 0 }
+};
+                                                         
+static uns verbose = 1;
+static byte *file_name_1;
+static byte *file_name_2;
+static enum image_format format_1;
+static enum image_format format_2;
+static struct color background_color;
+static uns transformations = IMAGE_DUP_TRANS_ALL;
+
+#define MSG(x...) do{ if (verbose) log(L_INFO, ##x); }while(0)
+
+int
+main(int argc, char **argv)
+{
+  log_init(argv[0]);
+  int opt;
+  while ((opt = cf_getopt(argc, argv, shortopts, longopts, NULL)) >= 0)
+    switch (opt)
+      {
+       case 'q':
+         verbose = 0;
+         break;
+       case 'f':
+         if (!(format_1 = image_extension_to_format(optarg)))
+           usage();
+         break;
+       case 'F':
+         if (!(format_2 = image_extension_to_format(optarg)))
+           usage();
+         break;
+       case 'g':
+         {
+           if (strlen(optarg) != 6)
+             usage();
+           errno = 0;
+           char *end;
+           long int v = strtol(optarg, &end, 16);
+           if (errno || *end || v < 0)
+             usage();
+           color_make_rgb(&background_color, (v >> 16) & 255, (v >> 8) & 255, v & 255);
+         }
+         break;
+       case 't':
+         {
+           errno = 0;
+           char *end;
+           long int v = strtol(optarg, &end, 16);
+           if (errno || *end || v < 0 || v > 0xff)
+             usage();
+           transformations = v;
+         }
+         break;
+       default:
+         usage();
+      }
+
+  if (argc != optind + 2)
+    usage();
+  file_name_1 = argv[optind++];
+  file_name_2 = argv[optind];
+  
+#define TRY(x) do{ if (!(x)) exit(1); }while(0)
+  MSG("Initializing image library");
+  struct image_context ctx;
+  struct image_io io;
+  image_context_init(&ctx);
+
+  struct image *img1, *img2;
+
+  TRY(image_io_init(&ctx, &io));
+  MSG("Reading %s", file_name_1);
+  io.fastbuf = bopen(file_name_1, O_RDONLY, 1 << 18);
+  io.format = format_1 ? : image_file_name_to_format(file_name_1);
+  TRY(image_io_read_header(&io));
+  io.flags = COLOR_SPACE_RGB | IMAGE_IO_USE_BACKGROUND;
+  if (background_color.color_space)
+    io.background_color = background_color;
+  else if (!io.background_color.color_space)
+    io.background_color = color_black;
+  TRY(image_io_read_data(&io, 1));
+  bclose(io.fastbuf);
+  img1 = io.image;
+  MSG("Image size=%ux%u", img1->cols, img1->rows);
+  
+  image_io_reset(&io);
+  MSG("Reading %s", file_name_2);
+  io.fastbuf = bopen(file_name_2, O_RDONLY, 1 << 18);
+  io.format = format_2 ? : image_file_name_to_format(file_name_2);
+  TRY(image_io_read_header(&io));
+  io.flags = COLOR_SPACE_RGB | IMAGE_IO_USE_BACKGROUND;
+  if (background_color.color_space)
+    io.background_color = background_color;
+  else if (!io.background_color.color_space)
+    io.background_color = color_black;
+  TRY(image_io_read_data(&io, 1));
+  bclose(io.fastbuf);
+  img2 = io.image;
+  image_io_cleanup(&io);
+  MSG("Image size=%ux%u", img2->cols, img2->rows);
+
+  struct image_dup dup1, dup2;
+  struct mempool *pool = mp_new(1 << 18);
+  MSG("Creating internal structures");
+  TRY(image_dup_init(&ctx, &dup1, img1, pool));
+  TRY(image_dup_init(&ctx, &dup2, img2, pool));
+
+  MSG("Similarity bitmap %02x", image_dup_compare(&dup1, &dup2, transformations | IMAGE_DUP_SCALE | IMAGE_DUP_WANT_ALL));
+
+  mp_delete(pool);
+  
+  image_destroy(img1);
+  image_destroy(img2);
+  image_context_cleanup(&ctx);
+  MSG("Done.");
+  return 0;
+}
diff --git a/images/image-sim-test.c b/images/image-sim-test.c
new file mode 100644 (file)
index 0000000..22f3951
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ *     Image similarity testing
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/getopt.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/color.h"
+#include "images/signature.h"
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static void NONRET
+usage(void)
+{
+  fputs("\
+Usage: image-sim-test [options] image1 [image2] \n\
+\n\
+-q --quiet           no progress messages\n\
+-f --format-1        image1 format (jpeg, gif, png)\n\
+-F --format-2        image2 format\n\
+-g --background      background color (hexadecimal RRGGBB)\n\
+-r --segmentation-1  writes image1 segmentation to given file\n\
+-R --segmentation-2  writes image2 segmentation to given file\n\
+", stderr);
+  exit(1);
+}
+
+static char *shortopts = "qf:F:g:t:r:R:" CF_SHORT_OPTS;
+static struct option longopts[] =
+{
+  CF_LONG_OPTS
+  { "quiet",           0, 0, 'q' },
+  { "format-1",                0, 0, 'f' },
+  { "format-2",                0, 0, 'F' },
+  { "background",      0, 0, 'g' },
+  { "segmentation-1",  0, 0, 'r' },
+  { "segmentation-2",  0, 0, 'R' },
+  { NULL,              0, 0, 0 }
+};
+
+static uns verbose = 1;
+static byte *file_name_1;
+static byte *file_name_2;
+static enum image_format format_1;
+static enum image_format format_2;
+static struct color background_color;
+static byte *segmentation_name_1;
+static byte *segmentation_name_2;
+
+#define MSG(x...) do{ if (verbose) log(L_INFO, ##x); }while(0)
+#define TRY(x) do{ if (!(x)) exit(1); }while(0)
+
+static void
+msg_str(byte *s, void *param UNUSED)
+{
+  MSG("%s", s);
+}
+
+static void
+dump_signature(struct image_signature *sig)
+{
+  byte buf[MAX(IMAGE_VECTOR_DUMP_MAX, IMAGE_REGION_DUMP_MAX)];
+  image_vector_dump(buf, &sig->vec);
+  MSG("vector: %s", buf);
+  for (uns i = 0; i < sig->len; i++)
+    {
+      image_region_dump(buf, sig->reg + i);
+      MSG("region %u: %s", i, buf);
+    }
+}
+
+static struct image_context ctx;
+static struct image_io io;
+
+static void
+write_segmentation(struct image_sig_data *data, byte *fn)
+{
+  MSG("Writing segmentation to %s", fn);
+  
+  struct fastbuf *fb = bopen(fn, O_WRONLY | O_CREAT | O_TRUNC, 4096);
+  struct image *img;
+  TRY(img = image_new(&ctx, data->image->cols, data->image->rows, COLOR_SPACE_RGB, NULL));
+  image_clear(&ctx, img);
+
+  for (uns i = 0; i < data->regions_count; i++)
+    {
+      byte c[3];
+      double luv[3], xyz[3], srgb[3];
+      luv[0] = data->regions[i].a[0] * (4 / 2.55);
+      luv[1] = ((int)data->regions[i].a[1] - 128) * (4 / 2.55);
+      luv[2] = ((int)data->regions[i].a[2] - 128) * (4 / 2.55);
+      luv_to_xyz_exact(xyz, luv);
+      xyz_to_srgb_exact(srgb, xyz);
+      c[0] = CLAMP(srgb[0] * 255, 0, 255); 
+      c[1] = CLAMP(srgb[1] * 255, 0, 255); 
+      c[2] = CLAMP(srgb[2] * 255, 0, 255); 
+      for (struct image_sig_block *block = data->regions[i].blocks; block; block = block->next)
+        {
+         uns x1 = block->x * 4;
+         uns y1 = block->y * 4;
+         uns x2 = MIN(x1 + 4, img->cols);
+         uns y2 = MIN(y1 + 4, img->rows);
+         byte *p = img->pixels + x1 * 3 + y1 * img->row_size;
+         for (uns y = y1; y < y2; y++, p += img->row_size)
+           {
+             byte *p2 = p;
+             for (uns x = x1; x < x2; x++, p2 += 3)
+               {
+                 p2[0] = c[0];
+                 p2[1] = c[1];
+                 p2[2] = c[2];
+               }
+           }
+        }
+    }
+
+  io.fastbuf = fb;
+  io.image = img;
+  io.format = image_file_name_to_format(fn); 
+  TRY(image_io_write(&io));
+  image_io_reset(&io);
+
+  image_destroy(img);
+  bclose(fb);
+}
+
+int
+main(int argc, char **argv)
+{
+  log_init(argv[0]);
+  int opt;
+  while ((opt = cf_getopt(argc, argv, shortopts, longopts, NULL)) >= 0)
+    switch (opt)
+      {
+       case 'q':
+         verbose = 0;
+         break;
+       case 'f':
+         if (!(format_1 = image_extension_to_format(optarg)))
+           usage();
+         break;
+       case 'F':
+         if (!(format_2 = image_extension_to_format(optarg)))
+           usage();
+         break;
+       case 'g':
+         {
+           if (strlen(optarg) != 6)
+             usage();
+           errno = 0;
+           char *end;
+           long int v = strtol(optarg, &end, 16);
+           if (errno || *end || v < 0)
+             usage();
+           color_make_rgb(&background_color, (v >> 16) & 255, (v >> 8) & 255, v & 255);
+         }
+         break;
+       case 'r':
+         segmentation_name_1 = optarg;
+         break;
+       case 'R':
+         segmentation_name_2 = optarg;
+         break;
+       default:
+         usage();
+      }
+
+  if (argc != optind + 2 && argc != optind + 1)
+    usage();
+  file_name_1 = argv[optind++];
+  if (argc > optind)
+    file_name_2 = argv[optind++];
+
+  MSG("Initializing image library");
+  srandom(time(NULL) ^ getpid());
+  srgb_to_luv_init();
+  image_context_init(&ctx);
+
+  struct image *img1, *img2;
+
+  TRY(image_io_init(&ctx, &io));
+
+  if (file_name_1)
+    {
+      MSG("Reading %s", file_name_1);
+      io.fastbuf = bopen(file_name_1, O_RDONLY, 1 << 18);
+      io.format = format_1 ? : image_file_name_to_format(file_name_1);
+      TRY(image_io_read_header(&io));
+      io.flags = COLOR_SPACE_RGB | IMAGE_IO_USE_BACKGROUND;
+      if (background_color.color_space)
+        io.background_color = background_color;
+      else if (!io.background_color.color_space)
+        io.background_color = color_black;
+      TRY(image_io_read_data(&io, 1));
+      bclose(io.fastbuf);
+      img1 = io.image;
+      MSG("Image size=%ux%u", img1->cols, img1->rows);
+      image_io_reset(&io);
+    }
+  else
+    img1 = NULL;
+
+  if (file_name_2)
+    {
+      MSG("Reading %s", file_name_2);
+      io.fastbuf = bopen(file_name_2, O_RDONLY, 1 << 18);
+      io.format = format_2 ? : image_file_name_to_format(file_name_2);
+      TRY(image_io_read_header(&io));
+      io.flags = COLOR_SPACE_RGB | IMAGE_IO_USE_BACKGROUND;
+      if (background_color.color_space)
+        io.background_color = background_color;
+      else if (!io.background_color.color_space)
+        io.background_color = color_black;
+      TRY(image_io_read_data(&io, 1));
+      bclose(io.fastbuf);
+      img2 = io.image;
+      MSG("Image size=%ux%u", img2->cols, img2->rows);
+      image_io_reset(&io);
+    }
+  else
+    img2 = NULL;
+
+  struct image_signature sig1, sig2;
+  MSG("Computing signatures");
+  if (img1)
+    {
+      struct image_sig_data data;
+      TRY(image_sig_init(&ctx, &data, img1));
+      image_sig_preprocess(&data);
+      if (data.valid)
+        {
+         image_sig_segmentation(&data);
+         image_sig_detect_textured(&data);
+       }
+      if (segmentation_name_1)
+       write_segmentation(&data, segmentation_name_1);
+      image_sig_finish(&data, &sig1);
+      image_sig_cleanup(&data);
+      dump_signature(&sig1);
+    }
+  if (img2)
+    {
+      struct image_sig_data data;
+      TRY(image_sig_init(&ctx, &data, img2));
+      image_sig_preprocess(&data);
+      if (data.valid)
+        {
+         image_sig_segmentation(&data);
+         image_sig_detect_textured(&data);
+       }
+      if (segmentation_name_2)
+       write_segmentation(&data, segmentation_name_2);
+      image_sig_finish(&data, &sig2);
+      image_sig_cleanup(&data);
+      dump_signature(&sig2);
+    }
+
+  if (img1 && img2)
+    {
+      uns dist;
+      if (verbose)
+        {
+          struct fastbuf *fb = bfdopen(0, 4096);
+          dist = image_signatures_dist_explain(&sig1, &sig2, msg_str, NULL);
+          bclose(fb);
+       }
+      else
+       dist = image_signatures_dist(&sig1, &sig2);
+      MSG("dist=%u", dist);
+    }
+
+  if (img1)
+    image_destroy(img1);
+  if (img2)
+    image_destroy(img2);
+
+  image_io_cleanup(&io);
+  image_context_cleanup(&ctx);
+  MSG("Done.");
+  return 0;
+}
diff --git a/images/image-test.c b/images/image-test.c
new file mode 100644 (file)
index 0000000..b046672
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ *     Image Library -- Simple automatic tests
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/color.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <time.h>
+#include <unistd.h>
+
+static uns want_image_iface;
+static uns want_threads;
+
+#define TRY(x) do { if (!(x)) ASSERT(0); } while (0)
+
+static void
+test_image_iface(void)
+{
+  struct mempool *pool;
+  struct image_context ctx;
+  struct image *i1, *i2;
+  struct image s1;
+
+  pool = mp_new(1024);
+  image_context_init(&ctx);
+
+  /* Image allocation */
+  i1 = image_new(&ctx, 731, 327, COLOR_SPACE_RGB, NULL);
+  ASSERT(i1);
+  ASSERT(i1->pixel_size == 3);
+  image_destroy(i1);
+
+  /* Test invalid image size  */
+  ctx.msg_callback = image_context_msg_silent;
+  i1 = image_new(&ctx, 2214, 0, COLOR_SPACE_RGB, NULL);
+  ASSERT(!i1);
+  i1 = image_new(&ctx, 0xffffff, 0xffffff, COLOR_SPACE_RGB, NULL);
+  ASSERT(!i1);
+  ctx.msg_callback = image_context_msg_default;
+
+  /* Various image allocatio parameters */
+  i1 = image_new(&ctx, 370, 100, COLOR_SPACE_GRAYSCALE, pool);
+  ASSERT(i1);
+  ASSERT(i1->pixel_size == 1);
+  image_destroy(i1);
+  mp_flush(pool);
+
+  i1 = image_new(&ctx, 373, 101, COLOR_SPACE_RGB | IMAGE_ALIGNED, NULL);
+  ASSERT(i1);
+  ASSERT(i1->pixel_size == 4);
+  ASSERT(IMAGE_SSE_ALIGN_SIZE >= 16);
+  ASSERT(!(i1->row_size & (IMAGE_SSE_ALIGN_SIZE - 1)));
+  ASSERT(!((addr_int_t)i1->pixels & (IMAGE_SSE_ALIGN_SIZE - 1)));
+  image_destroy(i1);
+
+  i1 = image_new(&ctx, 283, 329, COLOR_SPACE_RGB, NULL);
+  ASSERT(i1);
+  ASSERT(i1->pixel_size == 3);
+
+  /* Image structures cloning */
+  i2 = image_clone(&ctx, i1, COLOR_SPACE_RGB, NULL);
+  ASSERT(i2);
+  ASSERT(i2->pixel_size == 3);
+  image_destroy(i2);
+
+  i2 = image_clone(&ctx, i1, COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED, NULL);
+  ASSERT(i2);
+  ASSERT(i2->pixel_size == 4);
+  image_destroy(i2);
+
+  /* Subimages */
+  i2 = image_init_subimage(&ctx, &s1, i1, 29, 39, 283 - 29, 100);
+  ASSERT(i2);
+  image_destroy(&s1);
+
+  image_destroy(i1);
+
+  image_context_cleanup(&ctx);
+  mp_delete(pool);
+}
+
+#define TEST_THREADS_COUNT 4
+
+static void *
+test_threads_thread(void *param UNUSED)
+{
+  DBG("Starting thread");
+  struct image_context ctx;
+  struct image_io io;
+  image_context_init(&ctx);
+  TRY(image_io_init(&ctx, &io));
+
+  for (uns num = 0; num < 200; num++)
+    {
+      int r0 = random_max(100);
+
+      /* realloc context */
+      if ((r0 -= 2) < 0)
+        {
+         image_io_cleanup(&io);
+         image_context_cleanup(&ctx);
+         image_context_init(&ctx);
+         TRY(image_io_init(&ctx, &io));
+       }
+
+      /* realloc I/O */
+      else if ((r0 -= 2) < 0)
+        {
+         image_io_cleanup(&io);
+         TRY(image_io_init(&ctx, &io));
+       }
+
+      /* encode and decode random image */
+      else
+        {
+          struct image *img;
+
+          TRY(img = image_new(&ctx, 10 + random_max(140), 10 + random_max(140), COLOR_SPACE_RGB, NULL));
+         image_clear(&ctx, img);
+
+#if defined(CONFIG_IMAGES_LIBJPEG) || defined(CONFIG_IMAGES_LIBPNG) || defined(CONFIG_IMAGES_LIBMAGICK)
+
+         struct fastbuf *wfb = fbmem_create(10000);
+         struct fastbuf *rfb;
+         uns format = 0;
+         while (!format)
+           {
+             switch (random_max(3))
+               {
+                 case 0:
+#if defined(CONFIG_IMAGES_LIBJPEG) || defined(CONFIG_IMAGES_LIBMAGICK)
+                   format = IMAGE_FORMAT_JPEG;
+#endif
+                   break;
+                 case 1:
+#if defined(CONFIG_IMAGES_LIBPNG) || defined(CONFIG_IMAGES_LIBMAGICK)
+                   format = IMAGE_FORMAT_PNG;
+#endif
+                   break;
+                 case 2:
+#if defined(CONFIG_IMAGES_LIBMAGICK)
+                   format = IMAGE_FORMAT_GIF;
+#endif
+                   break;
+                 default:
+                   ASSERT(0);
+               }
+           }
+
+         io.format = format;
+         io.fastbuf = wfb;
+         io.image = img;
+         TRY(image_io_write(&io));
+         image_io_reset(&io);
+
+         rfb = fbmem_clone_read(wfb);
+         io.format = format;
+         io.fastbuf = rfb;
+         TRY(image_io_read(&io, 0));
+         image_io_reset(&io);
+
+         bclose(rfb);
+         bclose(wfb);
+
+#endif
+          image_destroy(img);
+       }
+    }
+
+  image_io_cleanup(&io);
+  image_context_cleanup(&ctx);
+  DBG("Stopping thread");
+  return NULL;
+}
+
+static void
+test_threads(void)
+{
+  pthread_t threads[TEST_THREADS_COUNT - 1];
+  pthread_attr_t attr;
+  if (pthread_attr_init(&attr) < 0 ||
+      pthread_attr_setstacksize(&attr, 1 << 20) < 0)
+    ASSERT(0);
+  for (uns i = 0; i < TEST_THREADS_COUNT - 1; i++)
+    {
+      if (pthread_create(threads + i, &attr, test_threads_thread, NULL) < 0)
+        die("Unable to create thread: %m");
+    }
+  test_threads_thread(NULL);
+  for (uns i = 0; i < TEST_THREADS_COUNT - 1; i++)
+    if (pthread_join(threads[i], NULL) < 0)
+      die("Cannot join thread: %m");
+}
+
+int
+main(int argc, char **argv)
+{
+  for (int i = 1; i < argc; i++)
+    if (!strcmp(argv[i], "image-iface"))
+      want_image_iface++;
+    else if (!strcmp(argv[i], "threads"))
+      want_threads++;
+    else
+      die("Invalid parameter");
+
+  srandom(time(NULL) ^ getpid());
+
+  if (want_image_iface)
+    test_image_iface();
+  if (want_threads)
+    test_threads();
+
+  return 0;
+}
+
diff --git a/images/image-tool.c b/images/image-tool.c
new file mode 100644 (file)
index 0000000..3e40bcb
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ *     Image Library -- Simple image manipulation utility
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/color.h"
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+
+static void NONRET
+usage(void)
+{
+  fputs("\
+Usage: image-tool [options] infile [outfile]\n\
+\n\
+-q --quiet               no progress messages\n\
+-f --input-format        input image format (jpeg, gif, png)\n\
+-F --output-format       output image format\n\
+-s --size                force output dimensions (100x200)\n\
+-b --fit-to-box          scale to fit the box (100x200)\n\
+-c --colorspace          force output colorspace (Gray, GrayAlpha, RGB, RGBAlpha)\n\
+-Q --jpeg-quality        JPEG quality (1..100)\n\
+-g --background          background color (hexadecimal RRGGBB)\n\
+-G --default-background  background applied only if the image contains no background info (RRGGBB, default=FFFFFF)\n\
+-a --remove-alpha        remove alpha channel\n\
+-e --exif                reads Exif data\n"
+, stderr);
+  exit(1);
+}
+
+static char *shortopts = "qf:F:s:b:c:Q:g:G:ae";
+static struct option longopts[] =
+{
+  { "quiet",                   0, 0, 'q' },
+  { "input-format",            0, 0, 'f' },
+  { "output-format",           0, 0, 'F' },
+  { "size",                    0, 0, 's' },
+  { "fit-to-box",              0, 0, 'b' },
+  { "colorspace",              0, 0, 'c' },
+  { "jpeg-quality",            0, 0, 'Q' },
+  { "background",              0, 0, 'g' },
+  { "default-background",      0, 0, 'G' },
+  { "remove-alpha",            0, 0, 'a' },
+  { "exif",                    0, 0, 'e' },
+  { NULL,                      0, 0, 0 }
+};
+
+static uns verbose = 1;
+static byte *input_file_name;
+static enum image_format input_format;
+static byte *output_file_name;
+static enum image_format output_format;
+static uns cols;
+static uns rows;
+static uns fit_to_box;
+static uns channels_format;
+static uns jpeg_quality;
+static struct color background_color;
+static struct color default_background_color;
+static uns remove_alpha;
+static uns exif;
+
+static void
+parse_color(struct color *color, byte *s)
+{
+  if (strlen(s) != 6)
+    usage();
+  errno = 0;
+  char *end;
+  long int v = strtol(s, &end, 16);
+  if (errno || *end || v < 0)
+    usage();
+  color_make_rgb(color, (v >> 16) & 255, (v >> 8) & 255, v & 255);
+}
+
+#define MSG(x...) do{ if (verbose) log(L_INFO, ##x); }while(0)
+
+int
+main(int argc, char **argv)
+{
+  log_init(argv[0]);
+  int opt;
+  default_background_color = color_white;
+  while ((opt = getopt_long(argc, argv, shortopts, longopts, NULL)) >= 0)
+    switch (opt)
+      {
+       case 'q':
+         verbose = 0;
+         break;
+       case 'f':
+         if (!(input_format = image_extension_to_format(optarg)))
+           usage();
+         break;
+       case 'F':
+         if (!(output_format = image_extension_to_format(optarg)))
+           usage();
+         break;
+       case 's':
+         {
+           byte *r = strchr(optarg, 'x');
+           if (!r)
+             usage();
+           *r++ = 0;
+           if (!(cols = atoi(optarg)) || !(rows = atoi(r)))
+             usage();
+           fit_to_box = 0;
+           break;
+         }
+       case 'b':
+         {
+           byte *r = strchr(optarg, 'x');
+           if (!r)
+             usage();
+           *r++ = 0;
+           if (!(cols = atoi(optarg)) || !(rows = atoi(r)))
+             usage();
+           fit_to_box = 1;
+           break;
+         }
+       case 'c':
+         if (!(channels_format = image_name_to_channels_format(optarg)))
+           usage();
+         break;
+       case 'Q':
+         if (!(jpeg_quality = atoi(optarg)))
+           usage();
+         break;
+       case 'g':
+         parse_color(&background_color, optarg);
+         break;
+       case 'G':
+         parse_color(&default_background_color, optarg);
+         break;
+       case 'a':
+         remove_alpha++;
+         break;
+       case 'e':
+         exif++;
+         break;
+       default:
+         usage();
+      }
+
+  if (argc != optind + 1 && argc != optind + 2)
+    usage();
+  input_file_name = argv[optind++];
+  if (argc > optind)
+    output_file_name = argv[optind];
+
+#define TRY(x) do{ if (!(x)) exit(1); }while(0)
+  MSG("Initializing image library");
+  struct image_context ctx;
+  struct image_io io;
+  image_context_init(&ctx);
+  ctx.tracing_level = ~0U;
+  if (!image_io_init(&ctx, &io))
+    die("Cannot initialize image I/O");
+
+  MSG("Reading %s", input_file_name);
+  io.fastbuf = bopen(input_file_name, O_RDONLY, 1 << 18);
+  io.format = input_format ? : image_file_name_to_format(input_file_name);
+  if (exif)
+    io.flags |= IMAGE_IO_WANT_EXIF;
+  TRY(image_io_read_header(&io));
+  if (!output_file_name)
+    {
+      bclose(io.fastbuf);
+      printf("Format:      %s\n", image_format_to_extension(io.format) ? : (byte *)"?");
+      printf("Dimensions:  %dx%d\n", io.cols, io.rows);
+      printf("Colorspace:  %s\n", (io.flags & IMAGE_IO_HAS_PALETTE) ? (byte *)"Palette" : image_channels_format_to_name(io.flags & IMAGE_CHANNELS_FORMAT));
+      printf("NumColors:   %d\n", io.number_of_colors);
+      if (io.background_color.color_space)
+        {
+         byte rgb[3];
+         color_put_rgb(rgb, &io.background_color);
+          printf("Background:  %02x%02x%02x\n", rgb[0], rgb[1], rgb[2]);
+       }
+      if (io.exif_size)
+       printf("ExifSize:    %u\n", io.exif_size);
+    }
+  else
+    {
+      MSG("%s %dx%d %s", image_format_to_extension(io.format) ? : (byte *)"?", io.cols, io.rows,
+         (io.flags & IMAGE_IO_HAS_PALETTE) ? (byte *)"Palette" : image_channels_format_to_name(io.flags & IMAGE_CHANNELS_FORMAT));
+      if (cols)
+        if (fit_to_box)
+         {
+            image_dimensions_fit_to_box(&io.cols, &io.rows, MIN(cols, 0xffff), MIN(rows, 0xffff), 0);
+         }
+        else
+          {
+            io.cols = cols;
+            io.rows = rows;
+          }
+      if (background_color.color_space)
+       io.background_color = background_color;
+      else if (!io.background_color.color_space)
+       io.background_color = default_background_color;
+      if (remove_alpha)
+       io.flags &= ~IMAGE_ALPHA;
+      if (channels_format)
+        io.flags = io.flags & ~IMAGE_PIXEL_FORMAT | channels_format;
+      if (!(io.flags & IMAGE_ALPHA))
+        io.flags |= IMAGE_IO_USE_BACKGROUND;
+      if (jpeg_quality)
+       io.jpeg_quality = jpeg_quality;
+      TRY(image_io_read_data(&io, 0));
+      bclose(io.fastbuf);
+      MSG("Writing %s", output_file_name);
+      io.fastbuf = bopen(output_file_name, O_WRONLY | O_CREAT | O_TRUNC, 1 << 18);
+      io.format = output_format ? : image_file_name_to_format(output_file_name);
+      MSG("%s %dx%d %s", image_format_to_extension(io.format) ? : (byte *)"?", io.cols, io.rows,
+         image_channels_format_to_name(io.flags & IMAGE_CHANNELS_FORMAT));
+      TRY(image_io_write(&io));
+      bclose(io.fastbuf);
+    }
+
+  image_io_cleanup(&io);
+  image_context_cleanup(&ctx);
+  MSG("Done.");
+  return 0;
+}
diff --git a/images/image-walk.h b/images/image-walk.h
new file mode 100644 (file)
index 0000000..dfc72a1
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ *     Image Library -- Pixels iteration
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#ifndef IMAGE_WALK_PREFIX
+#  error Undefined IMAGE_WALK_PREFIX
+#endif
+
+#define P(x) IMAGE_WALK_PREFIX(x)
+
+#if !defined(IMAGE_WALK_UNROLL)
+#  define IMAGE_WALK_UNROLL 1
+#elif IMAGE_WALK_UNROLL != 1 && IMAGE_WALK_UNROLL != 2 && IMAGE_WALK_UNROLL != 4
+#  error IMAGE_WALK_UNROLL must be 1, 2 or 4
+#endif
+
+#ifndef IMAGE_WALK_IMAGE
+#  define IMAGE_WALK_IMAGE P(img)
+#endif
+#ifndef IMAGE_WALK_PIXELS
+#  define IMAGE_WALK_PIXELS (IMAGE_WALK_IMAGE->pixels)
+#endif
+#ifndef IMAGE_WALK_COLS
+#  define IMAGE_WALK_COLS (IMAGE_WALK_IMAGE->cols)
+#endif
+#ifndef IMAGE_WALK_ROWS
+#  define IMAGE_WALK_ROWS (IMAGE_WALK_IMAGE->rows)
+#endif
+#ifndef IMAGE_WALK_COL_STEP
+#  define IMAGE_WALK_COL_STEP (IMAGE_WALK_IMAGE->pixel_size)
+#endif
+#ifndef IMAGE_WALK_ROW_STEP
+#  define IMAGE_WALK_ROW_STEP (IMAGE_WALK_IMAGE->row_size)
+#endif
+
+#ifdef IMAGE_WALK_DOUBLE
+#  ifndef IMAGE_WALK_SEC_IMAGE
+#    define IMAGE_WALK_SEC_IMAGE P(sec_img)
+#  endif
+#  ifndef IMAGE_WALK_SEC_PIXELS
+#    define IMAGE_WALK_SEC_PIXELS (IMAGE_WALK_SEC_IMAGE->pixels)
+#  endif
+#  ifndef IMAGE_WALK_SEC_COLS
+#    define IMAGE_WALK_SEC_COLS (IMAGE_WALK_SEC_IMAGE->cols)
+#  endif
+#  ifndef IMAGE_WALK_SEC_ROWS
+#    define IMAGE_WALK_SEC_ROWS (IMAGE_WALK_SEC_IMAGE->rows)
+#  endif
+#  ifndef IMAGE_WALK_SEC_COL_STEP
+#    define IMAGE_WALK_SEC_COL_STEP (IMAGE_WALK_SEC_IMAGE->pixel_size)
+#  endif
+#  ifndef IMAGE_WALK_SEC_ROW_STEP
+#    define IMAGE_WALK_SEC_ROW_STEP (IMAGE_WALK_SEC_IMAGE->row_size)
+#  endif
+#  define IMAGE_WALK__STEP IMAGE_WALK_DO_STEP; P(pos) += P(col_step); P(sec_pos) += P(sec_col_step)
+#else
+#  define IMAGE_WALK__STEP IMAGE_WALK_DO_STEP; P(pos) += P(col_step)
+#endif
+
+#ifndef IMAGE_WALK_DO_START
+#  define IMAGE_WALK_DO_START
+#endif
+
+#ifndef IMAGE_WALK_DO_END
+#  define IMAGE_WALK_DO_END
+#endif
+
+#ifndef IMAGE_WALK_DO_ROW_START
+#  define IMAGE_WALK_DO_ROW_START
+#endif
+
+#ifndef IMAGE_WALK_DO_ROW_END
+#  define IMAGE_WALK_DO_ROW_END
+#endif
+
+#ifndef IMAGE_WALK_DO_STEP
+#  define IMAGE_WALK_DO_STEP
+#endif
+
+#ifndef IMAGE_WALK_INLINE
+static void P(walk)
+    (struct image *P(img)
+#   ifdef IMAGE_WALK_DOUBLE
+    , struct image *P(sec_img)
+#   endif
+#   ifdef IMAGE_WALK_EXTRA_ARGS
+    , IMAGE_WALK_EXTRA_ARGS
+#   endif
+    )
+#endif
+{
+  uns P(cols) = IMAGE_WALK_COLS;
+  uns P(rows) = IMAGE_WALK_ROWS;
+# if IMAGE_WALK_UNROLL > 1
+  uns P(cols_unroll_block_count) = P(cols) / IMAGE_WALK_UNROLL;
+  uns P(cols_unroll_end_count) = P(cols) % IMAGE_WALK_UNROLL;
+# endif
+  byte *P(pos) = IMAGE_WALK_PIXELS, *P(row_start) = P(pos);
+  int P(col_step) = IMAGE_WALK_COL_STEP;
+  int P(row_step) = IMAGE_WALK_ROW_STEP;
+# ifdef IMAGE_WALK_DOUBLE
+  byte *P(sec_pos) = IMAGE_WALK_SEC_PIXELS, *P(sec_row_start) = P(sec_pos);
+  int P(sec_col_step) = IMAGE_WALK_SEC_COL_STEP;
+  int P(sec_row_step) = IMAGE_WALK_SEC_ROW_STEP;
+# endif
+  IMAGE_WALK_DO_START;
+  while (P(rows)--)
+    {
+      IMAGE_WALK_DO_ROW_START;
+#     if IMAGE_WALK_UNROLL == 1
+      for (uns P(_i) = P(cols); P(_i)--; )
+#     else
+      for (uns P(_i) = P(cols_unroll_block_count); P(_i)--; )
+#     endif
+        {
+#         if IMAGE_WALK_UNROLL >= 4
+         IMAGE_WALK__STEP;
+         IMAGE_WALK__STEP;
+#         endif
+#         if IMAGE_WALK_UNROLL >= 2
+         IMAGE_WALK__STEP;
+#         endif
+         IMAGE_WALK__STEP;
+       }
+#     if IMAGE_WALK_UNROLL > 1
+      for (uns P(_i) = P(cols_unroll_end_count); P(_i)--; )
+        {
+         IMAGE_WALK__STEP;
+       }
+#     endif
+      IMAGE_WALK_DO_ROW_END;
+      P(pos) = (P(row_start) += P(row_step));
+#     ifdef IMAGE_WALK_DOUBLE
+      P(sec_pos) = (P(sec_row_start) += P(sec_row_step));
+#     endif
+    }
+  IMAGE_WALK_DO_END;
+}
+
+#undef IMAGE_WALK_PREFIX
+#undef IMAGE_WALK_INLINE
+#undef IMAGE_WALK_UNROLL
+#undef IMAGE_WALK_DOUBLE
+#undef IMAGE_WALK_EXTRA_ARGS
+#undef IMAGE_WALK_IMAGE
+#undef IMAGE_WALK_PIXELS
+#undef IMAGE_WALK_COLS
+#undef IMAGE_WALK_ROWS
+#undef IMAGE_WALK_COL_STEP
+#undef IMAGE_WALK_ROW_STEP
+#undef IMAGE_WALK_SEC_IMAGE
+#undef IMAGE_WALK_SEC_PIXELS
+#undef IMAGE_WALK_SEC_COLS
+#undef IMAGE_WALK_SEC_ROWS
+#undef IMAGE_WALK_SEC_COL_STEP
+#undef IMAGE_WALK_SEC_ROW_STEP
+#undef IMAGE_WALK_DO_START
+#undef IMAGE_WALK_DO_END
+#undef IMAGE_WALK_DO_ROW_START
+#undef IMAGE_WALK_DO_ROW_END
+#undef IMAGE_WALK_DO_STEP
+#undef IMAGE_WALK__STEP
+#undef P
diff --git a/images/image.c b/images/image.c
new file mode 100644 (file)
index 0000000..e6111ab
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ *     Image Library -- Basic image manipulation
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "images/images.h"
+#include "images/error.h"
+#include "images/color.h"
+
+#include <string.h>
+
+static inline uns
+flags_to_pixel_size(uns flags)
+{
+  uns pixel_size;
+  switch (flags & IMAGE_COLOR_SPACE)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       pixel_size = 1;
+       break;
+      case COLOR_SPACE_RGB:
+       pixel_size = 3;
+       break;
+      default:
+       ASSERT(0);
+    }
+  if (flags & IMAGE_ALPHA)
+    pixel_size++;
+  return pixel_size;
+}
+
+struct image *
+image_new(struct image_context *ctx, uns cols, uns rows, uns flags, struct mempool *pool)
+{
+  DBG("image_new(cols=%u rows=%u flags=0x%x pool=%p)", cols, rows, flags, pool);
+  flags &= IMAGE_NEW_FLAGS;
+  if (unlikely(!image_dimensions_valid(cols, rows)))
+    {
+      IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_DIMENSIONS, "Invalid image dimensions (%ux%u)", cols, rows);
+      return NULL;
+    }
+  struct image *img;
+  uns pixel_size, row_pixels_size, row_size, align;
+  pixel_size = flags_to_pixel_size(flags);
+  switch (pixel_size)
+    {
+      case 1:
+      case 2:
+      case 4:
+       flags |= IMAGE_PIXELS_ALIGNED;
+       break;
+      case 3:
+       if (flags & IMAGE_PIXELS_ALIGNED)
+         pixel_size = 4;
+       break;
+      default:
+       ASSERT(0);
+    }
+  if (flags & IMAGE_SSE_ALIGNED)
+    align = IMAGE_SSE_ALIGN_SIZE;
+  else if (flags & IMAGE_PIXELS_ALIGNED)
+    align = pixel_size;
+  else
+    align = 1;
+  row_pixels_size = cols * pixel_size;
+  row_size = ALIGN(row_pixels_size, align);
+  u64 image_size_64 = (u64)row_size * rows;
+  u64 bytes_64 = image_size_64 + (sizeof(struct image) + IMAGE_SSE_ALIGN_SIZE - 1 + sizeof(uns));
+  if (unlikely(bytes_64 > image_max_bytes))
+    {
+      IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_DIMENSIONS, "Image does not fit in memory");
+      return NULL;
+    }
+  if (pool)
+    img = mp_alloc(pool, bytes_64);
+  else
+    {
+      img = xmalloc(bytes_64);
+      flags |= IMAGE_NEED_DESTROY;
+    }
+  bzero(img, sizeof(struct image));
+  byte *p = (byte *)img + sizeof(struct image);
+  img->pixels = ALIGN_PTR(p, IMAGE_SSE_ALIGN_SIZE);
+  img->flags = flags;
+  img->pixel_size = pixel_size;
+  img->cols = cols;
+  img->rows = rows;
+  img->row_size = row_size;
+  img->row_pixels_size = row_pixels_size;
+  img->image_size = image_size_64;
+  DBG("img=%p flags=0x%x pixel_size=%u row_size=%u image_size=%u pixels=%p",
+    img, img->flags, img->pixel_size, img->row_size, img->image_size, img->pixels);
+  return img;
+}
+
+struct image *
+image_clone(struct image_context *ctx, struct image *src, uns flags, struct mempool *pool)
+{
+  DBG("image_clone(src=%p flags=0x%x pool=%p)", src, src->flags, pool);
+  struct image *img;
+  flags &= IMAGE_NEW_FLAGS & ~IMAGE_CHANNELS_FORMAT;
+  flags |= src->flags & IMAGE_CHANNELS_FORMAT;
+  if (!(img = image_new(ctx, src->cols, src->rows, flags, pool)))
+    return NULL;
+  if (img->image_size)
+    {
+      if (src->pixel_size != img->pixel_size) /* conversion between aligned and unaligned RGB */
+        {
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#         define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE img
+#        define IMAGE_WALK_SEC_IMAGE src
+#         define IMAGE_WALK_DOUBLE
+#         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"
+       }
+      else if (src->row_size != img->row_size || ((img->flags | src->flags) & IMAGE_GAPS_PROTECTED))
+        {
+          byte *s = src->pixels;
+          byte *d = img->pixels;
+         for (uns row = src->rows; row--; )
+            {
+             memcpy(d, s, src->row_pixels_size);
+             d += img->row_size;
+             s += src->row_size;
+           }
+        }
+      else
+        memcpy(img->pixels, src->pixels, img->image_size);
+    }
+  return img;
+}
+
+void
+image_destroy(struct image *img)
+{
+  DBG("image_destroy(img=%p)", img);
+  if (img->flags & IMAGE_NEED_DESTROY)
+    xfree(img);
+}
+
+void
+image_clear(struct image_context *ctx UNUSED, struct image *img)
+{
+  DBG("image_clear(img=%p)", img);
+  if (img->image_size)
+    if (img->flags & IMAGE_GAPS_PROTECTED)
+      {
+        byte *p = img->pixels;
+        uns bytes = img->cols * img->pixel_size;
+       for (uns row = img->rows; row--; p += img->row_size)
+         bzero(p, bytes);
+      }
+    else
+      bzero(img->pixels, img->image_size);
+}
+
+struct image *
+image_init_matrix(struct image_context *ctx, struct image *img, byte *pixels, uns cols, uns rows, uns row_size, uns flags)
+{
+  DBG("image_init_matrix(img=%p pixels=%p cols=%u rows=%u row_size=%u flags=0x%x)", img, pixels, cols, rows, row_size, flags);
+  if (unlikely(!image_dimensions_valid(cols, rows)))
+    {
+      IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_DIMENSIONS, "Invalid image dimensions (%ux%u)", cols, rows);
+      return NULL;
+    }
+  img->pixels = pixels;
+  img->cols = cols;
+  img->rows = rows;
+  img->pixel_size = flags_to_pixel_size(flags);
+  img->row_size = row_size;
+  img->row_pixels_size = cols * img->pixel_size;
+  img->image_size = rows * row_size;
+  img->flags = flags & (IMAGE_NEW_FLAGS | IMAGE_GAPS_PROTECTED);
+  return img;
+}
+
+struct image *
+image_init_subimage(struct image_context *ctx UNUSED, struct image *img, struct image *src, uns left, uns top, uns cols, uns rows)
+{
+  DBG("image_init_subimage(img=%p src=%p left=%u top=%u cols=%u rows=%u)", img, src, left, top, cols, rows);
+  ASSERT(left + cols <= src->cols && top + rows <= src->rows);
+  img->pixels = src->pixels + left * src->pixel_size + top * src->row_size;
+  img->cols = cols;
+  img->rows = rows;
+  img->pixel_size = src->pixel_size;
+  img->row_size = src->row_size;
+  img->row_pixels_size = cols * src->pixel_size;
+  img->image_size = src->row_size * rows;
+  img->flags = src->flags & IMAGE_NEW_FLAGS;
+  img->flags |= IMAGE_GAPS_PROTECTED;
+  return img;
+}
+
+byte *
+color_space_to_name(uns cs)
+{
+  return image_channels_format_to_name(cs);
+}
+
+byte *
+image_channels_format_to_name(uns format)
+{
+  switch (format)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       return "Gray";
+      case COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA:
+       return "GrayAlpha";
+      case COLOR_SPACE_RGB:
+       return "RGB";
+      case COLOR_SPACE_RGB | IMAGE_ALPHA:
+       return "RGBAlpha";
+      default:
+       return NULL;
+    }
+}
+
+uns
+image_name_to_channels_format(byte *name)
+{
+  if (!strcasecmp(name, "gray"))
+    return COLOR_SPACE_GRAYSCALE;
+  if (!strcasecmp(name, "grayscale"))
+    return COLOR_SPACE_GRAYSCALE;
+  if (!strcasecmp(name, "grayalpha"))
+    return COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA;
+  if (!strcasecmp(name, "grayscalealpha"))
+    return COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA;
+  if (!strcasecmp(name, "rgb"))
+    return COLOR_SPACE_RGB;
+  if (!strcasecmp(name, "rgbalpha"))
+    return COLOR_SPACE_RGB | IMAGE_ALPHA;
+  if (!strcasecmp(name, "rgba"))
+    return COLOR_SPACE_RGB | IMAGE_ALPHA;
+  return 0;
+}
diff --git a/images/images.h b/images/images.h
new file mode 100644 (file)
index 0000000..dbaa055
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ *     Image Library -- Main header file
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#ifndef _IMAGES_IMAGES_H
+#define _IMAGES_IMAGES_H
+
+#include "lib/bbuf.h"
+
+struct mempool;
+struct fastbuf;
+
+
+/* context.c
+ * - contexts with error/message handling
+ * - imagelib is thread-safe as long as threads work in different contexts */
+
+struct image_context {
+  byte *msg;                           /* last message */
+  uns msg_code;                                /* last message code (see images/error.h for details) */
+  bb_t msg_buf;                                /* message buffer */
+  void (*msg_callback)(struct image_context *ctx); /* called for each message (in msg_{str,code}) */
+  uns tracing_level;                   /* tracing level (zero to disable) */
+};
+
+/* initialization/cleanup */
+void image_context_init(struct image_context *ctx);
+void image_context_cleanup(struct image_context *ctx);
+
+/* message handling, see images/error.h for useful macros */
+void image_context_msg(struct image_context *ctx, uns code, char *msg, ...);
+void image_context_vmsg(struct image_context *ctx, uns code, char *msg, va_list args);
+
+/* default callback, displays messages with standard libucw's log() routine */
+void image_context_msg_default(struct image_context *ctx);
+
+/* empty callback */
+void image_context_msg_silent(struct image_context *ctx);
+
+
+/* image.c
+ * - basic manipulation with images
+ * - image structure is not directly connected to a single context
+ *   but manipulation routines are (user must synchronize the access himself)! */
+
+extern uns image_max_dim;              /* ImageLib.ImageMaxDim */
+extern uns image_max_bytes;            /* ImageLib.ImageMaxBytes */
+
+/* SSE aligning size, see IMAGE_SSE_ALIGNED */
+#define IMAGE_SSE_ALIGN_SIZE 16
+
+enum image_flag {
+  IMAGE_COLOR_SPACE = 0x7,             /* mask for enum color_space */
+  IMAGE_ALPHA = 0x8,                   /* alpha channel */
+  IMAGE_PIXELS_ALIGNED = 0x10,         /* align pixel size to the nearest power of two  */
+  IMAGE_SSE_ALIGNED = 0x20,            /* align scanlines to multiples of 16 bytes (both start and size) */
+  IMAGE_NEED_DESTROY = 0x40,           /* image is allocated with xmalloc */
+  IMAGE_GAPS_PROTECTED = 0x80,         /* cannot access gaps between rows */
+  IMAGE_CHANNELS_FORMAT = IMAGE_COLOR_SPACE | IMAGE_ALPHA,
+  IMAGE_PIXEL_FORMAT = IMAGE_CHANNELS_FORMAT | IMAGE_PIXELS_ALIGNED,
+  IMAGE_ALIGNED = IMAGE_PIXELS_ALIGNED | IMAGE_SSE_ALIGNED,
+  IMAGE_NEW_FLAGS = IMAGE_PIXEL_FORMAT | IMAGE_SSE_ALIGNED,
+  IMAGE_INTERNAL_FLAGS = IMAGE_NEED_DESTROY | IMAGE_GAPS_PROTECTED,
+};
+
+struct image {
+  byte *pixels;                        /* aligned top left pixel, there are at least sizeof(uns)
+                                  unused bytes after the buffer (possible optimizations) */
+  uns cols;                    /* number of columns */
+  uns rows;                    /* number of rows */
+  uns pixel_size;              /* size of pixel in bytes (1, 2, 3 or 4) */
+  uns row_size;                        /* scanline size in bytes */
+  uns row_pixels_size;         /* scanline size in bytes excluding rows gaps */
+  uns image_size;              /* rows * row_size */
+  uns flags;                   /* enum image_flag */
+};
+
+struct image *image_new(struct image_context *ctx, uns cols, uns rows, uns flags, struct mempool *pool);
+struct image *image_clone(struct image_context *ctx, struct image *src, uns flags, struct mempool *pool);
+void image_destroy(struct image *img);
+void image_clear(struct image_context *ctx, struct image *img);
+struct image *image_init_matrix(struct image_context *ctx, struct image *img, byte *pixels, uns cols, uns rows, uns row_size, uns flags);
+struct image *image_init_subimage(struct image_context *ctx, struct image *img, struct image *src, uns left, uns top, uns cols, uns rows);
+
+static inline int
+image_dimensions_valid(uns cols, uns rows)
+{
+  return cols && rows && cols <= image_max_dim && rows <= image_max_dim;
+}
+
+byte *color_space_to_name(uns cs);
+byte *image_channels_format_to_name(uns format);
+uns image_name_to_channels_format(byte *name);
+
+struct color {
+  byte c[3];
+  byte color_space;
+} PACKED;
+
+/* scale.c */
+
+int image_scale(struct image_context *ctx, struct image *dest, struct image *src);
+void image_dimensions_fit_to_box(uns *cols, uns *rows, uns max_cols, uns max_rows, uns upsample);
+
+/* alpha.c */
+
+int image_apply_background(struct image_context *ctx, struct image *dest, struct image *src, struct color *background);
+
+/* image-io.c */
+
+enum image_format {
+  IMAGE_FORMAT_UNDEFINED,
+  IMAGE_FORMAT_JPEG,
+  IMAGE_FORMAT_PNG,
+  IMAGE_FORMAT_GIF,
+  IMAGE_FORMAT_MAX
+};
+
+struct image_io {
+                                       /*  R - read_header input */
+                                       /*   H - read_header output */
+                                       /*    I - read_data input */
+                                       /*     O - read_data output */
+                                       /*      W - write input */
+
+  struct image *image;                 /* [   OW] - image data */
+  enum image_format format;            /* [R   W] - file format (IMAGE_FORMAT_x) */
+  struct fastbuf *fastbuf;             /* [R   W] - source/destination stream */
+  struct mempool *pool;                        /* [  I  ] - parameter to image_new */
+  uns cols;                            /* [ HI  ] - number of columns, parameter to image_new */
+  uns rows;                            /* [ HI  ] - number of rows, parameter to image_new */
+  uns flags;                           /* [ HI  ] - see enum image_io_flags */
+  uns jpeg_quality;                    /* [    W] - JPEG compression quality (1..100) */
+  uns number_of_colors;                        /* [ H   ] - number of image colors */
+  struct color background_color;       /* [ HI  ] - background color, zero if undefined */
+  uns exif_size;                       /* [ H  W] - EXIF size in bytes (zero if not present) */
+  byte *exif_data;                     /* [ H  W] - EXIF data */
+
+  /* internals */
+  struct image_context *context;
+  struct mempool *internal_pool;
+  void *read_data;
+  void (*read_cancel)(struct image_io *io);
+};
+
+enum image_io_flags {
+  IMAGE_IO_IMAGE_FLAGS = 0xffff,       /* [ HI  ] - mask of parameters to image new, read_header fills IMAGE_CHANNELS_FORMAT */
+  IMAGE_IO_NEED_DESTROY = 0x10000,     /* [   O ] - enables automatic call of image_destroy */
+  IMAGE_IO_HAS_PALETTE = 0x20000,      /* [ H   ] - true for image with indexed colors */
+  IMAGE_IO_USE_BACKGROUND = 0x40000,   /* [  I  ] - merge transparent pixels with background_color */
+  IMAGE_IO_WANT_EXIF = 0x80000,                /* [R    ] - read EXIF data if present */
+};
+
+int image_io_init(struct image_context *ctx, struct image_io *io);
+void image_io_cleanup(struct image_io *io);
+void image_io_reset(struct image_io *io);
+
+int image_io_read_header(struct image_io *io);
+struct image *image_io_read_data(struct image_io *io, int ref);
+struct image *image_io_read(struct image_io *io, int ref);
+
+int image_io_write(struct image_io *io);
+
+byte *image_format_to_extension(enum image_format format);
+enum image_format image_extension_to_format(byte *extension);
+enum image_format image_file_name_to_format(byte *file_name);
+
+#endif
diff --git a/images/io-libjpeg.c b/images/io-libjpeg.c
new file mode 100644 (file)
index 0000000..1d3d37c
--- /dev/null
@@ -0,0 +1,542 @@
+/*
+ *     Image Library -- libjpeg
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/error.h"
+#include "images/color.h"
+#include "images/io-main.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <jpeglib.h>
+#include <jerror.h>
+#include <setjmp.h>
+
+struct libjpeg_err {
+  struct jpeg_error_mgr pub;
+  jmp_buf setjmp_buf;
+  struct image_io *io;
+};
+
+struct libjpeg_read_internals {
+  struct jpeg_decompress_struct cinfo;
+  struct jpeg_source_mgr src;
+  struct libjpeg_err err;
+  struct fastbuf *fastbuf;
+  byte *fastbuf_pos;
+};
+
+struct libjpeg_write_internals {
+  struct jpeg_compress_struct cinfo;
+  struct jpeg_destination_mgr dest;
+  struct libjpeg_err err;
+  struct fastbuf *fastbuf;
+  byte *fastbuf_pos;
+};
+
+static void NONRET
+libjpeg_read_error_exit(j_common_ptr cinfo)
+{
+  DBG("libjpeg_error_exit()");
+  struct libjpeg_err *e = (struct libjpeg_err *)cinfo->err;
+  byte buf[JMSG_LENGTH_MAX];
+  e->pub.format_message(cinfo, buf);
+  IMAGE_ERROR(e->io->context, IMAGE_ERROR_READ_FAILED, "%s", buf);
+  longjmp(e->setjmp_buf, 1);
+}
+
+static void NONRET
+libjpeg_write_error_exit(j_common_ptr cinfo)
+{
+  DBG("libjpeg_error_exit()");
+  struct libjpeg_err *e = (struct libjpeg_err *)cinfo->err;
+  byte buf[JMSG_LENGTH_MAX];
+  e->pub.format_message(cinfo, buf);
+  IMAGE_ERROR(e->io->context, IMAGE_ERROR_WRITE_FAILED, "%s", buf);
+  longjmp(e->setjmp_buf, 1);
+}
+
+static void
+libjpeg_emit_message(j_common_ptr cinfo UNUSED, int msg_level UNUSED)
+{
+#ifdef LOCAL_DEBUG
+  byte buf[JMSG_LENGTH_MAX];
+  cinfo->err->format_message(cinfo, buf);
+  DBG("libjpeg_emit_message(): [%d] %s", msg_level, buf);
+#endif
+  if (unlikely(msg_level == -1))
+    longjmp(((struct libjpeg_err *)(cinfo)->err)->setjmp_buf, 1);
+}
+
+static inline uns
+libjpeg_fastbuf_read_prepare(struct libjpeg_read_internals *i)
+{
+  byte *start;
+  uns len = bdirect_read_prepare(i->fastbuf, &start);
+  i->fastbuf_pos = start + len;
+  i->src.next_input_byte = start;
+  i->src.bytes_in_buffer = len;
+  return len;
+}
+
+static inline void
+libjpeg_fastbuf_read_commit(struct libjpeg_read_internals *i)
+{
+  bdirect_read_commit(i->fastbuf, i->fastbuf_pos);
+}
+
+static void
+libjpeg_init_source(j_decompress_ptr cinfo)
+{
+  DBG("libjpeg_init_source()");
+  libjpeg_fastbuf_read_prepare((struct libjpeg_read_internals *)cinfo);
+}
+
+static void
+libjpeg_term_source(j_decompress_ptr cinfo UNUSED)
+{
+  DBG("libjpeg_term_source()");
+  //libjpeg_fastbuf_read_commit((struct libjpeg_read_internals *)cinfo);
+}
+
+static boolean
+libjpeg_fill_input_buffer(j_decompress_ptr cinfo)
+{
+  DBG("libjpeg_fill_input_buffer()");
+  struct libjpeg_read_internals *i = (struct libjpeg_read_internals *)cinfo;
+  libjpeg_fastbuf_read_commit(i);
+  return !!libjpeg_fastbuf_read_prepare(i);
+}
+
+static void
+libjpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+  DBG("libjpeg_skip_input_data(num_bytes=%d)", (int)num_bytes);
+  if (num_bytes > 0)
+    {
+      struct libjpeg_read_internals *i = (struct libjpeg_read_internals *)cinfo;
+      if ((unsigned long)num_bytes <= i->src.bytes_in_buffer)
+        {
+          i->src.next_input_byte += num_bytes;
+          i->src.bytes_in_buffer -= num_bytes;
+       }
+      else
+        {
+         num_bytes -= i->src.bytes_in_buffer;
+         libjpeg_fastbuf_read_commit(i);
+         if (!bskip(i->fastbuf, num_bytes))
+           {
+             IMAGE_ERROR(i->err.io->context, IMAGE_ERROR_READ_FAILED, "Incomplete JPEG file");
+             longjmp(i->err.setjmp_buf, 1);
+           }
+         libjpeg_fastbuf_read_prepare(i);
+       }
+    }
+}
+
+static inline void
+libjpeg_fastbuf_write_prepare(struct libjpeg_write_internals *i)
+{
+  byte *start;
+  uns len = bdirect_write_prepare(i->fastbuf, &start);
+  i->fastbuf_pos = start + len;
+  i->dest.next_output_byte = start;
+  i->dest.free_in_buffer = len;
+  if (!len)
+    {
+      IMAGE_ERROR(i->err.io->context, IMAGE_ERROR_WRITE_FAILED, "Unexpected end of stream");
+      longjmp(i->err.setjmp_buf, 1);
+    }
+}
+
+static void
+libjpeg_init_destination(j_compress_ptr cinfo)
+{
+  DBG("libjpeg_init_destination()");
+  libjpeg_fastbuf_write_prepare((struct libjpeg_write_internals *)cinfo);
+}
+
+static void
+libjpeg_term_destination(j_compress_ptr cinfo)
+{
+  DBG("libjpeg_term_destination()");
+  struct libjpeg_write_internals *i = (struct libjpeg_write_internals *)cinfo;
+  bdirect_write_commit(i->fastbuf, (byte *)i->dest.next_output_byte);
+}
+
+static boolean
+libjpeg_empty_output_buffer(j_compress_ptr cinfo)
+{
+  DBG("libjpeg_empty_output_buffer()");
+  struct libjpeg_write_internals *i = (struct libjpeg_write_internals *)cinfo;
+  bdirect_write_commit(i->fastbuf, i->fastbuf_pos);
+  libjpeg_fastbuf_write_prepare(i);
+  return TRUE;
+}
+
+static inline uns
+libjpeg_read_byte(struct libjpeg_read_internals *i)
+{
+  DBG("libjpeg_read_byte()");
+  if (!i->src.bytes_in_buffer)
+    if (!libjpeg_fill_input_buffer(&i->cinfo))
+      ERREXIT(&i->cinfo, JERR_CANT_SUSPEND);
+  i->src.bytes_in_buffer--;
+  return *i->src.next_input_byte++;
+}
+
+static inline void
+libjpeg_read_buf(struct libjpeg_read_internals *i, byte *buf, uns len)
+{
+  DBG("libjpeg_read_buf(len=%u)", len);
+  while (len)
+    {
+      if (!i->src.bytes_in_buffer)
+       if (!libjpeg_fill_input_buffer(&i->cinfo))
+         ERREXIT(&i->cinfo, JERR_CANT_SUSPEND);
+      uns buf_size = i->src.bytes_in_buffer;
+      uns read_size = MIN(buf_size, len);
+      memcpy(buf, i->src.next_input_byte, read_size);
+      i->src.bytes_in_buffer -= read_size;
+      i->src.next_input_byte += read_size;
+      len -= read_size;
+    }
+}
+
+static byte libjpeg_exif_header[6] = { 'E', 'x', 'i', 'f', 0, 0 };
+
+static boolean
+libjpeg_app1_preprocessor(j_decompress_ptr cinfo)
+{
+  struct libjpeg_read_internals *i = (struct libjpeg_read_internals *)cinfo;
+  struct image_io *io = i->err.io;
+  uns len = libjpeg_read_byte(i) << 8;
+  len += libjpeg_read_byte(i);
+  DBG("Found APP1 marker, len=%u", len);
+  if (len < 2)
+    return TRUE;
+  len -= 2;
+  if (len < 7 /*|| io->exif_size*/)
+    {
+      libjpeg_skip_input_data(cinfo, len);
+      return TRUE;
+    }
+  byte header[6];
+  libjpeg_read_buf(i, header, 6);
+  if (memcmp(header, libjpeg_exif_header, 6))
+    {
+      libjpeg_skip_input_data(cinfo, len - 6);
+      return TRUE;
+    }
+  io->exif_size = len;
+  io->exif_data = mp_alloc(io->internal_pool, len);
+  memcpy(io->exif_data, header, 6);
+  libjpeg_read_buf(i, io->exif_data + 6, len - 6);
+  DBG("Parsed EXIF of length %u", len);
+  return TRUE;
+}
+
+static void
+libjpeg_read_cancel(struct image_io *io)
+{
+  DBG("libjpeg_read_cancel()");
+  struct libjpeg_read_internals *i = io->read_data;
+  jpeg_destroy_decompress(&i->cinfo);
+}
+
+int
+libjpeg_read_header(struct image_io *io)
+{
+  DBG("libjpeg_read_header()");
+  struct libjpeg_read_internals *i = io->read_data = mp_alloc(io->internal_pool, sizeof(*i));
+  i->fastbuf = io->fastbuf;
+
+  /* Create libjpeg read structure */
+  DBG("Creating libjpeg read structure");
+  i->cinfo.err = jpeg_std_error(&i->err.pub);
+  i->err.pub.error_exit = libjpeg_read_error_exit;
+  i->err.pub.emit_message = libjpeg_emit_message;
+  i->err.io = io;
+  if (setjmp(i->err.setjmp_buf))
+    {
+      DBG("Libjpeg failed to read the image, longjump saved us");
+      jpeg_destroy_decompress(&i->cinfo);
+      return 0;
+    }
+  jpeg_create_decompress(&i->cinfo);
+
+  /* Initialize source manager */
+  i->cinfo.src = &i->src;
+  i->src.init_source = libjpeg_init_source;
+  i->src.fill_input_buffer = libjpeg_fill_input_buffer;
+  i->src.skip_input_data = libjpeg_skip_input_data;
+  i->src.resync_to_restart = jpeg_resync_to_restart;
+  i->src.term_source = libjpeg_term_source;
+
+  if (io->flags & IMAGE_IO_WANT_EXIF)
+    jpeg_set_marker_processor(&i->cinfo, JPEG_APP0 + 1, libjpeg_app1_preprocessor);
+
+  /* Read JPEG header and setup decompression options */
+  DBG("Reading image header");
+  jpeg_read_header(&i->cinfo, TRUE);
+  switch (i->cinfo.jpeg_color_space)
+    {
+      case JCS_GRAYSCALE:
+        io->flags = COLOR_SPACE_GRAYSCALE;
+       io->number_of_colors = 1 << 8;
+        break;
+      default:
+        io->flags = COLOR_SPACE_RGB;
+       io->number_of_colors = 1 << 24;
+        break;
+    }
+  io->cols = i->cinfo.image_width;
+  io->rows = i->cinfo.image_height;
+
+  io->read_cancel = libjpeg_read_cancel;
+  return 1;
+}
+
+int
+libjpeg_read_data(struct image_io *io)
+{
+  DBG("libjpeg_read_data()");
+
+  struct libjpeg_read_internals *i = io->read_data;
+
+  /* Select color space */
+  switch (io->flags & IMAGE_COLOR_SPACE)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       i->cinfo.out_color_space = JCS_GRAYSCALE;
+       break;
+      case COLOR_SPACE_RGB:
+       i->cinfo.out_color_space = JCS_RGB;
+       break;
+      default:
+       jpeg_destroy_decompress(&i->cinfo);
+       IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Unsupported color space.");
+       return 0;
+    }
+
+  /* Prepare the image  */
+  struct image_io_read_data_internals rdi;
+  if (io->cols <= (i->cinfo.image_width >> 3) && io->rows <= (i->cinfo.image_height >> 3))
+    {
+      DBG("Scaling to 1/8");
+      i->cinfo.scale_num = 1;
+      i->cinfo.scale_denom = 8;
+    }
+  else if (io->cols <= (i->cinfo.image_width >> 2) && io->rows <= (i->cinfo.image_height >> 2))
+    {
+      DBG("Scaling to 1/4");
+      i->cinfo.scale_num = 1;
+      i->cinfo.scale_denom = 4;
+    }
+  else if (io->cols <= (i->cinfo.image_width >> 1) && io->rows <= (i->cinfo.image_height >> 1))
+    {
+      DBG("Scaling to 1/2");
+      i->cinfo.scale_num = 1;
+      i->cinfo.scale_denom = 2;
+    }
+  jpeg_calc_output_dimensions(&i->cinfo);
+  DBG("Output dimensions %ux%u", (uns)i->cinfo.output_width, (uns)i->cinfo.output_height);
+  if (unlikely(!image_io_read_data_prepare(&rdi, io, i->cinfo.output_width, i->cinfo.output_height, io->flags)))
+    {
+      jpeg_destroy_decompress(&i->cinfo);
+      return 0;
+    }
+
+  /* Setup fallback */
+  if (setjmp(i->err.setjmp_buf))
+    {
+      DBG("Libjpeg failed to read the image, longjump saved us");
+      jpeg_destroy_decompress(&i->cinfo);
+      image_io_read_data_break(&rdi, io);
+      return 0;
+    }
+
+  /* Decompress the image */
+  struct image *img = rdi.image;
+  jpeg_start_decompress(&i->cinfo);
+  switch (img->pixel_size)
+    {
+      /* grayscale or RGB */
+      case 1:
+      case 3:
+       {
+          byte *pixels = img->pixels;
+         for (uns r = img->rows; r--; )
+            {
+              jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&pixels, 1);
+              pixels += img->row_size;
+            }
+       }
+       break;
+      /* grayscale with alpha */
+      case 2:
+       {
+         byte buf[img->cols], *src;
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#         define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE img
+#         define IMAGE_WALK_UNROLL 4
+#         define IMAGE_WALK_COL_STEP 2
+#         define IMAGE_WALK_DO_ROW_START do{ src = buf; jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&src, 1); }while(0)
+#         define IMAGE_WALK_DO_STEP do{ walk_pos[0] = *src++; walk_pos[1] = 255; }while(0)
+#         include "images/image-walk.h"
+       }
+       break;
+      /* RGBA or aligned RGB */
+      case 4:
+       {
+         byte buf[img->cols * 3], *src;
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#         define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE img
+#         define IMAGE_WALK_UNROLL 4
+#         define IMAGE_WALK_COL_STEP 4
+#         define IMAGE_WALK_DO_ROW_START do{ src = buf; jpeg_read_scanlines(&i->cinfo, (JSAMPLE **)&src, 1); }while(0)
+#         define IMAGE_WALK_DO_STEP do{ *(u32 *)walk_pos = *(u32 *)src; walk_pos[3] = 255; src += 3; }while(0)
+#         include "images/image-walk.h"
+       }
+       break;
+      default:
+       ASSERT(0);
+    }
+  ASSERT(i->cinfo.output_scanline == i->cinfo.output_height);
+
+  /* Destroy libjpeg object */
+  jpeg_finish_decompress(&i->cinfo);
+  jpeg_destroy_decompress(&i->cinfo);
+
+  /* Finish the image */
+  return image_io_read_data_finish(&rdi, io);
+}
+
+int
+libjpeg_write(struct image_io *io)
+{
+  DBG("libjpeg_write()");
+  struct libjpeg_write_internals i;
+  i.fastbuf = io->fastbuf;
+
+  /* Create libjpeg write structure */
+  DBG("Creating libjpeg write structure");
+  i.cinfo.err = jpeg_std_error(&i.err.pub);
+  i.err.pub.error_exit = libjpeg_write_error_exit;
+  i.err.pub.emit_message = libjpeg_emit_message;
+  i.err.io = io;
+  if (setjmp(i.err.setjmp_buf))
+    {
+      DBG("Libjpeg failed to write the image, longjump saved us");
+      jpeg_destroy_compress(&i.cinfo);
+      return 0;
+    }
+  jpeg_create_compress(&i.cinfo);
+
+  /* Initialize destination manager */
+  i.cinfo.dest = &i.dest;
+  i.dest.init_destination = libjpeg_init_destination;
+  i.dest.term_destination = libjpeg_term_destination;
+  i.dest.empty_output_buffer = libjpeg_empty_output_buffer;
+
+  /* Set output parameters */
+  struct image *img = io->image;
+  i.cinfo.image_width = img->cols;
+  i.cinfo.image_height = img->rows;
+  switch (img->flags & IMAGE_COLOR_SPACE)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       i.cinfo.input_components = 1;
+       i.cinfo.in_color_space = JCS_GRAYSCALE;
+       break;
+      case COLOR_SPACE_RGB:
+       i.cinfo.input_components = 3;
+       i.cinfo.in_color_space = JCS_RGB;
+       break;
+      default:
+       jpeg_destroy_compress(&i.cinfo);
+       IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Unsupported pixel format.");
+       return 0;
+    }
+  jpeg_set_defaults(&i.cinfo);
+  if (io->jpeg_quality)
+    jpeg_set_quality(&i.cinfo, MIN(io->jpeg_quality, 100), 1);
+  if (io->exif_size)
+    {
+      /* According to the Exif specification, the Exif APP1 marker has to follow immediately after the SOI,
+       * just as the JFIF specification requires the same for the JFIF APP0 marker!
+       * Therefore a JPEG file cannot legally be both Exif and JFIF.  */
+      i.cinfo.write_JFIF_header = FALSE;
+      i.cinfo.write_Adobe_marker = FALSE;
+    }
+
+  /* Compress the image */
+  jpeg_start_compress(&i.cinfo, TRUE);
+  if (io->exif_size)
+    {
+      DBG("Writing EXIF");
+      jpeg_write_marker(&i.cinfo, JPEG_APP0 + 1, io->exif_data, io->exif_size);
+    }
+  switch (img->pixel_size)
+    {
+      /* grayscale or RGB */
+      case 1:
+      case 3:
+       {
+          byte *pixels = img->pixels;
+         for (uns r = img->rows; r--; )
+           {
+              jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&pixels, 1);
+              pixels += img->row_size;
+            }
+       }
+       break;
+      /* grayscale with alpha (ignore alpha) */
+      case 2:
+       {
+         byte buf[img->cols], *dest = buf;
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#         define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE img
+#         define IMAGE_WALK_UNROLL 4
+#         define IMAGE_WALK_COL_STEP 2
+#         define IMAGE_WALK_DO_ROW_END do{ dest = buf; jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&dest, 1); }while(0)
+#         define IMAGE_WALK_DO_STEP do{ *dest++ = walk_pos[0]; }while(0)
+#         include "images/image-walk.h"
+       }
+       break;
+      /* RGBA (ignore alpha) or aligned RGB */
+      case 4:
+       {
+         byte buf[img->cols * 3], *dest = buf;
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#         define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE img
+#         define IMAGE_WALK_UNROLL 4
+#         define IMAGE_WALK_COL_STEP 4
+#         define IMAGE_WALK_DO_ROW_END do{ dest = buf; jpeg_write_scanlines(&i.cinfo, (JSAMPLE **)&dest, 1); }while(0)
+#         define IMAGE_WALK_DO_STEP do{ *dest++ = walk_pos[0]; *dest++ = walk_pos[1]; *dest++ = walk_pos[2]; }while(0)
+#         include "images/image-walk.h"
+       }
+       break;
+      default:
+       ASSERT(0);
+    }
+  ASSERT(i.cinfo.next_scanline == i.cinfo.image_height);
+  jpeg_finish_compress(&i.cinfo);
+  jpeg_destroy_compress(&i.cinfo);
+  return 1;
+}
diff --git a/images/io-libmagick.c b/images/io-libmagick.c
new file mode 100644 (file)
index 0000000..cdf5804
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ *     Image Library -- GraphicsMagick (slow fallback library)
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/error.h"
+#include "images/color.h"
+#include "images/io-main.h"
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <magick/api.h>
+#include <pthread.h>
+
+#define MAX_FILE_SIZE (1 << 30)
+#define QUANTUM_SCALE (QuantumDepth - 8)
+#define QUANTUM_TO_BYTE(x) ((uns)(x) >> QUANTUM_SCALE)
+#define BYTE_TO_QUANTUM(x) ((uns)(x) << QUANTUM_SCALE)
+#define ALPHA_TO_BYTE(x) (255 - QUANTUM_TO_BYTE(x))
+#define BYTE_TO_ALPHA(x) (BYTE_TO_QUANTUM(255 - (x)))
+
+static pthread_mutex_t libmagick_mutex = PTHREAD_MUTEX_INITIALIZER;
+static uns libmagick_counter;
+
+struct magick_read_data {
+  ExceptionInfo exception;
+  ImageInfo *info;
+  Image *image;
+};
+
+int
+libmagick_init(struct image_io *io UNUSED)
+{
+  pthread_mutex_lock(&libmagick_mutex);
+  if (!libmagick_counter++)
+    InitializeMagick(NULL);
+  pthread_mutex_unlock(&libmagick_mutex);
+  return 1;
+}
+
+void
+libmagick_cleanup(struct image_io *io UNUSED)
+{
+  pthread_mutex_lock(&libmagick_mutex);
+  if (!--libmagick_counter)
+    DestroyMagick();
+  pthread_mutex_unlock(&libmagick_mutex);
+}
+
+static void
+libmagick_destroy_read_data(struct magick_read_data *rd)
+{
+  if (rd->image)
+    DestroyImage(rd->image);
+  DestroyImageInfo(rd->info);
+  DestroyExceptionInfo(&rd->exception);
+}
+
+static void
+libmagick_read_cancel(struct image_io *io)
+{
+  DBG("libmagick_read_cancel()");
+
+  struct magick_read_data *rd = io->read_data;
+  libmagick_destroy_read_data(rd);
+}
+
+int
+libmagick_read_header(struct image_io *io)
+{
+  DBG("libmagick_read_header()");
+
+  /* Read entire stream */
+  sh_off_t file_size = bfilesize(io->fastbuf) - btell(io->fastbuf);
+  if (unlikely(file_size > MAX_FILE_SIZE))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Too long stream.");
+      return 0;
+    }
+  uns buf_size = file_size;
+  byte *buf = xmalloc(buf_size);
+  breadb(io->fastbuf, buf, buf_size);
+
+  /* Allocate read structure */
+  struct magick_read_data *rd = io->read_data = mp_alloc_zero(io->internal_pool, sizeof(*rd));
+
+  /* Initialize GraphicsMagick */
+  GetExceptionInfo(&rd->exception);
+  rd->info = CloneImageInfo(NULL);
+  rd->info->subrange = 1;
+
+  /* Read the image */
+  rd->image = BlobToImage(rd->info, buf, buf_size, &rd->exception);
+  xfree(buf);
+  if (unlikely(!rd->image))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "GraphicsMagick failed to read the image.");
+      goto err;
+    }
+  if (unlikely(rd->image->columns > image_max_dim || rd->image->rows > image_max_dim))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_DIMENSIONS, "Image too large.");
+      goto err;
+    }
+
+  /* Fill image parameters */
+  io->cols = rd->image->columns;
+  io->rows = rd->image->rows;
+  switch (rd->image->colorspace)
+    {
+      case GRAYColorspace:
+        io->flags = COLOR_SPACE_GRAYSCALE;
+        break;
+      default:
+        io->flags = COLOR_SPACE_RGB;
+        break;
+    }
+  if (rd->image->matte)
+    io->flags |= IMAGE_ALPHA;
+  io->number_of_colors = rd->image->colors;
+  if (rd->image->storage_class == PseudoClass && rd->image->compression != JPEGCompression)
+    io->flags |= IMAGE_IO_HAS_PALETTE;
+
+  io->read_cancel = libmagick_read_cancel;
+  return 1;
+
+err:
+  libmagick_destroy_read_data(rd);
+  return 0;
+}
+
+static inline byte
+libmagick_pixel_to_gray(PixelPacket *pixel)
+{
+  return rgb_to_gray_func(pixel->red, pixel->green, pixel->blue) >> QUANTUM_SCALE;
+}
+
+int
+libmagick_read_data(struct image_io *io)
+{
+  DBG("libmagick_read_data()");
+
+  struct magick_read_data *rd = io->read_data;
+
+  /* Quantize image */
+  switch (rd->image->colorspace)
+    {
+      case RGBColorspace:
+      case GRAYColorspace:
+        break;
+      default: ;
+        QuantizeInfo quantize;
+        GetQuantizeInfo(&quantize);
+        quantize.colorspace = RGBColorspace;
+        QuantizeImage(&quantize, rd->image);
+       break;
+    }
+
+  /* Prepare the image */
+  struct image_io_read_data_internals rdi;
+  uns read_flags = io->flags;
+  if ((read_flags & IMAGE_IO_USE_BACKGROUND) && !(read_flags & IMAGE_ALPHA))
+    read_flags = (read_flags | IMAGE_ALPHA) & IMAGE_CHANNELS_FORMAT;
+  if (unlikely(!image_io_read_data_prepare(&rdi, io, rd->image->columns, rd->image->rows, read_flags)))
+    {
+      libmagick_destroy_read_data(rd);
+      return 0;
+    }
+
+  /* Acquire pixels */
+  PixelPacket *src = (PixelPacket *)AcquireImagePixels(rd->image, 0, 0, rd->image->columns, rd->image->rows, &rd->exception);
+  if (unlikely(!src))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Cannot acquire image pixels.");
+      libmagick_destroy_read_data(rd);
+      image_io_read_data_break(&rdi, io);
+      return 0;
+    }
+
+  /* Convert pixels */
+  switch (rdi.image->pixel_size)
+    {
+      case 1:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE (rdi.image)
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 1
+#       define IMAGE_WALK_DO_STEP do{ \
+         walk_pos[0] = libmagick_pixel_to_gray(src); \
+         src++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      case 2:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE (rdi.image)
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 2
+#       define IMAGE_WALK_DO_STEP do{ \
+         walk_pos[0] = libmagick_pixel_to_gray(src); \
+         walk_pos[1] = ALPHA_TO_BYTE(src->opacity); \
+         src++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      case 3:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE (rdi.image)
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 3
+#       define IMAGE_WALK_DO_STEP do{ \
+         walk_pos[0] = QUANTUM_TO_BYTE(src->red); \
+         walk_pos[1] = QUANTUM_TO_BYTE(src->green); \
+         walk_pos[2] = QUANTUM_TO_BYTE(src->blue); \
+         src++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      case 4:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE (rdi.image)
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 4
+#       define IMAGE_WALK_DO_STEP do{ \
+         walk_pos[0] = QUANTUM_TO_BYTE(src->red); \
+         walk_pos[1] = QUANTUM_TO_BYTE(src->green); \
+         walk_pos[2] = QUANTUM_TO_BYTE(src->blue); \
+         walk_pos[3] = ALPHA_TO_BYTE(src->opacity); \
+         src++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      default:
+       ASSERT(0);
+    }
+
+  /* Free GraphicsMagick structures */
+  libmagick_destroy_read_data(rd);
+
+  /* Finish the image */
+  return image_io_read_data_finish(&rdi, io);
+}
+
+int
+libmagick_write(struct image_io *io)
+{
+  DBG("libmagick_write()");
+
+  /* Initialize GraphicsMagick */
+  int result = 0;
+  ExceptionInfo exception;
+  ImageInfo *info;
+  GetExceptionInfo(&exception);
+  info = CloneImageInfo(NULL);
+
+  /* Setup image parameters and allocate the image*/
+  struct image *img = io->image;
+  switch (img->flags & IMAGE_COLOR_SPACE)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+       info->colorspace = GRAYColorspace;
+       break;
+      case COLOR_SPACE_RGB:
+        info->colorspace = RGBColorspace;
+        break;
+      default:
+        ASSERT(0);
+    }
+  switch (io->format)
+    {
+      case IMAGE_FORMAT_JPEG:
+       strcpy(info->magick, "JPEG");
+       if (io->jpeg_quality)
+         info->quality = MIN(io->jpeg_quality, 100);
+       break;
+      case IMAGE_FORMAT_PNG:
+       strcpy(info->magick, "PNG");
+       break;
+      case IMAGE_FORMAT_GIF:
+        strcpy(info->magick, "GIF");
+       break;
+      default:
+        ASSERT(0);
+    }
+  Image *image = AllocateImage(info);
+  if (unlikely(!image))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_WRITE_FAILED, "GraphicsMagick failed to allocate the image.");
+      goto err;
+    }
+  image->columns = img->cols;
+  image->rows = img->rows;
+
+  /* Get pixels */
+  PixelPacket *pixels = SetImagePixels(image, 0, 0, img->cols, img->rows), *dest = pixels;
+  if (unlikely(!pixels))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_WRITE_FAILED, "Cannot get GraphicsMagick pixels.");
+      goto err2;
+    }
+
+  /* Convert pixels */
+  switch (img->pixel_size)
+    {
+      case 1:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE img
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 1
+#       define IMAGE_WALK_DO_STEP do{ \
+         dest->red = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->green = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->blue = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->opacity = 0; \
+         dest++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      case 2:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE img
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 2
+#       define IMAGE_WALK_DO_STEP do{ \
+         dest->red = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->green = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->blue = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->opacity = BYTE_TO_ALPHA(walk_pos[1]); \
+         dest++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      case 3:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE img
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 3
+#       define IMAGE_WALK_DO_STEP do{ \
+         dest->red = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->green = BYTE_TO_QUANTUM(walk_pos[1]); \
+         dest->blue = BYTE_TO_QUANTUM(walk_pos[2]); \
+         dest->opacity = 0; \
+         dest++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      case 4:
+#      define IMAGE_WALK_PREFIX(x) walk_##x
+#       define IMAGE_WALK_INLINE
+#      define IMAGE_WALK_IMAGE img
+#       define IMAGE_WALK_UNROLL 4
+#       define IMAGE_WALK_COL_STEP 4
+#       define IMAGE_WALK_DO_STEP do{ \
+         dest->red = BYTE_TO_QUANTUM(walk_pos[0]); \
+         dest->green = BYTE_TO_QUANTUM(walk_pos[1]); \
+         dest->blue = BYTE_TO_QUANTUM(walk_pos[2]); \
+         dest->opacity = BYTE_TO_ALPHA(walk_pos[3]); \
+         dest++; }while(0)
+#       include "images/image-walk.h"
+       break;
+
+      default:
+       ASSERT(0);
+    }
+
+  /* Store pixels */
+  if (unlikely(!SyncImagePixels(image)))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_WRITE_FAILED, "Cannot sync GraphicsMagick pixels.");
+      goto err2;
+    }
+
+  /* Write image */
+  size_t buf_len = 0;
+  void *buf = ImageToBlob(info, image, &buf_len, &exception);
+  if (unlikely(!buf))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_WRITE_FAILED, "GraphicsMagick failed to compress the image.");
+      goto err2;
+    }
+  if (unlikely(buf_len > MAX_FILE_SIZE))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_WRITE_FAILED, "Image too large.");
+      goto err2;
+    }
+
+  /* Write to stream */
+  bwrite(io->fastbuf, buf, buf_len);
+
+  /* Success */
+  result = 1;
+
+err2:
+  DestroyImage(image);
+err:
+  DestroyImageInfo(info);
+  DestroyExceptionInfo(&exception);
+  return result;
+}
diff --git a/images/io-libpng.c b/images/io-libpng.c
new file mode 100644 (file)
index 0000000..76b2d24
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ *     Image Library -- libpng
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/error.h"
+#include "images/color.h"
+#include "images/io-main.h"
+
+#include <png.h>
+#include <setjmp.h>
+
+struct libpng_read_data {
+  png_structp png_ptr;
+  png_infop info_ptr;
+  png_infop end_ptr;
+  png_uint_32 cols;
+  png_uint_32 rows;
+  int bit_depth;
+  int color_type;
+};
+
+static png_voidp
+libpng_malloc(png_structp png_ptr, png_size_t size)
+{
+  DBG("libpng_malloc(size=%u)", (uns)size);
+  return mp_alloc(png_get_mem_ptr(png_ptr), size);
+}
+
+static void
+libpng_free(png_structp png_ptr UNUSED, png_voidp ptr UNUSED)
+{
+  DBG("libpng_free()");
+}
+
+static void NONRET
+libpng_read_error(png_structp png_ptr, png_const_charp msg)
+{
+  DBG("libpng_read_error()");
+  IMAGE_ERROR(png_get_error_ptr(png_ptr), IMAGE_ERROR_READ_FAILED, "%s", msg);
+  longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+static void NONRET
+libpng_write_error(png_structp png_ptr, png_const_charp msg)
+{
+  DBG("libpng_write_error()");
+  IMAGE_ERROR(png_get_error_ptr(png_ptr), IMAGE_ERROR_WRITE_FAILED, "%s", msg);
+  longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+static void
+libpng_warning(png_structp png_ptr UNUSED, png_const_charp msg UNUSED)
+{
+  DBG("libpng_warning(): %s", (byte *)msg);
+}
+
+static void
+libpng_read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+  DBG("libpng_read_fn(len=%u)", (uns)length);
+  if (unlikely(bread((struct fastbuf *)png_get_io_ptr(png_ptr), (byte *)data, length) < length))
+    png_error(png_ptr, "Incomplete data");
+}
+
+static void
+libpng_write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+  DBG("libpng_write_fn(len=%u)", (uns)length);
+  bwrite((struct fastbuf *)png_get_io_ptr(png_ptr), (byte *)data, length);
+}
+
+static void
+libpng_flush_fn(png_structp png_ptr UNUSED)
+{
+  DBG("libpng_flush_fn()");
+}
+
+static void
+libpng_read_cancel(struct image_io *io)
+{
+  DBG("libpng_read_cancel()");
+
+  struct libpng_read_data *rd = io->read_data;
+  png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
+}
+
+int
+libpng_read_header(struct image_io *io)
+{
+  DBG("libpng_read_header()");
+
+  /* Create libpng structures */
+  struct libpng_read_data *rd = io->read_data = mp_alloc(io->internal_pool, sizeof(*rd));
+  rd->png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
+      io->context, libpng_read_error, libpng_warning,
+      io->internal_pool, libpng_malloc, libpng_free);
+  if (unlikely(!rd->png_ptr))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Cannot create libpng read structure.");
+      return 0;
+    }
+  rd->info_ptr = png_create_info_struct(rd->png_ptr);
+  if (unlikely(!rd->info_ptr))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Cannot create libpng info structure.");
+      png_destroy_read_struct(&rd->png_ptr, NULL, NULL);
+      return 0;
+    }
+  rd->end_ptr = png_create_info_struct(rd->png_ptr);
+  if (unlikely(!rd->end_ptr))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Cannot create libpng info structure.");
+      png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, NULL);
+      return 0;
+    }
+
+  /* Setup libpng longjump */
+  if (unlikely(setjmp(png_jmpbuf(rd->png_ptr))))
+    {
+      DBG("Libpng failed to read the image, longjump saved us");
+      png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
+      return 0;
+    }
+
+  /* Setup libpng IO */
+  png_set_read_fn(rd->png_ptr, io->fastbuf, libpng_read_fn);
+  png_set_user_limits(rd->png_ptr, image_max_dim, image_max_dim);
+
+  /* Read header */
+  png_read_info(rd->png_ptr, rd->info_ptr);
+  png_get_IHDR(rd->png_ptr, rd->info_ptr, &rd->cols, &rd->rows, &rd->bit_depth, &rd->color_type, NULL, NULL, NULL);
+
+  /* Fill image_io values */
+  io->cols = rd->cols;
+  io->rows = rd->rows;
+  switch (rd->color_type)
+    {
+      case PNG_COLOR_TYPE_GRAY:
+        io->flags = COLOR_SPACE_GRAYSCALE;
+       io->number_of_colors = 1 << 8;
+        break;
+      case PNG_COLOR_TYPE_GRAY_ALPHA:
+        io->flags = COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA;
+       io->number_of_colors = 1 << 8;
+        break;
+      case PNG_COLOR_TYPE_RGB:
+        io->flags = COLOR_SPACE_RGB;
+       io->number_of_colors = 1 << 24;
+        break;
+      case PNG_COLOR_TYPE_RGB_ALPHA:
+       io->number_of_colors = 1 << 24;
+        io->flags = COLOR_SPACE_RGB | IMAGE_ALPHA;
+        break;
+      case PNG_COLOR_TYPE_PALETTE:
+        io->flags = COLOR_SPACE_RGB | IMAGE_ALPHA | IMAGE_IO_HAS_PALETTE;
+       int num_palette;
+       if (png_get_PLTE(rd->png_ptr, rd->info_ptr, NULL, &num_palette))
+         io->number_of_colors = num_palette;
+       else
+         io->number_of_colors = 1 << rd->bit_depth;
+        break;
+      default:
+        png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
+        IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Unknown color type");
+        break;
+    }
+
+  /* Success */
+  io->read_cancel = libpng_read_cancel;
+  return 1;
+}
+
+int
+libpng_read_data(struct image_io *io)
+{
+  DBG("libpng_read_data()");
+
+  struct libpng_read_data *rd = io->read_data;
+
+  /* Test supported pixel formats */
+  switch (io->flags & IMAGE_COLOR_SPACE)
+    {
+      case COLOR_SPACE_GRAYSCALE:
+      case COLOR_SPACE_RGB:
+       break;
+      default:
+        png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
+       IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Unsupported color space.");
+        return 0;
+    }
+
+  struct image_io_read_data_internals rdi;
+  rdi.image = NULL;
+
+  if (setjmp(png_jmpbuf(rd->png_ptr)))
+    {
+      DBG("Libpng failed to read the image, longjump saved us");
+      png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
+      if (rdi.image)
+        image_io_read_data_break(&rdi, io);
+      return 0;
+    }
+
+  uns read_flags = io->flags;
+  
+  /* Apply transformations */
+  if (rd->bit_depth == 16)
+    png_set_strip_16(rd->png_ptr);
+  switch (rd->color_type)
+    {
+      case PNG_COLOR_TYPE_PALETTE:
+       if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_GRAYSCALE)
+         {
+           png_set_palette_to_rgb(rd->png_ptr);
+           png_set_rgb_to_gray_fixed(rd->png_ptr, 1, 21267, 71514);
+         }
+       else
+         png_set_palette_to_rgb(rd->png_ptr);
+       if (!(io->flags & IMAGE_ALPHA))
+         {
+           if (io->flags & IMAGE_IO_USE_BACKGROUND)
+             {
+                png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
+               read_flags = (read_flags | IMAGE_ALPHA) & IMAGE_CHANNELS_FORMAT;
+             }
+           else if ((io->flags & IMAGE_PIXEL_FORMAT) == (COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED))
+              png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
+            else
+             png_set_strip_alpha(rd->png_ptr);
+         }
+       else
+          png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
+       break;
+      case PNG_COLOR_TYPE_GRAY:
+       if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_RGB)
+          png_set_gray_to_rgb(rd->png_ptr);
+       if (io->flags & IMAGE_ALPHA)
+         png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
+       break;
+      case PNG_COLOR_TYPE_GRAY_ALPHA:
+       if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_RGB)
+          png_set_gray_to_rgb(rd->png_ptr);
+       if (!(io->flags & IMAGE_ALPHA))
+         {
+           if (io->flags & IMAGE_IO_USE_BACKGROUND)
+             read_flags = (read_flags | IMAGE_ALPHA) & IMAGE_CHANNELS_FORMAT;
+           else
+              png_set_strip_alpha(rd->png_ptr);
+         }  
+       break;
+      case PNG_COLOR_TYPE_RGB:
+       if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_GRAYSCALE)
+         png_set_rgb_to_gray_fixed(rd->png_ptr, 1, 21267, 71514);
+       if ((io->flags & IMAGE_ALPHA) || (io->flags & IMAGE_PIXEL_FORMAT) == (COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED))
+         png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
+       break;
+      case PNG_COLOR_TYPE_RGB_ALPHA:
+       if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_GRAYSCALE)
+         png_set_rgb_to_gray_fixed(rd->png_ptr, 1, 21267, 71514);
+       if (!(io->flags & IMAGE_ALPHA))
+         if (io->flags & IMAGE_IO_USE_BACKGROUND)
+           read_flags = (read_flags | IMAGE_ALPHA) & IMAGE_CHANNELS_FORMAT;
+         else if ((io->flags & IMAGE_PIXEL_FORMAT) != (COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED))
+            png_set_strip_alpha(rd->png_ptr);
+       break;
+      default:
+       ASSERT(0);
+    }
+  png_read_update_info(rd->png_ptr, rd->info_ptr);
+
+  /* Prepare the image */
+  if (unlikely(!image_io_read_data_prepare(&rdi, io, rd->cols, rd->rows, read_flags)))
+    {
+      png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
+      return 0;
+    }
+  /* Read image data */
+  DBG("Reading image data");
+  struct image *img = rdi.image;
+  byte *pixels = img->pixels;
+  png_bytep rows[img->rows];
+  for (uns r = 0; r < img->rows; r++, pixels += img->row_size)
+    rows[r] = (png_bytep)pixels;
+  png_read_image(rd->png_ptr, rows);
+  png_read_end(rd->png_ptr, rd->end_ptr);
+
+  /* Destroy libpng read structure */
+  png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
+
+  /* Finish the image  */
+  return image_io_read_data_finish(&rdi, io);
+}
+
+int
+libpng_write(struct image_io *io)
+{
+  DBG("libpng_write()");
+
+  /* Create libpng structures */
+  png_structp png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
+      io->context, libpng_write_error, libpng_warning,
+      io->internal_pool, libpng_malloc, libpng_free);
+  if (unlikely(!png_ptr))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_WRITE_FAILED, "Cannot create libpng write structure.");
+      return 0;
+    }
+  png_infop info_ptr = png_create_info_struct(png_ptr);
+  if (unlikely(!info_ptr))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_WRITE_FAILED, "Cannot create libpng info structure.");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 0;
+    }
+
+  /* Setup libpng longjump */
+  if (unlikely(setjmp(png_jmpbuf(png_ptr))))
+    {
+      DBG("Libpng failed to write the image, longjump saved us.");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 0;
+    }
+
+  /* Setup libpng IO */
+  png_set_write_fn(png_ptr, io->fastbuf, libpng_write_fn, libpng_flush_fn);
+
+  /* Setup PNG parameters */
+  struct image *img = io->image;
+  switch (img->flags & IMAGE_PIXEL_FORMAT)
+    {
+      case COLOR_SPACE_GRAYSCALE | IMAGE_PIXELS_ALIGNED:
+        png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_GRAY,
+         PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+       break;
+      case COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA | IMAGE_PIXELS_ALIGNED:
+        png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_GRAY_ALPHA,
+         PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+       break;
+      case COLOR_SPACE_RGB:
+        png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_RGB,
+         PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+       break;
+      case COLOR_SPACE_RGB | IMAGE_ALPHA | IMAGE_PIXELS_ALIGNED:
+        png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_RGB_ALPHA,
+         PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+       break;
+      case COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED:
+        png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_RGB,
+         PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+       png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
+       break;
+      default:
+        ASSERT(0);
+    }
+  png_write_info(png_ptr, info_ptr);
+
+  /* Write pixels */
+  byte *pixels = img->pixels;
+  png_bytep rows[img->rows];
+  for (uns r = 0; r < img->rows; r++, pixels += img->row_size)
+    rows[r] = (png_bytep)pixels;
+  png_write_image(png_ptr, rows);
+  png_write_end(png_ptr, info_ptr);
+
+  /* Free libpng structure */
+  png_destroy_write_struct(&png_ptr, &info_ptr);
+  return 1;
+}
diff --git a/images/io-libungif.c b/images/io-libungif.c
new file mode 100644 (file)
index 0000000..c464843
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ *     Image Library -- libungif
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "images/images.h"
+#include "images/error.h"
+#include "images/color.h"
+#include "images/io-main.h"
+
+#include <gif_lib.h>
+
+struct libungif_read_data {
+  GifFileType *gif;
+  int transparent_index;
+};
+
+static int
+libungif_read_func(GifFileType *gif, GifByteType *ptr, int len)
+{
+  DBG("libungif_read_func(len=%d)", len);
+  return bread((struct fastbuf *)gif->UserData, (byte *)ptr, len);
+}
+
+static void
+libungif_read_cancel(struct image_io *io)
+{
+  DBG("libungif_read_cancel()");
+
+  struct libungif_read_data *rd = io->read_data;
+  DGifCloseFile(rd->gif);
+}
+
+int
+libungif_read_header(struct image_io *io)
+{
+  DBG("libungif_read_header()");
+
+  /* Create libungif structure */
+  GifFileType *gif;
+  if (unlikely(!(gif = DGifOpen(io->fastbuf, libungif_read_func))))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Cannot create libungif structure.");
+      return 0;
+    }
+
+  struct libungif_read_data *rd = io->read_data = mp_alloc(io->internal_pool, sizeof(*rd));
+  rd->gif = gif;
+
+  DBG("executing DGifSlurp()");
+  if (unlikely(DGifSlurp(gif) != GIF_OK))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Gif read failed.");
+      DGifCloseFile(gif);
+      return 0;
+    }
+
+  DBG("ImageCount=%d ColorResolution=%d SBackGroundColor=%d SColorMap=%p", gif->ImageCount, gif->SColorResolution, gif->SBackGroundColor, gif->SColorMap);
+  if (unlikely(!gif->ImageCount))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "There are no images in gif file.");
+      DGifCloseFile(gif);
+      return 0;
+    }
+
+  /* Read image parameters */
+  SavedImage *image = gif->SavedImages;
+  if (unlikely(image->ImageDesc.Width <= 0 || image->ImageDesc.Height <= 0 ||
+      image->ImageDesc.Width > (int)image_max_dim || image->ImageDesc.Height > (int)image_max_dim))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_DIMENSIONS, "Invalid gif dimensions.");
+      DGifCloseFile(gif);
+      return 0;
+    }
+  ColorMapObject *color_map = image->ImageDesc.ColorMap ? : gif->SColorMap;
+  if (unlikely(!color_map))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Missing palette.");
+      DGifCloseFile(gif);
+      return 0;
+    }
+  io->cols = image->ImageDesc.Width;
+  io->rows = image->ImageDesc.Height;
+  if (unlikely((io->number_of_colors = color_map->ColorCount) > 256))
+    {
+      IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Too many gif colors.");
+      DGifCloseFile(gif);
+      return 0;
+    }
+  io->flags = COLOR_SPACE_RGB | IMAGE_IO_HAS_PALETTE;
+
+  /* Search extension blocks */
+  rd->transparent_index = -1;
+  for (int i = 0; i < image->ExtensionBlockCount; i++)
+    {
+      ExtensionBlock *e = image->ExtensionBlocks + i;
+      if (e->Function == 0xF9)
+        {
+         DBG("Found graphics control extension");
+         if (unlikely(e->ByteCount != 4))
+           {
+              IMAGE_ERROR(io->context, IMAGE_ERROR_READ_FAILED, "Invalid graphics control extension.");
+              DGifCloseFile(gif);
+              return 0;
+           }
+         byte *b = e->Bytes;
+         /* transparent color present */
+         if (b[0] & 1)
+           {
+             rd->transparent_index = b[3];
+             io->flags |= IMAGE_ALPHA;
+             if (gif->SColorMap)
+               {
+                  GifColorType *background = color_map->Colors + gif->SBackGroundColor;
+                  color_make_rgb(&io->background_color, background->Red, background->Green, background->Blue);
+               }
+           }
+         /* We've got everything we need :-) */
+         break;
+       }
+      else
+       DBG("Found unknown extension: type=%d size=%d", e->Function, e->ByteCount);
+    }
+
+  /* Success */
+  io->read_cancel = libungif_read_cancel;
+  return 1;
+}
+
+int
+libungif_read_data(struct image_io *io)
+{
+  DBG("libungif_read_data()");
+
+  struct libungif_read_data *rd = io->read_data;
+  GifFileType *gif = rd->gif;
+  SavedImage *image = gif->SavedImages;
+
+  /* Prepare image */
+  struct image_io_read_data_internals rdi;
+  if (unlikely(!image_io_read_data_prepare(&rdi, io, image->ImageDesc.Width, image->ImageDesc.Height, io->flags)))
+    {
+      DGifCloseFile(gif);
+      return 0;
+    }
+
+  /* Get pixels and palette */
+  byte *pixels = (byte *)image->RasterBits;
+  ColorMapObject *color_map = image->ImageDesc.ColorMap ? : gif->SColorMap;
+  GifColorType *palette = color_map->Colors;
+  byte *img_end = rdi.image->pixels + rdi.image->image_size;
+
+  /* Handle deinterlacing */
+  uns dein_step, dein_next;
+  if (image->ImageDesc.Interlace)
+    {
+      DBG("Deinterlaced image");
+      dein_step = dein_next = rdi.image->row_size << 3;
+    }
+  else
+    dein_step = dein_next = rdi.image->row_size;
+
+  /* Convert pixels */
+  switch (rdi.image->pixel_size)
+    {
+      case 1:
+       {
+         byte pal[256], *pal_pos = pal, *pal_end = pal + 256;
+         for (uns i = 0; i < (uns)color_map->ColorCount; i++, pal_pos++, palette++)
+           *pal_pos = rgb_to_gray_func(palette->Red, palette->Green, palette->Blue);
+         if (pal_pos != pal_end)
+           bzero(pal_pos, pal_end - pal_pos);
+         if (rd->transparent_index >= 0 && (io->flags & IMAGE_IO_USE_BACKGROUND))
+           color_put_grayscale(pal + rd->transparent_index, &io->background_color);
+#        define DO_ROW_END do{ \
+             walk_row_start += dein_step; \
+             if (walk_row_start >= img_end) \
+               { uns n = dein_next >> 1; walk_row_start = rdi.image->pixels + n, dein_step = dein_next; dein_next = n; } \
+           }while(0)
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#        define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE (rdi.image)
+#        define IMAGE_WALK_UNROLL 4
+#        define IMAGE_WALK_COL_STEP 1
+#        define IMAGE_WALK_ROW_STEP 0
+#        define IMAGE_WALK_DO_STEP do{ *walk_pos = pal[*pixels++]; }while(0)
+#        define IMAGE_WALK_DO_ROW_END DO_ROW_END
+#        include "images/image-walk.h"
+         break;
+       }
+      case 2:
+       {
+         byte pal[256 * 2], *pal_pos = pal, *pal_end = pal + 256 * 2;
+         for (uns i = 0; i < (uns)color_map->ColorCount; i++, pal_pos += 2, palette++)
+           {
+             pal_pos[0] = rgb_to_gray_func(palette->Red, palette->Green, palette->Blue);
+             pal_pos[1] = 255;
+           }
+         if (pal_pos != pal_end)
+           bzero(pal_pos, pal_end - pal_pos);
+         if (rd->transparent_index >= 0)
+           pal[rd->transparent_index * 2 + 1] = 0;
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#        define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE (rdi.image)
+#        define IMAGE_WALK_UNROLL 4
+#        define IMAGE_WALK_COL_STEP 2
+#        define IMAGE_WALK_ROW_STEP 0
+#        define IMAGE_WALK_DO_STEP do{ *(u16 *)walk_pos = ((u16 *)pal)[*pixels++]; }while(0)
+#        define IMAGE_WALK_DO_ROW_END DO_ROW_END
+#        include "images/image-walk.h"
+         break;
+       }
+      case 3:
+       {
+         byte pal[256 * 4], *pal_pos = pal, *pal_end = pal + 256 * 4;
+         for (uns i = 0; i < (uns)color_map->ColorCount; i++, pal_pos += 4, palette++)
+           {
+             pal_pos[0] = palette->Red;
+             pal_pos[1] = palette->Green;
+             pal_pos[2] = palette->Blue;
+           }
+         if (pal_pos != pal_end)
+           bzero(pal_pos, pal_end - pal_pos);
+         if (rd->transparent_index >= 0 && (io->flags & IMAGE_IO_USE_BACKGROUND))
+           color_put_rgb(pal + 4 * rd->transparent_index, &io->background_color);
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#        define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE (rdi.image)
+#        define IMAGE_WALK_UNROLL 4
+#        define IMAGE_WALK_COL_STEP 3
+#        define IMAGE_WALK_ROW_STEP 0
+#        define IMAGE_WALK_DO_STEP do{ byte *p = pal + 4 * (*pixels++); walk_pos[0] = p[0]; walk_pos[1] = p[1]; walk_pos[2] = p[2]; }while(0)
+#        define IMAGE_WALK_DO_ROW_END DO_ROW_END
+#        include "images/image-walk.h"
+         break;
+       }
+      case 4:
+       {
+         byte pal[256 * 4], *pal_pos = pal, *pal_end = pal + 256 * 4;
+         for (uns i = 0; i < (uns)color_map->ColorCount; i++, pal_pos += 4, palette++)
+           {
+             pal_pos[0] = palette->Red;
+             pal_pos[1] = palette->Green;
+             pal_pos[2] = palette->Blue;
+             pal_pos[3] = 255;
+           }
+         if (pal_pos != pal_end)
+           bzero(pal_pos, pal_end - pal_pos);
+         if (rd->transparent_index >= 0)
+           pal[rd->transparent_index * 4 + 3] = 0;
+#        define IMAGE_WALK_PREFIX(x) walk_##x
+#        define IMAGE_WALK_INLINE
+#        define IMAGE_WALK_IMAGE (rdi.image)
+#        define IMAGE_WALK_UNROLL 4
+#        define IMAGE_WALK_COL_STEP 4
+#        define IMAGE_WALK_ROW_STEP 0
+#        define IMAGE_WALK_DO_STEP do{ *(u32 *)walk_pos = ((u32 *)pal)[*pixels++]; }while(0)
+#        define IMAGE_WALK_DO_ROW_END DO_ROW_END
+#        include "images/image-walk.h"
+         break;
+       }
+      default:
+        ASSERT(0);
+    }
+
+  /* Destroy libungif structure */
+  DGifCloseFile(gif);
+
+  /* Finish image */
+  return image_io_read_data_finish(&rdi, io);
+}
diff --git a/images/io-main.c b/images/io-main.c
new file mode 100644 (file)
index 0000000..7b176b7
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+ *     Image Library -- Image compression/decompression interface
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/mempool.h"
+#include "images/images.h"
+#include "images/error.h"
+#include "images/io-main.h"
+
+#include <string.h>
+
+int
+image_io_init(struct image_context *ctx, struct image_io *io)
+{
+  DBG("image_io_init()");
+  bzero(io, sizeof(*io));
+  io->context = ctx;
+#ifdef CONFIG_IMAGES_LIBJPEG
+  if (!libjpeg_init(io))
+    goto libjpeg_failed;
+#endif
+#ifdef CONFIG_IMAGES_LIBPNG
+  if (!libpng_init(io))
+    goto libpng_failed;
+#endif
+#ifdef CONFIG_IMAGES_LIBUNGIF
+  if (!libungif_init(io))
+    goto libungif_failed;
+#endif
+#ifdef CONFIG_IMAGES_LIBMAGICK
+  if (!libmagick_init(io))
+    goto libmagick_failed;
+#endif
+  io->internal_pool = mp_new(1024);
+  return 1;
+#ifdef CONFIG_IMAGES_LIBMAGICK
+  libmagick_cleanup(io);
+libmagick_failed:
+#endif
+#ifdef CONFIG_IMAGES_LIBUNGIF
+  libungif_cleanup(io);
+libungif_failed:
+#endif
+#ifdef CONFIG_IMAGES_LIBPNG
+  libpng_cleanup(io);
+libpng_failed:  
+#endif
+#ifdef CONFIG_IMAGES_LIBJPEG
+  libjpeg_cleanup(io);
+libjpeg_failed:
+#endif
+  return 0;
+}
+
+static inline void
+image_io_read_cancel(struct image_io *io)
+{
+  if (io->read_cancel)
+    {
+      io->read_cancel(io);
+      io->read_cancel = NULL;
+    }
+}
+
+static inline void
+image_io_image_destroy(struct image_io *io)
+{
+  if (io->image && (io->flags & IMAGE_IO_NEED_DESTROY))
+    {
+      image_destroy(io->image);
+      io->flags &= ~IMAGE_IO_NEED_DESTROY;
+      io->image = NULL;
+    }
+}
+
+void
+image_io_cleanup(struct image_io *io)
+{
+  DBG("image_io_cleanup()");
+  image_io_read_cancel(io);
+  image_io_image_destroy(io);
+#ifdef CONFIG_IMAGES_LIBMAGICK
+  libmagick_cleanup(io);
+#endif
+#ifdef CONFIG_IMAGES_LIBUNGIF
+  libungif_cleanup(io);
+#endif
+#ifdef CONFIG_IMAGES_LIBPNG
+  libpng_cleanup(io);
+#endif
+#ifdef CONFIG_IMAGES_LIBJPEG
+  libjpeg_cleanup(io);
+#endif
+  mp_delete(io->internal_pool);
+}
+
+void
+image_io_reset(struct image_io *io)
+{
+  DBG("image_io_reset()");
+  image_io_read_cancel(io);
+  image_io_image_destroy(io);
+  struct mempool *pool = io->internal_pool;
+  struct image_context *ctx = io->context;
+  mp_flush(pool);
+  bzero(io, sizeof(*io));
+  io->internal_pool = pool;
+  io->context = ctx;
+}
+
+int
+image_io_read_header(struct image_io *io)
+{
+  DBG("image_io_read_header()");
+  image_io_read_cancel(io);
+  image_io_image_destroy(io);
+  switch (io->format) {
+    case IMAGE_FORMAT_JPEG:
+#if defined(CONFIG_IMAGES_LIBJPEG)
+      return libjpeg_read_header(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      return libmagick_read_header(io);
+#endif
+      break;
+
+    case IMAGE_FORMAT_PNG:
+#if defined(CONFIG_IMAGES_LIBPNG)
+      return libpng_read_header(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      return libmagick_read_header(io);
+#endif
+      break;
+
+    case IMAGE_FORMAT_GIF:
+#if defined(CONFIG_IMAGES_LIBUNGIF) || defined(CONFIG_IMAGES_LIBGIF)
+      return libungif_read_header(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      return libmagick_read_header(io);
+#endif
+      break;
+
+    case IMAGE_FORMAT_UNDEFINED:
+#if defined (CONFIG_IMAGES_LIBMAGICK)
+      return libmagick_read_header(io);
+#endif
+      break;
+
+    default:
+      ASSERT(0);
+  }
+  IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_FILE_FORMAT, "Image format not supported.");
+  return 0;
+}
+
+struct image *
+image_io_read_data(struct image_io *io, int ref)
+{
+  DBG("image_io_read_data()");
+  ASSERT(io->read_cancel);
+  io->read_cancel = NULL;
+  int result;
+  switch (io->format) {
+    case IMAGE_FORMAT_JPEG:
+#if defined(CONFIG_IMAGES_LIBJPEG)
+      result = libjpeg_read_data(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      result = libmagick_read_data(io);
+#else
+      ASSERT(0);
+#endif
+      break;
+
+    case IMAGE_FORMAT_PNG:
+#if defined(CONFIG_IMAGES_LIBPNG)
+      result = libpng_read_data(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      result = libmagick_read_data(io);
+#else
+      ASSERT(0);
+#endif
+      break;
+
+    case IMAGE_FORMAT_GIF:
+#if defined(CONFIG_IMAGES_LIBUNGIF) || defined(CONFIG_IMAGES_LIBGIF)
+      result = libungif_read_data(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      result = libmagick_read_data(io);
+#else
+      ASSERT(0);
+#endif
+      break;
+
+    case IMAGE_FORMAT_UNDEFINED:
+#if defined(CONFIG_IMAGES_LIBMAGICK)
+      result = libmagick_read_data(io);
+#else
+      ASSERT(0);
+#endif
+      break;
+
+    default:
+      ASSERT(0);
+  }
+  if (result)
+    {
+      if (!ref)
+       io->flags |= IMAGE_IO_NEED_DESTROY;
+      else
+       io->flags &= ~IMAGE_IO_NEED_DESTROY;
+      return io->image;
+    }
+  else
+    return NULL;
+}
+
+struct image *
+image_io_read(struct image_io *io, int ref)
+{
+  if (!image_io_read_header(io))
+    return NULL;
+  return image_io_read_data(io, ref);
+}
+
+int
+image_io_write(struct image_io *io)
+{
+  DBG("image_io_write()");
+  image_io_read_cancel(io);
+  switch (io->format) {
+    case IMAGE_FORMAT_JPEG:
+#if defined(CONFIG_IMAGES_LIBJPEG)
+      return libjpeg_write(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      return libmagick_write(io);
+#endif
+      break;
+
+    case IMAGE_FORMAT_PNG:
+#if defined(CONFIG_IMAGES_LIBPNG)
+      return libpng_write(io);
+#elif defined(CONFIG_IMAGES_LIBMAGICK)
+      return libmagick_write(io);
+#endif
+      break;
+
+    case IMAGE_FORMAT_GIF:
+#if defined(CONFIG_IMAGES_LIBMAGICK)
+      return libmagick_write(io);
+#endif
+      break;
+
+    default:
+      break;
+  }
+  IMAGE_ERROR(io->context, IMAGE_ERROR_INVALID_FILE_FORMAT, "Output format not supported.");
+  return 0;
+}
+
+byte *
+image_format_to_extension(enum image_format format)
+{
+  switch (format)
+    {
+      case IMAGE_FORMAT_JPEG:
+       return "jpg";
+      case IMAGE_FORMAT_PNG:
+       return "png";
+      case IMAGE_FORMAT_GIF:
+       return "gif";
+      default:
+       return NULL;
+    }
+}
+
+enum image_format
+image_extension_to_format(byte *extension)
+{
+  if (!strcasecmp(extension, "jpg"))
+    return IMAGE_FORMAT_JPEG;
+  if (!strcasecmp(extension, "jpeg"))
+    return IMAGE_FORMAT_JPEG;
+  if (!strcasecmp(extension, "png"))
+    return IMAGE_FORMAT_PNG;
+  if (!strcasecmp(extension, "gif"))
+    return IMAGE_FORMAT_GIF;
+  return IMAGE_FORMAT_UNDEFINED;
+}
+
+enum image_format
+image_file_name_to_format(byte *file_name)
+{
+  byte *extension = strrchr(file_name, '.');
+  return extension ? image_extension_to_format(extension + 1) : IMAGE_FORMAT_UNDEFINED;
+}
+
+struct image *
+image_io_read_data_prepare(struct image_io_read_data_internals *rdi, struct image_io *io, uns cols, uns rows, uns flags)
+{
+  DBG("image_io_read_data_prepare()");
+  if (rdi->need_transformations = io->cols != cols || io->rows != rows ||
+      ((io->flags ^ flags) & IMAGE_NEW_FLAGS))
+    return rdi->image = image_new(io->context, cols, rows, flags & IMAGE_IO_IMAGE_FLAGS, NULL);
+  else
+    return rdi->image = image_new(io->context, io->cols, io->rows, io->flags & IMAGE_IO_IMAGE_FLAGS, io->pool);
+}
+
+int
+image_io_read_data_finish(struct image_io_read_data_internals *rdi, struct image_io *io)
+{
+  DBG("image_io_read_data_finish()");
+  if (rdi->need_transformations)
+    {
+      /* Scale the image */
+      if (io->cols != rdi->image->cols || io->rows != rdi->image->rows)
+        {
+         DBG("Scaling image");
+         uns flags = rdi->image->flags;
+         if (!(rdi->need_transformations = ((io->flags ^ rdi->image->flags) & (IMAGE_NEW_FLAGS & ~IMAGE_PIXELS_ALIGNED))))
+           flags = io->flags;
+         struct image *img = image_new(io->context, io->cols, io->rows, flags, rdi->need_transformations ? NULL : io->pool);
+         if (unlikely(!img))
+           {
+             image_destroy(rdi->image);
+             return 0;
+           }
+          if (unlikely(!image_scale(io->context, img, rdi->image)))
+            {
+              image_destroy(rdi->image);
+             image_destroy(img);
+             return 0;
+           }
+         image_destroy(rdi->image);
+         rdi->image = img;
+       }
+
+      /* Merge with background */
+      if ((io->flags ^ rdi->image->flags) & IMAGE_ALPHA)
+        {
+         DBG("Applying background");
+         uns flags = rdi->image->flags & ~IMAGE_ALPHA;
+         if (!(rdi->need_transformations = (flags ^ io->flags) & (IMAGE_NEW_FLAGS & ~IMAGE_PIXELS_ALIGNED)))
+           flags = io->flags;
+         struct image *img = image_new(io->context, io->cols, io->rows, flags, rdi->need_transformations ? NULL : io->pool);
+         if (unlikely(!img))
+           {
+             image_destroy(rdi->image);
+             return 0;
+           }
+          if (unlikely(!image_apply_background(io->context, img, rdi->image, &io->background_color)))
+            {
+              image_destroy(rdi->image);
+             image_destroy(img);
+             return 0;
+           }
+         image_destroy(rdi->image);
+         rdi->image = img;
+       }
+
+      // FIXME: support for various color spaces
+
+      ASSERT(!rdi->need_transformations);
+    }
+
+  /* Success */
+  io->image = rdi->image;
+  return 1;
+}
+
+void
+image_io_read_data_break(struct image_io_read_data_internals *rdi, struct image_io *io UNUSED)
+{
+  DBG("image_io_read_data_break()");
+  if (rdi->image)
+    image_destroy(rdi->image);
+}
diff --git a/images/io-main.h b/images/io-main.h
new file mode 100644 (file)
index 0000000..477c5dd
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef _IMAGES_IO_MAIN_H
+#define _IMAGES_IO_MAIN_H
+
+static inline int libjpeg_init(struct image_io *io UNUSED) { return 1; }
+static inline void libjpeg_cleanup(struct image_io *io UNUSED) {}
+int libjpeg_read_header(struct image_io *io);
+int libjpeg_read_data(struct image_io *io);
+int libjpeg_write(struct image_io *io);
+
+static inline int libpng_init(struct image_io *io UNUSED) { return 1; }
+static inline void libpng_cleanup(struct image_io *io UNUSED) {}
+int libpng_read_header(struct image_io *io);
+int libpng_read_data(struct image_io *io);
+int libpng_write(struct image_io *io);
+
+static inline int libungif_init(struct image_io *io UNUSED) { return 1; }
+static inline void libungif_cleanup(struct image_io *io UNUSED) {}
+int libungif_read_header(struct image_io *io);
+int libungif_read_data(struct image_io *io);
+
+int libmagick_init(struct image_io *io);
+void libmagick_cleanup(struct image_io *io);
+int libmagick_read_header(struct image_io *io);
+int libmagick_read_data(struct image_io *io);
+int libmagick_write(struct image_io *io);
+
+struct image_io_read_data_internals {
+  struct image *image;
+  int need_transformations;
+};
+
+struct image *image_io_read_data_prepare(struct image_io_read_data_internals *rdi, struct image_io *io, uns cols, uns rows, uns flags);
+int image_io_read_data_finish(struct image_io_read_data_internals *rdi, struct image_io *io);
+void image_io_read_data_break(struct image_io_read_data_internals *rdi, struct image_io *io);
+
+#endif
diff --git a/images/math.c b/images/math.c
new file mode 100644 (file)
index 0000000..f88daa6
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *     Image Library -- Math routines
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU General Public License.
+ */
+
+#include "lib/lib.h"
+#include "images/math.h"
+
+const u32 fast_div_tab[] = {
+          0, 4294967295U,2147483648U,1431655766, 1073741824,  858993460,  715827883,  613566757,
+  536870912,  477218589,  429496730,  390451573,  357913942,  330382100,  306783379,  286331154,
+  268435456,  252645136,  238609295,  226050911,  214748365,  204522253,  195225787,  186737709,
+  178956971,  171798692,  165191050,  159072863,  153391690,  148102321,  143165577,  138547333,
+  134217728,  130150525,  126322568,  122713352,  119304648,  116080198,  113025456,  110127367,
+  107374183,  104755300,  102261127,   99882961,   97612894,   95443718,   93368855,   91382283,
+   89478486,   87652394,   85899346,   84215046,   82595525,   81037119,   79536432,   78090315,
+   76695845,   75350304,   74051161,   72796056,   71582789,   70409300,   69273667,   68174085,
+   67108864,   66076420,   65075263,   64103990,   63161284,   62245903,   61356676,   60492498,
+   59652324,   58835169,   58040099,   57266231,   56512728,   55778797,   55063684,   54366675,
+   53687092,   53024288,   52377650,   51746594,   51130564,   50529028,   49941481,   49367441,
+   48806447,   48258060,   47721859,   47197443,   46684428,   46182445,   45691142,   45210183,
+   44739243,   44278014,   43826197,   43383509,   42949673,   42524429,   42107523,   41698712,
+   41297763,   40904451,   40518560,   40139882,   39768216,   39403370,   39045158,   38693400,
+   38347923,   38008561,   37675152,   37347542,   37025581,   36709123,   36398028,   36092163,
+   35791395,   35495598,   35204650,   34918434,   34636834,   34359739,   34087043,   33818641,
+   33554432,   33294321,   33038210,   32786010,   32537632,   32292988,   32051995,   31814573,
+   31580642,   31350127,   31122952,   30899046,   30678338,   30460761,   30246249,   30034737,
+   29826162,   29620465,   29417585,   29217465,   29020050,   28825284,   28633116,   28443493,
+   28256364,   28071682,   27889399,   27709467,   27531842,   27356480,   27183338,   27012373,
+   26843546,   26676816,   26512144,   26349493,   26188825,   26030105,   25873297,   25718368,
+   25565282,   25414008,   25264514,   25116768,   24970741,   24826401,   24683721,   24542671,
+   24403224,   24265352,   24129030,   23994231,   23860930,   23729102,   23598722,   23469767,
+   23342214,   23216040,   23091223,   22967740,   22845571,   22724695,   22605092,   22486740,
+   22369622,   22253717,   22139007,   22025474,   21913099,   21801865,   21691755,   21582751,
+   21474837,   21367997,   21262215,   21157475,   21053762,   20951060,   20849356,   20748635,
+   20648882,   20550083,   20452226,   20355296,   20259280,   20164166,   20069941,   19976593,
+   19884108,   19792477,   19701685,   19611723,   19522579,   19434242,   19346700,   19259944,
+   19173962,   19088744,   19004281,   18920561,   18837576,   18755316,   18673771,   18592933,
+   18512791,   18433337,   18354562,   18276457,   18199014,   18122225,   18046082,   17970575,
+   17895698,   17821442,   17747799,   17674763,   17602325,   17530479,   17459217,   17388532,
+   17318417,   17248865,   17179870,   17111424,   17043522,   16976156,   16909321,   16843010 };
+
+const byte fast_sqrt_tab[] = {
+    0,   16,   23,   28,   32,   36,   39,   43,   46,   48,   51,   53,   56,   58,   60,   62,
+   64,   66,   68,   70,   72,   74,   75,   77,   79,   80,   82,   83,   85,   86,   88,   89,
+   91,   92,   94,   95,   96,   98,   99,  100,  101,  103,  104,  105,  106,  108,  109,  110,
+  111,  112,  113,  115,  116,  117,  118,  119,  120,  121,  122,  123,  124,  125,  126,  127,
+  128,  129,  130,  131,  132,  133,  134,  135,  136,  137,  138,  139,  140,  141,  142,  143,
+  143,  144,  145,  146,  147,  148,  149,  150,  150,  151,  152,  153,  154,  155,  155,  156,
+  157,  158,  159,  159,  160,  161,  162,  163,  163,  164,  165,  166,  167,  167,  168,  169,
+  170,  170,  171,  172,  173,  173,  174,  175,  176,  176,  177,  178,  178,  179,  180,  181,
+  181,  182,  183,  183,  184,  185,  186,  186,  187,  188,  188,  189,  190,  190,  191,  192,
+  192,  193,  194,  194,  195,  196,  196,  197,  198,  198,  199,  199,  200,  201,  201,  202,
+  203,  203,  204,  205,  205,  206,  206,  207,  208,  208,  209,  210,  210,  211,  211,  212,
+  213,  213,  214,  214,  215,  216,  216,  217,  217,  218,  219,  219,  220,  220,  221,  221,
+  222,  223,  223,  224,  224,  225,  225,  226,  227,  227,  228,  228,  229,  229,  230,  230,
+  231,  232,  232,  233,  233,  234,  234,  235,  235,  236,  237,  237,  238,  238,  239,  239,
+  240,  240,  241,  241,  242,  242,  243,  243,  244,  245,  245,  246,  246,  247,  247,  248,
+  248,  249,  249,  250,  250,  251,  251,  252,  252,  253,  253,  254,  254,  255,  255,  255 };
+
diff --git a/images/math.h b/images/math.h
new file mode 100644 (file)
index 0000000..1824c95
--- /dev/null
@@ -0,0 +1,103 @@
+#ifndef _IMAGES_MATH_H
+#define _IMAGES_MATH_H
+
+extern const u32 fast_div_tab[];
+extern const byte fast_sqrt_tab[];
+
+static inline uns
+isqr(int x)
+{
+  return x * x;
+}
+
+static inline uns
+fast_div_u32_u8(uns x, uns y)
+{
+#ifdef CPU_I386
+  int ret, dmy;
+  asm volatile (
+    "mull %3"
+    :"=d"(ret),"=a"(dmy)
+    :"1"(x),"g"(fast_div_tab[y])
+  );
+  return ret;
+#else
+  return ((u64)(x) * fast_div_tab[y]) >> 32;
+#endif
+}
+
+static inline uns
+fast_sqrt_u16(uns x)
+{
+  uns y;
+  if (x < (1 << 10) - 3)
+    y = fast_sqrt_tab[(x + 3) >> 2] >> 3;
+  else if (x < (1 << 14) - 28)
+    y = fast_sqrt_tab[(x + 28) >> 6] >> 1;
+  else
+    y = fast_sqrt_tab[x >> 8];
+  return (x < y * y) ? y - 1 : y;
+}
+
+static inline uns
+fast_sqrt_u32(uns x)
+{
+  uns y;
+  if (x < (1 << 16))
+    {
+      if (x < (1 << 10) - 3)
+       y = fast_sqrt_tab[(x + 3) >> 2] >> 3;
+      else if (x < (1 << 14) - 28)
+       y = fast_sqrt_tab[(x + 28) >> 6] >> 1;
+      else
+        y = fast_sqrt_tab[x >> 8];
+    }
+  else
+    {
+      if (x < (1 << 24))
+        {
+         if (x < (1 << 20))
+           {
+              y = fast_sqrt_tab[x >> 12];
+              y = (fast_div_u32_u8(x, y) >> 3) + (y << 1);
+            }
+         else
+           {
+              y = fast_sqrt_tab[x >> 16];
+              y = (fast_div_u32_u8(x, y) >> 5) + (y << 3);
+            }
+        }
+      else
+        {
+          if (x < (1 << 28))
+           {
+              if (x < (1 << 26))
+               {
+                  y = fast_sqrt_tab[x >> 18];
+                  y = (fast_div_u32_u8(x, y) >> 6) + (y << 4);
+                }
+             else
+               {
+                  y = fast_sqrt_tab[x >> 20];
+                  y = (fast_div_u32_u8(x, y) >> 7) + (y << 5);
+                }
+            }
+         else
+           {
+              if (x < (1 << 30))
+               {
+                  y = fast_sqrt_tab[x >> 22];
+                  y = (fast_div_u32_u8(x, y) >> 8) + (y << 6);
+                }
+             else
+               {
+                  y = fast_sqrt_tab[x >> 24];
+                  y = (fast_div_u32_u8(x, y) >> 9) + (y << 7);
+                }
+            }
+        }
+    }
+  return (x < y * y) ? y - 1 : y;
+}
+
+#endif
diff --git a/images/object.c b/images/object.c
new file mode 100644 (file)
index 0000000..0375fdd
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ *     Image Library -- Image cards manipulations
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ */
+
+#undef LOCAL_DEBUG
+
+#include "sherlock/sherlock.h"
+#include "lib/base224.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "sherlock/object.h"
+#include "images/images.h"
+#include "images/object.h"
+#include "images/color.h"
+#include "images/signature.h"
+#include <stdio.h>
+#include <string.h>
+
+uns
+get_image_obj_info(struct image_obj_info *ioi, struct odes *o)
+{
+  byte *v = obj_find_aval(o, 'G');
+  if (!v)
+    {
+      DBG("Missing image info attribute");
+      return 0;
+    }
+  byte color_space[MAX_ATTR_SIZE], thumb_format[MAX_ATTR_SIZE];
+  UNUSED uns cnt = sscanf(v, "%d%d%s%d%d%d%s", &ioi->cols, &ioi->rows, color_space,
+      &ioi->colors, &ioi->thumb_cols, &ioi->thumb_rows, thumb_format);
+  ASSERT(cnt == 7);
+  ioi->thumb_format = (*thumb_format == 'p') ? IMAGE_FORMAT_PNG : IMAGE_FORMAT_JPEG;
+  DBG("Readed image info attribute: dim=%ux%u", ioi->cols, ioi->rows);
+  return 1;
+}
+
+uns
+get_image_obj_thumb(struct image_obj_info *ioi, struct odes *o, struct mempool *pool)
+{
+  struct oattr *a = obj_find_attr(o, 'N');
+  if (!a)
+    {
+      DBG("Missing image thumbnail attribute");
+      return 0;
+    }
+  uns count = 0;
+  for (struct oattr *b = a; b; b = b->same)
+    count++;
+  byte buf[count * MAX_ATTR_SIZE], *b = buf;
+  for (; a; a = a->same)
+    b += base224_decode(b, a->val, strlen(a->val));
+  ASSERT(b != buf);
+  ioi->thumb_data = mp_alloc(pool, ioi->thumb_size = b - buf);
+  memcpy(ioi->thumb_data, buf, ioi->thumb_size);
+  DBG("Readed thumbnail of size %u", ioi->thumb_size);
+  return 1;
+}
+
+struct image *
+read_image_obj_thumb(struct image_obj_info *ioi, struct fastbuf *fb, struct image_io *io, struct mempool *pool)
+{
+  struct fastbuf tmp_fb;
+  if (!fb)
+    fbbuf_init_read(fb = &tmp_fb, ioi->thumb_data, ioi->thumb_size, 0);
+  io->format = ioi->thumb_format;
+  io->fastbuf = fb;
+  if (!image_io_read_header(io))
+    goto error;
+  io->pool = pool;
+  io->flags = COLOR_SPACE_RGB | IMAGE_IO_USE_BACKGROUND;
+  if (!io->background_color.color_space)
+    io->background_color = color_white;
+  struct image *img;
+  if (!(img = image_io_read_data(io, 1)))
+    goto error;
+  DBG("Decompressed thumbnail: size=%ux%u", img->cols, img->rows);
+  return img;
+error:
+  DBG("Failed to decompress thumbnail: %s", io->thread->err_msg);
+  return NULL;  
+}
+
+void
+put_image_obj_signature(struct odes *o, struct image_signature *sig)
+{
+  /* signatures should be short enough to in a single attribute */
+  byte buf[MAX_ATTR_SIZE];
+  uns size = image_signature_size(sig->len);
+  ASSERT(MAX_ATTR_SIZE > BASE224_ENC_LENGTH(size));
+  buf[base224_encode(buf, (byte *)sig, size)] = 0;
+  obj_set_attr(o, 'H', buf);
+}
+
+uns
+get_image_obj_signature(struct image_signature *sig, struct odes *o)
+{
+  byte *a = obj_find_aval(o, 'H');
+  if (!a)
+    return 0;
+  UNUSED uns size = base224_decode((byte *)sig, a, strlen(a));
+  ASSERT(size == image_signature_size(sig->len));
+  return 1;
+}
diff --git a/images/object.h b/images/object.h
new file mode 100644 (file)
index 0000000..189b3c0
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef _IMAGES_OBJECT_H
+#define _IMAGES_OBJECT_H
+
+#include "images/images.h"
+
+struct image_obj_info {
+  uns cols;
+  uns rows;
+  uns colors;
+  enum image_format thumb_format;
+  uns thumb_cols;
+  uns thumb_rows;
+  uns thumb_size;
+  byte *thumb_data;
+};
+
+struct odes;
+struct mempool;
+struct image_signature;
+
+uns get_image_obj_info(struct image_obj_info *ioi, struct odes *o);
+uns get_image_obj_thumb(struct image_obj_info *ioi, struct odes *o, struct mempool *pool);
+struct image *read_image_obj_thumb(struct image_obj_info *ioi, struct fastbuf *fb, struct image_io *io, struct mempool *pool);
+void put_image_obj_signature(struct odes *o, struct image_signature *sig);
+uns get_image_obj_signature(struct image_signature *sig, struct odes *o);
+
+#endif
diff --git a/images/scale-gen.h b/images/scale-gen.h
new file mode 100644 (file)
index 0000000..bbe5496
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ *     Image Library -- Image scaling algorithms
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#ifndef IMAGE_SCALE_CHANNELS
+#  define IMAGE_SCALE_CHANNELS IMAGE_SCALE_PIXEL_SIZE
+#endif
+
+#undef IMAGE_COPY_PIXEL
+#if IMAGE_SCALE_PIXEL_SIZE == 1
+#define IMAGE_COPY_PIXEL(dest, src) do{ *(byte *)dest = *(byte *)src; }while(0)
+#elif IMAGE_SCALE_PIXEL_SIZE == 2
+#define IMAGE_COPY_PIXEL(dest, src) do{ *(u16 *)dest = *(u16 *)src; }while(0)
+#elif IMAGE_SCALE_PIXEL_SIZE == 3
+#define IMAGE_COPY_PIXEL(dest, src) do{ ((byte *)dest)[0] = ((byte *)src)[0]; ((byte *)dest)[1] = ((byte *)src)[1]; ((byte *)dest)[2] = ((byte *)src)[2]; }while(0)
+#elif IMAGE_SCALE_PIXEL_SIZE == 4
+#define IMAGE_COPY_PIXEL(dest, src) do{ *(u32 *)dest = *(u32 *)src; }while(0)
+#endif
+
+static void
+IMAGE_SCALE_PREFIX(nearest_xy)(struct image *dest, struct image *src)
+{
+  uns x_inc = (src->cols << 16) / dest->cols;
+  uns y_inc = (src->rows << 16) / dest->rows;
+  uns x_start = x_inc >> 1, x_pos;
+  uns y_pos = y_inc >> 1;
+  byte *row_start;
+# define IMAGE_WALK_PREFIX(x) walk_##x
+# define IMAGE_WALK_INLINE
+# define IMAGE_WALK_UNROLL 4
+# define IMAGE_WALK_IMAGE dest
+# define IMAGE_WALK_COL_STEP IMAGE_SCALE_PIXEL_SIZE
+# define IMAGE_WALK_DO_ROW_START do{ row_start = src->pixels + (y_pos >> 16) * src->row_size; y_pos += y_inc; x_pos = x_start; }while(0)
+# define IMAGE_WALK_DO_STEP do{ byte *pos = row_start + (x_pos >> 16) * IMAGE_SCALE_PIXEL_SIZE; x_pos += x_inc; IMAGE_COPY_PIXEL(walk_pos, pos); }while(0)
+# include "images/image-walk.h"
+}
+
+#if 0 /* Experiments with rearranging pixels for SSE... */
+static void
+IMAGE_SCALE_PREFIX(linear_x)(struct image *dest, struct image *src)
+{
+  /* Handle problematic special case */
+  byte *src_row = src->pixels;
+  byte *dest_row = dest->pixels;
+  if (src->cols == 1)
+    {
+      for (uns y_counter = dest->rows; y_counter--; )
+        {
+          // FIXME
+          ASSERT(0);
+         src_row += src->row_size;
+         dest_row += dest->row_size;
+       }
+      return;
+    }
+  /* Initialize the main loop */
+  uns x_inc = ((src->cols - 1) << 16) / (dest->cols - 1);
+# define COLS_AT_ONCE 256
+  byte pixel_buf[COLS_AT_ONCE * 2 * IMAGE_SCALE_PIXEL_SIZE]; /* Buffers should fit in cache */
+  u16 coef_buf[COLS_AT_ONCE * IMAGE_SCALE_PIXEL_SIZE];
+  /* Main loop */
+  for (uns y_counter = dest->rows; y_counter--; )
+    {
+      uns x_pos = 0;
+      byte *dest_pos = dest_row;
+      for (uns x_counter = dest->cols; --x_counter; )
+      for (uns x_counter = dest->cols; x_counter > COLS_AT_ONCE; x_counter -= COLS_AT_ONCE)
+        {
+         byte *pixel_buf_pos = pixel_buf;
+         u16 *coef_buf_pos = coef_buf;
+         for (uns i = 0; i < COLS_AT_ONCE / 2; i++)
+           {
+             byte *src_pos = src_row + (x_pos >> 16) * IMAGE_SCALE_PIXEL_SIZE;
+             uns ofs = x_pos & 0xffff;
+             x_pos += x_inc;
+             byte *src_pos_2 = src_row + (x_pos >> 16) * IMAGE_SCALE_PIXEL_SIZE;
+             uns ofs_2 = x_pos & 0xffff;
+             x_pos += x_inc;
+             *coef_buf_pos++ = ofs;
+             byte *pixel_buf_pos_2 = pixel_buf_pos + IMAGE_SCALE_PIXEL_SIZE;
+             byte *pixel_buf_pos_3 = pixel_buf_pos + IMAGE_SCALE_PIXEL_SIZE * 2;
+             byte *pixel_buf_pos_4 = pixel_buf_pos + IMAGE_SCALE_PIXEL_SIZE * 3;
+              IMAGE_COPY_PIXEL(pixel_buf_pos, src_pos);
+             IMAGE_COPY_PIXEL(pixel_buf_pos_2, src_pos + IMAGE_SCALE_PIXEL_SIZE);
+              IMAGE_COPY_PIXEL(pixel_buf_pos_3, src_pos_2);
+             IMAGE_COPY_PIXEL(pixel_buf_pos_4, src_pos_2 + IMAGE_SCALE_PIXEL_SIZE);
+             pixel_buf_pos += 4 * IMAGE_SCALE_PIXEL_SIZE;
+             *coef_buf_pos++ = ofs_2;
+           }
+/*
+         byte *src_pos = src_row + (x_pos >> 16) * IMAGE_SCALE_PIXEL_SIZE;
+         uns ofs = x_pos & 0xffff;
+         x_pos += x_inc;
+         dest_pos[0] = LINEAR_INTERPOLATE(src_pos[0], src_pos[0 + IMAGE_SCALE_PIXEL_SIZE], ofs);
+#        if IMAGE_SCALE_CHANNELS >= 2
+         dest_pos[1] = LINEAR_INTERPOLATE(src_pos[1], src_pos[1 + IMAGE_SCALE_PIXEL_SIZE], ofs);
+#        endif
+#        if IMAGE_SCALE_CHANNELS >= 3
+         dest_pos[2] = LINEAR_INTERPOLATE(src_pos[2], src_pos[2 + IMAGE_SCALE_PIXEL_SIZE], ofs);
+#        endif
+#        if IMAGE_SCALE_CHANNELS >= 4
+         dest_pos[3] = LINEAR_INTERPOLATE(src_pos[3], src_pos[3 + IMAGE_SCALE_PIXEL_SIZE], ofs);
+#        endif
+         dest_pos += IMAGE_SCALE_PIXEL_SIZE;*/
+
+       }
+      /* Always copy the last column - handle "x_pos == dest->cols * 0x10000" overflow */
+      IMAGE_COPY_PIXEL(dest_pos, src_row + src->row_pixels_size - IMAGE_SCALE_PIXEL_SIZE);
+      /* Next step */
+      src_row += src->row_size;
+      dest_row += dest->row_size;
+    }
+#undef COLS_AT_ONCE
+}
+
+static void
+IMAGE_SCALE_PREFIX(bilinear_xy)(struct image *dest, struct image *src)
+{
+  uns x_inc = (((src->cols - 1) << 16) - 1) / (dest->cols);
+  uns y_inc = (((src->rows - 1) << 16) - 1) / (dest->rows);
+  uns y_pos = 0x10000;
+  byte *cache[2], buf1[dest->row_pixels_size + 16], buf2[dest->row_pixels_size + 16], *pbuf[2];
+  byte *dest_row = dest->pixels, *dest_pos;
+  uns cache_index = ~0U, cache_i = 0;
+  pbuf[0] = cache[0] = ALIGN_PTR((void *)buf1, 16);
+  pbuf[1] = cache[1] = ALIGN_PTR((void *)buf2, 16);
+#ifdef __SSE2__
+  __m128i zero = _mm_setzero_si128();
+#endif
+  for (uns row_counter = dest->rows; row_counter--; )
+    {
+      dest_pos = dest_row;
+      uns y_index = y_pos >> 16;
+      uns y_ofs = y_pos & 0xffff;
+      y_pos += y_inc;
+      uns x_pos = 0;
+      if (y_index > (uns)(cache_index + 1))
+       cache_index = y_index - 1;
+      while (y_index > cache_index)
+        {
+         cache[0] = cache[1];
+         cache[1] = pbuf[cache_i ^= 1];
+         cache_index++;
+         byte *src_row = src->pixels + cache_index * src->row_size;
+         byte *cache_pos = cache[1];
+         for (uns col_counter = dest->cols; --col_counter; )
+           {
+             byte *c1 = src_row + (x_pos >> 16) * IMAGE_SCALE_PIXEL_SIZE;
+             byte *c2 = c1 + IMAGE_SCALE_PIXEL_SIZE;
+             uns ofs = x_pos & 0xffff;
+             cache_pos[0] = LINEAR_INTERPOLATE(c1[0], c2[0], ofs);
+#            if IMAGE_SCALE_CHANNELS >= 2
+             cache_pos[1] = LINEAR_INTERPOLATE(c1[1], c2[1], ofs);
+#            endif
+#            if IMAGE_SCALE_CHANNELS >= 3
+             cache_pos[2] = LINEAR_INTERPOLATE(c1[2], c2[2], ofs);
+#            endif
+#            if IMAGE_SCALE_CHANNELS >= 4
+             cache_pos[3] = LINEAR_INTERPOLATE(c1[3], c2[3], ofs);
+#            endif
+             cache_pos += IMAGE_SCALE_PIXEL_SIZE;
+             x_pos += x_inc;
+           }
+         IMAGE_COPY_PIXEL(cache_pos, src_row + src->row_pixels_size - IMAGE_SCALE_PIXEL_SIZE);
+       }
+      uns i = 0;
+#ifdef __SSE2__
+      __m128i coef = _mm_set1_epi16(y_ofs >> 9);
+      for (; (int)i < (int)dest->row_pixels_size - 15; i += 16)
+        {
+         __m128i a2 = _mm_loadu_si128((__m128i *)(cache[0] + i));
+         __m128i a1 = _mm_unpacklo_epi8(a2, zero);
+         a2 = _mm_unpackhi_epi8(a2, zero);
+         __m128i b2 = _mm_loadu_si128((__m128i *)(cache[1] + i));
+         __m128i b1 = _mm_unpacklo_epi8(b2, zero);
+         b2 = _mm_unpackhi_epi8(b2, zero);
+         b1 = _mm_sub_epi16(b1, a1);
+         b2 = _mm_sub_epi16(b2, a2);
+         a1 = _mm_slli_epi16(a1, 7);
+         a2 = _mm_slli_epi16(a2, 7);
+         b1 = _mm_mullo_epi16(b1, coef);
+         b2 = _mm_mullo_epi16(b2, coef);
+         a1 = _mm_add_epi16(a1, b1);
+         a2 = _mm_add_epi16(a2, b2);
+         a1 = _mm_srli_epi16(a1, 7);
+         a2 = _mm_srli_epi16(a2, 7);
+         a1 = _mm_packus_epi16(a1, a2);
+         _mm_storeu_si128((__m128i *)(dest_pos + i), a1);
+       }
+#elif 1
+      for (; (int)i < (int)dest->row_pixels_size - 3; i += 4)
+        {
+         dest_pos[i + 0] = LINEAR_INTERPOLATE(cache[0][i + 0], cache[1][i + 0], y_ofs);
+         dest_pos[i + 1] = LINEAR_INTERPOLATE(cache[0][i + 1], cache[1][i + 1], y_ofs);
+         dest_pos[i + 2] = LINEAR_INTERPOLATE(cache[0][i + 2], cache[1][i + 2], y_ofs);
+         dest_pos[i + 3] = LINEAR_INTERPOLATE(cache[0][i + 3], cache[1][i + 3], y_ofs);
+       }
+#endif
+      for (; i < dest->row_pixels_size; i++)
+       dest_pos[i] = LINEAR_INTERPOLATE(cache[0][i], cache[1][i], y_ofs);
+      dest_row += dest->row_size;
+    }
+}
+#endif
+
+static void
+IMAGE_SCALE_PREFIX(downsample_xy)(struct image *dest, struct image *src)
+{
+  /* FIXME slow */
+  byte *rsrc = src->pixels, *psrc;
+  byte *rdest = dest->pixels, *pdest;
+  u64 x_inc = ((u64)dest->cols << 32) / src->cols, x_pos;
+  u64 y_inc = ((u64)dest->rows << 32) / src->rows, y_pos = 0;
+  uns x_inc_frac = (u64)0xffffffffff / x_inc;
+  uns y_inc_frac = (u64)0xffffffffff / y_inc;
+  uns final_mul = ((u64)(x_inc >> 16) * (y_inc >> 16)) >> 16;
+  uns buf_size = dest->cols * IMAGE_SCALE_CHANNELS;
+  u32 buf[buf_size], *pbuf;
+  buf_size *= sizeof(u32);
+  bzero(buf, buf_size);
+  for (uns rows_counter = src->rows; rows_counter--; )
+    {
+      pbuf = buf;
+      psrc = rsrc;
+      rsrc += src->row_size;
+      x_pos = 0;
+      y_pos += y_inc;
+      if (y_pos <= 0x100000000)
+        {
+          for (uns cols_counter = src->cols; cols_counter--; )
+            {
+             x_pos += x_inc;
+             if (x_pos <= 0x100000000)
+               {
+                 pbuf[0] += psrc[0];
+#                if IMAGE_SCALE_CHANNELS >= 2
+                 pbuf[1] += psrc[1];
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 3
+                 pbuf[2] += psrc[2];
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 4
+                 pbuf[3] += psrc[3];
+#                 endif
+               }
+             else
+               {
+                 x_pos -= 0x100000000;
+                 uns mul2 = (uns)(x_pos >> 16) * x_inc_frac;
+                 uns mul1 = 0xffffff - mul2;
+                 pbuf[0] += (psrc[0] * mul1) >> 24;
+                 pbuf[0 + IMAGE_SCALE_CHANNELS] += (psrc[0] * mul2) >> 24;
+#                if IMAGE_SCALE_CHANNELS >= 2
+                 pbuf[1] += (psrc[1] * mul1) >> 24;
+                 pbuf[1 + IMAGE_SCALE_CHANNELS] += (psrc[1] * mul2) >> 24;
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 3
+                 pbuf[2] += (psrc[2] * mul1) >> 24;
+                 pbuf[2 + IMAGE_SCALE_CHANNELS] += (psrc[2] * mul2) >> 24;
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 4
+                 pbuf[3] += (psrc[3] * mul1) >> 24;
+                 pbuf[3 + IMAGE_SCALE_CHANNELS] += (psrc[3] * mul2) >> 24;
+#                 endif
+                 pbuf += IMAGE_SCALE_CHANNELS;
+               }
+             psrc += IMAGE_SCALE_PIXEL_SIZE;
+           }
+       }
+      else
+        {
+         y_pos -= 0x100000000;
+          pdest = rdest;
+          rdest += dest->row_size;
+         uns mul2 = (uns)(y_pos >> 16) * y_inc_frac;
+         uns mul1 = 0xffffff - mul2;
+         uns a0 = 0;
+#        if IMAGE_SCALE_CHANNELS >= 2
+         uns a1 = 0;
+#        endif
+#        if IMAGE_SCALE_CHANNELS >= 3
+         uns a2 = 0;
+#        endif
+#        if IMAGE_SCALE_CHANNELS >= 4
+         uns a3 = 0;
+#        endif
+          for (uns cols_counter = src->cols; cols_counter--; )
+            {
+             x_pos += x_inc;
+             if (x_pos <= 0x100000000)
+               {
+                 pbuf[0] += ((psrc[0] * mul1) >> 24);
+                 a0 += (psrc[0] * mul2) >> 24;
+#                if IMAGE_SCALE_CHANNELS >= 2
+                 pbuf[1] += ((psrc[1] * mul1) >> 24);
+                 a1 += (psrc[1] * mul2) >> 24;
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 3
+                 pbuf[2] += ((psrc[2] * mul1) >> 24);
+                 a2 += (psrc[2] * mul2) >> 24;
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 4
+                 pbuf[3] += ((psrc[3] * mul1) >> 24);
+                 a3 += (psrc[3] * mul2) >> 24;
+#                 endif
+               }
+             else
+               {
+                 x_pos -= 0x100000000;
+                 uns mul4 = (uns)(x_pos >> 16) * x_inc_frac;
+                 uns mul3 = 0xffffff - mul4;
+                 uns mul13 = ((u64)mul1 * mul3) >> 24;
+                 uns mul23 = ((u64)mul2 * mul3) >> 24;
+                 uns mul14 = ((u64)mul1 * mul4) >> 24;
+                 uns mul24 = ((u64)mul2 * mul4) >> 24;
+                 pdest[0] = ((((psrc[0] * mul13) >> 24) + pbuf[0]) * final_mul) >> 16;
+                 pbuf[0] = ((psrc[0] * mul23) >> 24) + a0;
+                 pbuf[0 + IMAGE_SCALE_CHANNELS] += ((psrc[0 + IMAGE_SCALE_PIXEL_SIZE] * mul14) >> 24);
+                 a0 = ((psrc[0 + IMAGE_SCALE_PIXEL_SIZE] * mul24) >> 24);
+#                if IMAGE_SCALE_CHANNELS >= 2
+                 pdest[1] = ((((psrc[1] * mul13) >> 24) + pbuf[1]) * final_mul) >> 16;
+                 pbuf[1] = ((psrc[1] * mul23) >> 24) + a1;
+                 pbuf[1 + IMAGE_SCALE_CHANNELS] += ((psrc[1 + IMAGE_SCALE_PIXEL_SIZE] * mul14) >> 24);
+                 a1 = ((psrc[1 + IMAGE_SCALE_PIXEL_SIZE] * mul24) >> 24);
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 3
+                 pdest[2] = ((((psrc[2] * mul13) >> 24) + pbuf[2]) * final_mul) >> 16;
+                 pbuf[2] = ((psrc[2] * mul23) >> 24) + a2;
+                 pbuf[2 + IMAGE_SCALE_CHANNELS] += ((psrc[2 + IMAGE_SCALE_PIXEL_SIZE] * mul14) >> 24);
+                 a2 = ((psrc[2 + IMAGE_SCALE_PIXEL_SIZE] * mul24) >> 24);
+#                 endif
+#                if IMAGE_SCALE_CHANNELS >= 4
+                 pdest[3] = ((((psrc[3] * mul13) >> 24) + pbuf[3]) * final_mul) >> 16;
+                 pbuf[3] = ((psrc[3] * mul23) >> 24) + a3;
+                 pbuf[3 + IMAGE_SCALE_CHANNELS] += ((psrc[3 + IMAGE_SCALE_PIXEL_SIZE] * mul14) >> 24);
+                 a3 = ((psrc[3 + IMAGE_SCALE_PIXEL_SIZE] * mul24) >> 24);
+#                 endif
+                 pbuf += IMAGE_SCALE_CHANNELS;
+                 pdest += IMAGE_SCALE_PIXEL_SIZE;
+               }
+             psrc += IMAGE_SCALE_PIXEL_SIZE;
+           }
+         pdest[0] = (pbuf[0] * final_mul) >> 16;
+         pbuf[0] = a0;
+#         if IMAGE_SCALE_CHANNELS >= 2
+         pdest[1] = (pbuf[1] * final_mul) >> 16;
+         pbuf[1] = a1;
+#        endif
+#         if IMAGE_SCALE_CHANNELS >= 3
+         pdest[2] = (pbuf[2] * final_mul) >> 16;
+         pbuf[2] = a2;
+#        endif
+#         if IMAGE_SCALE_CHANNELS >= 4
+         pdest[3] = (pbuf[3] * final_mul) >> 16;
+         pbuf[3] = a3;
+#        endif
+       }
+    }
+  pdest = rdest;
+  pbuf = buf;
+  for (uns cols_counter = dest->cols; cols_counter--; )
+    {
+      pdest[0] = (pbuf[0] * final_mul) >> 16;
+#     if IMAGE_SCALE_CHANNELS >= 2
+      pdest[1] = (pbuf[1] * final_mul) >> 16;
+#     endif
+#     if IMAGE_SCALE_CHANNELS >= 3
+      pdest[2] = (pbuf[2] * final_mul) >> 16;
+#     endif
+#     if IMAGE_SCALE_CHANNELS >= 4
+      pdest[3] = (pbuf[3] * final_mul) >> 16;
+#     endif
+      pbuf += IMAGE_SCALE_CHANNELS;
+      pdest += IMAGE_SCALE_PIXEL_SIZE;
+    }
+}
+
+#undef IMAGE_SCALE_PREFIX
+#undef IMAGE_SCALE_PIXEL_SIZE
+#undef IMAGE_SCALE_CHANNELS
diff --git a/images/scale.c b/images/scale.c
new file mode 100644 (file)
index 0000000..ca80616
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ *     Image Library -- Image scaling algorithms
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "images/images.h"
+#include "images/error.h"
+#include "images/math.h"
+
+#include <string.h>
+
+#ifdef __SSE2__
+#include <emmintrin.h>
+#endif
+
+#define LINEAR_INTERPOLATE(a, b, t) (((int)((a) << 16) + (int)(t) * ((int)(b) - (int)(a)) + 0x8000) >> 16)
+
+/* Generate optimized code for various pixel formats */
+
+#define IMAGE_SCALE_PREFIX(x) image_scale_1_##x
+#define IMAGE_SCALE_PIXEL_SIZE 1
+#include "images/scale-gen.h"
+
+#define IMAGE_SCALE_PREFIX(x) image_scale_2_##x
+#define IMAGE_SCALE_PIXEL_SIZE 2
+#include "images/scale-gen.h"
+
+#define IMAGE_SCALE_PREFIX(x) image_scale_3_##x
+#define IMAGE_SCALE_PIXEL_SIZE 3
+#include "images/scale-gen.h"
+
+#define IMAGE_SCALE_PREFIX(x) image_scale_4_##x
+#define IMAGE_SCALE_PIXEL_SIZE 4
+#include "images/scale-gen.h"
+
+/* Simple "nearest neighbour" algorithm */
+
+static void
+image_scale_nearest_xy(struct image *dest, struct image *src)
+{
+  switch (src->pixel_size)
+    {
+      case 1:
+       image_scale_1_nearest_xy(dest, src);
+       return;
+      case 2:
+       image_scale_2_nearest_xy(dest, src);
+       return;
+      case 3:
+       image_scale_3_nearest_xy(dest, src);
+       return;
+      case 4:
+       image_scale_4_nearest_xy(dest, src);
+       return;
+      default:
+       ASSERT(0);
+    }
+}
+
+static inline void
+image_scale_nearest_x(struct image *dest, struct image *src)
+{
+  image_scale_nearest_xy(dest, src);
+}
+
+static void
+image_scale_nearest_y(struct image *dest, struct image *src)
+{
+  uns y_inc = (src->rows << 16) / dest->rows;
+  uns y_pos = y_inc >> 1;
+  byte *dest_pos = dest->pixels;
+  for (uns row_counter = dest->rows; row_counter--; )
+    {
+      byte *src_pos = src->pixels + (y_pos >> 16) * src->row_size;
+      y_pos += y_inc;
+      memcpy(dest_pos, src_pos, dest->row_pixels_size);
+      dest_pos += dest->row_size;
+    }
+}
+
+/* Bilinear filter */
+
+UNUSED static void
+image_scale_linear_y(struct image *dest, struct image *src)
+{
+  byte *dest_row = dest->pixels;
+  /* Handle problematic special case */
+  if (src->rows == 1)
+    {
+      for (uns y_counter = dest->rows; y_counter--; dest_row += dest->row_size)
+        memcpy(dest_row, src->pixels, src->row_pixels_size);
+      return;
+    }
+  /* Initialize the main loop */
+  uns y_inc  = ((src->rows - 1) << 16) / (dest->rows - 1), y_pos = 0;
+#ifdef __SSE2__
+  __m128i zero = _mm_setzero_si128();
+#endif
+  /* Main loop */
+  for (uns y_counter = dest->rows; --y_counter; )
+    {
+      uns coef = y_pos & 0xffff;
+      byte *src_row_1 = src->pixels + (y_pos >> 16) * src->row_size;
+      byte *src_row_2 = src_row_1 + src->row_size;
+      uns i = 0;
+#ifdef __SSE2__
+      /* SSE2 */
+      __m128i sse_coef = _mm_set1_epi16(coef >> 9);
+      for (; (int)i < (int)dest->row_pixels_size - 15; i += 16)
+        {
+         __m128i a2 = _mm_loadu_si128((__m128i *)(src_row_1 + i));
+         __m128i a1 = _mm_unpacklo_epi8(a2, zero);
+         a2 = _mm_unpackhi_epi8(a2, zero);
+         __m128i b2 = _mm_loadu_si128((__m128i *)(src_row_2 + i));
+         __m128i b1 = _mm_unpacklo_epi8(b2, zero);
+         b2 = _mm_unpackhi_epi8(b2, zero);
+         b1 = _mm_sub_epi16(b1, a1);
+         b2 = _mm_sub_epi16(b2, a2);
+         a1 = _mm_slli_epi16(a1, 7);
+         a2 = _mm_slli_epi16(a2, 7);
+         b1 = _mm_mullo_epi16(b1, sse_coef);
+         b2 = _mm_mullo_epi16(b2, sse_coef);
+         a1 = _mm_add_epi16(a1, b1);
+         a2 = _mm_add_epi16(a2, b2);
+         a1 = _mm_srli_epi16(a1, 7);
+         a2 = _mm_srli_epi16(a2, 7);
+         a1 = _mm_packus_epi16(a1, a2);
+         _mm_storeu_si128((__m128i *)(dest_row + i), a1);
+       }
+#endif
+      /* Unrolled loop using general-purpose registers */
+      for (; (int)i < (int)dest->row_pixels_size - 3; i += 4)
+        {
+         dest_row[i + 0] = LINEAR_INTERPOLATE(src_row_1[i + 0], src_row_2[i + 0], coef);
+         dest_row[i + 1] = LINEAR_INTERPOLATE(src_row_1[i + 1], src_row_2[i + 1], coef);
+         dest_row[i + 2] = LINEAR_INTERPOLATE(src_row_1[i + 2], src_row_2[i + 2], coef);
+         dest_row[i + 3] = LINEAR_INTERPOLATE(src_row_1[i + 3], src_row_2[i + 3], coef);
+       }
+      /* Remaining columns */
+      for (; i < dest->row_pixels_size; i++)
+       dest_row[i] = LINEAR_INTERPOLATE(src_row_1[i], src_row_2[i], coef);
+      dest_row += dest->row_size;
+      y_pos += y_inc;
+    }
+  /* Always copy the last row - faster and also handle "y_pos == dest->rows * 0x10000" overflow */
+  memcpy(dest_row, src->pixels + src->image_size - src->row_size, src->row_pixels_size);
+}
+
+/* Box filter */
+
+static void
+image_scale_downsample_xy(struct image *dest, struct image *src)
+{
+  switch (src->pixel_size)
+    {
+      case 1:
+       image_scale_1_downsample_xy(dest, src);
+       return;
+      case 2:
+       image_scale_2_downsample_xy(dest, src);
+       return;
+      case 3:
+       image_scale_3_downsample_xy(dest, src);
+       return;
+      case 4:
+       image_scale_4_downsample_xy(dest, src);
+       return;
+      default:
+       ASSERT(0);
+    }
+}
+
+/* General routine
+ * FIXME: customizable; implement at least bilinear and bicubic filters */
+
+int
+image_scale(struct image_context *ctx, struct image *dest, struct image *src)
+{
+  if ((src->flags & IMAGE_PIXEL_FORMAT) != (dest->flags & IMAGE_PIXEL_FORMAT))
+    {
+      IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_PIXEL_FORMAT, "Different pixel formats not supported.");
+      return 0;
+    }
+  if (dest->cols == src->cols)
+    {
+      if (dest->rows == src->rows)
+        {
+         /* No scale, copy only */
+         image_scale_nearest_y(dest, src);
+         return 1;
+       }
+      else if (dest->rows < src->rows)
+        {
+         /* Downscale vertically */
+         image_scale_downsample_xy(dest, src);
+         return 1;
+       }
+      else
+        {
+         /* Upscale vertically */
+         image_scale_nearest_y(dest, src);
+         return 1;
+       }
+    }
+  else if (dest->rows == src->rows)
+    {
+      if (dest->cols < src->cols)
+        {
+          /* Downscale horizontally */
+          image_scale_downsample_xy(dest, src);
+          return 1;
+       }
+      else
+        {
+         /* Upscale horizontally */
+         image_scale_nearest_x(dest, src);
+         return 1;
+       }
+    }
+  else
+    {
+      if (dest->cols <= src->cols && src->cols <= dest->cols)
+        {
+         /* Downscale in both dimensions */
+          image_scale_downsample_xy(dest, src);
+         return 1;
+       }
+      else
+        {
+         image_scale_nearest_xy(dest, src);
+         return 1;
+       }
+    }
+}
+
+void
+image_dimensions_fit_to_box(uns *cols, uns *rows, uns max_cols, uns max_rows, uns upsample)
+{
+  ASSERT(image_dimensions_valid(*cols, *rows));
+  ASSERT(image_dimensions_valid(max_cols, max_rows));
+  if (*cols <= max_cols && *rows <= max_rows)
+    {
+      if (!upsample)
+       return;
+      if (max_cols * *rows > max_rows * *cols)
+        {
+         *cols = *cols * max_rows / *rows;
+         *cols = MIN(*cols, max_cols);
+         *rows = max_rows;
+       }
+      else
+        {
+         *rows = *rows * max_cols / *cols;
+         *rows = MIN(*rows, max_rows);
+         *cols = max_cols;
+       }
+    }
+  else if (*cols <= max_cols)
+    goto down_cols;
+  else if (*rows <= max_rows || max_rows * *cols > max_cols * *rows)
+    goto down_rows;
+down_cols:
+  *cols = *cols * max_rows / *rows;
+  *cols = MAX(*cols, 1);
+  *rows = max_rows;
+  return;
+down_rows:
+  *rows = *rows * max_cols / *cols;
+  *rows = MAX(*rows, 1);
+  *cols = max_cols;
+}
diff --git a/images/sig-cmp-gen.h b/images/sig-cmp-gen.h
new file mode 100644 (file)
index 0000000..5a391db
--- /dev/null
@@ -0,0 +1,393 @@
+#ifdef EXPLAIN
+#  define MSG(x...) do{ line += sprintf(line, x); }while(0)
+#  define LINE do{ line = buf; msg(line, param); }while(0)
+
+static void
+explain_signature(struct image_signature *sig, void (*msg)(byte *text, void *param), void *param)
+{
+  byte buf[1024], *line = buf;
+  MSG("signature: flags=0x%x df=%u dh=%u f=(%u", sig->flags, sig->df, sig->dh, sig->vec.f[0]);
+  for (uns i = 1; i < IMAGE_VEC_F; i++)
+    MSG(" %u", sig->vec.f[i]);
+  MSG(")");
+  LINE;
+  for (uns j = 0; j < sig->len; j++)
+    {
+      struct image_region *reg = sig->reg + j;
+      MSG("region %u: wa=%u wb=%u f=(%u", j, reg->wa, reg->wb, reg->f[0]);
+      for (uns i = 1; i < IMAGE_VEC_F; i++)
+       MSG(" %u", reg->f[i]);
+      MSG(") h=(%u", reg->h[0]);
+      for (uns i = 1; i < IMAGE_REG_H; i++)
+       MSG(" %u", reg->h[i]);
+      MSG(")");
+      LINE;
+    }
+}
+
+#else
+#  define MSG(x...) do{}while(0)
+#  define LINE do{}while(0)
+#endif
+
+#define MSGL(x...) do{ MSG(x); LINE; }while(0)
+
+#ifndef EXPLAIN
+static uns
+image_signatures_dist_integrated(struct image_signature *sig1, struct image_signature *sig2)
+#else
+static uns
+image_signatures_dist_integrated_explain(struct image_signature *sig1, struct image_signature *sig2, void (*msg)(byte *text, void *param), void *param)
+#endif
+{
+  uns dist[IMAGE_REG_MAX * IMAGE_REG_MAX], p[IMAGE_REG_MAX], q[IMAGE_REG_MAX];
+  uns n, i, j, k, l, s, d;
+  struct image_region *reg1, *reg2;
+#ifdef EXPLAIN
+  byte buf[1024], *line = buf;
+  MSGL("Integrated matching");
+  explain_signature(sig1, msg, param);
+  explain_signature(sig2, msg, param);
+#endif
+
+  /* FIXME: do not mux textured and non-textured images (should be split in clusters tree) */
+  if ((sig1->flags ^ sig2->flags) & IMAGE_SIG_TEXTURED)
+    {
+      MSGL("Textured vs non-textured");
+      return ~0U;
+    }
+
+  /* Compute distance matrix */
+  n = 0;
+  MSGL("Distance matrix:");
+  /* ... for non-textured images */
+  if (!((sig1->flags | sig2->flags) & IMAGE_SIG_TEXTURED))
+    for (j = 0, reg2 = sig2->reg; j < sig2->len; j++, reg2++)
+      for (i = 0, reg1 = sig1->reg; i < sig1->len; i++, reg1++)
+        {
+         uns dt = 0, ds = 0, dp = 0, d;
+         for (uns i = 0; i < IMAGE_VEC_F; i++)
+           dt += image_sig_cmp_features_weights[i] * isqr((int)reg1->f[i] - (int)reg2->f[i]);
+         for (uns i = 0; i < 3; i++)
+           ds += image_sig_cmp_features_weights[IMAGE_VEC_F + i] * isqr((int)reg1->h[i] - (int)reg2->h[i]);
+         for (uns i = 3; i < 5; i++)
+           dp += image_sig_cmp_features_weights[IMAGE_VEC_F + i] * isqr((int)reg1->h[i] - (int)reg2->h[i]);
+#if 0
+         int x1, y1, x2, y2;
+         if (sig1->cols > sig1->rows)
+           {
+             x1 = reg1->h[3];
+             y1 = ((int)reg1->h[4] - 64) * (int)sig1->rows / (int)sig1->cols + 64;
+           }
+         else
+           {
+             y1 = reg1->h[4];
+             x1 = ((int)reg1->h[3] - 64) * (int)sig1->cols / (int)sig1->rows + 64;
+           }
+         if (sig2->cols > sig2->rows)
+           {
+             x2 = reg2->h[3];
+             y2 = ((int)reg2->h[4] - 64) * (int)sig2->rows / (int)sig2->cols + 64;
+           }
+         else
+           {
+             y2 = reg2->h[4];
+             x2 = ((int)reg2->h[3] - 64) * (int)sig2->cols / (int)sig2->rows + 64;
+           }
+         MSGL("%d %d %d %d", x1, y1, x2, y2);
+         dp = image_sig_cmp_features_weights[IMAGE_VEC_F + 3] * isqr(x1 - x2) +
+              image_sig_cmp_features_weights[IMAGE_VEC_F + 4] * isqr(y1 - y2);
+#endif
+#if 0
+         d = dt * (4 + MIN(8, (ds >> 12))) * (4 + MIN(8, (dp >> 10))) + (ds >> 11) + (dp >> 10);
+         MSG("[%u, %u] d=%u=(%u * %u * %u + %u + %u) dt=%u ds=%u dp=%u df=(%d", i, j, d,
+             dt, 4 + MIN(8, (ds >> 12)), 4 + MIN(8, dp >> 10), ds >> 11, dp >> 10, dt, ds, dp, (int)reg1->f[0] - (int)reg2->f[0]);
+#endif
+#if 1
+         d = dt;
+         if (ds < 1000)
+           d = d * 4;
+         else if (ds < 4000)
+           d = d * 6 + 8;
+         else if (ds < 10000)
+           d = d * 8 + 20;
+         else if (ds < 50000)
+           d = d * 10 + 50;
+         else
+           d = d * 12 + 100;
+         if (dp < 1000)
+           d = d * 2;
+         else if (dp < 4000)
+           d = d * 3 + 100;
+         else if (dp < 10000)
+           d = d * 4 + 800;
+         else
+           d = d * 5 + 3000;
+#endif
+         dist[n++] = (d << 8) + i + (j << 4);
+         MSG("[%u, %u] d=%u dt=%u ds=%u dp=%u df=(%d", i, j, d, dt, ds, dp, (int)reg1->f[0] - (int)reg2->f[0]);
+#ifdef EXPLAIN
+         for (uns i = 1; i < IMAGE_VEC_F; i++)
+           MSG(" %d", (int)reg1->f[i] - (int)reg2->f[i]);
+         MSG(") dh=(%d", (int)reg1->h[0] - (int)reg2->h[0]);
+         for (uns i = 1; i < IMAGE_REG_H; i++)
+           MSG(" %d", (int)reg1->h[i] - (int)reg2->h[i]);
+         MSGL(")");
+#endif
+        }
+  /* ... for textured images (ignore shape properties) */
+  else
+    for (j = 0, reg2 = sig2->reg; j < sig2->len; j++, reg2++)
+      for (i = 0, reg1 = sig1->reg; i < sig1->len; i++, reg1++)
+        {
+         uns dt = 0;
+         for (uns i = 0; i < IMAGE_VEC_F; i++)
+           dt += image_sig_cmp_features_weights[i] * isqr((int)reg1->f[i] - (int)reg2->f[i]);
+         dist[n++] = (dt << 12) + i + (j << 4);
+#ifdef EXPLAIN
+         MSG("[%u, %u] dt=%u df=(%d", i, j, dt, (int)reg1->f[0] - (int)reg2->f[0]);
+         for (uns i = 1; i < IMAGE_VEC_F; i++)
+           MSG(" %d", (int)reg1->f[i] - (int)reg2->f[i]);
+         MSGL(")");
+#endif
+        }
+
+  /* One or both signatures have no regions */
+  if (!n)
+    return ~0U;
+
+  /* Get percentages */
+  for (i = 0, reg1 = sig1->reg; i < sig1->len; i++, reg1++)
+    p[i] = reg1->wb;
+  for (i = 0, reg2 = sig2->reg; i < sig2->len; i++, reg2++)
+    q[i] = reg2->wb;
+
+  /* Sort entries in distance matrix */
+  image_signatures_dist_integrated_sort(n, dist);
+
+  /* Compute significance matrix and resulting distance */
+  uns sum = 0;
+  MSGL("Significance matrix:");
+  for (k = 0, l = 128; l; k++)
+    {
+      i = dist[k] & 15;
+      j = (dist[k] >> 4) & 15;
+      d = dist[k] >> 8;
+      if (p[i] <= q[j])
+        {
+         s = p[i];
+         q[j] -= p[i];
+         p[i] = 0;
+       }
+      else
+        {
+         s = q[j];
+         p[i] -= q[j];
+         q[j] = 0;
+       }
+      l -= s;
+      sum += s * d;
+#ifdef EXPLAIN
+      reg1 = sig1->reg + i;
+      reg2 = sig2->reg + j;
+      MSG("[%u, %u] s=%u d=%u df=(%d", i, j, s, d, (int)reg1->f[0] - (int)reg2->f[0]);
+      for (uns i = 1; i < IMAGE_VEC_F; i++)
+        MSG(" %d", (int)reg1->f[i] - (int)reg2->f[i]);
+      if (!((sig1->flags | sig2->flags) & IMAGE_SIG_TEXTURED))
+        {
+          MSG(") dh=(%d", (int)reg1->h[0] - (int)reg2->h[0]);
+          for (uns i = 1; i < IMAGE_REG_H; i++)
+            MSG(" %d", (int)reg1->h[i] - (int)reg2->h[i]);
+       }
+      MSGL(")");
+#endif
+    }
+
+  d = sum / 32;
+
+  uns a = sig1->cols * sig2->rows;
+  uns b = sig1->rows * sig2->cols;
+  if (a < 2 * b && b < 2 * a)
+    d = d * 2;
+  else if (a < 4 * b && b < 4 * a)
+    d = d * 3;
+  else
+    d = d * 5;
+
+  a = sig1->cols * sig1->rows;
+  b = sig2->cols * sig2->rows;
+
+  if ((a < 1000 && b > 5000) || (b < 1000 && a > 5000))
+    d = d * 2;
+  else if ((a < 5000 && b > 20000) || (b < 5000 && a > 20000))
+    d = d * 3 / 2;
+
+  return d;
+}
+
+#ifndef EXPLAIN
+static uns
+image_signatures_dist_fuzzy(struct image_signature *sig1, struct image_signature *sig2)
+#else
+static uns
+image_signatures_dist_fuzzy_explain(struct image_signature *sig1, struct image_signature *sig2, void (*msg)(byte *text, void *param), void *param)
+#endif
+{
+#ifdef EXPLAIN
+  byte buf[1024], *line = buf;
+  MSGL("Fuzzy matching");
+  explain_signature(sig1, msg, param);
+  explain_signature(sig2, msg, param);
+#endif
+
+  /* FIXME: do not mux textured and non-textured images (should be split in clusters tree) */
+  if ((sig1->flags ^ sig2->flags) & IMAGE_SIG_TEXTURED)
+    {
+      MSGL("Textured vs non-textured");
+      return ~0U;
+    }
+
+  uns cnt1 = sig1->len;
+  uns cnt2 = sig2->len;
+  struct image_region *reg1 = sig1->reg;
+  struct image_region *reg2 = sig2->reg;
+  uns mf[IMAGE_REG_MAX][IMAGE_REG_MAX], mh[IMAGE_REG_MAX][IMAGE_REG_MAX];
+  uns lf[IMAGE_REG_MAX * 2], lh[IMAGE_REG_MAX * 2];
+  uns df = sig1->df + sig2->df, dh = sig1->dh + sig2->dh;
+
+  /* Compute distance matrix */
+  for (uns i = 0; i < cnt1; i++)
+    for (uns j = 0; j < cnt2; j++)
+      {
+       uns d = 0;
+       for (uns k = 0; k < IMAGE_VEC_F; k++)
+         {
+           int dif = reg1[i].f[k] - reg2[j].f[k];
+           d += image_sig_cmp_features_weights[k] * dif * dif;
+         }
+       mf[i][j] = d;
+       d = 0;
+       for (uns k = 0; k < IMAGE_REG_H; k++)
+         {
+           int dif = reg1[i].h[k] - reg2[j].h[k];
+           d += image_sig_cmp_features_weights[k + IMAGE_VEC_F] * dif * dif;
+         }
+       mh[i][j] = d;
+      }
+
+  uns lfs = 0, lhs = 0;
+  for (uns i = 0; i < cnt1; i++)
+    {
+      uns f = mf[i][0], h = mh[i][0];
+      for (uns j = 1; j < cnt2; j++)
+        {
+         f = MIN(f, mf[i][j]);
+         h = MIN(h, mh[i][j]);
+       }
+      lf[i] = (df * 0x10000) / (df + fast_sqrt_u32(f));
+      lh[i] = (dh * 0x10000) / (dh + fast_sqrt_u32(h));
+      lfs += lf[i] * (6 * reg1[i].wa + 2 * reg1[i].wb);
+      lhs += lh[i] * reg1[i].wa;
+    }
+  for (uns i = 0; i < cnt2; i++)
+    {
+      uns f = mf[0][i], h = mh[0][i];
+      for (uns j = 1; j < cnt1; j++)
+        {
+         f = MIN(f, mf[j][i]);
+         h = MIN(h, mh[j][i]);
+       }
+      lf[i + cnt1] = (df * 0x10000) / (df + fast_sqrt_u32(f));
+      lh[i + cnt1] = (dh * 0x10000) / (dh + fast_sqrt_u32(h));
+      lfs += lf[i] * (6 * reg2[i].wa + 2 * reg2[i].wb);
+      lhs += lh[i] * reg2[i].wa;
+    }
+
+  uns measure = lfs * 6 + lhs * 2 * 8;
+
+#ifdef EXPLAIN
+  /* Display similarity vectors */
+  MSG("Lf=(");
+  for (uns i = 0; i < cnt1 + cnt2; i++)
+    {
+      if (i)
+       MSG(" ");
+      if (i == cnt1)
+       MSG("~ ");
+      MSG("%.4f", (double)lf[i] / 0x10000);
+    }
+  MSGL(")");
+  MSG("Lh=(");
+  for (uns i = 0; i < cnt1 + cnt2; i++)
+    {
+      if (i)
+       MSG(" ");
+      if (i == cnt1)
+       MSG("~ ");
+      MSG("%.4f", (double)lh[i] / 0x10000);
+    }
+  MSGL(")");
+  MSGL("Lfm=%.4f", lfs / (double)(1 << (3 + 8 + 16)));
+  MSGL("Lhm=%.4f", lhs / (double)(1 << (8 + 16)));
+  MSGL("measure=%.4f", measure / (double)(1 << (3 + 3 + 8 + 16)));
+#endif
+
+  return (1 << (3 + 3 + 8 + 16)) - measure;
+}
+
+#ifndef EXPLAIN
+static uns
+image_signatures_dist_average(struct image_signature *sig1, struct image_signature *sig2)
+#else
+static uns
+image_signatures_dist_average_explain(struct image_signature *sig1, struct image_signature *sig2, void (*msg)(byte *text, void *param), void *param)
+#endif
+{
+#ifdef EXPLAIN
+  byte buf[1024], *line = buf;
+  MSGL("Average matching");
+#endif
+
+  uns dist = 0;
+  for (uns i = 0; i < IMAGE_VEC_F; i++)
+    {
+      uns d = image_sig_cmp_features_weights[0] * isqr((int)sig1->vec.f[i] - (int)sig2->vec.f[i]); 
+      MSGL("feature %u: d=%u (%u %u)", i, d, sig1->vec.f[i], sig2->vec.f[i]);
+      dist += d;
+    }
+
+  MSGL("dist=%u", dist);
+  return dist;
+}
+
+#ifndef EXPLAIN
+#define CALL(x) image_signatures_dist_##x(sig1, sig2)
+uns
+image_signatures_dist(struct image_signature *sig1, struct image_signature *sig2)
+#else
+#define CALL(x) image_signatures_dist_##x##_explain(sig1, sig2, msg, param)
+uns
+image_signatures_dist_explain(struct image_signature *sig1, struct image_signature *sig2, void (*msg)(byte *text, void *param), void *param)
+#endif
+{
+  if (!sig1->len)
+    return CALL(average);
+  else
+    switch (image_sig_compare_method)
+      {
+        case 0:
+         return CALL(integrated);
+       case 1:
+         return CALL(fuzzy);
+       case 2:
+         return CALL(average);
+       default:
+         ASSERT(0);
+      }
+}
+#undef CALL
+
+#undef EXPLAIN
+#undef MSG
+#undef LINE
+#undef MSGL
diff --git a/images/sig-cmp.c b/images/sig-cmp.c
new file mode 100644 (file)
index 0000000..b420f06
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ *     Image Library -- Comparisions of image signatures
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "lib/lib.h"
+#include "lib/math.h"
+#include "images/math.h"
+#include "images/images.h"
+#include "images/signature.h"
+
+#include <stdio.h>
+
+#define ASORT_PREFIX(x) image_signatures_dist_integrated_##x
+#define ASORT_KEY_TYPE uns
+#define ASORT_ELT(i) items[i]
+#define ASORT_EXTRA_ARGS , uns *items
+#include "lib/arraysort.h"
+
+#define EXPLAIN
+#include "images/sig-cmp-gen.h"
+#include "images/sig-cmp-gen.h"
diff --git a/images/sig-dump.c b/images/sig-dump.c
new file mode 100644 (file)
index 0000000..a150f61
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *     Image Library -- Dumping of image signatures
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "images/images.h"
+#include "images/signature.h"
+#include <stdio.h>
+
+byte *
+image_vector_dump(byte *buf, struct image_vector *vec)
+{
+  byte *p = buf;
+  *p++ = '(';
+  for (uns i = 0; i < IMAGE_VEC_F; i++)
+    {
+      if (i)
+       *p++ = ' ';
+      p += sprintf(p, "%u", vec->f[i]);
+    }
+  *p++ = ')';
+  *p = 0;
+  return buf;
+}
+
+byte *
+image_region_dump(byte *buf, struct image_region *reg)
+{
+  byte *p = buf;
+  p += sprintf(p, "(txt=");
+  for (uns i = 0; i < IMAGE_REG_F; i++)
+    {
+      if (i)
+       *p++ = ' ';
+      p += sprintf(p, "%u", reg->f[i]);
+    }
+  p += sprintf(p, " shp=");
+  for (uns i = 0; i < IMAGE_REG_H; i++)
+    {
+      if (i)
+       *p++ = ' ';
+      p += sprintf(p, "%u", reg->h[i]);
+    }
+  p += sprintf(p, " wa=%u wb=%u)", reg->wa, reg->wb);
+  *p = 0;
+  return buf;
+}
diff --git a/images/sig-init.c b/images/sig-init.c
new file mode 100644 (file)
index 0000000..8a3b5ea
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ *     Image Library -- Computation of image signatures
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "sherlock/sherlock.h"
+#include "lib/fastbuf.h"
+#include "lib/conf.h"
+#include "lib/math.h"
+#include "images/images.h"
+#include "images/math.h"
+#include "images/error.h"
+#include "images/color.h"
+#include "images/signature.h"
+
+#include <alloca.h>
+
+int
+image_sig_init(struct image_context *ctx, struct image_sig_data *data, struct image *image)
+{
+  ASSERT((image->flags & IMAGE_PIXEL_FORMAT) == COLOR_SPACE_RGB);
+  data->image = image;
+  data->flags = 0;
+  data->cols = (image->cols + 3) >> 2;
+  data->rows = (image->rows + 3) >> 2;
+  data->full_cols = image->cols >> 2;
+  data->full_rows = image->rows >> 2;
+  data->blocks_count = data->cols * data->rows;
+  if (data->blocks_count >= 0x10000)
+    {
+      IMAGE_ERROR(ctx, IMAGE_ERROR_INVALID_DIMENSIONS, "Image too large for implemented signature algorithm.");
+      return 0;
+    }
+  data->blocks = xmalloc(data->blocks_count * sizeof(struct image_sig_block));
+  data->area = image->cols * image->rows;
+  DBG("Computing signature for image of %ux%u pixels (%ux%u blocks)",
+      image->cols, image->rows, data->cols, data->rows);
+  return 1;
+}
+
+void
+image_sig_preprocess(struct image_sig_data *data)
+{
+  struct image *image = data->image;
+  struct image_sig_block *block = data->blocks;
+  uns sum[IMAGE_VEC_F];
+  bzero(sum, sizeof(sum));
+
+  /* Every block of 4x4 pixels */
+  byte *row_start = image->pixels;
+  for (uns block_y = 0; block_y < data->rows; block_y++, row_start += image->row_size * 4)
+    {
+      byte *p = row_start;
+      for (uns block_x = 0; block_x < data->cols; block_x++, p += 12, block++)
+        {
+          int t[16], s[16], *tp = t;
+         block->x = block_x;
+         block->y = block_y;
+
+         /* Convert pixels to Luv color space and compute average coefficients */
+         uns l_sum = 0, u_sum = 0, v_sum = 0;
+         byte *p2 = p;
+         if (block_x < data->full_cols && block_y < data->full_rows)
+           {
+             for (uns y = 0; y < 4; y++, p2 += image->row_size - 12)
+               for (uns x = 0; x < 4; x++, p2 += 3)
+                 {
+                   byte luv[3];
+                   srgb_to_luv_pixel(luv, p2);
+                   l_sum += *tp++ = luv[0] / 4;
+                   u_sum += luv[1];
+                   v_sum += luv[2];
+                 }
+             block->area = 16;
+             sum[0] += l_sum;
+             sum[1] += u_sum;
+             sum[2] += v_sum;
+             block->v[0] = (l_sum >> 4);
+             block->v[1] = (u_sum >> 4);
+             block->v[2] = (v_sum >> 4);
+           }
+         /* Incomplete square near the edge */
+         else
+           {
+             uns x, y;
+             uns square_cols = (block_x < data->full_cols) ? 4 : image->cols & 3;
+             uns square_rows = (block_y < data->full_rows) ? 4 : image->rows & 3;
+             for (y = 0; y < square_rows; y++, p2 += image->row_size)
+               {
+                 byte *p3 = p2;
+                 for (x = 0; x < square_cols; x++, p3 += 3)
+                   {
+                     byte luv[3];
+                     srgb_to_luv_pixel(luv, p3);
+                     l_sum += *tp++ = luv[0] / 4;
+                     u_sum += luv[1];
+                     v_sum += luv[2];
+                   }
+                 for (; x < 4; x++)
+                   {
+                     *tp = tp[-square_cols];
+                     tp++;
+                   }
+               }
+             for (; y < 4; y++)
+               for (x = 0; x < 4; x++)
+                 {
+                   *tp = tp[-square_rows * 4];
+                   tp++;
+                 }
+             block->area = square_cols * square_rows;
+             uns inv = 0x10000 / block->area;
+             sum[0] += l_sum;
+             sum[1] += u_sum;
+             sum[2] += v_sum;
+             block->v[0] = (l_sum * inv) >> 16;
+             block->v[1] = (u_sum * inv) >> 16;
+             block->v[2] = (v_sum * inv) >> 16;
+           }
+
+         /* Apply Daubechies wavelet transformation */
+
+#         define DAUB_0        31651   /* (1 + sqrt 3) / (4 * sqrt 2) * 0x10000 */
+#         define DAUB_1        54822   /* (3 + sqrt 3) / (4 * sqrt 2) * 0x10000 */
+#         define DAUB_2        14689   /* (3 - sqrt 3) / (4 * sqrt 2) * 0x10000 */
+#         define DAUB_3        -8481   /* (1 - sqrt 3) / (4 * sqrt 2) * 0x10000 */
+
+         /* ... to the rows */
+         uns i;
+          for (i = 0; i < 16; i += 4)
+            {
+             s[i + 0] = (DAUB_0 * t[i + 2] + DAUB_1 * t[i + 3] + DAUB_2 * t[i + 0] + DAUB_3 * t[i + 1]) / 0x10000;
+             s[i + 1] = (DAUB_0 * t[i + 0] + DAUB_1 * t[i + 1] + DAUB_2 * t[i + 2] + DAUB_3 * t[i + 3]) / 0x10000;
+             s[i + 2] = (DAUB_3 * t[i + 2] - DAUB_2 * t[i + 3] + DAUB_1 * t[i + 0] - DAUB_0 * t[i + 1]) / 0x10000;
+             s[i + 3] = (DAUB_3 * t[i + 0] - DAUB_2 * t[i + 1] + DAUB_1 * t[i + 2] - DAUB_0 * t[i + 3]) / 0x10000;
+           }
+
+         /* ... and to the columns... skip LL band */
+         for (i = 0; i < 2; i++)
+           {
+             t[i + 8] = (DAUB_3 * s[i + 8] - DAUB_2 * s[i +12] + DAUB_1 * s[i + 0] - DAUB_0 * s[i + 4]) / 0x10000;
+             t[i +12] = (DAUB_3 * s[i + 0] - DAUB_2 * s[i + 4] + DAUB_1 * s[i + 8] - DAUB_0 * s[i +12]) / 0x10000;
+           }
+         for (; i < 4; i++)
+           {
+             t[i + 0] = (DAUB_0 * s[i + 8] + DAUB_1 * s[i +12] + DAUB_2 * s[i + 0] + DAUB_3 * s[i + 4]) / 0x10000;
+             t[i + 4] = (DAUB_0 * s[i + 0] + DAUB_1 * s[i + 4] + DAUB_2 * s[i + 8] + DAUB_3 * s[i +12]) / 0x10000;
+             t[i + 8] = (DAUB_3 * s[i + 8] - DAUB_2 * s[i +12] + DAUB_1 * s[i + 0] - DAUB_0 * s[i + 4]) / 0x10000;
+             t[i +12] = (DAUB_3 * s[i + 0] - DAUB_2 * s[i + 4] + DAUB_1 * s[i + 8] - DAUB_0 * s[i +12]) / 0x10000;
+           }
+
+         /* Extract energies in LH, HL and HH bands */
+         block->v[3] = fast_sqrt_u32(isqr(t[8]) + isqr(t[9]) + isqr(t[12]) + isqr(t[13]));
+         block->v[4] = fast_sqrt_u32(isqr(t[2]) + isqr(t[3]) + isqr(t[6]) + isqr(t[7]));
+         block->v[5] = fast_sqrt_u32(isqr(t[10]) + isqr(t[11]) + isqr(t[14]) + isqr(t[15]));
+         sum[3] += block->v[3] * block->area;
+         sum[4] += block->v[4] * block->area;
+         sum[5] += block->v[5] * block->area;
+        }
+    }
+
+  /* Compute featrures average */
+  uns inv = 0xffffffffU / data->area;
+  for (uns i = 0; i < IMAGE_VEC_F; i++)
+    data->f[i] = ((u64)sum[i] * inv) >> 32;
+
+  if (image->cols < image_sig_min_width || image->rows < image_sig_min_height)
+    {
+      data->valid = 0;
+      data->regions_count = 0;
+    }
+  else
+    data->valid = 1;
+}
+
+void
+image_sig_finish(struct image_sig_data *data, struct image_signature *sig)
+{
+  for (uns i = 0; i < IMAGE_VEC_F; i++)
+    sig->vec.f[i] = data->f[i];
+  sig->len = data->regions_count;
+  sig->flags = data->flags;
+  if (!sig->len)
+    return;
+  
+  /* For each region */
+  u64 w_total = 0;
+  uns w_border = MIN(data->cols, data->rows) * image_sig_border_size;
+  int w_mul = w_border ? image_sig_border_bonus * 256 / (int)w_border : 0;
+  for (uns i = 0; i < sig->len; i++)
+    {
+      struct image_sig_region *r = data->regions + i;
+      DBG("Processing region %u: count=%u", i, r->count);
+      ASSERT(r->count);
+
+      /* Copy texture properties */
+      sig->reg[i].f[0] = r->a[0];
+      sig->reg[i].f[1] = r->a[1];
+      sig->reg[i].f[2] = r->a[2];
+      sig->reg[i].f[3] = r->a[3];
+      sig->reg[i].f[4] = r->a[4];
+      sig->reg[i].f[5] = r->a[5];
+
+      /* Compute coordinates centroid and region weight */
+      u64 x_sum = 0, y_sum = 0, w_sum = 0;
+      for (struct image_sig_block *b = r->blocks; b; b = b->next)
+        {
+         x_sum += b->x;
+         y_sum += b->y;
+         uns d = b->x;
+         d = MIN(d, b->y);
+         d = MIN(d, data->cols - b->x - 1);
+         d = MIN(d, data->rows - b->y - 1);
+         if (d >= w_border)
+           w_sum += 128;
+         else
+           w_sum += 128 + (int)(w_border - d) * w_mul / 256;
+       }
+      w_total += w_sum;
+      r->w_sum = w_sum;
+      uns x_avg = x_sum / r->count;
+      uns y_avg = y_sum / r->count;
+      DBG("  centroid=(%u %u)", x_avg, y_avg);
+
+      /* Compute normalized inertia */
+      u64 sum1 = 0, sum2 = 0, sum3 = 0;
+      for (struct image_sig_block *b = r->blocks; b; b = b->next)
+        {
+         uns inc2 = isqr(x_avg - b->x) + isqr(y_avg - b->y);
+         uns inc1 = fast_sqrt_u32(inc2);
+         sum1 += inc1;
+         sum2 += inc2;
+         sum3 += inc1 * inc2;
+       }
+      sig->reg[i].h[0] = CLAMP(image_sig_inertia_scale[0] * sum1 * ((3 * M_PI * M_PI) / 2) * pow(r->count, -1.5), 0, 255);
+      sig->reg[i].h[1] = CLAMP(image_sig_inertia_scale[1] * sum2 * ((4 * M_PI * M_PI * M_PI) / 2) / ((u64)r->count * r->count), 0, 255);
+      sig->reg[i].h[2] = CLAMP(image_sig_inertia_scale[2] * sum3 * ((5 * M_PI * M_PI * M_PI * M_PI) / 2) * pow(r->count, -2.5), 0, 255);
+      sig->reg[i].h[3] = (uns)x_avg * 127 / data->cols;
+      sig->reg[i].h[4] = (uns)y_avg * 127 / data->rows;
+    }
+
+  /* Compute average differences */
+  u64 df = 0, dh = 0;
+
+  if (sig->len < 2)
+    {
+      sig->df = 1;
+      sig->dh = 1;
+    }
+  else
+    {
+      uns cnt = 0;
+      for (uns i = 0; i < sig->len; i++)
+        for (uns j = i + 1; j < sig->len; j++)
+          {
+           uns d = 0;
+           for (uns k = 0; k < IMAGE_REG_F; k++)
+             d += image_sig_cmp_features_weights[k] * isqr(sig->reg[i].f[k] - sig->reg[j].f[k]);
+           df += fast_sqrt_u32(d);
+           d = 0;
+           for (uns k = 0; k < IMAGE_REG_H; k++)
+             d += image_sig_cmp_features_weights[k + IMAGE_REG_F] * isqr(sig->reg[i].h[k] - sig->reg[j].h[k]);
+           dh += fast_sqrt_u32(d);
+           cnt++;
+          }
+      sig->df = CLAMP(df / cnt, 1, 0xffff);
+      sig->dh = CLAMP(dh / cnt, 1, 0xffff);
+    }
+  DBG("Average regions difs: df=%u dh=%u", sig->df, sig->dh);
+
+  /* Compute normalized weights */
+  uns wa = 128, wb = 128;
+  for (uns i = sig->len; --i > 0; )
+    {
+      struct image_sig_region *r = data->regions + i;
+      wa -= sig->reg[i].wa = CLAMP(r->count * 128 / data->blocks_count, 1, (int)(wa - i));
+      wb -= sig->reg[i].wb = CLAMP(r->w_sum * 128 / w_total, 1, (int)(wb - i));
+    }
+  sig->reg[0].wa = wa;
+  sig->reg[0].wb = wb;
+
+  /* Store image dimensions */
+  sig->cols = data->image->cols;
+  sig->rows = data->image->rows;
+
+  /* Dump regions features */
+#ifdef LOCAL_DEBUG
+  for (uns i = 0; i < sig->len; i++)
+    {
+      byte buf[IMAGE_REGION_DUMP_MAX];
+      image_region_dump(buf, sig->reg + i);
+      DBG("region %u: features=%s", i, buf);
+    }
+#endif
+}
+
+void
+image_sig_cleanup(struct image_sig_data *data)
+{
+  xfree(data->blocks);
+}
+
+int
+compute_image_signature(struct image_context *ctx, struct image_signature *sig, struct image *image)
+{
+  struct image_sig_data data;
+  if (!image_sig_init(ctx, &data, image))
+    return 0;
+  image_sig_preprocess(&data);
+  if (data.valid)
+    {
+      image_sig_segmentation(&data);
+      image_sig_detect_textured(&data);
+    }
+  image_sig_finish(&data, sig);
+  image_sig_cleanup(&data);
+  return 1;
+}
diff --git a/images/sig-seg.c b/images/sig-seg.c
new file mode 100644 (file)
index 0000000..cdcb71c
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ *     Image Library -- Image segmentation
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "sherlock/sherlock.h"
+#include "lib/conf.h"
+#include "lib/heap.h"
+#include "images/images.h"
+#include "images/signature.h"
+#include "images/math.h"
+
+#include <string.h>
+
+#ifdef LOCAL_DEBUG
+static void
+dump_segmentation(struct image_sig_region *regions, uns regions_count)
+{
+  uns cols = 0, rows = 0;
+  for (uns i = 0; i < regions_count; i++)
+    for (struct image_sig_block *b = regions[i].blocks; b; b = b->next)
+      {
+       cols = MAX(cols, b->x + 1);
+       rows = MAX(rows, b->y + 1);
+      }
+  uns size = (cols + 1) * rows;
+  byte buf[size];
+  bzero(buf, size);
+  for (uns i = 0; i < regions_count; i++)
+    {
+      byte c = (i < 10) ? '0' + i : 'A' - 10 + i;
+      for (struct image_sig_block *b = regions[i].blocks; b; b = b->next)
+        buf[b->x + b->y * (cols + 1)] = c;
+    }
+  for (uns i = 0; i < rows; i++)
+    log(L_DEBUG, "%s", &buf[i * (cols + 1)]);
+}
+#endif
+
+/* Pre-quantization - recursively split groups of blocks with large error */
+
+static inline void
+prequant_init_region(struct image_sig_region *region)
+{
+  bzero(region, sizeof(*region));
+}
+
+static inline void
+prequant_add_block(struct image_sig_region *region, struct image_sig_block *block)
+{
+  block->next = region->blocks;
+  region->blocks = block;
+  region->count++;
+  for (uns i = 0; i < IMAGE_VEC_F; i++)
+    {
+      region->b[i] += block->v[i];
+      region->c[i] += isqr(block->v[i]);
+    }
+}
+
+static void
+prequant_finish_region(struct image_sig_region *region)
+{
+  if (region->count < 2)
+    {
+      region->e = 0;
+    }
+  else
+    {
+      u64 a = 0;
+      region->e = 0;
+      for (uns i = 0; i < IMAGE_VEC_F; i++)
+        {
+         region->e += region->c[i];
+         a += (u64)region->b[i] * region->b[i];
+       }
+      region->e -= a / region->count;
+      DBG("Finished region %u", (uns)region->e / region->count);
+    }
+}
+
+static inline uns
+prequant_heap_cmp(struct image_sig_region *a, struct image_sig_region *b)
+{
+  return a->e > b->e;
+}
+
+#define ASORT_PREFIX(x) prequant_##x
+#define ASORT_KEY_TYPE uns
+#define ASORT_ELT(i) val[i]
+#define ASORT_EXTRA_ARGS , uns *val
+#include "lib/arraysort.h"
+
+static uns
+prequant(struct image_sig_block *blocks, uns blocks_count, struct image_sig_region *regions)
+{
+  DBG("Starting pre-quantization");
+
+  uns regions_count, heap_count, axis;
+  struct image_sig_block *blocks_end = blocks + blocks_count, *block, *block2;
+  struct image_sig_region *heap[IMAGE_REG_MAX + 1], *region, *region2;
+
+  /* Initialize single region with all blocks */
+  regions_count = heap_count = 1;
+  heap[1] = regions;
+  prequant_init_region(regions);
+  for (block = blocks; block != blocks_end; block++)
+    prequant_add_block(regions, block);
+  prequant_finish_region(regions);
+
+  /* Main cycle */
+  while (regions_count < IMAGE_REG_MAX &&
+      regions_count <= DARY_LEN(image_sig_prequant_thresholds) && heap_count)
+    {
+      region = heap[1];
+      DBG("Step... regions_count=%u heap_count=%u region->count=%u, region->e=%u",
+         regions_count, heap_count, region->count, (uns)region->e);
+      if (region->count < 2 ||
+         region->e < image_sig_prequant_thresholds[regions_count - 1] * blocks_count)
+        {
+         HEAP_DELMIN(struct image_sig_region *, heap, heap_count, prequant_heap_cmp, HEAP_SWAP);
+         continue;
+       }
+
+      /* Select axis to split - the one with maximum average quadratic error */
+      axis = 0;
+      u64 cov = (u64)region->count * region->c[0] - (u64)region->b[0] * region->b[0];
+      for (uns i = 1; i < 6; i++)
+        {
+         uns j = (u64)region->count * region->c[i] - (u64)region->b[i] * region->b[i];
+         if (j > cov)
+           {
+             axis = i;
+             cov = j;
+           }
+       }
+      DBG("Splitting axis %u with average quadratic error %u", axis, (uns)(cov / (region->count * region->count)));
+
+      /* Sort values on the split axis */
+      uns val[256], cnt[256], cval;
+      if (region->count > 64)
+        {
+         bzero(cnt, sizeof(cnt));
+         for (block = region->blocks; block; block = block->next)
+           cnt[block->v[axis]]++;
+         cval = 0;
+         for (uns i = 0; i < 256; i++)
+           if (cnt[i])
+             {
+               val[cval] = i;
+               cnt[cval] = cnt[i];
+               cval++;
+             }
+       }
+      else
+        {
+          block = region->blocks;
+          for (uns i = 0; i < region->count; i++, block = block->next)
+           val[i] = block->v[axis];
+         prequant_sort(region->count, val);
+         cval = 1;
+         cnt[0] = 1;
+         for (uns i = 1; i < region->count; i++)
+           if (val[i] == val[cval - 1])
+             cnt[cval - 1]++;
+           else
+             {
+               val[cval] = val[i];
+               cnt[cval] = 1;
+               cval++;
+             }
+       }
+      
+      /* Select split value - to minimize error */
+      uns b1 = val[0] * cnt[0];
+      uns c1 = isqr(val[0]) * cnt[0];
+      uns b2 = region->b[axis] - b1;
+      uns c2 = region->c[axis] - c1;
+      uns i = cnt[0], j = region->count - cnt[0];
+      u64 best_err = c1 - (u64)b1 * b1 / i + c2 - (u64)b2 * b2 / j;
+      uns split_val = val[0];
+      for (uns k = 1; k < cval - 1; k++)
+        {
+         uns b0 = val[k] * cnt[k];
+         uns c0 = isqr(val[k]) * cnt[k];
+         b1 += b0;
+         b2 -= b0;
+         c1 += c0;
+         c2 -= c0;
+         i += cnt[k];
+         j -= cnt[k];
+         u64 err = (u64)c1 - (u64)b1 * b1 / i + (u64)c2 - (u64)b2 * b2 / j; 
+         if (err < best_err)
+           {
+             best_err = err;
+             split_val = val[k];
+           }
+       }
+      DBG("split_val=%u best_err=%Lu b[axis]=%u c[axis]=%u", split_val, (long long)best_err, region->b[axis], region->c[axis]);
+
+      /* Split region */
+      block = region->blocks;
+      region2 = regions + regions_count++;
+      prequant_init_region(region);
+      prequant_init_region(region2);
+      while (block)
+        {
+         block2 = block->next;
+         if (block->v[axis] <= split_val)
+           prequant_add_block(region, block);
+         else
+           prequant_add_block(region2, block);
+         block = block2;
+       }
+      prequant_finish_region(region);
+      prequant_finish_region(region2);
+      HEAP_INCREASE(struct image_sig_region *, heap, heap_count, prequant_heap_cmp, HEAP_SWAP, 1);
+      heap[++heap_count] = region2;
+      HEAP_INSERT(struct image_sig_region *, heap, heap_count, prequant_heap_cmp, HEAP_SWAP);
+    }
+
+  DBG("Pre-quantized to %u regions", regions_count);
+
+  return regions_count;
+}
+
+/* Post-quantization - run a few K-mean iterations to improve pre-quantized regions */
+
+static uns
+postquant(struct image_sig_block *blocks, uns blocks_count, struct image_sig_region *regions, uns regions_count)
+{
+  DBG("Starting post-quantization");
+
+  struct image_sig_block *blocks_end = blocks + blocks_count, *block;
+  struct image_sig_region *regions_end = regions + regions_count, *region;
+  uns error = 0, last_error;
+
+  /* Initialize regions and initial segmentation error */
+  for (region = regions; region != regions_end; )
+    {
+      uns inv = 0xffffffffU / region->count;
+      for (uns i = 0; i < IMAGE_VEC_F; i++)
+        {
+          region->a[i] = ((u64)region->b[i] * inv) >> 32;
+          error += region->c[i] - region->a[i] * region->b[i];
+        }
+      region++;
+    }
+
+  /* Convergation cycle */
+  for (uns step = 0; step < image_sig_postquant_max_steps; step++)
+    {
+      DBG("Step...");
+
+      /* Clear regions */
+      for (region = regions; region != regions_end; region++)
+        {
+         region->blocks = NULL;
+         region->count = 0;
+         bzero(region->b, sizeof(region->b));
+         bzero(region->c, sizeof(region->c));
+       }
+
+      /* Assign each block to its nearest pivot and accumulate region variables */
+      for (block = blocks; block != blocks_end; block++)
+        {
+         struct image_sig_region *best_region = NULL;
+         uns best_dist = ~0U;
+         for (region = regions; region != regions_end; region++)
+           {
+             uns dist =
+               isqr(block->v[0] - region->a[0]) +
+               isqr(block->v[1] - region->a[1]) +
+               isqr(block->v[2] - region->a[2]) +
+               isqr(block->v[3] - region->a[3]) +
+               isqr(block->v[4] - region->a[4]) +
+               isqr(block->v[5] - region->a[5]);
+             if (dist <= best_dist)
+               {
+                 best_dist = dist;
+                 best_region = region;
+               }
+           }
+         region = best_region;
+         region->count++;
+         block->next = region->blocks;
+         region->blocks = block;
+         for (uns i = 0; i < IMAGE_VEC_F; i++)
+           {
+             region->b[i] += block->v[i];
+             region->c[i] += isqr(block->v[i]);
+           }
+       }
+
+      /* Finish regions, delete empty ones (should appear rarely), compute segmentation error */
+      last_error = error;
+      error = 0;
+      for (region = regions; region != regions_end; )
+       if (region->count)
+          {
+           uns inv = 0xffffffffU / region->count;
+           for (uns i = 0; i < IMAGE_VEC_F; i++)
+             {
+               region->a[i] = ((u64)region->b[i] * inv) >> 32;
+               error += region->c[i] - region->a[i] * region->b[i];
+             }
+           region++;
+         }
+        else
+         {
+           regions_end--;
+           *region = *regions_end;
+         }
+
+      DBG("last_error=%u error=%u", last_error, error);
+
+      /* Convergation criteria */
+      if (step >= image_sig_postquant_min_steps)
+        {
+         if (error > last_error)
+           break;
+         u64 dif = last_error - error;
+         if (dif * image_sig_postquant_threshold < (u64)last_error * 100)
+           break;
+       }
+    }
+
+  DBG("Post-quantized to %u regions with average square error %u", regions_end - regions, error / blocks_count);
+
+  return regions_end - regions;
+}
+
+void
+image_sig_segmentation(struct image_sig_data *data)
+{
+  data->regions_count = prequant(data->blocks, data->blocks_count, data->regions);
+#ifdef LOCAL_DEBUG
+  dump_segmentation(data->regions, data->regions_count);
+#endif
+  data->regions_count = postquant(data->blocks, data->blocks_count, data->regions, data->regions_count);
+#ifdef LOCAL_DEBUG
+  dump_segmentation(data->regions, data->regions_count);
+#endif
+}
+
diff --git a/images/sig-txt.c b/images/sig-txt.c
new file mode 100644 (file)
index 0000000..ab04143
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ *     Image Library -- Detection of textured images
+ *
+ *     (c) 2006 Pavel Charvat <pchar@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "sherlock/sherlock.h"
+#include "images/images.h"
+#include "images/signature.h"
+#include "images/math.h"
+
+#include <string.h>
+
+#define MAX_CELLS_COLS 4
+#define MAX_CELLS_ROWS 4
+
+void
+image_sig_detect_textured(struct image_sig_data *data)
+{
+  if (image_sig_textured_threshold <= 0)
+    {
+      DBG("Zero textured threshold.");
+      return;
+    }
+       
+  uns cols = data->cols;
+  uns rows = data->rows;
+  uns cell_cols = MIN((cols + 1) / 2, MAX_CELLS_COLS);
+  uns cell_rows = MIN((rows + 1) / 2, MAX_CELLS_ROWS);
+  uns cell_x[MAX_CELLS_COLS + 1];
+  uns cell_y[MAX_CELLS_ROWS + 1];
+  uns i, j;
+  u32 cnt[IMAGE_REG_MAX];
+
+  if (cell_cols * cell_rows < 4)
+    {
+      DBG("Image is not textured.");
+      return;
+    }
+
+  DBG("Detecting textured image... cols=%u rows=%u cell_cols=%u cell_rows=%u", cols, rows, cell_cols, cell_rows);
+  
+  /* Compute cells boundaries */
+  for (i = 1, j = 0; i < cell_cols; i++)
+    cell_x[i] = fast_div_u32_u8(j += cols, cell_cols);
+  cell_x[0] = 0;
+  cell_x[cell_cols] = cols;
+  for (i = 1, j = 0; i < cell_rows; i++)
+    cell_y[i] = fast_div_u32_u8(j += rows, cell_rows);
+  cell_y[0] = 0;
+  cell_y[cell_rows] = rows;
+
+  /* Preprocess blocks */
+  for (uns i = 0; i < data->regions_count; i++)
+    for (struct image_sig_block *block = data->regions[i].blocks; block; block = block->next)
+      block->region = i;
+  
+  /* Process cells */
+  double e = 0;
+  for (uns j = 0; j < cell_rows; j++)
+    for (uns i = 0; i < cell_cols; i++)
+      {
+       uns cell_area = 0;
+        bzero(cnt, data->regions_count * sizeof(u32));
+       struct image_sig_block *b1 = data->blocks + cell_x[i] + cell_y[j] * cols, *b2;
+        for (uns y = cell_y[j]; y < cell_y[j + 1]; y++, b1 += cols)
+         {
+           b2 = b1;
+            for (uns x = cell_x[i]; x < cell_x[i + 1]; x++, b2++)
+             {
+               cnt[b2->region]++;
+               cell_area++;
+             }
+         }
+       for (uns k = 0; k < data->regions_count; k++)
+         {
+           int a = data->blocks_count * cnt[k] - cell_area * data->regions[k].count; 
+           e += (double)a * a / ((double)isqr(data->regions[k].count) * cell_area);
+         }
+      }
+
+  DBG("Coefficient=%g", (double)e / (data->regions_count * data->blocks_count));
+
+  /* Threshold */
+  if (e < image_sig_textured_threshold * data->regions_count * data->blocks_count)
+    {
+      data->flags |= IMAGE_SIG_TEXTURED;
+      DBG("Image is textured.");
+    }
+  else
+    DBG("Image is not textured.");
+}
diff --git a/images/signature.h b/images/signature.h
new file mode 100644 (file)
index 0000000..2041747
--- /dev/null
@@ -0,0 +1,130 @@
+#ifndef _IMAGES_SIGNATURE_H
+#define _IMAGES_SIGNATURE_H
+
+/* Configuration */
+extern uns image_sig_min_width, image_sig_min_height;
+extern uns *image_sig_prequant_thresholds;
+extern uns image_sig_postquant_min_steps, image_sig_postquant_max_steps, image_sig_postquant_threshold;
+extern double image_sig_border_size;
+extern int image_sig_border_bonus;
+extern double image_sig_inertia_scale[];
+extern double image_sig_textured_threshold;
+extern int image_sig_compare_method;
+extern uns image_sig_cmp_features_weights[];
+
+#define IMAGE_VEC_F    6
+#define IMAGE_REG_F    IMAGE_VEC_F
+#define IMAGE_REG_H    5
+#define IMAGE_REG_MAX  16
+
+/* K-dimensional feature vector (6 bytes) */
+struct image_vector {
+  byte f[IMAGE_VEC_F];         /* texture features */
+} PACKED;
+
+/* Features for image regions (16 bytes) */
+struct image_region {
+  byte f[IMAGE_VEC_F];         /* texture features - L, u, v, LH, HL, HH */
+  byte h[IMAGE_REG_H];         /* shape/pos features - I1, I2, I3, X, Y */
+  byte wa;                     /* normalized area percentage */
+  byte wb;                     /* normalized weight */
+};
+
+#define IMAGE_SIG_TEXTURED     0x1
+
+/* Image signature (usually 16 + len * 16 bytes) */
+struct image_signature {
+  byte len;                    /* number of regions */
+  byte flags;                  /* IMAGE_SIG_xxx */
+  u16 cols;                    /* image width */
+  u16 rows;                    /* image height */
+  u16 df;                      /* average weighted f dist */
+  u16 dh;                      /* average weighted h dist */
+  struct image_vector vec;     /* average features of all regions... simple signature */
+  struct image_region reg[IMAGE_REG_MAX];/* feature vector for every region */
+};
+
+struct image_cluster {
+  union {
+    struct {
+      s32 dot;                 /* dot product of the splitting plane */
+      s8 vec[IMAGE_VEC_F];     /* normal vector of the splitting plane */
+    };
+    struct {
+      u64 pos;                 /* cluster size in bytes */
+    };
+  };
+};
+
+static inline uns
+image_signature_size(uns len)
+{
+  return OFFSETOF(struct image_signature, reg) + len * sizeof(struct image_region);
+}
+
+/* sig-dump.c */
+
+#define IMAGE_VECTOR_DUMP_MAX (IMAGE_VEC_F * 16 + 1)
+#define IMAGE_REGION_DUMP_MAX ((IMAGE_REG_F + IMAGE_REG_H) * 16 + 100)
+
+byte *image_vector_dump(byte *buf, struct image_vector *vec);
+byte *image_region_dump(byte *buf, struct image_region *reg);
+
+struct image_sig_block {
+  struct image_sig_block *next;                /* linked list */
+  u32 x, y;                            /* block position */
+  byte area;                           /* block area in pixels (usually 16) */
+  byte region;                         /* region index */
+  byte v[IMAGE_VEC_F];                 /* feature vector */
+};
+
+struct image_sig_region {
+  struct image_sig_block *blocks;
+  u32 count;
+  u32 a[IMAGE_VEC_F];
+  u32 b[IMAGE_VEC_F];
+  u32 c[IMAGE_VEC_F];
+  u64 e;
+  u64 w_sum;
+};
+
+struct image_sig_data {
+  struct image *image;
+  struct image_sig_block *blocks;
+  struct image_sig_region regions[IMAGE_REG_MAX];
+  u32 cols;
+  u32 rows;
+  u32 full_cols;
+  u32 full_rows;
+  u32 flags;
+  u32 area;
+  u32 valid;
+  u32 blocks_count;
+  u32 regions_count;
+  u32 f[IMAGE_VEC_F];
+};
+
+/* sig-init.c */
+
+int compute_image_signature(struct image_context *ctx, struct image_signature *sig, struct image *image);
+
+int image_sig_init(struct image_context *ctx, struct image_sig_data *data, struct image *image);
+void image_sig_preprocess(struct image_sig_data *data);
+void image_sig_finish(struct image_sig_data *data, struct image_signature *sig);
+void image_sig_cleanup(struct image_sig_data *data);
+
+/* sig-seg.c */
+
+void image_sig_segmentation(struct image_sig_data *data);
+
+/* sig-txt.c */
+
+void image_sig_detect_textured(struct image_sig_data *data);
+
+/* sig-cmp.c */
+
+uns image_signatures_dist(struct image_signature *sig1, struct image_signature *sig2);
+uns image_signatures_dist_explain(struct image_signature *sig1, struct image_signature *sig2, void (*msg)(byte *text, void *param), void *param);
+
+#endif
+