diff src/image.c @ 12:147f4c4b9025

##### Note: GQview CVS on sourceforge is not always up to date, please use ##### ##### an offical release when making enhancements and translation updates. ##### Tue Mar 1 11:32:26 2005 John Ellis <johne@verizon.net> * src/Makefile.am: Add pan-view.[ch]: * image.[ch]: Add support for using a grid of tiles as soource image. Added scroll_notify callback for when the viewable regionis scrolled. Added ability to set min and max for the zoom range. Removed unnecessary gtk_widget_size_request from image_size_sync. Added image_scroll_to_point. * layout_util.c: Add menu item and callback for the new 'Pan view'. * pixbuf_util.c (pixbuf_draw_layout): Fix for when offset is non-zero. * typedefs.h: Add source tile stuff for ImageWindow. * ui_tabcomp.c: Fix tab completion pop-up menu placement. * pan-view.[ch]: New files for the pan view - 2.1 is officially started :)
author gqview
date Tue, 01 Mar 2005 17:16:34 +0000
parents d907d608745f
children ef790149ae21
line wrap: on
line diff
--- a/src/image.c	Tue Mar 01 14:50:03 2005 +0000
+++ b/src/image.c	Tue Mar 01 17:16:34 2005 +0000
@@ -23,7 +23,7 @@
 #include <math.h>
 
 
-#define IMAGE_TILE_SIZE 128
+#define IMAGE_TILE_SIZE 512
 #define IMAGE_ZOOM_MIN -32.0
 #define IMAGE_ZOOM_MAX 32.0
 
@@ -119,6 +119,468 @@
 	gint always;	/* hide temporarily when scrolling */
 };
 
