]> Pileus Git - ~andy/gtk/blob - modules/engines/pixbuf/pixbuf-render.c
Handle drawing transparency without a mask correctly.
[~andy/gtk] / modules / engines / pixbuf / pixbuf-render.c
1 /* GTK+ Pixbuf Engine
2  * Copyright (C) 1998-2000 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Written by Owen Taylor <otaylor@redhat.com>, based on code by
20  * Carsten Haitzler <raster@rasterman.com>
21  */
22
23 #include "pixmap_theme.h"
24 #include <gdk-pixbuf/gdk-pixbuf.h>
25
26 GCache *pixbuf_cache = NULL;
27
28 static void
29 pixbuf_render (GdkPixbuf    *src,
30                GdkWindow    *window,
31                GdkBitmap    *mask,
32                GdkRectangle *clip_rect,
33                gint          src_x,
34                gint          src_y,
35                gint          src_width,
36                gint          src_height,
37                gint          dest_x,
38                gint          dest_y,
39                gint          dest_width,
40                gint          dest_height)
41 {
42   GdkPixbuf *tmp_pixbuf;
43   GdkRectangle rect;
44   int x_offset, y_offset;
45
46   if (dest_width <= 0 || dest_height <= 0)
47     return;
48
49   rect.x = dest_x;
50   rect.y = dest_y;
51   rect.width = dest_width;
52   rect.height = dest_height;
53
54   /* FIXME: we need the full mask, not a partial mask; the following is, however,
55    *  horribly expensive
56    */
57   if (!mask && clip_rect && !gdk_rectangle_intersect (clip_rect, &rect, &rect))
58     return;
59   
60   if (dest_width != src->art_pixbuf->width ||
61       dest_height != src->art_pixbuf->height)
62     {
63       ArtPixBuf *partial_src_art;
64       GdkPixbuf *partial_src_gdk;
65
66       if (src->art_pixbuf->n_channels == 3)
67         {
68           partial_src_art = 
69             art_pixbuf_new_const_rgb (src->art_pixbuf->pixels + src_y * src->art_pixbuf->rowstride + src_x * src->art_pixbuf->n_channels,
70                                       src_width, 
71                                       src_height, 
72                                       src->art_pixbuf->rowstride);
73         }
74       else
75         {
76           partial_src_art = 
77             art_pixbuf_new_const_rgba (src->art_pixbuf->pixels + src_y * src->art_pixbuf->rowstride + src_x * src->art_pixbuf->n_channels,
78                                        src_width, 
79                                        src_height, 
80                                        src->art_pixbuf->rowstride);
81         }
82
83       partial_src_gdk = gdk_pixbuf_new_from_art_pixbuf (partial_src_art);
84       tmp_pixbuf = gdk_pixbuf_new (ART_PIX_RGB, src->art_pixbuf->has_alpha, 8, rect.width, rect.height);
85
86       gdk_pixbuf_scale (partial_src_gdk, tmp_pixbuf, 0, 0, rect.width, rect.height,
87                         dest_x - rect.x, dest_y - rect.y, 
88                         (double)dest_width / src_width, (double)dest_height / src_height,
89                         ART_FILTER_BILINEAR);
90
91       gdk_pixbuf_unref (partial_src_gdk);
92       
93       x_offset = 0;
94       y_offset = 0;
95     }
96   else
97     {
98       tmp_pixbuf = src;
99       gdk_pixbuf_ref (tmp_pixbuf);
100
101       x_offset = src_x + rect.x - dest_x;
102       y_offset = src_y + rect.y - dest_y;
103     }
104
105   if (mask)
106     {
107       GdkGC *tmp_gc;
108
109       gdk_pixbuf_render_threshold_alpha (tmp_pixbuf, mask,
110                                          x_offset, y_offset,
111                                          rect.x, rect.y,
112                                          rect.width, rect.height,
113                                          128);
114
115       tmp_gc = gdk_gc_new (window);
116       gdk_pixbuf_render_to_drawable (tmp_pixbuf, window, tmp_gc, 
117                                      x_offset, y_offset,
118                                      rect.x, rect.y,
119                                      rect.width, rect.height,
120                                      GDK_RGB_DITHER_NORMAL,
121                                      0, 0);
122       gdk_gc_unref (tmp_gc);
123     }
124   else
125     gdk_pixbuf_render_to_drawable_alpha (tmp_pixbuf, window,
126                                          x_offset, y_offset,
127                                          rect.x, rect.y,
128                                          rect.width, rect.height,
129                                          GDK_PIXBUF_ALPHA_BILEVEL, 128,
130                                          GDK_RGB_DITHER_NORMAL,
131                                          0, 0);
132   gdk_pixbuf_unref (tmp_pixbuf);
133 }
134
135 ThemePixbuf *
136 theme_pixbuf_new (void)
137 {
138   ThemePixbuf *result = g_new (ThemePixbuf, 1);
139   result->filename = NULL;
140   result->pixbuf = NULL;
141
142   result->stretch = TRUE;
143   result->border_left = 0;
144   result->border_right = 0;
145   result->border_bottom = 0;
146   result->border_top = 0;
147
148   return result;
149 }
150
151 void
152 theme_pixbuf_destroy (ThemePixbuf *theme_pb)
153 {
154   if (theme_pb->pixbuf)
155     g_cache_remove (pixbuf_cache, theme_pb->pixbuf);
156 }
157
158 void         
159 theme_pixbuf_set_filename (ThemePixbuf *theme_pb,
160                            const char  *filename)
161 {
162   if (theme_pb->pixbuf)
163     {
164       g_cache_remove (pixbuf_cache, theme_pb->pixbuf);
165       theme_pb->pixbuf = NULL;
166     }
167
168   if (theme_pb->filename)
169     g_free (theme_pb->filename);
170
171   theme_pb->filename = g_strdup (filename);
172 }
173
174 void
175 theme_pixbuf_set_border (ThemePixbuf *theme_pb,
176                          gint         left,
177                          gint         right,
178                          gint         top,
179                          gint         bottom)
180 {
181   theme_pb->border_left = left;
182   theme_pb->border_right = right;
183   theme_pb->border_top = top;
184   theme_pb->border_bottom = bottom;
185 }
186
187 void
188 theme_pixbuf_set_stretch (ThemePixbuf *theme_pb,
189                           gboolean     stretch)
190 {
191   theme_pb->stretch = stretch;
192 }
193
194 GdkPixbuf *
195 pixbuf_cache_value_new (gchar *filename)
196 {
197   GdkPixbuf *result = gdk_pixbuf_new_from_file (filename);
198   if (!result)
199     g_warning("Pixbuf theme: Cannot load pixmap file %s\n", filename);
200
201   return result;
202 }
203
204 GdkPixbuf *
205 theme_pixbuf_get_pixbuf (ThemePixbuf *theme_pb)
206 {
207   if (!theme_pb->pixbuf)
208     {
209       if (!pixbuf_cache)
210         pixbuf_cache = g_cache_new ((GCacheNewFunc)pixbuf_cache_value_new,
211                                     (GCacheDestroyFunc)gdk_pixbuf_unref,
212                                     (GCacheDupFunc)g_strdup,
213                                     (GCacheDestroyFunc)g_free,
214                                     g_str_hash, g_direct_hash, g_str_equal);
215       
216       theme_pb->pixbuf = g_cache_insert (pixbuf_cache, theme_pb->filename);
217     }
218   
219   return theme_pb->pixbuf;
220 }
221
222 void
223 theme_pixbuf_render (ThemePixbuf  *theme_pb,
224                      GdkWindow    *window,
225                      GdkBitmap    *mask,
226                      GdkRectangle *clip_rect,
227                      guint         component_mask,
228                      gboolean      center,
229                      gint          x,
230                      gint          y,
231                      gint          width,
232                      gint          height)
233 {
234   GdkPixbuf *pixbuf = theme_pixbuf_get_pixbuf (theme_pb);
235   gint src_x[4], src_y[4], dest_x[4], dest_y[4];
236
237   if (!pixbuf)
238     return;
239
240   if (theme_pb->stretch)
241     {
242       src_x[0] = 0;
243       src_x[1] = theme_pb->border_left;
244       src_x[2] = pixbuf->art_pixbuf->width - theme_pb->border_right;
245       src_x[3] = pixbuf->art_pixbuf->width;
246       
247       src_y[0] = 0;
248       src_y[1] = theme_pb->border_top;
249       src_y[2] = pixbuf->art_pixbuf->height - theme_pb->border_bottom;
250       src_y[3] = pixbuf->art_pixbuf->height;
251       
252       dest_x[0] = x;
253       dest_x[1] = x + theme_pb->border_left;
254       dest_x[2] = x + width - theme_pb->border_right;
255       dest_x[3] = x + width;
256
257       dest_y[0] = y;
258       dest_y[1] = y + theme_pb->border_top;
259       dest_y[2] = y + height - theme_pb->border_bottom;
260       dest_y[3] = y + height;
261
262       if (component_mask & COMPONENT_ALL)
263         component_mask = (COMPONENT_ALL - 1) & ~component_mask;
264
265 #define RENDER_COMPONENT(X1,X2,Y1,Y2)                                   \
266         pixbuf_render (pixbuf, window, mask, clip_rect,                 \
267                        src_x[X1], src_y[Y1],                            \
268                        src_x[X2] - src_x[X1], src_y[Y2] - src_y[Y1],    \
269                        dest_x[X1], dest_y[Y1],                          \
270                        dest_x[X2] - dest_x[X1], dest_y[Y2] - dest_y[Y1]);
271       
272       if (component_mask & COMPONENT_NORTH_WEST)
273         RENDER_COMPONENT (0, 1, 0, 1);
274
275       if (component_mask & COMPONENT_NORTH)
276         RENDER_COMPONENT (1, 2, 0, 1);
277
278       if (component_mask & COMPONENT_NORTH_EAST)
279         RENDER_COMPONENT (2, 3, 0, 1);
280
281       if (component_mask & COMPONENT_WEST)
282         RENDER_COMPONENT (0, 1, 1, 2);
283
284       if (component_mask & COMPONENT_CENTER)
285         RENDER_COMPONENT (1, 2, 1, 2);
286
287       if (component_mask & COMPONENT_EAST)
288         RENDER_COMPONENT (2, 3, 1, 2);
289
290       if (component_mask & COMPONENT_SOUTH_WEST)
291         RENDER_COMPONENT (0, 1, 2, 3);
292
293       if (component_mask & COMPONENT_SOUTH)
294         RENDER_COMPONENT (1, 2, 2, 3);
295
296       if (component_mask & COMPONENT_SOUTH_EAST)
297         RENDER_COMPONENT (2, 3, 2, 3);
298     }
299   else
300     {
301       if (center)
302         {
303           x += (width - pixbuf->art_pixbuf->width) / 2;
304           y += (height - pixbuf->art_pixbuf->height) / 2;
305           
306           pixbuf_render (pixbuf, window, NULL, clip_rect,
307                          0, 0,
308                          pixbuf->art_pixbuf->width, pixbuf->art_pixbuf->height,
309                          x, y,
310                          pixbuf->art_pixbuf->width, pixbuf->art_pixbuf->height);
311         }
312       else
313         {
314           GdkPixmap *tmp_pixmap;
315           GdkGC *tmp_gc;
316           GdkGCValues gc_values;
317
318           tmp_pixmap = gdk_pixmap_new (window,
319                                        pixbuf->art_pixbuf->width,
320                                        pixbuf->art_pixbuf->height,
321                                        -1);
322           tmp_gc = gdk_gc_new (tmp_pixmap);
323           gdk_pixbuf_render_to_drawable (pixbuf, tmp_pixmap, tmp_gc,
324                                          0, 0, 
325                                          0, 0,
326                                          pixbuf->art_pixbuf->width, pixbuf->art_pixbuf->height,
327                                          GDK_RGB_DITHER_NORMAL,
328                                          0, 0);
329           gdk_gc_unref (tmp_gc);
330
331           gc_values.fill = GDK_TILED;
332           gc_values.tile = tmp_pixmap;
333           tmp_gc = gdk_gc_new_with_values (window,
334                                            &gc_values, GDK_GC_FILL | GDK_GC_TILE);
335           if (clip_rect)
336             gdk_draw_rectangle (window, tmp_gc, TRUE,
337                                 clip_rect->x, clip_rect->y, clip_rect->width, clip_rect->height);
338           else
339             gdk_draw_rectangle (window, tmp_gc, TRUE, x, y, width, height);
340           
341           gdk_gc_unref (tmp_gc);
342           gdk_pixmap_unref (tmp_pixmap);
343         }
344     }
345 }