1 /*
2 * Copyright © 2009-2018 Siyan Panayotov <contact@siyanpanayotov.com>
3 *
4 * Based on code by (see README for details):
5 * - Björn Lindqvist <bjourne@gmail.com>
6 *
7 * This file is part of Viewnior.
8 *
9 * Viewnior is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * Viewnior is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with Viewnior. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "uni-cache.h"
24 #include "uni-utils.h"
25 #include <string.h>
26
27 static gboolean
uni_rectangle_contains_rect(GdkRectangle r1,GdkRectangle r2)28 uni_rectangle_contains_rect (GdkRectangle r1, GdkRectangle r2)
29 {
30 return
31 r1.x <= r2.x &&
32 r1.y <= r2.y &&
33 (r2.x + r2.width) <= (r1.x + r1.width) &&
34 (r2.y + r2.height) <= (r1.y + r1.height);
35 }
36
37 static void
uni_pixbuf_copy_area_intact(GdkPixbuf * src,int src_x,int src_y,int width,int height,GdkPixbuf * dst,int dst_x,int dst_y)38 uni_pixbuf_copy_area_intact (GdkPixbuf * src,
39 int src_x,
40 int src_y,
41 int width,
42 int height,
43 GdkPixbuf * dst, int dst_x, int dst_y)
44 {
45 int y;
46 if (src_x == dst_x && src_y == dst_y && src == dst)
47 return;
48
49 int src_stride = gdk_pixbuf_get_rowstride (src);
50 int dst_stride = gdk_pixbuf_get_rowstride (dst);
51 int chans = gdk_pixbuf_get_n_channels (src);
52
53 int linelen = width * chans;
54
55 guchar *src_base = gdk_pixbuf_get_pixels (src);
56 guchar *dst_base = gdk_pixbuf_get_pixels (dst);
57
58 int src_y_ofs = src_y * src_stride;
59 int dst_y_ofs = dst_y * dst_stride;
60 if (dst_y > src_y)
61 {
62 src_y_ofs = (src_y + height - 1) * src_stride;
63 dst_y_ofs = (dst_y + height - 1) * dst_stride;
64 src_stride = -src_stride;
65 dst_stride = -dst_stride;
66 }
67 guchar *src_ofs = src_base + src_y_ofs + src_x * chans;
68 guchar *dst_ofs = dst_base + dst_y_ofs + dst_x * chans;
69
70 void (*copy_func) (void *, void *, size_t) = (void *) memcpy;
71 if (dst_x > src_x)
72 copy_func = (void *) memmove;
73
74 for (y = 0; y < height; y++)
75 {
76 copy_func (dst_ofs, src_ofs, linelen);
77 src_ofs += src_stride;
78 dst_ofs += dst_stride;
79 }
80 }
81
82 /**
83 * uni_pixbuf_draw_cache_get_method:
84 * @old: the last draw options used
85 * @new_: the current draw options
86 * @returns: the best draw method to use to draw
87 *
88 * Gets the fastest method to draw the specified draw options.
89 * @last_opts is assumed to be the last #PixbufDrawOpts used and
90 * @new_opts is the one to use this time.
91 **/
92 UniPixbufDrawMethod
uni_pixbuf_draw_cache_get_method(UniPixbufDrawOpts * old,UniPixbufDrawOpts * new_)93 uni_pixbuf_draw_cache_get_method (UniPixbufDrawOpts * old,
94 UniPixbufDrawOpts * new_)
95 {
96 if (new_->zoom != old->zoom ||
97 new_->interp != old->interp || new_->pixbuf != old->pixbuf)
98 {
99 return UNI_PIXBUF_DRAW_METHOD_SCALE;
100 }
101 else if (uni_rectangle_contains_rect (old->zoom_rect, new_->zoom_rect))
102 {
103 return UNI_PIXBUF_DRAW_METHOD_CONTAINS;
104 }
105 else
106 {
107 return UNI_PIXBUF_DRAW_METHOD_SCROLL;
108 }
109 }
110
111 /**
112 * uni_pixbuf_draw_cache_new:
113 * @returns: a new #UniPixbufDrawCache
114 *
115 * Creates a new pixbuf draw cache.
116 **/
117 UniPixbufDrawCache *
uni_pixbuf_draw_cache_new()118 uni_pixbuf_draw_cache_new ()
119 {
120 UniPixbufDrawCache *cache = g_new0 (UniPixbufDrawCache, 1);
121 cache->last_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
122 cache->check_size = 16;
123 cache->old = (UniPixbufDrawOpts)
124 {
125 0,
126 {
127 0, 0, 0, 0}
128 , 0, 0, GDK_INTERP_NEAREST, cache->last_pixbuf};
129 return cache;
130 }
131
132 /**
133 * uni_pixbuf_draw_cache_free:
134 * @cache: a #UniPixbufDrawCache
135 *
136 * Deallocates a pixbuf draw cache and all its data.
137 **/
138 void
uni_pixbuf_draw_cache_free(UniPixbufDrawCache * cache)139 uni_pixbuf_draw_cache_free (UniPixbufDrawCache * cache)
140 {
141 g_object_unref (cache->last_pixbuf);
142 g_free (cache);
143 }
144
145 /**
146 * uni_pixbuf_draw_cache_invalidate:
147 * @cache: a #UniPixbufDrawCache
148 *
149 * Force the pixbuf draw cache to scale the pixbuf at the next draw.
150 *
151 * UniPixbufDrawCache tries to minimize the number of scale operations
152 * needed by caching the last drawn pixbuf. It would be inefficient to
153 * check the individual pixels inside the pixbuf so it assumes that if
154 * the memory address of the pixbuf has not changed, then the cache is
155 * good to use.
156 *
157 * However, when the image data is modified, this assumtion breaks,
158 * which is why this method must be used to tell draw cache about it.
159 **/
160 void
uni_pixbuf_draw_cache_invalidate(UniPixbufDrawCache * cache)161 uni_pixbuf_draw_cache_invalidate (UniPixbufDrawCache * cache)
162 {
163 /* Set the cached zoom to a bogus value, to force a
164 DRAW_FLAGS_SCALE. */
165 cache->old.zoom = -1234.0;
166 }
167
168 static GdkPixbuf *
uni_pixbuf_draw_cache_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)169 uni_pixbuf_draw_cache_scroll_intersection (GdkPixbuf * pixbuf,
170 int new_width,
171 int new_height,
172 int src_x,
173 int src_y,
174 int inter_width,
175 int inter_height,
176 int dst_x, int dst_y)
177 {
178 int last_width = gdk_pixbuf_get_width (pixbuf);
179 int last_height = gdk_pixbuf_get_height (pixbuf);
180
181 int width = MAX (last_width, new_width);
182 int height = MAX (last_height, new_height);
183 if (width > last_width || height > last_height)
184 {
185 GdkColorspace cs = gdk_pixbuf_get_colorspace (pixbuf);
186 int bps = gdk_pixbuf_get_bits_per_sample (pixbuf);
187 gboolean alpha = gdk_pixbuf_get_has_alpha (pixbuf);
188 GdkPixbuf *tmp = gdk_pixbuf_new (cs, alpha, bps, width, height);
189
190 uni_pixbuf_copy_area_intact (pixbuf,
191 src_x, src_y,
192 inter_width, inter_height,
193 tmp, dst_x, dst_y);
194 g_object_unref (pixbuf);
195 return tmp;
196 }
197 uni_pixbuf_copy_area_intact (pixbuf,
198 src_x, src_y,
199 inter_width, inter_height,
200 pixbuf, dst_x, dst_y);
201 return pixbuf;
202 }
203
204 /**
205 * uni_pixbuf_draw_cache_intersect_draw:
206 *
207 * Updates the cache by first scrolling the still valid area in the
208 * cache. Then the newly exposed areas in the cache is sampled from
209 * the pixbuf.
210 **/
211 static void
uni_pixbuf_draw_cache_intersect_draw(UniPixbufDrawCache * cache,UniPixbufDrawOpts * opts,GdkDrawable * drawable)212 uni_pixbuf_draw_cache_intersect_draw (UniPixbufDrawCache * cache,
213 UniPixbufDrawOpts * opts,
214 GdkDrawable * drawable)
215 {
216 GdkRectangle this = opts->zoom_rect;
217 GdkRectangle old_rect = cache->old.zoom_rect;
218 int n;
219
220 /* If there is no intersection, we have to scale the whole area
221 from the source pixbuf. */
222 GdkRectangle inter;
223 GdkRectangle around[4] = {
224 this,
225 {0, 0, 0, 0},
226 {0, 0, 0, 0},
227 {0, 0, 0, 0}
228 };
229 if (gdk_rectangle_intersect (&old_rect, &this, &inter))
230 uni_rectangle_get_rects_around (&this, &inter, around);
231
232 cache->last_pixbuf =
233 uni_pixbuf_draw_cache_scroll_intersection (cache->last_pixbuf,
234 this.width,
235 this.height,
236 inter.x - old_rect.x,
237 inter.y - old_rect.y,
238 inter.width,
239 inter.height,
240 around[1].width,
241 around[0].height);
242
243 for (n = 0; n < 4; n++)
244 {
245 if (!around[n].width || !around[n].height)
246 continue;
247 uni_pixbuf_scale_blend (opts->pixbuf,
248 cache->last_pixbuf,
249 around[n].x - this.x,
250 around[n].y - this.y,
251 around[n].width, around[n].height,
252 -this.x, -this.y,
253 opts->zoom,
254 opts->interp, around[n].x, around[n].y);
255 }
256 }
257
258 /**
259 * uni_pixbuf_draw_cache_draw:
260 * @cache: a #UniPixbufDrawCache
261 * @opts: the #UniPixbufDrawOpts to use in this draw
262 * @drawable: a #GdkDrawable to draw on
263 *
264 * Redraws the area specified in the pixbuf draw options in an
265 * efficient way by using caching.
266 **/
267 void
uni_pixbuf_draw_cache_draw(UniPixbufDrawCache * cache,UniPixbufDrawOpts * opts,GdkDrawable * drawable)268 uni_pixbuf_draw_cache_draw (UniPixbufDrawCache * cache,
269 UniPixbufDrawOpts * opts, GdkDrawable * drawable)
270 {
271 GdkRectangle this = opts->zoom_rect;
272 UniPixbufDrawMethod method =
273 uni_pixbuf_draw_cache_get_method (&cache->old, opts);
274 int deltax = 0;
275 int deltay = 0;
276 if (method == UNI_PIXBUF_DRAW_METHOD_CONTAINS)
277 {
278 deltax = this.x - cache->old.zoom_rect.x;
279 deltay = this.y - cache->old.zoom_rect.y;
280 }
281 else if (method == UNI_PIXBUF_DRAW_METHOD_SCROLL)
282 {
283 uni_pixbuf_draw_cache_intersect_draw (cache, opts, drawable);
284 }
285 else if (method == UNI_PIXBUF_DRAW_METHOD_SCALE)
286 {
287 int last_width = gdk_pixbuf_get_width (cache->last_pixbuf);
288 int last_height = gdk_pixbuf_get_height (cache->last_pixbuf);
289 GdkColorspace new_cs = gdk_pixbuf_get_colorspace (opts->pixbuf);
290 GdkColorspace last_cs =
291 gdk_pixbuf_get_colorspace (cache->last_pixbuf);
292 int new_bps = gdk_pixbuf_get_bits_per_sample (opts->pixbuf);
293 int last_bps = gdk_pixbuf_get_bits_per_sample (cache->last_pixbuf);
294
295 if (this.width > last_width || this.height > last_height ||
296 new_cs != last_cs || new_bps != last_bps)
297 {
298 g_object_unref (cache->last_pixbuf);
299 cache->last_pixbuf = gdk_pixbuf_new (new_cs, FALSE, new_bps,
300 this.width, this.height);
301 }
302
303 uni_pixbuf_scale_blend (opts->pixbuf,
304 cache->last_pixbuf,
305 0, 0,
306 this.width, this.height,
307 (double) -this.x, (double) -this.y,
308 opts->zoom, opts->interp, this.x, this.y);
309 }
310 gdk_draw_pixbuf (drawable,
311 NULL,
312 cache->last_pixbuf,
313 deltax, deltay,
314 opts->widget_x, opts->widget_y,
315 this.width, this.height,
316 GDK_RGB_DITHER_MAX, opts->widget_x, opts->widget_y);
317 if (method != UNI_PIXBUF_DRAW_METHOD_CONTAINS)
318 cache->old = *opts;
319 }
320