+/* needed to be declared before the source_tile stuff */
+static void image_pixbuf_sync(ImageWindow *imd, gdouble zoom, gint blank, gint new);
+static void image_zoom_sync(ImageWindow *imd, gdouble zoom,
+                            gint force, gint blank, gint new,
+                            gint center_point, gint px, gint py);
+static void image_queue(ImageWindow *imd, gint x, gint y, gint w, gint h,
+			gint clamp, TileRenderType render, gint new_data);
+
+static gint util_clip_region(gint x, gint y, gint w, gint h,
+			     gint clip_x, gint clip_y, gint clip_w, gint clip_h,
+			     gint *rx, gint *ry, gint *rw, gint *rh);
+
+/*
+ *-------------------------------------------------------------------
+ * source tiles
+ *-------------------------------------------------------------------
+ */
+
+typedef struct _SourceTile SourceTile;
+struct _SourceTile
+{
+	gint x;
+	gint y;
+	GdkPixbuf *pixbuf;
+	gint blank;
+};
+
+static void source_tile_free(SourceTile *st)
+{
+	if (!st) return;
+
+	if (st->pixbuf) gdk_pixbuf_unref(st->pixbuf);
+	g_free(st);
+}
+
+static void source_tile_free_all(ImageWindow *imd)
+{
+	GList *work;
+
+	work = imd->source_tiles;
+	while (work)
+		{
+		SourceTile *st = work->data;
+		work = work->next;
+
+		source_tile_free(st);
+		}
+
+	g_list_free(imd->source_tiles);
+	imd->source_tiles = NULL;
+}
+
+static gint source_tile_visible(ImageWindow *imd, SourceTile *st)
+{
+	gint x, y, w, h;
+
+	if (!st) return FALSE;
+
+	x = (imd->x_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE;
+	y = (imd->y_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE;
+	w = ((imd->x_scroll + imd->vis_width) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE;
+	h = ((imd->y_scroll + imd->vis_height) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE;
+
+	return !((double)st->x * imd->scale < (double)x ||
+		 (double)(st->x + imd->source_tile_width) * imd->scale > (double)w ||
+		 (double)st->y * imd->scale < (double)y ||
+		 (double)(st->y + imd->source_tile_height) * imd->scale > (double)h);
+}
+
+static SourceTile *source_tile_new(ImageWindow *imd, gint x, gint y)
+{
+	SourceTile *st = NULL;
+	gint count;
+
+	if (imd->source_tiles_cache_size < 4) imd->source_tiles_cache_size = 4;
+
+	if (imd->source_tile_width < 1 || imd->source_tile_height < 1)
+		{
+		printf("warning: source tile size too small %d x %d\n", imd->source_tile_width, imd->source_tile_height);
+		return NULL;
+		}
+
+	count = g_list_length(imd->source_tiles);
+	if (count >= imd->source_tiles_cache_size)
+		{
+		GList *work;
+
+		work = g_list_last(imd->source_tiles);
+		while (work && count >= imd->source_tiles_cache_size)
+			{
+			SourceTile *needle;
+
+			needle = work->data;
+			work = work->prev;
+
+			if (!source_tile_visible(imd, needle))
+				{
+				imd->source_tiles = g_list_remove(imd->source_tiles, needle);
+
+				if (imd->func_tile_dispose)
+					{
+					if (debug) printf("tile dispose: %d x %d @ %d x %d\n",
+							 needle->x, needle->y, imd->x_scroll, imd->y_scroll);
+					imd->func_tile_dispose(imd, needle->x, needle->y,
+							       imd->source_tile_width, imd->source_tile_height,
+							       needle->pixbuf, imd->data_tile);
+					}
+
+				if (!st)
+					{
+					st = needle;
+					}
+				else
+					{
+					source_tile_free(needle);
+					}
+
+				count--;
+				}
+			else if (debug)
+				{
+				printf("we still think %d x %d is visble\n", needle->x, needle->y);
+				}
+			}
+
+		if (debug)
+			{
+			printf("cache count %d, max is %d\n", count, imd->source_tiles_cache_size);
+			}
+		}
+
+	if (!st)
+		{
+		st = g_new0(SourceTile, 1);
+		st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
+					    imd->source_tile_width, imd->source_tile_height);
+		}
+
+	st->x = (x / imd->source_tile_width) * imd->source_tile_width;
+	st->y = (y / imd->source_tile_height) * imd->source_tile_height;
+	st->blank = TRUE;
+
+	imd->source_tiles = g_list_prepend(imd->source_tiles, st);
+
+	if (debug)
+		{
+		printf("tile request: %d x %d\n", st->x, st->y);
+		if (!source_tile_visible(imd, st)) printf("tile request for invisible tile!\n");
+		}
+
+	return st;
+}
+
+static void image_tile_invalidate(ImageWindow *imd, gint x, gint y, gint w, gint h)
+{
+	gint i, j;
+	gint x1, x2;
+	gint y1, y2;
+	GList *work;
+
+	x1 = (gint)floor(x / imd->tile_width) * imd->tile_width;
+	x2 = (gint)ceil((x + w) / imd->tile_width) * imd->tile_width;
+
+	y1 = (gint)floor(y / imd->tile_height) * imd->tile_height;
+	y2 = (gint)ceil((y + h) / imd->tile_height) * imd->tile_height;
+
+	work = g_list_nth(imd->tiles, y1 / imd->tile_height * imd->tile_cols + (x1 / imd->tile_width));
+	for (j = y1; j <= y2; j += imd->tile_height)
+		{
+		GList *tmp;
+		tmp = work;
+		for (i = x1; i <= x2; i += imd->tile_width)
+			{
+			if (tmp)
+				{
+				ImageTile *it = tmp->data;
+
+				it->render_done = TILE_RENDER_NONE;
+				it->render_todo = TILE_RENDER_ALL;
+
+				tmp = tmp->next;
+				}
+			}
+		work = g_list_nth(work, imd->tile_cols);        /* step 1 row */
+		}
+}
+
+static SourceTile *source_tile_request(ImageWindow *imd, gint x, gint y)
+{
+	SourceTile *st;
+
+	st = source_tile_new(imd, x, y);
+
+	if (imd->func_tile_request &&
+	    imd->func_tile_request(imd, st->x, st->y,
+				   imd->source_tile_width, imd->source_tile_height, st->pixbuf, imd->data_tile))
+		{
+		st->blank = FALSE;
+		}
+#if 0
+	/* fixme: somehow invalidate the new st region */
+	image_queue(imd, st->x, st->y, imd->source_tile_width, imd->source_tile_height, FALSE, TILE_RENDER_AREA, TRUE);
+#endif
+	image_tile_invalidate(imd, st->x * imd->scale, st->y * imd->scale,
+			      imd->source_tile_width * imd->scale, imd->source_tile_height * imd->scale);
+
+	return st;
+}
+
+static SourceTile *source_tile_find(ImageWindow *imd, gint x, gint y)
+{
+	GList *work;
+
+	work = imd->source_tiles;
+	while (work)
+		{
+		SourceTile *st = work->data;
+
+		if (x >= st->x && x < st->x + imd->source_tile_width &&
+		    y >= st->y && y < st->y + imd->source_tile_height)
+			{
+			if (work != imd->source_tiles)
+				{
+				imd->source_tiles = g_list_remove_link(imd->source_tiles, work);
+				imd->source_tiles = g_list_concat(work, imd->source_tiles);
+				}
+			return st;
+			}
+
+		work = work->next;
+		}
+
+	return NULL;
+}
+
+static GList *source_tile_compute_region(ImageWindow *imd, gint x, gint y, gint w, gint h, gint request)
+{
+	gint x1, y1;
+	GList *list = NULL;
+	gint sx, sy;
+
+	if (x < 0) x = 0;
+	if (y < 0) y = 0;
+	if (w > imd->image_width) w = imd->image_width;
+	if (h > imd->image_height) h = imd->image_height;
+
+	sx = (x / imd->source_tile_width) * imd->source_tile_width;
+	sy = (y / imd->source_tile_height) * imd->source_tile_height;
+
+	for (x1 = sx; x1 < x + w; x1+= imd->source_tile_width)
+		{
+		for (y1 = sy; y1 < y + h; y1 += imd->source_tile_height)
+			{
+			SourceTile *st;
+
+			st = source_tile_find(imd, x1, y1);
+			if (!st && request) st = source_tile_request(imd, x1, y1);
+
+			if (st) list = g_list_prepend(list, st);
+			}
+		}
+
+	return g_list_reverse(list);
+}
+
+static void source_tile_changed(ImageWindow *imd, gint x, gint y, gint width, gint height)
+{
+	GList *work;
+
+	work = imd->source_tiles;
+	while (work)
+		{
+		SourceTile *st;
+		gint rx, ry, rw, rh;
+
+		st = work->data;
+		work = work->next;
+
+		if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height,
+				     x, y, width, height,
+				     &rx, &ry, &rw, &rh))
+			{
+			GdkPixbuf *pixbuf;
+
+			pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh);
+			if (imd->func_tile_request &&
+			    imd->func_tile_request(imd, rx, ry, rw, rh, pixbuf, imd->data_tile))
+				{
+				image_tile_invalidate(imd, rx * imd->scale, ry * imd->scale, rw * imd->scale, rh * imd->scale);
+				}
+			g_object_unref(pixbuf);
+			}
+		}
+}
+
+
+static gint source_tile_render(ImageWindow *imd, ImageTile *it,
+			       gint x, gint y, gint w, gint h,
+			       gint new_data, gint fast)
+{
+	GList *list;
+	GList *work;
+	gint draw = FALSE;
+
+	if (imd->zoom == 1.0 || imd->scale == 1.0)
+		{
+		list = source_tile_compute_region(imd, it->x + x, it->y + y, w, h, TRUE);
+		work = list;
+		while (work)
+			{
+			SourceTile *st;
+			gint rx, ry, rw, rh;
+
+			st = work->data;
+			work = work->next;
+
+			if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height,
+					     it->x + x, it->y + y, w, h,
+					     &rx, &ry, &rw, &rh))
+				{
+				if (st->blank)
+					{
+					gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
+							   rx - st->x, ry - st->y, rw, rh);
+					}
+				else /* (imd->zoom == 1.0 || imd->scale == 1.0) */
+					{
+					gdk_draw_pixbuf(it->pixmap,
+							imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)],
+							st->pixbuf,
+							rx - st->x, ry - st->y,
+							rx - it->x, ry - it->y,
+							rw, rh,
+							(GdkRgbDither)dither_quality, rx, ry);
+					}
+				}
+			}
+		}
+	else
+		{
+		double scale_x, scale_y;
+		gint sx, sy, sw, sh;
+
+		if (imd->image_width == 0 || imd->image_height == 0) return FALSE;
+		scale_x = (double)imd->width / imd->image_width;
+		scale_y = (double)imd->height / imd->image_height;
+
+		sx = (double)(it->x + x) / scale_x;
+		sy = (double)(it->y + y) / scale_y;
+		sw = (double)w / scale_x;
+		sh = (double)h / scale_y;
+
+		if (imd->width < IMAGE_MIN_SCALE_SIZE || imd->height < IMAGE_MIN_SCALE_SIZE) fast = TRUE;
+
+#if 0
+		/* draws red over draw region, to check for leaks (regions not filled) */
+		pixbuf_draw_rect(it->pixbuf, x, y, w, h, 255, 0, 0, 255, FALSE);
+#endif
+
+		list = source_tile_compute_region(imd, sx, sy, sw, sh, TRUE);
+		work = list;
+		while (work)
+			{
+			SourceTile *st;
+			gint rx, ry, rw, rh;
+			gint stx, sty, stw, sth;
+
+			st = work->data;
+			work = work->next;
+
+			stx = floor((double)st->x * scale_x);
+			sty = floor((double)st->y * scale_y);
+			stw = ceil ((double)(st->x + imd->source_tile_width) * scale_x) - stx;
+			sth = ceil ((double)(st->y + imd->source_tile_height) * scale_y) - sty;
+
+			if (util_clip_region(stx, sty, stw, sth,
+					     it->x + x, it->y + y, w, h,
+					     &rx, &ry, &rw, &rh))
+				{
+				if (st->blank)
+					{
+					gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
+					   rx - st->x, ry - st->y, rw, rh);
+					}
+				else
+					{
+					double offset_x;
+					double offset_y;
+
+					/* may need to use unfloored stx,sty values here */
+					offset_x = ((double)stx < (double)it->x) ?
+						    (double)stx - (double)it->x  : 0.0;
+					offset_y = ((double)sty < (double)it->y) ?
+						    (double)sty - (double)it->y  : 0.0;
+
+					gdk_pixbuf_scale(st->pixbuf, it->pixbuf, rx - it->x, ry - it->y, rw, rh,
+						 (double) 0.0 + rx - it->x + offset_x,
+						 (double) 0.0 + ry - it->y + offset_y,
+						 scale_x, scale_y,
+						 (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality);
+					draw = TRUE;
+					}
+				}
+			}
+		}
+
+	g_list_free(list);
+
+	return draw;
+}
+
+static void image_source_tile_unset(ImageWindow *imd)
+{
+	source_tile_free_all(imd);
+
+	imd->source_tiles_enabled = FALSE;
+}
+
+void image_set_image_as_tiles(ImageWindow *imd, gint width, gint height,
+			      gint tile_width, gint tile_height, gint cache_size,
+			      ImageTileRequestFunc func_tile_request,
+			      ImageTileDisposeFunc func_tile_dispose,
+			      gpointer data,
+			      gdouble zoom)
+{
+	/* FIXME: unset any current image */
+	image_source_tile_unset(imd);
+
+	if (tile_width < 32 || tile_height < 32)
+		{
+		printf("warning: tile size too small %d x %d (min 32x32)\n", tile_width, tile_height);
+		return;
+		}
+	if (width < 32 || height < 32)
+		{
+		printf("warning: tile canvas too small %d x %d (min 32x32)\n", width, height);
+		return;
+		}
+	if (!func_tile_request)
+		{
+		printf("warning: tile request function is null\n");
+		}
+
+	printf("Setting source tiles to size %d x %d, grid is %d x %d\n", tile_width, tile_height, width, height);
+
+	if (cache_size < 4) cache_size = 4;
+
+	imd->source_tiles_enabled = TRUE;
+	imd->source_tiles_cache_size = cache_size;
+	imd->source_tile_width = tile_width;
+	imd->source_tile_height = tile_height;
+
+	imd->image_width = width;
+	imd->image_height = height;
+
+	imd->func_tile_request = func_tile_request;
+	imd->func_tile_dispose = func_tile_dispose;
+	imd->data_tile = data;
+
+	image_zoom_sync(imd, zoom, TRUE, FALSE, TRUE, FALSE, 0, 0);
+}
+
 
 static void image_queue_clear(ImageWindow *imd);
 
