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