7 * This software is released under the GNU General Public License (GNU GPL).
8 * Please read the included file COPYING for more information.
9 * This software comes with no warranty of any kind, use at your own risk!
17 #include "image-load.h"
20 #include "pixbuf_util.h"
21 #include "ui_fileops.h"
26 /* size to use when breaking up image pane for rendering */
27 #define IMAGE_TILE_SIZE 512
29 /* default min and max zoom */
30 #define IMAGE_ZOOM_MIN -32.0
31 #define IMAGE_ZOOM_MAX 32.0
33 /* size of the image loader buffer (512 bytes x defined number) */
34 #define IMAGE_LOAD_BUFFER_COUNT 8
36 /* define this so that more bytes are read per idle loop on larger images (> 1MB) */
37 #define IMAGE_THROTTLE_LARGER_IMAGES 1
39 /* throttle factor to increase read bytes by (2 is double, 3 is triple, etc.) */
40 #define IMAGE_THROTTLE_FACTOR 4
42 /* the file size at which throttling take place */
43 #define IMAGE_THROTTLE_THRESHOLD 1048576
45 /* distance to drag mouse to disable image flip */
46 #define IMAGE_DRAG_SCROLL_THRESHHOLD 4
48 /* increase pan rate when holding down shift */
49 #define IMAGE_PAN_SHIFT_MULTIPLIER 6
51 /* alpha channel checkerboard background (same as gimp) */
52 #define IMAGE_ALPHA_CHECK1 0x00999999
53 #define IMAGE_ALPHA_CHECK2 0x00666666
54 #define IMAGE_ALPHA_CHECK_SIZE 16
56 #define IMAGE_AUTO_REFRESH_TIME 3000
58 /* when scaling image to below this size, use nearest pixel for scaling
59 * (below about 4, the other scale types become slow generating their conversion tables)
61 #define IMAGE_MIN_SCALE_SIZE 8
65 TILE_RENDER_NONE = 0, /* do nothing */
66 TILE_RENDER_AREA, /* render an area of the tile */
67 TILE_RENDER_ALL /* render the whole tile */
70 typedef struct _ImageTile ImageTile;
73 GdkPixmap *pixmap; /* off screen buffer */
74 GdkPixbuf *pixbuf; /* pixbuf area for zooming */
75 gint x; /* x offset into image */
76 gint y; /* y offset into image */
77 gint w; /* width that is visible (may be less if at edge of image) */
78 gint h; /* height '' */
82 /* render_todo: (explanation)
84 AREA render area of tile, usually only used when loading an image
85 note: will jump to an ALL if render_done is not ALL.
86 ALL render entire tile, if never done before w/ ALL, for expose events *only*
89 TileRenderType render_todo; /* what to do (see above) */
90 TileRenderType render_done; /* highest that has been done before on tile */
93 typedef struct _QueueData QueueData;
104 typedef struct _CacheData CacheData;
113 typedef struct _OverlayData OverlayData;
122 gint relative; /* x,y coordinates are relative, negative values start bottom right */
125 gint always; /* hide temporarily when scrolling */
128 /* needed to be declared before the source_tile stuff */
129 static void image_pixbuf_sync(ImageWindow *imd, gdouble zoom, gint blank, gint new);
130 static void image_zoom_sync(ImageWindow *imd, gdouble zoom,
131 gint force, gint blank, gint new,
132 gint center_point, gint px, gint py);
133 static void image_queue(ImageWindow *imd, gint x, gint y, gint w, gint h,
134 gint clamp, TileRenderType render, gint new_data);
136 static gint util_clip_region(gint x, gint y, gint w, gint h,
137 gint clip_x, gint clip_y, gint clip_w, gint clip_h,
138 gint *rx, gint *ry, gint *rw, gint *rh);
141 *-------------------------------------------------------------------
143 *-------------------------------------------------------------------
146 typedef struct _SourceTile SourceTile;
155 static void source_tile_free(SourceTile *st)
159 if (st->pixbuf) gdk_pixbuf_unref(st->pixbuf);
163 static void source_tile_free_all(ImageWindow *imd)
167 work = imd->source_tiles;
170 SourceTile *st = work->data;
173 source_tile_free(st);
176 g_list_free(imd->source_tiles);
177 imd->source_tiles = NULL;
180 static gint source_tile_visible(ImageWindow *imd, SourceTile *st)
184 if (!st) return FALSE;
186 x = (imd->x_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE;
187 y = (imd->y_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE;
188 w = ((imd->x_scroll + imd->vis_width) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE;
189 h = ((imd->y_scroll + imd->vis_height) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE;
191 return !((double)st->x * imd->scale < (double)x ||
192 (double)(st->x + imd->source_tile_width) * imd->scale > (double)w ||
193 (double)st->y * imd->scale < (double)y ||
194 (double)(st->y + imd->source_tile_height) * imd->scale > (double)h);
197 static SourceTile *source_tile_new(ImageWindow *imd, gint x, gint y)
199 SourceTile *st = NULL;
202 if (imd->source_tiles_cache_size < 4) imd->source_tiles_cache_size = 4;
204 if (imd->source_tile_width < 1 || imd->source_tile_height < 1)
206 printf("warning: source tile size too small %d x %d\n", imd->source_tile_width, imd->source_tile_height);
210 count = g_list_length(imd->source_tiles);
211 if (count >= imd->source_tiles_cache_size)
215 work = g_list_last(imd->source_tiles);
216 while (work && count >= imd->source_tiles_cache_size)
223 if (!source_tile_visible(imd, needle))
225 imd->source_tiles = g_list_remove(imd->source_tiles, needle);
227 if (imd->func_tile_dispose)
229 if (debug) printf("tile dispose: %d x %d @ %d x %d\n",
230 needle->x, needle->y, imd->x_scroll, imd->y_scroll);
231 imd->func_tile_dispose(imd, needle->x, needle->y,
232 imd->source_tile_width, imd->source_tile_height,
233 needle->pixbuf, imd->data_tile);
242 source_tile_free(needle);
249 printf("we still think %d x %d is visble\n", needle->x, needle->y);
255 printf("cache count %d, max is %d\n", count, imd->source_tiles_cache_size);
261 st = g_new0(SourceTile, 1);
262 st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
263 imd->source_tile_width, imd->source_tile_height);
266 st->x = (x / imd->source_tile_width) * imd->source_tile_width;
267 st->y = (y / imd->source_tile_height) * imd->source_tile_height;
270 imd->source_tiles = g_list_prepend(imd->source_tiles, st);
274 printf("tile request: %d x %d\n", st->x, st->y);
275 if (!source_tile_visible(imd, st)) printf("tile request for invisible tile!\n");
281 static void image_tile_invalidate(ImageWindow *imd, gint x, gint y, gint w, gint h)
288 x1 = (gint)floor(x / imd->tile_width) * imd->tile_width;
289 x2 = (gint)ceil((x + w) / imd->tile_width) * imd->tile_width;
291 y1 = (gint)floor(y / imd->tile_height) * imd->tile_height;
292 y2 = (gint)ceil((y + h) / imd->tile_height) * imd->tile_height;
294 work = g_list_nth(imd->tiles, y1 / imd->tile_height * imd->tile_cols + (x1 / imd->tile_width));
295 for (j = y1; j <= y2; j += imd->tile_height)
299 for (i = x1; i <= x2; i += imd->tile_width)
303 ImageTile *it = tmp->data;
305 it->render_done = TILE_RENDER_NONE;
306 it->render_todo = TILE_RENDER_ALL;
311 work = g_list_nth(work, imd->tile_cols); /* step 1 row */
315 static SourceTile *source_tile_request(ImageWindow *imd, gint x, gint y)
319 st = source_tile_new(imd, x, y);
321 if (imd->func_tile_request &&
322 imd->func_tile_request(imd, st->x, st->y,
323 imd->source_tile_width, imd->source_tile_height, st->pixbuf, imd->data_tile))
328 /* fixme: somehow invalidate the new st region */
329 image_queue(imd, st->x, st->y, imd->source_tile_width, imd->source_tile_height, FALSE, TILE_RENDER_AREA, TRUE);
331 image_tile_invalidate(imd, st->x * imd->scale, st->y * imd->scale,
332 imd->source_tile_width * imd->scale, imd->source_tile_height * imd->scale);
337 static SourceTile *source_tile_find(ImageWindow *imd, gint x, gint y)
341 work = imd->source_tiles;
344 SourceTile *st = work->data;
346 if (x >= st->x && x < st->x + imd->source_tile_width &&
347 y >= st->y && y < st->y + imd->source_tile_height)
349 if (work != imd->source_tiles)
351 imd->source_tiles = g_list_remove_link(imd->source_tiles, work);
352 imd->source_tiles = g_list_concat(work, imd->source_tiles);
363 static GList *source_tile_compute_region(ImageWindow *imd, gint x, gint y, gint w, gint h, gint request)
371 if (w > imd->image_width) w = imd->image_width;
372 if (h > imd->image_height) h = imd->image_height;
374 sx = (x / imd->source_tile_width) * imd->source_tile_width;
375 sy = (y / imd->source_tile_height) * imd->source_tile_height;
377 for (x1 = sx; x1 < x + w; x1+= imd->source_tile_width)
379 for (y1 = sy; y1 < y + h; y1 += imd->source_tile_height)
383 st = source_tile_find(imd, x1, y1);
384 if (!st && request) st = source_tile_request(imd, x1, y1);
386 if (st) list = g_list_prepend(list, st);
390 return g_list_reverse(list);
393 static void source_tile_changed(ImageWindow *imd, gint x, gint y, gint width, gint height)
397 work = imd->source_tiles;
406 if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height,
412 pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh);
413 if (imd->func_tile_request &&
414 imd->func_tile_request(imd, rx, ry, rw, rh, pixbuf, imd->data_tile))
416 image_tile_invalidate(imd, rx * imd->scale, ry * imd->scale, rw * imd->scale, rh * imd->scale);
418 g_object_unref(pixbuf);
424 static gint source_tile_render(ImageWindow *imd, ImageTile *it,
425 gint x, gint y, gint w, gint h,
426 gint new_data, gint fast)
432 if (imd->zoom == 1.0 || imd->scale == 1.0)
434 list = source_tile_compute_region(imd, it->x + x, it->y + y, w, h, TRUE);
444 if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height,
445 it->x + x, it->y + y, w, h,
450 gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
451 rx - st->x, ry - st->y, rw, rh);
453 else /* (imd->zoom == 1.0 || imd->scale == 1.0) */
455 gdk_draw_pixbuf(it->pixmap,
456 imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
458 rx - st->x, ry - st->y,
459 rx - it->x, ry - it->y,
461 (GdkRgbDither)dither_quality, rx, ry);
468 double scale_x, scale_y;
471 if (imd->image_width == 0 || imd->image_height == 0) return FALSE;
472 scale_x = (double)imd->width / imd->image_width;
473 scale_y = (double)imd->height / imd->image_height;
475 sx = (double)(it->x + x) / scale_x;
476 sy = (double)(it->y + y) / scale_y;
477 sw = (double)w / scale_x;
478 sh = (double)h / scale_y;
480 if (imd->width < IMAGE_MIN_SCALE_SIZE || imd->height < IMAGE_MIN_SCALE_SIZE) fast = TRUE;
483 /* draws red over draw region, to check for leaks (regions not filled) */
484 pixbuf_draw_rect(it->pixbuf, x, y, w, h, 255, 0, 0, 255, FALSE);
487 list = source_tile_compute_region(imd, sx, sy, sw, sh, TRUE);
493 gint stx, sty, stw, sth;
498 stx = floor((double)st->x * scale_x);
499 sty = floor((double)st->y * scale_y);
500 stw = ceil ((double)(st->x + imd->source_tile_width) * scale_x) - stx;
501 sth = ceil ((double)(st->y + imd->source_tile_height) * scale_y) - sty;
503 if (util_clip_region(stx, sty, stw, sth,
504 it->x + x, it->y + y, w, h,
509 gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
510 rx - st->x, ry - st->y, rw, rh);
517 /* may need to use unfloored stx,sty values here */
518 offset_x = ((double)stx < (double)it->x) ?
519 (double)stx - (double)it->x : 0.0;
520 offset_y = ((double)sty < (double)it->y) ?
521 (double)sty - (double)it->y : 0.0;
523 gdk_pixbuf_scale(st->pixbuf, it->pixbuf, rx - it->x, ry - it->y, rw, rh,
524 (double) 0.0 + rx - it->x + offset_x,
525 (double) 0.0 + ry - it->y + offset_y,
527 (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality);
539 static void image_source_tile_unset(ImageWindow *imd)
541 source_tile_free_all(imd);
543 imd->source_tiles_enabled = FALSE;
546 void image_set_image_as_tiles(ImageWindow *imd, gint width, gint height,
547 gint tile_width, gint tile_height, gint cache_size,
548 ImageTileRequestFunc func_tile_request,
549 ImageTileDisposeFunc func_tile_dispose,
553 /* FIXME: unset any current image */
554 image_source_tile_unset(imd);
556 if (tile_width < 32 || tile_height < 32)
558 printf("warning: tile size too small %d x %d (min 32x32)\n", tile_width, tile_height);
561 if (width < 32 || height < 32)
563 printf("warning: tile canvas too small %d x %d (min 32x32)\n", width, height);
566 if (!func_tile_request)
568 printf("warning: tile request function is null\n");
571 printf("Setting source tiles to size %d x %d, grid is %d x %d\n", tile_width, tile_height, width, height);
573 if (cache_size < 4) cache_size = 4;
575 imd->source_tiles_enabled = TRUE;
576 imd->source_tiles_cache_size = cache_size;
577 imd->source_tile_width = tile_width;
578 imd->source_tile_height = tile_height;
580 imd->image_width = width;
581 imd->image_height = height;
583 imd->func_tile_request = func_tile_request;
584 imd->func_tile_dispose = func_tile_dispose;
585 imd->data_tile = data;
587 image_zoom_sync(imd, zoom, TRUE, FALSE, TRUE, FALSE, 0, 0);
591 static void image_queue_clear(ImageWindow *imd);
593 static void image_update_title(ImageWindow *imd);
594 static void image_update_util(ImageWindow *imd);
595 static void image_complete_util(ImageWindow *imd, gint preload);
597 static void image_button_do(ImageWindow *imd, GdkEventButton *bevent);
599 static void image_overlay_draw(ImageWindow *imd, gint x, gint y, gint w, gint h);
600 static void image_overlay_queue_all(ImageWindow *imd);
602 static void image_scroller_timer_set(ImageWindow *imd, gint start);
605 static gint util_clip_region(gint x, gint y, gint w, gint h,
606 gint clip_x, gint clip_y, gint clip_w, gint clip_h,
607 gint *rx, gint *ry, gint *rw, gint *rh)
609 if (clip_x + clip_w <= x ||
611 clip_y + clip_h <= y ||
617 *rx = MAX(x, clip_x);
618 *rw = MIN((x + w), (clip_x + clip_w)) - *rx;
620 *ry = MAX(y, clip_y);
621 *rh = MIN((y + h), (clip_y + clip_h)) - *ry;
628 *-------------------------------------------------------------------
630 *-------------------------------------------------------------------
633 static gint pixmap_calc_size(GdkPixmap *pixmap)
637 d = gdk_drawable_get_depth(pixmap);
638 gdk_drawable_get_size(pixmap, &w, &h);
639 return w * h * (d / 8);
642 static void image_tile_cache_remove(ImageWindow *imd, ImageTile *it)
646 work = imd->tile_cache;
649 CacheData *cd = work->data;
654 imd->tile_cache = g_list_remove(imd->tile_cache, cd);
655 imd->tile_cache_size -= cd->size;
661 static void image_tile_cache_free(ImageWindow *imd, CacheData *cd)
663 imd->tile_cache = g_list_remove(imd->tile_cache, cd);
666 g_object_unref(cd->it->pixmap);
667 cd->it->pixmap = NULL;
668 cd->it->render_done = TILE_RENDER_NONE;
672 gdk_pixbuf_unref(cd->it->pixbuf);
673 cd->it->pixbuf = NULL;
675 imd->tile_cache_size -= cd->size;
679 static void image_tile_cache_free_space(ImageWindow *imd, gint space, ImageTile *it)
684 work = g_list_last(imd->tile_cache);
686 if (imd->source_tiles_enabled && imd->scale < 1.0)
690 tiles = (imd->vis_width / IMAGE_TILE_SIZE + 1) * (imd->vis_width / IMAGE_TILE_SIZE + 1);
691 tile_max = MAX(tiles * IMAGE_TILE_SIZE * IMAGE_TILE_SIZE * 3,
692 (gint)((double)tile_cache_max * 1048576.0 * imd->scale));
696 tile_max = tile_cache_max * 1048576;
699 while (work && imd->tile_cache_size > 0 && imd->tile_cache_size + space > tile_max)
701 CacheData *cd = work->data;
703 if (cd->it != it) image_tile_cache_free(imd, cd);
707 static void image_tile_cache_add(ImageWindow *imd, ImageTile *it,
708 GdkPixmap *pixmap, GdkPixbuf *pixbuf, guint size)
712 cd = g_new(CacheData, 1);
718 imd->tile_cache = g_list_prepend(imd->tile_cache, cd);
720 imd->tile_cache_size += cd->size;
723 static void image_tile_prepare(ImageWindow *imd, ImageTile *it)
730 pixmap = gdk_pixmap_new(imd->image->window, imd->tile_width, imd->tile_height, -1);
732 size = pixmap_calc_size(pixmap);
733 image_tile_cache_free_space(imd, size, it);
736 image_tile_cache_add(imd, it, pixmap, NULL, size);
739 if ((imd->zoom != 1.0 || imd->source_tiles_enabled || (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf)) ) &&
747 pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(imd->pixbuf),
748 gdk_pixbuf_get_has_alpha(imd->pixbuf),
749 gdk_pixbuf_get_bits_per_sample(imd->pixbuf),
750 imd->tile_width, imd->tile_height);
754 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, imd->tile_width, imd->tile_height);
757 size = gdk_pixbuf_get_rowstride(pixbuf) * imd->tile_height;
758 image_tile_cache_free_space(imd, size, it);
761 image_tile_cache_add(imd, it, NULL, pixbuf, size);
766 *-------------------------------------------------------------------
768 *-------------------------------------------------------------------
771 static ImageTile *image_tile_new(gint w, gint h)
775 it = g_new0(ImageTile, 1);
781 it->render_todo = TILE_RENDER_NONE;
782 it->render_done = TILE_RENDER_NONE;
787 static void image_tile_free(ImageTile *it)
791 if (it->pixbuf) gdk_pixbuf_unref(it->pixbuf);
792 if (it->pixmap) g_object_unref(it->pixmap);
797 static void image_tile_sync_count(ImageWindow *imd, gint n)
801 l = g_list_length(imd->tiles);
809 imd->tiles = g_list_prepend(imd->tiles, image_tile_new(imd->tile_width, imd->tile_height));
815 /* This should remove from the tail of the GList, but with large images there are many tiles,
816 * making this significantly faster for those cases.
818 while (l > n && imd->tiles)
820 ImageTile *it = imd->tiles->data;
821 imd->tiles = g_list_remove(imd->tiles, it);
822 image_tile_cache_remove(imd, it);
829 static void image_tile_sync(ImageWindow *imd, gint width, gint height, gint blank)
835 imd->tile_cols = (width + imd->tile_width - 1) / imd->tile_width;
837 rows = (height + imd->tile_height - 1) / imd->tile_height;
839 image_tile_sync_count(imd, imd->tile_cols * rows);
845 ImageTile *it = work->data;
850 if (x + imd->tile_width > width)
856 it->w = imd->tile_width;
858 if (y + imd->tile_height > height)
864 it->h = imd->tile_height;
868 it->render_todo = TILE_RENDER_NONE;
869 it->render_done = TILE_RENDER_NONE;
871 x += imd->tile_width;
875 y += imd->tile_height;
879 /* all it's are now useless in queue */
880 image_queue_clear(imd);
883 static void image_tile_render(ImageWindow *imd, ImageTile *it,
884 gint x, gint y, gint w, gint h,
885 gint new_data, gint fast)
890 if (it->render_todo == TILE_RENDER_NONE && it->pixmap && !new_data) return;
892 if (it->render_done != TILE_RENDER_ALL)
898 if (!fast) it->render_done = TILE_RENDER_ALL;
900 else if (it->render_todo != TILE_RENDER_AREA)
902 if (!fast) it->render_todo = TILE_RENDER_NONE;
906 if (!fast) it->render_todo = TILE_RENDER_NONE;
908 if (new_data) it->blank = FALSE;
910 image_tile_prepare(imd, it);
911 has_alpha = (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf));
913 /* FIXME checker colors for alpha should be configurable,
914 * also should be drawn for blank = TRUE
919 /* no data, do fast rect fill */
920 gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
923 else if (imd->source_tiles_enabled)
925 draw = source_tile_render(imd, it, x, y, w, h, new_data, fast);
927 else if (imd->zoom == 1.0 || imd->scale == 1.0)
931 gdk_pixbuf_composite_color(imd->pixbuf, it->pixbuf, x, y, w, h,
932 (double) 0.0 - it->x,
933 (double) 0.0 - it->y,
934 1.0, 1.0, GDK_INTERP_NEAREST,
935 255, it->x + x, it->y + y,
936 IMAGE_ALPHA_CHECK_SIZE, IMAGE_ALPHA_CHECK1, IMAGE_ALPHA_CHECK2);
942 gdk_draw_pixbuf(it->pixmap,
943 imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
945 it->x + x, it->y + y,
948 (GdkRgbDither)dither_quality, it->x + x, it->y + y);
953 double scale_x, scale_y;
955 if (imd->image_width == 0 || imd->image_height == 0) return;
956 scale_x = (double)imd->width / imd->image_width;
957 scale_y = (double)imd->height / imd->image_height;
959 /* HACK: The pixbuf scalers get kinda buggy(crash) with extremely
960 * small sizes for anything but GDK_INTERP_NEAREST
962 if (imd->width < IMAGE_MIN_SCALE_SIZE || imd->height < IMAGE_MIN_SCALE_SIZE) fast = TRUE;
966 gdk_pixbuf_scale(imd->pixbuf, it->pixbuf, x, y, w, h,
967 (double) 0.0 - it->x,
968 (double) 0.0 - it->y,
970 (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality);
974 gdk_pixbuf_composite_color(imd->pixbuf, it->pixbuf, x, y, w, h,
975 (double) 0.0 - it->x,
976 (double) 0.0 - it->y,
978 (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality,
979 255, it->x + x, it->y + y,
980 IMAGE_ALPHA_CHECK_SIZE, IMAGE_ALPHA_CHECK1, IMAGE_ALPHA_CHECK2);
985 if (draw && it->pixbuf && !it->blank)
987 gdk_draw_pixbuf(it->pixmap,
988 imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
993 (GdkRgbDither)dither_quality, it->x + x, it->y + y);
997 static void image_tile_expose(ImageWindow *imd, ImageTile *it,
998 gint x, gint y, gint w, gint h,
999 gint new_data, gint fast)
1001 image_tile_render(imd, it, x, y, w, h, new_data, fast);
1003 gdk_draw_drawable(imd->image->window, imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
1005 imd->x_offset + (it->x - imd->x_scroll) + x, imd->y_offset + (it->y - imd->y_scroll) + y, w, h);
1007 if (imd->overlay_list)
1009 image_overlay_draw(imd, imd->x_offset + (it->x - imd->x_scroll) + x,
1010 imd->y_offset + (it->y - imd->y_scroll) + y,
1015 static gint image_tile_is_visible(ImageWindow *imd, ImageTile *it)
1017 return (it->x + it->w >= imd->x_scroll && it->x <= imd->x_scroll + imd->window_width - imd->x_offset * 2 &&
1018 it->y + it->h >= imd->y_scroll && it->y <= imd->y_scroll + imd->window_height - imd->y_offset * 2);
1022 *-------------------------------------------------------------------
1024 *-------------------------------------------------------------------
1028 static gint image_queue_draw_idle_cb(gpointer data)
1030 ImageWindow *imd = data;
1034 if ((!imd->pixbuf && !imd->source_tiles_enabled) || (!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1)
1036 if (!imd->completed) image_complete_util(imd, FALSE);
1038 imd->draw_idle_id = -1;
1042 if (imd->draw_queue)
1044 qd = imd->draw_queue->data;
1045 fast = (two_pass_zoom && (GdkInterpType)zoom_quality != GDK_INTERP_NEAREST && imd->scale != 1.0);
1051 /* still loading, wait till done (also drops the higher priority) */
1053 imd->draw_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
1054 image_queue_draw_idle_cb, imd, NULL);
1055 imd->draw_idle_high = FALSE;
1058 qd = imd->draw_queue_2pass->data;
1062 if (GTK_WIDGET_REALIZED(imd->image))
1064 if (image_tile_is_visible(imd, qd->it))
1066 image_tile_expose(imd, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
1068 else if (qd->new_data)
1070 /* if new pixel data, and we already have a pixmap, update the tile */
1071 qd->it->blank = FALSE;
1072 if (qd->it->pixmap && qd->it->render_done == TILE_RENDER_ALL)
1074 image_tile_render(imd, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
1079 if (imd->draw_queue)
1081 imd->draw_queue = g_list_remove(imd->draw_queue, qd);
1084 imd->draw_queue_2pass = g_list_append(imd->draw_queue_2pass, qd);
1093 imd->draw_queue_2pass = g_list_remove(imd->draw_queue_2pass, qd);
1097 if (!imd->draw_queue && !imd->draw_queue_2pass)
1099 if (!imd->completed) image_complete_util(imd, FALSE);
1101 imd->draw_idle_id = -1;
1108 static QueueData *image_queue_combine(ImageWindow *imd, QueueData *qd)
1110 QueueData *found = NULL;
1113 work = imd->draw_queue;
1114 while (work && !found)
1119 if (found->it != qd->it) found = NULL;
1124 if (found->x + found->w < qd->x + qd->w) found->w += (qd->x + qd->w) - (found->x + found->w);
1125 if (found->x > qd->x)
1127 found->w += found->x - qd->x;
1131 if (found->y + found->h < qd->y + qd->h) found->h += (qd->y + qd->h) - (found->y + found->h);
1132 if (found->y > qd->y)
1134 found->h += found->y - qd->y;
1137 found->new_data |= qd->new_data;
1143 static gint image_clamp_to_visible(ImageWindow *imd, gint *x, gint *y, gint *w, gint *h)
1150 vw = imd->vis_width;
1151 vh = imd->vis_height;
1156 if (*x + *w < vx || *x > vx + vw || *y + *h < vy || *y > vy + vh) return FALSE;
1159 nx = CLAMP(*x, vx, vx + vw);
1160 nw = CLAMP(*w - (nx - *x), 1, vw);
1162 ny = CLAMP(*y, vy, vy + vh);
1163 nh = CLAMP(*h - (ny - *y), 1, vh);
1173 static gint image_queue_to_tiles(ImageWindow *imd, gint x, gint y, gint w, gint h,
1174 gint clamp, TileRenderType render, gint new_data)
1181 if (clamp && !image_clamp_to_visible(imd, &x, &y, &w, &h)) return FALSE;
1183 x1 = (gint)floor(x / imd->tile_width) * imd->tile_width;
1184 x2 = (gint)ceil((x + w) / imd->tile_width) * imd->tile_width;
1186 y1 = (gint)floor(y / imd->tile_height) * imd->tile_height;
1187 y2 = (gint)ceil((y + h) / imd->tile_height) * imd->tile_height;
1189 work = g_list_nth(imd->tiles, y1 / imd->tile_height * imd->tile_cols + (x1 / imd->tile_width));
1190 for (j = y1; j <= y2; j += imd->tile_height)
1194 for (i = x1; i <= x2; i += imd->tile_width)
1198 ImageTile *it = tmp->data;
1201 if ((render == TILE_RENDER_ALL && it->render_done != TILE_RENDER_ALL) ||
1202 (render == TILE_RENDER_AREA && it->render_todo != TILE_RENDER_ALL))
1204 it->render_todo = render;
1207 qd = g_new(QueueData, 1);
1209 qd->new_data = new_data;
1219 qd->w = x + w - i - qd->x;
1220 if (qd->x + qd->w > imd->tile_width) qd->w = imd->tile_width - qd->x;
1231 qd->h = y + h - j - qd->y;
1232 if (qd->y + qd->h > imd->tile_height) qd->h = imd->tile_height - qd->y;
1234 if (qd->w < 1 || qd->h < 1 || /* <--- sanity checks, rare cases cause this */
1235 image_queue_combine(imd, qd))
1241 imd->draw_queue = g_list_append(imd->draw_queue, qd);
1247 work = g_list_nth(work, imd->tile_cols); /* step 1 row */
1253 static void image_queue(ImageWindow *imd, gint x, gint y, gint w, gint h,
1254 gint clamp, TileRenderType render, gint new_data)
1258 nx = CLAMP(x, 0, imd->width - 1);
1259 ny = CLAMP(y, 0, imd->height - 1);
1262 w = CLAMP(w, 0, imd->width - nx);
1263 h = CLAMP(h, 0, imd->height - ny);
1264 if (w < 1 || h < 1) return;
1266 if (image_queue_to_tiles(imd, nx, ny, w, h, clamp, render, new_data) &&
1267 ((!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1 || !imd->draw_idle_high))
1269 if (imd->draw_idle_id != -1) g_source_remove(imd->draw_idle_id);
1270 imd->draw_idle_id = g_idle_add_full(GDK_PRIORITY_REDRAW,
1271 image_queue_draw_idle_cb, imd, NULL);
1272 imd->draw_idle_high = TRUE;
1276 static void image_queue_list_free(GList *list)
1293 static void image_queue_clear(ImageWindow *imd)
1295 image_queue_list_free(imd->draw_queue);
1296 imd->draw_queue = NULL;
1298 image_queue_list_free(imd->draw_queue_2pass);
1299 imd->draw_queue_2pass = NULL;
1301 if (imd->draw_idle_id != -1) g_source_remove(imd->draw_idle_id);
1302 imd->draw_idle_id = -1;
1306 *-------------------------------------------------------------------
1308 *-------------------------------------------------------------------
1311 static gint image_top_window_sizable(ImageWindow *imd)
1313 if (!imd->top_window) return FALSE;
1314 if (!fit_window) return FALSE;
1315 if (!imd->top_window_sync) return FALSE;
1316 if (!imd->image->window) return FALSE;
1317 if (window_maximized(imd->top_window)) return FALSE;
1322 static gint image_size_top_window(ImageWindow *imd, gint w, gint h)
1326 if (!image_top_window_sizable(imd)) return FALSE;
1328 if (limit_window_size)
1330 gint sw = gdk_screen_width() * max_window_size / 100;
1331 gint sh = gdk_screen_height() * max_window_size / 100;
1337 w += (imd->top_window->allocation.width - imd->image->allocation.width);
1338 h += (imd->top_window->allocation.height - imd->image->allocation.height);
1340 gdk_drawable_get_size(imd->top_window->window, &ww, &wh);
1341 if (w == ww && h == wh) return FALSE;
1343 gdk_window_resize(imd->top_window->window, w, h);
1348 static void image_redraw(ImageWindow *imd, gint new_data)
1350 image_queue_clear(imd);
1351 image_queue(imd, 0, 0, imd->width, imd->height, TRUE, TILE_RENDER_ALL, new_data);
1354 static void image_border_draw(ImageWindow *imd, gint x, gint y, gint w, gint h)
1356 gint rx, ry, rw, rh;
1358 if (!imd->image->window) return;
1360 if (!imd->pixbuf && !imd->source_tiles_enabled)
1362 if (util_clip_region(x, y, w, h,
1364 imd->window_width, imd->window_height,
1365 &rx, &ry, &rw, &rh))
1367 gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1368 image_overlay_draw(imd, rx, ry, rw, rh);
1373 if (imd->vis_width < imd->window_width)
1375 if (imd->x_offset > 0 &&
1376 util_clip_region(x, y, w, h,
1378 imd->x_offset, imd->window_height,
1379 &rx, &ry, &rw, &rh))
1381 gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1382 image_overlay_draw(imd, rx, ry, rw, rh);
1384 if (imd->window_width - imd->vis_width - imd->x_offset > 0 &&
1385 util_clip_region(x, y, w, h,
1386 imd->x_offset + imd->vis_width, 0,
1387 imd->window_width - imd->vis_width - imd->x_offset, imd->window_height,
1388 &rx, &ry, &rw, &rh))
1390 gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1391 image_overlay_draw(imd, rx, ry, rw, rh);
1394 if (imd->vis_height < imd->window_height)
1396 if (imd->y_offset > 0 &&
1397 util_clip_region(x, y, w, h,
1399 imd->vis_width, imd->y_offset,
1400 &rx, &ry, &rw, &rh))
1402 gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1403 image_overlay_draw(imd, rx, ry, rw, rh);
1405 if (imd->window_height - imd->vis_height - imd->y_offset > 0 &&
1406 util_clip_region(x, y, w, h,
1407 imd->x_offset, imd->y_offset + imd->vis_height,
1408 imd->vis_width, imd->window_height - imd->vis_height - imd->y_offset,
1409 &rx, &ry, &rw, &rh))
1411 gdk_window_clear_area(imd->image->window, rx, ry, rw, rh);
1412 image_overlay_draw(imd, rx, ry, rw, rh);
1417 static void image_border_clear(ImageWindow *imd)
1419 image_border_draw(imd, 0, 0, imd->window_width, imd->window_height);
1422 static void image_scroll_notify(ImageWindow *imd)
1424 if (imd->func_scroll_notify && imd->scale)
1426 imd->func_scroll_notify(imd,
1427 (gint)((gdouble)imd->x_scroll / imd->scale),
1428 (gint)((gdouble)imd->y_scroll / imd->scale),
1429 (gint)((gdouble)imd->image_width - imd->vis_width / imd->scale),
1430 (gint)((gdouble)imd->image_height - imd->vis_height / imd->scale),
1431 imd->data_scroll_notify);
1435 static gint image_scroll_clamp(ImageWindow *imd)
1440 if (imd->zoom == 0.0)
1445 image_scroll_notify(imd);
1449 old_xs = imd->x_scroll;
1450 old_ys = imd->y_scroll;
1452 if (imd->x_offset > 0)
1458 imd->x_scroll = CLAMP(imd->x_scroll, 0, imd->width - imd->vis_width);
1461 if (imd->y_offset > 0)
1467 imd->y_scroll = CLAMP(imd->y_scroll, 0, imd->height - imd->vis_height);
1470 image_scroll_notify(imd);
1472 return (old_xs != imd->x_scroll || old_ys != imd->y_scroll);
1475 static gint image_zoom_clamp(ImageWindow *imd, gdouble zoom, gint force, gint new)
1480 zoom = CLAMP(zoom, imd->zoom_min, imd->zoom_max);
1482 if (imd->zoom == zoom && !force) return FALSE;
1484 w = imd->image_width;
1485 h = imd->image_height;
1487 if (zoom == 0.0 && !imd->pixbuf)
1491 else if (zoom == 0.0)
1497 sizeable = (new && image_top_window_sizable(imd));
1501 max_w = gdk_screen_width();
1502 max_h = gdk_screen_height();
1504 if (limit_window_size)
1506 max_w = max_w * max_window_size / 100;
1507 max_h = max_h * max_window_size / 100;
1512 max_w = imd->window_width;
1513 max_h = imd->window_height;
1516 if ((zoom_to_fit_expands && !sizeable) || w > max_w || h > max_h)
1518 if ((gdouble)max_w / w > (gdouble)max_h / h)
1520 scale = (gdouble)max_h / h;
1522 w = w * scale + 0.5;
1523 if (w > max_w) w = max_w;
1527 scale = (gdouble)max_w / w;
1529 h = h * scale + 0.5;
1530 if (h > max_h) h = max_h;
1540 else if (zoom > 0.0) /* zoom orig, in */
1548 scale = 1.0 / (0.0 - zoom);
1561 static gint image_size_clamp(ImageWindow *imd)
1563 gint old_vw, old_vh;
1565 old_vw = imd->vis_width;
1566 old_vh = imd->vis_height;
1568 if (imd->width < imd->window_width)
1570 imd->vis_width = imd->width;
1571 imd->x_offset = (imd->window_width - imd->width) / 2;
1575 imd->vis_width = imd->window_width;
1579 if (imd->height < imd->window_height)
1581 imd->vis_height = imd->height;
1582 imd->y_offset = (imd->window_height - imd->height) / 2;
1586 imd->vis_height = imd->window_height;
1590 return (old_vw != imd->vis_width || old_vh != imd->vis_height);
1593 static void image_size_sync(ImageWindow *imd, gint new_width, gint new_height)
1595 if (imd->window_width == new_width && imd->window_height == new_height) return;
1597 imd->window_width = new_width;
1598 imd->window_height = new_height;
1600 if (imd->zoom == 0.0) image_zoom_clamp(imd, 0.0, TRUE, FALSE);
1602 image_size_clamp(imd);
1603 image_scroll_clamp(imd);
1606 gtk_widget_set_size_request(imd->image, imd->window_width, imd->window_height);
1609 /* ensure scroller remains visible */
1610 if (imd->scroller_overlay != -1)
1612 gint update = FALSE;
1614 if (imd->scroller_x > new_width)
1616 imd->scroller_x = new_width;
1617 imd->scroller_xpos = new_width;
1620 if (imd->scroller_y > new_height)
1622 imd->scroller_y = new_height;
1623 imd->scroller_ypos = new_height;
1631 if (image_overlay_get(imd, imd->scroller_overlay, &pixbuf, NULL, NULL))
1635 w = gdk_pixbuf_get_width(pixbuf);
1636 h = gdk_pixbuf_get_height(pixbuf);
1637 image_overlay_set(imd, imd->scroller_overlay, pixbuf,
1638 imd->scroller_x - w / 2, imd->scroller_y - h / 2);
1643 /* clear any borders */
1644 image_border_clear(imd);
1646 image_tile_sync(imd, imd->width, imd->height, FALSE);
1648 /* no longer needed? (expose event should be doing this for us) */
1649 image_redraw(imd, FALSE);
1652 if (imd->title_show_zoom) image_update_title(imd);
1653 image_update_util(imd);
1657 *-------------------------------------------------------------------
1659 *-------------------------------------------------------------------
1662 static void image_update_title(ImageWindow *imd)
1664 gchar *title = NULL;
1666 gchar *collection = NULL;
1668 if (!imd->top_window) return;
1670 if (imd->collection && collection_to_number(imd->collection) >= 0)
1673 name = imd->collection->name;
1674 if (!name) name = _("Untitled");
1675 collection = g_strdup_printf(" (Collection %s)", name);
1678 if (imd->title_show_zoom)
1680 gchar *buf = image_zoom_get_as_text(imd);
1681 zoom = g_strconcat(" [", buf, "]", NULL);
1685 title = g_strdup_printf("%s%s%s%s%s%s",
1686 imd->title ? imd->title : "",
1687 imd->image_name ? imd->image_name : "",
1689 collection ? collection : "",
1690 imd->image_name ? " - " : "",
1691 imd->title_right ? imd->title_right : "");
1693 gtk_window_set_title(GTK_WINDOW(imd->top_window), title);
1700 static void image_update_util(ImageWindow *imd)
1702 if (imd->func_update) imd->func_update(imd, imd->data_update);
1705 static void image_complete_util(ImageWindow *imd, gint preload)
1707 if (imd->il && imd->pixbuf != image_loader_get_pixbuf(imd->il)) return;
1709 if (debug) printf("image load completed \"%s\" (%s)\n",
1710 (preload) ? imd->read_ahead_path : imd->image_path,
1711 (preload) ? "preload" : "current");
1713 if (!preload) imd->completed = TRUE;
1714 if (imd->func_complete) imd->func_complete(imd, preload, imd->data_complete);
1717 static void image_new_util(ImageWindow *imd)
1719 if (imd->func_new) imd->func_new(imd, imd->data_new);
1722 static void image_scroll_real(ImageWindow *imd, gint x, gint y)
1728 if (!imd->pixbuf && !imd->source_tiles_enabled) return;
1730 old_x = imd->x_scroll;
1731 old_y = imd->y_scroll;
1736 image_scroll_clamp(imd);
1737 if (imd->x_scroll == old_x && imd->y_scroll == old_y) return;
1739 if (imd->overlay_list)
1743 new_x = imd->x_scroll;
1744 new_y = imd->y_scroll;
1745 imd->x_scroll = old_x;
1746 imd->y_scroll = old_y;
1747 image_overlay_queue_all(imd);
1748 imd->x_scroll = new_x;
1749 imd->y_scroll = new_y;
1752 x_off = imd->x_scroll - old_x;
1753 y_off = imd->y_scroll - old_y;
1755 w = imd->vis_width - abs(x_off);
1756 h = imd->vis_height - abs(y_off);
1760 /* scrolled completely to new material */
1761 image_queue(imd, 0, 0, imd->width, imd->height, TRUE, TILE_RENDER_ALL, FALSE);
1792 gc = gdk_gc_new(imd->image->window);
1793 gdk_gc_set_exposures(gc, TRUE);
1794 gdk_draw_drawable(imd->image->window, gc,
1796 x2 + imd->x_offset, y2 + imd->y_offset,
1797 x1 + imd->x_offset, y1 + imd->y_offset, w, h);
1800 if (imd->overlay_list)
1802 image_overlay_queue_all(imd);
1805 w = imd->vis_width - w;
1806 h = imd->vis_height - h;
1811 x_off > 0 ? imd->x_scroll + (imd->vis_width - w) : imd->x_scroll, imd->y_scroll,
1812 w, imd->vis_height, TRUE, TILE_RENDER_ALL, FALSE);
1816 /* FIXME, to optimize this, remove overlap */
1818 imd->x_scroll, y_off > 0 ? imd->y_scroll + (imd->vis_height - h) : imd->y_scroll,
1819 imd->vis_width, h, TRUE, TILE_RENDER_ALL, FALSE);
1824 static void widget_set_cursor(GtkWidget *widget, gint icon)
1828 if (!widget->window) return;
1836 cursor = gdk_cursor_new (icon);
1839 gdk_window_set_cursor(widget->window, cursor);
1841 if (cursor) gdk_cursor_unref(cursor);
1845 *-------------------------------------------------------------------
1846 * image pixbuf handling
1847 *-------------------------------------------------------------------
1850 static void image_zoom_sync(ImageWindow *imd, gdouble zoom,
1851 gint force, gint blank, gint new,
1852 gint center_point, gint px, gint py)
1855 gint old_cx, old_cy;
1859 old_scale = imd->scale;
1862 px = CLAMP(px, 0, imd->width);
1863 py = CLAMP(py, 0, imd->height);
1864 old_cx = imd->x_scroll + (px - imd->x_offset);
1865 old_cy = imd->y_scroll + (py - imd->y_offset);
1870 old_cx = imd->x_scroll + imd->vis_width / 2;
1871 old_cy = imd->y_scroll + imd->vis_height / 2;
1874 if (!image_zoom_clamp(imd, zoom, force, new)) return;
1876 clamped = image_size_clamp(imd);
1877 sized = image_size_top_window(imd, imd->width, imd->height);
1881 /* force means new image, so update scroll offset per options */
1882 switch (scroll_reset_method)
1884 case SCROLL_RESET_NOCHANGE:
1885 /* maintain old scroll position, do nothing */
1887 case SCROLL_RESET_CENTER:
1888 /* center new image */
1889 imd->x_scroll = ((double)imd->image_width / 2.0 * imd->scale) - imd->vis_width / 2;
1890 imd->y_scroll = ((double)imd->image_height / 2.0 * imd->scale) - imd->vis_height / 2;
1892 case SCROLL_RESET_TOPLEFT:
1894 /* reset to upper left */
1902 /* user zoom does not force, so keep visible center point */
1905 imd->x_scroll = old_cx / old_scale * imd->scale - (px - imd->x_offset);
1906 imd->y_scroll = old_cy / old_scale * imd->scale - (py - imd->y_offset);
1910 imd->x_scroll = old_cx / old_scale * imd->scale - (imd->vis_width / 2);
1911 imd->y_scroll = old_cy / old_scale * imd->scale - (imd->vis_height / 2);
1914 image_scroll_clamp(imd);
1916 image_tile_sync(imd, imd->width, imd->height, blank);
1918 /* If the window was not sized, redraw the image - we know there will be no size/expose signal.
1919 * But even if a size is claimed, there is no guarantee that the window manager will allow it,
1920 * so redraw the window anyway :/
1922 if (sized || clamped) image_border_clear(imd);
1923 image_redraw(imd, FALSE);
1925 if (imd->title_show_zoom) image_update_title(imd);
1926 image_update_util(imd);
1929 static void image_pixbuf_sync(ImageWindow *imd, gdouble zoom, gint blank, gint new)
1933 /* no pixbuf so just clear the window */
1934 imd->image_width = 0;
1935 imd->image_height = 0;
1938 if (imd->image->window)
1940 gdk_window_clear(imd->image->window);
1941 image_overlay_draw(imd, 0, 0, imd->window_width, imd->window_height);
1944 image_update_util(imd);
1949 imd->image_width = gdk_pixbuf_get_width(imd->pixbuf);
1950 imd->image_height = gdk_pixbuf_get_height(imd->pixbuf);
1953 /* reset scrolling */
1958 image_zoom_sync(imd, zoom, TRUE, blank, new, FALSE, 0, 0);
1961 static void image_set_pixbuf(ImageWindow *imd, GdkPixbuf *pixbuf, gdouble zoom, gint new)
1963 if (pixbuf) g_object_ref(pixbuf);
1964 if (imd->pixbuf) g_object_unref(imd->pixbuf);
1965 imd->pixbuf = pixbuf;
1967 image_pixbuf_sync(imd, zoom, FALSE, new);
1970 static void image_alter_real(ImageWindow *imd, AlterType type, gint clamp)
1972 GdkPixbuf *new = NULL;
1976 imd->delay_alter_type = ALTER_NONE;
1978 if (!imd->pixbuf) return;
1980 x = imd->x_scroll + (imd->vis_width / 2);
1981 y = imd->y_scroll + (imd->vis_height / 2);
1985 case ALTER_ROTATE_90:
1986 new = pixbuf_copy_rotate_90(imd->pixbuf, FALSE);
1988 x = imd->height - y;
1991 case ALTER_ROTATE_90_CC:
1992 new = pixbuf_copy_rotate_90(imd->pixbuf, TRUE);
1997 case ALTER_ROTATE_180:
1998 new = pixbuf_copy_mirror(imd->pixbuf, TRUE, TRUE);
2000 y = imd->height - y;
2003 new = pixbuf_copy_mirror(imd->pixbuf, TRUE, FALSE);
2007 new = pixbuf_copy_mirror(imd->pixbuf, FALSE, TRUE);
2008 y = imd->height - y;
2020 image_set_pixbuf(imd, new, imd->zoom, TRUE);
2021 g_object_unref(new);
2023 if (imd->zoom != 0.0)
2025 image_scroll(imd, x - (imd->vis_width / 2), y - (imd->vis_height / 2));
2030 g_object_unref(imd->pixbuf);
2035 static void image_post_process(ImageWindow *imd, gint clamp)
2037 if (exif_rotate_enable && imd->pixbuf)
2042 ed = exif_read(imd->image_path);
2043 if (ed && exif_get_integer(ed, "Orientation", &orientation))
2045 /* see http://jpegclub.org/exif_orientation.html
2048 888888 888888 88 88 8888888888 88 88 8888888888
2049 88 88 88 88 88 88 88 88 88 88 88 88
2050 8888 8888 8888 8888 88 8888888888 8888888888 88
2055 switch (orientation)
2057 case EXIF_ORIENTATION_TOP_LEFT:
2058 /* normal -- nothing to do */
2060 case EXIF_ORIENTATION_TOP_RIGHT:
2062 imd->delay_alter_type = ALTER_MIRROR;
2064 case EXIF_ORIENTATION_BOTTOM_RIGHT:
2066 imd->delay_alter_type = ALTER_ROTATE_180;
2068 case EXIF_ORIENTATION_BOTTOM_LEFT:
2070 imd->delay_alter_type = ALTER_FLIP;
2072 case EXIF_ORIENTATION_LEFT_TOP:
2073 /* not implemented -- too wacky to fix in one step */
2075 case EXIF_ORIENTATION_RIGHT_TOP:
2076 /* rotated -90 (270) */
2077 imd->delay_alter_type = ALTER_ROTATE_90;
2079 case EXIF_ORIENTATION_RIGHT_BOTTOM:
2080 /* not implemented -- too wacky to fix in one step */
2082 case EXIF_ORIENTATION_LEFT_BOTTOM:
2084 imd->delay_alter_type = ALTER_ROTATE_90_CC;
2087 /* The other values are out of range */
2094 if (imd->delay_alter_type != ALTER_NONE)
2096 image_alter_real(imd, imd->delay_alter_type, clamp);
2101 *-------------------------------------------------------------------
2102 * read ahead (prebuffer)
2103 *-------------------------------------------------------------------
2106 static void image_read_ahead_cancel(ImageWindow *imd)
2108 if (debug) printf("read ahead cancelled for :%s\n", imd->read_ahead_path);
2110 image_loader_free(imd->read_ahead_il);
2111 imd->read_ahead_il = NULL;
2113 if (imd->read_ahead_pixbuf) g_object_unref(imd->read_ahead_pixbuf);
2114 imd->read_ahead_pixbuf = NULL;
2116 g_free(imd->read_ahead_path);
2117 imd->read_ahead_path = NULL;
2120 static void image_read_ahead_done_cb(ImageLoader *il, gpointer data)
2122 ImageWindow *imd = data;
2124 if (debug) printf("read ahead done for :%s\n", imd->read_ahead_path);
2126 imd->read_ahead_pixbuf = image_loader_get_pixbuf(imd->read_ahead_il);
2127 if (imd->read_ahead_pixbuf)
2129 g_object_ref(imd->read_ahead_pixbuf);
2133 imd->read_ahead_pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
2135 image_loader_free(imd->read_ahead_il);
2136 imd->read_ahead_il = NULL;
2138 image_complete_util(imd, TRUE);
2141 static void image_read_ahead_error_cb(ImageLoader *il, gpointer data)
2143 /* we even treat errors as success, maybe at least some of the file was ok */
2144 image_read_ahead_done_cb(il, data);
2147 static void image_read_ahead_start(ImageWindow *imd)
2149 /* already started ? */
2150 if (!imd->read_ahead_path || imd->read_ahead_il || imd->read_ahead_pixbuf) return;
2152 /* still loading ?, do later */
2153 if (imd->il) return;
2155 if (debug) printf("read ahead started for :%s\n", imd->read_ahead_path);
2157 imd->read_ahead_il = image_loader_new(imd->read_ahead_path);
2159 image_loader_set_error_func(imd->read_ahead_il, image_read_ahead_error_cb, imd);
2160 if (!image_loader_start(imd->read_ahead_il, image_read_ahead_done_cb, imd))
2162 image_read_ahead_cancel(imd);
2163 image_complete_util(imd, TRUE);
2167 static void image_read_ahead_set(ImageWindow *imd, const gchar *path)
2169 if (imd->read_ahead_path && path && strcmp(imd->read_ahead_path, path) == 0) return;
2171 image_read_ahead_cancel(imd);
2173 imd->read_ahead_path = g_strdup(path);
2175 if (debug) printf("read ahead set to :%s\n", imd->read_ahead_path);
2177 image_read_ahead_start(imd);
2181 *-------------------------------------------------------------------
2183 *-------------------------------------------------------------------
2186 static void image_post_buffer_set(ImageWindow *imd, const gchar *path, GdkPixbuf *pixbuf)
2188 g_free(imd->prev_path);
2189 if (imd->prev_pixbuf) g_object_unref(imd->prev_pixbuf);
2193 imd->prev_path = g_strdup(path);
2195 g_object_ref(pixbuf);
2196 imd->prev_pixbuf = pixbuf;
2200 imd->prev_path = NULL;
2201 imd->prev_pixbuf = NULL;
2204 if (debug) printf("post buffer set: %s\n", path);
2207 static gint image_post_buffer_get(ImageWindow *imd)
2211 if (imd->prev_pixbuf &&
2212 imd->image_path && imd->prev_path && strcmp(imd->image_path, imd->prev_path) == 0)
2214 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2215 imd->pixbuf = imd->prev_pixbuf;
2220 if (imd->prev_pixbuf) g_object_unref(imd->prev_pixbuf);
2224 imd->prev_pixbuf = NULL;
2226 g_free(imd->prev_path);
2227 imd->prev_path = NULL;
2233 *-------------------------------------------------------------------
2235 *-------------------------------------------------------------------
2238 static void image_load_pixbuf_ready(ImageWindow *imd)
2240 if (imd->pixbuf || !imd->il) return;
2242 imd->pixbuf = image_loader_get_pixbuf(imd->il);
2244 if (imd->pixbuf) g_object_ref(imd->pixbuf);
2246 image_pixbuf_sync(imd, imd->zoom, TRUE, TRUE);
2249 static void image_load_area_cb(ImageLoader *il, guint x, guint y, guint w, guint h, gpointer data)
2251 ImageWindow *imd = data;
2253 if (imd->delay_flip &&
2254 imd->pixbuf != image_loader_get_pixbuf(il))
2259 if (!imd->pixbuf) image_load_pixbuf_ready(imd);
2261 if (imd->scale != 1.0)
2263 x = (guint) floor((double)x * imd->scale);
2264 y = (guint) floor((double)y * imd->scale);
2265 w = (guint) ceil((double)w * imd->scale);
2266 h = (guint) ceil((double)h * imd->scale);
2271 if ((GdkInterpType)zoom_quality != GDK_INTERP_NEAREST)
2273 /* some scaling types use surrounding pixels to smooth the image,
2274 * this will expand the new area to cover up for faint black
2275 * lines caused by previous renders with non-complete image
2283 image_queue(imd, (gint) x, (gint) y, (gint) w, (gint) h, FALSE, TILE_RENDER_AREA, TRUE);
2286 static void image_load_done_cb(ImageLoader *il, gpointer data)
2288 ImageWindow *imd = data;
2290 if (debug) printf ("image done\n");
2292 if (imd->delay_flip &&
2293 imd->pixbuf != image_loader_get_pixbuf(imd->il))
2295 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2296 imd->pixbuf = image_loader_get_pixbuf(imd->il);
2297 if (imd->pixbuf) g_object_ref(imd->pixbuf);
2298 image_pixbuf_sync(imd, imd->zoom, FALSE, TRUE);
2301 image_loader_free(imd->il);
2304 image_post_process(imd, TRUE);
2306 image_read_ahead_start(imd);
2309 static void image_load_error_cb(ImageLoader *il, gpointer data)
2311 if (debug) printf ("image error\n");
2313 /* even on error handle it like it was done,
2314 * since we have a pixbuf with _something_ */
2316 image_load_done_cb(il, data);
2319 #ifdef IMAGE_THROTTLE_LARGER_IMAGES
2320 static void image_load_buffer_throttle(ImageLoader *il)
2322 if (!il || il->bytes_total < IMAGE_THROTTLE_THRESHOLD) return;
2324 /* Larger image files usually have larger chunks of data per pixel...
2325 * So increase the buffer read size so that the rendering chunks called
2329 image_loader_set_buffer_size(il, IMAGE_LOAD_BUFFER_COUNT * IMAGE_THROTTLE_FACTOR);
2333 /* this read ahead is located here merely for the callbacks, above */
2335 static gint image_read_ahead_check(ImageWindow *imd)
2337 if (!imd->read_ahead_path) return FALSE;
2338 if (imd->il) return FALSE;
2340 if (!imd->image_path || strcmp(imd->read_ahead_path, imd->image_path) != 0)
2342 image_read_ahead_cancel(imd);
2346 if (imd->read_ahead_il)
2348 imd->il = imd->read_ahead_il;
2349 imd->read_ahead_il = NULL;
2351 /* override the old signals */
2352 image_loader_set_area_ready_func(imd->il, image_load_area_cb, imd);
2353 image_loader_set_error_func(imd->il, image_load_error_cb, imd);
2354 image_loader_set_buffer_size(imd->il, IMAGE_LOAD_BUFFER_COUNT);
2356 #ifdef IMAGE_THROTTLE_LARGER_IMAGES
2357 image_load_buffer_throttle(imd->il);
2360 /* do this one directly (probably should add a set func) */
2361 imd->il->func_done = image_load_done_cb;
2363 if (!imd->delay_flip)
2365 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2366 imd->pixbuf = image_loader_get_pixbuf(imd->il);
2367 if (imd->pixbuf) g_object_ref(imd->pixbuf);
2370 image_read_ahead_cancel(imd);
2373 else if (imd->read_ahead_pixbuf)
2375 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2376 imd->pixbuf = imd->read_ahead_pixbuf;
2377 imd->read_ahead_pixbuf = NULL;
2379 image_read_ahead_cancel(imd);
2381 image_post_process(imd, FALSE);
2385 image_read_ahead_cancel(imd);
2389 static gint image_load_begin(ImageWindow *imd, const gchar *path)
2391 if (debug) printf ("image begin \n");
2393 if (imd->il) return FALSE;
2395 imd->completed = FALSE;
2397 if (image_post_buffer_get(imd))
2399 if (debug) printf("from post buffer: %s\n", imd->image_path);
2401 image_pixbuf_sync(imd, imd->zoom, FALSE, TRUE);
2405 if (image_read_ahead_check(imd))
2407 if (debug) printf("from read ahead buffer: %s\n", imd->image_path);
2409 if (!imd->delay_flip || !imd->il) image_pixbuf_sync(imd, imd->zoom, FALSE, TRUE);
2413 if (!imd->delay_flip && imd->pixbuf)
2415 g_object_unref(imd->pixbuf);
2419 imd->il = image_loader_new(path);
2421 image_loader_set_area_ready_func(imd->il, image_load_area_cb, imd);
2422 image_loader_set_error_func(imd->il, image_load_error_cb, imd);
2423 image_loader_set_buffer_size(imd->il, IMAGE_LOAD_BUFFER_COUNT);
2425 if (!image_loader_start(imd->il, image_load_done_cb, imd))
2427 if (debug) printf("image start error\n");
2429 image_loader_free(imd->il);
2432 image_complete_util(imd, FALSE);
2437 #ifdef IMAGE_THROTTLE_LARGER_IMAGES
2438 image_load_buffer_throttle(imd->il);
2441 if (!imd->delay_flip && !imd->pixbuf) image_load_pixbuf_ready(imd);
2446 static void image_reset(ImageWindow *imd)
2448 /* stops anything currently being done */
2450 if (debug) printf("image reset\n");
2452 image_loader_free(imd->il);
2455 image_queue_clear(imd);
2456 imd->delay_alter_type = ALTER_NONE;
2460 *-------------------------------------------------------------------
2462 *-------------------------------------------------------------------
2465 static void image_change_complete(ImageWindow *imd, gdouble zoom, gint new)
2469 image_source_tile_unset(imd);
2471 imd->zoom = zoom; /* store the zoom, needed by the loader */
2475 if (imd->image_path && isfile(imd->image_path))
2477 if (image_load_begin(imd, imd->image_path))
2479 imd->unknown = FALSE;
2484 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2485 imd->pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
2486 imd->unknown = TRUE;
2488 imd->size = filesize(imd->image_path);
2489 imd->mtime = filetime(imd->image_path);
2493 if (imd->pixbuf) g_object_unref(imd->pixbuf);
2496 if (imd->image_path)
2498 imd->pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
2499 imd->mtime = filetime(imd->image_path);
2506 imd->unknown = TRUE;
2512 image_pixbuf_sync(imd, zoom, FALSE, new);
2516 image_update_util(imd);
2520 static void image_change_real(ImageWindow *imd, const gchar *path,
2521 CollectionData *cd, CollectInfo *info, gdouble zoom)
2523 GdkPixbuf *prev_pixbuf = NULL;
2524 gchar *prev_path = NULL;
2525 gint prev_clear = FALSE;
2527 imd->collection = cd;
2528 imd->collection_info = info;
2530 if (enable_read_ahead && imd->image_path && imd->pixbuf)
2534 /* current image is not finished */
2539 prev_path = g_strdup(imd->image_path);
2540 prev_pixbuf = imd->pixbuf;
2541 g_object_ref(prev_pixbuf);
2545 g_free(imd->image_path);
2546 imd->image_path = g_strdup(path);
2547 imd->image_name = filename_from_path(imd->image_path);
2549 image_change_complete(imd, zoom, TRUE);
2553 image_post_buffer_set(imd, prev_path, prev_pixbuf);
2555 g_object_unref(prev_pixbuf);
2557 else if (prev_clear)
2559 image_post_buffer_set(imd, NULL, NULL);
2562 image_update_title(imd);
2563 image_new_util(imd);
2567 *-------------------------------------------------------------------
2569 *-------------------------------------------------------------------
2572 static gint image_expose_cb(GtkWidget *widget, GdkEventExpose *event, gpointer data)
2576 ImageWindow *imd = data;
2578 image_border_draw(imd, event->area.x, event->area.y,
2579 event->area.width, event->area.height);
2582 x = MAX(0, (gint)event->area.x - imd->x_offset + imd->x_scroll);
2583 y = MAX(0, (gint)event->area.y - imd->y_offset + imd->y_scroll);
2585 image_queue(imd, x, y,
2586 MIN((gint)event->area.width, imd->width - x),
2587 MIN((gint)event->area.height, imd->height - y),
2588 FALSE, TILE_RENDER_ALL, FALSE);
2593 static void image_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data)
2595 ImageWindow *imd = data;
2597 image_size_sync(imd, allocation->width, allocation->height);
2601 *-------------------------------------------------------------------
2603 *-------------------------------------------------------------------
2606 static void image_focus_paint(ImageWindow *imd, gint has_focus, GdkRectangle *area)
2610 widget = imd->widget;
2611 if (!widget->window) return;
2615 gtk_paint_focus (widget->style, widget->window, GTK_STATE_ACTIVE,
2616 area, widget, "image_window",
2617 widget->allocation.x, widget->allocation.y,
2618 widget->allocation.width - 1, widget->allocation.height - 1);
2622 gtk_paint_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN,
2623 area, widget, "image_window",
2624 widget->allocation.x, widget->allocation.y,
2625 widget->allocation.width - 1, widget->allocation.height - 1);
2629 static gint image_focus_expose(GtkWidget *widget, GdkEventExpose *event, gpointer data)
2631 ImageWindow *imd = data;
2633 image_focus_paint(imd, GTK_WIDGET_HAS_FOCUS(widget), &event->area);
2637 static gint image_focus_in_cb(GtkWidget *widget, GdkEventFocus *event, gpointer data)
2639 ImageWindow *imd = data;
2641 GTK_WIDGET_SET_FLAGS(imd->widget, GTK_HAS_FOCUS);
2642 image_focus_paint(imd, TRUE, NULL);
2647 static gint image_focus_out_cb(GtkWidget *widget, GdkEventFocus *event, gpointer data)
2649 ImageWindow *imd = data;
2651 GTK_WIDGET_UNSET_FLAGS(imd->widget, GTK_HAS_FOCUS);
2652 image_focus_paint(imd, FALSE, NULL);
2659 *-------------------------------------------------------------------
2661 *-------------------------------------------------------------------
2664 static void image_overlay_draw(ImageWindow *imd, gint x, gint y, gint w, gint h)
2668 work = imd->overlay_list;
2672 gint px, py, pw, ph;
2673 gint rx, ry, rw, rh;
2678 if (!od->visible) continue;
2680 pw = gdk_pixbuf_get_width(od->pixbuf);
2681 ph = gdk_pixbuf_get_height(od->pixbuf);
2687 if (px < 0) px = imd->window_width - pw + px;
2688 if (py < 0) py = imd->window_height - ph + py;
2691 if (util_clip_region(x, y, w, h, px, py, pw, ph, &rx, &ry, &rw, &rh))
2693 gdk_draw_pixbuf(imd->image->window,
2694 imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
2698 (GdkRgbDither)dither_quality, rx, ry);
2703 static void image_overlay_queue_draw(ImageWindow *imd, OverlayData *od, gint hidden)
2708 w = gdk_pixbuf_get_width(od->pixbuf);
2709 h = gdk_pixbuf_get_height(od->pixbuf);
2715 if (x < 0) x = imd->window_width - w + x;
2716 if (y < 0) y = imd->window_height - h + y;
2719 image_queue(imd, imd->x_scroll - imd->x_offset + x,
2720 imd->y_scroll - imd->y_offset + y,
2722 FALSE, TILE_RENDER_ALL, FALSE);
2724 old_vis = od->visible;
2725 if (hidden) od->visible = FALSE;
2726 image_border_draw(imd, x, y, w, h);
2727 od->visible = old_vis;
2730 static void image_overlay_queue_all(ImageWindow *imd)
2734 work = imd->overlay_list;
2737 OverlayData *od = work->data;
2740 image_overlay_queue_draw(imd, od, FALSE);
2744 static OverlayData *image_overlay_find(ImageWindow *imd, gint id)
2748 work = imd->overlay_list;
2751 OverlayData *od = work->data;
2754 if (od->id == id) return od;
2760 gint image_overlay_add(ImageWindow *imd, GdkPixbuf *pixbuf, gint x, gint y,
2761 gint relative, gint always)
2766 if (!imd || !pixbuf) return -1;
2769 while (image_overlay_find(imd, id)) id++;
2771 od = g_new0(OverlayData, 1);
2773 od->pixbuf = pixbuf;
2774 g_object_ref(G_OBJECT(od->pixbuf));
2777 od->relative = relative;
2779 od->always = always;
2781 imd->overlay_list = g_list_append(imd->overlay_list, od);
2783 image_overlay_queue_draw(imd, od, FALSE);
2788 static void image_overlay_free(ImageWindow *imd, OverlayData *od)
2790 imd->overlay_list = g_list_remove(imd->overlay_list, od);
2792 if (od->pixbuf) g_object_unref(G_OBJECT(od->pixbuf));
2796 static void image_overlay_list_clear(ImageWindow *imd)
2798 while (imd->overlay_list)
2802 od = imd->overlay_list->data;
2803 image_overlay_free(imd, od);
2807 void image_overlay_set(ImageWindow *imd, gint id, GdkPixbuf *pixbuf, gint x, gint y)
2813 od = image_overlay_find(imd, id);
2818 image_overlay_queue_draw(imd, od, TRUE);
2820 g_object_ref(G_OBJECT(pixbuf));
2821 g_object_unref(G_OBJECT(od->pixbuf));
2822 od->pixbuf = pixbuf;
2827 image_overlay_queue_draw(imd, od, FALSE);
2831 image_overlay_queue_draw(imd, od, TRUE);
2832 image_overlay_free(imd, od);
2836 gint image_overlay_get(ImageWindow *imd, gint id, GdkPixbuf **pixbuf, gint *x, gint *y)
2840 if (!imd) return FALSE;
2842 od = image_overlay_find(imd, id);
2843 if (!od) return FALSE;
2845 if (pixbuf) *pixbuf = od->pixbuf;
2852 void image_overlay_remove(ImageWindow *imd, gint id)
2854 image_overlay_set(imd, id, NULL, 0, 0);
2858 *-------------------------------------------------------------------
2860 *-------------------------------------------------------------------
2863 #define SCROLLER_UPDATES_PER_SEC 30
2864 #define SCROLLER_DEAD_ZONE 6
2867 static gboolean image_scroller_update_cb(gpointer data)
2869 ImageWindow *imd = data;
2873 /* this was a simple scroll by difference between scroller and mouse position,
2874 * but all this math results in a smoother result and accounts for a dead zone.
2877 if (abs(imd->scroller_xpos - imd->scroller_x) < SCROLLER_DEAD_ZONE)
2883 gint shift = SCROLLER_DEAD_ZONE / 2 * SCROLLER_UPDATES_PER_SEC;
2884 x = (imd->scroller_xpos - imd->scroller_x) / 2 * SCROLLER_UPDATES_PER_SEC;
2885 x += (x > 0) ? -shift : shift;
2888 if (abs(imd->scroller_ypos - imd->scroller_y) < SCROLLER_DEAD_ZONE)
2894 gint shift = SCROLLER_DEAD_ZONE / 2 * SCROLLER_UPDATES_PER_SEC;
2895 y = (imd->scroller_ypos - imd->scroller_y) / 2 * SCROLLER_UPDATES_PER_SEC;
2896 y += (y > 0) ? -shift : shift;
2899 if (abs(x) < SCROLLER_DEAD_ZONE * SCROLLER_UPDATES_PER_SEC)
2905 xinc = imd->scroller_xinc;
2909 if (xinc < 0) xinc = 0;
2910 if (x < xinc) xinc = x;
2911 if (x > xinc) xinc = MIN(xinc + x / SCROLLER_UPDATES_PER_SEC, x);
2915 if (xinc > 0) xinc = 0;
2916 if (x > xinc) xinc = x;
2917 if (x < xinc) xinc = MAX(xinc + x / SCROLLER_UPDATES_PER_SEC, x);
2921 if (abs(y) < SCROLLER_DEAD_ZONE * SCROLLER_UPDATES_PER_SEC)
2927 yinc = imd->scroller_yinc;
2931 if (yinc < 0) yinc = 0;
2932 if (y < yinc) yinc = y;
2933 if (y > yinc) yinc = MIN(yinc + y / SCROLLER_UPDATES_PER_SEC, y);
2937 if (yinc > 0) yinc = 0;
2938 if (y > yinc) yinc = y;
2939 if (y < yinc) yinc = MAX(yinc + y / SCROLLER_UPDATES_PER_SEC, y);
2943 imd->scroller_xinc = xinc;
2944 imd->scroller_yinc = yinc;
2946 xinc = xinc / SCROLLER_UPDATES_PER_SEC;
2947 yinc = yinc / SCROLLER_UPDATES_PER_SEC;
2949 image_scroll(imd, xinc, yinc);
2954 static void image_scroller_timer_set(ImageWindow *imd, gint start)
2956 if (imd->scroller_id != -1)
2958 g_source_remove(imd->scroller_id);
2959 imd->scroller_id = -1;
2964 imd->scroller_id = g_timeout_add(1000 / SCROLLER_UPDATES_PER_SEC,
2965 image_scroller_update_cb, imd);
2969 static void image_scroller_start(ImageWindow *imd, gint x, gint y)
2971 if (imd->scroller_overlay == -1)
2976 pixbuf = pixbuf_inline(PIXBUF_INLINE_SCROLLER);
2977 w = gdk_pixbuf_get_width(pixbuf);
2978 h = gdk_pixbuf_get_height(pixbuf);
2980 imd->scroller_overlay = image_overlay_add(imd, pixbuf, x - w / 2, y - h / 2, FALSE, TRUE);
2983 imd->scroller_x = x;
2984 imd->scroller_y = y;
2985 imd->scroller_xpos = x;
2986 imd->scroller_ypos = y;
2988 image_scroller_timer_set(imd, TRUE);
2991 static void image_scroller_stop(ImageWindow *imd)
2993 if (imd->scroller_id == -1) return;
2995 image_overlay_remove(imd, imd->scroller_overlay);
2996 imd->scroller_overlay = -1;
2998 image_scroller_timer_set(imd, FALSE);
3002 *-------------------------------------------------------------------
3004 *-------------------------------------------------------------------
3007 static gint image_mouse_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3009 ImageWindow *imd = data;
3012 if (imd->scroller_id != -1)
3014 imd->scroller_xpos = bevent->x;
3015 imd->scroller_ypos = bevent->y;
3018 if (!imd->in_drag || !gdk_pointer_is_grabbed()) return FALSE;
3020 if (imd->drag_moved < IMAGE_DRAG_SCROLL_THRESHHOLD)
3026 widget_set_cursor (imd->image, GDK_FLEUR);
3029 if (bevent->state & GDK_SHIFT_MASK)
3031 accel = IMAGE_PAN_SHIFT_MULTIPLIER;
3039 image_scroll_real(imd, (imd->drag_last_x - bevent->x) * accel,
3040 (imd->drag_last_y - bevent->y) * accel);
3042 imd->drag_last_x = bevent->x;
3043 imd->drag_last_y = bevent->y;
3048 static gint image_mouse_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3050 ImageWindow *imd = data;
3052 if (imd->scroller_id != -1) return TRUE;
3054 switch (bevent->button)
3057 imd->in_drag = TRUE;
3058 imd->drag_last_x = bevent->x;
3059 imd->drag_last_y = bevent->y;
3060 imd->drag_moved = 0;
3061 gdk_pointer_grab(imd->image->window, FALSE,
3062 GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
3063 NULL, NULL, bevent->time);
3064 gtk_grab_add(imd->image);
3067 imd->drag_moved = 0;
3070 image_button_do(imd, bevent);
3076 gtk_widget_grab_focus(imd->widget);
3081 static gint image_mouse_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3083 ImageWindow *imd = data;
3085 if (imd->scroller_id != -1)
3087 image_scroller_stop(imd);
3091 if (gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB(imd->image))
3093 gtk_grab_remove(imd->image);
3094 gdk_pointer_ungrab(bevent->time);
3095 widget_set_cursor(imd->image, -1);
3098 if (imd->drag_moved < IMAGE_DRAG_SCROLL_THRESHHOLD)
3100 if (bevent->button == 1 && (bevent->state & GDK_SHIFT_MASK))
3102 image_scroller_start(imd, bevent->x, bevent->y);
3104 else if (bevent->button == 1 || bevent->button == 2)
3106 image_button_do(imd, bevent);
3110 imd->in_drag = FALSE;
3115 static gint image_mouse_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
3117 ImageWindow *imd = data;
3119 if (imd->scroller_id != -1)
3121 imd->scroller_xpos = imd->scroller_x;
3122 imd->scroller_ypos = imd->scroller_y;
3123 imd->scroller_xinc = 0;
3124 imd->scroller_yinc = 0;
3130 static gint image_scroll_cb(GtkWidget *widget, GdkEventScroll *event, gpointer data)
3132 ImageWindow *imd = data;
3134 if (imd->func_scroll &&
3135 event && event->type == GDK_SCROLL)
3137 imd->func_scroll(imd, event->direction, event->time,
3138 event->x, event->y, event->state, imd->data_scroll);
3145 static void image_mouse_drag_cb(GtkWidget *widget, GdkDragContext *context, gpointer data)
3147 ImageWindow *imd = data;
3149 imd->drag_moved = IMAGE_DRAG_SCROLL_THRESHHOLD;
3153 *-------------------------------------------------------------------
3155 *-------------------------------------------------------------------
3159 *-------------------------------------------------------------------
3161 *-------------------------------------------------------------------
3164 void image_attach_window(ImageWindow *imd, GtkWidget *window,
3165 const gchar *title, const gchar *title_right, gint show_zoom)
3167 imd->top_window = window;
3169 imd->title = g_strdup(title);
3170 g_free(imd->title_right);
3171 imd->title_right = g_strdup(title_right);
3172 imd->title_show_zoom = show_zoom;
3174 image_update_title(imd);
3177 void image_set_update_func(ImageWindow *imd,
3178 void (*func)(ImageWindow *imd, gpointer data),
3181 imd->func_update = func;
3182 imd->data_update = data;
3185 void image_set_complete_func(ImageWindow *imd,
3186 void (*func)(ImageWindow *, gint preload, gpointer),
3189 imd->func_complete = func;
3190 imd->data_complete = data;
3193 void image_set_new_func(ImageWindow *imd,
3194 void (*func)(ImageWindow *, gpointer),
3197 imd->func_new = func;
3198 imd->data_new = data;
3202 static void image_button_do(ImageWindow *imd, GdkEventButton *bevent)
3204 if (imd->func_button &&
3206 (bevent->type == GDK_BUTTON_PRESS || bevent->type == GDK_BUTTON_RELEASE))
3208 imd->func_button(imd, bevent->button, bevent->time,
3209 bevent->x, bevent->y, bevent->state, imd->data_button);
3213 void image_set_button_func(ImageWindow *imd,
3214 void (*func)(ImageWindow *, gint button, guint32 time, gdouble x, gdouble y, guint state, gpointer),
3217 imd->func_button = func;
3218 imd->data_button = data;
3221 void image_set_scroll_func(ImageWindow *imd,
3222 void (*func)(ImageWindow *, GdkScrollDirection direction, guint32 time, gdouble x, gdouble y, guint state, gpointer),
3225 imd->func_scroll = func;
3226 imd->data_scroll = data;
3229 void image_set_scroll_notify_func(ImageWindow *imd,
3230 void (*func)(ImageWindow *imd, gint x, gint y, gint width, gint height, gpointer data),
3233 imd->func_scroll_notify = func;
3234 imd->data_scroll_notify = data;
3239 const gchar *image_get_path(ImageWindow *imd)
3241 return imd->image_path;
3244 const gchar *image_get_name(ImageWindow *imd)
3246 return imd->image_name;
3249 /* merely changes path string, does not change the image! */
3250 void image_set_path(ImageWindow *imd, const gchar *newpath)
3252 g_free(imd->image_path);
3253 imd->image_path = g_strdup(newpath);
3254 imd->image_name = filename_from_path(imd->image_path);
3256 image_update_title(imd);
3257 image_new_util(imd);
3260 /* load a new image */
3262 void image_change_path(ImageWindow *imd, const gchar *path, gdouble zoom)
3264 if (imd->image_path == path ||
3265 (path && imd->image_path && !strcmp(path, imd->image_path)) ) return;
3267 image_source_tile_unset(imd);
3269 image_change_real(imd, path, NULL, NULL, zoom);
3272 void image_change_pixbuf(ImageWindow *imd, GdkPixbuf *pixbuf, gdouble zoom)
3274 image_source_tile_unset(imd);
3276 image_set_pixbuf(imd, pixbuf, zoom, TRUE);
3277 image_new_util(imd);
3280 void image_change_from_collection(ImageWindow *imd, CollectionData *cd, CollectInfo *info, gdouble zoom)
3282 if (!cd || !info || !g_list_find(cd->list, info)) return;
3284 image_source_tile_unset(imd);
3286 image_change_real(imd, info->path, cd, info, zoom);
3289 CollectionData *image_get_collection(ImageWindow *imd, CollectInfo **info)
3291 if (collection_to_number(imd->collection) >= 0)
3293 if (g_list_find(imd->collection->list, imd->collection_info) != NULL)
3295 if (info) *info = imd->collection_info;
3299 if (info) *info = NULL;
3301 return imd->collection;
3304 if (info) *info = NULL;
3308 static void image_loader_sync_data(ImageLoader *il, gpointer data)
3310 /* change data for the callbacks directly */
3312 il->data_area_ready = data;
3313 il->data_error = data;
3314 il->data_done = data;
3315 il->data_percent = data;
3318 /* this is more like a move function
3319 * it moves most data from source to imd, source does keep a ref on the pixbuf
3321 void image_change_from_image(ImageWindow *imd, ImageWindow *source)
3323 if (imd == source) return;
3325 imd->zoom_min = source->zoom_min;
3326 imd->zoom_max = source->zoom_max;
3328 imd->unknown = source->unknown;
3330 image_set_pixbuf(imd, source->pixbuf, image_zoom_get(source), TRUE);
3332 imd->collection = source->collection;
3333 imd->collection_info = source->collection_info;
3334 imd->size = source->size;
3335 imd->mtime = source->mtime;
3337 image_set_path(imd, image_get_path(source));
3339 image_loader_free(imd->il);
3342 if (imd->pixbuf && source->il)
3344 imd->il = source->il;
3347 image_loader_sync_data(imd->il, imd);
3349 imd->delay_alter_type = source->delay_alter_type;
3350 source->delay_alter_type = ALTER_NONE;
3353 image_loader_free(imd->read_ahead_il);
3354 imd->read_ahead_il = source->read_ahead_il;
3355 source->read_ahead_il = NULL;
3356 if (imd->read_ahead_il) image_loader_sync_data(imd->read_ahead_il, imd);
3358 if (imd->read_ahead_pixbuf) g_object_unref(imd->read_ahead_pixbuf);
3359 imd->read_ahead_pixbuf = source->read_ahead_pixbuf;
3360 source->read_ahead_pixbuf = NULL;
3362 g_free(imd->read_ahead_path);
3363 imd->read_ahead_path = source->read_ahead_path;
3364 source->read_ahead_path = NULL;
3366 if (imd->prev_pixbuf) g_object_unref(imd->prev_pixbuf);
3367 imd->prev_pixbuf = source->prev_pixbuf;
3368 source->prev_pixbuf = NULL;
3370 g_free(imd->prev_path);
3371 imd->prev_path = source->prev_path;
3372 source->prev_path = NULL;
3374 imd->completed = source->completed;
3376 imd->x_scroll = source->x_scroll;
3377 imd->y_scroll = source->y_scroll;
3379 if (imd->source_tiles_enabled)
3381 image_source_tile_unset(imd);
3384 if (source->source_tiles_enabled)
3386 imd->source_tiles_enabled = source->source_tiles_enabled;
3387 imd->source_tiles_cache_size = source->source_tiles_cache_size;
3388 imd->source_tiles = source->source_tiles;
3389 imd->source_tile_width = source->source_tile_width;
3390 imd->source_tile_height = source->source_tile_height;
3392 source->source_tiles_enabled = FALSE;
3393 source->source_tiles = NULL;
3395 imd->func_tile_request = source->func_tile_request;
3396 imd->func_tile_dispose = source->func_tile_dispose;
3397 imd->data_tile = source->data_tile;
3399 source->func_tile_request = NULL;
3400 source->func_tile_dispose = NULL;
3401 source->data_tile = NULL;
3403 imd->image_width = source->image_width;
3404 imd->image_height = source->image_height;
3406 if (image_zoom_clamp(imd, source->zoom, TRUE, TRUE))
3408 image_size_clamp(imd);
3409 image_scroll_clamp(imd);
3410 image_tile_sync(imd, imd->width, imd->height, FALSE);
3411 image_redraw(imd, FALSE);
3416 image_scroll_clamp(imd);
3421 void image_area_changed(ImageWindow *imd, gint x, gint y, gint width, gint height)
3423 gint sx, sy, sw, sh;
3425 sx = (gint)floor((double)x * imd->scale);
3426 sy = (gint)floor((double)y * imd->scale);
3427 sw = (gint)ceil((double)width * imd->scale);
3428 sh = (gint)ceil((double)height * imd->scale);
3430 if (imd->source_tiles_enabled)
3432 source_tile_changed(imd, x, y, width, height);
3435 image_queue(imd, sx, sy, sw, sh, FALSE, TILE_RENDER_AREA, TRUE);
3438 void image_reload(ImageWindow *imd)
3440 if (imd->source_tiles_enabled) return;
3442 image_change_complete(imd, imd->zoom, FALSE);
3445 void image_scroll(ImageWindow *imd, gint x, gint y)
3447 image_scroll_real(imd, x, y);
3450 void image_scroll_to_point(ImageWindow *imd, gint x, gint y,
3451 gdouble x_align, gdouble y_align)
3456 x_align = CLAMP(x_align, 0.0, 1.0);
3457 y_align = CLAMP(y_align, 0.0, 1.0);
3459 ax = (gdouble)imd->vis_width * x_align;
3460 ay = (gdouble)imd->vis_height * y_align;
3462 px = (gdouble)x * imd->scale - (imd->x_scroll + ax);
3463 py = (gdouble)y * imd->scale - (imd->y_scroll + ay);
3465 image_scroll(imd, px, py);
3468 void image_alter(ImageWindow *imd, AlterType type)
3470 if (imd->source_tiles_enabled) return;
3474 /* still loading, wait till done */
3475 imd->delay_alter_type = type;
3479 image_alter_real(imd, type, TRUE);
3484 static void image_zoom_adjust_real(ImageWindow *imd, gdouble increment,
3485 gint center_point, gint x, gint y)
3487 gdouble zoom = imd->zoom;
3489 if (increment == 0.0) return; /* avoid possible div by zero, a no-op anyway... */
3493 if (imd->scale < 1.0)
3495 zoom = 0.0 - 1.0 / imd->scale;
3503 if (increment < 0.0)
3505 if (zoom >= 1.0 && zoom + increment < 1.0)
3507 zoom = zoom + increment - 2.0;
3511 zoom = zoom + increment;
3516 if (zoom <= -1.0 && zoom + increment > -1.0)
3518 zoom = zoom + increment + 2.0;
3522 zoom = zoom + increment;
3526 image_zoom_sync(imd, zoom, FALSE, FALSE, FALSE, center_point, x, y);
3529 void image_zoom_adjust(ImageWindow *imd, gdouble increment)
3531 image_zoom_adjust_real(imd, increment, FALSE, 0, 0);
3534 void image_zoom_adjust_at_point(ImageWindow *imd, gdouble increment, gint x, gint y)
3536 image_zoom_adjust_real(imd, increment, TRUE, x, y);
3539 void image_zoom_set_limits(ImageWindow *imd, gdouble min, gdouble max)
3541 if (min > 1.0 || max < 1.0) return;
3542 if (min < 1.0 && min > -1.0) return;
3543 if (min < -200.0 || max > 200.0) return;
3545 imd->zoom_min = min;
3546 imd->zoom_max = max;
3549 void image_zoom_set(ImageWindow *imd, gdouble zoom)
3551 image_zoom_sync(imd, zoom, FALSE, FALSE, FALSE, FALSE, 0, 0);
3554 void image_zoom_set_fill_geometry(ImageWindow *imd, gint vertical)
3558 if (!imd->pixbuf || imd->image_width < 1 || imd->image_height < 1) return;
3562 zoom = (gdouble)imd->window_height / imd->image_height;
3566 zoom = (gdouble)imd->window_width / imd->image_width;
3571 zoom = 0.0 - 1.0 / zoom;
3574 image_zoom_set(imd, zoom);
3577 gdouble image_zoom_get(ImageWindow *imd)
3582 gdouble image_zoom_get_real(ImageWindow *imd)
3587 gchar *image_zoom_get_as_text(ImageWindow *imd)
3593 gchar *approx = " ";
3595 if (imd->zoom > 0.0)
3599 else if (imd->zoom < 0.0)
3601 r = 0.0 - imd->zoom;
3603 else if (imd->zoom == 0.0 && imd->scale != 0.0)
3605 if (imd->scale >= 1.0)
3611 r = 1.0 / imd->scale;
3616 if (rint(l) != l) pl = 1;
3617 if (rint(r) != r) pr = 1;
3619 return g_strdup_printf("%.*f :%s%.*f", pl, l, approx, pr, r);
3622 gdouble image_zoom_get_default(ImageWindow *imd, gint mode)
3626 if (mode == ZOOM_RESET_ORIGINAL)
3630 else if (mode == ZOOM_RESET_FIT_WINDOW)
3638 zoom = image_zoom_get(imd);
3651 void image_prebuffer_set(ImageWindow *imd, const gchar *path)
3653 if (imd->source_tiles_enabled) return;
3657 image_read_ahead_set(imd, path);
3661 image_read_ahead_cancel(imd);
3665 static gint image_auto_refresh_cb(gpointer data)
3667 ImageWindow *imd = data;
3670 if (!imd || !imd->pixbuf ||
3671 imd->il || !imd->image_path ||
3672 !update_on_time_change) return TRUE;
3674 newtime = filetime(imd->image_path);
3675 if (newtime > 0 && newtime != imd->mtime)
3677 imd->mtime = newtime;
3684 /* image auto refresh on time stamp change, in 1/1000's second, -1 disables */
3686 void image_auto_refresh(ImageWindow *imd, gint interval)
3689 if (imd->source_tiles_enabled) return;
3691 if (imd->auto_refresh_id > -1)
3693 g_source_remove(imd->auto_refresh_id);
3694 imd->auto_refresh_id = -1;
3695 imd->auto_refresh_interval = -1;
3698 if (interval < 0) return;
3700 if (interval == 0) interval = IMAGE_AUTO_REFRESH_TIME;
3702 imd->auto_refresh_id = g_timeout_add((guint32)interval, image_auto_refresh_cb, imd);
3703 imd->auto_refresh_interval = interval;
3706 /* allow top window to be resized ? */
3708 void image_top_window_set_sync(ImageWindow *imd, gint allow_sync)
3710 imd->top_window_sync = allow_sync;
3713 /* background colors */
3715 void image_background_set_black(ImageWindow *imd, gint black)
3721 style = gtk_style_copy(gtk_widget_get_style(imd->widget));
3722 g_object_ref(G_OBJECT(style));
3726 style->bg[GTK_STATE_NORMAL] = style->black;
3729 gtk_widget_set_style(imd->image, style);
3730 g_object_unref(G_OBJECT(style));
3732 if (GTK_WIDGET_VISIBLE(imd->widget)) image_border_clear(imd);
3735 void image_background_set_color(ImageWindow *imd, GdkColor *color)
3741 style = gtk_style_copy(gtk_widget_get_style(imd->widget));
3742 g_object_ref(G_OBJECT(style));
3748 slot = &style->bg[GTK_STATE_NORMAL];
3750 slot->red = color->red;
3751 slot->green = color->green;
3752 slot->blue = color->blue;
3755 gtk_widget_set_style(imd->image, style);
3756 g_object_unref(G_OBJECT(style));
3758 if (GTK_WIDGET_VISIBLE(imd->widget)) image_border_clear(imd);
3761 void image_set_delay_flip(ImageWindow *imd, gint delay)
3764 imd->delay_flip == delay) return;
3766 imd->delay_flip = delay;
3767 if (!imd->delay_flip && imd->il)
3769 if (imd->pixbuf) g_object_unref(imd->pixbuf);
3771 image_load_pixbuf_ready(imd);
3773 image_queue_clear(imd);
3774 image_queue(imd, 0, 0, imd->width, imd->height, FALSE, TILE_RENDER_AREA, TRUE);
3778 /* wallpaper util */
3780 void image_to_root_window(ImageWindow *imd, gint scaled)
3783 GdkWindow *rootwindow;
3787 if (!imd || !imd->pixbuf) return;
3789 screen = gtk_widget_get_screen(imd->image);
3790 rootwindow = gdk_screen_get_root_window(screen);
3791 if (gdk_drawable_get_visual(rootwindow) != gdk_visual_get_system()) return;
3795 pb = gdk_pixbuf_scale_simple(imd->pixbuf, gdk_screen_width(), gdk_screen_height(), (GdkInterpType)zoom_quality);
3799 pb = gdk_pixbuf_scale_simple(imd->pixbuf, imd->width, imd->height, (GdkInterpType)zoom_quality);
3802 gdk_pixbuf_render_pixmap_and_mask (pb, &pixmap, NULL, 128);
3803 gdk_window_set_back_pixmap(rootwindow, pixmap, FALSE);
3804 gdk_window_clear(rootwindow);
3806 g_object_unref(pixmap);
3813 *-------------------------------------------------------------------
3815 *-------------------------------------------------------------------
3818 static void image_free(ImageWindow *imd)
3820 image_read_ahead_cancel(imd);
3821 image_post_buffer_set(imd, NULL, NULL);
3822 image_auto_refresh(imd, -1);
3824 g_free(imd->image_path);
3826 g_free(imd->title_right);
3829 image_tile_sync_count(imd, 0);
3830 if (imd->pixbuf) g_object_unref(imd->pixbuf);
3832 image_scroller_timer_set(imd, FALSE);
3834 image_overlay_list_clear(imd);
3836 source_tile_free_all(imd);
3841 static void image_destroy_cb(GtkObject *widget, gpointer data)
3843 ImageWindow *imd = data;
3847 ImageWindow *image_new(gint frame)
3851 imd = g_new0(ImageWindow, 1);
3853 imd->zoom_min = IMAGE_ZOOM_MIN;
3854 imd->zoom_max = IMAGE_ZOOM_MAX;
3858 imd->draw_idle_id = -1;
3860 imd->tile_width = IMAGE_TILE_SIZE;
3861 imd->tile_height = IMAGE_TILE_SIZE;
3863 imd->top_window = NULL;
3865 imd->title_right = NULL;
3866 imd->title_show_zoom = FALSE;
3868 imd->unknown = TRUE;
3872 imd->has_frame = frame;
3873 imd->top_window_sync = FALSE;
3875 imd->tile_cache = NULL;
3876 imd->tile_cache_size = 0;
3878 imd->delay_alter_type = ALTER_NONE;
3880 imd->read_ahead_il = NULL;
3881 imd->read_ahead_pixbuf = NULL;
3882 imd->read_ahead_path = NULL;
3884 imd->completed = FALSE;
3886 imd->auto_refresh_id = -1;
3887 imd->auto_refresh_interval = -1;
3889 imd->delay_flip = FALSE;
3891 imd->func_update = NULL;
3892 imd->func_complete = NULL;
3893 imd->func_tile_request = NULL;
3894 imd->func_tile_dispose = NULL;
3896 imd->func_button = NULL;
3897 imd->func_scroll = NULL;
3899 imd->scroller_id = -1;
3900 imd->scroller_overlay = -1;
3902 imd->source_tiles_enabled = FALSE;
3903 imd->source_tiles = NULL;
3905 imd->image = gtk_drawing_area_new();
3906 gtk_widget_set_double_buffered(imd->image, FALSE);
3910 imd->widget = gtk_frame_new(NULL);
3911 gtk_frame_set_shadow_type(GTK_FRAME(imd->widget), GTK_SHADOW_IN);
3912 gtk_container_add(GTK_CONTAINER(imd->widget), imd->image);
3913 gtk_widget_show(imd->image);
3915 GTK_WIDGET_SET_FLAGS(imd->widget, GTK_CAN_FOCUS);
3916 g_signal_connect(G_OBJECT(imd->widget), "focus_in_event",
3917 G_CALLBACK(image_focus_in_cb), imd);
3918 g_signal_connect(G_OBJECT(imd->widget), "focus_out_event",
3919 G_CALLBACK(image_focus_out_cb), imd);
3921 g_signal_connect_after(G_OBJECT(imd->widget), "expose_event",
3922 G_CALLBACK(image_focus_expose), imd);
3926 imd->widget = imd->image;
3929 g_signal_connect(G_OBJECT(imd->image), "motion_notify_event",
3930 G_CALLBACK(image_mouse_motion_cb), imd);
3931 g_signal_connect(G_OBJECT(imd->image), "button_press_event",
3932 G_CALLBACK(image_mouse_press_cb), imd);
3933 g_signal_connect(G_OBJECT(imd->image), "button_release_event",
3934 G_CALLBACK(image_mouse_release_cb), imd);
3935 g_signal_connect(G_OBJECT(imd->image), "leave_notify_event",
3936 G_CALLBACK(image_mouse_leave_cb), imd);
3937 gtk_widget_set_events(imd->image, GDK_POINTER_MOTION_MASK |
3938 GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
3939 GDK_LEAVE_NOTIFY_MASK);
3941 g_signal_connect(G_OBJECT(imd->image), "expose_event",
3942 G_CALLBACK(image_expose_cb), imd);
3943 g_signal_connect_after(G_OBJECT(imd->image), "size_allocate",
3944 G_CALLBACK(image_size_cb), imd);
3946 g_signal_connect(G_OBJECT(imd->image), "drag_begin",
3947 G_CALLBACK(image_mouse_drag_cb), imd);
3948 g_signal_connect(G_OBJECT(imd->image), "scroll_event",
3949 G_CALLBACK(image_scroll_cb), imd);
3951 g_signal_connect(G_OBJECT(imd->widget), "destroy",
3952 G_CALLBACK(image_destroy_cb), imd);