@@ -210,9 +672,25 @@
 
 static void image_tile_cache_free_space(ImageWindow *imd, gint space, ImageTile *it)
 {
-	GList *work = g_list_last(imd->tile_cache);
-
-	while (work && imd->tile_cache_size > 0 && imd->tile_cache_size + space > tile_cache_max * 1048576)
+	GList *work;
+	gint tile_max;
+
+	work = g_list_last(imd->tile_cache);
+
+	if (imd->source_tiles_enabled && imd->scale < 1.0)
+		{
+		gint tiles;
+
+		tiles = (imd->vis_width / IMAGE_TILE_SIZE + 1) * (imd->vis_width / IMAGE_TILE_SIZE + 1);
+		tile_max = MAX(tiles * IMAGE_TILE_SIZE * IMAGE_TILE_SIZE * 3,
+			       (gint)((double)tile_cache_max * 1048576.0 * imd->scale));
+		}
+	else
+		{
+		tile_max = tile_cache_max * 1048576;
+		}
+
+	while (work && imd->tile_cache_size > 0 && imd->tile_cache_size + space > tile_max)
 		{
 		CacheData *cd = work->data;
 		work = work->prev;
@@ -252,16 +730,23 @@
 		image_tile_cache_add(imd, it, pixmap, NULL, size);
 		}
 	
-	if ((imd->zoom != 1.0 || gdk_pixbuf_get_has_alpha(imd->pixbuf)) &&
+	if ((imd->zoom != 1.0 || imd->source_tiles_enabled || (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf)) ) &&
 	    !it->pixbuf)
 		{
 		GdkPixbuf *pixbuf;
 		guint size;
 
-		pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(imd->pixbuf),
-					gdk_pixbuf_get_has_alpha(imd->pixbuf),
-					gdk_pixbuf_get_bits_per_sample(imd->pixbuf),
-					imd->tile_width, imd->tile_height);
+		if (imd->pixbuf)
+			{
+			pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(imd->pixbuf),
+						gdk_pixbuf_get_has_alpha(imd->pixbuf),
+						gdk_pixbuf_get_bits_per_sample(imd->pixbuf),
+						imd->tile_width, imd->tile_height);
+			}
+		else
+			{
+			 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, imd->tile_width, imd->tile_height);
+			}
 
 		size = gdk_pixbuf_get_rowstride(pixbuf) * imd->tile_height;
 		image_tile_cache_free_space(imd, size, it);
