]> Pileus Git - ~andy/gtk/blob - gdk/gdkimage.c
1d1c09c5efbf767bf90f064d61b271f88fee3e21
[~andy/gtk] / gdk / gdkimage.c
1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser 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
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include <stdlib.h>
28 #include <sys/types.h>
29
30 #include "gdk.h"                /* For gdk_flush() */
31 #include "gdkimage.h"
32 #include "gdkprivate.h"
33 #include "gdkinternals.h"       /* For scratch_image code */
34
35 /**
36  * gdk_image_ref:
37  * @image: a #GdkImage
38  * 
39  * Deprecated function; use g_object_ref() instead.
40  * 
41  * Return value: the image
42  **/
43 GdkImage *
44 gdk_image_ref (GdkImage *image)
45 {
46   return (GdkImage *) g_object_ref (G_OBJECT (image));
47 }
48
49 /**
50  * gdk_image_unref:
51  * @image: a #GdkImage
52  * 
53  * Deprecated function; use g_object_unref() instead.
54  * 
55  **/
56 void
57 gdk_image_unref (GdkImage *image)
58 {
59   g_return_if_fail (GDK_IS_IMAGE (image));
60
61   g_object_unref (G_OBJECT (image));
62 }
63
64 /**
65  * gdk_image_get:
66  * @drawable: a #GdkDrawable
67  * @x: x coordinate in @window
68  * @y: y coordinate in @window
69  * @width: width of area in @window
70  * @height: height of area in @window
71  * 
72  * This is a deprecated wrapper for gdk_drawable_get_image();
73  * gdk_drawable_get_image() should be used instead. Or even better: in
74  * most cases gdk_pixbuf_get_from_drawable() is the most convenient
75  * choice.
76  * 
77  * Return value: a new #GdkImage or %NULL
78  **/
79 GdkImage*
80 gdk_image_get (GdkWindow *drawable,
81                gint       x,
82                gint       y,
83                gint       width,
84                gint       height)
85 {
86   g_return_val_if_fail (GDK_IS_DRAWABLE (drawable), NULL);
87   g_return_val_if_fail (x >= 0, NULL);
88   g_return_val_if_fail (y >= 0, NULL);
89   g_return_val_if_fail (width >= 0, NULL);
90   g_return_val_if_fail (height >= 0, NULL);
91   
92   return gdk_drawable_get_image (drawable, x, y, width, height);
93 }
94
95 /**
96  * gdk_image_set_colormap:
97  * @image: a #GdkImage
98  * @colormap: a #GdkColormap
99  * 
100  * Sets the colormap for the image to the given colormap.  Normally
101  * there's no need to use this function, images are created with the
102  * correct colormap if you get the image from a drawable. If you
103  * create the image from scratch, use the colormap of the drawable you
104  * intend to render the image to.
105  **/
106 void
107 gdk_image_set_colormap (GdkImage       *image,
108                         GdkColormap    *colormap)
109 {
110   g_return_if_fail (GDK_IS_IMAGE (image));
111   g_return_if_fail (GDK_IS_COLORMAP (colormap));
112
113   if (image->colormap != colormap)
114     {
115       if (image->colormap)
116         g_object_unref (G_OBJECT (image->colormap));
117
118       image->colormap = colormap;
119       g_object_ref (G_OBJECT (image->colormap));
120     }
121     
122 }
123
124 /**
125  * gdk_image_get_colormap:
126  * @image: a #GdkImage
127  * 
128  * Retrieves the colormap for a given image, if it exists.  An image
129  * will have a colormap if the drawable from which it was created has
130  * a colormap, or if a colormap was set explicitely with
131  * gdk_image_set_colormap().
132  * 
133  * Return value: colormap for the image
134  **/
135 GdkColormap *
136 gdk_image_get_colormap (GdkImage *image)
137 {
138   g_return_val_if_fail (GDK_IS_IMAGE (image), NULL);
139
140   return image->colormap;
141 }
142
143 /* We have N_REGION GDK_SCRATCH_IMAGE_WIDTH x GDK_SCRATCH_IMAGE_HEIGHT regions divided
144  * up between n_images different images. possible_n_images gives
145  * various divisors of N_REGIONS. The reason for allowing this
146  * flexibility is that we want to create as few images as possible,
147  * but we want to deal with the abberant systems that have a SHMMAX
148  * limit less than
149  *
150  * GDK_SCRATCH_IMAGE_WIDTH * GDK_SCRATCH_IMAGE_HEIGHT * N_REGIONS * 4 (384k)
151  *
152  * (Are there any such?)
153  */
154 #define N_REGIONS 6
155 static const int possible_n_images[] = { 1, 2, 3, 6 };
156
157 /* We allocate one GdkScratchImageInfo structure for each
158  * depth where we are allocating scratch images. (Future: one
159  * per depth, per display)
160  */
161 typedef struct _GdkScratchImageInfo GdkScratchImageInfo;
162
163 struct _GdkScratchImageInfo {
164   gint depth;
165   
166   gint n_images;
167   GdkImage *static_image[N_REGIONS];
168   gint static_image_idx;
169
170   /* In order to optimize filling fractions, we simultaneously fill in up
171    * to three regions of size GDK_SCRATCH_IMAGE_WIDTH * GDK_SCRATCH_IMAGE_HEIGHT: one
172    * for images that are taller than GDK_SCRATCH_IMAGE_HEIGHT / 2, and must
173    * be tiled horizontally. One for images that are wider than
174    * GDK_SCRATCH_IMAGE_WIDTH / 2 and must be tiled vertically, and a third
175    * for images smaller than GDK_SCRATCH_IMAGE_HEIGHT / 2 x GDK_SCRATCH_IMAGE_WIDTH x 2
176    * that we tile in horizontal rows.
177    */
178   gint horiz_idx;
179   gint horiz_y;
180   gint vert_idx;
181   gint vert_x;
182   
183   /* tile_y1 and tile_y2 define the horizontal band into
184    * which we are tiling images. tile_x is the x extent to
185    * which that is filled
186    */
187   gint tile_idx;
188   gint tile_x;
189   gint tile_y1;
190   gint tile_y2;
191
192   GdkScreen *screen;
193 };
194
195 static GSList *scratch_image_infos = NULL;
196
197 static gboolean
198 allocate_scratch_images (GdkScratchImageInfo *info,
199                          gint                 n_images,
200                          gboolean             shared)
201 {
202   gint i;
203   
204   for (i = 0; i < n_images; i++)
205     {
206       info->static_image[i] = _gdk_image_new_for_depth (info->screen,
207                                                         shared ? GDK_IMAGE_SHARED : GDK_IMAGE_NORMAL,
208                                                         NULL,
209                                                         GDK_SCRATCH_IMAGE_WIDTH * (N_REGIONS / n_images), 
210                                                         GDK_SCRATCH_IMAGE_HEIGHT,
211                                                         info->depth);
212       
213       if (!info->static_image[i])
214         {
215           gint j;
216           
217           for (j = 0; j < i; j++)
218             gdk_image_unref (info->static_image[i]);
219           
220           return FALSE;
221         }
222     }
223   
224   return TRUE;
225 }
226
227 static GdkScratchImageInfo *
228 scratch_image_info_for_depth (GdkScreen *screen,
229                               gint       depth)
230 {
231   GSList *tmp_list;
232   GdkScratchImageInfo *image_info;
233   gint i;
234
235   tmp_list = scratch_image_infos;
236   while (tmp_list)
237     {
238       image_info = tmp_list->data;
239       if (image_info->depth == depth && image_info->screen == screen)
240         return image_info;
241       
242       tmp_list = tmp_list->next;
243     }
244
245   image_info = g_new (GdkScratchImageInfo, 1);
246
247   image_info->depth = depth;
248   image_info->screen = screen;
249
250     /* Try to allocate as few possible shared images */
251   for (i=0; i < G_N_ELEMENTS (possible_n_images); i++)
252     {
253       if (allocate_scratch_images (image_info, possible_n_images[i], TRUE))
254         {
255           image_info->n_images = possible_n_images[i];
256           break;
257         }
258     }
259
260   /* If that fails, just allocate N_REGIONS normal images */
261   if (i == G_N_ELEMENTS (possible_n_images))
262     {
263       allocate_scratch_images (image_info, N_REGIONS, FALSE);
264       image_info->n_images = N_REGIONS;
265     }
266
267   image_info->static_image_idx = 0;
268
269   image_info->horiz_y = GDK_SCRATCH_IMAGE_HEIGHT;
270   image_info->vert_x = GDK_SCRATCH_IMAGE_WIDTH;
271   image_info->tile_x = GDK_SCRATCH_IMAGE_WIDTH;
272   image_info->tile_y1 = image_info->tile_y2 = GDK_SCRATCH_IMAGE_HEIGHT;
273
274   scratch_image_infos = g_slist_prepend (scratch_image_infos, image_info);
275
276   return image_info;
277 }
278
279 /* Defining NO_FLUSH can cause inconsistent screen updates, but is useful
280    for performance evaluation. */
281
282 #undef NO_FLUSH
283
284 #ifdef VERBOSE
285 static gint sincelast;
286 #endif
287
288 static gint
289 alloc_scratch_image (GdkScratchImageInfo *image_info)
290 {
291   if (image_info->static_image_idx == N_REGIONS)
292     {
293 #ifndef NO_FLUSH
294       gdk_flush ();
295 #endif
296 #ifdef VERBOSE
297       g_print ("flush, %d puts since last flush\n", sincelast);
298       sincelast = 0;
299 #endif
300       image_info->static_image_idx = 0;
301
302       /* Mark all regions that we might be filling in as completely
303        * full, to force new tiles to be allocated for subsequent
304        * images
305        */
306       image_info->horiz_y = GDK_SCRATCH_IMAGE_HEIGHT;
307       image_info->vert_x = GDK_SCRATCH_IMAGE_WIDTH;
308       image_info->tile_x = GDK_SCRATCH_IMAGE_WIDTH;
309       image_info->tile_y1 = image_info->tile_y2 = GDK_SCRATCH_IMAGE_HEIGHT;
310     }
311   return image_info->static_image_idx++;
312 }
313
314 /**
315  * _gdk_image_get_scratch:
316  * @screen: a #GdkScreen
317  * @width: desired width
318  * @height: desired height
319  * @depth: depth of image 
320  * @x: X location within returned image of scratch image
321  * @y: Y location within returned image of scratch image
322  * 
323  * Allocates an image of size width/height, up to a maximum
324  * of GDK_SCRATCH_IMAGE_WIDTHxGDK_SCRATCH_IMAGE_HEIGHT that is
325  * suitable to use on @screen.
326  * 
327  * Return value: a scratch image. This must be used by a
328  *  call to gdk_image_put() before any other calls to
329  *  _gdk_image_get_scratch()
330  **/
331 GdkImage *
332 _gdk_image_get_scratch (GdkScreen   *screen,
333                         gint         width,                     
334                         gint         height,
335                         gint         depth,
336                         gint        *x,
337                         gint        *y)
338 {
339   GdkScratchImageInfo *image_info;
340   GdkImage *image;
341   gint idx;
342   
343   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
344
345   image_info = scratch_image_info_for_depth (screen, depth);
346
347   if (width >= (GDK_SCRATCH_IMAGE_WIDTH >> 1))
348     {
349       if (height >= (GDK_SCRATCH_IMAGE_HEIGHT >> 1))
350         {
351           idx = alloc_scratch_image (image_info);
352           *x = 0;
353           *y = 0;
354         }
355       else
356         {
357           if (height + image_info->horiz_y > GDK_SCRATCH_IMAGE_HEIGHT)
358             {
359               image_info->horiz_idx = alloc_scratch_image (image_info);
360               image_info->horiz_y = 0;
361             }
362           idx = image_info->horiz_idx;
363           *x = 0;
364           *y = image_info->horiz_y;
365           image_info->horiz_y += height;
366         }
367     }
368   else
369     {
370       if (height >= (GDK_SCRATCH_IMAGE_HEIGHT >> 1))
371         {
372           if (width + image_info->vert_x > GDK_SCRATCH_IMAGE_WIDTH)
373             {
374               image_info->vert_idx = alloc_scratch_image (image_info);
375               image_info->vert_x = 0;
376             }
377           idx = image_info->vert_idx;
378           *x = image_info->vert_x;
379           *y = 0;
380           /* using 3 and -4 would be slightly more efficient on 32-bit machines
381              with > 1bpp displays */
382           image_info->vert_x += (width + 7) & -8;
383         }
384       else
385         {
386           if (width + image_info->tile_x > GDK_SCRATCH_IMAGE_WIDTH)
387             {
388               image_info->tile_y1 = image_info->tile_y2;
389               image_info->tile_x = 0;
390             }
391           if (height + image_info->tile_y1 > GDK_SCRATCH_IMAGE_HEIGHT)
392             {
393               image_info->tile_idx = alloc_scratch_image (image_info);
394               image_info->tile_x = 0;
395               image_info->tile_y1 = 0;
396               image_info->tile_y2 = 0;
397             }
398           if (height + image_info->tile_y1 > image_info->tile_y2)
399             image_info->tile_y2 = height + image_info->tile_y1;
400           idx = image_info->tile_idx;
401           *x = image_info->tile_x;
402           *y = image_info->tile_y1;
403           image_info->tile_x += (width + 7) & -8;
404         }
405     }
406   image = image_info->static_image[idx * image_info->n_images / N_REGIONS];
407   *x += GDK_SCRATCH_IMAGE_WIDTH * (idx % (N_REGIONS / image_info->n_images));
408 #ifdef VERBOSE
409   g_print ("index %d, x %d, y %d (%d x %d)\n", idx, *x, *y, width, height);
410   sincelast++;
411 #endif
412   return image;
413 }
414
415 GdkImage*
416 gdk_image_new (GdkImageType  type,
417                GdkVisual    *visual,
418                gint          width,
419                gint          height)
420 {
421   return _gdk_image_new_for_depth (gdk_visual_get_screen (visual), type,
422                                    visual, width, height, -1);
423 }