/* 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 . * * * 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 #endif #include #include #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); } }