@@ -396,7 +881,7 @@
 	gint has_alpha;
 	gint draw = FALSE;
 
-	if (it->render_todo == TILE_RENDER_NONE && it->pixmap) return;
+	if (it->render_todo == TILE_RENDER_NONE && it->pixmap && !new_data) return;
 
 	if (it->render_done != TILE_RENDER_ALL)
 		{
@@ -417,7 +902,7 @@
 	if (new_data) it->blank = FALSE;
 
 	image_tile_prepare(imd, it);
-	has_alpha = gdk_pixbuf_get_has_alpha(imd->pixbuf);
+	has_alpha = (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf));
 
 	/* FIXME checker colors for alpha should be configurable,
 	 * also should be drawn for blank = TRUE
@@ -429,6 +914,10 @@
 		gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE,
 				   0, 0, it->w, it->h);
 		}
+	else if (imd->source_tiles_enabled)
+		{
+		draw = source_tile_render(imd, it, x, y, w, h, new_data, fast);
+		}
 	else if (imd->zoom == 1.0 || imd->scale == 1.0)
 		{
 		if (has_alpha)
@@ -536,7 +1025,7 @@
 	QueueData *qd;
 	gint fast;
 
-	if (!imd->pixbuf || (!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1)
+	if ((!imd->pixbuf && !imd->source_tiles_enabled) || (!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1)
 		{
 		if (!imd->completed) image_complete_util(imd, FALSE);
 
@@ -862,7 +1351,7 @@
 
 	if (!imd->image->window) return;
 
-	if (!imd->pixbuf)
+	if (!imd->pixbuf && !imd->source_tiles_enabled)
 		{
 		if (util_clip_region(x, y, w, h,
 			0, 0,
@@ -924,6 +1413,19 @@
 	image_border_draw(imd, 0, 0, imd->window_width, imd->window_height);
 }
 
+static void image_scroll_notify(ImageWindow *imd)
+{
+	if (imd->func_scroll_notify && imd->scale)
+		{
+		imd->func_scroll_notify(imd,
+					(gint)((gdouble)imd->x_scroll / imd->scale),
+					(gint)((gdouble)imd->y_scroll / imd->scale),
+					(gint)((gdouble)imd->image_width - imd->vis_width / imd->scale),
+					(gint)((gdouble)imd->image_height - imd->vis_height / imd->scale),
+					imd->data_scroll_notify);
+		}
+}
+
 static gint image_scroll_clamp(ImageWindow *imd)
 {
 	gint old_xs;
@@ -933,6 +1435,8 @@
 		{
 		imd->x_scroll = 0;
 		imd->y_scroll = 0;
+
+		image_scroll_notify(imd);
 		return FALSE;
 		}
 
@@ -957,6 +1461,8 @@
 		imd->y_scroll = CLAMP(imd->y_scroll, 0, imd->height - imd->vis_height);
 		}
 
+	image_scroll_notify(imd);
+
 	return (old_xs != imd->x_scroll || old_ys != imd->y_scroll);
 }
 
@@ -965,7 +1471,7 @@
 	gint w, h;
 	gdouble scale;
 
-	zoom = CLAMP(zoom, IMAGE_ZOOM_MIN, IMAGE_ZOOM_MAX);
+	zoom = CLAMP(zoom, imd->zoom_min, imd->zoom_max);
 
 	if (imd->zoom == zoom && !force) return FALSE;
 
@@ -1090,7 +1596,9 @@
 	image_size_clamp(imd);
 	image_scroll_clamp(imd);
 
+#if 0
 	gtk_widget_set_size_request(imd->image, imd->window_width, imd->window_height);
+#endif
 
 	/* ensure scroller remains visible */
 	if (imd->scroller_overlay != -1)
