Add include of cairo.h when using librsvg
[dana/openbox.git] / obrender / image.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    image.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "geom.h"
21 #include "image.h"
22 #include "color.h"
23 #include "imagecache.h"
24 #ifdef USE_IMLIB2
25 #include <Imlib2.h>
26 #endif
27 #ifdef USE_LIBRSVG
28 #include <cairo.h>
29 #include <librsvg/rsvg.h>
30 #endif
31
32 #include <glib.h>
33
34 #define FRACTION        12
35 #define FLOOR(i)        ((i) & (~0UL << FRACTION))
36 #define AVERAGE(a, b)   (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)))
37
38 /************************************************************************
39  RrImagePic functions.
40
41  RrImagePics are pictures that are grouped together into RrImageSets.  Each
42  RrImagePic in the set has the same logical image inside it, but they are
43  of different sizes.  An RrImagePic can be an original (which comes from some
44  outside source, such as an image file), or resized from some other RrImagePic
45  to meet the needs of the user.
46 **************************************************************************/
47
48
49 /*! Set up an RrImagePic.
50   This does _not_ make a copy of the data. So the value of data must be
51   owned by the caller of this function, and not freed afterward.
52   This function does not allocate an RrImagePic, and can be used for setting
53   up a temporary RrImagePic on the stack.  Such an object would then also
54   not be freed with RrImagePicFree.
55 */
56 static void RrImagePicInit(RrImagePic *pic, gint w, gint h, RrPixel32 *data)
57 {
58     gint i;
59
60     pic->width = w;
61     pic->height = h;
62     pic->data = data;
63     pic->sum = 0;
64     for (i = w*h; i > 0; --i)
65         pic->sum += *(data++);
66 }
67
68 /*! Create a new RrImagePic from some picture data.
69   This makes a duplicate of the data.
70 */
71 static RrImagePic* RrImagePicNew(gint w, gint h, RrPixel32 *data)
72 {
73     RrImagePic *pic;
74
75     pic = g_slice_new(RrImagePic);
76     RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
77     return pic;
78 }
79
80
81 /*! Destroy an RrImagePic.
82   This frees the RrImagePic object and everything inside it.
83 */
84 static void RrImagePicFree(RrImagePic *pic)
85 {
86     if (pic) {
87         g_free(pic->data);
88         g_slice_free(RrImagePic, pic);
89     }
90 }
91
92 /************************************************************************
93  RrImageSet functions.
94
95  RrImageSets hold a group of pictures, each of which represent the same logical
96  image, but are physically different sizes.
97  At any time, it may be discovered that two different RrImageSets are actually
98  holding the same logical image.  At that time, they would be merged.
99  An RrImageSet holds both original images which come from an outside source,
100  and resized images, which are generated when requests for a specific size are
101  made, and kept around in case they are request again.  There is a maximum
102  number of resized images that an RrImageSet will keep around, however.
103
104  Each RrImage points to a single RrImageSet, which keeps track of which
105  RrImages point to it.  If two RrImageSets are merged, then the RrImages which
106  pointed to the two RrImageSets will all point at the resulting merged set.
107 **************************************************************************/
108
109
110 /*! Free an RrImageSet and the stuff inside it.
111   This should only occur when there are no more RrImages pointing to the set.
112 */
113 static void RrImageSetFree(RrImageSet *self)
114 {
115     GSList *it;
116     gint i;
117
118     if (self) {
119         g_assert(self->images == NULL);
120
121         /* remove all names associated with this RrImageSet */
122         for (it = self->names; it; it = g_slist_next(it)) {
123             g_hash_table_remove(self->cache->name_table, it->data);
124             g_free(it->data);
125         }
126         g_slist_free(self->names);
127
128         /* destroy the RrImagePic objects stored in the RrImageSet.  they will
129            be keys in the cache to RrImageSet objects, so remove them from
130            the cache's pic_table as well. */
131         for (i = 0; i < self->n_original; ++i) {
132             g_hash_table_remove(self->cache->pic_table, self->original[i]);
133             RrImagePicFree(self->original[i]);
134         }
135         g_free(self->original);
136         for (i = 0; i < self->n_resized; ++i) {
137             g_hash_table_remove(self->cache->pic_table, self->resized[i]);
138             RrImagePicFree(self->resized[i]);
139         }
140         g_free(self->resized);
141
142         g_slice_free(RrImageSet, self);
143     }
144 }
145
146 /*! Remove a picture from an RrImageSet as a given position.
147    @param set The RrImageSet to remove the picture from.
148    @param i The index of the picture in the RrImageSet in the list of
149      originals (if @original is TRUE), or in the list of resized pictures (if
150      @original is FALSE).
151    @param original TRUE if the picture is an original, FALSE if it is a resized
152      version of another picture in the RrImageSet.
153  */
154 static void RrImageSetRemovePictureAt(RrImageSet *self, gint i,
155                                       gboolean original)
156 {
157     RrImagePic ***list;
158     gint *len;
159
160     if (original) {
161         list = &self->original;
162         len = &self->n_original;
163     }
164     else {
165         list = &self->resized;
166         len = &self->n_resized;
167     }
168
169     g_assert(i >= 0 && i < *len);
170
171     /* remove the picture data as a key in the cache */
172     g_hash_table_remove(self->cache->pic_table, (*list)[i]);
173
174     /* free the picture being removed */
175     RrImagePicFree((*list)[i]);
176
177     /* copy the elements after the removed one in the array forward one space
178        and shrink the array down one size */
179     for (i = i+1; i < *len; ++i)
180         (*list)[i-1] = (*list)[i];
181     --(*len);
182     *list = g_renew(RrImagePic*, *list, *len);
183 }
184
185 /*! Add an RrImagePic to an RrImageSet.
186   The RrImagePic should _not_ exist in the image cache already.
187   Pictures are added to the front of the list, to maintain the ordering of
188   newest to oldest.
189 */
190 static void RrImageSetAddPicture(RrImageSet *self, RrImagePic *pic,
191                                  gboolean original)
192 {
193     gint i;
194     RrImagePic ***list;
195     gint *len;
196
197     g_assert(pic->width > 0 && pic->height > 0);
198     g_assert(g_hash_table_lookup(self->cache->pic_table, pic) == NULL);
199
200     /* choose which list in the RrImageSet to add the new picture to. */
201     if (original) {
202         /* remove the resized picture of the same size if one exists */
203         for (i = 0; i < self->n_resized; ++i)
204             if (self->resized[i]->width == pic->width ||
205                 self->resized[i]->height == pic->height)
206             {
207                 RrImageSetRemovePictureAt(self, i, FALSE);
208                 break;
209             }
210
211         list = &self->original;
212         len = &self->n_original;
213     }
214     else {
215         list = &self->resized;
216         len = &self->n_resized;
217     }
218
219     /* grow the list by one spot, shift everything down one, and insert the new
220        picture at the front of the list */
221     *list = g_renew(RrImagePic*, *list, ++*len);
222     for (i = *len-1; i > 0; --i)
223         (*list)[i] = (*list)[i-1];
224     (*list)[0] = pic;
225
226     /* add the picture as a key to point to this image in the cache */
227     g_hash_table_insert(self->cache->pic_table, (*list)[0], self);
228
229 /*
230 #ifdef DEBUG
231     g_debug("Adding %s picture to the cache:\n    "
232             "Image 0x%lx, w %d h %d Hash %u",
233             (*list == self->original ? "ORIGINAL" : "RESIZED"),
234             (gulong)self, pic->width, pic->height, RrImagePicHash(pic));
235 #endif
236 */
237 }
238
239 /*! Merges two image sets, destroying one, and returning the other. */
240 RrImageSet* RrImageSetMergeSets(RrImageSet *b, RrImageSet *a)
241 {
242     gint a_i, b_i, merged_i;
243     RrImagePic **original, **resized;
244     gint n_original, n_resized, tmp;
245     GSList *it;
246
247     gint max_resized;
248
249     if (!a)
250         return b;
251     if (!b)
252         return a;
253     if (a == b)
254         return b;
255     /* the original and resized picture lists in an RrImageSet are kept ordered
256        as newest to oldest.  we don't have timestamps for them, so we cannot
257        preserve this in the merged RrImageSet exactly.  a decent approximation,
258        i think, is to add them in alternating order (one from a, one from b,
259        repeat).  this way, the newest from each will be near the front at
260        least, and in the resized list, when we drop an old picture, we will
261        not always only drop from a or b only, but from each of them equally (or
262        from whichever has more resized pictures.
263     */
264
265     g_assert(b->cache == a->cache);
266
267     max_resized = a->cache->max_resized_saved;
268
269     a_i = b_i = merged_i = 0;
270     n_original = a->n_original + b->n_original;
271     original = g_new(RrImagePic*, n_original);
272     while (merged_i < n_original) {
273         if (a_i < a->n_original)
274             original[merged_i++] = a->original[a_i++];
275         if (b_i < b->n_original)
276             original[merged_i++] = b->original[b_i++];
277     }
278
279     a_i = b_i = merged_i = 0;
280     n_resized = MIN(max_resized, a->n_resized + b->n_resized);
281     resized = g_new(RrImagePic*, n_resized);
282     while (merged_i < n_resized) {
283         if (a_i < a->n_resized)
284             resized[merged_i++] = a->resized[a_i++];
285         if (b_i < b->n_resized && merged_i < n_resized)
286             resized[merged_i++] = b->resized[b_i++];
287     }
288
289     /* if there are any RrImagePic objects left over in a->resized or
290        b->resized, they need to be disposed of, and removed from the cache.
291
292        updates the size of the list, as we want to remember which pointers
293        were merged from which list (and don't want to remember the ones we
294        did not merge and have freed).
295     */
296     tmp = a_i;
297     for (; a_i < a->n_resized; ++a_i) {
298         g_hash_table_remove(a->cache->pic_table, a->resized[a_i]);
299         RrImagePicFree(a->resized[a_i]);
300     }
301     a->n_resized = tmp;
302
303     tmp = b_i;
304     for (; b_i < b->n_resized; ++b_i) {
305         g_hash_table_remove(a->cache->pic_table, b->resized[b_i]);
306         RrImagePicFree(b->resized[b_i]);
307     }
308     b->n_resized = tmp;
309
310     /* we will use the a object as the merge destination, so things in b will
311        be moving.
312
313        the cache's name_table will point to b for all the names in b->names,
314        so these need to be updated to point at a instead.
315        also, the cache's pic_table will point to b for all the pictures in b,
316        so these need to be updated to point at a as well.
317
318        any RrImage objects that were using b should now use a instead.
319
320        the names and images will be all moved into a, and the merged picture
321        lists will be placed in a.  the pictures in a and b are moved to new
322        arrays, so the arrays in a and b need to be freed explicitly (the
323        RrImageSetFree function would free the picture data too which we do not
324        want here). then b can be freed.
325     */
326
327     for (it = b->names; it; it = g_slist_next(it))
328         g_hash_table_insert(a->cache->name_table, it->data, a);
329     for (b_i = 0; b_i < b->n_original; ++b_i)
330         g_hash_table_insert(a->cache->pic_table, b->original[b_i], a);
331     for (b_i = 0; b_i < b->n_resized; ++b_i)
332         g_hash_table_insert(a->cache->pic_table, b->resized[b_i], a);
333
334     for (it = b->images; it; it = g_slist_next(it))
335         ((RrImage*)it->data)->set = a;
336
337     a->images = g_slist_concat(a->images, b->images);
338     b->images = NULL;
339     a->names = g_slist_concat(a->names, b->names);
340     b->names = NULL;
341
342     a->n_original = a->n_resized = 0;
343     g_free(a->original);
344     g_free(a->resized);
345     a->original = a->resized = NULL;
346     b->n_original = b->n_resized = 0;
347     g_free(b->original);
348     g_free(b->resized);
349     b->original = b->resized = NULL;
350
351     a->n_original = n_original;
352     a->original = original;
353     a->n_resized = n_resized;
354     a->resized = resized;
355
356     RrImageSetFree(b);
357
358     return a;
359 }
360
361 static void RrImageSetAddName(RrImageSet *set, const gchar *name)
362 {
363     gchar *n;
364
365     n = g_strdup(name);
366     set->names = g_slist_prepend(set->names, n);
367
368     /* add the new name to the hash table */
369     g_assert(g_hash_table_lookup(set->cache->name_table, n) == NULL);
370     g_hash_table_insert(set->cache->name_table, n, set);
371 }
372
373
374 /************************************************************************
375  RrImage functions.
376 **************************************************************************/
377
378
379 void RrImageRef(RrImage *self)
380 {
381     ++self->ref;
382 }
383
384 void RrImageUnref(RrImage *self)
385 {
386     if (self && --self->ref == 0) {
387         RrImageSet *set;
388 /*
389 #ifdef DEBUG
390         g_debug("Refcount to 0, removing ALL pictures from the cache:\n    "
391                 "Image 0x%lx", (gulong)self);
392 #endif
393 */
394         if (self->destroy_func)
395             self->destroy_func(self, self->destroy_data);
396
397         set = self->set;
398         set->images = g_slist_remove(set->images, self);
399
400         /* free the set as well if there are no images pointing to it */
401         if (!set->images)
402             RrImageSetFree(set);
403         g_slice_free(RrImage, self);
404     }
405 }
406
407 /*! Set function that will be called just before RrImage is destroyed. */
408 void RrImageSetDestroyFunc(RrImage *self, RrImageDestroyFunc func,
409                            gpointer data)
410 {
411     self->destroy_func = func;
412     self->destroy_data = data;
413 }
414
415 void RrImageAddFromData(RrImage *self, RrPixel32 *data, gint w, gint h)
416 {
417     RrImagePic pic, *ppic;
418     RrImageSet *set;
419
420     g_return_if_fail(self != NULL);
421     g_return_if_fail(data != NULL);
422     g_return_if_fail(w > 0 && h > 0);
423
424     RrImagePicInit(&pic, w, h, data);
425     set = g_hash_table_lookup(self->set->cache->pic_table, &pic);
426     if (set)
427         self->set = RrImageSetMergeSets(self->set, set);
428     else {
429         ppic = RrImagePicNew(w, h, data);
430         RrImageSetAddPicture(self->set, ppic, TRUE);
431     }
432 }
433
434 RrImage* RrImageNewFromData(RrImageCache *cache, RrPixel32 *data,
435                             gint w, gint h)
436 {
437     RrImagePic pic, *ppic;
438     RrImage *self;
439     RrImageSet *set;
440
441     g_return_val_if_fail(cache != NULL, NULL);
442     g_return_val_if_fail(data != NULL, NULL);
443     g_return_val_if_fail(w > 0 && h > 0, NULL);
444
445     /* finds a picture in the cache, if it is already in there, and use the
446        RrImageSet the picture lives in. */
447     RrImagePicInit(&pic, w, h, data);
448     set = g_hash_table_lookup(cache->pic_table, &pic);
449     if (set) {
450         self = set->images->data; /* just grab any RrImage from the list */
451         RrImageRef(self);
452         return self;
453     }
454
455     /* the image does not exist in any RrImageSet in the cache, so make
456        a new RrImageSet, and a new RrImage that points to it, and place the
457        new image inside the new RrImageSet */
458
459     self = g_slice_new0(RrImage);
460     self->ref = 1;
461     self->set = g_slice_new0(RrImageSet);
462     self->set->cache = cache;
463     self->set->images = g_slist_append(self->set->images, self);
464
465     ppic = RrImagePicNew(w, h, data);
466     RrImageSetAddPicture(self->set, ppic, TRUE);
467
468     return self;
469 }
470
471 #if defined(USE_IMLIB2)
472 typedef struct _ImlibLoader ImlibLoader;
473
474 struct _ImlibLoader
475 {
476     Imlib_Image img;
477 };
478
479 void DestroyImlibLoader(ImlibLoader *loader)
480 {
481     if (!loader)
482         return;
483
484     imlib_free_image();
485     g_slice_free(ImlibLoader, loader);
486 }
487
488 ImlibLoader* LoadWithImlib(gchar *path,
489                            RrPixel32 **pixel_data,
490                            gint *width,
491                            gint *height)
492 {
493     ImlibLoader *loader = g_slice_new0(ImlibLoader);
494     if (!(loader->img = imlib_load_image(path))) {
495         DestroyImlibLoader(loader);
496         return NULL;
497     }
498
499     /* Get data and dimensions of the image.
500
501        WARNING: This stuff is NOT threadsafe !!
502     */
503     imlib_context_set_image(loader->img);
504     *pixel_data = imlib_image_get_data_for_reading_only();
505     *width = imlib_image_get_width();
506     *height = imlib_image_get_height();
507
508     return loader;
509 }
510 #endif  /* USE_IMLIB2 */
511
512 #if defined(USE_LIBRSVG)
513 typedef struct _RsvgLoader RsvgLoader;
514
515 struct _RsvgLoader
516 {
517     RsvgHandle *handle;
518     cairo_surface_t *surface;
519     RrPixel32 *pixel_data;
520 };
521
522 void DestroyRsvgLoader(RsvgLoader *loader)
523 {
524     if (!loader)
525         return;
526
527     if (loader->pixel_data)
528         g_free(loader->pixel_data);
529     if (loader->surface)
530         cairo_surface_destroy(loader->surface);
531     if (loader->handle)
532         g_object_unref(loader->handle);
533     g_slice_free(RsvgLoader, loader);
534 }
535
536 RsvgLoader* LoadWithRsvg(gchar *path,
537                          RrPixel32 **pixel_data,
538                          gint *width,
539                          gint *height)
540 {
541     RsvgLoader *loader = g_slice_new0(RsvgLoader);
542
543     if (!(loader->handle = rsvg_handle_new_from_file(path, NULL))) {
544         DestroyRsvgLoader(loader);
545         return NULL;
546     }
547
548     if (!rsvg_handle_close(loader->handle, NULL)) {
549         DestroyRsvgLoader(loader);
550         return NULL;
551     }
552
553     RsvgDimensionData dimension_data;
554     rsvg_handle_get_dimensions(loader->handle, &dimension_data);
555     *width = dimension_data.width;
556     *height = dimension_data.height;
557
558     loader->surface = cairo_image_surface_create(
559         CAIRO_FORMAT_ARGB32, *width, *height);
560
561     cairo_t* context = cairo_create(loader->surface);
562     gboolean success = rsvg_handle_render_cairo(loader->handle, context);
563     cairo_destroy(context);
564
565     if (!success) {
566         DestroyRsvgLoader(loader);
567         return NULL;
568     }
569
570     loader->pixel_data = g_new(guint32, *width * *height);
571
572     /*
573       Cairo has its data in ARGB with premultiplied alpha, but RrPixel32
574       non-premultipled, so convert that. Also, RrPixel32 doesn't allow
575       strides not equal to the width of the image.
576     */
577
578     /* Verify that RrPixel32 has the same ordering as cairo. */
579     g_assert(RrDefaultAlphaOffset == 24);
580     g_assert(RrDefaultRedOffset == 16);
581     g_assert(RrDefaultGreenOffset == 8);
582     g_assert(RrDefaultBlueOffset == 0);
583
584     guint32 *out_row = loader->pixel_data;
585
586     guint32 *in_row =
587         (guint32*)cairo_image_surface_get_data(loader->surface);
588     gint in_stride = cairo_image_surface_get_stride(loader->surface);
589
590     gint y;
591     for (y = 0; y < *height; ++y) {
592         gint x;
593         for (x = 0; x < *width; ++x) {
594             guchar a = in_row[x] >> 24;
595             guchar r = (in_row[x] >> 16) & 0xff;
596             guchar g = (in_row[x] >> 8) & 0xff;
597             guchar b = in_row[x] & 0xff;
598             out_row[x] =
599                 ((r * 256 / (a + 1)) << RrDefaultRedOffset) +
600                 ((g * 256 / (a + 1)) << RrDefaultGreenOffset) +
601                 ((b * 256 / (a + 1)) << RrDefaultBlueOffset) +
602                 (a << RrDefaultAlphaOffset);
603         }
604         in_row += in_stride / 4;
605         out_row += *width;
606     }
607
608     *pixel_data = loader->pixel_data;
609
610     return loader;
611 }
612 #endif  /* USE_LIBRSVG */
613
614 RrImage* RrImageNewFromName(RrImageCache *cache, const gchar *name)
615 {
616     RrImage *self;
617     RrImageSet *set;
618     gint w, h;
619     RrPixel32 *data;
620     gchar *path;
621     gboolean loaded;
622
623 #if defined(USE_IMLIB2)
624     ImlibLoader *imlib_loader = NULL;
625 #endif
626 #if defined(USE_LIBRSVG)
627     RsvgLoader *rsvg_loader = NULL;
628 #endif
629
630     g_return_val_if_fail(cache != NULL, NULL);
631     g_return_val_if_fail(name != NULL, NULL);
632
633     set = g_hash_table_lookup(cache->name_table, name);
634     if (set) {
635         self = set->images->data;
636         RrImageRef(self);
637         return self;
638     }
639
640     /* XXX find the path via freedesktop icon spec (use obt) ! */
641     path = g_strdup(name);
642
643     loaded = FALSE;
644 #if defined(USE_LIBRSVG)
645     if (!loaded) {
646         rsvg_loader = LoadWithRsvg(path, &data, &w, &h);
647         loaded = !!rsvg_loader;
648     }
649 #endif
650 #if defined(USE_IMLIB2)
651     if (!loaded) {
652         imlib_loader = LoadWithImlib(path, &data, &w, &h);
653         loaded = !!imlib_loader;
654     }
655 #endif
656
657     if (!loaded) {
658         g_message("Cannot load image \"%s\" from file \"%s\"", name, path);
659         g_free(path);
660 #if defined(USE_LIBRSVG)
661         DestroyRsvgLoader(rsvg_loader);
662 #endif
663 #if defined(USE_IMLIB2)
664         DestroyImlibLoader(imlib_loader);
665 #endif
666         return NULL;
667     }
668
669     g_free(path);
670
671     /* get an RrImage that contains an RrImageSet with this picture in it.
672        the RrImage might be new, or reused if the picture was already in the
673        cache.
674
675        either way, we get back an RrImageSet (via the RrImage), and we must add
676        the name to that RrImageSet.  because of the check above, we know that
677        there is no RrImageSet in the cache which already has the given name
678        asosciated with it.
679     */
680
681     self = RrImageNewFromData(cache, data, w, h);
682     RrImageSetAddName(self->set, name);
683
684 #if defined(USE_LIBRSVG)
685     DestroyRsvgLoader(rsvg_loader);
686 #endif
687 #if defined(USE_IMLIB2)
688     DestroyImlibLoader(imlib_loader);
689 #endif
690
691     return self;
692 }
693
694 /************************************************************************
695  Image drawing and resizing operations.
696 **************************************************************************/
697
698 /*! Given a picture in RGBA format, of a specified size, resize it to the new
699   requested size (but keep its aspect ratio).  If the image does not need to
700   be resized (it is already the right size) then this returns NULL.  Otherwise
701   it returns a newly allocated RrImagePic with the resized picture inside it
702   @return Returns a newly allocated RrImagePic object with a new version of the
703     image in the requested size (keeping aspect ratio).
704 */
705 static RrImagePic* ResizeImage(RrPixel32 *src,
706                                gulong srcW, gulong srcH,
707                                gulong dstW, gulong dstH)
708 {
709     RrPixel32 *dst, *dststart;
710     RrImagePic *pic;
711     gulong dstX, dstY, srcX, srcY;
712     gulong srcX1, srcX2, srcY1, srcY2;
713     gulong ratioX, ratioY;
714     gulong aspectW, aspectH;
715
716     g_assert(srcW > 0);
717     g_assert(srcH > 0);
718     g_assert(dstW > 0);
719     g_assert(dstH > 0);
720
721     /* keep the aspect ratio */
722     aspectW = dstW;
723     aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
724     if (aspectH > dstH) {
725         aspectH = dstH;
726         aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
727     }
728     dstW = aspectW ? aspectW : 1;
729     dstH = aspectH ? aspectH : 1;
730
731     if (srcW == dstW && srcH == dstH)
732         return NULL; /* no scaling needed! */
733
734     dststart = dst = g_new(RrPixel32, dstW * dstH);
735
736     ratioX = (srcW << FRACTION) / dstW;
737     ratioY = (srcH << FRACTION) / dstH;
738
739     srcY2 = 0;
740     for (dstY = 0; dstY < dstH; dstY++) {
741         srcY1 = srcY2;
742         srcY2 += ratioY;
743
744         srcX2 = 0;
745         for (dstX = 0; dstX < dstW; dstX++) {
746             gulong red = 0, green = 0, blue = 0, alpha = 0;
747             gulong portionX, portionY, portionXY, sumXY = 0;
748             RrPixel32 pixel;
749
750             srcX1 = srcX2;
751             srcX2 += ratioX;
752
753             for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) {
754                 if (srcY == srcY1) {
755                     srcY = FLOOR(srcY);
756                     portionY = (1UL << FRACTION) - (srcY1 - srcY);
757                     if (portionY > srcY2 - srcY1)
758                         portionY = srcY2 - srcY1;
759                 }
760                 else if (srcY == FLOOR(srcY2))
761                     portionY = srcY2 - srcY;
762                 else
763                     portionY = (1UL << FRACTION);
764
765                 for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) {
766                     if (srcX == srcX1) {
767                         srcX = FLOOR(srcX);
768                         portionX = (1UL << FRACTION) - (srcX1 - srcX);
769                         if (portionX > srcX2 - srcX1)
770                             portionX = srcX2 - srcX1;
771                     }
772                     else if (srcX == FLOOR(srcX2))
773                         portionX = srcX2 - srcX;
774                     else
775                         portionX = (1UL << FRACTION);
776
777                     portionXY = (portionX * portionY) >> FRACTION;
778                     sumXY += portionXY;
779
780                     pixel = *(src + (srcY >> FRACTION) * srcW
781                             + (srcX >> FRACTION));
782                     red   += ((pixel >> RrDefaultRedOffset)   & 0xFF)
783                              * portionXY;
784                     green += ((pixel >> RrDefaultGreenOffset) & 0xFF)
785                              * portionXY;
786                     blue  += ((pixel >> RrDefaultBlueOffset)  & 0xFF)
787                              * portionXY;
788                     alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF)
789                              * portionXY;
790                 }
791             }
792
793             g_assert(sumXY != 0);
794             red   /= sumXY;
795             green /= sumXY;
796             blue  /= sumXY;
797             alpha /= sumXY;
798
799             *dst++ = (red   << RrDefaultRedOffset)   |
800                      (green << RrDefaultGreenOffset) |
801                      (blue  << RrDefaultBlueOffset)  |
802                      (alpha << RrDefaultAlphaOffset);
803         }
804     }
805
806     pic = g_slice_new(RrImagePic);
807     RrImagePicInit(pic, dstW, dstH, dststart);
808
809     return pic;
810 }
811
812 /*! This draws an RGBA picture into the target, within the rectangle specified
813   by the area parameter.  If the area's size differs from the source's then it
814   will be centered within the rectangle */
815 void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h,
816               RrPixel32 *source, gint source_w, gint source_h,
817               gint alpha, RrRect *area)
818 {
819     RrPixel32 *dest;
820     gint col, num_pixels;
821     gint dw, dh;
822
823     g_assert(source_w <= area->width && source_h <= area->height);
824     g_assert(area->x + area->width <= target_w);
825     g_assert(area->y + area->height <= target_h);
826
827     /* keep the aspect ratio */
828     dw = area->width;
829     dh = (gint)(dw * ((gdouble)source_h / source_w));
830     if (dh > area->height) {
831         dh = area->height;
832         dw = (gint)(dh * ((gdouble)source_w / source_h));
833     }
834
835     /* copy source -> dest, and apply the alpha channel.
836        center the image if it is smaller than the area */
837     col = 0;
838     num_pixels = dw * dh;
839     dest = target + area->x + (area->width - dw) / 2 +
840         (target_w * (area->y + (area->height - dh) / 2));
841     while (num_pixels-- > 0) {
842         guchar a, r, g, b, bgr, bgg, bgb;
843
844         /* apply the rgba's opacity as well */
845         a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8;
846         r = *source >> RrDefaultRedOffset;
847         g = *source >> RrDefaultGreenOffset;
848         b = *source >> RrDefaultBlueOffset;
849
850         /* background color */
851         bgr = *dest >> RrDefaultRedOffset;
852         bgg = *dest >> RrDefaultGreenOffset;
853         bgb = *dest >> RrDefaultBlueOffset;
854
855         r = bgr + (((r - bgr) * a) >> 8);
856         g = bgg + (((g - bgg) * a) >> 8);
857         b = bgb + (((b - bgb) * a) >> 8);
858
859         *dest = ((r << RrDefaultRedOffset) |
860                  (g << RrDefaultGreenOffset) |
861                  (b << RrDefaultBlueOffset));
862
863         dest++;
864         source++;
865
866         if (++col >= dw) {
867             col = 0;
868             dest += target_w - dw;
869         }
870     }
871 }
872
873 /*! Draw an RGBA texture into a target pixel buffer. */
874 void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
875                      gint target_w, gint target_h,
876                      RrRect *area)
877 {
878     RrImagePic *scaled;
879
880     scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
881                          area->width, area->height);
882
883     if (scaled) {
884 #ifdef DEBUG
885             g_warning("Scaling an RGBA! You should avoid this and just make "
886                       "it the right size yourself!");
887 #endif
888             DrawRGBA(target, target_w, target_h,
889                      scaled->data, scaled->width, scaled->height,
890                      rgba->alpha, area);
891             RrImagePicFree(scaled);
892     }
893     else
894         DrawRGBA(target, target_w, target_h,
895                  rgba->data, rgba->width, rgba->height,
896                  rgba->alpha, area);
897 }
898
899 /*! Draw an RrImage texture into a target pixel buffer.  If the RrImage does
900   not contain a picture of the appropriate size, then one of its "original"
901   pictures will be resized and used (and stored in the RrImage as a "resized"
902   picture).
903  */
904 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
905                       gint target_w, gint target_h,
906                       RrRect *area)
907 {
908     gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
909     RrImage *self;
910     RrImageSet *set;
911     RrImagePic *pic;
912     gboolean free_pic;
913
914     self = img->image;
915     set = self->set;
916     pic = NULL;
917     free_pic = FALSE;
918
919     /* is there an original of this size? (only the larger of
920        w or h has to be right cuz we maintain aspect ratios) */
921     for (i = 0; i < set->n_original; ++i)
922         if ((set->original[i]->width >= set->original[i]->height &&
923              set->original[i]->width == area->width) ||
924             (set->original[i]->width <= set->original[i]->height &&
925              set->original[i]->height == area->height))
926         {
927             pic = set->original[i];
928             break;
929         }
930
931     /* is there a resize of this size? */
932     for (i = 0; i < set->n_resized; ++i)
933         if ((set->resized[i]->width >= set->resized[i]->height &&
934              set->resized[i]->width == area->width) ||
935             (set->resized[i]->width <= set->resized[i]->height &&
936              set->resized[i]->height == area->height))
937         {
938             gint j;
939             RrImagePic *saved;
940
941             /* save the selected one */
942             saved = set->resized[i];
943
944             /* shift all the others down */
945             for (j = i; j > 0; --j)
946                 set->resized[j] = set->resized[j-1];
947
948             /* and move the selected one to the top of the list */
949             set->resized[0] = saved;
950
951             pic = set->resized[0];
952             break;
953         }
954
955     if (!pic) {
956         gdouble aspect;
957         RrImageSet *cache_set;
958
959         /* find an original with a close size */
960         min_diff = min_aspect_diff = -1;
961         min_i = min_aspect_i = 0;
962         aspect = ((gdouble)area->width) / area->height;
963         for (i = 0; i < set->n_original; ++i) {
964             gint diff;
965             gint wdiff, hdiff;
966             gdouble myasp;
967
968             /* our size difference metric.. */
969             wdiff = set->original[i]->width - area->width;
970             if (wdiff < 0) wdiff *= 2; /* prefer scaling down than up */
971             hdiff = set->original[i]->height - area->height;
972             if (hdiff < 0) hdiff *= 2; /* prefer scaling down than up */
973             diff = (wdiff * wdiff) + (hdiff * hdiff);
974
975             /* find the smallest difference */
976             if (min_diff < 0 || diff < min_diff) {
977                 min_diff = diff;
978                 min_i = i;
979             }
980             /* and also find the smallest difference with the same aspect
981                ratio (and prefer this one) */
982             myasp = ((gdouble)set->original[i]->width) /
983                 set->original[i]->height;
984             if (ABS(aspect - myasp) < 0.0000001 &&
985                 (min_aspect_diff < 0 || diff < min_aspect_diff))
986             {
987                 min_aspect_diff = diff;
988                 min_aspect_i = i;
989             }
990         }
991
992         /* use the aspect ratio correct source if there is one */
993         if (min_aspect_i >= 0)
994             min_i = min_aspect_i;
995
996         /* resize the original to the given area */
997         pic = ResizeImage(set->original[min_i]->data,
998                           set->original[min_i]->width,
999                           set->original[min_i]->height,
1000                           area->width, area->height);
1001
1002         /* is it already in the cache ? */
1003         cache_set = g_hash_table_lookup(set->cache->pic_table, pic);
1004         if (cache_set) {
1005             /* merge this set with the one found in the cache - they are
1006                apparently the same image !  then next time we won't have to do
1007                this resizing, we will use the cache_set's pic instead. */
1008             set = RrImageSetMergeSets(set, cache_set);
1009             free_pic = TRUE;
1010         }
1011         else {
1012             /* add the resized image to the image, as the first in the resized
1013                list */
1014             while (set->n_resized >= set->cache->max_resized_saved)
1015                 /* remove the last one (last used one) to make space for
1016                  adding our resized picture */
1017                 RrImageSetRemovePictureAt(set, set->n_resized-1, FALSE);
1018             if (set->cache->max_resized_saved)
1019                 /* add it to the resized list */
1020                 RrImageSetAddPicture(set, pic, FALSE);
1021             else
1022                 free_pic = TRUE; /* don't leak mem! */
1023         }
1024     }
1025
1026     /* The RrImageSet may have changed if we merged it with another, so the
1027        RrImage object needs to be updated to use the new merged RrImageSet. */
1028     self->set = set;
1029
1030     g_assert(pic != NULL);
1031
1032     DrawRGBA(target, target_w, target_h,
1033              pic->data, pic->width, pic->height,
1034              img->alpha, area);
1035     if (free_pic)
1036         RrImagePicFree(pic);
1037 }