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