@@ -1211,7 +1719,7 @@
 	gint x_off, y_off;
 	gint w, h;
 
-	if (!imd->pixbuf) return;
+	if (!imd->pixbuf && !imd->source_tiles_enabled) return;
 
 	old_x = imd->x_scroll;
 	old_y = imd->y_scroll;
@@ -1952,6 +2460,8 @@
 {
 	gint sync = TRUE;
 
+	image_source_tile_unset(imd);
+
 	imd->zoom = zoom;	/* store the zoom, needed by the loader */
 
 	image_reset(imd);
@@ -2696,6 +3206,14 @@
 	imd->data_scroll = data;
 }
 
+void image_set_scroll_notify_func(ImageWindow *imd,
+				  void (*func)(ImageWindow *imd, gint x, gint y, gint width, gint height, gpointer data),
+				  gpointer data)
+{
+	imd->func_scroll_notify = func;
+	imd->data_scroll_notify = data;
+}
+
 /* path, name */
 
 const gchar *image_get_path(ImageWindow *imd)
@@ -2726,11 +3244,15 @@
 	if (imd->image_path == path ||
 	    (path && imd->image_path && !strcmp(path, imd->image_path)) ) return;
 
+	image_source_tile_unset(imd);
+
 	image_change_real(imd, path, NULL, NULL, zoom);
 }
 
 void image_change_pixbuf(ImageWindow *imd, GdkPixbuf *pixbuf, gdouble zoom)
 {
+	image_source_tile_unset(imd);
+
 	image_set_pixbuf(imd, pixbuf, zoom, TRUE);
 	image_new_util(imd);
 }
