1 /*-
2  * Copyright (c) 2004-2007 os-cillation e.K.
3  * Copyright (c) 2003      Red Hat, Inc.
4  *
5  * Written by Benedikt Meurer <benny@xfce.org>.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <terminal/terminal-image-loader.h>
26 #include <terminal/terminal-private.h>
27 
28 /* max image resolution is 8K */
29 #define MAX_IMAGE_WIDTH  7680
30 #define MAX_IMAGE_HEIGHT 4320
31 
32 
33 
34 static void terminal_image_loader_finalize (GObject             *object);
35 static void terminal_image_loader_check    (TerminalImageLoader *loader);
36 static void terminal_image_loader_tile     (TerminalImageLoader *loader,
37                                             GdkPixbuf           *target,
38                                             gint                 width,
39                                             gint                 height);
40 static void terminal_image_loader_center   (TerminalImageLoader *loader,
41                                             GdkPixbuf           *target,
42                                             gint                 width,
43                                             gint                 height);
44 static void terminal_image_loader_scale    (TerminalImageLoader *loader,
45                                             GdkPixbuf           *target,
46                                             gint                 width,
47                                             gint                 height);
48 static void terminal_image_loader_stretch  (TerminalImageLoader *loader,
49                                             GdkPixbuf           *target,
50                                             gint                 width,
51                                             gint                 height);
52 
53 
54 struct _TerminalImageLoaderClass
55 {
56   GObjectClass parent_class;
57 };
58 
59 struct _TerminalImageLoader
60 {
61   GObject                  parent_instance;
62   TerminalPreferences     *preferences;
63 
64   /* the cached image data */
65   gchar                   *path;
66   GSList                  *cache;
67   GSList                  *cache_invalid;
68   GdkRGBA                  bgcolor;
69   GdkPixbuf               *pixbuf;
70   TerminalBackgroundStyle  style;
71 };
72 
73 
74 
G_DEFINE_TYPE(TerminalImageLoader,terminal_image_loader,G_TYPE_OBJECT)75 G_DEFINE_TYPE (TerminalImageLoader, terminal_image_loader, G_TYPE_OBJECT)
76 
77 
78 
79 static void
80 terminal_image_loader_class_init (TerminalImageLoaderClass *klass)
81 {
82   GObjectClass *gobject_class;
83 
84   gobject_class = G_OBJECT_CLASS (klass);
85   gobject_class->finalize = terminal_image_loader_finalize;
86 }
87 
88 
89 
90 static void
terminal_image_loader_init(TerminalImageLoader * loader)91 terminal_image_loader_init (TerminalImageLoader *loader)
92 {
93   loader->preferences = terminal_preferences_get ();
94 }
95 
96 
97 
98 static void
terminal_image_loader_finalize(GObject * object)99 terminal_image_loader_finalize (GObject *object)
100 {
101   TerminalImageLoader *loader = TERMINAL_IMAGE_LOADER (object);
102 
103   g_slist_free_full (loader->cache, g_object_unref);
104   g_slist_free_full (loader->cache_invalid, g_object_unref);
105 
106   g_object_unref (G_OBJECT (loader->preferences));
107 
108   if (G_LIKELY (loader->pixbuf != NULL))
109     g_object_unref (G_OBJECT (loader->pixbuf));
110   g_free (loader->path);
111 
112   (*G_OBJECT_CLASS (terminal_image_loader_parent_class)->finalize) (object);
113 }
114 
115 
116 
117 static void
terminal_image_loader_check(TerminalImageLoader * loader)118 terminal_image_loader_check (TerminalImageLoader *loader)
119 {
120   TerminalBackgroundStyle selected_style;
121   GdkRGBA                 selected_color;
122   gboolean                invalidate = FALSE;
123   gchar                  *selected_color_spec;
124   gchar                  *selected_path;
125 
126   terminal_return_if_fail (TERMINAL_IS_IMAGE_LOADER (loader));
127 
128   g_object_get (G_OBJECT (loader->preferences),
129                 "background-image-file", &selected_path,
130                 "background-image-style", &selected_style,
131                 "color-background", &selected_color_spec,
132                 NULL);
133 
134   if (g_strcmp0 (selected_path, loader->path) != 0)
135     {
136       gint width, height;
137 
138       g_free (loader->path);
139       loader->path = g_strdup (selected_path);
140 
141       if (GDK_IS_PIXBUF (loader->pixbuf))
142         g_object_unref (G_OBJECT (loader->pixbuf));
143 
144       if (gdk_pixbuf_get_file_info (loader->path, &width, &height) == NULL)
145         {
146           g_warning ("Unable to load background image file \"%s\"", loader->path);
147           loader->pixbuf = NULL;
148         }
149       else if (width <= MAX_IMAGE_WIDTH && height <= MAX_IMAGE_HEIGHT)
150         loader->pixbuf = gdk_pixbuf_new_from_file (loader->path, NULL);
151       else
152         loader->pixbuf = gdk_pixbuf_new_from_file_at_size (loader->path,
153                                                            MAX_IMAGE_WIDTH, MAX_IMAGE_WIDTH,
154                                                            NULL);
155 
156       invalidate = TRUE;
157     }
158 
159   if (selected_style != loader->style)
160     {
161       loader->style = selected_style;
162       invalidate = TRUE;
163     }
164 
165   gdk_rgba_parse (&selected_color, selected_color_spec);
166   if (!gdk_rgba_equal (&selected_color, &loader->bgcolor))
167     {
168       loader->bgcolor = selected_color;
169       invalidate = TRUE;
170     }
171 
172   if (invalidate)
173     {
174       loader->cache_invalid = g_slist_concat (loader->cache_invalid,
175                                               loader->cache);
176       loader->cache = NULL;
177     }
178 
179   g_free (selected_color_spec);
180   g_free (selected_path);
181 }
182 
183 
184 
185 static void
terminal_image_loader_tile(TerminalImageLoader * loader,GdkPixbuf * target,gint width,gint height)186 terminal_image_loader_tile (TerminalImageLoader *loader,
187                             GdkPixbuf           *target,
188                             gint                 width,
189                             gint                 height)
190 {
191   GdkRectangle area;
192   gint         source_width;
193   gint         source_height;
194   gint         i;
195   gint         j;
196 
197   source_width = gdk_pixbuf_get_width (loader->pixbuf);
198   source_height = gdk_pixbuf_get_height (loader->pixbuf);
199 
200   for (i = 0; (i * source_width) < width; ++i)
201     for (j = 0; (j * source_height) < height; ++j)
202       {
203         area.x = i * source_width;
204         area.y = j * source_height;
205         area.width = source_width;
206         area.height = source_height;
207 
208         if (area.x + area.width > width)
209           area.width = width - area.x;
210         if (area.y + area.height > height)
211           area.height = height - area.y;
212 
213         gdk_pixbuf_copy_area (loader->pixbuf, 0, 0,
214                               area.width, area.height,
215                               target, area.x, area.y);
216       }
217 }
218 
219 
220 
221 static void
terminal_image_loader_center(TerminalImageLoader * loader,GdkPixbuf * target,gint width,gint height)222 terminal_image_loader_center (TerminalImageLoader *loader,
223                               GdkPixbuf           *target,
224                               gint                 width,
225                               gint                 height)
226 {
227   guint32 rgba;
228   gint    source_width;
229   gint    source_height;
230   gint    dx;
231   gint    dy;
232   gint    x0;
233   gint    y0;
234 
235   /* fill with background color */
236   rgba = ((((guint)(loader->bgcolor.red * 65535) & 0xff00) << 8)
237         | (((guint)(loader->bgcolor.green * 65535) & 0xff00))
238         | (((guint)(loader->bgcolor.blue * 65535) & 0xff00) >> 8)) << 8;
239   gdk_pixbuf_fill (target, rgba);
240 
241   source_width = gdk_pixbuf_get_width (loader->pixbuf);
242   source_height = gdk_pixbuf_get_height (loader->pixbuf);
243 
244   dx = MAX ((width - source_width) / 2, 0);
245   dy = MAX ((height - source_height) / 2, 0);
246   x0 = MIN ((width - source_width) / 2, dx);
247   y0 = MIN ((height - source_height) / 2, dy);
248 
249   gdk_pixbuf_composite (loader->pixbuf, target, dx, dy,
250                         MIN (width, source_width),
251                         MIN (height, source_height),
252                         x0, y0, 1.0, 1.0,
253                         GDK_INTERP_BILINEAR, 255);
254 }
255 
256 
257 
258 static void
terminal_image_loader_scale(TerminalImageLoader * loader,GdkPixbuf * target,gint width,gint height)259 terminal_image_loader_scale (TerminalImageLoader *loader,
260                              GdkPixbuf           *target,
261                              gint                 width,
262                              gint                 height)
263 {
264   gdouble xscale;
265   gdouble yscale;
266   guint32 rgba;
267   gint    source_width;
268   gint    source_height;
269   gint    x;
270   gint    y;
271 
272   /* fill with background color */
273   rgba = ((((guint)(loader->bgcolor.red * 65535) & 0xff00) << 8)
274         | (((guint)(loader->bgcolor.green * 65535) & 0xff00))
275         | (((guint)(loader->bgcolor.blue * 65535) & 0xff00) >> 8)) << 8;
276   gdk_pixbuf_fill (target, rgba);
277 
278   source_width = gdk_pixbuf_get_width (loader->pixbuf);
279   source_height = gdk_pixbuf_get_height (loader->pixbuf);
280 
281   xscale = (gdouble) width / source_width;
282   yscale = (gdouble) height / source_height;
283 
284   if (xscale < yscale)
285     {
286       yscale = xscale;
287       x = 0;
288       y = (height - (source_height * yscale)) / 2;
289     }
290   else
291     {
292       xscale = yscale;
293       x = (width - (source_width * xscale)) / 2;
294       y = 0;
295     }
296 
297   gdk_pixbuf_composite (loader->pixbuf, target, x, y,
298                         source_width * xscale,
299                         source_height * yscale,
300                         x, y, xscale, yscale,
301                         GDK_INTERP_BILINEAR, 255);
302 }
303 
304 
305 
306 static void
terminal_image_loader_stretch(TerminalImageLoader * loader,GdkPixbuf * target,gint width,gint height)307 terminal_image_loader_stretch (TerminalImageLoader *loader,
308                                GdkPixbuf           *target,
309                                gint                 width,
310                                gint                 height)
311 {
312   gdouble xscale;
313   gdouble yscale;
314   gint    source_width;
315   gint    source_height;
316 
317   source_width = gdk_pixbuf_get_width (loader->pixbuf);
318   source_height = gdk_pixbuf_get_height (loader->pixbuf);
319 
320   xscale = (gdouble) width / source_width;
321   yscale = (gdouble) height / source_height;
322 
323   gdk_pixbuf_composite (loader->pixbuf, target,
324                         0, 0, width, height,
325                         0, 0, xscale, yscale,
326                         GDK_INTERP_BILINEAR, 255);
327 }
328 
329 
330 
331 /**
332  * terminal_image_loader_get:
333  *
334  * Returns the default #TerminalImageLoader instance. The returned
335  * pointer is already ref'ed, call g_object_unref() if you don't
336  * need it any longer.
337  *
338  * Return value : The default #TerminalImageLoader instance.
339  **/
340 TerminalImageLoader*
terminal_image_loader_get(void)341 terminal_image_loader_get (void)
342 {
343   static TerminalImageLoader *loader = NULL;
344 
345   if (G_UNLIKELY (loader == NULL))
346     {
347       loader = g_object_new (TERMINAL_TYPE_IMAGE_LOADER, NULL);
348       g_object_add_weak_pointer (G_OBJECT (loader), (gpointer) &loader);
349     }
350   else
351     {
352       g_object_ref (G_OBJECT (loader));
353     }
354 
355   return loader;
356 }
357 
358 
359 
360 /**
361  * terminal_image_loader_load:
362  * @loader      : A #TerminalImageLoader.
363  * @width       : The image width.
364  * @height      : The image height.
365  *
366  * Return value : The image in the given @width and @height drawn with
367  *                the configured style or %NULL on error.
368  **/
369 GdkPixbuf*
terminal_image_loader_load(TerminalImageLoader * loader,gint width,gint height)370 terminal_image_loader_load (TerminalImageLoader *loader,
371                             gint                 width,
372                             gint                 height)
373 {
374   GdkPixbuf *pixbuf;
375   GSList    *lp;
376 
377   terminal_return_val_if_fail (TERMINAL_IS_IMAGE_LOADER (loader), NULL);
378   terminal_return_val_if_fail (width > 0, NULL);
379   terminal_return_val_if_fail (height > 0, NULL);
380 
381   terminal_image_loader_check (loader);
382 
383   if (G_UNLIKELY (loader->pixbuf == NULL || width <= 1 || height <= 1))
384     return NULL;
385 
386 #ifdef G_ENABLE_DEBUG
387   g_debug ("Image Loader Memory Status: %d images in valid "
388            "cache, %d in invalid cache",
389            g_slist_length (loader->cache),
390            g_slist_length (loader->cache_invalid));
391 #endif
392 
393   /* check for a cached version */
394   for (lp = loader->cache; lp != NULL; lp = lp->next)
395     {
396       gint w, h;
397       pixbuf = GDK_PIXBUF (lp->data);
398       w = gdk_pixbuf_get_width (pixbuf);
399       h = gdk_pixbuf_get_height (pixbuf);
400 
401       if ((w == width && h == height) ||
402           (w >= width && h >= height && loader->style == TERMINAL_BACKGROUND_STYLE_TILED))
403         {
404           return GDK_PIXBUF (g_object_ref (G_OBJECT (pixbuf)));
405         }
406     }
407 
408   pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (loader->pixbuf),
409                            gdk_pixbuf_get_has_alpha (loader->pixbuf),
410                            gdk_pixbuf_get_bits_per_sample (loader->pixbuf),
411                            width, height);
412 
413   switch (loader->style)
414     {
415     case TERMINAL_BACKGROUND_STYLE_TILED:
416       terminal_image_loader_tile (loader, pixbuf, width, height);
417       break;
418 
419     case TERMINAL_BACKGROUND_STYLE_CENTERED:
420       terminal_image_loader_center (loader, pixbuf, width, height);
421       break;
422 
423     case TERMINAL_BACKGROUND_STYLE_SCALED:
424       terminal_image_loader_scale (loader, pixbuf, width, height);
425       break;
426 
427     case TERMINAL_BACKGROUND_STYLE_STRETCHED:
428       terminal_image_loader_stretch (loader, pixbuf, width, height);
429       break;
430 
431     default:
432       terminal_assert_not_reached ();
433     }
434 
435   loader->cache = g_slist_prepend (loader->cache, pixbuf);
436 
437   return GDK_PIXBUF (g_object_ref (G_OBJECT (pixbuf)));
438 }
439 
440 
441