]> Dogcows Code - chaz/openbox/blob - obrender/image.c
animateIconify was accidentally duplicated in the schema
[chaz/openbox] / 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 }
This page took 0.068774 seconds and 4 git commands to generate.