@@ -2739,6 +3261,8 @@
 {
 	if (!cd || !info || !g_list_find(cd->list, info)) return;
 
+	image_source_tile_unset(imd);
+
 	image_change_real(imd, info->path, cd, info, zoom);
 }
 
@@ -2778,6 +3302,9 @@
 {
 	if (imd == source) return;
 
+	imd->zoom_min = source->zoom_min;
+	imd->zoom_max = source->zoom_max;
+
 	imd->unknown = source->unknown;
 
 	image_set_pixbuf(imd, source->pixbuf, image_zoom_get(source), TRUE);
@@ -2829,6 +3356,43 @@
 	imd->x_scroll = source->x_scroll;
 	imd->y_scroll = source->y_scroll;
 
+	if (imd->source_tiles_enabled)
+		{
+		image_source_tile_unset(imd);
+		}
+
+	if (source->source_tiles_enabled)
+		{
+		imd->source_tiles_enabled = source->source_tiles_enabled;
+		imd->source_tiles_cache_size = source->source_tiles_cache_size;
+		imd->source_tiles = source->source_tiles;
+		imd->source_tile_width = source->source_tile_width;
+		imd->source_tile_height = source->source_tile_height;
+
+		source->source_tiles_enabled = FALSE;
+		source->source_tiles = NULL;
+
+		imd->func_tile_request = source->func_tile_request;
+		imd->func_tile_dispose = source->func_tile_dispose;
+		imd->data_tile = source->data_tile;
+
+		source->func_tile_request = NULL;
+		source->func_tile_dispose = NULL;
+		source->data_tile = NULL;
+
+		imd->image_width = source->image_width;
+		imd->image_height = source->image_height;
+
+		if (image_zoom_clamp(imd, source->zoom, TRUE, TRUE))
+			{
+			image_size_clamp(imd);
+			image_scroll_clamp(imd);
+			image_tile_sync(imd, imd->width, imd->height, FALSE);
+			image_redraw(imd, FALSE);
+			}
+		return;
+		}
+
 	image_scroll_clamp(imd);
 }
 
