/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*- * * Copyright © 2007 Björn Lindqvist * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "image_view_drawer.h" #include "utils.h" #include static gboolean gdk_rectangle_contains_rect (GdkRectangle r1, GdkRectangle r2) { return r1.x <= r2.x && r1.y <= r2.y && (r2.x + r2.width) <= (r1.x + r1.width) && (r2.y + r2.height) <= (r1.y + r1.height); } static void gdk_pixbuf_copy_area_intact (GdkPixbuf *src, int src_x, int src_y, int width, int height, GdkPixbuf *dst, int dst_x, int dst_y) { if (src_x == dst_x && src_y == dst_y && src == dst) return; int src_stride = gdk_pixbuf_get_rowstride (src); int dst_stride = gdk_pixbuf_get_rowstride (dst); int chans = gdk_pixbuf_get_n_channels (src); int linelen = width * chans; guchar *src_base = gdk_pixbuf_get_pixels (src); guchar *dst_base = gdk_pixbuf_get_pixels (dst); int src_y_ofs = src_y * src_stride; int dst_y_ofs = dst_y * dst_stride; if (dst_y > src_y) { src_y_ofs = (src_y + height - 1) * src_stride; dst_y_ofs = (dst_y + height - 1) * dst_stride; src_stride = -src_stride; dst_stride = -dst_stride; } guchar *src_ofs = src_base + src_y_ofs + src_x * chans; guchar *dst_ofs = dst_base + dst_y_ofs + dst_x * chans; void (*copy_func)(void *, void *, size_t) = (void *) memcpy; if (dst_x > src_x) copy_func = (void *) memmove; for (int y = 0; y < height; y++) { copy_func (dst_ofs, src_ofs, linelen); src_ofs += src_stride; dst_ofs += dst_stride; } } /** * draw_settings_get_method: * @old: The draw settings used in the last draw operation. * @new_: The draw settings used in the current draw operation. * @last_pixbuf: The pixbuf to be drawn. * * Returns which method that needs to be carried out in this draw. **/ DrawMethod draw_settings_get_method (DrawSettings *old, DrawSettings *new_, GdkPixbuf *last_pixbuf) { if (new_->zoom != old->zoom || new_->interp != old->interp || new_->check_color1 != old->check_color1 || new_->check_color2 != old->check_color2 || new_->pixbuf != old->pixbuf) return DRAW_METHOD_SCALE; if (gdk_rectangle_contains_rect (old->zoom_rect, new_->zoom_rect)) return DRAW_METHOD_CONTAINS; return DRAW_METHOD_SCROLL; } ImageViewDrawer * image_view_drawer_new () { ImageViewDrawer *drawer = g_new0 (ImageViewDrawer, 1); drawer->last_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 1, 1); drawer->check_size = 16; drawer->old = (DrawSettings){0, {0, 0, 0, 0}, 0, 0, GDK_INTERP_NEAREST, drawer->last_pixbuf, 0, 0}; return drawer; } void image_view_drawer_free (ImageViewDrawer *drawer) { g_object_unref (drawer->last_pixbuf); g_free (drawer); } /** * image_view_drawer_force_scale: * * This method forces the drawer to perform a %DRAW_FLAGS_SCALE * operation the next time image_view_drawer_draw() is called. * * ImageViewDrawer tries to minimize the number of scale operations * needed by caching the last drawn pixbuf. It would be inefficient to * check the individual pixels inside the pixbuf so it assumes that if * the memory address of the pixbuf has not changed, then the cache is * good to use. * * Use this method to override that assumption. **/ void image_view_drawer_force_scale (ImageViewDrawer *drawer) { /* Set the cached zoom to a bogus value, to force a DRAW_FLAGS_SCALE but not a DRAW_FLAGS_ALLOCATE. */ drawer->old.zoom = -1234.0; } static GdkPixbuf * image_view_drawer_scroll_intersection (GdkPixbuf *pixbuf, int new_width, int new_height, int src_x, int src_y, int inter_width, int inter_height, int dst_x, int dst_y) { int last_width = gdk_pixbuf_get_width (pixbuf); int last_height = gdk_pixbuf_get_height (pixbuf); int width = MAX (last_width, new_width); int height = MAX (last_height, new_height); if (width > last_width || height > last_height) { GdkColorspace cs = gdk_pixbuf_get_colorspace (pixbuf); int bps = gdk_pixbuf_get_bits_per_sample (pixbuf); gboolean alpha = gdk_pixbuf_get_has_alpha (pixbuf); GdkPixbuf *tmp = gdk_pixbuf_new (cs, alpha, bps, width, height); gdk_pixbuf_copy_area_intact (pixbuf, src_x, src_y, inter_width, inter_height, tmp, dst_x, dst_y); g_object_unref (pixbuf); return tmp; } gdk_pixbuf_copy_area_intact (pixbuf, src_x, src_y, inter_width, inter_height, pixbuf, dst_x, dst_y); return pixbuf; } /** * image_view_drawer_intersect_draw: * * Updates the cache by first scrolling the still valid area in the * cache. Then the newly exposed areas in the cache is sampled from * the pixbuf. **/ static void image_view_drawer_intersect_draw (ImageViewDrawer *drawer, DrawSettings *ds, GdkDrawable *drawable) { GdkRectangle this = ds->zoom_rect; GdkRectangle cache = drawer->old.zoom_rect; /* If there is no intersection, we have to scale the whole area from the source pixbuf. */ GdkRectangle inter; GdkRectangle around[4] = { this, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0} }; if (gdk_rectangle_intersect (&cache, &this, &inter)) gdk_rectangle_get_rects_around (&this, &inter, around); drawer->last_pixbuf = image_view_drawer_scroll_intersection (drawer->last_pixbuf, this.width, this.height, inter.x - cache.x, inter.y - cache.y, inter.width, inter.height, around[1].width, around[0].height); for (int n = 0; n < 4; n++) { if (!around[n].width || !around[n].height) continue; gdk_pixbuf_scale_blend (ds->pixbuf, drawer->last_pixbuf, around[n].x - this.x, around[n].y - this.y, around[n].width, around[n].height, -this.x, -this.y, ds->zoom, ds->interp, around[n].x, around[n].y, drawer->check_size, ds->check_color1, ds->check_color2); } } /** * image_view_drawer_draw: * @drawer: An #ImageViewDrawer. * @ds: The #DrawSettings to use for this redraw. **/ void image_view_drawer_draw (ImageViewDrawer *drawer, DrawSettings *ds, GdkDrawable *drawable) { GdkRectangle this = ds->zoom_rect; DrawMethod method = draw_settings_get_method (&drawer->old, ds, drawer->last_pixbuf); int deltax = 0; int deltay = 0; if (method == DRAW_METHOD_CONTAINS) { deltax = this.x - drawer->old.zoom_rect.x; deltay = this.y - drawer->old.zoom_rect.y; } else if (method == DRAW_METHOD_SCROLL) { image_view_drawer_intersect_draw (drawer, ds, drawable); } else if (method == DRAW_METHOD_SCALE) { int last_width = gdk_pixbuf_get_width (drawer->last_pixbuf); int last_height = gdk_pixbuf_get_height (drawer->last_pixbuf); GdkColorspace new_cs = gdk_pixbuf_get_colorspace (ds->pixbuf); GdkColorspace last_cs = gdk_pixbuf_get_colorspace (drawer->last_pixbuf); int new_bps = gdk_pixbuf_get_bits_per_sample (ds->pixbuf); int last_bps = gdk_pixbuf_get_bits_per_sample (drawer->last_pixbuf); if (this.width > last_width || this.height > last_height || new_cs != last_cs || new_bps != last_bps) { g_object_unref (drawer->last_pixbuf); drawer->last_pixbuf = gdk_pixbuf_new (new_cs, FALSE, new_bps, this.width, this.height); } gdk_pixbuf_scale_blend (ds->pixbuf, drawer->last_pixbuf, 0, 0, this.width, this.height, (double) -this.x, (double) -this.y, ds->zoom, ds->interp, this.x, this.y, drawer->check_size, ds->check_color1, ds->check_color2); } gdk_draw_pixbuf (drawable, NULL, drawer->last_pixbuf, deltax, deltay, ds->widget_x, ds->widget_y, this.width, this.height, GDK_RGB_DITHER_MAX, ds->widget_x, ds->widget_y); if (method != DRAW_METHOD_CONTAINS) drawer->old = *ds; }