Don't make desktop switching interactive when no mods used. (Fix bug #5203)
[openbox.git] / obrender / image.c
blob196d9d1ef419f8d68251a9dfbee39205a7ef461a
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 image.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
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
28 #include <glib.h>
30 #define FRACTION 12
31 #define FLOOR(i) ((i) & (~0UL << FRACTION))
32 #define AVERAGE(a, b) (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)))
34 /************************************************************************
35 RrImagePic functions.
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 **************************************************************************/
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.
52 static void RrImagePicInit(RrImagePic *pic, gint w, gint h, RrPixel32 *data)
54 gint i;
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++);
64 /*! Create a new RrImagePic from some picture data.
65 This makes a duplicate of the data.
67 static RrImagePic* RrImagePicNew(gint w, gint h, RrPixel32 *data)
69 RrImagePic *pic;
71 pic = g_slice_new(RrImagePic);
72 RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
73 return pic;
77 /*! Destroy an RrImagePic.
78 This frees the RrImagePic object and everything inside it.
80 static void RrImagePicFree(RrImagePic *pic)
82 if (pic) {
83 g_free(pic->data);
84 g_slice_free(RrImagePic, pic);
88 /************************************************************************
89 RrImageSet functions.
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.
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 **************************************************************************/
106 /*! Free an RrImageSet and the stuff inside it.
107 This should only occur when there are no more RrImages pointing to the set.
109 static void RrImageSetFree(RrImageSet *self)
111 GSList *it;
112 gint i;
114 if (self) {
115 g_assert(self->images == NULL);
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);
122 g_slist_free(self->names);
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]);
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]);
136 g_free(self->resized);
138 g_slice_free(RrImageSet, self);
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.
150 static void RrImageSetRemovePictureAt(RrImageSet *self, gint i,
151 gboolean original)
153 RrImagePic ***list;
154 gint *len;
156 if (original) {
157 list = &self->original;
158 len = &self->n_original;
160 else {
161 list = &self->resized;
162 len = &self->n_resized;
165 g_assert(i >= 0 && i < *len);
167 /* remove the picture data as a key in the cache */
168 g_hash_table_remove(self->cache->pic_table, (*list)[i]);
170 /* free the picture being removed */
171 RrImagePicFree((*list)[i]);
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);
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.
186 static void RrImageSetAddPicture(RrImageSet *self, RrImagePic *pic,
187 gboolean original)
189 gint i;
190 RrImagePic ***list;
191 gint *len;
193 g_assert(pic->width > 0 && pic->height > 0);
194 g_assert(g_hash_table_lookup(self->cache->pic_table, pic) == NULL);
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)
203 RrImageSetRemovePictureAt(self, i, FALSE);
204 break;
207 list = &self->original;
208 len = &self->n_original;
210 else {
211 list = &self->resized;
212 len = &self->n_resized;
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;
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);
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
235 /*! Merges two image sets, destroying one, and returning the other. */
236 RrImageSet* RrImageSetMergeSets(RrImageSet *b, RrImageSet *a)
238 gint a_i, b_i, merged_i;
239 RrImagePic **original, **resized;
240 gint n_original, n_resized, tmp;
241 GSList *it;
243 const gint max_resized = a->cache->max_resized_saved;
245 if (!a)
246 return b;
247 if (!b)
248 return a;
249 if (a == b)
250 return b;
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.
262 g_assert(b->cache == a->cache);
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++];
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++];
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.
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).
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]);
296 a->n_resized = tmp;
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]);
303 b->n_resized = tmp;
305 /* we will use the a object as the merge destination, so things in b will
306 be moving.
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.
313 any RrImage objects that were using b should now use a instead.
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.
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);
329 for (it = b->images; it; it = g_slist_next(it))
330 ((RrImage*)it->data)->set = a;
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;
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;
346 a->n_original = n_original;
347 a->original = original;
348 a->n_resized = n_resized;
349 a->resized = resized;
351 RrImageSetFree(b);
353 return a;
356 static void RrImageSetAddName(RrImageSet *set, const gchar *name)
358 gchar *n;
360 n = g_strdup(name);
361 set->names = g_slist_prepend(set->names, n);
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);
369 /************************************************************************
370 RrImage functions.
371 **************************************************************************/
374 void RrImageRef(RrImage *self)
376 ++self->ref;
379 void RrImageUnref(RrImage *self)
381 if (self && --self->ref == 0) {
382 RrImageSet *set;
384 #ifdef DEBUG
385 g_debug("Refcount to 0, removing ALL pictures from the cache:\n "
386 "Image 0x%lx", (gulong)self);
387 #endif
389 if (self->destroy_func)
390 self->destroy_func(self, self->destroy_data);
392 set = self->set;
393 set->images = g_slist_remove(set->images, self);
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);
402 /*! Set function that will be called just before RrImage is destroyed. */
403 void RrImageSetDestroyFunc(RrImage *self, RrImageDestroyFunc func,
404 gpointer data)
406 self->destroy_func = func;
407 self->destroy_data = data;
410 void RrImageAddFromData(RrImage *self, RrPixel32 *data, gint w, gint h)
412 RrImagePic pic, *ppic;
413 RrImageSet *set;
415 g_return_if_fail(self != NULL);
416 g_return_if_fail(data != NULL);
417 g_return_if_fail(w > 0 && h > 0);
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);
429 RrImage* RrImageNewFromData(RrImageCache *cache, RrPixel32 *data,
430 gint w, gint h)
432 RrImagePic pic, *ppic;
433 RrImage *self;
434 RrImageSet *set;
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);
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;
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 */
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);
460 ppic = RrImagePicNew(w, h, data);
461 RrImageSetAddPicture(self->set, ppic, TRUE);
463 return self;
466 RrImage* RrImageNewFromName(RrImageCache *cache, const gchar *name)
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;
478 g_return_val_if_fail(cache != NULL, NULL);
479 g_return_val_if_fail(name != NULL, NULL);
481 set = g_hash_table_lookup(cache->name_table, name);
482 if (set) {
483 self = set->images->data;
484 RrImageRef(self);
485 return self;
488 /* XXX find the path via freedesktop icon spec (use obt) ! */
489 path = g_strdup(name);
491 if (!(img = imlib_load_image(path)))
492 g_message("Cannot load image \"%s\" from file \"%s\"", name, path);
493 g_free(path);
495 if (!img)
496 return NULL;
498 /* Get data and dimensions of the image.
500 WARNING: This stuff is NOT threadsafe !!
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();
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.
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.
517 self = RrImageNewFromData(cache, data, w, h);
518 RrImageSetAddName(self->set, name);
520 imlib_free_image();
521 return self;
522 #endif
525 /************************************************************************
526 Image drawing and resizing operations.
527 **************************************************************************/
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).
536 static RrImagePic* ResizeImage(RrPixel32 *src,
537 gulong srcW, gulong srcH,
538 gulong dstW, gulong dstH)
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;
547 g_assert(srcW > 0);
548 g_assert(srcH > 0);
549 g_assert(dstW > 0);
550 g_assert(dstH > 0);
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));
559 dstW = aspectW ? aspectW : 1;
560 dstH = aspectH ? aspectH : 1;
562 if (srcW == dstW && srcH == dstH)
563 return NULL; /* no scaling needed! */
565 dststart = dst = g_new(RrPixel32, dstW * dstH);
567 ratioX = (srcW << FRACTION) / dstW;
568 ratioY = (srcH << FRACTION) / dstH;
570 srcY2 = 0;
571 for (dstY = 0; dstY < dstH; dstY++) {
572 srcY1 = srcY2;
573 srcY2 += ratioY;
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;
581 srcX1 = srcX2;
582 srcX2 += ratioX;
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;
591 else if (srcY == FLOOR(srcY2))
592 portionY = srcY2 - srcY;
593 else
594 portionY = (1UL << FRACTION);
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;
603 else if (srcX == FLOOR(srcX2))
604 portionX = srcX2 - srcX;
605 else
606 portionX = (1UL << FRACTION);
608 portionXY = (portionX * portionY) >> FRACTION;
609 sumXY += portionXY;
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;
624 g_assert(sumXY != 0);
625 red /= sumXY;
626 green /= sumXY;
627 blue /= sumXY;
628 alpha /= sumXY;
630 *dst++ = (red << RrDefaultRedOffset) |
631 (green << RrDefaultGreenOffset) |
632 (blue << RrDefaultBlueOffset) |
633 (alpha << RrDefaultAlphaOffset);
637 pic = g_slice_new(RrImagePic);
638 RrImagePicInit(pic, dstW, dstH, dststart);
640 return pic;
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)
650 RrPixel32 *dest;
651 gint col, num_pixels;
652 gint dw, dh;
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);
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));
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;
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;
681 /* background color */
682 bgr = *dest >> RrDefaultRedOffset;
683 bgg = *dest >> RrDefaultGreenOffset;
684 bgb = *dest >> RrDefaultBlueOffset;
686 r = bgr + (((r - bgr) * a) >> 8);
687 g = bgg + (((g - bgg) * a) >> 8);
688 b = bgb + (((b - bgb) * a) >> 8);
690 *dest = ((r << RrDefaultRedOffset) |
691 (g << RrDefaultGreenOffset) |
692 (b << RrDefaultBlueOffset));
694 dest++;
695 source++;
697 if (++col >= dw) {
698 col = 0;
699 dest += target_w - dw;
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)
709 RrImagePic *scaled;
711 scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
712 area->width, area->height);
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);
724 else
725 DrawRGBA(target, target_w, target_h,
726 rgba->data, rgba->width, rgba->height,
727 rgba->alpha, area);
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).
735 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
736 gint target_w, gint target_h,
737 RrRect *area)
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;
745 self = img->image;
746 set = self->set;
747 pic = NULL;
748 free_pic = FALSE;
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))
758 pic = set->original[i];
759 break;
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))
769 gint j;
770 RrImagePic *saved;
772 /* save the selected one */
773 saved = set->resized[i];
775 /* shift all the others down */
776 for (j = i; j > 0; --j)
777 set->resized[j] = set->resized[j-1];
779 /* and move the selected one to the top of the list */
780 set->resized[0] = saved;
782 pic = set->resized[0];
783 break;
786 if (!pic) {
787 gdouble aspect;
788 RrImageSet *cache_set;
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;
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);
806 /* find the smallest difference */
807 if (min_diff < 0 || diff < min_diff) {
808 min_diff = diff;
809 min_i = i;
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))
818 min_aspect_diff = diff;
819 min_aspect_i = i;
823 /* use the aspect ratio correct source if there is one */
824 if (min_aspect_i >= 0)
825 min_i = min_aspect_i;
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);
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;
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! */
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;
861 g_assert(pic != NULL);
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);