From 8b7d063442c10847f8aa05943e09c2ea8cd4e10c Mon Sep 17 00:00:00 2001 From: Ralph Little Date: Thu, 12 Jun 2025 17:37:15 -0700 Subject: [PATCH 1/2] preview: use GPixBuf and and lib function to do resize Better to use stock functions to do image preview resizing. --- src/preview.c | 314 ++++++++++++++++++++++++++++++-------------------- src/preview.h | 6 +- 2 files changed, 190 insertions(+), 130 deletions(-) diff --git a/src/preview.c b/src/preview.c index a7749d6..9116ec3 100644 --- a/src/preview.c +++ b/src/preview.c @@ -156,14 +156,24 @@ screen_size_get_dimensions (gint *width, gint *height) } static void -draw_rect(cairo_t *cr, double coord[4]) +draw_selection (Preview *p, cairo_t *cr) { + if (!p->selection.active) + return; + + double *coord = p->selection.coord; double x, y, w, h; + /* + * Make sure that we account for a selection dragged in reverse + * from what we expect, e.g leftwards or upwards or both. + * + */ x = coord[0]; y = coord[1]; w = coord[2] - x; h = coord[3] - y; + if (w < 0) { x = coord[2]; @@ -174,32 +184,30 @@ draw_rect(cairo_t *cr, double coord[4]) y = coord[3]; h = -h; } - cairo_set_line_width(cr, 1.5); - const double dashes1[2] = {4.0, 4.0}; + + /* + * Draw selection rectangle. One black dash, then another, filling in the + * black dash gaps with white. This means that regardless of the picture, the + * dash pattern is visible. + * + */ + const double dashes1[2] = { 4.0, 4.0 }; + const double dashes2[4] = { 0.0, 4.0, 4.0, 0.0 }; + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_line_width (cr, 1.5); + + // Black dashes. cairo_set_source_rgb (cr, 0, 0, 0); - cairo_set_dash(cr, dashes1, sizeof(dashes1)/sizeof(dashes1[0]), 0); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); - cairo_rectangle(cr, x, y, w + 1, h + 1); - cairo_stroke(cr); - cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); - const double dashes2[4] = {0.0, 4.0, 4.0, 0.0}; - cairo_set_dash(cr, dashes2, sizeof(dashes2)/sizeof(dashes2[0]), 0); - cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); - cairo_rectangle(cr, x, y, w + 1, h + 1); - cairo_stroke(cr); -} + cairo_set_dash (cr, dashes1, sizeof(dashes1) / sizeof(dashes1[0]), 0); + cairo_rectangle (cr, x, y, w + 1, h + 1); + cairo_stroke (cr); -static void -draw_selection (Preview * p, cairo_t *cr) -{ - if (p->previous_selection.active) - draw_rect (cr, p->previous_selection.coord); - - if (p->selection.active) - draw_rect (cr, p->selection.coord); - - p->previous_selection = p->selection; + // White dashes. + cairo_set_dash (cr, dashes2, sizeof(dashes2) / sizeof(dashes2[0]), 0); + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_rectangle (cr, x, y, w + 1, h + 1); + cairo_stroke (cr); } static void @@ -211,8 +219,6 @@ update_selection (Preview * p) SANE_Word val; int i, optnum; - p->previous_selection = p->selection; - memcpy (dev_selection, p->surface, sizeof (dev_selection)); for (i = 0; i < 4; ++i) { @@ -288,62 +294,62 @@ get_image_scale (Preview * p, float *xscalep, float *yscalep) static void paint_image (Preview * p) { - float xscale, yscale, src_x, src_y; - int dst_x, dst_y, height, x, y, src_offset; - gint gwidth, gheight; - - gwidth = p->preview_width; - gheight = p->preview_height; - - get_image_scale (p, &xscale, &yscale); - - if (p->preview_row == NULL) - p->preview_row = malloc (3 * gwidth); - else - p->preview_row = realloc (p->preview_row, 3 * gwidth); - memset (p->preview_row, 0xff, 3 * gwidth); - if (p->preview_data == NULL) - p->preview_data = malloc (3 * gwidth * gheight); - else - p->preview_data = realloc (p->preview_data, 3 * gwidth * gheight); - memset (p->preview_data, 0xff, 3 * gwidth * gheight); - gtk_widget_queue_draw (p->window); - - /* don't draw last line unless it's complete: */ - height = p->image_y; - if (p->image_x == 0 && height < p->image_height) - ++height; - - /* for now, use simple nearest-neighbor interpolation: */ - src_offset = 0; - src_x = src_y = 0.0; - for (dst_y = 0; dst_y < gheight; ++dst_y) - { - y = (int) (src_y + 0.5); - if (y >= height) - break; - src_offset = y * 3 * p->image_width; - - if (p->image_data) - for (dst_x = 0; dst_x < gwidth; ++dst_x) - { - x = (int) (src_x + 0.5); - if (x >= p->image_width) - break; - - p->preview_row[3 * dst_x + 0] = - p->image_data[src_offset + 3 * x + 0]; - p->preview_row[3 * dst_x + 1] = - p->image_data[src_offset + 3 * x + 1]; - p->preview_row[3 * dst_x + 2] = - p->image_data[src_offset + 3 * x + 2]; - src_x += xscale; - } - memcpy(p->preview_data + (size_t) dst_y * (size_t) gwidth * 3, p->preview_row, (size_t) gwidth * 3); - src_x = 0.0; - src_y += yscale; - } - gtk_widget_queue_draw (p->window); +// float xscale, yscale, src_x, src_y; +// int dst_x, dst_y, height, x, y, src_offset; +// gint gwidth, gheight; +// +// gwidth = p->preview_width; +// gheight = p->preview_height; +// +// get_image_scale (p, &xscale, &yscale); +// +// if (p->preview_row == NULL) +// p->preview_row = malloc (3 * gwidth); +// else +// p->preview_row = realloc (p->preview_row, 3 * gwidth); +// memset (p->preview_row, 0xff, 3 * gwidth); +//// if (p->preview_data == NULL) +//// p->preview_data = malloc (3 * gwidth * gheight); +//// else +//// p->preview_data = realloc (p->preview_data, 3 * gwidth * gheight); +//// memset (p->preview_data, 0xff, 3 * gwidth * gheight); +// gtk_widget_queue_draw (p->window); +// +// /* don't draw last line unless it's complete: */ +// height = p->image_y; +// if (p->image_x == 0 && height < p->image_height) +// ++height; +// +// /* for now, use simple nearest-neighbor interpolation: */ +// src_offset = 0; +// src_x = src_y = 0.0; +// for (dst_y = 0; dst_y < gheight; ++dst_y) +// { +// y = (int) (src_y + 0.5); +// if (y >= height) +// break; +// src_offset = y * 3 * p->image_width; +// +// if (p->image_data) +// for (dst_x = 0; dst_x < gwidth; ++dst_x) +// { +// x = (int) (src_x + 0.5); +// if (x >= p->image_width) +// break; +// +// p->preview_row[3 * dst_x + 0] = +// p->image_data[src_offset + 3 * x + 0]; +// p->preview_row[3 * dst_x + 1] = +// p->image_data[src_offset + 3 * x + 1]; +// p->preview_row[3 * dst_x + 2] = +// p->image_data[src_offset + 3 * x + 2]; +// src_x += xscale; +// } +// memcpy(p->preview_data + (size_t) dst_y * (size_t) gwidth * 3, p->preview_row, (size_t) gwidth * 3); +// src_x = 0.0; +// src_y += yscale; +// } +// gtk_widget_queue_draw (p->window); } static void @@ -376,8 +382,8 @@ display_image (Preview * p) p->image_data = realloc (p->image_data, 3 * p->image_width * p->image_height); assert (p->image_data); - p->preview_data = realloc (p->preview_data, - 3 * p->image_width * p->image_height); +// p->preview_data = realloc (p->preview_data, +// 3 * p->image_width * p->image_height); } display_partial_image (p); scan_done (p); @@ -608,7 +614,7 @@ increment_image_y (Preview * p) extra_size = 3 * 32 * p->image_width; p->image_height += 32; p->image_data = realloc (p->image_data, offset + extra_size); - p->preview_data = realloc (p->preview_data, offset + extra_size); +// p->preview_data = realloc (p->preview_data, offset + extra_size); if (!p->image_data) { snprintf (buf, sizeof (buf), @@ -619,7 +625,10 @@ increment_image_y (Preview * p) return -1; } memset (p->image_data + offset, 0xff, extra_size); - memset (p->preview_data + offset, 0xff, extra_size); +// memset (p->preview_data + offset, 0xff, extra_size); + + g_object_unref(p->image_pixbuf); + p->image_pixbuf = NULL; } return 0; } @@ -949,12 +958,16 @@ scan_start (Preview * p) gsg_set_sensitivity (p->dialog, FALSE); // gtk_widget_set_sensitive (p->dialog->window->parent->parent->parent, FALSE); - if (p->preview_data == NULL) - p->preview_data = malloc (p->image_width * p->image_height * 3); - else - p->preview_data = realloc (p->preview_data, 3 * p->image_width * p->image_height); +// if (p->preview_data == NULL) +// p->preview_data = malloc (p->image_width * p->image_height * 3); +// else +// p->preview_data = realloc (p->preview_data, 3 * p->image_width * p->image_height); /* clear old preview: */ - memset (p->preview_data, 0xff, 3 * p->image_width * p->image_height); +// memset (p->preview_data, 0xff, 3 * p->image_width * p->image_height); + + g_object_unref(p->image_pixbuf); + p->image_pixbuf = NULL; + gtk_widget_queue_draw (p->window); if (p->input_tag >= 0) @@ -1040,6 +1053,12 @@ scan_start (Preview * p) if (!p->image_data || p->params.pixels_per_line != p->image_width || (p->params.lines >= 0 && p->params.lines != p->image_height)) { + if (p->image_pixbuf) + { + g_object_unref(p->image_pixbuf); + p->image_pixbuf = NULL; + } + /* image size changed */ if (p->image_data) free (p->image_data); @@ -1050,7 +1069,7 @@ scan_start (Preview * p) p->image_height = 32; /* may have to adjust as we go... */ p->image_data = malloc (p->image_width * p->image_height * 3); - p->preview_data = malloc (p->preview_width * p->image_height * 3); +// p->preview_data = malloc (p->preview_width * p->image_height * 3); if (!p->image_data) { snprintf (buf, sizeof (buf), @@ -1064,7 +1083,7 @@ scan_start (Preview * p) if (p->selection.active) { - p->previous_selection = p->selection; +// p->previous_selection = p->selection; p->selection.active = FALSE; gtk_widget_queue_draw (p->window); } @@ -1129,23 +1148,22 @@ restore_preview_image (Preview * p) int width, height; float psurface[4]; size_t nread; - FILE *in; + FILE *in = NULL; /* See whether there is a saved preview and load it if present: */ - if (make_preview_image_path (p, sizeof (filename), filename) < 0) - return; + goto finish; in = fopen (filename, "r"); if (!in) - return; + goto finish; /* Be careful about consuming too many bytes after the final newline (e.g., consider an image whose first image byte is 13 (`\r'). */ if (fscanf (in, "P6\n# surface: %g %g %g %g %u %u\n%d %d\n255%*[\n]", psurface + 0, psurface + 1, psurface + 2, psurface + 3, &psurface_type, &psurface_unit, &width, &height) != 8) - return; + goto finish; if (GROSSLY_DIFFERENT (psurface[0], p->surface[0]) || GROSSLY_DIFFERENT (psurface[1], p->surface[1]) @@ -1153,12 +1171,12 @@ restore_preview_image (Preview * p) || GROSSLY_DIFFERENT (psurface[3], p->surface[3]) || psurface_type != p->surface_type || psurface_unit != p->surface_unit) /* ignore preview image that was acquired for/with a different surface */ - return; + goto finish; p->image_width = width; p->image_height = height; if ((width == 0) || (height == 0)) - return; + goto finish; int data_size = 3 * width * height; @@ -1166,25 +1184,21 @@ restore_preview_image (Preview * p) if ((data_size / width) / height != 3) { // overflow occurred. Ignore the image. The dimensions are probably corrupted. - return; + goto finish; } p->image_data = malloc (data_size); if (!p->image_data) - return; - - p->preview_data = malloc (data_size); - if (!p->preview_data) - { - free(p->image_data); - p->image_data = NULL; - return; - } + goto finish; nread = fread (p->image_data, 3, width * height, in); p->image_y = nread / width; p->image_x = nread % width; + +finish: + if (in) + fclose(in); } /* This is executed _after_ the gtkpreview's expose routine. */ @@ -1193,13 +1207,40 @@ expose_handler (GtkWidget * window, cairo_t *cr, gpointer data) { Preview *p = data; - if (p->preview_data == NULL) return FALSE; + if (p->image_data == NULL) + return FALSE; - GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data(p->preview_data, GDK_COLORSPACE_RGB, FALSE, 8, p->preview_width, - p->preview_height, p->preview_width * 3, NULL/*preview_pixbuf_data_destroy*/, NULL); - gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + if (p->image_pixbuf == NULL) + { + p->image_pixbuf = gdk_pixbuf_new_from_data (p->image_data, + GDK_COLORSPACE_RGB, FALSE, 8, + p->image_width, + p->image_height, + p->image_width * 3, NULL, + NULL); + + } + + gint width = gtk_widget_get_allocated_width (window); + gint height = gtk_widget_get_allocated_height (window); + + gint img_width = gdk_pixbuf_get_width (p->image_pixbuf); + gint img_height = gdk_pixbuf_get_height (p->image_pixbuf); + double scale = MIN((double )width / img_width, (double )height / img_height); + gint scaled_width = img_width * scale; + gint scaled_height = img_height * scale; + + /* + * TODO: we could store this scaled pixbuf in the Preview and only regenerate it + * when the scaled image dimensions change. [RL] + */ + GdkPixbuf *scaled = gdk_pixbuf_scale_simple (p->image_pixbuf, scaled_width, + scaled_height, + GDK_INTERP_BILINEAR); + + gdk_cairo_set_source_pixbuf (cr, scaled, 0, 0); cairo_paint(cr); - g_object_unref(pixbuf); + g_object_unref(scaled); if (p->selection_drag == FALSE) update_selection (p); @@ -1317,10 +1358,10 @@ preview_new (GSGDialog * dialog) p->surface_unit = SANE_UNIT_MM; p->input_tag = -1; p->image_data = NULL; - p->preview_data = NULL; p->preview_row = NULL; p->top = NULL; p->scanning = FALSE; + p->image_pixbuf = NULL; if (first_time) { @@ -1470,9 +1511,11 @@ preview_update (Preview * p) if (surface_changed && p->image_data) { free (p->image_data); - free (p->preview_data); + + g_object_unref(p->image_pixbuf); + p->image_pixbuf = NULL; + p->image_data = 0; - p->preview_data = 0; p->image_width = 0; p->image_height = 0; } @@ -1630,6 +1673,11 @@ preview_destroy (Preview * p) char filename[PATH_MAX]; FILE *out; + if (!p) + { + return; + } + if (p->scanning) scan_done (p); /* don't save partial window */ else if (preferences.preserve_preview && p->image_data @@ -1648,15 +1696,29 @@ preview_destroy (Preview * p) fclose (out); } } + if (p->image_data) - free (p->image_data); - p->image_data = NULL; - if (p->preview_data) - free (p->preview_data); - p->preview_data = NULL; + { + free (p->image_data); + p->image_data = NULL; + } + +// if (p->preview_data) +// free (p->preview_data); +// p->preview_data = NULL; + + if (p->image_pixbuf) + { + g_object_unref(p->image_pixbuf); + p->image_pixbuf = NULL; + } + if (p->preview_row) - free (p->preview_row); - p->preview_row = NULL; + { + free (p->preview_row); + p->preview_row = NULL; + } + if (p->top) gtk_widget_destroy (p->top); free (p); diff --git a/src/preview.h b/src/preview.h index e843693..492d9dd 100644 --- a/src/preview.h +++ b/src/preview.h @@ -62,7 +62,6 @@ typedef struct int image_width; int image_height; u_char *image_data; /* 3 * image_width * image_height bytes */ - u_char *preview_data; /* 3 * image_width * image_height bytes */ int selection_drag; struct @@ -70,7 +69,7 @@ typedef struct int active; double coord[4]; } - selection, previous_selection; + selection; // coords of selection box in the preview window GtkWidget *top; /* top-level widget */ GtkWidget *hruler; @@ -80,8 +79,7 @@ typedef struct GtkWidget *cancel; /* the cancel button */ GtkWidget *preview; /* the preview button */ - cairo_surface_t *surface_cairo; - gboolean drawable; + GdkPixbuf *image_pixbuf; } Preview; From 9d715d2adb98970787f422ee8f82ad4501857e10 Mon Sep 17 00:00:00 2001 From: Ralph Little Date: Mon, 23 Jun 2025 19:27:33 -0700 Subject: [PATCH 2/2] xscanimage: a bit of refactoring of preview I removed some redundant code and fixed up the preview selection. --- src/preferences.c | 6 ++- src/preferences.h | 2 + src/preview.c | 132 ++++++++++++++++++++-------------------------- 3 files changed, 63 insertions(+), 77 deletions(-) diff --git a/src/preferences.c b/src/preferences.c index 16c3839..91f8788 100644 --- a/src/preferences.c +++ b/src/preferences.c @@ -42,7 +42,9 @@ Preferences preferences = 10.0, /* length unit */ 1, /* preserve_preview */ 0, /* preview_own_cmap */ - 1.0 /* preview_gamma */ + 1.0, /* preview_gamma */ + 0, /* preview width, will be set by software */ + 0 /* preview height, will be set by software. */ }; static void w_string (Wire *w, Preferences *p, long offset); @@ -66,6 +68,8 @@ desc[] = {"preserve-preview", w_int, POFFSET(preserve_preview)}, {"preview-own-cmap", w_int, POFFSET(preview_own_cmap)}, {"preview-gamma", w_double, POFFSET(preview_gamma)}, + {"preview-width", w_int, POFFSET(preview_width)}, + {"preview-height", w_int, POFFSET(preview_height)}, }; static void diff --git a/src/preferences.h b/src/preferences.h index 4866eb0..a91d8c8 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -16,6 +16,8 @@ typedef struct int preserve_preview; /* save/restore preview image(s)? */ int preview_own_cmap; /* install colormap for preview */ double preview_gamma; /* gamma value for previews */ + int preview_width; /* width of the preview window. */ + int preview_height; /* height of the preview window. */ } Preferences; diff --git a/src/preview.c b/src/preview.c index 9116ec3..7506cd1 100644 --- a/src/preview.c +++ b/src/preview.c @@ -243,7 +243,7 @@ update_selection (Preview * p) min = 0.0; max = p->surface[i + 2]; if (max >= INF) - max = p->preview_width; + max = (i == 0) ? p->preview_width : p->preview_height; normal = ((i == 0) ? p->preview_width : p->preview_height) - 1; normal /= (max - min); @@ -1218,7 +1218,11 @@ expose_handler (GtkWidget * window, cairo_t *cr, gpointer data) p->image_height, p->image_width * 3, NULL, NULL); - + if (!p->image_pixbuf) + { + // Scaled image could not be generated. + return FALSE; + } } gint width = gtk_widget_get_allocated_width (window); @@ -1230,9 +1234,19 @@ expose_handler (GtkWidget * window, cairo_t *cr, gpointer data) gint scaled_width = img_width * scale; gint scaled_height = img_height * scale; + p->preview_width = scaled_width; + p->preview_height = scaled_height; + + /* + * Save out the preview window size to the prefs structure. + * + */ + gtk_window_get_size (GTK_WINDOW(p->top), &preferences.preview_width, + &preferences.preview_height); + /* * TODO: we could store this scaled pixbuf in the Preview and only regenerate it - * when the scaled image dimensions change. [RL] + * when the scaled image dimensions or content changes. [RL] */ GdkPixbuf *scaled = gdk_pixbuf_scale_simple (p->image_pixbuf, scaled_width, scaled_height, @@ -1376,6 +1390,17 @@ preview_new (GSGDialog * dialog) gtk_window_set_title (GTK_WINDOW (p->top), "xscanimage preview"); vbox = GTK_BOX (gtk_dialog_get_content_area(GTK_DIALOG (p->top))); + // Set the default size of the preview window. + if ((preferences.preview_width == 0) || (preferences.preview_height == 0)) + { + int screen_width, screen_height; + screen_size_get_dimensions (&screen_width, &screen_height); + preferences.preview_width = 0.5 * (double)screen_width; + preferences.preview_height = 0.7 * (double)screen_height; + } + gtk_window_set_default_size (GTK_WINDOW(p->top), preferences.preview_width, + preferences.preview_height); + // construct the preview area (table with sliders & preview window) table = gtk_grid_new (); gtk_grid_set_column_spacing (GTK_GRID (table), 1); @@ -1462,6 +1487,13 @@ preview_new (GSGDialog * dialog) return p; } +/* + * The purpose of this function is to take account of external changes + * and reflect them in the preview. + * + * - If the image geometry or other parameters change, then reset the preview window. + * - + */ void preview_update (Preview * p) { @@ -1472,6 +1504,20 @@ preview_update (Preview * p) SANE_Unit unit; float min, max; + /* + * Read preview image geometry from the options. This determines the area that + * we are scanning for the preview image itself. This will typically be the maximum + * extents of the width and height that the backend will support. So, maximum scan. + * + * For each, we also read type (data type) and unit (mm/pixel). + * + * Note: that we are making the bald assumption that all geometry items + * are the same type and unit. I don't believe that there is anything in the + * standard that says it has to be. It probably usually is, but there is no + * reason that we should do so. + * We will assume that the type and unit of the final option is the one to use. [RL] + * + */ surface_changed = 0; unit = SANE_UNIT_PIXEL; type = SANE_TYPE_INT; @@ -1508,6 +1554,13 @@ preview_update (Preview * p) surface_changed = 1; p->surface_type = type; } + + /* + * If any of the image preview geometry changes then remove the image and + * flush out anything that depends on it. + * This effectively puts us in the initial case with no preview. + * + */ if (surface_changed && p->image_data) { free (p->image_data); @@ -1520,81 +1573,8 @@ preview_update (Preview * p) p->image_height = 0; } - /* guess the initial preview window size: */ - - width = p->surface[GSG_BR_X] - p->surface[GSG_TL_X]; - height = p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y]; - if (p->surface_type == SANE_TYPE_INT) - { - width += 1.0; - height += 1.0; - } - else - { - width += SANE_UNFIX (1.0); - height += SANE_UNFIX (1.0); - } - - assert (width > 0.0 && height > 0.0); - - if (width >= INF || height >= INF) - p->aspect = 1.0; - else - p->aspect = width / height; - if (surface_changed) { - int width, height; - screen_size_get_dimensions (&width, &height); - max_width = 0.5 * (double)width; - max_height = 0.5 * (double)height; - } - else - { - GtkAllocation alloc; - gtk_widget_get_allocated_size (p->window, &alloc, NULL); - max_width = alloc.width; - max_height = alloc.height; - } - - if (p->surface_unit != SANE_UNIT_PIXEL) - { - width = max_width; - height = width / p->aspect; - - if (height > max_height) - { - height = max_height; - width = height * p->aspect; - } - } - else - { - if (width > max_width) - width = max_width; - - if (height > max_height) - height = max_height; - } - - /* re-adjust so we maintain aspect without exceeding max size: */ - if (width / height != p->aspect) - { - if (p->aspect > 1.0) - height = width / p->aspect; - else - width = height * p->aspect; - } - - p->preview_width = width + 0.5; - p->preview_height = height + 0.5; - if (surface_changed) - { - gtk_widget_set_size_request (GTK_WIDGET (p->window), - p->preview_width, p->preview_height); - if (gtk_widget_is_drawable(GTK_WIDGET(p->window))) - preview_area_resize (p->window, NULL, p); - if (preferences.preserve_preview) restore_preview_image (p); }