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