diff options
author | Kevin Dalley <kevind@rahul.net> | 2002-03-15 14:20:02 +0000 |
---|---|---|
committer | Mattia Rizzolo <mattia@mapreri.org> | 2014-10-03 14:05:02 +0000 |
commit | ac8459519a9ef2a1ee635509b52a653da1bfe9d5 (patch) | |
tree | 9ed2df2144d6ad3bb7cf1d43243d815a8eb90059 /src/xsane-preview.c | |
parent | 8b372c9248930ae29763202121434d3d676d0f30 (diff) | |
parent | e7e90b72fd3161c5d55fed49e100781dfa3e9408 (diff) |
Imported Debian patch 0.84-2debian/0.84-2
Diffstat (limited to 'src/xsane-preview.c')
-rw-r--r-- | src/xsane-preview.c | 5997 |
1 files changed, 5997 insertions, 0 deletions
diff --git a/src/xsane-preview.c b/src/xsane-preview.c new file mode 100644 index 0000000..da81e3f --- /dev/null +++ b/src/xsane-preview.c @@ -0,0 +1,5997 @@ +/* xsane -- a graphical (X11, gtk) scanner-oriented SANE frontend + + xsane-preview.c + + Oliver Rauch <Oliver.Rauch@rauch-domain.de> + Copyright (C) 1998-2002 Oliver Rauch + This file is part of the XSANE package. + + This program 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +/* + + The preview strategy is as follows: + ----------------------------------- + + 1) The preview is done on the full scan area or a part of it. + + 2) The preview is zoomable so the user can precisely pick + the selection area even for small scans on a large scan + surface. + + 3) The preview window is resizeable. + + 4) The preview scan resolution depends on preview window size + and the selected preview surface (zoom area). + + 5) 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) + - gamma table is set to default (gamma=1.0) + + 5) The initial size of the scan surface is determined based on the constraints + of the four corner coordinates. Missing constraints are replaced + by 0/+INF as appropriate (0 for top-left, +INF for bottom-right coords). + + 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. + + 8) Surface definitions: + device = surface of the scanner + image = same oriantation like device + preview = rotated (0/90/180/270 degree) device surface + window = same oriantation like device, may be different scaling + +*/ + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +#include "xsane.h" +/* #include <sys/param.h> */ +#include "xsane-back-gtk.h" +#include "xsane-front-gtk.h" +#include "xsane-preview.h" +#include "xsane-preferences.h" +#include "xsane-gamma.h" +#include <gdk/gdkkeysyms.h> + + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +/* Cut fp conversion routines some slack: */ +#define GROSSLY_DIFFERENT(f1,f2) (fabs ((f1) - (f2)) > 1e-3) +#define GROSSLY_EQUAL(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 + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static u_char *preview_gamma_data_red = 0; +static u_char *preview_gamma_data_green = 0; +static u_char *preview_gamma_data_blue = 0; + +static u_char *histogram_gamma_data_red = 0; +static u_char *histogram_gamma_data_green = 0; +static u_char *histogram_gamma_data_blue = 0; + +/* histogram_medium_gamma_data_* is used when medium correction is done after preview-scan by xsane */ +static u_char *histogram_medium_gamma_data_red = 0; +static u_char *histogram_medium_gamma_data_green = 0; +static u_char *histogram_medium_gamma_data_blue = 0; + +static int preview_gamma_input_bits; + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +/* forward declarations */ +static void preview_rotate_devicesurface_to_previewsurface(int rotation, float dsurface[4], float *psurface); +static void preview_rotate_previewsurface_to_devicesurface(int rotation, float psurface[4], float *dsurface); +static void preview_transform_coordinates_device_to_window(Preview *p, float dcoordinate[4], float *win_coord); +static void preview_transform_coordinate_window_to_device(Preview *p, float winx, float winy, float *previewx, float *previewy); +static void preview_transform_coordinate_window_to_image(Preview *p, int winx, int winy, int *imagex, int *imagey); +static void preview_order_selection(Preview *p); +static void preview_bound_selection(Preview *p); +static void preview_draw_rect(Preview *p, GdkWindow *win, GdkGC *gc, float coord[4]); +static void preview_draw_selection(Preview *p); +static void preview_update_selection(Preview *p); +static void preview_establish_selection(Preview *p); +/* static void preview_update_batch_selection(Preview *p); */ +static void preview_get_scale_device_to_image(Preview *p, float *xscalep, float *yscalep); +static void preview_get_scale_device_to_window(Preview *p, float *xscalep, float *yscalep); +static void preview_get_scale_window_to_image(Preview *p, float *xscalep, float *yscalep); +static void preview_paint_image(Preview *p); +static void preview_display_partial_image(Preview *p); +static void preview_display_maybe(Preview *p); +static void preview_display_image(Preview *p); +static void preview_save_option(Preview *p, int option, void *save_loc, int *valid); +static void preview_restore_option(Preview *p, int option, void *saved_value, int valid); +static void preview_set_option(Preview *p, int option, void *value); +static void preview_set_option_float(Preview *p, int option, float value); +static void preview_set_option_val(Preview *p, int option, SANE_Int value); +static int preview_increment_image_y(Preview *p); +static void preview_read_image_data(gpointer data, gint source, GdkInputCondition cond); +static void preview_scan_done(Preview *p, int save_image); +static void preview_scan_start(Preview *p); +static int preview_make_image_path(Preview *p, size_t filename_size, char *filename, int level); +static void preview_restore_image(Preview *p); +static gint preview_expose_event_handler_start(GtkWidget *window, GdkEvent *event, gpointer data); +static gint preview_expose_event_handler_end(GtkWidget *window, GdkEvent *event, gpointer data); +static gint preview_hold_event_handler(gpointer data); +static gint preview_motion_event_handler(GtkWidget *window, GdkEvent *event, gpointer data); +static gint preview_button_press_event_handler(GtkWidget *window, GdkEvent *event, gpointer data); +static gint preview_button_release_event_handler(GtkWidget *window, GdkEvent *event, gpointer data); +static void preview_start_button_clicked(GtkWidget *widget, gpointer data); +static void preview_cancel_button_clicked(GtkWidget *widget, gpointer data); +static void preview_area_correct(Preview *p); +static void preview_save_image(Preview *p); +static void preview_delete_images(Preview *p); +static void preview_zoom_not(GtkWidget *window, gpointer data); +static void preview_zoom_out(GtkWidget *window, gpointer data); +static void preview_zoom_in(GtkWidget *window, gpointer data); +static void preview_zoom_undo(GtkWidget *window, gpointer data); +static void preview_get_color(Preview *p, int x, int y, int range, int *red, int *green, int *blue); +static void preview_pipette_white(GtkWidget *window, gpointer data); +static void preview_pipette_gray(GtkWidget *window, gpointer data); +static void preview_pipette_black(GtkWidget *window, gpointer data); +void preview_select_full_preview_area(Preview *p); +static void preview_full_preview_area_callback(GtkWidget *widget, gpointer call_data); +static void preview_delete_images_callback(GtkWidget *widget, gpointer call_data); +static gint preview_preset_area_rename_callback(GtkWidget *widget, GtkWidget *preset_area_widget); +static gint preview_preset_area_add_callback(GtkWidget *widget, GtkWidget *preset_area_widget); +static gint preview_preset_area_delete_callback(GtkWidget *widget, GtkWidget *preset_area_widget); +static gint preview_preset_area_move_up_callback(GtkWidget *widget, GtkWidget *preset_area_widget); +static gint preview_preset_area_move_down_callback(GtkWidget *widget, GtkWidget *preset_area_widget); +static gint preview_preset_area_context_menu_callback(GtkWidget *widget, GdkEvent *event); +static void preview_preset_area_callback(GtkWidget *widget, gpointer call_data); +static void preview_rotation_callback(GtkWidget *widget, gpointer call_data); +static void preview_autoselect_scanarea_callback(GtkWidget *window, gpointer data); + +void preview_do_gamma_correction(Preview *p); +void preview_calculate_raw_histogram(Preview *p, SANE_Int *count_raw, SANE_Int *count_raw_red, SANE_Int *count_raw_green, SANE_Int *count_raw_blue); +void preview_calculate_enh_histogram(Preview *p, SANE_Int *count, SANE_Int *count_red, SANE_Int *count_green, SANE_Int *count_blue); +void preview_gamma_correction(Preview *p, int gamma_input_bits, + u_char *gamma_red, u_char *gamma_green, u_char *gamma_blue, + u_char *gamma_red_hist, u_char *gamma_green_hist, u_char *gamma_blue_hist, + u_char *medium_gamma_red_hist, u_char *medium_gamma_green_hist, u_char *medium_gamma_blue_hist); +void preview_area_resize(Preview *p); +gint preview_area_resize_handler(GtkWidget *widget, GdkEvent *event, gpointer data); +void preview_update_maximum_output_size(Preview *p); +void preview_set_maximum_output_size(Preview *p, float width, float height); +void preview_autoselect_scanarea(Preview *p, float *autoselect_coord); +void preview_display_valid(Preview *p); + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_rotate_devicesurface_to_previewsurface(int rotation, float dsurface[4], float *psurface) +{ + DBG(DBG_proc, "preview_rotate_devicesurface_to_previewsurface(rotation = %d)\n", rotation); + + switch (rotation & 3) + { + case 0: /* 0 degree */ + default: + *(psurface+0) = dsurface[0]; + *(psurface+1) = dsurface[1]; + *(psurface+2) = dsurface[2]; + *(psurface+3) = dsurface[3]; + break; + + case 1: /* 90 degree */ + *(psurface+0) = dsurface[3]; + *(psurface+1) = dsurface[0]; + *(psurface+2) = dsurface[1]; + *(psurface+3) = dsurface[2]; + break; + + case 2: /* 180 degree */ + *(psurface+0) = dsurface[2]; + *(psurface+1) = dsurface[3]; + *(psurface+2) = dsurface[0]; + *(psurface+3) = dsurface[1]; + break; + + case 3: /* 270 degree */ + *(psurface+0) = dsurface[1]; + *(psurface+1) = dsurface[2]; + *(psurface+2) = dsurface[3]; + *(psurface+3) = dsurface[0]; + break; + } + + if (rotation & 4) /* mirror in x direction */ + { + float help=*(psurface+0); + + *(psurface+0) = *(psurface+2); + *(psurface+2) = help; + } + + DBG(DBG_info, "device[%3.2f %3.2f %3.2f %3.2f] -> preview[%3.2f %3.2f %3.2f %3.2f]\n", + dsurface[0], dsurface[1], dsurface[2], dsurface[3], + *(psurface+0), *(psurface+1), *(psurface+2), *(psurface+3)); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_rotate_previewsurface_to_devicesurface(int rotation, float psurface[4], float *dsurface) +{ + DBG(DBG_proc, "preview_rotate_previewsurface_to_devicesurface(rotation = %d)\n", rotation); + + switch (rotation) + { + case 0: /* 0 degree */ + default: + *(dsurface+0) = psurface[0]; + *(dsurface+1) = psurface[1]; + *(dsurface+2) = psurface[2]; + *(dsurface+3) = psurface[3]; + break; + + case 1: /* 90 degree */ + *(dsurface+0) = psurface[1]; + *(dsurface+1) = psurface[2]; + *(dsurface+2) = psurface[3]; + *(dsurface+3) = psurface[0]; + break; + + case 2: /* 180 degree */ + *(dsurface+0) = psurface[2]; + *(dsurface+1) = psurface[3]; + *(dsurface+2) = psurface[0]; + *(dsurface+3) = psurface[1]; + break; + + case 3: /* 270 degree */ + *(dsurface+0) = psurface[3]; + *(dsurface+1) = psurface[0]; + *(dsurface+2) = psurface[1]; + *(dsurface+3) = psurface[2]; + break; + + case 4: /* 0 degree, x mirror */ + *(dsurface+0) = psurface[2]; + *(dsurface+1) = psurface[1]; + *(dsurface+2) = psurface[0]; + *(dsurface+3) = psurface[3]; + break; + + case 5: /* 90 degree, x mirror */ + *(dsurface+0) = psurface[1]; + *(dsurface+1) = psurface[0]; + *(dsurface+2) = psurface[3]; + *(dsurface+3) = psurface[2]; + break; + + case 6: /* 180 degree, x mirror */ + *(dsurface+0) = psurface[0]; + *(dsurface+1) = psurface[3]; + *(dsurface+2) = psurface[2]; + *(dsurface+3) = psurface[1]; + break; + + case 7: /* 270 degree, x mirror */ + *(dsurface+0) = psurface[3]; + *(dsurface+1) = psurface[2]; + *(dsurface+2) = psurface[1]; + *(dsurface+3) = psurface[0]; + break; + } + + DBG(DBG_info, "preview[%3.2f %3.2f %3.2f %3.2f] -> device[%3.2f %3.2f %3.2f %3.2f]\n", + psurface[0], psurface[1], psurface[2], psurface[3], + *(dsurface+0), *(dsurface+1), *(dsurface+2), *(dsurface+3)); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_transform_coordinates_device_to_window(Preview *p, float preview_coord[4], float *win_coord) +{ + float minx, maxx, miny, maxy; + float xscale, yscale; + + DBG(DBG_proc, "preview_transform_coordinates_device_to_window\n"); + + preview_get_scale_device_to_window(p, &xscale, &yscale); + + minx = preview_coord[0]; + miny = preview_coord[1]; + maxx = preview_coord[2]; + maxy = preview_coord[3]; + + if (minx > maxx) + { + float val = minx; + minx = maxx; + maxx = val; + } + + if (miny > maxy) + { + float val = miny; + miny = maxy; + maxy = val; + } + + switch (p->rotation) + { + case 0: /* 0 degree */ + default: + *(win_coord+0) = xscale * (minx - p->surface[0]); + *(win_coord+1) = yscale * (miny - p->surface[1]); + *(win_coord+2) = xscale * (maxx - p->surface[0]); + *(win_coord+3) = yscale * (maxy - p->surface[1]); + break; + + case 1: /* 90 degree */ + *(win_coord+0) = xscale * (p->surface[0] - maxx); + *(win_coord+1) = yscale * (miny - p->surface[1]); + *(win_coord+2) = xscale * (p->surface[0] - minx); + *(win_coord+3) = yscale * (maxy - p->surface[1]); + break; + + case 2: /* 180 degree */ + *(win_coord+0) = xscale * (p->surface[0] - maxx); + *(win_coord+1) = yscale * (p->surface[1] - maxy); + *(win_coord+2) = xscale * (p->surface[0] - minx); + *(win_coord+3) = yscale * (p->surface[1] - miny); + break; + + case 3: /* 270 degree */ + *(win_coord+0) = xscale * (minx - p->surface[0]); + *(win_coord+1) = yscale * (p->surface[1] - maxy); + *(win_coord+2) = xscale * (maxx - p->surface[0]); + *(win_coord+3) = yscale * (p->surface[1] - miny); + break; + + case 4: /* 0 degree, x mirror */ + *(win_coord+0) = xscale * (p->surface[0] - maxx); + *(win_coord+1) = yscale * (miny - p->surface[1]); + *(win_coord+2) = xscale * (p->surface[0] - minx); + *(win_coord+3) = yscale * (maxy - p->surface[1]); + break; + + case 5: /* 90 degree, x mirror */ + *(win_coord+0) = xscale * (minx - p->surface[0]); + *(win_coord+1) = yscale * (miny - p->surface[1]); + *(win_coord+2) = xscale * (maxx - p->surface[0]); + *(win_coord+3) = yscale * (maxy - p->surface[1]); + break; + + case 6: /* 180 degree, x mirror */ + *(win_coord+0) = xscale * (minx - p->surface[0]); + *(win_coord+1) = yscale * (p->surface[1] - maxy); + *(win_coord+2) = xscale * (maxx - p->surface[0]); + *(win_coord+3) = yscale * (p->surface[1] - miny); + break; + + case 7: /* 270 degree, x mirror */ + *(win_coord+0) = xscale * (p->surface[0] - maxx); + *(win_coord+1) = yscale * (p->surface[1] - maxy); + *(win_coord+2) = xscale * (p->surface[0] - minx); + *(win_coord+3) = yscale * (p->surface[1] - miny); + break; + } + + DBG(DBG_info, "preview[%3.2f %3.2f %3.2f %3.2f] -> window[%3.2f %3.2f %3.2f %3.2f]\n", + preview_coord[0], preview_coord[1], preview_coord[2], preview_coord[3], + *(win_coord+0), *(win_coord+1), *(win_coord+2), *(win_coord+3) ); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_transform_coordinate_window_to_device(Preview *p, float winx, float winy, float *devicex, float *devicey) +{ + float xscale, yscale; + + DBG(DBG_proc, "preview_transform_coordinate_window_to_device\n"); + + preview_get_scale_device_to_window(p, &xscale, &yscale); + + switch (p->rotation) + { + case 0: /* 0 degree */ + default: + *devicex = p->surface[0] + winx / xscale; + *devicey = p->surface[1] + winy / yscale; + break; + + case 1: /* 90 degree */ + *devicex = p->surface[0] - winx / xscale; + *devicey = p->surface[1] + winy / yscale; + break; + + case 2: /* 180 degree */ + *devicex = p->surface[0] - winx / xscale; + *devicey = p->surface[1] - winy / yscale; + break; + + case 3: /* 270 degree */ + *devicex = p->surface[0] + winx / xscale; + *devicey = p->surface[1] - winy / yscale; + break; + + case 4: /* 0 degree, x mirror */ + *devicex = p->surface[0] - winx / xscale; + *devicey = p->surface[1] + winy / yscale; + break; + + case 5: /* 90 degree, x mirror */ + *devicex = p->surface[0] + winx / xscale; + *devicey = p->surface[1] + winy / yscale; + break; + + case 6: /* 180 degree, x mirror */ + *devicex = p->surface[0] + winx / xscale; + *devicey = p->surface[1] - winy / yscale; + break; + + case 7: /* 270 degree, x mirror */ + *devicex = p->surface[0] - winx / xscale; + *devicey = p->surface[1] - winy / yscale; + break; + } + + DBG(DBG_info, "window[%3.2f %3.2f] -> device[%3.2f %3.2f]\n", winx, winy, *devicex, *devicey); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_transform_coordinate_window_to_image(Preview *p, int winx, int winy, int *imagex, int *imagey) +{ + float xscale, yscale; + + DBG(DBG_proc, "preview_transform_coordinate_window_to_image\n"); + + preview_get_scale_window_to_image(p, &xscale, &yscale); + + switch (p->rotation) + { + case 0: /* 0 degree */ + default: + *imagex = winx * xscale; + *imagey = winy * yscale; + break; + + case 1: /* 90 degree */ + *imagex = winy * yscale; + *imagey = p->image_height - winx * xscale; + break; + + case 2: /* 180 degree */ + *imagex = p->image_width - winx * xscale; + *imagey = p->image_height - winy * yscale; + break; + + case 3: /* 270 degree */ + *imagex = p->image_width - winy * yscale; + *imagey = winx * xscale; + break; + + case 4: /* 0 degree, x mirror */ + *imagex = p->image_width - winx * xscale; + *imagey = winy * yscale; + break; + + case 5: /* 90 degree, x mirror */ + *imagex = winy * yscale; + *imagey = winx * xscale; + break; + + case 6: /* 180 degree, x mirror */ + *imagex = winx * xscale; + *imagey = p->image_height - winy * yscale; + break; + + case 7: /* 270 degree, x mirror */ + *imagex = p->image_width - winy * yscale; + *imagey = p->image_height - winx * xscale; + break; + } + + DBG(DBG_info, "window[%d %d] -> image[%d %d]\n", winx, winy, *imagex, *imagey); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_order_selection(Preview *p) +{ + float tmp_coordinate; + + DBG(DBG_proc, "preview_order_selection\n"); + + p->selection.active = ( (p->selection.coordinate[0] != p->selection.coordinate[2]) && + (p->selection.coordinate[1] != p->selection.coordinate[3]) ); + + + if (p->selection.active) + { + + if (p->selection.coordinate[p->index_xmin] > p->selection.coordinate[p->index_xmax]) + { + tmp_coordinate = p->selection.coordinate[p->index_xmin]; + p->selection.coordinate[p->index_xmin] = p->selection.coordinate[p->index_xmax]; + p->selection.coordinate[p->index_xmax] = tmp_coordinate; + + p->selection_xedge = (p->selection_xedge + 2) & 3; + } + + if (p->selection.coordinate[p->index_ymin] > p->selection.coordinate[p->index_ymax]) + { + tmp_coordinate = p->selection.coordinate[p->index_ymin]; + p->selection.coordinate[p->index_ymin] = p->selection.coordinate[p->index_ymax]; + p->selection.coordinate[p->index_ymax] = tmp_coordinate; + + p->selection_yedge = (p->selection_yedge + 2) & 3; + } + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_bound_selection(Preview *p) +{ + DBG(DBG_proc, "preview_bound_selection\n"); + + p->selection.active = ( (p->selection.coordinate[0] != p->selection.coordinate[2]) && + (p->selection.coordinate[1] != p->selection.coordinate[3]) ); + + + if (p->selection.active) + { +#if 0 + xsane_bound_float(&p->selection.coordinate[0], p->scanner_surface[0], p->scanner_surface[2]); + xsane_bound_float(&p->selection.coordinate[2], p->scanner_surface[0], p->scanner_surface[2]); + xsane_bound_float(&p->selection.coordinate[1], p->scanner_surface[1], p->scanner_surface[3]); + xsane_bound_float(&p->selection.coordinate[3], p->scanner_surface[1], p->scanner_surface[3]); +#endif + xsane_bound_float(&p->selection.coordinate[0], p->surface[0], p->surface[2]); + xsane_bound_float(&p->selection.coordinate[2], p->surface[0], p->surface[2]); + xsane_bound_float(&p->selection.coordinate[1], p->surface[1], p->surface[3]); + xsane_bound_float(&p->selection.coordinate[3], p->surface[1], p->surface[3]); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_draw_rect(Preview *p, GdkWindow *win, GdkGC *gc, float preview_coord[4]) +{ + float win_coord[4]; + + DBG(DBG_proc, "preview_draw_rect [%3.2f %3.2f %3.2f %3.2f]\n", preview_coord[0], preview_coord[1], preview_coord[2], preview_coord[3]); + + preview_transform_coordinates_device_to_window(p, preview_coord, win_coord); + gdk_draw_rectangle(win, gc, FALSE, win_coord[0], win_coord[1], win_coord[2]-win_coord[0] + 1, win_coord[3] - win_coord[1] + 1); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_draw_selection(Preview *p) +{ + DBG(DBG_proc, "preview_draw_selection\n"); + + if (!p->gc_selection) /* window isn't mapped yet */ + { + return; + } + + if ( (p->show_selection == FALSE) || (p->calibration) ) + { + return; + } + + if (p->previous_selection.active) + { + preview_draw_rect(p, p->window->window, p->gc_selection, p->previous_selection.coordinate); + } + + if (p->selection.active) + { + preview_draw_rect(p, p->window->window, p->gc_selection, p->selection.coordinate); + } + + p->previous_selection = p->selection; + + + if (!p->gc_selection_maximum) /* window isn't mapped yet */ + { + return; + } + + if (p->previous_selection_maximum.active) + { + preview_draw_rect(p, p->window->window, p->gc_selection_maximum, p->previous_selection_maximum.coordinate); + } + + if (p->selection_maximum.active) + { + preview_draw_rect(p, p->window->window, p->gc_selection_maximum, p->selection_maximum.coordinate); + } + + p->previous_selection_maximum = p->selection_maximum; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_update_selection(Preview *p) +/* draw selection box as defined in backend */ +{ + const SANE_Option_Descriptor *opt; + SANE_Status status; + SANE_Word val; + int i, optnum; + float coord[4]; + + DBG(DBG_proc, "preview_update_selection\n"); + + p->previous_selection = p->selection; + + for (i = 0; i < 4; ++i) + { + optnum = xsane.well_known.coord[i]; + if (optnum > 0) + { + opt = xsane_get_option_descriptor(xsane.dev, optnum); + status = xsane_control_option(xsane.dev, optnum, SANE_ACTION_GET_VALUE, &val, 0); + if (status != SANE_STATUS_GOOD) + { + continue; + } + if (opt->type == SANE_TYPE_FIXED) + { + coord[i] = SANE_UNFIX(val); + } + else + { + coord[i] = val; + } + } + else /* backend does not use scanarea options */ + { + switch (i) + { + case 0: + case 1: + coord[i] = 0; + break; + + case 2: + coord[i] = p->preview_width; + break; + + case 3: + coord[i] = p->preview_height; + break; + } + } + } + + preview_rotate_devicesurface_to_previewsurface(p->rotation, coord, p->selection.coordinate); + + p->selection.active = ( (p->selection.coordinate[0] != p->selection.coordinate[2]) && + (p->selection.coordinate[1] != p->selection.coordinate[3])); + + preview_update_maximum_output_size(p); + preview_draw_selection(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_establish_selection(Preview *p) +{ + /* This routine only shall be called if the preview area really is changed. */ + int i; + float coord[4]; + + DBG(DBG_proc, "preview_establish_selection\n"); + + preview_order_selection(p); + + xsane.block_update_param = TRUE; /* do not change parameters each time */ + + preview_rotate_previewsurface_to_devicesurface(p->rotation, p->selection.coordinate, coord); + + for (i = 0; i < 4; ++i) + { + preview_set_option_float(p, xsane.well_known.coord[i], coord[i]); + } + + xsane_back_gtk_update_scan_window(); + + xsane.block_update_param = FALSE; + + xsane_update_param(0); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +#if 0 +static void preview_update_batch_selection(Preview *p) +{ + Batch_selection *batch_selection; + + DBG(DBG_proc, "preview_update_batch_selection\n"); + + if (!p->gc_selection) /* window isn't mapped yet */ + { + return; + } + + batch_selection = p->batch_selection; + + while (batch_selection) + { + preview_draw_rect(p, p->window->window, p->gc_selection, batch_selection->coordinate); + + batch_selection = batch_selection->next; + } +} +#endif + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_get_scale_device_to_image(Preview *p, float *xscalep, float *yscalep) +{ + float device_width, device_height; + float xscale = 1.0; + float yscale = 1.0; + + device_width = fabs(p->image_surface[2] - p->image_surface[0]); + device_height = fabs(p->image_surface[3] - p->image_surface[1]); + + if ( ((p->rotation & 3) == 0) || ((p->rotation & 3) == 2) ) /* 0 or 180 degree */ + { + if ( (device_width >0) && (device_width < INF) ) + { + xscale = p->image_width / device_width; + } + + if ( (device_height >0) && (device_height < INF) ) + { + yscale = p->image_height / device_height; + } + } + else /* 90 or 270 degree */ + { + if ( (device_width >0) && (device_width < INF) ) + { + xscale = p->image_height / device_width; + } + + if ( (device_height >0) && (device_height < INF) ) + { + yscale = p->image_width / device_height; + } + } + +#if 1 + if (xscale > yscale) + { + yscale = xscale; + } + else + { + xscale = yscale; + } +#endif + + *xscalep = xscale; + *yscalep = yscale; + + DBG(DBG_info, "preview_get_scale_device_to_image: scale = %f, %f\n", xscale, yscale); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_get_scale_device_to_window(Preview *p, float *xscalep, float *yscalep) +{ + float device_width, device_height; + float xscale = 1.0; + float yscale = 1.0; + + /* device_* in device coords */ + device_width = fabs(p->image_surface[2] - p->image_surface[0]); + device_height = fabs(p->image_surface[3] - p->image_surface[1]); + + if ( (device_width >0) && (device_width < INF) ) + { + xscale = p->preview_width / device_width; /* preview width is in window coords */ + } + + if ( (device_height >0) && (device_height < INF) ) + { + yscale = p->preview_height / device_height; /* preview height is in window coords */ + } + + /* make sure pixels have square dimension */ + if (xscale > yscale) + { + yscale = xscale; + } + else + { + xscale = yscale; + } + + *xscalep = xscale; + *yscalep = yscale; + + DBG(DBG_info, "preview_get_scale_device_to_window: scale = %f, %f\n", xscale, yscale); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_get_scale_window_to_image(Preview *p, float *xscalep, float *yscalep) +{ + float xscale = 1.0; + float yscale = 1.0; + + switch (p->rotation & 3) + { + case 0: /* do not rotate - 0 degree */ + case 2: /* rotate 180 degree */ + default: + if (p->image_width > 0) + { + xscale = p->image_width / (float) p->preview_width; + } + + if (p->image_height > 0) + { + yscale = p->image_height / (float) p->preview_height; + } + break; + + case 1: /* rotate 90 degree */ + case 3: /* rotate 270 degree */ + if (p->image_height > 0) + { + xscale = p->image_height / (float) p->preview_width; + } + + if (p->image_width > 0) + { + yscale = p->image_width / (float) p->preview_height; + } + break; + + } + + /* make sure pixels have square dimension */ + if (xscale > yscale) + { + yscale = xscale; + } + else + { + xscale = yscale; + } + + *xscalep = xscale; + *yscalep = yscale; + + DBG(DBG_info, "preview_get_scale_window_to_image: scale = %f, %f\n", xscale, yscale); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_paint_image(Preview *p) +{ + float xscale, yscale, src_x, src_y; + int dst_x, dst_y, height, x, y, old_y, src_offset, x_direction; + int rotation; + + DBG(DBG_proc, "preview_paint_image (rotation=%d)\n", p->rotation); + + if (!p->image_data_enh) + { + return; /* no image data */ + } + + memset(p->preview_row, 0x80, 3 * p->preview_window_width); + + old_y = -1; + height = 0; + + rotation = p->rotation; + if (p->calibration) /* do not rotate calibration image */ + { + p->rotation = 0; + xscale=1.0; + yscale=1.0; + } + else + { + preview_get_scale_window_to_image(p, &xscale, &yscale); + } + + + switch (p->rotation & 3) + { + case 0: /* do not rotate - 0 degree */ + default: + + /* don't draw last line unless it's complete: */ + height = p->image_y; /* last line */ + + if (p->image_x == 0 && height < p->image_height) + { + ++height; /* use last line if it is complete */ + } + + src_y = 0.0; /* Source Y position index */ + + DBG(DBG_info, "preview_height=%d\n", p->preview_height); + + for (dst_y = 0; dst_y < p->preview_height; ++dst_y) + { + y = (int) (src_y + 0.5); + if (y >= height) + { + break; + } + + if (p->rotation & 4) /* mirror in x direction */ + { + src_offset = (y+1) * 3 * p->image_width - 3; + x_direction = -1; + } + else /* not mirrored */ + { + src_offset = y * 3 * p->image_width; + x_direction = 1; + } + + if (old_y != y) /* create new line ? - not necessary if the same line is used several times */ + { + old_y = y; + src_x = 0.0; /* Source X position index */ + + for (dst_x = 0; dst_x < p->preview_width; ++dst_x) + { + x = (int) (src_x + 0.5); + if (x >= p->image_width) + { + break; + } + + p->preview_row[3*dst_x + 0] = p->image_data_enh[src_offset + x_direction * 3 * x + 0]; /* R */ + p->preview_row[3*dst_x + 1] = p->image_data_enh[src_offset + x_direction * 3 * x + 1]; /* G */ + p->preview_row[3*dst_x + 2] = p->image_data_enh[src_offset + x_direction * 3 * x + 2]; /* B */ + src_x += xscale; /* calculate new source x position index */ + } + } + + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + src_y += yscale; /* calculate new source y position index */ + } + + memset(p->preview_row, 0x80, 3*p->preview_window_width); + for (; dst_y < p->preview_window_height; ++dst_y) + { + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + } + break; + + + case 1: /* 90 degree */ + /* because we run in x direction we have to draw all rows all the time */ + + src_y = 0.0; + + DBG(DBG_info, "height=%d\n", height); + DBG(DBG_info, "preview_height=%d\n", p->preview_height); + + for (dst_y = 0; dst_y < p->preview_height; ++dst_y) + { + y = (int) (src_y + 0.5); + if (y >= p->image_width) + { + break; + } + + if (p->rotation & 4) /* mirror in x direction */ + { + src_offset = y * 3 + 3 * p->image_width * (p->image_height-1); + x_direction = -1; + } + else /* not mirrored */ + { + src_offset = y * 3; + x_direction = 1; + } + + if (old_y != y) /* create new line ? - not necessary if the same line is used several times */ + { + old_y = y; + src_x = p->image_height - 1; + + for (dst_x = 0; dst_x < p->preview_width; ++dst_x) + { + x = (int) (src_x + 0.5); + if (x < 0) + { + break; + } + + p->preview_row[3*dst_x + 0] = p->image_data_enh[src_offset + x_direction * 3 * x * p->image_width + 0]; /* R */ + p->preview_row[3*dst_x + 1] = p->image_data_enh[src_offset + x_direction * 3 * x * p->image_width + 1]; /* G */ + p->preview_row[3*dst_x + 2] = p->image_data_enh[src_offset + x_direction * 3 * x * p->image_width + 2]; /* B */ + src_x -= xscale; + } + } + + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + src_y += yscale; + } + + memset(p->preview_row, 0x80, 3*p->preview_window_width); + for (; dst_y < p->preview_window_height; ++dst_y) + { + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + } + break; + + + case 2: /* 180 degree */ + + /* don't draw last line unless it's complete: */ + height = p->image_y; /* last line */ + + if ( (p->image_x == 0) && (height < p->image_height) ) + { + ++height; /* use last line if it is complete */ + } + + src_y = 0; /* Source Y position index */ + + DBG(DBG_info, "height=%d\n", height); + DBG(DBG_info, "preview_height=%d\n", p->preview_height); + + /* it looks like it is necessary to write row 0 at first */ + memset(p->preview_row, 0x80, 3*p->preview_window_width); + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, 0, p->preview_window_width); + for (dst_y = p->preview_height-1; dst_y >=0; --dst_y) + { + y = (int) (src_y + 0.5); + if (y >= height) + { + break; + } + + if (p->rotation & 4) /* mirror in x direction */ + { + src_offset = (y+1) * 3 * p->image_width - 3; + x_direction = -1; + } + else /* not mirrored */ + { + src_offset = y * 3 * p->image_width; + x_direction = 1; + } + + if (old_y != y) /* create new line ? - not necessary if the same line is used several times */ + { + old_y = y; + src_x = p->image_width - 1; + + for (dst_x = 0; dst_x < p->preview_width; ++dst_x) + { + x = (int) (src_x + 0.5); + if (x < 0) + { + break; + } + + p->preview_row[3*dst_x + 0] = p->image_data_enh[src_offset + x_direction * 3 * x + 0]; /* R */ + p->preview_row[3*dst_x + 1] = p->image_data_enh[src_offset + x_direction * 3 * x + 1]; /* G */ + p->preview_row[3*dst_x + 2] = p->image_data_enh[src_offset + x_direction * 3 * x + 2]; /* B */ + src_x -= xscale; + } + } + + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + src_y += yscale; + } + dst_y = p->preview_height; + + memset(p->preview_row, 0x80, 3*p->preview_window_width); + for (; dst_y < p->preview_window_height; ++dst_y) + { + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + } + break; + + + case 3: /* 270 degree */ + /* because we run in x direction we have to draw all rows all the time */ + + src_y = 0.0; + + DBG(DBG_info, "preview_height=%d\n", p->preview_height); + + for (dst_y = 0; dst_y < p->preview_height; ++dst_y) + { + y = (int) (src_y + 0.5); + if (y >= p->image_width) + { + break; + } + + if (p->rotation & 4) /* mirror in x direction */ + { + src_offset = (p->image_width - y - 1) * 3 + 3 * p->image_width * (p->image_height - 1); + x_direction = -1; + } + else /* not mirrored */ + { + src_offset = (p->image_width - y - 1) * 3; + x_direction = 1; + } + + if (old_y != y) /* create new line ? - not necessary if the same line is used several times */ + { + old_y = y; + src_x = 0.0; + + for (dst_x = 0; dst_x < p->preview_width; ++dst_x) + { + x = (int) (src_x + 0.5); + if (x >= p->image_height) + { + break; + } + + p->preview_row[3*dst_x + 0] = p->image_data_enh[src_offset + x_direction * 3 * x * p->image_width + 0]; /* R */ + p->preview_row[3*dst_x + 1] = p->image_data_enh[src_offset + x_direction * 3 * x * p->image_width + 1]; /* G */ + p->preview_row[3*dst_x + 2] = p->image_data_enh[src_offset + x_direction * 3 * x * p->image_width + 2]; /* B */ + src_x += xscale; + } + } + + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + src_y += yscale; + } + + memset(p->preview_row, 0x80, 3*p->preview_window_width); + for (; dst_y < p->preview_window_height; ++dst_y) + { + gtk_preview_draw_row(GTK_PREVIEW(p->window), p->preview_row, 0, dst_y, p->preview_window_width); + } + break; + } + + if (p->calibration) /* do not rotate calibration image */ + { + p->rotation = rotation; + } + + /* image is redrawn, we have no visible selections */ + p->previous_selection.active = FALSE; + p->previous_selection_maximum.active = FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_display_partial_image(Preview *p) +{ + DBG(DBG_proc, "preview_display_partial_image\n"); + + preview_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_window_width, p->preview_window_height); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_display_maybe(Preview *p) +{ + time_t now; + + DBG(DBG_proc, "preview_display_maybe\n"); + + time(&now); + + if (now > p->image_last_time_updated) /* wait at least one secone */ + { + p->image_last_time_updated = now; + preview_display_partial_image(p); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_display_image(Preview *p) +{ + DBG(DBG_proc, "preview_display_image\n"); + + /* if image height was unknown and got larger than expected get missing memory */ + if (p->params.lines <= 0 && p->image_y < p->image_height) + { + p->image_height = p->image_y; + + p->image_data_raw = realloc(p->image_data_raw, 6 * p->image_width * p->image_height); + p->image_data_enh = realloc(p->image_data_enh, 3 * p->image_width * p->image_height); + assert(p->image_data_raw); + assert(p->image_data_enh); + } + + preview_do_gamma_correction(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_save_option(Preview *p, int option, void *save_loc, int *valid) +{ + SANE_Status status; + + DBG(DBG_proc, "preview_save_option\n"); + + if (option <= 0) + { + *valid = 0; + return; + } + + status = xsane_control_option(xsane.dev, option, SANE_ACTION_GET_VALUE, save_loc, 0); + *valid = (status == SANE_STATUS_GOOD); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_restore_option(Preview *p, int option, void *saved_value, int valid) +{ + const SANE_Option_Descriptor *opt; + SANE_Status status; + SANE_Handle dev; + + DBG(DBG_proc, "preview_restore_option\n"); + + if (!valid) + { + return; + } + + dev = xsane.dev; + status = xsane_control_option(dev, option, SANE_ACTION_SET_VALUE, saved_value, 0); + + if (status != SANE_STATUS_GOOD) + { + char buf[256]; + opt = xsane_get_option_descriptor(dev, option); + snprintf(buf, sizeof(buf), "%s %s: %s.", ERR_SET_OPTION, opt->name, XSANE_STRSTATUS(status)); + xsane_back_gtk_error(buf, TRUE); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_set_option_float(Preview *p, int option, float value) +{ + const SANE_Option_Descriptor *opt; + SANE_Handle dev; + SANE_Word word; + + DBG(DBG_proc, "preview_set_option_float\n"); + + if (option <= 0 || value <= -INF || value >= INF) + { + return; + } + + dev = xsane.dev; + opt = xsane_get_option_descriptor(dev, option); + if (opt->type == SANE_TYPE_FIXED) + { + word = SANE_FIX(value); + } + else + { + word = value + 0.5; + } + + xsane_control_option(dev, option, SANE_ACTION_SET_VALUE, &word, 0); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_set_option(Preview *p, int option, void *value) +{ + SANE_Handle dev; + + DBG(DBG_proc, "preview_set_option\n"); + + if (option <= 0) + { + return; + } + + dev = xsane.dev; + xsane_control_option(dev, option, SANE_ACTION_SET_VALUE, value, 0); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_set_option_val(Preview *p, int option, SANE_Int value) +{ + SANE_Handle dev; + + DBG(DBG_proc, "preview_set_option_val\n"); + + if (option <= 0) + { + return; + } + + dev = xsane.dev; + xsane_control_option(dev, option, SANE_ACTION_SET_VALUE, &value, 0); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static int preview_test_image_y(Preview *p) +{ + if (p->image_y >= p->image_height) /* make sure backend does not send more data then expected */ + { + char buf[256]; + + --p->image_y; + preview_scan_done(p, 1); + snprintf(buf, sizeof(buf), "%s", ERR_TOO_MUCH_DATA); + xsane_back_gtk_error(buf, TRUE); + return -1; + } + + return 0; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static int preview_increment_image_y(Preview *p) +{ + size_t extra_size, offset; + char buf[256]; + + DBG(DBG_proc, "preview_increment_image_y\n"); + + p->image_x = 0; + ++p->image_y; + + if (p->params.lines <= 0 && p->image_y >= p->image_height) /* backend said it does not know image height */ + { + offset = 3 * p->image_width*p->image_height; + extra_size = 3 * 32 * p->image_width; + p->image_height += 32; + + p->image_data_raw = realloc(p->image_data_raw, (offset + extra_size) * 2); + p->image_data_enh = realloc(p->image_data_enh, offset + extra_size); + + if ( (!p->image_data_enh) || (!p->image_data_raw) ) + { + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %s.", ERR_FAILED_ALLOCATE_IMAGE, strerror(errno)); + xsane_back_gtk_error(buf, TRUE); + return -1; + } + memset(p->image_data_enh + offset, 0xff, extra_size); + } + + return 0; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_read_image_data(gpointer data, gint source, GdkInputCondition cond) +{ + SANE_Status status; + Preview *p = data; + u_char buf[8192]; + guint16 *buf16 = (guint16 *) buf; + SANE_Handle dev; + SANE_Int len; + int i, j; + int offset = 0; + char last = 0; + + DBG(DBG_proc, "preview_read_image_data\n"); + + dev = xsane.dev; + while (1) + { + if ((p->params.depth == 1) || (p->params.depth == 8)) + { + status = sane_read(dev, buf, sizeof(buf), &len); + } + else if (p->params.depth == 16) + { + if (offset) + { + buf16[0] = last; /* ATTENTION: that is wrong! */ + /* use sizeof(buf) here because sizeof(buf16) returns the size of a pointer */ + status = sane_read(dev, ((SANE_Byte *) buf16) + 1, sizeof(buf) - 1, &len); + } + else + { + status = sane_read(dev, (SANE_Byte *) buf16, sizeof(buf), &len); + } + + if (len % 2) /* odd number of bytes */ + { + len--; + last = buf16[len]; + offset = 1; + } + else /* even number of bytes */ + { + offset = 0; + } + } + else /* bad bitdepth */ + { + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %d.", ERR_PREVIEW_BAD_DEPTH, p->params.depth); + xsane_back_gtk_error(buf, TRUE); + return; + } + + + if (!p->scanning) /* preview scan may have been canceled while sane_read was executed */ + { + return; /* ok, the scan has been canceled */ + } + + + if (status != SANE_STATUS_GOOD) + { + if (status == SANE_STATUS_EOF) + { + if (p->params.last_frame) /* got all preview image data */ + { + p->invalid = FALSE; /* preview is valid now */ + preview_scan_done(p, 1); /* scan is done, save image */ + return; /* ok, all finished */ + } + else + { + if (p->input_tag >= 0) + { + gdk_input_remove(p->input_tag); + p->input_tag = -1; + } + preview_scan_start(p); + break; /* exit while loop, display_maybe */ + } + } + else if (status == SANE_STATUS_CANCELLED) + { + p->invalid = FALSE; /* preview is valid now - although it is cancled */ + p->scan_incomplete = TRUE; /* preview is incomplete */ + preview_scan_done(p, 1); /* save scanned part of the preview */ + snprintf(buf, sizeof(buf), "%s", XSANE_STRSTATUS(status)); + xsane_back_gtk_info(buf, TRUE); + return; + } + + /* not SANE_STATUS_GOOD and not SANE_STATUS_EOF and not SANE_STATUS_CANCELLED */ + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %s.", ERR_DURING_READ, XSANE_STRSTATUS(status)); + xsane_back_gtk_error(buf, TRUE); + return; + } + + if (!len) + { + break; /* out of data for now */ + } + + switch (p->params.format) + { + case SANE_FRAME_RGB: + switch (p->params.depth) + { + case 8: + { + for (i = 0; i < len; ++i) + { + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + p->image_data_raw[p->image_offset] = buf[i] * 256; + p->image_data_enh[p->image_offset++] = buf[i]; + + if (p->image_offset%3 == 0) + { + if (++p->image_x >= p->image_width && preview_increment_image_y(p) < 0) + { + return; + } + } + } + } + break; + + case 16: + { + for (i = 0; i < len/2; ++i) + { + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + p->image_data_raw[p->image_offset] = buf16[i]; + p->image_data_enh[p->image_offset++] = (u_char) (buf16[i]/256); + + if (p->image_offset%3 == 0) + { + if (++p->image_x >= p->image_width && preview_increment_image_y(p) < 0) + { + return; + } + } + } + } + break; + + default: + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %d.", ERR_PREVIEW_BAD_DEPTH, p->params.depth); + xsane_back_gtk_error(buf, TRUE); + return; + } + break; + + case SANE_FRAME_GRAY: + switch (p->params.depth) + { + case 1: + for (i = 0; i < len; ++i) + { + u_char mask = buf[i]; + + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + for (j = 7; j >= 0; --j) + { + u_char gl = (mask & (1 << j)) ? 0x00 : 0xff; + + p->image_data_raw[p->image_offset] = gl * 256; + p->image_data_enh[p->image_offset++] = gl; + + p->image_data_raw[p->image_offset] = gl * 256; + p->image_data_enh[p->image_offset++] = gl; + + p->image_data_raw[p->image_offset] = gl * 256; + p->image_data_enh[p->image_offset++] = gl; + + if (++p->image_x >= p->image_width) + { + if (preview_increment_image_y(p) < 0) + { + return; + } + break; /* skip padding bits */ + } + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + u_char gray = buf[i]; + + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + p->image_data_raw[p->image_offset] = gray * 256; + p->image_data_enh[p->image_offset++] = gray; + + p->image_data_raw[p->image_offset] = gray * 256; + p->image_data_enh[p->image_offset++] = gray; + + p->image_data_raw[p->image_offset] = gray * 256; + p->image_data_enh[p->image_offset++] = gray; + if (++p->image_x >= p->image_width && preview_increment_image_y(p) < 0) + { + return; + } + } + break; + + case 16: + for (i = 0; i < len/2; ++i) + { + u_char gray = buf16[i]/256; + + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + p->image_data_raw[p->image_offset] = buf16[i]; + p->image_data_enh[p->image_offset++] = gray; + + p->image_data_raw[p->image_offset] = buf16[i]; + p->image_data_enh[p->image_offset++] = gray; + + p->image_data_raw[p->image_offset] = buf16[i]; + p->image_data_enh[p->image_offset++] = gray; + + if (++p->image_x >= p->image_width && preview_increment_image_y(p) < 0) + { + return; + } + } + break; + + default: + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %d.", ERR_PREVIEW_BAD_DEPTH, p->params.depth); + xsane_back_gtk_error(buf, TRUE); + return; + } + 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]; + + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + for (j = 0; j < 8; ++j) + { + u_char gl = (mask & 1) ? 0xff : 0x00; + mask >>= 1; + + p->image_data_raw[p->image_offset] = gl * 256; + p->image_data_enh[p->image_offset++] = gl; + + p->image_offset += 3; + if (++p->image_x >= p->image_width && preview_increment_image_y(p) < 0) + { + return; + } + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + p->image_data_raw[p->image_offset] = buf[i] * 256; + p->image_data_enh[p->image_offset] = buf[i]; + + p->image_offset += 3; + if (++p->image_x >= p->image_width && preview_increment_image_y(p) < 0) + { + return; + } + } + break; + + case 16: + for (i = 0; i < len/2; ++i) + { + if (preview_test_image_y(p)) + { + return; /* backend sends too much image data */ + } + + p->image_data_raw[p->image_offset] = buf16[i]; + p->image_data_enh[p->image_offset] = (u_char) (buf16[i]/256); + + p->image_offset += 3; + if (++p->image_x >= p->image_width && preview_increment_image_y(p) < 0) + { + return; + } + } + break; + + default: + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %d.", ERR_PREVIEW_BAD_DEPTH, p->params.depth); + xsane_back_gtk_error(buf, TRUE); + return; + } + break; + + default: + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %d.", ERR_BAD_FRAME_FORMAT, p->params.format); + xsane_back_gtk_error(buf, TRUE); + return; + } + + if (p->input_tag < 0) + { + preview_display_maybe(p); + while (gtk_events_pending()) + { + DBG(DBG_info, "preview_read_image_data: calling gtk_main_iteration\n"); + gtk_main_iteration(); + } + } + } + preview_display_maybe(p); + + return; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_scan_done(Preview *p, int save_image) +{ + int i; + + DBG(DBG_proc, "preview_scan_done\n"); + + p->scanning = FALSE; + + if (p->input_tag >= 0) + { + gdk_input_remove(p->input_tag); + p->input_tag = -1; + } + + sane_cancel(xsane.dev); + + xsane.block_update_param = TRUE; /* do not change parameters each time */ + + preview_restore_option(p, xsane.well_known.dpi, &p->saved_dpi, p->saved_dpi_valid); + preview_restore_option(p, xsane.well_known.dpi_x, &p->saved_dpi_x, p->saved_dpi_x_valid); + preview_restore_option(p, xsane.well_known.dpi_y, &p->saved_dpi_y, p->saved_dpi_y_valid); + + for (i = 0; i < 4; ++i) + { + preview_restore_option(p, xsane.well_known.coord[i], &p->saved_coord[i], p->saved_coord_valid[i]); + } + + preview_restore_option(p, xsane.well_known.scanmode, &p->saved_scanmode, p->saved_scanmode_valid); + + preview_restore_option(p, xsane.well_known.bit_depth, &p->saved_bit_depth, p->saved_bit_depth_valid); + + preview_set_option_val(p, xsane.well_known.preview, SANE_FALSE); + + gtk_widget_set_sensitive(p->cancel, FALSE); + xsane_set_sensitivity(TRUE); + + xsane.block_update_param = FALSE; + + preview_update_selection(p); + + if (save_image) + { + preview_save_image(p); /* save preview image */ + preview_display_image(p); + } + + preview_update_surface(p, 1); /* if surface was not defined it's necessary to redefine it now */ + + xsane_update_histogram(TRUE /* update raw */); + + sane_get_parameters(xsane.dev, &xsane.param); /* update xsane.param */ + + if ( (preferences.preselect_scanarea) && (!p->startimage)) + { + preview_autoselect_scanarea(p, p->selection.coordinate); /* get autoselection coordinates */ + preview_draw_selection(p); + preview_establish_selection(p); + xsane_update_histogram(TRUE /* update_raw */); /* update histogram (necessary because overwritten by preview_update_surface) */ + } + + if (preferences.auto_correct_colors) + { + xsane_calculate_raw_histogram(); + xsane_set_auto_enhancement(); + xsane_enhancement_by_histogram(preferences.auto_enhance_gamma); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static int preview_get_memory(Preview *p) +{ + char buf[256]; + + DBG(DBG_proc, "preview_get_memory\n"); + + if (p->image_data_enh) + { + free(p->image_data_enh); + p->image_data_enh = 0; + } + + if (p->image_data_raw) + { + free(p->image_data_raw); + p->image_data_raw = 0; + } + + if (p->preview_row) + { + free(p->preview_row); + p->preview_row = 0; + } + + p->image_data_raw = malloc(6 * p->image_width * p->image_height); + p->image_data_enh = malloc(3 * p->image_width * p->image_height); + p->preview_row = malloc(3 * p->preview_window_width); + + if ( (!p->image_data_raw) || (!p->image_data_enh) || (!p->preview_row) ) + { + if (p->image_data_enh) + { + free(p->image_data_enh); + p->image_data_enh = 0; + } + + if (p->image_data_raw) + { + free(p->image_data_raw); + p->image_data_raw = 0; + } + + if (p->preview_row) + { + free(p->preview_row); + p->preview_row = 0; + } + + DBG(DBG_error, "failed to allocate image buffer: %s", strerror(errno)); + snprintf(buf, sizeof(buf), "%s %s.", ERR_FAILED_ALLOCATE_IMAGE, strerror(errno)); + xsane_back_gtk_error(buf, TRUE); + + return -1; /* error */ + } + + memset(p->image_data_raw, 0x80, 6*p->image_width*p->image_height); /* clean memory */ + memset(p->image_data_enh, 0x80, 3*p->image_width*p->image_height); /* clean memory */ + + return 0; /* ok */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +/* preview_scan_start is called 3 times in 3 pass color scanning mode */ +static void preview_scan_start(Preview *p) +{ + SANE_Handle dev = xsane.dev; + SANE_Status status; + char buf[256]; + int fd, y; + int gamma_gray_size = 256; /* set this values to image depth for more than 8bpp input support!!! */ + int gamma_red_size = 256; + int gamma_green_size = 256; + int gamma_blue_size = 256; + int gamma_gray_max = 255; /* set this to to image depth for more than 8bpp output support */ + int gamma_red_max = 255; + int gamma_green_max = 255; + int gamma_blue_max = 255; + + DBG(DBG_proc, "preview_scan_start\n"); + + xsane.medium_changed = FALSE; + + preview_display_valid(p); + + p->startimage = 0; /* we start the scan so lets say the startimage is not displayed any more */ + + p->image_surface[0] = p->surface[p->index_xmin]; + p->image_surface[1] = p->surface[p->index_ymin]; + p->image_surface[2] = p->surface[p->index_xmax]; + p->image_surface[3] = p->surface[p->index_ymax]; + + gtk_widget_set_sensitive(p->cancel, TRUE); + xsane_set_sensitivity(FALSE); + + /* clear preview row */ + 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; + } + + if (xsane.well_known.gamma_vector >0) + { + const SANE_Option_Descriptor *opt; + + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.gamma_vector); + if (SANE_OPTION_IS_ACTIVE(opt->cap)) + { + SANE_Int *gamma_data; + + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.gamma_vector); + gamma_gray_size = opt->size / sizeof(opt->type); + gamma_gray_max = opt->constraint.range->max; + + gamma_data = malloc(gamma_gray_size * sizeof(SANE_Int)); + + if ((xsane.xsane_colors > 1) || (xsane.no_preview_medium_gamma)) /* color scan or medium preview gamma disabled */ + { + xsane_create_gamma_curve(gamma_data, 0, 1.0, 0.0, 0.0, 0.0, 100.0, 1.0, gamma_gray_size, gamma_gray_max); + } + else /* grayscale scan */ + { + xsane_create_gamma_curve(gamma_data, xsane.medium_negative, 1.0, 0.0, 0.0, + xsane.medium_shadow_gray, xsane.medium_highlight_gray, xsane.medium_gamma_gray, + gamma_gray_size, gamma_gray_max); + } + + xsane_back_gtk_update_vector(xsane.well_known.gamma_vector, gamma_data); + free(gamma_data); + } + } + + if (xsane.well_known.gamma_vector_r >0) + { + const SANE_Option_Descriptor *opt; + + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.gamma_vector_r); + if (SANE_OPTION_IS_ACTIVE(opt->cap)) + { + SANE_Int *gamma_data_red, *gamma_data_green, *gamma_data_blue; + + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.gamma_vector_r); + gamma_red_size = opt->size / sizeof(opt->type); + gamma_red_max = opt->constraint.range->max; + + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.gamma_vector_g); + gamma_green_size = opt->size / sizeof(opt->type); + gamma_green_max = opt->constraint.range->max; + + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.gamma_vector_b); + gamma_blue_size = opt->size / sizeof(opt->type); + gamma_blue_max = opt->constraint.range->max; + + gamma_data_red = malloc(gamma_red_size * sizeof(SANE_Int)); + gamma_data_green = malloc(gamma_green_size * sizeof(SANE_Int)); + gamma_data_blue = malloc(gamma_blue_size * sizeof(SANE_Int)); + + if (xsane.no_preview_medium_gamma) /* do not use medium gamma for preview */ + { + DBG(DBG_info, "preview: not using medium gamma table\n"); + + xsane_create_gamma_curve(gamma_data_red, 0, 1.0, 0.0, 0.0, 0.0, 100.0, 1.0, gamma_red_size, gamma_red_max); + xsane_create_gamma_curve(gamma_data_green, 0, 1.0, 0.0, 0.0, 0.0, 100.0, 1.0, gamma_green_size, gamma_green_max); + xsane_create_gamma_curve(gamma_data_blue, 0, 1.0, 0.0, 0.0, 0.0, 100.0, 1.0, gamma_blue_size, gamma_blue_max); + } + else /* use medium gamma for preview */ + { + DBG(DBG_info, "preview: using medium gamma table\n"); + + xsane_create_gamma_curve(gamma_data_red, xsane.medium_negative, 1.0, 0.0, 0.0, + xsane.medium_shadow_red, xsane.medium_highlight_red, xsane.medium_gamma_red, + gamma_red_size, gamma_red_max); + xsane_create_gamma_curve(gamma_data_green, xsane.medium_negative, 1.0, 0.0, 0.0, + xsane.medium_shadow_green, xsane.medium_highlight_green, xsane.medium_gamma_green, + gamma_green_size, gamma_green_max); + xsane_create_gamma_curve(gamma_data_blue, xsane.medium_negative, 1.0, 0.0, 0.0, + xsane.medium_shadow_blue, xsane.medium_highlight_blue, xsane.medium_gamma_blue, + gamma_blue_size, gamma_blue_max); + } + + xsane_back_gtk_update_vector(xsane.well_known.gamma_vector_r, gamma_data_red); + xsane_back_gtk_update_vector(xsane.well_known.gamma_vector_g, gamma_data_green); + xsane_back_gtk_update_vector(xsane.well_known.gamma_vector_b, gamma_data_blue); + + free(gamma_data_red); + free(gamma_data_green); + free(gamma_data_blue); + } + } + + status = sane_start(dev); + if (status != SANE_STATUS_GOOD) + { + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %s.", ERR_FAILED_START_SCANNER, XSANE_STRSTATUS(status)); + xsane_back_gtk_error(buf, TRUE); + return; + } + + status = sane_get_parameters(dev, &p->params); + if (status != SANE_STATUS_GOOD) + { + preview_scan_done(p, 0); + snprintf(buf, sizeof(buf), "%s %s.", ERR_FAILED_GET_PARAMS, XSANE_STRSTATUS(status)); + xsane_back_gtk_error(buf, TRUE); + 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_enh) || (p->params.pixels_per_line != p->image_width) + || ( (p->params.lines >= 0) && (p->params.lines != p->image_height) ) ) + { + 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... */ + } + + if (preview_get_memory(p)) + { + preview_scan_done(p, 0); /* error */ + snprintf(buf, sizeof(buf), "%s", ERR_NO_MEM); + xsane_back_gtk_error(buf, TRUE); + return; + } + } + else if (p->scanning == FALSE) /* single pass scan or first run in 3 pass mode */ + { + memset(p->image_data_raw, 0x80, 6*p->image_width*p->image_height); /* clean memory */ + memset(p->image_data_enh, 0x80, 3*p->image_width*p->image_height); /* clean memory */ + } + + /* we do not have any active selection (image is redrawn while scanning) */ + p->selection.active = FALSE; + p->previous_selection_maximum.active = FALSE; + +#ifndef BUGGY_GDK_INPUT_EXCEPTION + /* for unix */ + 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, preview_read_image_data, p); + } + else +#else + /* for win32 */ + sane_set_io_mode(dev, SANE_FALSE); +#endif + { + preview_read_image_data(p, -1, GDK_INPUT_READ); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static int preview_make_image_path(Preview *p, size_t filename_size, char *filename, int level) +{ + char buf[256]; + + DBG(DBG_proc, "preview_make_image_path\n"); + + snprintf(buf, sizeof(buf), "preview-level-%d-", level); + return xsane_back_gtk_make_path(filename_size, filename, 0, 0, buf, xsane.dev_name, ".ppm", XSANE_PATH_TMP); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static int preview_restore_image_from_file(Preview *p, FILE *in, int min_quality, int *min_time) +{ + u_int psurface_type, psurface_unit; + int image_width, image_height; + int xoffset, yoffset, width, height; + int max_val; + int quality = 0; + int x, y; + int time; + float psurface[4]; + float dsurface[4]; + size_t nread; + guint16 *imagep; + guint16 *imagepx; + char buf[255]; + + DBG(DBG_proc, "preview_restore_image_from_file\n"); + + if (!in) + { + return min_quality; + } + + /* See whether there is a saved preview and load it if present: */ + if (fscanf(in, "P6\n" + "# surface: %g %g %g %g %u %u\n" + "# time: %d\n" + "%d %d\n%d", + psurface + 0, psurface + 1, psurface + 2, psurface + 3, + &psurface_type, &psurface_unit, + &time, + &image_width, &image_height, + &max_val) != 10) + { + DBG(DBG_info, "no preview image\n"); + return min_quality; + } + + fgets(buf, sizeof(buf), in); /* skip newline character. this made a lot of problems in the past, so I skip it this way */ + + + if (min_quality >= 0) /* read real preview */ + { + if ((psurface_type != p->surface_type) || (psurface_unit != p->surface_unit)) + { + DBG(DBG_info, "incompatible surface types %d <> %d\n", psurface_type, p->surface_type); + return min_quality; + } + + preview_rotate_previewsurface_to_devicesurface(p->rotation, p->surface, dsurface); + + + DBG(DBG_info, "stored image surface = [%3.2f %3.2f %3.2f %3.2f]\n", + psurface[0], psurface[1], psurface[2], psurface[3]); + DBG(DBG_info, "preview selection surface = [%3.2f %3.2f %3.2f %3.2f]\n", + p->surface[0], p->surface[1], p->surface[2], p->surface[3]); + DBG(DBG_info, "preview device surface = [%3.2f %3.2f %3.2f %3.2f]\n", + dsurface[0], dsurface[1], dsurface[2], dsurface[3]); + + xoffset = (dsurface[0] - psurface[0])/(psurface[2] - psurface[0]) * image_width; + yoffset = (dsurface[1] - psurface[1])/(psurface[3] - psurface[1]) * image_height; + width = (dsurface[2] - dsurface[0])/(psurface[2] - psurface[0]) * image_width; + height = (dsurface[3] - dsurface[1])/(psurface[3] - psurface[1]) * image_height; + quality = width; + + if ((xoffset < 0) || (yoffset < 0) || + (xoffset+width > image_width) || (yoffset+height > image_height) || + (width == 0) || (height == 0)) + { + DBG(DBG_info, "image does not cover wanted surface part\n"); + return min_quality; /* file does not cover wanted surface part */ + } + + DBG(DBG_info, "quality = %d\n", quality); + + if ( ((float) min_quality / (quality+1)) > 1.05) /* already loaded image has better quality */ + { + DBG(DBG_info, "already loaded image has higher quality\n"); + return min_quality; + } + + if ( ((float) min_quality / (quality+1)) > 0.95) /* qualities are comparable */ + { + if (*min_time > time) /* take more recent scan */ + { + DBG(DBG_info, "images have comparable quality, already loaded is more up to date\n"); + return min_quality; + } + DBG(DBG_info, "images have comparable quality, this image is more up to date\n"); + } + else + { + DBG(DBG_info, "image has best quality\n"); + } + } + else /* read startimage or calibrationimage */ + { + xoffset = 0; + yoffset = 0; + width = image_width; + height = image_height; + } + + if (max_val == 65535) + { + p->params.depth = 16; + } + else + { + p->params.depth = 8; + } + + p->image_width = width; + p->image_height = height; + + if (preview_get_memory(p)) + { + return min_quality; /* error allocating memory */ + } + + if (p->params.depth == 16) + { + fseek(in, yoffset * image_width * 6, SEEK_CUR); /* skip unused lines */ + + imagep = p->image_data_raw; + + for (y = yoffset; y < yoffset + height; y++) + { + fseek(in, xoffset * 6, SEEK_CUR); /* skip unused pixel left of area */ + + nread = fread(imagep, 6, width, in); + imagep += width * 3; /* imagep is a pointer to a 2 byte value, so we use 3 instead 6 here */ + + fseek(in, (image_width - width - xoffset) * 6, SEEK_CUR); /* skip unused pixel right of area */ + } + } + else /* depth = 8 */ + { + fseek(in, yoffset * image_width * 3, SEEK_CUR); /* skip unused lines */ + + imagep = p->image_data_raw; + + for (y = yoffset; y < yoffset + height; y++) + { + fseek(in, xoffset * 3, SEEK_CUR); /* skip unused pixel left of area */ + + imagepx = imagep; + for (x = 0; x < width; x++) + { + *imagepx++ = ((guint16) fgetc(in)) * 256; /* transfrom to 16 bit image with correct byte order */ + *imagepx++ = ((guint16) fgetc(in)) * 256; + *imagepx++ = ((guint16) fgetc(in)) * 256; + } + imagep += width * 3; /* imagep is a pointer to a 2 byte value, so we use 3 instead 6 here */ + + fseek(in, (image_width - width - xoffset) * 3, SEEK_CUR); /* skip unused pixel right of area */ + } + } + + p->image_x = width; + p->image_y = height; + + p->image_surface[0] = p->surface[p->index_xmin]; + p->image_surface[1] = p->surface[p->index_ymin]; + p->image_surface[2] = p->surface[p->index_xmax]; + p->image_surface[3] = p->surface[p->index_ymax]; + + *min_time = time; + + return quality; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_restore_image(Preview *p) +{ + FILE *in; + int quality = 0; + int time = 0; + int level; + + DBG(DBG_proc, "preview_restore_image\n"); + + p->startimage = 0; + + if (p->calibration) + { + char filename[PATH_MAX]; + + DBG(DBG_proc, "calibration mode\n"); + xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-calibration", 0, ".pnm", XSANE_PATH_SYSTEM); + in = fopen(filename, "rb"); /* read binary (b for win32) */ + if (in) + { + quality = preview_restore_image_from_file(p, in, -1, &time); + fclose(in); + } + } + else + { + /* See whether there is a saved preview and load it if present: */ + for(level = 2; level >= 0; level--) + { + if (p->filename[level]) + { + in = fopen(p->filename[level], "rb"); /* read binary (b for win32) */ + if (in) + { + quality = preview_restore_image_from_file(p, in, quality, &time); + fclose(in); + } + } + } + + if (quality == 0) /* no image found, read startimage */ + { + char filename[PATH_MAX]; + + DBG(DBG_proc, "no suitable image available, using startimage\n"); + xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-startimage", 0, ".pnm", XSANE_PATH_SYSTEM); + in = fopen(filename, "rb"); /* read binary (b for win32) */ + if (in) + { + quality = preview_restore_image_from_file(p, in, -1, &time); + fclose(in); + } + else + { + guint16 *imagep; + + DBG(DBG_error0, "ERROR: xsane-startimage not found. Looks like xsane is not installed correct.\n"); + + p->image_width = 1; + p->image_height = 1; + p->params.depth = 16; + + preview_get_memory(p); + + imagep = p->image_data_raw; + *imagep++ = 65535; + *imagep++ = 00000; + *imagep++ = 00000; + + p->image_x = p->image_width; + p->image_y = p->image_height; + + p->image_surface[0] = p->surface[p->index_xmin]; + p->image_surface[1] = p->surface[p->index_ymin]; + p->image_surface[2] = p->surface[p->index_xmax]; + p->image_surface[3] = p->surface[p->index_ymax]; + } + p->startimage = 1; + } + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_hold_event_handler(gpointer data) +{ + Preview *p = data; + + DBG(DBG_proc, "preview_hold_event_handler\n"); + + preview_draw_selection(p); + p->gamma_functions_interruptable = TRUE; + preview_establish_selection(p); + p->gamma_functions_interruptable = FALSE; + + gtk_timeout_remove(p->hold_timer); + p->hold_timer = 0; + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_motion_event_handler(GtkWidget *window, GdkEvent *event, gpointer data) +{ + Preview *p = data; + GdkCursor *cursor; + float preview_selection[4]; + float preview_x, preview_y; + float xscale, yscale; + int cursornr; + + DBG(DBG_proc, "preview_motion_event_handler\n"); + + /* preview selection (device) -> cursor-position (window) */ + preview_transform_coordinates_device_to_window(p, p->selection.coordinate, preview_selection); + + /* cursor-prosition (window) -> preview coordinate (device) */ + preview_transform_coordinate_window_to_device(p, event->button.x, event->button.y, &preview_x, &preview_y); + + preview_get_scale_device_to_window(p, &xscale, &yscale); + + if (!p->scanning) + { + switch (((GdkEventMotion *)event)->state & + GDK_Num_Lock & GDK_Caps_Lock & GDK_Shift_Lock & GDK_Scroll_Lock) /* mask all Locks */ + { + case 256: /* left button */ + + DBG(DBG_info2, "left button\n"); + + if ( (p->selection_drag) || (p->selection_drag_edge) ) + { + p->selection.active = TRUE; + p->selection.coordinate[p->selection_xedge] = preview_x; + p->selection.coordinate[p->selection_yedge] = preview_y; + + preview_order_selection(p); + preview_bound_selection(p); + + if (preferences.gtk_update_policy == GTK_UPDATE_CONTINUOUS) + { + if (!p->hold_timer) /* hold timer active? then remove it, we had a motion */ + { + p->hold_timer = gtk_timeout_add(XSANE_CONTINUOUS_HOLD_TIME, preview_hold_event_handler, (gpointer *) p); + } + preview_update_maximum_output_size(p); + preview_draw_selection(p); + } + else if (preferences.gtk_update_policy == GTK_UPDATE_DELAYED) + { + /* call preview_hold_event_hanlder if mouse is not moved for ??? ms */ + if (p->hold_timer) /* hold timer active? then remove it, we had a motion */ + { + gtk_timeout_remove(p->hold_timer); + p->hold_timer = 0; + } + p->hold_timer = gtk_timeout_add(XSANE_HOLD_TIME, preview_hold_event_handler, (gpointer *) p); + preview_update_maximum_output_size(p); + preview_draw_selection(p); + } + else /* discontinuous */ + { + preview_update_maximum_output_size(p); + preview_draw_selection(p); /* only draw selection, do not update backend geometry options */ + } + } + + cursornr = p->cursornr; + + if ( (p->selection_xedge != -1) && (p->selection_yedge != -1) ) /* move corner */ + { + if ( ( (preview_selection[0] - SELECTION_RANGE_OUT < event->button.x) && + (event->button.x < preview_selection[0] + SELECTION_RANGE_IN) ) && /* left */ + ( (preview_selection[1] - SELECTION_RANGE_OUT < event->button.y) && + (event->button.y < preview_selection[1] + SELECTION_RANGE_IN) ) ) /* top */ + { + cursornr = GDK_TOP_LEFT_CORNER; + } + else if ( ( (preview_selection[2] - SELECTION_RANGE_IN < event->button.x) && + (event->button.x < preview_selection[2] + SELECTION_RANGE_OUT) ) && /* right */ + ( (preview_selection[1] - SELECTION_RANGE_OUT < event->button.y) && + (event->button.y < preview_selection[1] + SELECTION_RANGE_IN) ) ) /* top */ + { + cursornr = GDK_TOP_RIGHT_CORNER; + } + else if ( ( (preview_selection[0] - SELECTION_RANGE_OUT < event->button.x) && + (event->button.x < preview_selection[0] + SELECTION_RANGE_IN) ) && /* left */ + ( (preview_selection[3] - SELECTION_RANGE_IN < event->button.y) && + (event->button.y < preview_selection[3] + SELECTION_RANGE_OUT) ) ) /* bottom */ + { + cursornr = GDK_BOTTOM_LEFT_CORNER; + } + else if ( ( (preview_selection[2] - SELECTION_RANGE_IN < event->button.x) && + (event->button.x < preview_selection[2] + SELECTION_RANGE_OUT) ) && /* right */ + ( (preview_selection[3] - SELECTION_RANGE_IN < event->button.y) && + (event->button.y < preview_selection[3] + SELECTION_RANGE_OUT) ) ) /* bottom */ + { + cursornr = GDK_BOTTOM_RIGHT_CORNER; + } + } + else if ( (preview_selection[0] - SELECTION_RANGE_OUT < event->button.x) && + (event->button.x < preview_selection[0] + SELECTION_RANGE_IN) ) /* left */ + { + if (cursornr == GDK_RIGHT_SIDE) + { + cursornr = GDK_LEFT_SIDE; + } + } + else if ( (preview_selection[2] - SELECTION_RANGE_IN < event->button.x) && + (event->button.x < preview_selection[2] + SELECTION_RANGE_OUT) ) /* right */ + { + if (cursornr == GDK_LEFT_SIDE) + { + cursornr = GDK_RIGHT_SIDE; + } + } + else if ( (preview_selection[1] - SELECTION_RANGE_OUT < event->button.y) && + (event->button.y < preview_selection[1] + SELECTION_RANGE_IN) ) /* top */ + { + if (cursornr == GDK_BOTTOM_SIDE) + { + cursornr = GDK_TOP_SIDE; + } + } + else if ( (preview_selection[3] - SELECTION_RANGE_IN < event->button.y) && + (event->button.y < preview_selection[3] + SELECTION_RANGE_OUT) ) /* bottom */ + { + if (cursornr == GDK_TOP_SIDE) + { + cursornr = GDK_BOTTOM_SIDE; + } + } + + if (cursornr != p->cursornr) + { + cursor = gdk_cursor_new(cursornr); /* set curosr */ + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = cursornr; + } + break; + + case 512: /* middle button */ + case 1024: /* right button */ + DBG(DBG_info2, "middle or right button\n"); + + if (p->selection_drag) + { + double dx, dy; + + switch (p->rotation) + { + case 0: /* 0 degree */ + default: + dx = (p->selection_xpos - event->motion.x) / xscale; + dy = (p->selection_ypos - event->motion.y) / yscale; + break; + + case 1: /* 90 degree */ + dx = (event->motion.x - p->selection_xpos) / xscale; + dy = (p->selection_ypos - event->motion.y) / yscale; + break; + + case 2: /* 180 degree */ + dx = (event->motion.x - p->selection_xpos) / xscale; + dy = (event->motion.y - p->selection_ypos) / yscale; + break; + + case 3: /* 270 degree */ + dx = (p->selection_xpos - event->motion.x) / xscale; + dy = (event->motion.y - p->selection_ypos) / yscale; + break; + + case 4: /* 0 degree, x mirror */ + dx = (event->motion.x - p->selection_xpos) / xscale; + dy = (p->selection_ypos - event->motion.y) / yscale; + break; + + case 5: /* 90 degree, x mirror */ + dx = (p->selection_xpos - event->motion.x) / xscale; + dy = (p->selection_ypos - event->motion.y) / yscale; + break; + + case 6: /* 180 degree, x mirror */ + dx = (p->selection_xpos - event->motion.x) / xscale; + dy = (event->motion.y - p->selection_ypos) / yscale; + break; + + case 7: /* 270 degree, x mirror */ + dx = (event->motion.x - p->selection_xpos) / xscale; + dy = (event->motion.y - p->selection_ypos) / yscale; + break; + } + + p->selection_xpos = event->motion.x; + p->selection_ypos = event->motion.y; + + if (dx > p->selection.coordinate[p->index_xmin] - p->scanner_surface[p->index_xmin]) + { + dx = p->selection.coordinate[p->index_xmin] - p->scanner_surface[p->index_xmin]; + } + + if (dy > p->selection.coordinate[p->index_ymin] - p->scanner_surface[p->index_ymin]) + { + dy = p->selection.coordinate[p->index_ymin] - p->scanner_surface[p->index_ymin]; + } + + if (dx < p->selection.coordinate[p->index_xmax] - p->scanner_surface[p->index_xmax]) + { + dx = p->selection.coordinate[p->index_xmax] - p->scanner_surface[p->index_xmax]; + } + + if (dy < p->selection.coordinate[p->index_ymax] - p->scanner_surface[p->index_ymax]) + { + dy = p->selection.coordinate[p->index_ymax] - p->scanner_surface[p->index_ymax]; + } + + p->selection.active = TRUE; + p->selection.coordinate[0] -= dx; + p->selection.coordinate[1] -= dy; + p->selection.coordinate[2] -= dx; + p->selection.coordinate[3] -= dy; + + if (preferences.gtk_update_policy == GTK_UPDATE_CONTINUOUS) + { + if (!p->hold_timer) /* hold timer active? then remove it, we had a motion */ + { + p->hold_timer = gtk_timeout_add(XSANE_CONTINUOUS_HOLD_TIME, preview_hold_event_handler, (gpointer *) p); + } + preview_update_maximum_output_size(p); + preview_draw_selection(p); + } + else if (preferences.gtk_update_policy == GTK_UPDATE_DELAYED) + { + if (p->hold_timer) /* hold timer active? then remove it, we had a motion */ + { + gtk_timeout_remove(p->hold_timer); + p->hold_timer = 0; + } + p->hold_timer = gtk_timeout_add (XSANE_HOLD_TIME, preview_hold_event_handler, (gpointer *) p); + preview_update_maximum_output_size(p); + preview_draw_selection(p); + } + else /* discontinuous */ + { + preview_update_maximum_output_size(p); + preview_draw_selection(p); + } + } + break; + + default: + if ( ( (preview_selection[0] - SELECTION_RANGE_OUT < event->button.x) && + (event->button.x < preview_selection[0] + SELECTION_RANGE_IN) ) && /* left */ + ( (preview_selection[1] - SELECTION_RANGE_OUT < event->button.y) && + (event->button.y < preview_selection[1] + SELECTION_RANGE_IN) ) ) /* top */ + { + cursornr = GDK_TOP_LEFT_CORNER; + } + else if ( ( (preview_selection[2] - SELECTION_RANGE_IN < event->button.x) && + (event->button.x < preview_selection[2] + SELECTION_RANGE_OUT) ) && /* right */ + ( (preview_selection[1] - SELECTION_RANGE_OUT < event->button.y) && + (event->button.y < preview_selection[1] + SELECTION_RANGE_IN) ) ) /* top */ + { + cursornr = GDK_TOP_RIGHT_CORNER; + } + else if ( ( (preview_selection[0] - SELECTION_RANGE_OUT < event->button.x) && + (event->button.x < preview_selection[0] + SELECTION_RANGE_IN) ) && /* left */ + ( (preview_selection[3] - SELECTION_RANGE_IN < event->button.y) && + (event->button.y < preview_selection[3] + SELECTION_RANGE_OUT) ) ) /* bottom */ + { + cursornr = GDK_BOTTOM_LEFT_CORNER; + } + else if ( ( (preview_selection[2] - SELECTION_RANGE_IN < event->button.x) && + (event->button.x < preview_selection[2] + SELECTION_RANGE_OUT) ) && /* right */ + ( (preview_selection[3] - SELECTION_RANGE_IN < event->button.y) && + (event->button.y < preview_selection[3] + SELECTION_RANGE_OUT) ) ) /* bottom */ + { + cursornr = GDK_BOTTOM_RIGHT_CORNER; + } + else if ( ( (preview_selection[0] - SELECTION_RANGE_OUT < event->button.x) && + (event->button.x < preview_selection[0] + SELECTION_RANGE_IN) ) && /* left */ + ( (event->button.y > preview_selection[1]) && (event->button.y < preview_selection[3]) ) ) /* in height */ + { + cursornr = GDK_LEFT_SIDE; + } + else if ( ( (preview_selection[2] - SELECTION_RANGE_IN < event->button.x) && + (event->button.x < preview_selection[2] + SELECTION_RANGE_OUT) ) && /* right */ + ( (event->button.y > preview_selection[1]) && (event->button.y < preview_selection[3]) ) ) /* in height */ + { + cursornr = GDK_RIGHT_SIDE; + } + else if ( ( (preview_selection[1] - SELECTION_RANGE_OUT < event->button.y) && + (event->button.y < preview_selection[1] + SELECTION_RANGE_IN) ) && /* top */ + ( (event->button.x > preview_selection[0]) && (event->button.x < preview_selection[2]) ) ) /* in width */ + { + cursornr = GDK_TOP_SIDE; + } + else if ( ( (preview_selection[3] - SELECTION_RANGE_IN < event->button.y) && + (event->button.y < preview_selection[3] + SELECTION_RANGE_OUT) ) && /* bottom */ + ( (event->button.x > preview_selection[0]) && (event->button.x < preview_selection[2]) ) ) /* in width */ + { + cursornr = GDK_BOTTOM_SIDE; + } + else + { + cursornr = XSANE_CURSOR_PREVIEW; + } + + if ((cursornr != p->cursornr) && (p->cursornr != -1)) + { + cursor = gdk_cursor_new(cursornr); /* set curosr */ + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = cursornr; + } + break; + } + } + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_button_press_event_handler(GtkWidget *window, GdkEvent *event, gpointer data) +{ + Preview *p = data; + GdkCursor *cursor; + float preview_selection[4]; + float preview_x, preview_y; + int cursornr; + + DBG(DBG_proc, "preview_button_press_event_handler\n"); + + /* preview selection (device) -> cursor-position (window) */ + preview_transform_coordinates_device_to_window(p, p->selection.coordinate, preview_selection); + + /* cursor-prosition (window) -> preview coordinate (device) */ + preview_transform_coordinate_window_to_device(p, event->button.x, event->button.y, &preview_x, &preview_y); + + if (!p->scanning) + { + switch (p->mode) + { + case MODE_PIPETTE_WHITE: + { + DBG(DBG_info, "pipette white mode\n"); + if ( ( (((GdkEventButton *)event)->button == 1) || (((GdkEventButton *)event)->button == 2) ) && (p->image_data_raw) ) /* left or middle button */ + { + int r=255, g=255, b=255; /* preset color to white */ + + preview_get_color(p, event->button.x, event->button.y, preferences.preview_pipette_range, &r, &g, &b); + + xsane.slider_gray.value[2] = sqrt( (r*r+g*g+b*b) / 3)/2.55; + + if ( (!xsane.enhancement_rgb_default) && (((GdkEventButton *)event)->button == 2) ) /* middle button */ + { + xsane.slider_red.value[2] = r/2.55; + xsane.slider_green.value[2] = g/2.55; + xsane.slider_blue.value[2] = b/2.55; + } + else + { + xsane.slider_red.value[2] = xsane.slider_gray.value[2]; + xsane.slider_green.value[2] = xsane.slider_gray.value[2]; + xsane.slider_blue.value[2] = xsane.slider_gray.value[2]; + } + + if (xsane.slider_gray.value[2] < 2) + { + xsane.slider_gray.value[2] = 2; + } + if (xsane.slider_gray.value[1] >= xsane.slider_gray.value[2]) + { + xsane.slider_gray.value[1] = xsane.slider_gray.value[2]-1; + if (xsane.slider_gray.value[0] >= xsane.slider_gray.value[1]) + { + xsane.slider_gray.value[0] = xsane.slider_gray.value[1]-1; + } + } + + if (xsane.slider_red.value[2] < 2) + { + xsane.slider_red.value[2] = 2; + } + if (xsane.slider_red.value[1] >= xsane.slider_red.value[2]) + { + xsane.slider_red.value[1] = xsane.slider_red.value[2]-1; + if (xsane.slider_red.value[0] >= xsane.slider_red.value[1]) + { + xsane.slider_red.value[0] = xsane.slider_red.value[1]-1; + } + } + + if (xsane.slider_green.value[2] < 2) + { + xsane.slider_green.value[2] = 2; + } + if (xsane.slider_green.value[1] >= xsane.slider_green.value[2]) + { + xsane.slider_green.value[1] = xsane.slider_green.value[2]-1; + if (xsane.slider_green.value[0] >= xsane.slider_green.value[1]) + { + xsane.slider_green.value[0] = xsane.slider_green.value[1]-1; + } + } + + if (xsane.slider_blue.value[2] < 2) + { + xsane.slider_blue.value[2] = 2; + } + if (xsane.slider_blue.value[1] >= xsane.slider_blue.value[2]) + { + xsane.slider_blue.value[1] = xsane.slider_blue.value[2]-1; + if (xsane.slider_blue.value[0] >= xsane.slider_blue.value[1]) + { + xsane.slider_blue.value[0] = xsane.slider_blue.value[1]-1; + } + } + + xsane_enhancement_by_histogram(TRUE); + } + + p->mode = MODE_NORMAL; + + cursor = gdk_cursor_new(XSANE_CURSOR_PREVIEW); + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = XSANE_CURSOR_PREVIEW; + } + break; + + case MODE_PIPETTE_GRAY: + { + DBG(DBG_info, "pipette gray mode\n"); + + if ( ( (((GdkEventButton *)event)->button == 1) || (((GdkEventButton *)event)->button == 2) ) && (p->image_data_raw) ) /* left or middle button */ + { + int r=128, g=128, b=128; /* preset color to gray */ + + preview_get_color(p, event->button.x, event->button.y, preferences.preview_pipette_range, &r, &g, &b); + + xsane.slider_gray.value[1] = sqrt( (r*r+g*g+b*b) / 3)/2.55; + + if ( (!xsane.enhancement_rgb_default) && (((GdkEventButton *)event)->button == 2) ) /* middle button */ + { + xsane.slider_red.value[1] = r/2.55; + xsane.slider_green.value[1] = g/2.55; + xsane.slider_blue.value[1] = b/2.55; + } + else + { + xsane.slider_red.value[1] = xsane.slider_gray.value[1]; + xsane.slider_green.value[1] = xsane.slider_gray.value[1]; + xsane.slider_blue.value[1] = xsane.slider_gray.value[1]; + } + + if (xsane.slider_gray.value[1] == 0) + { + xsane.slider_gray.value[1] += 1; + } + if (xsane.slider_gray.value[1] == 100) + { + xsane.slider_gray.value[1] -= 1; + } + if (xsane.slider_gray.value[1] >= xsane.slider_gray.value[2]) + { + xsane.slider_gray.value[2] = xsane.slider_gray.value[1]+1; + } + if (xsane.slider_gray.value[1] <= xsane.slider_gray.value[0]) + { + xsane.slider_gray.value[0] = xsane.slider_gray.value[1]-1; + } + + if (xsane.slider_red.value[1] == 0) + { + xsane.slider_red.value[1] += 1; + } + if (xsane.slider_red.value[1] == 100) + { + xsane.slider_red.value[1] -= 1; + } + if (xsane.slider_red.value[1] >= xsane.slider_red.value[2]) + { + xsane.slider_red.value[2] = xsane.slider_red.value[1]+1; + } + if (xsane.slider_red.value[1] <= xsane.slider_red.value[0]) + { + xsane.slider_red.value[0] = xsane.slider_red.value[1]-1; + } + + if (xsane.slider_green.value[1] == 0) + { + xsane.slider_green.value[1] += 1; + } + if (xsane.slider_green.value[1] == 100) + { + xsane.slider_green.value[1] -= 1; + } + if (xsane.slider_green.value[1] >= xsane.slider_green.value[2]) + { + xsane.slider_green.value[2] = xsane.slider_green.value[1]+1; + } + if (xsane.slider_green.value[1] <= xsane.slider_green.value[0]) + { + xsane.slider_green.value[0] = xsane.slider_green.value[1]-1; + } + + if (xsane.slider_blue.value[1] == 0) + { + xsane.slider_blue.value[1] += 1; + } + if (xsane.slider_blue.value[1] == 100) + { + xsane.slider_blue.value[1] -= 1; + } + if (xsane.slider_blue.value[1] >= xsane.slider_blue.value[2]) + { + xsane.slider_blue.value[2] = xsane.slider_blue.value[1]+1; + } + if (xsane.slider_blue.value[1] <= xsane.slider_blue.value[0]) + { + xsane.slider_blue.value[0] = xsane.slider_blue.value[1]-1; + } + + xsane_enhancement_by_histogram(TRUE); + } + + p->mode = MODE_NORMAL; + + cursor = gdk_cursor_new(XSANE_CURSOR_PREVIEW); + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = XSANE_CURSOR_PREVIEW; + } + break; + + case MODE_PIPETTE_BLACK: + { + DBG(DBG_info, "pipette black mode\n"); + + if ( ( (((GdkEventButton *)event)->button == 1) || (((GdkEventButton *)event)->button == 2) ) && + (p->image_data_raw) ) /* left or middle button */ + { + int r=0, g=0, b=0; /* preset color to black */ + + preview_get_color(p, event->button.x, event->button.y, preferences.preview_pipette_range, &r, &g, &b); + + xsane.slider_gray.value[0] = sqrt( (r*r+g*g+b*b) / 3)/2.55; + + if ( (!xsane.enhancement_rgb_default) && (((GdkEventButton *)event)->button == 2) ) /* middle button */ + { + xsane.slider_red.value[0] = r/2.55; + xsane.slider_green.value[0] = g/2.55; + xsane.slider_blue.value[0] = b/2.55; + } + else + { + xsane.slider_red.value[0] = xsane.slider_gray.value[0]; + xsane.slider_green.value[0] = xsane.slider_gray.value[0]; + xsane.slider_blue.value[0] = xsane.slider_gray.value[0]; + } + + if (xsane.slider_gray.value[0] > 98) + { + xsane.slider_gray.value[0] = 98; + } + if (xsane.slider_gray.value[1] <= xsane.slider_gray.value[0]) + { + xsane.slider_gray.value[1] = xsane.slider_gray.value[0]+1; + if (xsane.slider_gray.value[2] <= xsane.slider_gray.value[1]) + { + xsane.slider_gray.value[2] = xsane.slider_gray.value[1]+1; + } + } + + if (xsane.slider_red.value[0] > 98) + { + xsane.slider_red.value[0] = 98; + } + if (xsane.slider_red.value[1] <= xsane.slider_red.value[0]) + { + xsane.slider_red.value[1] = xsane.slider_red.value[0]+1; + if (xsane.slider_red.value[2] <= xsane.slider_red.value[1]) + { + xsane.slider_red.value[2] = xsane.slider_red.value[1]+1; + } + } + + if (xsane.slider_green.value[0] > 98) + { + xsane.slider_green.value[0] = 98; + } + if (xsane.slider_green.value[1] <= xsane.slider_green.value[0]) + { + xsane.slider_green.value[1] = xsane.slider_green.value[0]+1; + if (xsane.slider_green.value[2] <= xsane.slider_green.value[1]) + { + xsane.slider_green.value[2] = xsane.slider_green.value[1]+1; + } + } + + if (xsane.slider_blue.value[0] > 98) + { + xsane.slider_blue.value[0] = 98; + } + if (xsane.slider_blue.value[1] <= xsane.slider_blue.value[0]) + { + xsane.slider_blue.value[1] = xsane.slider_blue.value[0]+1; + if (xsane.slider_blue.value[2] <= xsane.slider_blue.value[1]) + { + xsane.slider_blue.value[2] = xsane.slider_blue.value[1]+1; + } + } + + xsane_enhancement_by_histogram(TRUE); + } + + p->mode = MODE_NORMAL; + + cursor = gdk_cursor_new(XSANE_CURSOR_PREVIEW); + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = XSANE_CURSOR_PREVIEW; + } + break; + + case MODE_NORMAL: + { + DBG(DBG_info, "normal mode\n"); + + if (p->show_selection) + { + switch (((GdkEventButton *)event)->button) + { + case 1: /* left button: define selection area */ + DBG(DBG_info, "left button\n"); + + p->selection_xedge = -1; + if ( (preview_selection[0] - SELECTION_RANGE_OUT < event->button.x) && + (event->button.x < preview_selection[0] + SELECTION_RANGE_IN) ) /* left */ + { + DBG(DBG_info, "-left\n"); + p->selection_xedge = 0; + } + else if ( (preview_selection[2] - SELECTION_RANGE_IN < event->button.x) && + (event->button.x < preview_selection[2] + SELECTION_RANGE_OUT) ) /* right */ + { + DBG(DBG_info, "-right\n"); + p->selection_xedge = 2; + } + + p->selection_yedge = -1; + if ( (preview_selection[1] - SELECTION_RANGE_OUT < event->button.y) && + (event->button.y < preview_selection[1] + SELECTION_RANGE_IN) ) /* top */ + { + DBG(DBG_info, "-top\n"); + p->selection_yedge = 1; + } + else if ( (preview_selection[3] - SELECTION_RANGE_IN < event->button.y) && + (event->button.y < preview_selection[3] + SELECTION_RANGE_OUT) ) /* bottom */ + { + DBG(DBG_info, "-bottom\n"); + p->selection_yedge = 3; + } + + if ( (p->selection_xedge != -1) && (p->selection_yedge != -1) ) /* move corner */ + { + DBG(DBG_info, "-move corner (%f, %f)\n", preview_x, preview_y); + p->selection_drag_edge = TRUE; + p->selection.coordinate[p->selection_xedge] = preview_x; + p->selection.coordinate[p->selection_yedge] = preview_y; + preview_draw_selection(p); + } + else if ( (p->selection_xedge != -1) && (event->button.y > preview_selection[1]) + && (event->button.y < preview_selection[3]) ) /* move x-edge */ + { + DBG(DBG_info, "-move x-edge %f\n", preview_x); + p->selection_drag_edge = TRUE; + p->selection.coordinate[p->selection_xedge] = preview_x; + preview_draw_selection(p); + } + else if ( (p->selection_yedge != -1) && (event->button.x > preview_selection[0]) + && (event->button.x < preview_selection[2]) ) /* move y-edge */ + { + DBG(DBG_info, "-move y-edge %f\n", preview_y); + p->selection_drag_edge = TRUE; + p->selection.coordinate[p->selection_yedge] = preview_y; + preview_draw_selection(p); + } + else /* select new area */ + { + DBG(DBG_info, "-define new area (%f, %f)\n", preview_x, preview_y); + p->selection_xedge = 2; + p->selection_yedge = 3; + p->selection.coordinate[0] = preview_x; + p->selection.coordinate[1] = preview_y; + p->selection_drag = TRUE; + + cursornr = GDK_CROSS; + cursor = gdk_cursor_new(cursornr); /* set curosr */ + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = cursornr; + } + break; + + case 2: /* middle button */ + case 3: /* right button */ + DBG(DBG_info, "middle or right button\n"); + + if ( (preview_selection[0]-SELECTION_RANGE_OUT < event->button.x) && + (preview_selection[2]+SELECTION_RANGE_OUT > event->button.x) && + (preview_selection[1]-SELECTION_RANGE_OUT < event->button.y) && + (preview_selection[3]+SELECTION_RANGE_OUT > event->button.y) ) + { + DBG(DBG_info, "move selection area\n"); + p->selection_drag = TRUE; + p->selection_xpos = event->button.x; + p->selection_ypos = event->button.y; + + cursornr = GDK_HAND2; + cursor = gdk_cursor_new(cursornr); /* set curosr */ + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = cursornr; + } + break; + + default: + break; + } + } + } + } + } + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_button_release_event_handler(GtkWidget *window, GdkEvent *event, gpointer data) +{ + Preview *p = data; + GdkCursor *cursor; + float preview_selection[4]; + int cursornr; + + DBG(DBG_proc, "preview_button_release_event_handler\n"); + + /* preview selection (device) -> cursor-position (window) */ + preview_transform_coordinates_device_to_window(p, p->selection.coordinate, preview_selection); + + if (!p->scanning) + { + if (p->show_selection) + { + switch (((GdkEventButton *)event)->button) + { + case 1: /* left button */ + case 2: /* middle button */ + case 3: /* right button */ + if (p->selection_drag) + { + DBG(DBG_info, "selection finished\n"); + cursornr = XSANE_CURSOR_PREVIEW; + cursor = gdk_cursor_new(cursornr); /* set curosr */ + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = cursornr; + } + + preview_draw_selection(p); + preview_establish_selection(p); + + p->selection_drag_edge = FALSE; + p->selection_drag = FALSE; + break; + + default: + break; + } + } + } + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ +static int expose_event_selection_active, expose_event_selection_maximum_active; + +static gint preview_expose_event_handler_start(GtkWidget *window, GdkEvent *event, gpointer data) +{ + Preview *p = data; + GdkColor color; + GdkColormap *colormap; + + DBG(DBG_proc, "preview_expose_event_handler_start\n"); + + if (event->type == GDK_EXPOSE) + { + if (!p->gc_selection) + { + DBG(DBG_info, "defining line styles for selection and page frames\n"); + colormap = gdk_window_get_colormap(p->window->window); + + p->gc_selection = gdk_gc_new(p->window->window); + gdk_gc_set_function(p->gc_selection, GDK_INVERT); + gdk_gc_set_line_attributes(p->gc_selection, 1, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER); + + p->gc_selection_maximum = gdk_gc_new(p->window->window); + gdk_gc_set_function(p->gc_selection_maximum, GDK_XOR); + gdk_gc_set_line_attributes(p->gc_selection_maximum, 1, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER); + + color.red = 0; + color.green = 65535; + color.blue = 30000; + gdk_color_alloc(colormap, &color); + gdk_gc_set_foreground(p->gc_selection_maximum, &color); + } + else + { + expose_event_selection_active = p->selection.active; + expose_event_selection_maximum_active = p->selection_maximum.active; + p->selection_maximum.active = FALSE; + p->selection.active = FALSE; /* do not draw new selections */ + p->selection_maximum.active = FALSE; + preview_draw_selection(p); /* undraw selections */ + } + } + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_expose_event_handler_end(GtkWidget *window, GdkEvent *event, gpointer data) +{ + Preview *p = data; + GdkColor color; + GdkColormap *colormap; + + DBG(DBG_proc, "preview_expose_event_handler_end\n"); + + if (event->type == GDK_EXPOSE) + { + if (!p->gc_selection) + { + DBG(DBG_info, "defining line styles for selection and page frames\n"); + colormap = gdk_window_get_colormap(p->window->window); + + p->gc_selection = gdk_gc_new(p->window->window); + gdk_gc_set_function(p->gc_selection, GDK_INVERT); + gdk_gc_set_line_attributes(p->gc_selection, 1, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER); + + p->gc_selection_maximum = gdk_gc_new(p->window->window); + gdk_gc_set_function(p->gc_selection_maximum, GDK_XOR); + gdk_gc_set_line_attributes(p->gc_selection_maximum, 1, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER); + + color.red = 0; + color.green = 65535; + color.blue = 30000; + gdk_color_alloc(colormap, &color); + gdk_gc_set_foreground(p->gc_selection_maximum, &color); + } + else + { + p->selection.active = expose_event_selection_active; + p->selection_maximum.active = expose_event_selection_maximum_active; + preview_draw_selection(p); /* draw selections again */ + } + } + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_start_button_clicked(GtkWidget *widget, gpointer data) +{ + DBG(DBG_proc, "preview_start_button_clicked\n"); + + preview_scan(data); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_cancel_button_clicked(GtkWidget *widget, gpointer data) +{ + Preview *p = (Preview *) data; + + DBG(DBG_proc, "preview_cancel_button_clicked\n"); + + sane_cancel(xsane.dev); + gtk_widget_set_sensitive(p->cancel, FALSE); /* disable cancel button */ + + /* we have to make sure that xsane does detect that the scan has been cancled */ + /* but the select_fd does not make sure that preview_read_image_data is called */ + /* when the select_fd is closed by the backend, so we have to make sure that */ + /* preview_read_image_data is called */ + preview_read_image_data(p, -1, GDK_INPUT_READ); + + p->scan_incomplete = TRUE; + preview_display_valid(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_create_preset_area_menu(Preview *p, int selection) +{ + int i; + GtkWidget *preset_area_menu, *preset_area_item; + + preset_area_menu = gtk_menu_new(); + + for (i = 0; i < preferences.preset_area_definitions; ++i) + { + preset_area_item = gtk_menu_item_new_with_label(preferences.preset_area[i]->name); + gtk_container_add(GTK_CONTAINER(preset_area_menu), preset_area_item); + gtk_signal_connect(GTK_OBJECT(preset_area_item), "button_press_event", (GtkSignalFunc) preview_preset_area_context_menu_callback, p); + gtk_signal_connect(GTK_OBJECT(preset_area_item), "activate", (GtkSignalFunc) preview_preset_area_callback, p); + gtk_object_set_data(GTK_OBJECT(preset_area_item), "Selection", (void *) i); + gtk_object_set_data(GTK_OBJECT(preset_area_item), "Preview", (void *) p); + + gtk_widget_show(preset_area_item); + } + + gtk_option_menu_set_menu(GTK_OPTION_MENU(p->preset_area_option_menu), preset_area_menu); + gtk_option_menu_set_history(GTK_OPTION_MENU(p->preset_area_option_menu), selection); + + gtk_widget_show(preset_area_menu); + gtk_widget_queue_draw(p->preset_area_option_menu); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_generate_preview_filenames(Preview *p) +{ + char filename[PATH_MAX]; + char buf[256]; + int error_flag = 0; + int i; + + DBG(DBG_proc, "preview_generate_preview_filenames\n"); + + for(i=0; i<=2; i++) /* create random filenames for previews */ + { + if (preview_make_image_path(p, sizeof(filename), filename, i)>=0) + { + FILE *testfile; + + testfile = fopen(filename, "wb"); + if (testfile) + { + fclose(testfile); + p->filename[i] = strdup(filename);/* store filename */ + DBG(DBG_info, "preview file %s created\n", filename); + } + else + { + p->filename[i] = NULL; /* mark filename does not exist */ + DBG(DBG_error, "ERROR: could not create preview file %s\n", filename); + error_flag = 1; + } + } + else + { + DBG(DBG_error, "ERROR: could not create filename for preview level %d\n", i); + p->filename[i] = NULL; + error_flag = 2; + } + } + + if (error_flag == 1) + { + snprintf(buf, sizeof(buf), ERR_CREATE_PREVIEW_FILE); + xsane_back_gtk_error(buf, TRUE); + } + else if (error_flag == 2) + { + snprintf(buf, sizeof(buf), ERR_CREATE_PREVIEW_FILENAME); + xsane_back_gtk_error(buf, TRUE); + } + + return; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +Preview *preview_new(void) +{ + GtkWidget *table, *frame; + GtkSignalFunc signal_func; + GtkWidgetClass *class; + GtkWidget *vbox, *hbox; + GdkCursor *cursor; + GtkWidget *preset_area_option_menu; + GtkWidget *rotation_option_menu, *rotation_menu, *rotation_item; + GtkWidget *delete_images; + GdkBitmap *mask; + GdkPixmap *pixmap = NULL; + Preview *p; + int i; + char buf[256]; + + DBG(DBG_proc, "preview_new\n"); + + p = malloc(sizeof(*p)); + if (!p) + { + return 0; + } + memset(p, 0, sizeof(*p)); + + p->mode = MODE_NORMAL; /* no pipette functions etc */ + p->calibration = 0; /* do not display calibration image */ + p->input_tag = -1; + p->rotation = 0; + p->gamma_functions_interruptable = FALSE; + + p->index_xmin = 0; + p->index_xmax = 2; + p->index_ymin = 1; + p->index_ymax = 3; + + p->max_scanner_surface[0] = -INF; + p->max_scanner_surface[1] = -INF; + p->max_scanner_surface[2] = INF; + p->max_scanner_surface[3] = INF; + + p->scanner_surface[0] = -INF; + p->scanner_surface[1] = -INF; + p->scanner_surface[2] = INF; + p->scanner_surface[3] = INF; + + p->surface[0] = -INF; + p->surface[1] = -INF; + p->surface[2] = INF; + p->surface[3] = INF; + + gtk_preview_set_gamma(1.0); + gtk_preview_set_install_cmap(preferences.preview_own_cmap); + + preview_generate_preview_filenames(p); + + p->preset_surface[0] = 0; + p->preset_surface[1] = 0; + p->preset_surface[2] = INF; + p->preset_surface[3] = INF; + + p->maximum_output_width = INF; /* full output with */ + p->maximum_output_height = INF; /* full output height */ + + p->preview_colors = -1; + p->invalid = TRUE; /* no valid preview */ + +#ifndef XSERVER_WITH_BUGGY_VISUALS + gtk_widget_push_visual(gtk_preview_get_visual()); +#endif + gtk_widget_push_colormap(gtk_preview_get_cmap()); + + snprintf(buf, sizeof(buf), "%s %s", WINDOW_PREVIEW, xsane.device_text); + p->top = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(p->top), buf); + xsane_set_window_icon(p->top, 0); + gtk_accel_group_attach(xsane.accelerator_group, GTK_OBJECT(p->top)); + + /* set the main vbox */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 0); + gtk_container_add(GTK_CONTAINER(p->top), vbox); + gtk_widget_show(vbox); + + /* set the main hbox */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 5); + gtk_widget_show(hbox); + + + /* top hbox for icons */ + p->button_box = gtk_hbox_new(FALSE, 1); + gtk_container_set_border_width(GTK_CONTAINER(p->button_box), 1); + gtk_box_pack_start(GTK_BOX(vbox), p->button_box, FALSE, FALSE, 0); + + /* White, gray and black pipette button */ + p->pipette_white = xsane_button_new_with_pixmap(p->top->window, p->button_box, pipette_white_xpm, DESC_PIPETTE_WHITE, (GtkSignalFunc) preview_pipette_white, p); + p->pipette_gray = xsane_button_new_with_pixmap(p->top->window, p->button_box, pipette_gray_xpm, DESC_PIPETTE_GRAY, (GtkSignalFunc) preview_pipette_gray, p); + p->pipette_black = xsane_button_new_with_pixmap(p->top->window, p->button_box, pipette_black_xpm, DESC_PIPETTE_BLACK, (GtkSignalFunc) preview_pipette_black, p); + + /* Zoom not, zoom out and zoom in button */ + p->zoom_not = xsane_button_new_with_pixmap(p->top->window, p->button_box, zoom_not_xpm, DESC_ZOOM_FULL, (GtkSignalFunc) preview_zoom_not, p); + p->zoom_out = xsane_button_new_with_pixmap(p->top->window, p->button_box, zoom_out_xpm, DESC_ZOOM_OUT, (GtkSignalFunc) preview_zoom_out, p); + p->zoom_in = xsane_button_new_with_pixmap(p->top->window, p->button_box, zoom_in_xpm, DESC_ZOOM_IN, (GtkSignalFunc) preview_zoom_in, p); + p->zoom_undo = xsane_button_new_with_pixmap(p->top->window, p->button_box, zoom_undo_xpm, DESC_ZOOM_UNDO, (GtkSignalFunc) preview_zoom_undo, p); + p->full_area = xsane_button_new_with_pixmap(p->top->window, p->button_box, auto_select_preview_area_xpm, DESC_AUTOSELECT_SCANAREA, (GtkSignalFunc) preview_autoselect_scanarea_callback, p); + p->autoselect = xsane_button_new_with_pixmap(p->top->window, p->button_box, full_preview_area_xpm, DESC_FULL_PREVIEW_AREA, (GtkSignalFunc) preview_full_preview_area_callback, p); + delete_images = xsane_button_new_with_pixmap(p->top->window, p->button_box, delete_images_xpm, DESC_DELETE_IMAGES, (GtkSignalFunc) preview_delete_images_callback, p); + + gtk_widget_add_accelerator(p->zoom_not, "clicked", xsane.accelerator_group, GDK_KP_Multiply, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt keypad_* */ + gtk_widget_add_accelerator(p->zoom_out, "clicked", xsane.accelerator_group, GDK_KP_Subtract, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt keypad_- */ + gtk_widget_add_accelerator(p->zoom_in, "clicked", xsane.accelerator_group, GDK_KP_Add, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt keypad_+ */ + gtk_widget_add_accelerator(p->zoom_undo, "clicked", xsane.accelerator_group, GDK_KP_Divide, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt keypad_/ */ + gtk_widget_add_accelerator(p->full_area, "clicked", xsane.accelerator_group, GDK_A, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt keypad_* */ + gtk_widget_add_accelerator(p->autoselect, "clicked", xsane.accelerator_group, GDK_V, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt keypad_* */ + gtk_widget_add_accelerator(delete_images, "clicked", xsane.accelerator_group, GDK_KP_Delete, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt keypad_* */ + + gtk_widget_set_sensitive(p->zoom_not, FALSE); /* no zoom at this point, so no zoom not */ + gtk_widget_set_sensitive(p->zoom_out, FALSE); /* no zoom at this point, so no zoom out */ + gtk_widget_set_sensitive(p->zoom_undo, FALSE); /* no zoom at this point, so no zoom undo */ + gtk_widget_set_sensitive(p->full_area, FALSE); /* no selection */ + gtk_widget_set_sensitive(p->autoselect, FALSE); /* no selection */ + + + /* select maximum scanarea */ + + preset_area_option_menu = gtk_option_menu_new(); + xsane_back_gtk_set_tooltip(xsane.tooltips, preset_area_option_menu, DESC_PRESET_AREA); + gtk_box_pack_start(GTK_BOX(p->button_box), preset_area_option_menu, FALSE, FALSE, 0); + gtk_widget_show(preset_area_option_menu); + p->preset_area_option_menu = preset_area_option_menu; + preview_create_preset_area_menu(p, 0); /* build menu and set default to 0=full size */ + + + /* select rotation */ + rotation_menu = gtk_menu_new(); + + for (i = 0; i < 12; ++i) + { + char buffer[256]; + int rot; + + if (i<4) + { + snprintf(buffer, sizeof(buffer), "%03d ", i*90); + rot = i; + } + else if (i<8) + { + snprintf(buffer, sizeof(buffer), "%03d |", i*90-360); + rot = i; + } + else + { + snprintf(buffer, sizeof(buffer), "%03d -", i*90-2*360); + rot = (((i & 3) + 2) & 3) + 4; + } + rotation_item = gtk_menu_item_new_with_label(buffer); + gtk_container_add(GTK_CONTAINER(rotation_menu), rotation_item); + gtk_signal_connect(GTK_OBJECT(rotation_item), "activate", (GtkSignalFunc) preview_rotation_callback, p); + gtk_object_set_data(GTK_OBJECT(rotation_item), "Selection", (void *) rot); + + gtk_widget_show(rotation_item); + } + + rotation_option_menu = gtk_option_menu_new(); + xsane_back_gtk_set_tooltip(xsane.tooltips, rotation_option_menu, DESC_ROTATION); + gtk_box_pack_start(GTK_BOX(p->button_box), rotation_option_menu, FALSE, FALSE, 0); + gtk_option_menu_set_menu(GTK_OPTION_MENU(rotation_option_menu), rotation_menu); + gtk_option_menu_set_history(GTK_OPTION_MENU(rotation_option_menu), p->rotation); /* set rotation */ +/* xsane_back_gtk_set_tooltip(tooltips, rotation_option_menu, desc); */ + + gtk_widget_show(rotation_option_menu); + p->rotation_option_menu = rotation_option_menu; + + + gtk_widget_show(p->button_box); + + + + /* 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_set_border_width(GTK_CONTAINER(table), 1); + gtk_box_pack_start(GTK_BOX(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 unit label */ + p->unit_label = gtk_label_new("cm"); + gtk_container_add(GTK_CONTAINER(frame), p->unit_label); + gtk_widget_show(p->unit_label); + + /* 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); + + /* the first expose_event is responsible to undraw the selection frame */ + gtk_signal_connect(GTK_OBJECT(p->window), "expose_event", (GtkSignalFunc) preview_expose_event_handler_start, p); + gtk_signal_connect(GTK_OBJECT(p->window), "button_press_event", (GtkSignalFunc) preview_button_press_event_handler, p); + gtk_signal_connect(GTK_OBJECT(p->window), "motion_notify_event", (GtkSignalFunc) preview_motion_event_handler, p); + gtk_signal_connect(GTK_OBJECT(p->window), "button_release_event", (GtkSignalFunc) preview_button_release_event_handler, p); + gtk_signal_connect_after(GTK_OBJECT(p->window), "size_allocate", (GtkSignalFunc) preview_area_resize_handler, p); + /* the second expose_event is responsible to redraw the selection frame */ + gtk_signal_connect_after(GTK_OBJECT(p->window), "expose_event", (GtkSignalFunc) preview_expose_event_handler_end, p); + + /* Connect the motion-notify events of the preview area with the rulers. Nifty stuff! */ + class = GTK_WIDGET_CLASS(GTK_OBJECT(p->hruler)->klass); + signal_func = (GtkSignalFunc) class->motion_notify_event; + gtk_signal_connect_object(GTK_OBJECT(p->window), "motion_notify_event", signal_func, GTK_OBJECT(p->hruler)); + class = GTK_WIDGET_CLASS(GTK_OBJECT(p->vruler)->klass); + 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_surface(p, 0); + + /* fill in action area: */ + + /* the (in)valid pixmaps */ + pixmap = gdk_pixmap_create_from_xpm_d(p->top->window, &mask, xsane.bg_trans, (gchar **) valid_xpm); + p->valid_pixmap = gtk_pixmap_new(pixmap, mask); + gtk_box_pack_start(GTK_BOX(hbox), p->valid_pixmap, FALSE, FALSE, 0); + gtk_widget_show(p->valid_pixmap); + gdk_pixmap_unref(pixmap); + + pixmap = gdk_pixmap_create_from_xpm_d(p->top->window, &mask, xsane.bg_trans, (gchar **) scanning_xpm); + p->scanning_pixmap = gtk_pixmap_new(pixmap, mask); + gtk_box_pack_start(GTK_BOX(hbox), p->scanning_pixmap, FALSE, FALSE, 0); + gtk_widget_show(p->scanning_pixmap); + gdk_pixmap_unref(pixmap); + + pixmap = gdk_pixmap_create_from_xpm_d(p->top->window, &mask, xsane.bg_trans, (gchar **) incomplete_xpm); + p->incomplete_pixmap = gtk_pixmap_new(pixmap, mask); + gtk_box_pack_start(GTK_BOX(hbox), p->incomplete_pixmap, FALSE, FALSE, 0); + gtk_widget_show(p->incomplete_pixmap); + gdk_pixmap_unref(pixmap); + + pixmap = gdk_pixmap_create_from_xpm_d(p->top->window, &mask, xsane.bg_trans, (gchar **) invalid_xpm); + p->invalid_pixmap = gtk_pixmap_new(pixmap, mask); + gtk_box_pack_start(GTK_BOX(hbox), p->invalid_pixmap, FALSE, FALSE, 0); + gtk_widget_show(p->invalid_pixmap); + gdk_pixmap_unref(pixmap); + + /* Start button */ + p->start = gtk_button_new_with_label(BUTTON_PREVIEW_ACQUIRE); + xsane_back_gtk_set_tooltip(xsane.tooltips, p->start, DESC_PREVIEW_ACQUIRE); + gtk_signal_connect(GTK_OBJECT(p->start), "clicked", (GtkSignalFunc) preview_start_button_clicked, p); + gtk_box_pack_start(GTK_BOX(hbox), p->start, TRUE, TRUE, 10); + gtk_widget_add_accelerator(p->start, "clicked", xsane.accelerator_group, GDK_P, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt P */ + + /* Cancel button */ + p->cancel = gtk_button_new_with_label(BUTTON_PREVIEW_CANCEL); + xsane_back_gtk_set_tooltip(xsane.tooltips, p->cancel, DESC_PREVIEW_CANCEL); + gtk_signal_connect(GTK_OBJECT(p->cancel), "clicked", (GtkSignalFunc) preview_cancel_button_clicked, p); + gtk_box_pack_start(GTK_BOX(hbox), p->cancel, TRUE, TRUE, 10); + gtk_widget_add_accelerator(p->cancel, "clicked", xsane.accelerator_group, GDK_Escape, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); /* Alt ESC */ + gtk_widget_set_sensitive(p->cancel, FALSE); + + gtk_widget_show(p->cancel); + gtk_widget_show(p->start); + 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); + + cursor = gdk_cursor_new(XSANE_CURSOR_PREVIEW); /* set default curosr */ + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = XSANE_CURSOR_PREVIEW; + + gtk_widget_pop_colormap(); +#ifndef XSERVER_WITH_BUGGY_VISUALS + gtk_widget_pop_visual(); +#endif + + preview_update_surface(p, 0); + + preview_display_valid(p); + + return p; +} + + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_area_correct(Preview *p) +{ + float width, height, max_width, max_height; + float aspect; + + DBG(DBG_proc, "preview_area_correct\n"); + + if ( ((p->rotation & 3) == 0) || ((p->rotation & 3) == 2) || (p->calibration) ) + { + aspect = p->aspect; + } + else + { + aspect = 1.0 / p->aspect; + } + + max_width = p->preview_window_width; + max_height = p->preview_window_height; + + width = max_width; + height = width / aspect; + + if (height > max_height) + { + height = max_height; + width = height * aspect; + } + + p->preview_width = width + 0.5; + p->preview_height = height + 0.5; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_update_surface(Preview *p, int surface_changed) +{ + float val; + float width, height; + float rotated_preset_surface[4]; + const SANE_Option_Descriptor *opt; + int i; + SANE_Value_Type type; + SANE_Unit unit; + double min, max; + int expand_surface = 0; + + DBG(DBG_proc, "preview_update_surface\n"); + + unit = SANE_UNIT_PIXEL; + type = SANE_TYPE_INT; + + preview_update_selection(p); /* make sure preview selection is up to date */ + + p->show_selection = FALSE; /* at first let's say we have no corrdinate selection */ + + for (i = 0; i < 4; ++i) /* test if surface (max vals of scanarea) has changed */ + { +/* val = (i & 2) ? INF : -INF; */ + val = (i & 2) ? INF : 0; + + if (xsane.well_known.coord[i] > 0) + { + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.coord[i]); + assert(opt->unit == SANE_UNIT_PIXEL || opt->unit == SANE_UNIT_MM); + unit = opt->unit; + type = opt->type; + p->show_selection = TRUE; /* ok, we have a coordinate selection */ + + xsane_get_bounds(opt, &min, &max); + + if (i & 2) + { + val = max; + } + else + { + val = min; + } + } + + if (p->orig_scanner_surface[i] != val) + { + DBG(DBG_info, "preview_update_surface: orig_scanner_surface[%d] has changed\n", i); + surface_changed = 2; + p->orig_scanner_surface[i] = val; + } + } + + if (surface_changed == 2) /* redefine all surface subparts */ + { + DBG(DBG_info, "preview_update_surface: rotating surfaces\n"); + + /* max_scanner_surface are the rotated coordinates of orig_scanner_surface */ + preview_rotate_devicesurface_to_previewsurface(p->rotation, p->orig_scanner_surface, p->max_scanner_surface); + + gtk_widget_set_sensitive(p->zoom_not, TRUE); /* allow unzoom */ + gtk_widget_set_sensitive(p->zoom_undo, FALSE); /* forbid undo zoom */ + + expand_surface = 1; + for (i = 0; i < 4; i++) + { + if (p->surface[i] != p->scanner_surface[i]) + { + expand_surface = 0; + } + } + } + else + { + expand_surface = 0; + } + + + /* scanner_surface are the rotated coordinates of the reduced (preset) surface */ + preview_rotate_devicesurface_to_previewsurface(p->rotation, p->preset_surface, rotated_preset_surface); + for (i = 0; i < 4; i++) + { + val = rotated_preset_surface[i]; + + xsane_bound_float(&val, p->max_scanner_surface[i % 2], p->max_scanner_surface[(i % 2) + 2]); + if (val != p->scanner_surface[i]) + { + surface_changed = 1; + p->scanner_surface[i] = val; + + if (expand_surface) + { + p->surface[i] = val; + } + } + DBG(DBG_info, "preview_update_surface: scanner_surface[%d] = %3.2f\n", i, val); + } + + for (i = 0; i < 4; i++) + { + val = p->surface[i]; + + xsane_bound_float(&val, p->scanner_surface[i % 2], p->scanner_surface[(i % 2) + 2]); + if (val != p->surface[i]) + { + surface_changed = 1; + p->surface[i] = val; + } + DBG(DBG_info, "preview_update_surface: surface[%d] = %3.2f\n", i, val); + } + +/* may be we need to define p->old_surface[i] here too */ + + if (p->surface_unit != unit) + { + surface_changed = 1; + p->surface_unit = unit; + } + + if (p->show_selection) + { + gtk_widget_set_sensitive(p->preset_area_option_menu, TRUE); /* enable preset area */ + gtk_widget_set_sensitive(p->zoom_in, TRUE); /* zoom in is allowed at all */ + gtk_widget_set_sensitive(p->full_area, TRUE); /* enable selection buttons */ + gtk_widget_set_sensitive(p->autoselect, TRUE); + } + else + { + gtk_widget_set_sensitive(p->preset_area_option_menu, FALSE); /* disable preset area */ + gtk_widget_set_sensitive(p->zoom_in, FALSE); /* no zoom at all */ + gtk_widget_set_sensitive(p->zoom_out, FALSE); + gtk_widget_set_sensitive(p->zoom_undo, FALSE); + gtk_widget_set_sensitive(p->zoom_not, FALSE); + gtk_widget_set_sensitive(p->full_area, FALSE); /* no selection */ + gtk_widget_set_sensitive(p->autoselect, FALSE); /* no selection */ + } + + if (p->surface_type != type) + { + surface_changed = 1; + p->surface_type = type; + } + + + if (surface_changed) + { + DBG(DBG_info, "preview_update_surface: surface_changed\n"); + /* guess the initial preview window size: */ + + preview_restore_image(p); /* load scanned image */ + + width = p->surface[p->index_xmax] - p->surface[p->index_xmin]; + height = p->surface[p->index_ymax] - p->surface[p->index_ymin]; + + if ( (p->calibration) || (p->startimage) ) /* predefined image should have constant aspect */ + { + p->aspect = fabs(p->image_width/(float) p->image_height); + } + else if (width >= INF || height >= INF) /* undefined size */ + { + p->aspect = 1.0; + } + else /* we have a surface size that can be used to calculate the aspect ratio */ + { + if (((p->rotation & 3) == 0) || ((p->rotation & 3) == 2)) /* 0 or 180 degree */ + { + p->aspect = width/height; + } + else /* 90 or 270 degree */ + { + p->aspect = height/width; + } + } + } +#if 0 + else if ( (p->image_height) && (p->image_width) ) /* we have an image so let´s calculate the correct aspect ratio */ + { + p->aspect = fabs(p->image_width/(float) p->image_height); + } +#endif + + DBG(DBG_info, "preview_update_surface: aspect = %f\n", p->aspect); + + if ( (surface_changed) && (p->preview_window_width == 0) ) /* window is new */ + { + DBG(DBG_info, "preview_update_surface: defining size of preview window\n"); + + p->preview_window_width = 0.3 * gdk_screen_width(); + p->preview_window_height = 0.5 * gdk_screen_height(); + preview_area_correct(p); /* calculate preview_width and height */ + gtk_widget_set_usize(GTK_WIDGET(p->window), p->preview_width, p->preview_height); + } + else if (surface_changed) /* establish new surface */ + { + DBG(DBG_info, "preview_update_surface: establish new surface\n"); + preview_area_correct(p); /* calculate preview_width and height */ + preview_area_resize(p); /* correct rulers */ + preview_do_gamma_correction(p); /* draw preview */ + xsane_update_histogram(TRUE /* update raw */); + + p->previous_selection.active = FALSE; + p->previous_selection_maximum.active = FALSE; + preview_bound_selection(p); /* make sure selection is not larger than surface */ + preview_draw_selection(p); /* the selection is overpainted, we have to update it */ + preview_establish_selection(p); /* send selection to backend, it may be changed */ + } + else /* leave everything like it is */ + { + DBG(DBG_info, "preview_update_surface: surface unchanged\n"); + preview_update_selection(p); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_scan(Preview *p) +{ + double min, max, swidth, sheight, width, height, dpi = 0; + const SANE_Option_Descriptor *opt; + gint gwidth, gheight; + int i; + float dsurface[4]; + + DBG(DBG_proc, "preview_scan\n"); + + /* we are overpainting the image, so we do not have any visible selections */ + p->previous_selection.active = FALSE; + p->previous_selection_maximum.active = FALSE; + + xsane.block_update_param = TRUE; /* do not change parameters each time */ + + preview_save_option(p, xsane.well_known.dpi, &p->saved_dpi, &p->saved_dpi_valid); + preview_save_option(p, xsane.well_known.dpi_x, &p->saved_dpi_x, &p->saved_dpi_x_valid); + preview_save_option(p, xsane.well_known.dpi_y, &p->saved_dpi_y, &p->saved_dpi_y_valid); + preview_save_option(p, xsane.well_known.scanmode, &p->saved_scanmode, &p->saved_scanmode_valid); + + for (i = 0; i < 4; ++i) + { + preview_save_option(p, xsane.well_known.coord[i], &p->saved_coord[i], p->saved_coord_valid + i); + } + + preview_save_option(p, xsane.well_known.bit_depth, &p->saved_bit_depth, &p->saved_bit_depth_valid); + +#if 0 + xsane_set_medium(preferences.medium[xsane.medium_nr]); /* make sure medium gamma values are up to date */ +#endif + + /* determine dpi, if necessary: */ + + if (xsane.well_known.dpi > 0) + { + float aspect; + + if ( ((p->rotation & 3) == 0) || ((p->rotation & 3) == 2) ) + { + aspect = p->aspect; + } + else + { + aspect = 1.0 / p->aspect; + } + + opt = xsane_get_option_descriptor(xsane.dev, xsane.well_known.dpi); + + gwidth = p->preview_width; + gheight = p->preview_height; + + height = gheight; + width = height * aspect; + + if (width > gwidth) + { + width = gwidth; + height = width / aspect; + } + + swidth = fabs(p->surface[xsane_back_gtk_BR_X] - p->surface[xsane_back_gtk_TL_X]); + + if (swidth < INF) + { + dpi = MM_PER_INCH * width/swidth; + } + else + { + sheight = fabs(p->surface[xsane_back_gtk_BR_Y] - p->surface[xsane_back_gtk_TL_Y]); + if (sheight < INF) + { + dpi = MM_PER_INCH * height/sheight; + } + else + { + dpi = 18.0; + } + } + + dpi = dpi * preferences.preview_oversampling; /* faktor for resolution */ + + xsane_get_bounds(opt, &min, &max); + + if (dpi < min) + { + dpi = min; + } + + if (dpi > max) + { + dpi = max; + } + + xsane_set_resolution(xsane.well_known.dpi, dpi); /* set resolution to dpi or next higher value that is available */ + xsane_set_resolution(xsane.well_known.dpi_x, dpi); /* set resolution to dpi or next higher value that is available */ + xsane_set_resolution(xsane.well_known.dpi_y, dpi); /* set resolution to dpi or next higher value that is available */ + } + + preview_rotate_previewsurface_to_devicesurface(p->rotation, p->surface, dsurface); + + for (i = 0; i < 4; ++i) + { + preview_set_option_float(p, xsane.well_known.coord[i], dsurface[i]); + } + + preview_set_option_val(p, xsane.well_known.preview, SANE_TRUE); + + if ( (xsane.grayscale_scanmode) && (xsane.param.depth == 1) && (xsane.lineart_mode == XSANE_LINEART_GRAYSCALE) ) + { + preview_set_option(p, xsane.well_known.scanmode, xsane.grayscale_scanmode); + } + +#if 0 + if ( (p->saved_bit_depth == 16) && (p->saved_bit_depth_valid) ) /* don't scan with 16 bpp */ + { + preview_set_option_val(p, xsane.well_known.bit_depth, 8); + } +#endif + + xsane.block_update_param = FALSE; + p->preview_colors = xsane.xsane_colors; + p->scan_incomplete = FALSE; + p->invalid = TRUE; /* no valid preview */ + p->scanning = TRUE; + + preview_display_valid(p); + + + xsane_clear_histogram(&xsane.histogram_raw); + xsane_clear_histogram(&xsane.histogram_enh); + + + /* OK, all set to go */ + preview_scan_start(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_save_image_file(Preview *p, FILE *out) +{ + DBG(DBG_proc, "preview_save_image_file\n"); + + if (out) + { + float dsurface[4]; + + preview_rotate_previewsurface_to_devicesurface(p->rotation, p->surface, dsurface); + + /* always save it as a 16 bit PPM image: */ + fprintf(out, "P6\n" + "# surface: %g %g %g %g %u %u\n" + "# time: %d\n" + "%d %d\n65535\n", + dsurface[0], dsurface[1], dsurface[2], dsurface[3], + p->surface_type, p->surface_unit, + (int) time(NULL), + p->image_width, p->image_height); + + fwrite(p->image_data_raw, 6, p->image_width*p->image_height, out); + fclose(out); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_save_image(Preview *p) +{ + FILE *out; + int level=0; + + DBG(DBG_proc, "preview_save_image\n"); + + if (!p->image_data_raw) + { + return; + } + + if ( GROSSLY_EQUAL(p->max_scanner_surface[0], p->surface[0]) && /* full device surface */ + GROSSLY_EQUAL(p->max_scanner_surface[1], p->surface[1]) && + GROSSLY_EQUAL(p->max_scanner_surface[2], p->surface[2]) && + GROSSLY_EQUAL(p->max_scanner_surface[3], p->surface[3]) ) + { + level = 0; + } + else if ( GROSSLY_EQUAL(p->scanner_surface[0], p->surface[0]) && /* user defined surface */ + GROSSLY_EQUAL(p->scanner_surface[1], p->surface[1]) && + GROSSLY_EQUAL(p->scanner_surface[2], p->surface[2]) && + GROSSLY_EQUAL(p->scanner_surface[3], p->surface[3]) ) + { + level = 1; + } + else /* zoom area */ + { + level = 2; + } + + if (p->filename[level]) + { + /* save preview image */ + out = fopen(p->filename[level], "wb"); /* b = binary mode for win32*/ + + preview_save_image_file(p, out); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_delete_images(Preview *p) +{ + FILE *out; + int level=0; + + DBG(DBG_proc, "preview_delete_images_file\n"); + + for (level = 0; level<3; level++) + { + out = fopen(p->filename[level], "wb"); /* b = binary mode for win32*/ + if (out) + fclose(out); + } + preview_update_surface(p, 1); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_destroy(Preview *p) +{ + int level; + + DBG(DBG_proc, "preview_destroy\n"); + + if (p->scanning) + { + preview_scan_done(p, 0); /* don't save partial window */ + } + + for(level = 0; level <= 2; level++) + { + if (p->filename[level]) + { + remove(p->filename[level]); /* remove existing preview */ + } + } + + if (p->image_data_enh) + { + free(p->image_data_enh); + p->image_data_enh = 0; + } + + if (p->image_data_raw) + { + free(p->image_data_raw); + p->image_data_raw = 0; + } + + if (p->preview_row) + { + free(p->preview_row); + p->preview_row = 0; + } + + if (p->gc_selection) + { + gdk_gc_destroy(p->gc_selection); + } + + if (p->gc_selection_maximum) + { + gdk_gc_destroy(p->gc_selection_maximum); + } + + if (p->top) + { + gtk_widget_destroy(p->top); + } + free(p); + + p = 0; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_zoom_not(GtkWidget *window, gpointer data) +{ + Preview *p=data; + int i; + + DBG(DBG_proc, "preview_zoom_not\n"); + + for (i=0; i<4; i++) + { + p->surface[i] = p->scanner_surface[i]; + } + + preview_update_surface(p, 1); + gtk_widget_set_sensitive(p->zoom_not, FALSE); /* forbid unzoom */ + gtk_widget_set_sensitive(p->zoom_out, FALSE); /* forbid zoom out */ + gtk_widget_set_sensitive(p->zoom_undo,TRUE); /* allow zoom undo */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_zoom_out(GtkWidget *window, gpointer data) +{ + Preview *p=data; + int i; + float delta_width; + float delta_height; + + DBG(DBG_proc, "preview_zoom_out\n"); + + for (i=0; i<4; i++) + { + p->old_surface[i] = p->surface[i]; + } + + delta_width = (p->surface[p->index_xmax] - p->surface[p->index_xmin]) * 0.2; + delta_height = (p->surface[p->index_ymax] - p->surface[p->index_ymin]) * 0.2; + + p->surface[p->index_xmin] -= delta_width; + p->surface[p->index_xmax] += delta_width; + p->surface[p->index_ymin] -= delta_height; + p->surface[p->index_ymax] += delta_height; + + if (p->surface[p->index_xmin] < p->scanner_surface[p->index_xmin]) + { + p->surface[p->index_xmin] = p->scanner_surface[p->index_xmin]; + } + + if (p->surface[p->index_ymin] < p->scanner_surface[p->index_ymin]) + { + p->surface[p->index_ymin] = p->scanner_surface[p->index_ymin]; + } + + if (p->surface[p->index_xmax] > p->scanner_surface[p->index_xmax]) + { + p->surface[p->index_xmax] = p->scanner_surface[p->index_xmax]; + } + + if (p->surface[p->index_ymax] > p->scanner_surface[p->index_ymax]) + { + p->surface[p->index_ymax] = p->scanner_surface[p->index_ymax]; + } + + preview_update_surface(p, 1); + gtk_widget_set_sensitive(p->zoom_not, TRUE); /* allow unzoom */ + gtk_widget_set_sensitive(p->zoom_undo,TRUE); /* allow zoom undo */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_zoom_in(GtkWidget *window, gpointer data) +{ + Preview *p=data; + int i; + + DBG(DBG_proc, "preview_zoom_in\n"); + + for (i=0; i<4; i++) + { + p->old_surface[i] = p->surface[i]; + p->surface[i] = p->selection.coordinate[i]; + } + + preview_update_surface(p, 1); + gtk_widget_set_sensitive(p->zoom_not, TRUE); /* allow unzoom */ + gtk_widget_set_sensitive(p->zoom_out, TRUE); /* allow zoom out */ + gtk_widget_set_sensitive(p->zoom_undo,TRUE); /* allow zoom undo */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_zoom_undo(GtkWidget *window, gpointer data) +{ + Preview *p=data; + int i; + + DBG(DBG_proc, "preview_zoom_undo\n"); + + for (i=0; i<4; i++) + { + p->surface[i] = p->old_surface[i]; + } + + preview_update_surface(p, 1); + gtk_widget_set_sensitive(p->zoom_not, TRUE); /* allow unzoom */ + gtk_widget_set_sensitive(p->zoom_out, TRUE); /* allow zoom out */ + gtk_widget_set_sensitive(p->zoom_undo, FALSE); /* forbid zoom undo */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_get_color(Preview *p, int x, int y, int range, int *red, int *green, int *blue) +{ + int image_x, image_y; + int image_x_min, image_y_min; + int image_x_max, image_y_max; + int offset; + int count = 0; + + DBG(DBG_proc, "preview_get_color\n"); + + if (p->image_data_raw) + { + preview_transform_coordinate_window_to_image(p, x, y, &image_x, &image_y); + + if ( (image_x < p->image_width) && (image_y < p->image_height) ) + { + image_x_min = image_x - range/2; + image_y_min = image_y - range/2; + image_x_max = image_x + range/2; + image_y_max = image_y + range/2; + + xsane_bound_int(&image_x_min, 0, p->image_width - 1); + xsane_bound_int(&image_x_max, 0, p->image_width - 1); + xsane_bound_int(&image_y_min, 0, p->image_height - 1); + xsane_bound_int(&image_y_max, 0, p->image_height - 1); + + *red = 0; + *green = 0; + *blue = 0; + + for (image_x = image_x_min; image_x <= image_x_max; image_x++) + { + for (image_y = image_y_min; image_y <= image_y_max; image_y++) + { + count++; + + offset = 3 * (image_y * p->image_width + image_x); + + if (!xsane.negative) /* positive */ + { + *red += (p->image_data_raw[offset ]) >> 8; + *green += (p->image_data_raw[offset + 1]) >> 8; + *blue += (p->image_data_raw[offset + 2]) >> 8; + } + else /* negative */ + { + *red += 255 - (p->image_data_raw[offset ] >> 8); + *green += 255 - (p->image_data_raw[offset + 1] >> 8); + *blue += 255 - (p->image_data_raw[offset + 2] >> 8); + } + } + } + + *red /= count; + *green /= count; + *blue /= count; + } + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_pipette_white(GtkWidget *window, gpointer data) +{ + Preview *p=data; + GdkCursor *cursor; + GdkColor fg; + GdkColor bg; + GdkPixmap *pixmap; + GdkPixmap *mask; + + DBG(DBG_proc, "preview_pipette_white\n"); + + p->mode = MODE_PIPETTE_WHITE; + + pixmap = gdk_bitmap_create_from_data(p->top->window, cursor_pipette_white, CURSOR_PIPETTE_WIDTH, CURSOR_PIPETTE_HEIGHT); + mask = gdk_bitmap_create_from_data(p->top->window, cursor_pipette_mask, CURSOR_PIPETTE_WIDTH, CURSOR_PIPETTE_HEIGHT); + + fg.red = 0; + fg.green = 0; + fg.blue = 0; + + bg.red = 65535; + bg.green = 65535; + bg.blue = 65535; + + cursor = gdk_cursor_new_from_pixmap(pixmap, mask, &fg, &bg, CURSOR_PIPETTE_HOT_X, CURSOR_PIPETTE_HOT_Y); + + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = -1; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_pipette_gray(GtkWidget *window, gpointer data) +{ + Preview *p=data; + GdkCursor *cursor; + GdkColor fg; + GdkColor bg; + GdkPixmap *pixmap; + GdkPixmap *mask; + + DBG(DBG_proc, "preview_pipette_gray\n"); + + p->mode = MODE_PIPETTE_GRAY; + + pixmap = gdk_bitmap_create_from_data(p->top->window, cursor_pipette_gray, CURSOR_PIPETTE_WIDTH, CURSOR_PIPETTE_HEIGHT); + mask = gdk_bitmap_create_from_data(p->top->window, cursor_pipette_mask, CURSOR_PIPETTE_WIDTH, CURSOR_PIPETTE_HEIGHT); + + fg.red = 0; + fg.green = 0; + fg.blue = 0; + + bg.red = 65535; + bg.green = 65535; + bg.blue = 65535; + + cursor = gdk_cursor_new_from_pixmap(pixmap, mask, &fg, &bg, CURSOR_PIPETTE_HOT_X, CURSOR_PIPETTE_HOT_Y); + + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = -1; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_pipette_black(GtkWidget *window, gpointer data) +{ + Preview *p=data; + GdkCursor *cursor; + GdkColor fg; + GdkColor bg; + GdkPixmap *pixmap; + GdkPixmap *mask; + + DBG(DBG_proc, "preview_pipette_black\n"); + + p->mode = MODE_PIPETTE_BLACK; + + pixmap = gdk_bitmap_create_from_data(p->top->window, cursor_pipette_black, CURSOR_PIPETTE_WIDTH, CURSOR_PIPETTE_HEIGHT); + mask = gdk_bitmap_create_from_data(p->top->window, cursor_pipette_mask , CURSOR_PIPETTE_WIDTH, CURSOR_PIPETTE_HEIGHT); + + fg.red = 0; + fg.green = 0; + fg.blue = 0; + + bg.red = 65535; + bg.green = 65535; + bg.blue = 65535; + + cursor = gdk_cursor_new_from_pixmap(pixmap, mask, &fg, &bg, CURSOR_PIPETTE_HOT_X, CURSOR_PIPETTE_HOT_Y); + + gdk_window_set_cursor(p->window->window, cursor); + gdk_cursor_destroy(cursor); + p->cursornr = -1; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_select_full_preview_area(Preview *p) +{ + int i; + + DBG(DBG_proc, "preview_select_full_preview_area\n"); + + p->selection.active = TRUE; + + for (i=0; i<4; i++) + { + p->selection.coordinate[i] = p->surface[i]; + } + + preview_update_maximum_output_size(p); + preview_draw_selection(p); + preview_establish_selection(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_full_preview_area_callback(GtkWidget *widget, gpointer call_data) +{ + Preview *p = call_data; + + DBG(DBG_proc, "preview_full_preview_area_callback\n"); + + preview_select_full_preview_area(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_delete_images_callback(GtkWidget *widget, gpointer call_data) +{ + Preview *p = call_data; + + DBG(DBG_proc, "preview_delete_images_callback\n"); + + preview_delete_images(p); + p->invalid = TRUE; + preview_display_valid(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +int xsane_preset_area_entry_rename; + +static void xsane_preset_area_entry_rename_button_callback(GtkWidget *widget, gpointer data) +{ + DBG(DBG_proc, "xsane_preset_area_entry_rename\n"); + + xsane_preset_area_entry_rename = (int) data; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_preset_area_rename_callback(GtkWidget *widget, GtkWidget *preset_area_widget) +{ + int selection; + char *oldname; + char *newname; + Preview *p; + GtkWidget *rename_dialog; + GtkWidget *text; + GtkWidget *button; + GtkWidget *vbox, *hbox; + GtkWidget *old_preset_area_menu; + char buf[256]; + int old_selection; + + + DBG(DBG_proc, "preview_preset_area_rename_callback\n"); + + selection = (int) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Selection"); + p = (Preview *) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Preview"); + + DBG(DBG_info ,"rename %s\n", preferences.preset_area[selection]->name); + + old_preset_area_menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + old_selection = (int) gtk_object_get_data(GTK_OBJECT(gtk_menu_get_active(GTK_MENU(old_preset_area_menu))), "Selection"); + + gtk_menu_popdown(GTK_MENU(old_preset_area_menu)); + /* set menu in correct state, is a bit strange this way but I do not have a better idea */ + gtk_option_menu_set_history(GTK_OPTION_MENU(p->preset_area_option_menu), old_selection); + + oldname = strdup(preferences.preset_area[selection]->name); + + rename_dialog = gtk_window_new(GTK_WINDOW_DIALOG); + xsane_set_window_icon(rename_dialog, 0); + + /* set rename dialog */ + gtk_window_set_position(GTK_WINDOW(rename_dialog), GTK_WIN_POS_CENTER); + gtk_window_set_policy(GTK_WINDOW(rename_dialog), FALSE, FALSE, FALSE); + snprintf(buf, sizeof(buf), "%s %s", xsane.prog_name, WINDOW_PRESET_AREA_RENAME); + gtk_window_set_title(GTK_WINDOW(rename_dialog), buf); + gtk_signal_connect(GTK_OBJECT(rename_dialog), "delete_event", (GtkSignalFunc) xsane_preset_area_entry_rename_button_callback, (void *) -1); + gtk_widget_show(rename_dialog); + + /* set the main vbox */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 0); + gtk_container_add(GTK_CONTAINER(rename_dialog), vbox); + gtk_widget_show(vbox); + + /* set the main hbox */ + hbox = gtk_hbox_new(FALSE, 0); + xsane_separator_new(vbox, 2); + gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 5); + gtk_widget_show(hbox); + + text = gtk_entry_new_with_max_length(64); + xsane_back_gtk_set_tooltip(xsane.tooltips, text, DESC_PRESET_AREA_NAME); + gtk_entry_set_text(GTK_ENTRY(text), oldname); + gtk_widget_set_usize(text, 300, 0); + gtk_box_pack_start(GTK_BOX(vbox), text, TRUE, TRUE, 4); + gtk_widget_show(text); + + + button = gtk_button_new_with_label("OK"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_preset_area_entry_rename_button_callback, (void *) 1); + gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); + gtk_widget_show(button); + + button = gtk_button_new_with_label("Cancel"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_preset_area_entry_rename_button_callback, (void *) -1); + gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); + gtk_widget_show(button); + + xsane_preset_area_entry_rename = 0; + + while (xsane_preset_area_entry_rename == 0) + { + while (gtk_events_pending()) + { + DBG(DBG_info, "preview_preset_area_rename_callback: calling gtk_main_iteration\n"); + gtk_main_iteration(); + } + } + + newname = strdup(gtk_entry_get_text(GTK_ENTRY(text))); + + if (xsane_preset_area_entry_rename == 1) /* OK button has been pressed */ + { + gtk_option_menu_remove_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + + if (GTK_IS_WIDGET(old_preset_area_menu)) /* the menu normally is closed when we come here */ + { + gtk_widget_destroy(old_preset_area_menu); + } + + free(preferences.preset_area[selection]->name); + preferences.preset_area[selection]->name = strdup(newname); + DBG(DBG_info, "renaming %s to %s\n", oldname, newname); + + preview_create_preset_area_menu(p, old_selection); + } + + free(oldname); + free(newname); + + gtk_widget_destroy(rename_dialog); + + xsane_set_sensitivity(TRUE); + + return TRUE; /* event is handled */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_preset_area_add_callback(GtkWidget *widget, GtkWidget *preset_area_widget) +{ + int selection, i, old_selection = 0; + Preview *p; + GtkWidget *old_preset_area_menu; + + DBG(DBG_proc, "preview_preset_area_add_callback\n"); + + selection = (int) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Selection"); + p = (Preview *) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Preview"); + + if (selection < preferences.preset_area_definitions) + { + char buf[256]; + float coord[4]; + + preferences.preset_area = realloc(preferences.preset_area, (preferences.preset_area_definitions+1) * sizeof(void *)); + + /* shift all items after selection */ + for (i = preferences.preset_area_definitions-1; i > selection; i--) + { + preferences.preset_area[i+1] = preferences.preset_area[i]; + } + + /* insert new item behind selected item, name is size in mm */ + preview_rotate_previewsurface_to_devicesurface(p->rotation, p->selection.coordinate, coord); + snprintf(buf, sizeof(buf), "%d mm x %d mm", (int) (coord[2]-coord[0]), (int) (coord[3]-coord[1])); + preferences.preset_area[selection+1] = calloc(sizeof(Preferences_preset_area_t), 1); + preferences.preset_area[selection+1]->name = strdup(buf); + preferences.preset_area[selection+1]->xoffset = coord[0]; + preferences.preset_area[selection+1]->yoffset = coord[1]; + preferences.preset_area[selection+1]->width = coord[2] - coord[0]; + preferences.preset_area[selection+1]->height = coord[3] - coord[1]; + + DBG(DBG_proc, "added %s\n", buf); + + preferences.preset_area_definitions++; + + old_preset_area_menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + + gtk_option_menu_remove_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + old_selection = (int) gtk_object_get_data(GTK_OBJECT(gtk_menu_get_active(GTK_MENU(old_preset_area_menu))), "Selection"); + + if (old_selection > selection) /* we are moving the selected surface */ + { + old_selection++; + } + + gtk_widget_destroy(old_preset_area_menu); + + preview_create_preset_area_menu(p, old_selection); + } + + return TRUE; /* event is handled */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_preset_area_delete_callback(GtkWidget *widget, GtkWidget *preset_area_widget) +{ + int selection, i, old_selection = 0; + Preview *p; + GtkWidget *old_preset_area_menu; + + DBG(DBG_proc, "preview_preset_area_delete_callback\n"); + + selection = (int) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Selection"); + p = (Preview *) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Preview"); + + + if (selection) /* full size can not be deleted */ + { + DBG(DBG_info ,"deleting %s\n", preferences.preset_area[selection]->name); + + free(preferences.preset_area[selection]); + + for (i=selection; i<preferences.preset_area_definitions-1; i++) + { + preferences.preset_area[i] = preferences.preset_area[i+1]; + } + + preferences.preset_area_definitions--; + + old_preset_area_menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + + gtk_option_menu_remove_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + old_selection = (int) gtk_object_get_data(GTK_OBJECT(gtk_menu_get_active(GTK_MENU(old_preset_area_menu))), "Selection"); + + if (old_selection == selection) /* we are deleting the selected surface */ + { + old_selection = 0; + } + else if (old_selection > selection) /* we are deleting the selected surface */ + { + old_selection--; + } + + gtk_widget_destroy(old_preset_area_menu); + + preview_create_preset_area_menu(p, old_selection); /* build menu and set default to 0=full size */ + } + + return TRUE; /* event is handled */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_preset_area_move_up_callback(GtkWidget *widget, GtkWidget *preset_area_widget) +{ + int selection, old_selection = 0; + Preview *p; + GtkWidget *old_preset_area_menu; + + DBG(DBG_proc, "preview_preset_area_move_up_callback\n"); + + selection = (int) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Selection"); + p = (Preview *) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Preview"); + + if (selection > 1) /* make sure "full area" stays at top */ + { + Preferences_preset_area_t *help_area; + + DBG(DBG_info ,"moving up %s\n", preferences.preset_area[selection]->name); + + help_area = preferences.preset_area[selection-1]; + preferences.preset_area[selection-1] = preferences.preset_area[selection]; + preferences.preset_area[selection] = help_area; + + old_preset_area_menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + + gtk_option_menu_remove_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + old_selection = (int) gtk_object_get_data(GTK_OBJECT(gtk_menu_get_active(GTK_MENU(old_preset_area_menu))), "Selection"); + + if (old_selection == selection) + { + old_selection--; + } + else if (old_selection == selection-1) + { + old_selection++; + } + + gtk_widget_destroy(old_preset_area_menu); + + preview_create_preset_area_menu(p, old_selection); /* build menu and set default to 0=full size */ + } + + return TRUE; /* event is handled */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_preset_area_move_down_callback(GtkWidget *widget, GtkWidget *preset_area_widget) +{ + int selection, old_selection = 0; + Preview *p; + GtkWidget *old_preset_area_menu; + + DBG(DBG_proc, "preview_preset_area_move_down_callback\n"); + + selection = (int) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Selection"); + p = (Preview *) gtk_object_get_data(GTK_OBJECT(preset_area_widget), "Preview"); + + /* full size can not moved down */ + if ((selection) && (selection < preferences.preset_area_definitions-1)) + { + Preferences_preset_area_t *help_area; + + DBG(DBG_info ,"moving down %s\n", preferences.preset_area[selection]->name); + + help_area = preferences.preset_area[selection]; + preferences.preset_area[selection] = preferences.preset_area[selection+1]; + preferences.preset_area[selection+1] = help_area; + + old_preset_area_menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + + gtk_option_menu_remove_menu(GTK_OPTION_MENU(p->preset_area_option_menu)); + old_selection = (int) gtk_object_get_data(GTK_OBJECT(gtk_menu_get_active(GTK_MENU(old_preset_area_menu))), "Selection"); + + if (old_selection == selection) + { + old_selection++; + } + else if (old_selection == selection+1) + { + old_selection--; + } + + gtk_widget_destroy(old_preset_area_menu); + + preview_create_preset_area_menu(p, old_selection); /* build menu and set default to 0=full size */ + } + + return TRUE; /* event is handled */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static gint preview_preset_area_context_menu_callback(GtkWidget *widget, GdkEvent *event) +{ + GtkWidget *menu; + GtkWidget *menu_item; + GdkEventButton *event_button; + int selection; + + DBG(DBG_proc, "preview_preset_area_context_menu_callback\n"); + + selection = (int) gtk_object_get_data(GTK_OBJECT(widget), "Selection"); + + if (event->type == GDK_BUTTON_PRESS) + { + event_button = (GdkEventButton *) event; + + if (event_button->button == 3) + { + menu = gtk_menu_new(); + + /** add selection */ + menu_item = gtk_menu_item_new_with_label(MENU_ITEM_PRESET_AREA_ADD_SEL); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + gtk_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) preview_preset_area_add_callback, widget); + + /* rename preset area */ + menu_item = gtk_menu_item_new_with_label(MENU_ITEM_PRESET_AREA_RENAME); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + gtk_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) preview_preset_area_rename_callback, widget); + + if (selection) /* not available for "full area" */ + { + /* delete preset area */ + menu_item = gtk_menu_item_new_with_label(MENU_ITEM_PRESET_AREA_DELETE); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + gtk_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) preview_preset_area_delete_callback, widget); + } + + if (selection>1) /* available from 3rd item */ + { + /* move up */ + menu_item = gtk_menu_item_new_with_label(MENU_OTEM_PRESET_AREA_MOVE_UP); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + gtk_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) preview_preset_area_move_up_callback, widget); + } + + if ((selection) && (selection < preferences.preset_area_definitions-1)) + { + /* move down */ + menu_item = gtk_menu_item_new_with_label(MENU_OTEM_PRESET_AREA_MOVE_DWN); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + gtk_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) preview_preset_area_move_down_callback, widget); + } + + gtk_widget_show(menu); + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event_button->button, event_button->time); + + return TRUE; /* event is handled */ + } + } + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_preset_area_callback(GtkWidget *widget, gpointer call_data) +{ + Preview *p = call_data; + int selection; + + DBG(DBG_proc, "preview_preset_area_callback\n"); + + selection = (int) gtk_object_get_data(GTK_OBJECT(widget), "Selection"); + + p->preset_surface[0] = preferences.preset_area[selection]->xoffset; + p->preset_surface[1] = preferences.preset_area[selection]->yoffset; + p->preset_surface[2] = preferences.preset_area[selection]->xoffset + preferences.preset_area[selection]->width; + p->preset_surface[3] = preferences.preset_area[selection]->yoffset + preferences.preset_area[selection]->height; + + gtk_widget_set_sensitive(p->zoom_not, TRUE); /* allow unzoom */ + gtk_widget_set_sensitive(p->zoom_undo, FALSE); /* forbid undo zoom */ + + preview_update_surface(p, 0); + preview_zoom_not(NULL, p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_rotation_callback(GtkWidget *widget, gpointer call_data) +{ + Preview *p = call_data; + float rotated_surface[4]; + int rot; + + DBG(DBG_proc, "preview_rotation_callback\n"); + + rot = (int) gtk_object_get_data(GTK_OBJECT(widget), "Selection"); + + switch (rot) + { + case 0: /* 0 degree */ + default: + p->index_xmin = 0; + p->index_xmax = 2; + p->index_ymin = 1; + p->index_ymax = 3; + break; + + case 1: /* 90 degree */ + p->index_xmin = 2; + p->index_xmax = 0; + p->index_ymin = 1; + p->index_ymax = 3; + break; + + case 2: /* 180 degree */ + p->index_xmin = 2; + p->index_xmax = 0; + p->index_ymin = 3; + p->index_ymax = 1; + break; + + case 3: /* 270 degree */ + p->index_xmin = 0; + p->index_xmax = 2; + p->index_ymin = 3; + p->index_ymax = 1; + break; + + case 4: /* 0 degree, x mirror */ + p->index_xmin = 2; + p->index_xmax = 0; + p->index_ymin = 1; + p->index_ymax = 3; + break; + + case 5: /* 90 degree, x mirror */ + p->index_xmin = 0; + p->index_xmax = 2; + p->index_ymin = 1; + p->index_ymax = 3; + break; + + case 6: /* 180 degree, x mirror */ + p->index_xmin = 0; + p->index_xmax = 2; + p->index_ymin = 3; + p->index_ymax = 1; + break; + + case 7: /* 270 degree, x mirror */ + p->index_xmin = 2; + p->index_xmax = 0; + p->index_ymin = 3; + p->index_ymax = 1; + } + + /* at first undo mirror function, this is necessary because order does matter */ + if (p->rotation & 4) + { + rotated_surface[0] = p->surface[0]; + rotated_surface[1] = p->surface[1]; + rotated_surface[2] = p->surface[2]; + rotated_surface[3] = p->surface[3]; + preview_rotate_devicesurface_to_previewsurface(4, rotated_surface, p->surface); + } + + /* now rotate the selection area and do mirror function (can be done in one step) */ + rotated_surface[0] = p->surface[0]; + rotated_surface[1] = p->surface[1]; + rotated_surface[2] = p->surface[2]; + rotated_surface[3] = p->surface[3]; + preview_rotate_devicesurface_to_previewsurface(( ( (rot & 3) - (p->rotation & 3) ) & 3 ) + /* rotation */ (rot & 4)/* x mirror */, rotated_surface, p->surface); + + p->rotation = rot; + + preview_update_selection(p); /* read selection from backend: correct rotation */ + preview_update_surface(p, 2); /* rotate surfaces */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +static void preview_autoselect_scanarea_callback(GtkWidget *window, gpointer data) +{ + Preview *p=data; + + preview_autoselect_scanarea(p, p->selection.coordinate); /* get autoselection coordinates */ + preview_draw_selection(p); + preview_establish_selection(p); + xsane_update_histogram(TRUE /* update raw */); /* update histogram (necessary because overwritten by preview_update_surface */ +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_do_gamma_correction(Preview *p) +{ + int x,y; + int offset; + u_char *image_data_enhp; + guint16 *image_data_rawp; + int rotate = 16 - preview_gamma_input_bits; + + DBG(DBG_proc, "preview_do_gamma_correction\n"); + + if ((p->image_data_raw) && (p->params.depth > 1) && (preview_gamma_data_red)) + { + if ( (xsane.param.format == SANE_FRAME_RGB) || /* color preview */ + (xsane.param.format == SANE_FRAME_RED) || + (xsane.param.format == SANE_FRAME_GREEN) || + (xsane.param.format == SANE_FRAME_BLUE) ) + { + for (y=0; y < p->image_height; y++) + { + offset = 3 * (y * p->image_width); + + image_data_rawp = p->image_data_raw + offset; + image_data_enhp = p->image_data_enh + offset; + + for (x=0; x < p->image_width; x++) + { + *image_data_enhp++ = preview_gamma_data_red [(*image_data_rawp++) >> rotate]; + *image_data_enhp++ = preview_gamma_data_green[(*image_data_rawp++) >> rotate]; + *image_data_enhp++ = preview_gamma_data_blue [(*image_data_rawp++) >> rotate]; + } + + if (p->gamma_functions_interruptable) + { + while (gtk_events_pending()) + { + DBG(DBG_info, "preview_do_gamma_correction: calling gtk_main_iteration\n"); + gtk_main_iteration(); + } + } + } + } + else /* grayscale preview */ + { + int level; + + for (y=0; y < p->image_height; y++) + { + offset = 3 * (y * p->image_width); + + image_data_rawp = p->image_data_raw + offset; + image_data_enhp = p->image_data_enh + offset; + + for (x=0; x < p->image_width; x++) + { + level = ((*image_data_rawp++) + (*image_data_rawp++) + (*image_data_rawp++)) / 3; + level >>= rotate; + *image_data_enhp++ = preview_gamma_data_red [level]; /* use 12 bit gamma table */ + *image_data_enhp++ = preview_gamma_data_green[level]; + *image_data_enhp++ = preview_gamma_data_blue [level]; + } + + if (p->gamma_functions_interruptable) + { + while (gtk_events_pending()) + { + DBG(DBG_info, "preview_read_image_data (raw): calling gtk_main_iteration\n"); + gtk_main_iteration(); + } + } + } + } + } + + if (p->image_data_enh) + { + preview_display_partial_image(p); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_calculate_raw_histogram(Preview *p, SANE_Int *count_raw, SANE_Int *count_raw_red, SANE_Int *count_raw_green, SANE_Int *count_raw_blue) +{ + int x, y; + int offset; + SANE_Int red_raw, green_raw, blue_raw; + SANE_Int min_x, max_x, min_y, max_y; + float xscale, yscale; + guint16 *image_data_rawp; + + DBG(DBG_proc, "preview_calculate_raw_histogram\n"); + + preview_get_scale_device_to_image(p, &xscale, &yscale); + + switch (p->rotation) + { + case 0: /* 0 degree */ + default: + min_x = (p->selection.coordinate[0] - p->surface[0]) * xscale; + min_y = (p->selection.coordinate[1] - p->surface[1]) * yscale; + max_x = (p->selection.coordinate[2] - p->surface[0]) * xscale; + max_y = (p->selection.coordinate[3] - p->surface[1]) * yscale; + break; + + case 1: /* 90 degree */ + min_x = (p->selection.coordinate[1] - p->surface[1]) * xscale; + min_y = (p->selection.coordinate[2] - p->surface[2]) * xscale; + max_x = (p->selection.coordinate[3] - p->surface[1]) * xscale; + max_y = (p->selection.coordinate[0] - p->surface[2]) * xscale; + break; + + case 2: /* 180 degree */ + min_x = (p->selection.coordinate[2] - p->surface[2]) * xscale; + min_y = (p->selection.coordinate[3] - p->surface[3]) * yscale; + max_x = (p->selection.coordinate[0] - p->surface[2]) * xscale; + max_y = (p->selection.coordinate[1] - p->surface[3]) * yscale; + break; + + case 3: /* 270 degree */ + min_x = (p->selection.coordinate[3] - p->surface[3]) * xscale; + min_y = (p->selection.coordinate[0] - p->surface[0]) * yscale; + max_x = (p->selection.coordinate[1] - p->surface[3]) * xscale; + max_y = (p->selection.coordinate[2] - p->surface[0]) * yscale; + break; + + case 4: /* 0 degree, x mirror */ + min_x = (p->selection.coordinate[2] - p->surface[2]) * xscale; + min_y = (p->selection.coordinate[1] - p->surface[1]) * yscale; + max_x = (p->selection.coordinate[0] - p->surface[2]) * xscale; + max_y = (p->selection.coordinate[3] - p->surface[1]) * yscale; + break; + + case 5: /* 90 degree, x mirror */ + min_x = (p->selection.coordinate[1] - p->surface[1]) * xscale; + min_y = (p->selection.coordinate[0] - p->surface[0]) * yscale; + max_x = (p->selection.coordinate[3] - p->surface[1]) * xscale; + max_y = (p->selection.coordinate[2] - p->surface[0]) * yscale; + break; + + case 6: /* 180 degree, x mirror */ + min_x = (p->selection.coordinate[0] - p->surface[0]) * xscale; + min_y = (p->selection.coordinate[3] - p->surface[3]) * yscale; + max_x = (p->selection.coordinate[2] - p->surface[0]) * xscale; + max_y = (p->selection.coordinate[1] - p->surface[3]) * yscale; + break; + + case 7: /* 270 degree, x mirror */ + min_x = (p->selection.coordinate[3] - p->surface[3]) * xscale; + min_y = (p->selection.coordinate[2] - p->surface[2]) * yscale; + max_x = (p->selection.coordinate[1] - p->surface[3]) * xscale; + max_y = (p->selection.coordinate[0] - p->surface[2]) * yscale; + break; + } + + if (min_x < 0) + { + min_x = 0; + } + + if (max_x >= p->image_width) + { + max_x = p->image_width-1; + } + + if (min_y < 0) + { + min_y = 0; + } + + if (max_y >= p->image_height) + { + max_y = p->image_height-1; + } + + if ((p->image_data_raw) && (p->params.depth > 1) && (preview_gamma_data_red)) + { + for (y = min_y; y <= max_y; y++) + { + offset = 3 * (y * p->image_width + min_x); + image_data_rawp = p->image_data_raw + offset; + + if (!histogram_medium_gamma_data_red) /* no medium gamma table for histogran */ + { + for (x = min_x; x <= max_x; x++) + { + red_raw = (*image_data_rawp++) >> 8; /* reduce from 16 to 8 bits */ + green_raw = (*image_data_rawp++) >> 8; + blue_raw = (*image_data_rawp++) >> 8; + + count_raw [(u_char) ((red_raw + green_raw + blue_raw)/3)]++; + count_raw_red [red_raw]++; + count_raw_green[green_raw]++; + count_raw_blue [blue_raw]++; + } + } + else /* use medium gamma table for raw histogram */ + { + int rotate = 16 - preview_gamma_input_bits; + + for (x = min_x; x <= max_x; x++) + { + red_raw = histogram_medium_gamma_data_red [(*image_data_rawp++) >> rotate]; + green_raw = histogram_medium_gamma_data_green[(*image_data_rawp++) >> rotate]; + blue_raw = histogram_medium_gamma_data_blue [(*image_data_rawp++) >> rotate]; + + count_raw [(u_char) ((red_raw + green_raw + blue_raw)/3)]++; + count_raw_red [red_raw]++; + count_raw_green[green_raw]++; + count_raw_blue [blue_raw]++; + } + } + + if (p->gamma_functions_interruptable) + { + while (gtk_events_pending()) + { + DBG(DBG_info, "preview_calculate_raw_histogram: calling gtk_main_iteration\n"); + gtk_main_iteration(); + } + } + } + } + else /* no preview image => all colors = 1 */ + { + int i; + + for (i = 1; i <= 254; i++) + { + count_raw [i] = 0; + count_raw_red [i] = 0; + count_raw_green[i] = 0; + count_raw_blue [i] = 0; + } + + count_raw [0] = 10; + count_raw_red [0] = 10; + count_raw_green[0] = 10; + count_raw_blue [0] = 10; + + count_raw [255] = 10; + count_raw_red [255] = 10; + count_raw_green[255] = 10; + count_raw_blue [255] = 10; + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_calculate_enh_histogram(Preview *p, SANE_Int *count, SANE_Int *count_red, SANE_Int *count_green, SANE_Int *count_blue) +{ + int x, y; + int offset; + u_char red, green, blue; + SANE_Int min_x, max_x, min_y, max_y; + float xscale, yscale; + guint16 *image_data_rawp; + int rotate = 16 - preview_gamma_input_bits; + + DBG(DBG_proc, "preview_calculate_enh_histogram\n"); + + preview_get_scale_device_to_image(p, &xscale, &yscale); + + switch (p->rotation) + { + case 0: /* 0 degree */ + default: + min_x = (p->selection.coordinate[0] - p->surface[0]) * xscale; + min_y = (p->selection.coordinate[1] - p->surface[1]) * yscale; + max_x = (p->selection.coordinate[2] - p->surface[0]) * xscale; + max_y = (p->selection.coordinate[3] - p->surface[1]) * yscale; + break; + + case 1: /* 90 degree */ + min_x = (p->selection.coordinate[1] - p->surface[1]) * xscale; + min_y = (p->selection.coordinate[2] - p->surface[2]) * xscale; + max_x = (p->selection.coordinate[3] - p->surface[1]) * xscale; + max_y = (p->selection.coordinate[0] - p->surface[2]) * xscale; + break; + + case 2: /* 180 degree */ + min_x = (p->selection.coordinate[2] - p->surface[2]) * xscale; + min_y = (p->selection.coordinate[3] - p->surface[3]) * yscale; + max_x = (p->selection.coordinate[0] - p->surface[2]) * xscale; + max_y = (p->selection.coordinate[1] - p->surface[3]) * yscale; + break; + + case 3: /* 270 degree */ + min_x = (p->selection.coordinate[3] - p->surface[3]) * xscale; + min_y = (p->selection.coordinate[0] - p->surface[0]) * yscale; + max_x = (p->selection.coordinate[1] - p->surface[3]) * xscale; + max_y = (p->selection.coordinate[2] - p->surface[0]) * yscale; + break; + + case 4: /* 0 degree, x mirror */ + min_x = (p->selection.coordinate[2] - p->surface[2]) * xscale; + min_y = (p->selection.coordinate[1] - p->surface[1]) * yscale; + max_x = (p->selection.coordinate[0] - p->surface[2]) * xscale; + max_y = (p->selection.coordinate[3] - p->surface[1]) * yscale; + break; + + case 5: /* 90 degree, x mirror */ + min_x = (p->selection.coordinate[1] - p->surface[1]) * xscale; + min_y = (p->selection.coordinate[0] - p->surface[0]) * yscale; + max_x = (p->selection.coordinate[3] - p->surface[1]) * xscale; + max_y = (p->selection.coordinate[2] - p->surface[0]) * yscale; + break; + + case 6: /* 180 degree, x mirror */ + min_x = (p->selection.coordinate[0] - p->surface[0]) * xscale; + min_y = (p->selection.coordinate[3] - p->surface[3]) * yscale; + max_x = (p->selection.coordinate[2] - p->surface[0]) * xscale; + max_y = (p->selection.coordinate[1] - p->surface[3]) * yscale; + break; + + case 7: /* 270 degree, x mirror */ + min_x = (p->selection.coordinate[3] - p->surface[3]) * xscale; + min_y = (p->selection.coordinate[2] - p->surface[2]) * yscale; + max_x = (p->selection.coordinate[1] - p->surface[3]) * xscale; + max_y = (p->selection.coordinate[0] - p->surface[2]) * yscale; + break; + } + + if (min_x < 0) + { + min_x = 0; + } + + if (max_x >= p->image_width) + { + max_x = p->image_width-1; + } + + if (min_y < 0) + { + min_y = 0; + } + + if (max_y >= p->image_height) + { + max_y = p->image_height-1; + } + + if ((p->image_data_raw) && (p->params.depth > 1) && (preview_gamma_data_red)) + { + for (y = min_y; y <= max_y; y++) + { + offset = 3 * (y * p->image_width + min_x); + image_data_rawp = p->image_data_raw + offset; + + for (x = min_x; x <= max_x; x++) + { + red = histogram_gamma_data_red [(*image_data_rawp++) >> rotate]; + green = histogram_gamma_data_green[(*image_data_rawp++) >> rotate]; + blue = histogram_gamma_data_blue [(*image_data_rawp++) >> rotate]; + + count [(u_char) ((red + green + blue)/3)]++; + count_red [red]++; + count_green[green]++; + count_blue [blue]++; + } + + if (p->gamma_functions_interruptable) + { + while (gtk_events_pending()) + { + DBG(DBG_info, "preview_calculate_enh_histogram: calling gtk_main_iteration\n"); + gtk_main_iteration(); + } + } + } + } + else /* no preview image => all colors = 1 */ + { + int i; + + for (i = 1; i <= 254; i++) + { + count [i] = 0; + count_red [i] = 0; + count_green[i] = 0; + count_blue [i] = 0; + } + + count [0] = 10; + count_red [0] = 10; + count_green[0] = 10; + count_blue [0] = 10; + + count [255] = 10; + count_red [255] = 10; + count_green[255] = 10; + count_blue [255] = 10; + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_gamma_correction(Preview *p, int gamma_input_bits, + u_char *gamma_red, u_char *gamma_green, u_char *gamma_blue, + u_char *gamma_red_hist, u_char *gamma_green_hist, u_char *gamma_blue_hist, + u_char *medium_gamma_red_hist, u_char *medium_gamma_green_hist, u_char *medium_gamma_blue_hist) +{ + DBG(DBG_proc, "preview_gamma_correction\n"); + + preview_gamma_data_red = gamma_red; + preview_gamma_data_green = gamma_green; + preview_gamma_data_blue = gamma_blue; + + histogram_gamma_data_red = gamma_red_hist; + histogram_gamma_data_green = gamma_green_hist; + histogram_gamma_data_blue = gamma_blue_hist; + + histogram_medium_gamma_data_red = medium_gamma_red_hist; + histogram_medium_gamma_data_green = medium_gamma_green_hist; + histogram_medium_gamma_data_blue = medium_gamma_blue_hist; + + preview_gamma_input_bits = gamma_input_bits; + + preview_do_gamma_correction(p); + preview_draw_selection(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_area_resize(Preview *p) +{ + float min_x, max_x, delta_x; + float min_y, max_y, delta_y; + float xscale, yscale; + + DBG(DBG_proc, "preview_area_resize\n"); + + p->preview_window_width = p->window->allocation.width; + p->preview_window_height = p->window->allocation.height; + + p->preview_width = p->window->allocation.width; + p->preview_height = p->window->allocation.height; + + preview_area_correct(p); /* set preview dimensions (with right aspect) that it fits into the window */ + + if (p->preview_row) /* make sure preview_row is large enough for one line of the new size */ + { + p->preview_row = realloc(p->preview_row, 3 * p->preview_window_width); + } + else + { + p->preview_row = malloc(3 * p->preview_window_width); + } + + /* set the ruler ranges: */ + + min_x = p->surface[xsane_back_gtk_TL_X]; + max_x = p->surface[xsane_back_gtk_BR_X]; + min_y = p->surface[xsane_back_gtk_TL_Y]; + max_y = p->surface[xsane_back_gtk_BR_Y]; + + + if (min_x <= -INF) + { + min_x = 0.0; + } + if (min_x >= INF) + { + min_x = p->image_width - 1; + } + + if (max_x <= -INF) + { + max_x = 0.0; + } + if (max_x >= INF) + { + max_x = p->image_width - 1; + } + + if (min_y <= -INF) + { + min_y = 0.0; + } + if (min_y >= INF) + { + min_y = p->image_height - 1; + } + + if (max_y <= -INF) + { + max_y = 0.0; + } + if (max_y >= INF) + { + max_y = p->image_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; + } + + preview_get_scale_window_to_image(p, &xscale, &yscale); + + delta_x = max_x - min_x; + + gtk_ruler_set_range(GTK_RULER(p->hruler), min_x, min_x + delta_x*p->preview_window_width/p->preview_width, min_x, /* max_size */ 20); + + delta_y = max_y - min_y; + + gtk_ruler_set_range(GTK_RULER(p->vruler), min_y, min_y + delta_y*p->preview_window_height/p->preview_height, min_y, /* max_size */ 20); + + gtk_label_set_text(GTK_LABEL(p->unit_label), xsane_back_gtk_unit_string(p->surface_unit)); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +gint preview_area_resize_handler(GtkWidget *widget, GdkEvent *event, gpointer data) +{ + Preview *p = (Preview *) data; + DBG(DBG_proc, "preview_area_resize_handler\n"); + + preview_area_resize(p); + preview_paint_image(p); + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ +#if 0 +void preview_update_maximum_output_size(Preview *p) +{ + DBG(DBG_proc, "preview_update_maximum_output_size\n"); + + if ( (p->maximum_output_width >= INF) || (p->maximum_output_height >= INF) ) + { + if (p->selection_maximum.active) + { + p->selection_maximum.active = FALSE; + } + } + else + { + p->previous_selection_maximum = p->selection_maximum; + + p->selection_maximum.active = TRUE; + p->selection_maximum.coordinate[0] = (p->selection.coordinate[0] + p->selection.coordinate[2] - p->maximum_output_width )/2.0; + p->selection_maximum.coordinate[1] = (p->selection.coordinate[1] + p->selection.coordinate[3] - p->maximum_output_height)/2.0; + p->selection_maximum.coordinate[2] = (p->selection.coordinate[0] + p->selection.coordinate[2] + p->maximum_output_width )/2.0; + p->selection_maximum.coordinate[3] = (p->selection.coordinate[1] + p->selection.coordinate[3] + p->maximum_output_height)/2.0; + + if (p->selection_maximum.coordinate[0] < p->max_scanner_surface[0]) + { + p->selection_maximum.coordinate[0] = p->max_scanner_surface[0]; + } + + if (p->selection_maximum.coordinate[1] < p->max_scanner_surface[1]) + { + p->selection_maximum.coordinate[1] = p->max_scanner_surface[1]; + } + + if (p->selection_maximum.coordinate[2] > p->max_scanner_surface[2]) + { + p->selection_maximum.coordinate[2] = p->max_scanner_surface[2]; + } + + if (p->selection_maximum.coordinate[3] > p->max_scanner_surface[3]) + { + p->selection_maximum.coordinate[3] = p->max_scanner_surface[3]; + } + + if ( (p->selection.coordinate[0] < p->selection_maximum.coordinate[0]) || + (p->selection.coordinate[1] < p->selection_maximum.coordinate[1]) || + (p->selection.coordinate[2] > p->selection_maximum.coordinate[2]) || + (p->selection.coordinate[3] > p->selection_maximum.coordinate[3]) ) + { + if (p->selection.coordinate[0] < p->selection_maximum.coordinate[0]) + { + p->selection.coordinate[0] = p->selection_maximum.coordinate[0]; + } + + if (p->selection.coordinate[1] < p->selection_maximum.coordinate[1]) + { + p->selection.coordinate[1] = p->selection_maximum.coordinate[1]; + } + + if (p->selection.coordinate[2] > p->selection_maximum.coordinate[2]) + { + p->selection.coordinate[2] = p->selection_maximum.coordinate[2]; + } + + if (p->selection.coordinate[3] > p->selection_maximum.coordinate[3]) + { + p->selection.coordinate[3] = p->selection_maximum.coordinate[3]; + } + preview_draw_selection(p); + preview_establish_selection(p); + } + } +} +#endif + +void preview_update_maximum_output_size(Preview *p) +{ + if ( (p->maximum_output_width >= INF) || (p->maximum_output_height >= INF) ) + { + if (p->selection_maximum.active) + { + p->selection_maximum.active = FALSE; + } + } + else + { + p->previous_selection_maximum = p->selection_maximum; + + p->selection_maximum.active = TRUE; + p->selection_maximum.coordinate[p->index_xmin] = p->selection.coordinate[p->index_xmin]; + p->selection_maximum.coordinate[p->index_ymin] = p->selection.coordinate[p->index_ymin]; + p->selection_maximum.coordinate[p->index_xmax] = p->selection.coordinate[p->index_xmin] + p->maximum_output_width; + p->selection_maximum.coordinate[p->index_ymax] = p->selection.coordinate[p->index_ymin] + p->maximum_output_height; + + if (p->selection_maximum.coordinate[p->index_xmax] > p->max_scanner_surface[p->index_xmax]) + { + p->selection_maximum.coordinate[p->index_xmax] = p->max_scanner_surface[p->index_xmax]; + } + + if (p->selection_maximum.coordinate[p->index_ymax] > p->max_scanner_surface[p->index_ymax]) + { + p->selection_maximum.coordinate[p->index_ymax] = p->max_scanner_surface[p->index_ymax]; + } + + if ( (p->selection.coordinate[p->index_xmin] < p->selection_maximum.coordinate[p->index_xmin]) || + (p->selection.coordinate[p->index_ymin] < p->selection_maximum.coordinate[p->index_ymin]) || + (p->selection.coordinate[p->index_xmax] > p->selection_maximum.coordinate[p->index_xmax]) || + (p->selection.coordinate[p->index_ymax] > p->selection_maximum.coordinate[p->index_ymax]) ) + { + if (p->selection.coordinate[p->index_xmax] > p->selection_maximum.coordinate[p->index_xmax]) + { + p->selection.coordinate[p->index_xmax] = p->selection_maximum.coordinate[p->index_xmax]; + } + + if (p->selection.coordinate[p->index_ymax] > p->selection_maximum.coordinate[p->index_ymax]) + { + p->selection.coordinate[p->index_ymax] = p->selection_maximum.coordinate[p->index_ymax]; + } + preview_draw_selection(p); + preview_establish_selection(p); + } + } +} +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_set_maximum_output_size(Preview *p, float width, float height) +{ + /* witdh and height in device units */ + DBG(DBG_proc, "preview_set_maximum_output_size\n"); + + p->maximum_output_width = width; + p->maximum_output_height = height; + + preview_update_maximum_output_size(p); + preview_draw_selection(p); +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_autoselect_scanarea(Preview *p, float *autoselect_coord) +{ + int x, y; + int offset; + float color; + int top, bottom, left, right; + float xscale, yscale; + long bright_sum = 0; + int brightness; + int background_white; + + DBG(DBG_proc, "preview_autoselect_scanarea\n"); + + /* try to find out background color */ + /* add color values at the margins */ + /* and see if it is more black or more white */ + + /* upper line */ + for (x = 0; x < p->image_width; x++) + { + offset = 3 * x; + bright_sum += (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + } + + /* lower line */ + for (x = 0; x < p->image_width; x++) + { + offset = 3 * ( (p->image_height-1) * p->image_width + x); + bright_sum += (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + } + + /* left line */ + for (y = 0; y < p->image_height; y++) + { + offset = 3 * y * p->image_width; + bright_sum += (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + } + + /* right line */ + for (y = 0; y < p->image_height; y++) + { + offset = 3 * (y * p->image_width + p->image_width - 1); + bright_sum += (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + } + + brightness = bright_sum / (2 * (p->image_width + p->image_height) ); + DBG(DBG_info, "preview_autoselect_scanarea: average margin brightness is %d\n", brightness); + + if ( brightness > 128 ) + { + DBG(DBG_info, "preview_autoselect_scanarea: background is white\n"); + background_white = 1; + } + else + { + DBG(DBG_info, "preview_autoselect_scanarea: background is black\n"); + background_white = 0; + } + + + /* search top */ + top = 0; + for (y = 0; y < p->image_height; y++) + { + for (x = 0; x < p->image_width; x++) + { + offset = 3 * (y * p->image_width + x); + color = (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + if (background_white) + { + if (color < 200) + { + top = y; + break; + } + } + + else if (color > 55 ) + { + top = y; + break; + } + } + if (top) + { + break; + } + } + + + /* search bottom */ + bottom = 0; + for (y = p->image_height-1; y > top; y--) + { + for (x = 0; x < p->image_width; x++) + { + offset = 3 * (y * p->image_width + x); + color = (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + if (background_white) + { + if (color < 200) + { + bottom = y; + break; + } + } + else if (color > 55 ) + { + bottom = y; + break; + } + } + if (bottom) + { + break; + } + } + + + /* search left */ + left = 0; + for (x = 0; x < p->image_width; x++) + { + for (y = 0; y < p->image_height; y++) + { + offset = 3 * (y * p->image_width + x); + color = (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + if (background_white) + { + if (color < 200) + { + left = x; + break; + } + } + else if (color > 55 ) + { + left = x; + break; + } + } + if (left) + { + break; + } + } + + + /* search right */ + right = 0; + for (x = p->image_width-1; x > left; x--) + { + for (y = 0; y < p->image_height; y++) + { + offset = 3 * (y * p->image_width + x); + color = (p->image_data_enh[offset + 0] + p->image_data_enh[offset + 1] + p->image_data_enh[offset + 2]) / 3.0; + if (background_white) + { + if (color < 200) + { + right = x; + break; + } + } + else if (color > 55 ) + { + right = x; + break; + } + } + if (right) + { + break; + } + } + + preview_get_scale_device_to_image(p, &xscale, &yscale); + + if (((p->rotation & 3) == 0) || ((p->rotation & 3) == 2)) /* 0 or 180 degree */ + { + *(autoselect_coord+0) = p->image_surface[0] + left / xscale; + *(autoselect_coord+2) = p->image_surface[0] + right / xscale; + *(autoselect_coord+1) = p->image_surface[1] + top / yscale; + *(autoselect_coord+3) = p->image_surface[1] + bottom / yscale; + } + else /* 90 or 270 degree */ + { + *(autoselect_coord+1) = p->image_surface[1] + left / xscale; + *(autoselect_coord+3) = p->image_surface[1] + right / xscale; + *(autoselect_coord+0) = p->image_surface[0] + top / yscale; + *(autoselect_coord+2) = p->image_surface[0] + bottom / yscale; + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ + +void preview_display_valid(Preview *p) +{ + DBG(DBG_proc, "preview_display_valid\n"); + + if (p->scanning)/* we are just scanning the preview */ + { + DBG(DBG_info, "preview scanning\n"); + + gtk_widget_show(p->scanning_pixmap); + gtk_widget_hide(p->incomplete_pixmap); + gtk_widget_hide(p->valid_pixmap); + gtk_widget_hide(p->invalid_pixmap); + } + else if ((xsane.medium_changed) || (xsane.xsane_colors != p->preview_colors) || (p->invalid) ) /* preview is not valid */ + { + DBG(DBG_info, "preview not vaild\n"); + + gtk_widget_show(p->invalid_pixmap); + gtk_widget_hide(p->scanning_pixmap); + gtk_widget_hide(p->incomplete_pixmap); + gtk_widget_hide(p->valid_pixmap); + } + else if (p->scan_incomplete)/* preview scan has been cancled */ + { + DBG(DBG_info, "preview incomplete\n"); + + gtk_widget_show(p->incomplete_pixmap); + gtk_widget_hide(p->scanning_pixmap); + gtk_widget_hide(p->valid_pixmap); + gtk_widget_hide(p->invalid_pixmap); + } + else /* preview is valid */ + { + DBG(DBG_info, "preview vaild\n"); + + gtk_widget_show(p->valid_pixmap); + gtk_widget_hide(p->scanning_pixmap); + gtk_widget_hide(p->incomplete_pixmap); + gtk_widget_hide(p->invalid_pixmap); + } +} + +/* ---------------------------------------------------------------------------------------------------------------------- */ |