@@ -2843,11 +3407,18 @@
 	sw = (gint)ceil((double)width * imd->scale);
 	sh = (gint)ceil((double)height * imd->scale);
 
+	if (imd->source_tiles_enabled)
+		{
+		source_tile_changed(imd, x, y, width, height);
+		}
+
 	image_queue(imd, sx, sy, sw, sh, FALSE, TILE_RENDER_AREA, TRUE);
 }
 
 void image_reload(ImageWindow *imd)
 {
+	if (imd->source_tiles_enabled) return;
+
 	image_change_complete(imd, imd->zoom, FALSE);
 }
 
@@ -2856,8 +3427,20 @@
 	image_scroll_real(imd, x, y);
 }
 
+void image_scroll_to_point(ImageWindow *imd, gint x, gint y)
+{
+	gint px, py;
+
+	px = (gdouble)x * imd->scale - imd->x_scroll;
+	py = (gdouble)y * imd->scale - imd->y_scroll;
+
+	image_scroll(imd, px, py);
+}
+
 void image_alter(ImageWindow *imd, AlterType type)
 {
+	if (imd->source_tiles_enabled) return;
+
 	if (imd->il)
 		{
 		/* still loading, wait till done */
@@ -2925,6 +3508,16 @@
 	image_zoom_adjust_real(imd, increment, TRUE, x, y);
 }
 
+void image_zoom_set_limits(ImageWindow *imd, gdouble min, gdouble max)
+{
+	if (min > 1.0 || max < 1.0) return;
+	if (min < 1.0 && min > -1.0) return;
+	if (min < -200.0 || max > 200.0) return;
+
+	imd->zoom_min = min;
+	imd->zoom_max = max;
+}
+
 void image_zoom_set(ImageWindow *imd, gdouble zoom)
 {
 	image_zoom_sync(imd, zoom, FALSE, FALSE, FALSE, FALSE, 0, 0);
@@ -3029,6 +3622,8 @@
 
 void image_prebuffer_set(ImageWindow *imd, const gchar *path)
 {
+	if (imd->source_tiles_enabled) return;
+
 	if (path)
 		{
 		image_read_ahead_set(imd, path);
@@ -3063,6 +3658,7 @@
 void image_auto_refresh(ImageWindow *imd, gint interval)
 {
 	if (!imd) return;
+	if (imd->source_tiles_enabled) return;
 
 	if (imd->auto_refresh_id > -1)
 		{
@@ -3162,7 +3758,6 @@
 
 	if (!imd || !imd->pixbuf) return;
 
-
 	screen = gtk_widget_get_screen(imd->image);
 	rootwindow = gdk_screen_get_root_window(screen);
 	if (gdk_drawable_get_visual(rootwindow) != gdk_visual_get_system()) return;
@@ -3210,6 +3805,8 @@
 
 	image_overlay_list_clear(imd);
 
+	source_tile_free_all(imd);
+
 	g_free(imd);
 }
 
@@ -3224,6 +3821,9 @@
 	ImageWindow *imd;
 
 	imd = g_new0(ImageWindow, 1);
+
+	imd->zoom_min = IMAGE_ZOOM_MIN;
+	imd->zoom_max = IMAGE_ZOOM_MAX;
 	imd->zoom = 1.0;
 	imd->scale = 1.0;
 
@@ -3262,6 +3862,8 @@
 
 	imd->func_update = NULL;
 	imd->func_complete = NULL;
+	imd->func_tile_request = NULL;
+	imd->func_tile_dispose = NULL;
 
 	imd->func_button = NULL;
 	imd->func_scroll = NULL;
@@ -3269,6 +3871,9 @@
 	imd->scroller_id = -1;
 	imd->scroller_overlay = -1;
 
+	imd->source_tiles_enabled = FALSE;
+	imd->source_tiles = NULL;
+
 	imd->image = gtk_drawing_area_new();
 	gtk_widget_set_double_buffered(imd->image, FALSE);