diff options
Diffstat (limited to 'src/preview.c')
-rw-r--r-- | src/preview.c | 1536 |
1 files changed, 1536 insertions, 0 deletions
diff --git a/src/preview.c b/src/preview.c new file mode 100644 index 0000000..6fb21c0 --- /dev/null +++ b/src/preview.c @@ -0,0 +1,1536 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 1997 David Mosberger-Tang and Tristan Tarrant + This file is part of the SANE package. + + SANE 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. + + SANE 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 should have received a copy of the GNU General Public License + along with sane; see the file COPYING. If not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* + The preview strategy is as follows: + + 1) A preview always acquires an image that covers the entire + scan surface. This is necessary so the user can see not + only what is, but also what isn't selected. + + 2) The preview must be zoomable so the user can precisely pick + the selection area even for small scans on a large scan + surface. The user also should have the option of resizing + the preview window. + + 3) We let the user/backend pick whether a preview is in color, + grayscale, lineart or what not. The only options that the + preview may (temporarily) modify are: + + - resolution (set so the preview fills the window) + - scan area options (top-left corner, bottom-right corner) + - preview option (to let the backend know we're doing a preview) + + 4) The size of the scan surface is determined based on the constraints + of the four corner coordinates. Missing constraints are replaced + by +/-INF as appropriate (-INF form top-left, +INF for bottom-right + coords). + + 5) The size of the preview window is determined based on the scan + surface size: + + If the surface area is specified in pixels and if that size + fits on the screen, we use that size. In all other cases, + we make the window of a size so that neither the width nor + the height is bigger than some fraction of the screen-size + while preserving the aspect ratio (a surface width or height + of INF implies an aspect ratio of 1). + + 6) Given the preview window size and the scan surface size, we + select the resolution so the acquired preview image just fits + in the preview window. The resulting resolution may be out + of range in which case we pick the minum/maximum if there is + a range or word-list constraint or a default value if there is + no such constraint. + + 7) Once a preview image has been acquired, we know the size of the + preview image (in pixels). An initial scale factor is chosen + so the image fits into the preview window. + + */ + +#include "../include/sane/config.h" + +#include <assert.h> +#include <math.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <sys/param.h> + +#include "gtkglue.h" +#include "preview.h" +#include "preferences.h" + +#define DBG_fatal 0 +#define DBG_error 1 +#define DBG_warning 2 +#define DBG_info 3 +#define DBG_debug 4 + +#define BACKEND_NAME preview +#include "../include/sane/sanei_debug.h" + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + +/* Anything bigger than 2G will do, since SANE coordinates are 32 bit + values. */ +#define INF 5.0e9 + +#define MM_PER_INCH 25.4 + +/* Cut fp conversion routines some slack: */ +#define GROSSLY_DIFFERENT(f1,f2) (fabs ((f1) - (f2)) > 1e-3) + +#ifdef __alpha__ + /* This seems to be necessary for at least some XFree86 3.1.2 + servers. It's known to be necessary for the XF86_TGA server for + Linux/Alpha. Fortunately, it's no great loss so we turn this on + by default for now. */ +# define XSERVER_WITH_BUGGY_VISUALS +#endif + +/* forward declarations */ +static void scan_start (Preview * p); +static void scan_done (Preview * p); + +static void +draw_rect (GdkWindow * win, GdkGC * gc, int coord[4]) +{ + gint x, y, w, h; + + x = coord[0]; + y = coord[1]; + w = coord[2] - x; + h = coord[3] - y; + if (w < 0) + { + x = coord[2]; + w = -w; + } + if (h < 0) + { + y = coord[3]; + h = -h; + } + gdk_draw_rectangle (win, gc, FALSE, x, y, w + 1, h + 1); +} + +static void +draw_selection (Preview * p) +{ + if (!p->gc) + /* window isn't mapped yet */ + return; + + if (p->previous_selection.active) + draw_rect (p->window->window, p->gc, p->previous_selection.coord); + + if (p->selection.active) + draw_rect (p->window->window, p->gc, p->selection.coord); + + p->previous_selection = p->selection; +} + +static void +update_selection (Preview * p) +{ + float min, max, normal, dev_selection[4]; + const SANE_Option_Descriptor *opt; + SANE_Status status; + SANE_Word val; + int i, optnum; + + p->previous_selection = p->selection; + + memcpy (dev_selection, p->surface, sizeof (dev_selection)); + for (i = 0; i < 4; ++i) + { + optnum = p->dialog->well_known.coord[i]; + if (optnum > 0) + { + opt = sane_get_option_descriptor (p->dialog->dev, optnum); + status = sane_control_option (p->dialog->dev, optnum, + SANE_ACTION_GET_VALUE, &val, 0); + if (status != SANE_STATUS_GOOD) + continue; + if (opt->type == SANE_TYPE_FIXED) + dev_selection[i] = SANE_UNFIX (val); + else + dev_selection[i] = val; + } + } + for (i = 0; i < 2; ++i) + { + min = p->surface[i]; + if (min <= -INF) + min = 0.0; + max = p->surface[i + 2]; + if (max >= INF) + max = p->preview_width; + + normal = ((i == 0) ? p->preview_width : p->preview_height) - 1; + normal /= (max - min); + p->selection.active = TRUE; + p->selection.coord[i] = ((dev_selection[i] - min) * normal) + 0.5; + p->selection.coord[i + 2] = + ((dev_selection[i + 2] - min) * normal) + 0.5; + if (p->selection.coord[i + 2] < p->selection.coord[i]) + p->selection.coord[i + 2] = p->selection.coord[i]; + } + draw_selection (p); +} + +static void +get_image_scale (Preview * p, float *xscalep, float *yscalep) +{ + float xscale, yscale; + + if (p->image_width == 0) + xscale = 1.0; + else + { + xscale = p->image_width / (float) p->preview_width; + if (p->image_height > 0 && p->preview_height * xscale < p->image_height) + xscale = p->image_height / (float) p->preview_height; + } + yscale = xscale; + + if (p->surface_unit == SANE_UNIT_PIXEL + && p->image_width <= p->preview_width + && p->image_height <= p->preview_height) + { + float swidth, sheight; + + assert (p->surface_type == SANE_TYPE_INT); + swidth = (p->surface[GSG_BR_X] - p->surface[GSG_TL_X] + 1); + sheight = (p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y] + 1); + xscale = 1.0; + yscale = 1.0; + if (p->image_width > 0 && swidth < INF) + xscale = p->image_width / swidth; + if (p->image_height > 0 && sheight < INF) + yscale = p->image_height / sheight; + } + *xscalep = xscale; + *yscalep = yscale; +} + +static void +paint_image (Preview * p) +{ + float xscale, yscale, src_x, src_y; + int dst_x, dst_y, height, x, y, src_offset; + gint gwidth, gheight; + + gwidth = p->preview_width; + gheight = p->preview_height; + + get_image_scale (p, &xscale, &yscale); + + memset (p->preview_row, 0xff, 3 * gwidth); + + /* don't draw last line unless it's complete: */ + height = p->image_y; + if (p->image_x == 0 && height < p->image_height) + ++height; + + /* for now, use simple nearest-neighbor interpolation: */ + src_offset = 0; + src_x = src_y = 0.0; + for (dst_y = 0; dst_y < gheight; ++dst_y) + { + y = (int) (src_y + 0.5); + if (y >= height) + break; + src_offset = y * 3 * p->image_width; + + if (p->image_data) + for (dst_x = 0; dst_x < gwidth; ++dst_x) + { + x = (int) (src_x + 0.5); + if (x >= p->image_width) + break; + + p->preview_row[3 * dst_x + 0] = + p->image_data[src_offset + 3 * x + 0]; + p->preview_row[3 * dst_x + 1] = + p->image_data[src_offset + 3 * x + 1]; + p->preview_row[3 * dst_x + 2] = + p->image_data[src_offset + 3 * x + 2]; + src_x += xscale; + } + gtk_preview_draw_row (GTK_PREVIEW (p->window), p->preview_row, + 0, dst_y, gwidth); + src_x = 0.0; + src_y += yscale; + } +} + +static void +display_partial_image (Preview * p) +{ + paint_image (p); + + if (GTK_WIDGET_DRAWABLE (p->window)) + { + GtkPreview *preview = GTK_PREVIEW (p->window); + int src_x, src_y; + + src_x = (p->window->allocation.width - preview->buffer_width) / 2; + src_y = (p->window->allocation.height - preview->buffer_height) / 2; + gtk_preview_put (preview, p->window->window, p->window->style->black_gc, + src_x, src_y, + 0, 0, p->preview_width, p->preview_height); + } +} + +static void +display_maybe (Preview * p) +{ + time_t now; + + time (&now); + if (now > p->image_last_time_updated) + { + p->image_last_time_updated = now; + display_partial_image (p); + } +} + +static void +display_image (Preview * p) +{ + if (p->params.lines <= 0 && p->image_y < p->image_height) + { + p->image_height = p->image_y; + p->image_data = realloc (p->image_data, + 3 * p->image_width * p->image_height); + assert (p->image_data); + } + display_partial_image (p); + scan_done (p); +} + +static void +preview_area_resize (GtkWidget * widget) +{ + float min_x, max_x, min_y, max_y, xscale, yscale, f; + Preview *p; + + p = gtk_object_get_data (GTK_OBJECT (widget), "PreviewPointer"); + + p->preview_width = widget->allocation.width; + p->preview_height = widget->allocation.height; + + if (p->preview_row) + p->preview_row = realloc (p->preview_row, 3 * p->preview_width); + else + p->preview_row = malloc (3 * p->preview_width); + + /* set the ruler ranges: */ + + min_x = p->surface[GSG_TL_X]; + if (min_x <= -INF) + min_x = 0.0; + + max_x = p->surface[GSG_BR_X]; + if (max_x >= INF) + max_x = p->preview_width - 1; + + min_y = p->surface[GSG_TL_Y]; + if (min_y <= -INF) + min_y = 0.0; + + max_y = p->surface[GSG_BR_Y]; + if (max_y >= INF) + max_y = p->preview_height - 1; + + /* convert mm to inches if that's what the user wants: */ + + if (p->surface_unit == SANE_UNIT_MM) + { + double factor = 1.0 / preferences.length_unit; + min_x *= factor; + max_x *= factor; + min_y *= factor; + max_y *= factor; + } + + get_image_scale (p, &xscale, &yscale); + + if (p->surface_unit == SANE_UNIT_PIXEL) + f = 1.0 / xscale; + else if (p->image_width) + f = xscale * p->preview_width / p->image_width; + else + f = 1.0; + gtk_ruler_set_range (GTK_RULER (p->hruler), f * min_x, f * max_x, f * min_x, + /* max_size */ 20); + + if (p->surface_unit == SANE_UNIT_PIXEL) + f = 1.0 / yscale; + else if (p->image_height) + f = yscale * p->preview_height / p->image_height; + else + f = 1.0; + gtk_ruler_set_range (GTK_RULER (p->vruler), f * min_y, f * max_y, f * min_y, + /* max_size */ 20); + + paint_image (p); + update_selection (p); +} + +static void +get_bounds (const SANE_Option_Descriptor * opt, float *minp, float *maxp) +{ + float min, max; + int i; + + min = -INF; + max = INF; + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_RANGE: + min = opt->constraint.range->min; + max = opt->constraint.range->max; + break; + + case SANE_CONSTRAINT_WORD_LIST: + min = INF; + max = -INF; + for (i = 1; i <= opt->constraint.word_list[0]; ++i) + { + if (opt->constraint.word_list[i] < min) + min = opt->constraint.word_list[i]; + if (opt->constraint.word_list[i] > max) + max = opt->constraint.word_list[i]; + } + break; + + default: + break; + } + if (opt->type == SANE_TYPE_FIXED) + { + if (min > -INF && min < INF) + min = SANE_UNFIX (min); + if (max > -INF && max < INF) + max = SANE_UNFIX (max); + } + *minp = min; + *maxp = max; +} + +static void +save_option (Preview * p, int option, SANE_Word * save_loc, int *valid) +{ + SANE_Status status; + + if (option <= 0) + { + *valid = 0; + return; + } + status = sane_control_option (p->dialog->dev, option, SANE_ACTION_GET_VALUE, + save_loc, 0); + *valid = (status == SANE_STATUS_GOOD); +} + +static void +restore_option (Preview * p, int option, SANE_Word saved_value, int valid) +{ + const SANE_Option_Descriptor *opt; + SANE_Status status; + SANE_Handle dev; + + if (!valid) + return; + + dev = p->dialog->dev; + status = sane_control_option (dev, option, SANE_ACTION_SET_VALUE, + &saved_value, 0); + if (status != SANE_STATUS_GOOD) + { + char buf[256]; + opt = sane_get_option_descriptor (dev, option); + snprintf (buf, sizeof (buf), "Failed restore value of option %s: %s.", + opt->name, sane_strstatus (status)); + gsg_error (buf); + } +} + +static SANE_Status +set_option_float (Preview * p, int option, float value) +{ + const SANE_Option_Descriptor *opt; + SANE_Handle dev; + SANE_Word word; + SANE_Status status; + + if (option <= 0 || value <= -INF || value >= INF) + return SANE_STATUS_INVAL; + + dev = p->dialog->dev; + opt = sane_get_option_descriptor (dev, option); + if (opt->type == SANE_TYPE_FIXED) + word = SANE_FIX (value) + 0.5; + else + word = value + 0.5; + status = sane_control_option (dev, option, SANE_ACTION_SET_VALUE, &word, 0); + if (status != SANE_STATUS_GOOD) + { + char buf[256]; + opt = sane_get_option_descriptor (dev, option); + snprintf (buf, sizeof (buf), "Failed to set option %s: %s.", + opt->name, sane_strstatus (status)); + gsg_error (buf); + return status; + } + return SANE_STATUS_GOOD; +} + +static void +set_option_bool (Preview * p, int option, SANE_Bool value) +{ + SANE_Handle dev; + SANE_Status status; + + if (option <= 0) + return; + + dev = p->dialog->dev; + status = + sane_control_option (dev, option, SANE_ACTION_SET_VALUE, &value, 0); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "set_option_bool: sane_control_option failed: %s\n", + sane_strstatus (status)); + return; + } +} + +static int +increment_image_y (Preview * p) +{ + size_t extra_size, offset; + char buf[256]; + + p->image_x = 0; + ++p->image_y; + if (p->params.lines <= 0 && p->image_y >= p->image_height) + { + offset = 3 * p->image_width * p->image_height; + extra_size = 3 * 32 * p->image_width; + p->image_height += 32; + p->image_data = realloc (p->image_data, offset + extra_size); + if (!p->image_data) + { + snprintf (buf, sizeof (buf), + "Failed to reallocate image memory: %s.", + strerror (errno)); + gsg_error (buf); + scan_done (p); + return -1; + } + memset (p->image_data + offset, 0xff, extra_size); + } + return 0; +} + +static void +input_available (gpointer data, gint source, GdkInputCondition cond) +{ + SANE_Status status; + Preview *p = data; + u_char buf[8192]; + SANE_Handle dev; + SANE_Int len; + int i, j; + + DBG_INIT (); + + DBG (DBG_debug, "input_available: enter\n"); + + dev = p->dialog->dev; + while (1) + { + status = sane_read (dev, buf, sizeof (buf), &len); + if (status != SANE_STATUS_GOOD) + { + if (status == SANE_STATUS_EOF) + { + if (p->params.last_frame) + display_image (p); + else + { + if (p->input_tag < 0) + { + display_maybe (p); + while (gtk_events_pending ()) + gtk_main_iteration (); + } + else + { + gdk_input_remove (p->input_tag); + p->input_tag = -1; + } + scan_start (p); + break; + } + } + else + { + snprintf (buf, sizeof (buf), "Error during read: %s.", + sane_strstatus (status)); + gsg_error (buf); + } + scan_done (p); + return; + } + if (!len) /* out of data for now */ + { + if (p->input_tag < 0) + { + display_maybe (p); + while (gtk_events_pending ()) + gtk_main_iteration (); + continue; + } + else + break; + } + + switch (p->params.format) + { + case SANE_FRAME_RGB: + switch (p->params.depth) + { + case 1: + for (i = 0; i < len; ++i) + { + u_char mask = buf[i]; + + for (j = 7; j >= 0; --j) + { + u_char gl = (mask & (1 << j)) ? 0xff : 0x00; + + p->image_data[p->image_offset] = gl; + if (j != 0) + p->image_offset += 3; + else + { + if ((p->image_offset % 3) != 2) + p->image_offset -= 20; + else + p->image_offset++; + } + if (p->image_offset % 3 == 0) + { + if (++p->image_x >= p->image_width) + { + if (increment_image_y (p) < 0) + return; + } + } + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + p->image_data[p->image_offset++] = buf[i]; + if (p->image_offset % 3 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + case 16: + for (i = 0; i < len; ++i) + { + guint16 value = buf[i]; + if ((i % 2) == 1) + p->image_data[p->image_offset++] = *(guint8 *) (&value); + if (p->image_offset % 6 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + default: + goto bad_depth; + } + break; + + case SANE_FRAME_GRAY: + switch (p->params.depth) + { + case 1: + for (i = 0; i < len; ++i) + { + u_char mask = buf[i]; + + for (j = 7; j >= 0; --j) + { + u_char gl = (mask & (1 << j)) ? 0x00 : 0xff; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + if (++p->image_x >= p->image_width) + { + if (increment_image_y (p) < 0) + return; + break; /* skip padding bits */ + } + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + u_char gl = buf[i]; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + break; + case 16: + for (i = 0; i < len; ++i) + { + guint16 value = buf[i]; + if ((i % 2) == 1) + { + p->image_data[p->image_offset++] = *(guint8 *) (&value); + p->image_data[p->image_offset++] = *(guint8 *) (&value); + p->image_data[p->image_offset++] = *(guint8 *) (&value); + } + if (p->image_offset % 2 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + + default: + goto bad_depth; + } + break; + + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + switch (p->params.depth) + { + case 1: + for (i = 0; i < len; ++i) + { + u_char mask = buf[i]; + + for (j = 7; j >= 0; --j) + { + u_char gl = (mask & (1 << j)) ? 0xff : 0x00; + p->image_data[p->image_offset] = gl; + p->image_offset += 3; + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + p->image_data[p->image_offset] = buf[i]; + p->image_offset += 3; + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + break; + + case 16: + for (i = 0; i < len; ++i) + { + guint16 value = buf[i]; + if ((i % 2) == 1) + { + p->image_data[p->image_offset] = *(guint8 *) (&value); + p->image_offset += 3; + } + if (p->image_offset % 2 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + + default: + goto bad_depth; + } + break; + + default: + fprintf (stderr, "preview.input_available: bad frame format %d\n", + p->params.format); + scan_done (p); + return; + } + if (p->input_tag < 0) + { + display_maybe (p); + while (gtk_events_pending ()) + gtk_main_iteration (); + } + } + display_maybe (p); + return; + +bad_depth: + snprintf (buf, sizeof (buf), "Preview cannot handle depth %d.", + p->params.depth); + gsg_error (buf); + scan_done (p); + return; +} + +static void +scan_done (Preview * p) +{ + int i; + + p->scanning = FALSE; + if (p->input_tag >= 0) + { + gdk_input_remove (p->input_tag); + p->input_tag = -1; + } + sane_cancel (p->dialog->dev); + + restore_option (p, p->dialog->well_known.dpi, + p->saved_dpi, p->saved_dpi_valid); + for (i = 0; i < 4; ++i) + restore_option (p, p->dialog->well_known.coord[i], + p->saved_coord[i], p->saved_coord_valid[i]); + set_option_bool (p, p->dialog->well_known.preview, SANE_FALSE); + + gtk_widget_set_sensitive (p->cancel, FALSE); + gtk_widget_set_sensitive (p->preview, TRUE); + gsg_set_sensitivity (p->dialog, TRUE); + gtk_widget_set_sensitive (p->dialog->window->parent->parent->parent, TRUE); +} + +static void +scan_start (Preview * p) +{ + SANE_Handle dev = p->dialog->dev; + SANE_Status status; + char buf[256]; + int fd, y; + + gtk_widget_set_sensitive (p->cancel, TRUE); + gtk_widget_set_sensitive (p->preview, FALSE); + gsg_set_sensitivity (p->dialog, FALSE); + gtk_widget_set_sensitive (p->dialog->window->parent->parent->parent, FALSE); + + /* clear old preview: */ + memset (p->preview_row, 0xff, 3 * p->preview_width); + for (y = 0; y < p->preview_height; ++y) + gtk_preview_draw_row (GTK_PREVIEW (p->window), p->preview_row, + 0, y, p->preview_width); + + if (p->input_tag >= 0) + { + gdk_input_remove (p->input_tag); + p->input_tag = -1; + } + + gsg_sync (p->dialog); + + status = sane_start (dev); + if (status != SANE_STATUS_GOOD) + { + snprintf (buf, sizeof (buf), + "Failed to start scanner: %s.", sane_strstatus (status)); + gsg_error (buf); + scan_done (p); + return; + } + + status = sane_get_parameters (dev, &p->params); + if (status != SANE_STATUS_GOOD) + { + snprintf (buf, sizeof (buf), + "Failed to obtain parameters: %s.", sane_strstatus (status)); + gsg_error (buf); + scan_done (p); + return; + } + + if ((p->params.format >= SANE_FRAME_RGB && + p->params.format <= SANE_FRAME_BLUE) && + p->params.depth == 1 && p->params.pixels_per_line % 8 != 0) + { + snprintf (buf, sizeof (buf), + "Can't handle unaligned 1 bit RGB or three-pass mode."); + gsg_error (buf); + scan_done (p); + return; + } + + p->image_offset = p->image_x = p->image_y = 0; + + if (p->params.format >= SANE_FRAME_RED + && p->params.format <= SANE_FRAME_BLUE) + p->image_offset = p->params.format - SANE_FRAME_RED; + + if (!p->image_data || p->params.pixels_per_line != p->image_width + || (p->params.lines >= 0 && p->params.lines != p->image_height)) + { + /* image size changed */ + if (p->image_data) + free (p->image_data); + + p->image_width = p->params.pixels_per_line; + p->image_height = p->params.lines; + if (p->image_height < 0) + p->image_height = 32; /* may have to adjust as we go... */ + + p->image_data = malloc (p->image_width * p->image_height * 3); + if (!p->image_data) + { + snprintf (buf, sizeof (buf), + "Failed to allocate image memory: %s.", strerror (errno)); + gsg_error (buf); + scan_done (p); + return; + } + memset (p->image_data, 0xff, 3 * p->image_width * p->image_height); + } + + if (p->selection.active) + { + p->previous_selection = p->selection; + p->selection.active = FALSE; + draw_selection (p); + } + p->scanning = TRUE; + + if (sane_set_io_mode (dev, SANE_TRUE) == SANE_STATUS_GOOD + && sane_get_select_fd (dev, &fd) == SANE_STATUS_GOOD) + p->input_tag = + gdk_input_add (fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, + input_available, p); + else + input_available (p, -1, GDK_INPUT_READ); +} + +static void +establish_selection (Preview * p) +{ + float min, max, normal, dev_selection[4]; + int i; + + memcpy (dev_selection, p->surface, sizeof (dev_selection)); + if (p->selection.active) + for (i = 0; i < 2; ++i) + { + min = p->surface[i]; + if (min <= -INF) + min = 0.0; + max = p->surface[i + 2]; + if (max >= INF) + max = p->preview_width; + + normal = + 1.0 / (((i == 0) ? p->preview_width : p->preview_height) - 1); + normal *= (max - min); + dev_selection[i] = p->selection.coord[i] * normal + min; + dev_selection[i + 2] = p->selection.coord[i + 2] * normal + min; + } + for (i = 0; i < 4; ++i) + set_option_float (p, p->dialog->well_known.coord[i], dev_selection[i]); + gsg_update_scan_window (p->dialog); + if (p->dialog->param_change_callback) + (*p->dialog->param_change_callback) (p->dialog, + p->dialog->param_change_arg); +} + +static int +make_preview_image_path (Preview * p, size_t filename_size, char *filename) +{ + return gsg_make_path (filename_size, filename, 0, "preview-", + p->dialog->dev_name, ".ppm"); +} + +static void +restore_preview_image (Preview * p) +{ + u_int psurface_type, psurface_unit; + char filename[PATH_MAX]; + int width, height; + float psurface[4]; + size_t nread; + FILE *in; + + /* See whether there is a saved preview and load it if present: */ + + if (make_preview_image_path (p, sizeof (filename), filename) < 0) + return; + + in = fopen (filename, "r"); + if (!in) + return; + + /* Be careful about consuming too many bytes after the final newline + (e.g., consider an image whose first image byte is 13 (`\r'). */ + if (fscanf (in, "P6\n# surface: %g %g %g %g %u %u\n%d %d\n255%*[\n]", + psurface + 0, psurface + 1, psurface + 2, psurface + 3, + &psurface_type, &psurface_unit, &width, &height) != 8) + return; + + if (GROSSLY_DIFFERENT (psurface[0], p->surface[0]) + || GROSSLY_DIFFERENT (psurface[1], p->surface[1]) + || GROSSLY_DIFFERENT (psurface[2], p->surface[2]) + || GROSSLY_DIFFERENT (psurface[3], p->surface[3]) + || psurface_type != p->surface_type || psurface_unit != p->surface_unit) + /* ignore preview image that was acquired for/with a different surface */ + return; + + p->image_width = width; + p->image_height = height; + if ((width == 0) || (height == 0)) + return; + p->image_data = malloc (3 * width * height); + if (!p->image_data) + return; + + nread = fread (p->image_data, 3, width * height, in); + + p->image_y = nread / width; + p->image_x = nread % width; +} + +/* This is executed _after_ the gtkpreview's expose routine. */ +static gint +expose_handler (GtkWidget * window, GdkEvent * event, gpointer data) +{ + Preview *p = data; + + p->selection.active = FALSE; + update_selection (p); + return FALSE; +} + +static gint +event_handler (GtkWidget * window, GdkEvent * event, gpointer data) +{ + Preview *p = data; + int i, tmp; + + if (event->type == GDK_EXPOSE) + { + if (!p->gc) + { + p->gc = gdk_gc_new (p->window->window); + gdk_gc_set_function (p->gc, GDK_INVERT); + gdk_gc_set_line_attributes (p->gc, 1, GDK_LINE_ON_OFF_DASH, + GDK_CAP_BUTT, GDK_JOIN_MITER); + paint_image (p); + } + else + { + p->selection.active = FALSE; + draw_selection (p); + } + } + else if (!p->scanning) + switch (event->type) + { + case GDK_UNMAP: + case GDK_MAP: + break; + + case GDK_BUTTON_PRESS: + p->selection.coord[0] = event->button.x; + p->selection.coord[1] = event->button.y; + p->selection_drag = TRUE; + break; + + case GDK_BUTTON_RELEASE: + if (!p->selection_drag) + break; + p->selection_drag = FALSE; + + p->selection.coord[2] = event->button.x; + p->selection.coord[3] = event->button.y; + p->selection.active = + (p->selection.coord[0] != p->selection.coord[2] + || p->selection.coord[1] != p->selection.coord[3]); + + if (p->selection.active) + { + for (i = 0; i < 2; i += 1) + if (p->selection.coord[i] > p->selection.coord[i + 2]) + { + tmp = p->selection.coord[i]; + p->selection.coord[i] = p->selection.coord[i + 2]; + p->selection.coord[i + 2] = tmp; + } + if (p->selection.coord[0] < 0) + p->selection.coord[0] = 0; + if (p->selection.coord[1] < 0) + p->selection.coord[1] = 0; + if (p->selection.coord[2] >= p->preview_width) + p->selection.coord[2] = p->preview_width - 1; + if (p->selection.coord[3] >= p->preview_height) + p->selection.coord[3] = p->preview_height - 1; + } + draw_selection (p); + establish_selection (p); + break; + + case GDK_MOTION_NOTIFY: + if (p->selection_drag) + { + p->selection.active = TRUE; + p->selection.coord[2] = event->motion.x; + p->selection.coord[3] = event->motion.y; + draw_selection (p); + } + break; + + default: +#if 0 + fprintf (stderr, "event_handler: unhandled event type %d\n", + event->type); +#endif + break; + } + return FALSE; +} + +static void +start_button_clicked (GtkWidget * widget, gpointer data) +{ + preview_scan (data); +} + +static void +cancel_button_clicked (GtkWidget * widget, gpointer data) +{ + scan_done (data); +} + +static void +top_destroyed (GtkWidget * widget, gpointer call_data) +{ + Preview *p = call_data; + + p->top = NULL; +} + +Preview * +preview_new (GSGDialog * dialog) +{ + static int first_time = 1; + GtkWidget *table, *frame; + GtkSignalFunc signal_func; + GtkWidgetClass *class; + GtkBox *vbox, *hbox; + Preview *p; + + p = malloc (sizeof (*p)); + if (!p) + return 0; + memset (p, 0, sizeof (*p)); + + p->dialog = dialog; + p->input_tag = -1; + + if (first_time) + { + first_time = 0; + gtk_preview_set_gamma (preferences.preview_gamma); + gtk_preview_set_install_cmap (preferences.preview_own_cmap); + } + +#ifndef XSERVER_WITH_BUGGY_VISUALS + gtk_widget_push_visual (gtk_preview_get_visual ()); +#endif + gtk_widget_push_colormap (gtk_preview_get_cmap ()); + + p->top = gtk_dialog_new (); + gtk_signal_connect (GTK_OBJECT (p->top), "destroy", + GTK_SIGNAL_FUNC (top_destroyed), p); + gtk_window_set_title (GTK_WINDOW (p->top), "xscanimage preview"); + vbox = GTK_BOX (GTK_DIALOG (p->top)->vbox); + hbox = GTK_BOX (GTK_DIALOG (p->top)->action_area); + + /* construct the preview area (table with sliders & preview window) */ + table = gtk_table_new (2, 2, /* homogeneous */ FALSE); + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 1); + gtk_container_border_width (GTK_CONTAINER (table), 2); + gtk_box_pack_start (vbox, table, /* expand */ TRUE, /* fill */ TRUE, + /* padding */ 0); + + /* the empty box in the top-left corner */ + frame = gtk_frame_new ( /* label */ 0); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_table_attach (GTK_TABLE (table), frame, 0, 1, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + + /* the horizontal ruler */ + p->hruler = gtk_hruler_new (); + gtk_table_attach (GTK_TABLE (table), p->hruler, 1, 2, 0, 1, + GTK_FILL, 0, 0, 0); + + /* the vertical ruler */ + p->vruler = gtk_vruler_new (); + gtk_table_attach (GTK_TABLE (table), p->vruler, 0, 1, 1, 2, 0, + GTK_FILL, 0, 0); + + /* the preview area */ + + p->window = gtk_preview_new (GTK_PREVIEW_COLOR); + gtk_preview_set_expand (GTK_PREVIEW (p->window), TRUE); + gtk_widget_set_events (p->window, + GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + gtk_signal_connect (GTK_OBJECT (p->window), "event", + (GtkSignalFunc) event_handler, p); + gtk_signal_connect_after (GTK_OBJECT (p->window), "expose_event", + (GtkSignalFunc) expose_handler, p); + gtk_signal_connect_after (GTK_OBJECT (p->window), "size_allocate", + (GtkSignalFunc) preview_area_resize, 0); + gtk_object_set_data (GTK_OBJECT (p->window), "PreviewPointer", p); + + /* Connect the motion-notify events of the preview area with the + rulers. Nifty stuff! */ + +#if GTK_MAJOR_VERSION == 2 + class = GTK_WIDGET_CLASS (GTK_OBJECT_GET_CLASS (GTK_OBJECT (p->hruler))); +#else + class = GTK_WIDGET_CLASS (GTK_OBJECT (p->hruler)->klass); +#endif /* GTK_MAJOR_VERSION == 2 */ + signal_func = (GtkSignalFunc) class->motion_notify_event; + gtk_signal_connect_object (GTK_OBJECT (p->window), "motion_notify_event", + signal_func, GTK_OBJECT (p->hruler)); + +#if GTK_MAJOR_VERSION == 2 + class = GTK_WIDGET_CLASS (GTK_OBJECT_GET_CLASS (GTK_OBJECT (p->vruler))); +#else + class = GTK_WIDGET_CLASS (GTK_OBJECT (p->vruler)->klass); +#endif /* GTK_MAJOR_VERSION == 2 */ + signal_func = (GtkSignalFunc) class->motion_notify_event; + gtk_signal_connect_object (GTK_OBJECT (p->window), "motion_notify_event", + signal_func, GTK_OBJECT (p->vruler)); + + p->viewport = gtk_frame_new ( /* label */ 0); + gtk_frame_set_shadow_type (GTK_FRAME (p->viewport), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (p->viewport), p->window); + + gtk_table_attach (GTK_TABLE (table), p->viewport, 1, 2, 1, 2, + GTK_FILL | GTK_EXPAND | GTK_SHRINK, + GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0); + + preview_update (p); + + /* fill in action area: */ + + /* Preview button */ + p->preview = gtk_button_new_with_label ("Acquire Preview"); + gtk_signal_connect (GTK_OBJECT (p->preview), "clicked", + (GtkSignalFunc) start_button_clicked, p); + gtk_box_pack_start (GTK_BOX (hbox), p->preview, TRUE, TRUE, 0); + gtk_widget_show (p->preview); + + /* Cancel button */ + p->cancel = gtk_button_new_with_label ("Cancel Preview"); + gtk_signal_connect (GTK_OBJECT (p->cancel), "clicked", + (GtkSignalFunc) cancel_button_clicked, p); + gtk_box_pack_start (GTK_BOX (hbox), p->cancel, TRUE, TRUE, 0); + gtk_widget_set_sensitive (p->cancel, FALSE); + + gtk_widget_show (p->cancel); + gtk_widget_show (p->viewport); + gtk_widget_show (p->window); + gtk_widget_show (p->hruler); + gtk_widget_show (p->vruler); + gtk_widget_show (frame); + gtk_widget_show (table); + gtk_widget_show (p->top); + + gtk_widget_pop_colormap (); +#ifndef XSERVER_WITH_BUGGY_VISUALS + gtk_widget_pop_visual (); +#endif + return p; +} + +void +preview_update (Preview * p) +{ + float val, width, height, max_width, max_height; + const SANE_Option_Descriptor *opt; + int i, surface_changed; + SANE_Value_Type type; + SANE_Unit unit; + float min, max; + + surface_changed = 0; + unit = SANE_UNIT_PIXEL; + type = SANE_TYPE_INT; + for (i = 0; i < 4; ++i) + { + val = (i & 2) ? INF : -INF; + if (p->dialog->well_known.coord[i] > 0) + { + opt = sane_get_option_descriptor (p->dialog->dev, + p->dialog->well_known.coord[i]); + assert (opt->unit == SANE_UNIT_PIXEL || opt->unit == SANE_UNIT_MM); + unit = opt->unit; + type = opt->type; + + get_bounds (opt, &min, &max); + if (i & 2) + val = max; + else + val = min; + } + if (p->surface[i] != val) + { + surface_changed = 1; + p->surface[i] = val; + } + } + if (p->surface_unit != unit) + { + surface_changed = 1; + p->surface_unit = unit; + } + if (p->surface_type != type) + { + surface_changed = 1; + p->surface_type = type; + } + if (surface_changed && p->image_data) + { + free (p->image_data); + p->image_data = 0; + p->image_width = 0; + p->image_height = 0; + } + + /* guess the initial preview window size: */ + + width = p->surface[GSG_BR_X] - p->surface[GSG_TL_X]; + height = p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y]; + if (p->surface_type == SANE_TYPE_INT) + { + width += 1.0; + height += 1.0; + } + else + { + width += SANE_UNFIX (1.0); + height += SANE_UNFIX (1.0); + } + + assert (width > 0.0 && height > 0.0); + + if (width >= INF || height >= INF) + p->aspect = 1.0; + else + p->aspect = width / height; + + if (surface_changed) + { + max_width = 0.5 * gdk_screen_width (); + max_height = 0.5 * gdk_screen_height (); + } + else + { + max_width = p->window->allocation.width; + max_height = p->window->allocation.height; + } + + if (p->surface_unit != SANE_UNIT_PIXEL) + { + width = max_width; + height = width / p->aspect; + + if (height > max_height) + { + height = max_height; + width = height * p->aspect; + } + } + else + { + if (width > max_width) + width = max_width; + + if (height > max_height) + height = max_height; + } + + /* re-adjust so we maintain aspect without exceeding max size: */ + if (width / height != p->aspect) + { + if (p->aspect > 1.0) + height = width / p->aspect; + else + width = height * p->aspect; + } + + p->preview_width = width + 0.5; + p->preview_height = height + 0.5; + if (surface_changed) + { + gtk_widget_set_usize (GTK_WIDGET (p->window), + p->preview_width, p->preview_height); + if (GTK_WIDGET_DRAWABLE (p->window)) + preview_area_resize (p->window); + + if (preferences.preserve_preview) + restore_preview_image (p); + } + update_selection (p); +} + +void +preview_scan (Preview * p) +{ + float min, max, swidth, sheight, width, height, dpi = 0; + const SANE_Option_Descriptor *opt; + gint gwidth, gheight; + int i; + SANE_Status status; + + + save_option (p, p->dialog->well_known.dpi, + &p->saved_dpi, &p->saved_dpi_valid); + for (i = 0; i < 4; ++i) + save_option (p, p->dialog->well_known.coord[i], + &p->saved_coord[i], p->saved_coord_valid + i); + + /* determine dpi, if necessary: */ + + if (p->dialog->well_known.dpi > 0) + { + opt = sane_get_option_descriptor (p->dialog->dev, + p->dialog->well_known.dpi); + + gwidth = p->preview_width; + gheight = p->preview_height; + + height = gheight; + width = height * p->aspect; + if (width > gwidth) + { + width = gwidth; + height = width / p->aspect; + } + + swidth = (p->surface[GSG_BR_X] - p->surface[GSG_TL_X]); + if (swidth < INF) + dpi = MM_PER_INCH * width / swidth; + else + { + sheight = (p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y]); + if (sheight < INF) + dpi = MM_PER_INCH * height / sheight; + else + dpi = 18.0; + } + get_bounds (opt, &min, &max); + if (dpi < min) + dpi = min; + if (dpi > max) + dpi = max; + status = set_option_float (p, p->dialog->well_known.dpi, dpi); + if (status != SANE_STATUS_GOOD) + return; + } + + /* set the scan window (necessary since backends may default to + non-maximum size): */ + for (i = 0; i < 4; ++i) + set_option_float (p, p->dialog->well_known.coord[i], p->surface[i]); + set_option_bool (p, p->dialog->well_known.preview, SANE_TRUE); + + /* OK, all set to go */ + scan_start (p); +} + +void +preview_destroy (Preview * p) +{ + char filename[PATH_MAX]; + FILE *out; + + if (p->scanning) + scan_done (p); /* don't save partial window */ + else if (preferences.preserve_preview && p->image_data + && make_preview_image_path (p, sizeof (filename), filename) >= 0) + { + /* save preview image */ + out = fopen (filename, "w"); + if (out) + { + /* always save it as a PPM image: */ + fprintf (out, "P6\n# surface: %g %g %g %g %u %u\n%d %d\n255\n", + p->surface[0], p->surface[1], p->surface[2], p->surface[3], + p->surface_type, p->surface_unit, + p->image_width, p->image_height); + fwrite (p->image_data, 3, p->image_width * p->image_height, out); + fclose (out); + } + } + if (p->image_data) + free (p->image_data); + if (p->preview_row) + free (p->preview_row); + if (p->gc) + gdk_gc_destroy (p->gc); + if (p->top) + gtk_widget_destroy (p->top); + free (p); +} |