From: Pavel Charvat Date: Thu, 19 Oct 2006 08:13:19 +0000 (+0200) Subject: Partial merge with dev-img - added libimages. X-Git-Tag: holmes-import~507^2~42 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=f079adbc69c1521039ee8c6b3aaac5eb209889cd;p=libucw.git Partial merge with dev-img - added libimages. --- diff --git a/cf/images b/cf/images new file mode 100644 index 00000000..0f4a331d --- /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 index 00000000..0b4b27de --- /dev/null +++ b/images/Makefile @@ -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 index 00000000..d97207e8 --- /dev/null +++ b/images/alpha.c @@ -0,0 +1,78 @@ +/* + * Image Library -- Alpha channels + * + * (c) 2006 Pavel Charvat + * + * 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 index 00000000..112f7386 --- /dev/null +++ b/images/color-tool.c @@ -0,0 +1,97 @@ +/* + * Color spaces tool + * + * (c) 2006 Pavel Charvat + * + * 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 +#include +#include +#include +#include + +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 index 00000000..8ea60228 --- /dev/null +++ b/images/color.c @@ -0,0 +1,618 @@ +/* + * Image Library -- Color Spaces + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..8c87e87c --- /dev/null +++ b/images/color.h @@ -0,0 +1,231 @@ +/* + * Image Library -- Color Spaces + * + * (c) 2006 Pavel Charvat + * + * 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 index 00000000..acae30f4 --- /dev/null +++ b/images/color.t @@ -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 index 00000000..a268525e --- /dev/null +++ b/images/config.c @@ -0,0 +1,67 @@ +/* + * Image Library -- Configuration + * + * (c) 2006 Pavel Charvat + */ + +#undef LOCAL_DEBUG + +#include "lib/lib.h" +#include "lib/conf.h" +#include "images/images.h" +#include "images/signature.h" + +#include + +/* 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 index 00000000..6281ba3c --- /dev/null +++ b/images/context.c @@ -0,0 +1,61 @@ +/* + * Image Library -- Image contexts + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..5fec1760 --- /dev/null +++ b/images/dup-cmp.c @@ -0,0 +1,291 @@ +/* + * Image Library -- Duplicates Comparison + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..1dda196c --- /dev/null +++ b/images/dup-init.c @@ -0,0 +1,105 @@ +/* + * Image Library -- Duplicates Comparison + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..dd07b763 --- /dev/null +++ b/images/duplicates.h @@ -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 index 00000000..92f3766f --- /dev/null +++ b/images/error.h @@ -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 index 00000000..9824f092 --- /dev/null +++ b/images/hilbert-test.c @@ -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 +#include + +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 index 00000000..f8794e3c --- /dev/null +++ b/images/hilbert-test.t @@ -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 index 00000000..4971826a --- /dev/null +++ b/images/hilbert.h @@ -0,0 +1,340 @@ +/* + * Image Library -- multidimensional Hilbert curves + * + * (c) 2006 Pavel Charvat + * + * 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 index 00000000..cbd37e5a --- /dev/null +++ b/images/image-dup-test.c @@ -0,0 +1,164 @@ +/* + * Image duplicates testing + * + * (c) 2006 Pavel Charvat + * + * 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 +#include +#include +#include + +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 index 00000000..22f3951c --- /dev/null +++ b/images/image-sim-test.c @@ -0,0 +1,294 @@ +/* + * Image similarity testing + * + * (c) 2006 Pavel Charvat + * + * 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 +#include +#include +#include +#include +#include +#include + +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 index 00000000..b046672f --- /dev/null +++ b/images/image-test.c @@ -0,0 +1,228 @@ +/* + * Image Library -- Simple automatic tests + * + * (c) 2006 Pavel Charvat + * + * 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 +#include +#include +#include +#include + +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 index 00000000..3e40bcb9 --- /dev/null +++ b/images/image-tool.c @@ -0,0 +1,233 @@ +/* + * Image Library -- Simple image manipulation utility + * + * (c) 2006 Pavel Charvat + * + * 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 +#include +#include +#include +#include + +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 index 00000000..dfc72a19 --- /dev/null +++ b/images/image-walk.h @@ -0,0 +1,168 @@ +/* + * Image Library -- Pixels iteration + * + * (c) 2006 Pavel Charvat + * + * 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 index 00000000..e6111ab8 --- /dev/null +++ b/images/image.c @@ -0,0 +1,245 @@ +/* + * Image Library -- Basic image manipulation + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..dbaa055c --- /dev/null +++ b/images/images.h @@ -0,0 +1,173 @@ +/* + * Image Library -- Main header file + * + * (c) 2006 Pavel Charvat + * + * 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 index 00000000..1d3d37c5 --- /dev/null +++ b/images/io-libjpeg.c @@ -0,0 +1,542 @@ +/* + * Image Library -- libjpeg + * + * (c) 2006 Pavel Charvat + * + * 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 +#include +#include +#include +#include + +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 index 00000000..cdf58048 --- /dev/null +++ b/images/io-libmagick.c @@ -0,0 +1,417 @@ +/* + * Image Library -- GraphicsMagick (slow fallback library) + * + * (c) 2006 Pavel Charvat + * + * 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 +#include +#include +#include +#include + +#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 index 00000000..76b2d247 --- /dev/null +++ b/images/io-libpng.c @@ -0,0 +1,379 @@ +/* + * Image Library -- libpng + * + * (c) 2006 Pavel Charvat + * + * 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 +#include + +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 index 00000000..c4648431 --- /dev/null +++ b/images/io-libungif.c @@ -0,0 +1,281 @@ +/* + * Image Library -- libungif + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..7b176b70 --- /dev/null +++ b/images/io-main.c @@ -0,0 +1,383 @@ +/* + * Image Library -- Image compression/decompression interface + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..477c5dd2 --- /dev/null +++ b/images/io-main.h @@ -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 index 00000000..f88daa60 --- /dev/null +++ b/images/math.c @@ -0,0 +1,64 @@ +/* + * Image Library -- Math routines + * + * (c) 2006 Pavel Charvat + * + * 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 index 00000000..1824c95b --- /dev/null +++ b/images/math.h @@ -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 index 00000000..0375fdd9 --- /dev/null +++ b/images/object.c @@ -0,0 +1,107 @@ +/* + * Image Library -- Image cards manipulations + * + * (c) 2006 Pavel Charvat + * + * 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 +#include + +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 index 00000000..189b3c0b --- /dev/null +++ b/images/object.h @@ -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 index 00000000..bbe5496e --- /dev/null +++ b/images/scale-gen.h @@ -0,0 +1,385 @@ +/* + * Image Library -- Image scaling algorithms + * + * (c) 2006 Pavel Charvat + * + * 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 index 00000000..ca80616f --- /dev/null +++ b/images/scale.c @@ -0,0 +1,278 @@ +/* + * Image Library -- Image scaling algorithms + * + * (c) 2006 Pavel Charvat + * + * 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 + +#ifdef __SSE2__ +#include +#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 index 00000000..5a391db0 --- /dev/null +++ b/images/sig-cmp-gen.h @@ -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 index 00000000..b420f067 --- /dev/null +++ b/images/sig-cmp.c @@ -0,0 +1,28 @@ +/* + * Image Library -- Comparisions of image signatures + * + * (c) 2006 Pavel Charvat + * + * 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 + +#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 index 00000000..a150f612 --- /dev/null +++ b/images/sig-dump.c @@ -0,0 +1,52 @@ +/* + * Image Library -- Dumping of image signatures + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..8a3b5ea2 --- /dev/null +++ b/images/sig-init.c @@ -0,0 +1,324 @@ +/* + * Image Library -- Computation of image signatures + * + * (c) 2006 Pavel Charvat + * + * 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 + +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 index 00000000..cdcb71c8 --- /dev/null +++ b/images/sig-seg.c @@ -0,0 +1,351 @@ +/* + * Image Library -- Image segmentation + * + * (c) 2006 Pavel Charvat + * + * 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 + +#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 index 00000000..ab041434 --- /dev/null +++ b/images/sig-txt.c @@ -0,0 +1,97 @@ +/* + * Image Library -- Detection of textured images + * + * (c) 2006 Pavel Charvat + * + * 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 + +#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 index 00000000..20417476 --- /dev/null +++ b/images/signature.h @@ -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 +