diff options
Diffstat (limited to 'backend/dip-obj.c')
-rw-r--r-- | backend/dip-obj.c | 746 |
1 files changed, 746 insertions, 0 deletions
diff --git a/backend/dip-obj.c b/backend/dip-obj.c new file mode 100644 index 0000000..021cb01 --- /dev/null +++ b/backend/dip-obj.c @@ -0,0 +1,746 @@ +/* dip-obj.c -- digital image processing functionality singleton + * Copyright (C) 2011 SEIKO EPSON CORPORATION + * + * License: GPLv2+|iscan + * Authors: AVASYS CORPORATION + * + * This file is part of the SANE backend distributed with Image Scan! + * + * Image Scan!'s SANE backend is free software. + * You can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation; + * either version 2 of the License or at your option any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You ought to have received a copy of the GNU General Public License + * along with this package. If not, see <http://www.gnu.org/licenses/>. + * + * + * Linking Image Scan!'s SANE backend statically or dynamically with + * other modules is making a combined work based on this SANE backend. + * Thus, the terms and conditions of the GNU General Public License + * cover the whole combination. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * SANE frontends that communicate with Image Scan!'s SANE backend + * solely through the SANE Application Programming Interface, + * regardless of the license terms of these SANE frontends, and to + * copy and distribute the resulting combined work under terms of your + * choice, provided that every copy of the combined work is + * accompanied by a complete copy of the source code of Image Scan!'s + * SANE backend (the version of Image Scan!'s SANE backend used to + * produce the combined work), being distributed under the terms of + * the GNU General Public License plus this exception. An independent + * module is a module which is not derived from or based on Image + * Scan!'s SANE backend. + * + * As a special exception, the copyright holders of Image Scan!'s SANE + * backend give you permission to link Image Scan!'s SANE backend with + * independent modules that communicate with Image Scan!'s SANE + * backend solely through the "Interpreter" interface, regardless of + * the license terms of these independent modules, and to copy and + * distribute the resulting combined work under terms of your choice, + * provided that every copy of the combined work is accompanied by a + * complete copy of the source code of Image Scan!'s SANE backend (the + * version of Image Scan!'s SANE backend used to produce the combined + * work), being distributed under the terms of the GNU General Public + * License plus this exception. An independent module is a module + * which is not derived from or based on Image Scan!'s SANE backend. + * + * Note that people who make modified versions of Image Scan!'s SANE + * backend are not obligated to grant special exceptions for their + * modified versions; it is their choice whether to do so. The GNU + * General Public License gives permission to release a modified + * version without this exception; this exception also makes it + * possible to release a modified version which carries forward this + * exception. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> +#include <string.h> + +#include "dip-obj.h" +#include "defines.h" +#include "hw-data.h" +#include "include/sane/sanei_magic.h" +#include "ipc.h" + +#ifndef ENABLE_SANEI_MAGIC +#define ENABLE_SANEI_MAGIC 0 +#endif + +/*! \brief Constrain a value \a v in the interval \c [lo,hi] + */ +#define clamp(v,lo,hi) \ + ((v) < (lo) \ + ? (lo) \ + : ((v) > (hi) \ + ? (hi) \ + : (v))) \ + /**/ + +typedef struct +{ + process *plugin; + + void (*autocrop) (); + void (*deskew) (buffer *, int, int); + +} dip_type; + +static dip_type *dip = NULL; + +static void esdip_crop (buffer *buf, const device *hw, unsigned int count, + const Option_Value *val); +static void esdip_turn (buffer *buf, int res_x, int res_y); + +static void magic_crop (buffer *buf, int res_x, int res_y); +static void magic_turn (buffer *buf, int res_x, int res_y); + +void * +dip_init (const char *pkglibdir, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + + log_call ("(%s, %p)", pkglibdir, status); + + if (dip) + { + err_minor ("been here, done that"); + if (status) *status = s; + return dip; + } + + dip = t_calloc (1, dip_type); + if (!dip) + { + if (status) *status = SANE_STATUS_NO_MEM; + return dip; + } + + dip->plugin = ipc_exec ("esdip", pkglibdir, status); + + if (dip->plugin) + { + dip->autocrop = esdip_crop; + dip->deskew = esdip_turn; + } + else if (ENABLE_SANEI_MAGIC) /* use free alternative API */ + { + sanei_magic_init (); + dip->autocrop = magic_crop; + dip->deskew = magic_turn; + } + + if (status) *status = s; + return dip; +} + +void * +dip_exit (void *self) +{ + log_call ("(%p)", self); + require (dip == self); + + if (dip) + { + if (dip->plugin) + { + dip->plugin = ipc_kill (dip->plugin); + } + else + { + /* sanei_magic_exit, if there was one */ + } + delete (dip); + } + + return dip; +} + +bool +dip_needs_whole_image (const void *self, const Option_Value *val, + const SANE_Option_Descriptor *opt) +{ + require (dip == self && val); + + /* esdip_deskew and esdip_autocrop do not support this + */ + if (val[OPT_X_RESOLUTION].w != val[OPT_Y_RESOLUTION].w) + return false; + + return ((SANE_OPTION_IS_ACTIVE (opt[OPT_DESKEW].cap) + && val[OPT_DESKEW].b) + || (SANE_OPTION_IS_ACTIVE (opt[OPT_AUTOCROP].cap) + && val[OPT_AUTOCROP].b)); +} + +void +dip_apply_LUT (const void *self, const buffer *buf, + const LUT *m) +{ + require (dip == self && buf && m); + require (m->depth == buf->ctx.depth); + + /**/ if (16 == buf->ctx.depth) + { + uint16_t *p = (uint16_t *) buf->ptr; + uint16_t *e = (uint16_t *) buf->end; + + while (p < e) + { + *p = * (uint16_t *) (m->lut + 2 * *p); + ++p; + } + } + else if ( 8 == buf->ctx.depth) + { + SANE_Byte *p = buf->ptr; + SANE_Byte *e = buf->end; + + while (p < e) + { + *p = m->lut[*p]; + ++p; + } + } + else + err_major ("noop: unsupported bit depth %d", buf->ctx.depth); +} + +void +dip_apply_LUT_RGB (const void *self, const buffer *buf, + const LUT *r, const LUT *g, const LUT *b) +{ + require (dip == self && buf && r && g && b); + require (r->depth == buf->ctx.depth); + require (g->depth == buf->ctx.depth); + require (b->depth == buf->ctx.depth); + + if (SANE_FRAME_RGB != buf->ctx.format) + { + err_minor ("noop: image data not in RGB format"); + return; + } + + /**/ if (16 == buf->ctx.depth) + { + uint16_t *p = (uint16_t *) buf->ptr; + uint16_t *e = (uint16_t *) buf->end; + + while (p < e) + { + p[0] = * (uint16_t *) (r->lut + 2 * p[0]); + p[1] = * (uint16_t *) (g->lut + 2 * p[1]); + p[2] = * (uint16_t *) (b->lut + 2 * p[2]); + p += 3; + } + } + else if ( 8 == buf->ctx.depth) + { + SANE_Byte *p = buf->ptr; + SANE_Byte *e = buf->end; + + while (p < e) + { + p[0] = r->lut[p[0]]; + p[1] = g->lut[p[1]]; + p[2] = b->lut[p[2]]; + p += 3; + } + } + else + err_major ("noop: unsupported bit depth %d", buf->ctx.depth); +} + +/*! \brief Destroys a LUT object + */ +LUT * +dip_destroy_LUT (const void *self, LUT *m) +{ + require (dip == self); + + if (m) delete (m->lut); + delete (m); + + return m; +} + +/*! \brief Creates a LUT with an encoding \a gamma + * + * \sa http://en.wikipedia.org/wiki/Gamma_correction + */ +LUT * +dip_gamma_LUT (const void *self, int depth, + double gamma) +{ + SANE_Byte *lut; + LUT *m; + + size_t i; + double max; + + require (dip == self); + require (8 == depth || 16 == depth); + + lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte); + m = t_malloc (1, LUT); + + if (!lut || !m) + { + delete (lut); + delete (m); + return m; + } + + m->lut = lut; + m->depth = depth; + + max = (1 << depth) - 1; + + for (i = 0; i < (1 << depth); ++i) + { + double value = max * pow (i / max, 1 / gamma); + + if (16 == depth) + { + uint16_t *p = (uint16_t *) lut; + + p[i] = clamp (value, 0, max); + } + else + { + lut[i] = clamp (value, 0, max); + } + } + + return m; +} + +LUT * +dip_iscan_BCHS_LUT (const void *self, int depth, + double brightness, double contrast, + double highlight, double shadow) +{ + SANE_Byte *lut; + LUT *m; + + size_t i; + size_t max; + + int32_t b, c, h, s, f; + + require (dip == self); + require (-1 <= brightness && brightness <= 1); + require (-1 <= contrast && contrast <= 1); + require ( 0 <= highlight && highlight <= 1); + require ( 0 <= shadow && shadow <= 1); + require (8 == depth || 16 == depth); + + lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte); + m = t_malloc (1, LUT); + if (!lut || !m) + { + delete (lut); + delete (m); + return m; + } + + m->lut = lut; + m->depth = depth; + + /* Compute algorithm parameters by scaling the double values into + * integral values that correspond to the bit depth. Computation + * of loop variable independent parts is done by absorption into + * existing variables. + */ + f = (1 << (depth - 1)) - 1; + s = f * shadow; + h = -f * highlight; h += 2 * f + 1; + b = f * brightness; + c = ((0 > contrast) + ? f * contrast + : (h - s) / 2 * contrast); + + log_data ("b = %d", b); + log_data ("c = %d", c); + log_data ("h = %d", h); + log_data ("s = %d", s); + + if (2 * c == (h - s)) --c; /* avoid zero division */ + + s += c; /* absorb scaled contrast */ + h -= c; /* absorb scaled contrast */ + h -= s; /* absorb shifted shadow */ + + log_data ("h' = %d", h); + log_data ("s' = %d", s); + + max = (1 << depth) - 1; + log_data ("max = %zd", max); + + for (i = 0; i < (1 << depth); ++i) + { + int32_t value = i; + + value -= s; + value *= max; + value /= h; + value += b; + + if (16 == depth) + { + uint16_t *p = (uint16_t *) lut; + + p[i] = clamp (value, 0, max); + } + else + { + lut[i] = clamp (value, 0, max); + } + } + return m; +} + +LUT * +dip_iscan_BC_LUT (const void *self, int depth, + double brightness, double contrast) +{ + return dip_iscan_BCHS_LUT (self, depth, brightness, contrast, 0, 0); +} + +/*! \brief Creates a LUT for brightness/contrast manipulations + * + * Algorithm indirectly taken from the GIMP. + * + * \sa http://en.wikipedia.org/wiki/Image_editing#Contrast_change_and_brightening + */ +LUT * +dip_gimp_BC_LUT (const void *self, int depth, + double brightness, double contrast) +{ + SANE_Byte *lut; + LUT *m; + + size_t i; + double max; + + require (dip == self); + require (-1 <= brightness && brightness <= 1); + require (-1 <= contrast && contrast <= 1); + require (8 == depth || 16 == depth); + + lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte); + m = t_malloc (1, LUT); + + if (!lut || !m) + { + delete (lut); + delete (m); + return m; + } + + m->lut = lut; + m->depth = depth; + + max = (1 << depth) - 1; + + for (i = 0; i < (1 << depth); ++i) + { + double value = i / max; + + if (brightness < 0.0) + value *= 1.0 + brightness; + else + value += (1 - value) * brightness; + + value = (value - 0.5) * tan ((contrast + 1) * M_PI / 4) + 0.5; + value *= max; + + if (16 == depth) + { + uint16_t *p = (uint16_t *) lut; + + p[i] = clamp (value, 0, max); + } + else + { + lut[i] = clamp (value, 0, max); + } + } + + return m; +} + +void +dip_flip_bits (const void *self, const buffer *buf) +{ + SANE_Byte *p; + + require (dip == self && buf); + + p = buf->ptr; + while (p != buf->end) + { + *p = ~*p; + ++p; + } +} + +static +void +dip_change_GRB_to_RGB_16 (const void *self, const buffer *buf) +{ + SANE_Byte *p, tmp; + + require (dip == self && buf && 16 == buf->ctx.depth); + + p = buf->ptr; + while (p < buf->end) + { + tmp = p[0]; /* most significant byte */ + p[0] = p[2]; + p[2] = p[0]; + tmp = p[1]; /* least significant byte */ + p[1] = p[3]; + p[3] = tmp; + + p += 6; + } +} + +static +void +dip_change_GRB_to_RGB_8 (const void *self, const buffer *buf) +{ + SANE_Byte *p, tmp; + + require (dip == self && buf && 8 == buf->ctx.depth); + + p = buf->ptr; + while (p < buf->end) + { + tmp = p[0]; + p[0] = p[1]; + p[1] = tmp; + + p += 3; + } +} + +void +dip_change_GRB_to_RGB (const void *self, const buffer *buf) +{ + require (dip == self && buf); + + if (SANE_FRAME_RGB != buf->ctx.format) + return; + + /**/ if (16 == buf->ctx.depth) + return dip_change_GRB_to_RGB_16 (self, buf); + else if ( 8 == buf->ctx.depth) + return dip_change_GRB_to_RGB_8 (self, buf); + + err_major ("unsupported bit depth"); + return; +} + +/*! \todo Add support for 16 bit color values (#816). + */ +void +dip_apply_color_profile (const void *self, const buffer *buf, + const double profile[9]) +{ + SANE_Int i; + SANE_Byte *r_buf, *g_buf, *b_buf; + double red, grn, blu; + + SANE_Byte *data; + SANE_Int size; + + require (dip == self && buf && profile); + require (8 == buf->ctx.depth); + + if (SANE_FRAME_RGB != buf->ctx.format) + return; + + data = buf->ptr; + size = buf->end - buf->ptr; + + for (i = 0; i < size / 3; i++) + { + r_buf = data; + g_buf = data + 1; + b_buf = data + 2; + + red = + profile[0] * (*r_buf) + profile[1] * (*g_buf) + profile[2] * (*b_buf); + grn = + profile[3] * (*r_buf) + profile[4] * (*g_buf) + profile[5] * (*b_buf); + blu = + profile[6] * (*r_buf) + profile[7] * (*g_buf) + profile[8] * (*b_buf); + + *data++ = clamp (red, 0, 255); + *data++ = clamp (grn, 0, 255); + *data++ = clamp (blu, 0, 255); + } +} + +bool +dip_has_deskew (const void *self, const device *hw) +{ + require (dip == self); + + return (magic_turn == dip->deskew + || (esdip_turn == dip->deskew + && enable_dip_deskew (hw))); +} + +static +void +esdip_turn (buffer *buf, int res_x, int res_y) +{ + ipc_dip_parms p; + + require (dip->plugin); + + memset (&p, 0, sizeof (p)); + memcpy (&p.parms, &buf->ctx, sizeof (p.parms)); + p.res_x = res_x; + p.res_y = res_y; + + ipc_dip_proc (dip->plugin, TYPE_DIP_SKEW_FLAG, &p, + &buf->ctx, (void **) &buf->buf); + + buf->cap = buf->ctx.bytes_per_line * buf->ctx.lines; + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +static +void +magic_turn (buffer *buf, int res_x, int res_y) +{ + SANE_Status status; + int center_x, center_y; + double angle; + const int bg_sample = 0xff; /* white */ + + require (buf); + + status = sanei_magic_findSkew (&buf->ctx, buf->buf, res_x, res_y, + ¢er_x, ¢er_y, &angle); + if (SANE_STATUS_GOOD == status) + { + status = sanei_magic_rotate (&buf->ctx, buf->buf, + center_x, center_y, -angle, bg_sample); + } + + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +void +dip_deskew (const void *self, const device *hw, unsigned int count, + buffer *buf, const Option_Value *val) +{ + require (dip == self && buf && val); + + dip->deskew (buf, val[OPT_X_RESOLUTION].w, val[OPT_Y_RESOLUTION].w); +} + +/*! \bug autocrop_max_y() is the \e wrong criterion to limit autocrop + * support to selected devices. + */ +bool +dip_has_autocrop (const void *self, const device *hw) +{ + require (dip == self); + + return (magic_crop == dip->autocrop + || (esdip_crop == dip->autocrop + && 0 != autocrop_max_y (hw))); +} + +static +void +esdip_crop (buffer *buf, const device *hw, unsigned int count, + const Option_Value *val) +{ + ipc_dip_parms p; + + require (dip->plugin && hw && hw->fw_name && val); + + memset (&p, 0, sizeof (p)); + memcpy (&p.parms, &buf->ctx, sizeof (p.parms)); + p.res_x = val[OPT_X_RESOLUTION].w; + p.res_y = val[OPT_Y_RESOLUTION].w; + p.gamma = hw->gamma_type[ val[OPT_GAMMA_CORRECTION].w ]; + p.bside = SANE_FALSE; + if (using (hw, adf) && val[OPT_ADF_MODE].w) + { + p.bside = (0 == count % 2); + } + strncpy (p.fw_name, hw->fw_name, num_of (p.fw_name)); + + ipc_dip_proc (dip->plugin, TYPE_DIP_CROP_FLAG, &p, + &buf->ctx, (void **) &buf->buf); + + buf->cap = buf->ctx.bytes_per_line * buf->ctx.lines; + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +static +void +magic_crop (buffer *buf, int res_x, int res_y) +{ + SANE_Status status; + int top, left, bottom, right; + + require (buf); + + status = sanei_magic_findEdges (&buf->ctx, buf->buf, res_x, res_y, + &top, &bottom, &left, &right); + if (SANE_STATUS_GOOD == status) + { + status = sanei_magic_crop (&buf->ctx, buf->buf, + top, bottom, left, right); + } + + buf->ptr = buf->buf; + buf->end = buf->ptr; + buf->end += buf->ctx.bytes_per_line * buf->ctx.lines; +} + +void +dip_autocrop (const void *self, const device *hw, unsigned int count, + buffer *buf, const Option_Value *val) +{ + require (dip == self && buf && val); + + /**/ if (esdip_crop == dip->autocrop) + { + esdip_crop (buf, hw, count, val); + } + else if (magic_crop == dip->autocrop) + { + int res_x = val[OPT_X_RESOLUTION].w; + int res_y = val[OPT_Y_RESOLUTION].w; + + magic_crop (buf, res_x, res_y); + } +} |