]> Dogcows Code - chaz/openbox/blob - obrender/image.c
Include rsvg-cairo.h for cairo-specific things
[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 #ifdef USE_LIBRSVG
28 #include <librsvg/rsvg.h>
29 #include <librsvg/rsvg-cairo.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 const gint max_resized = a->cache->max_resized_saved;
248
249 if (!a)
250 return b;
251 if (!b)
252 return a;
253 if (a == b)
254 return b;
255
256 /* the original and resized picture lists in an RrImageSet are kept ordered
257 as newest to oldest. we don't have timestamps for them, so we cannot
258 preserve this in the merged RrImageSet exactly. a decent approximation,
259 i think, is to add them in alternating order (one from a, one from b,
260 repeat). this way, the newest from each will be near the front at
261 least, and in the resized list, when we drop an old picture, we will
262 not always only drop from a or b only, but from each of them equally (or
263 from whichever has more resized pictures.
264 */
265
266 g_assert(b->cache == a->cache);
267
268 a_i = b_i = merged_i = 0;
269 n_original = a->n_original + b->n_original;
270 original = g_new(RrImagePic*, n_original);
271 while (merged_i < n_original) {
272 if (a_i < a->n_original)
273 original[merged_i++] = a->original[a_i++];
274 if (b_i < b->n_original)
275 original[merged_i++] = b->original[b_i++];
276 }
277
278 a_i = b_i = merged_i = 0;
279 n_resized = MIN(max_resized, a->n_resized + b->n_resized);
280 resized = g_new(RrImagePic*, n_resized);
281 while (merged_i < n_resized) {
282 if (a_i < a->n_resized)
283 resized[merged_i++] = a->resized[a_i++];
284 if (b_i < b->n_resized && merged_i < n_resized)
285 resized[merged_i++] = b->resized[b_i++];
286 }
287
288 /* if there are any RrImagePic objects left over in a->resized or
289 b->resized, they need to be disposed of, and removed from the cache.
290
291 updates the size of the list, as we want to remember which pointers
292 were merged from which list (and don't want to remember the ones we
293 did not merge and have freed).
294 */
295 tmp = a_i;
296 for (; a_i < a->n_resized; ++a_i) {
297 g_hash_table_remove(a->cache->pic_table, a->resized[a_i]);
298 RrImagePicFree(a->resized[a_i]);
299 }
300 a->n_resized = tmp;
301
302 tmp = b_i;
303 for (; b_i < b->n_resized; ++b_i) {
304 g_hash_table_remove(a->cache->pic_table, b->resized[b_i]);
305 RrImagePicFree(b->resized[b_i]);
306 }
307 b->n_resized = tmp;
308
309 /* we will use the a object as the merge destination, so things in b will
310 be moving.
311
312 the cache's name_table will point to b for all the names in b->names,
313 so these need to be updated to point at a instead.
314 also, the cache's pic_table will point to b for all the pictures in b,
315 so these need to be updated to point at a as well.
316
317 any RrImage objects that were using b should now use a instead.
318
319 the names and images will be all moved into a, and the merged picture
320 lists will be placed in a. the pictures in a and b are moved to new
321 arrays, so the arrays in a and b need to be freed explicitly (the
322 RrImageSetFree function would free the picture data too which we do not
323 want here). then b can be freed.
324 */
325
326 for (it = b->names; it; it = g_slist_next(it))
327 g_hash_table_insert(a->cache->name_table, it->data, a);
328 for (b_i = 0; b_i < b->n_original; ++b_i)
329 g_hash_table_insert(a->cache->pic_table, b->original[b_i], a);
330 for (b_i = 0; b_i < b->n_resized; ++b_i)
331 g_hash_table_insert(a->cache->pic_table, b->resized[b_i], a);
332
333 for (it = b->images; it; it = g_slist_next(it))
334 ((RrImage*)it->data)->set = a;
335
336 a->images = g_slist_concat(a->images, b->images);
337 b->images = NULL;
338 a->names = g_slist_concat(a->names, b->names);
339 b->names = NULL;
340
341 a->n_original = a->n_resized = 0;
342 g_free(a->original);
343 g_free(a->resized);
344 a->original = a->resized = NULL;
345 b->n_original = b->n_resized = 0;
346 g_free(b->original);
347 g_free(b->resized);
348 b->original = b->resized = NULL;
349
350 a->n_original = n_original;
351 a->original = original;
352 a->n_resized = n_resized;
353 a->resized = resized;
354
355 RrImageSetFree(b);
356
357 return a;
358 }
359
360 static void RrImageSetAddName(RrImageSet *set, const gchar *name)
361 {
362 gchar *n;
363
364 n = g_strdup(name);
365 set->names = g_slist_prepend(set->names, n);
366
367 /* add the new name to the hash table */
368 g_assert(g_hash_table_lookup(set->cache->name_table, n) == NULL);
369 g_hash_table_insert(set->cache->name_table, n, set);
370 }
371
372
373 /************************************************************************
374 RrImage functions.
375 **************************************************************************/
376
377
378 void RrImageRef(RrImage *self)
379 {
380 ++self->ref;
381 }
382
383 void RrImageUnref(RrImage *self)
384 {
385 if (self && --self->ref == 0) {
386 RrImageSet *set;
387 /*
388 #ifdef DEBUG
389 g_debug("Refcount to 0, removing ALL pictures from the cache:\n "
390 "Image 0x%lx", (gulong)self);
391 #endif
392 */
393 if (self->destroy_func)
394 self->destroy_func(self, self->destroy_data);
395
396 set = self->set;
397 set->images = g_slist_remove(set->images, self);
398
399 /* free the set as well if there are no images pointing to it */
400 if (!set->images)
401 RrImageSetFree(set);
402 g_slice_free(RrImage, self);
403 }
404 }
405
406 /*! Set function that will be called just before RrImage is destroyed. */
407 void RrImageSetDestroyFunc(RrImage *self, RrImageDestroyFunc func,
408 gpointer data)
409 {
410 self->destroy_func = func;
411 self->destroy_data = data;
412 }
413
414 void RrImageAddFromData(RrImage *self, RrPixel32 *data, gint w, gint h)
415 {
416 RrImagePic pic, *ppic;
417 RrImageSet *set;
418
419 g_return_if_fail(self != NULL);
420 g_return_if_fail(data != NULL);
421 g_return_if_fail(w > 0 && h > 0);
422
423 RrImagePicInit(&pic, w, h, data);
424 set = g_hash_table_lookup(self->set->cache->pic_table, &pic);
425 if (set)
426 self->set = RrImageSetMergeSets(self->set, set);
427 else {
428 ppic = RrImagePicNew(w, h, data);
429 RrImageSetAddPicture(self->set, ppic, TRUE);
430 }
431 }
432
433 RrImage* RrImageNewFromData(RrImageCache *cache, RrPixel32 *data,
434 gint w, gint h)
435 {
436 RrImagePic pic, *ppic;
437 RrImage *self;
438 RrImageSet *set;
439
440 g_return_val_if_fail(cache != NULL, NULL);
441 g_return_val_if_fail(data != NULL, NULL);
442 g_return_val_if_fail(w > 0 && h > 0, NULL);
443
444 /* finds a picture in the cache, if it is already in there, and use the
445 RrImageSet the picture lives in. */
446 RrImagePicInit(&pic, w, h, data);
447 set = g_hash_table_lookup(cache->pic_table, &pic);
448 if (set) {
449 self = set->images->data; /* just grab any RrImage from the list */
450 RrImageRef(self);
451 return self;
452 }
453
454 /* the image does not exist in any RrImageSet in the cache, so make
455 a new RrImageSet, and a new RrImage that points to it, and place the
456 new image inside the new RrImageSet */
457
458 self = g_slice_new0(RrImage);
459 self->ref = 1;
460 self->set = g_slice_new0(RrImageSet);
461 self->set->cache = cache;
462 self->set->images = g_slist_append(self->set->images, self);
463
464 ppic = RrImagePicNew(w, h, data);
465 RrImageSetAddPicture(self->set, ppic, TRUE);
466
467 return self;
468 }
469
470 #if defined(USE_IMLIB2)
471 typedef struct _ImlibLoader ImlibLoader;
472
473 struct _ImlibLoader
474 {
475 Imlib_Image img;
476 };
477
478 void DestroyImlibLoader(ImlibLoader *loader)
479 {
480 if (!loader)
481 return;
482
483 imlib_free_image();
484 g_slice_free(ImlibLoader, loader);
485 }
486
487 ImlibLoader* LoadWithImlib(gchar *path,
488 RrPixel32 **pixel_data,
489 gint *width,
490 gint *height)
491 {
492 ImlibLoader *loader = g_slice_new0(ImlibLoader);
493 if (!(loader->img = imlib_load_image(path))) {
494 DestroyImlibLoader(loader);
495 return NULL;
496 }
497
498 /* Get data and dimensions of the image.
499
500 WARNING: This stuff is NOT threadsafe !!
501 */
502 imlib_context_set_image(loader->img);
503 *pixel_data = imlib_image_get_data_for_reading_only();
504 *width = imlib_image_get_width();
505 *height = imlib_image_get_height();
506
507 return loader;
508 }
509 #endif /* USE_IMLIB2 */
510
511 #if defined(USE_LIBRSVG)
512 typedef struct _RsvgLoader RsvgLoader;
513
514 struct _RsvgLoader
515 {
516 RsvgHandle *handle;
517 cairo_surface_t *surface;
518 RrPixel32 *pixel_data;
519 };
520
521 void DestroyRsvgLoader(RsvgLoader *loader)
522 {
523 if (!loader)
524 return;
525
526 if (loader->pixel_data)
527 g_free(loader->pixel_data);
528 if (loader->surface)
529 cairo_surface_destroy(loader->surface);
530 if (loader->handle)
531 g_object_unref(loader->handle);
532 g_slice_free(RsvgLoader, loader);
533 }
534
535 RsvgLoader* LoadWithRsvg(gchar *path,
536 RrPixel32 **pixel_data,
537 gint *width,
538 gint *height)
539 {
540 RsvgLoader *loader = g_slice_new0(RsvgLoader);
541
542 if (!(loader->handle = rsvg_handle_new_from_file(path, NULL))) {
543 DestroyRsvgLoader(loader);
544 return NULL;
545 }
546
547 if (!rsvg_handle_close(loader->handle, NULL)) {
548 DestroyRsvgLoader(loader);
549 return NULL;
550 }
551
552 RsvgDimensionData dimension_data;
553 rsvg_handle_get_dimensions(loader->handle, &dimension_data);
554 *width = dimension_data.width;
555 *height = dimension_data.height;
556
557 loader->surface = cairo_image_surface_create(
558 CAIRO_FORMAT_ARGB32, *width, *height);
559
560 cairo_t* context = cairo_create(loader->surface);
561 gboolean success = rsvg_handle_render_cairo(loader->handle, context);
562 cairo_destroy(context);
563
564 if (!success) {
565 DestroyRsvgLoader(loader);
566 return NULL;
567 }
568
569 loader->pixel_data = g_new(guint32, *width * *height);
570
571 /*
572 Cairo has its data in ARGB with premultiplied alpha, but RrPixel32
573 non-premultipled, so convert that. Also, RrPixel32 doesn't allow
574 strides not equal to the width of the image.
575 */
576
577 /* Verify that RrPixel32 has the same ordering as cairo. */
578 g_assert(RrDefaultAlphaOffset == 24);
579 g_assert(RrDefaultRedOffset == 16);
580 g_assert(RrDefaultGreenOffset == 8);
581 g_assert(RrDefaultBlueOffset == 0);
582
583 guint32 *out_row = loader->pixel_data;
584
585 guint32 *in_row =
586 (guint32*)cairo_image_surface_get_data(loader->surface);
587 gint in_stride = cairo_image_surface_get_stride(loader->surface);
588
589 gint y;
590 for (y = 0; y < *height; ++y) {
591 gint x;
592 for (x = 0; x < *width; ++x) {
593 guchar a = in_row[x] >> 24;
594 guchar r = (in_row[x] >> 16) & 0xff;
595 guchar g = (in_row[x] >> 8) & 0xff;
596 guchar b = in_row[x] & 0xff;
597 out_row[x] =
598 ((r * 256 / (a + 1)) << RrDefaultRedOffset) +
599 ((g * 256 / (a + 1)) << RrDefaultGreenOffset) +
600 ((b * 256 / (a + 1)) << RrDefaultBlueOffset) +
601 (a << RrDefaultAlphaOffset);
602 }
603 in_row += in_stride / 4;
604 out_row += *width;
605 }
606
607 *pixel_data = loader->pixel_data;
608
609 return loader;
610 }
611 #endif /* USE_LIBRSVG */
612
613 RrImage* RrImageNewFromName(RrImageCache *cache, const gchar *name)
614 {
615 RrImage *self;
616 RrImageSet *set;
617 gint w, h;
618 RrPixel32 *data;
619 gchar *path;
620 gboolean loaded;
621
622 #if defined(USE_IMLIB2)
623 ImlibLoader *imlib_loader = NULL;
624 #endif
625 #if defined(USE_LIBRSVG)
626 RsvgLoader *rsvg_loader = NULL;
627 #endif
628
629 g_return_val_if_fail(cache != NULL, NULL);
630 g_return_val_if_fail(name != NULL, NULL);
631
632 set = g_hash_table_lookup(cache->name_table, name);
633 if (set) {
634 self = set->images->data;
635 RrImageRef(self);
636 return self;
637 }
638
639 /* XXX find the path via freedesktop icon spec (use obt) ! */
640 path = g_strdup(name);
641
642 loaded = FALSE;
643 #if defined(USE_LIBRSVG)
644 if (!loaded) {
645 rsvg_loader = LoadWithRsvg(path, &data, &w, &h);
646 loaded = !!rsvg_loader;
647 }
648 #endif
649 #if defined(USE_IMLIB2)
650 if (!loaded) {
651 imlib_loader = LoadWithImlib(path, &data, &w, &h);
652 loaded = !!imlib_loader;
653 }
654 #endif
655
656 if (!loaded) {
657 g_message("Cannot load image \"%s\" from file \"%s\"", name, path);
658 g_free(path);
659 #if defined(USE_LIBRSVG)
660 DestroyRsvgLoader(rsvg_loader);
661 #endif
662 #if defined(USE_IMLIB2)
663 DestroyImlibLoader(imlib_loader);
664 #endif
665 return NULL;
666 }
667
668 g_free(path);
669
670 /* get an RrImage that contains an RrImageSet with this picture in it.
671 the RrImage might be new, or reused if the picture was already in the
672 cache.
673
674 either way, we get back an RrImageSet (via the RrImage), and we must add
675 the name to that RrImageSet. because of the check above, we know that
676 there is no RrImageSet in the cache which already has the given name
677 asosciated with it.
678 */
679
680 self = RrImageNewFromData(cache, data, w, h);
681 RrImageSetAddName(self->set, name);
682
683 #if defined(USE_LIBRSVG)
684 DestroyRsvgLoader(rsvg_loader);
685 #endif
686 #if defined(USE_IMLIB2)
687 DestroyImlibLoader(imlib_loader);
688 #endif
689
690 return self;
691 }
692
693 /************************************************************************
694 Image drawing and resizing operations.
695 **************************************************************************/
696
697 /*! Given a picture in RGBA format, of a specified size, resize it to the new
698 requested size (but keep its aspect ratio). If the image does not need to
699 be resized (it is already the right size) then this returns NULL. Otherwise
700 it returns a newly allocated RrImagePic with the resized picture inside it
701 @return Returns a newly allocated RrImagePic object with a new version of the
702 image in the requested size (keeping aspect ratio).
703 */
704 static RrImagePic* ResizeImage(RrPixel32 *src,
705 gulong srcW, gulong srcH,
706 gulong dstW, gulong dstH)
707 {
708 RrPixel32 *dst, *dststart;
709 RrImagePic *pic;
710 gulong dstX, dstY, srcX, srcY;
711 gulong srcX1, srcX2, srcY1, srcY2;
712 gulong ratioX, ratioY;
713 gulong aspectW, aspectH;
714
715 g_assert(srcW > 0);
716 g_assert(srcH > 0);
717 g_assert(dstW > 0);
718 g_assert(dstH > 0);
719
720 /* keep the aspect ratio */
721 aspectW = dstW;
722 aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
723 if (aspectH > dstH) {
724 aspectH = dstH;
725 aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
726 }
727 dstW = aspectW ? aspectW : 1;
728 dstH = aspectH ? aspectH : 1;
729
730 if (srcW == dstW && srcH == dstH)
731 return NULL; /* no scaling needed! */
732
733 dststart = dst = g_new(RrPixel32, dstW * dstH);
734
735 ratioX = (srcW << FRACTION) / dstW;
736 ratioY = (srcH << FRACTION) / dstH;
737
738 srcY2 = 0;
739 for (dstY = 0; dstY < dstH; dstY++) {
740 srcY1 = srcY2;
741 srcY2 += ratioY;
742
743 srcX2 = 0;
744 for (dstX = 0; dstX < dstW; dstX++) {
745 gulong red = 0, green = 0, blue = 0, alpha = 0;
746 gulong portionX, portionY, portionXY, sumXY = 0;
747 RrPixel32 pixel;
748
749 srcX1 = srcX2;
750 srcX2 += ratioX;
751
752 for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) {
753 if (srcY == srcY1) {
754 srcY = FLOOR(srcY);
755 portionY = (1UL << FRACTION) - (srcY1 - srcY);
756 if (portionY > srcY2 - srcY1)
757 portionY = srcY2 - srcY1;
758 }
759 else if (srcY == FLOOR(srcY2))
760 portionY = srcY2 - srcY;
761 else
762 portionY = (1UL << FRACTION);
763
764 for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) {
765 if (srcX == srcX1) {
766 srcX = FLOOR(srcX);
767 portionX = (1UL << FRACTION) - (srcX1 - srcX);
768 if (portionX > srcX2 - srcX1)
769 portionX = srcX2 - srcX1;
770 }
771 else if (srcX == FLOOR(srcX2))
772 portionX = srcX2 - srcX;
773 else
774 portionX = (1UL << FRACTION);
775
776 portionXY = (portionX * portionY) >> FRACTION;
777 sumXY += portionXY;
778
779 pixel = *(src + (srcY >> FRACTION) * srcW
780 + (srcX >> FRACTION));
781 red += ((pixel >> RrDefaultRedOffset) & 0xFF)
782 * portionXY;
783 green += ((pixel >> RrDefaultGreenOffset) & 0xFF)
784 * portionXY;
785 blue += ((pixel >> RrDefaultBlueOffset) & 0xFF)
786 * portionXY;
787 alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF)
788 * portionXY;
789 }
790 }
791
792 g_assert(sumXY != 0);
793 red /= sumXY;
794 green /= sumXY;
795 blue /= sumXY;
796 alpha /= sumXY;
797
798 *dst++ = (red << RrDefaultRedOffset) |
799 (green << RrDefaultGreenOffset) |
800 (blue << RrDefaultBlueOffset) |
801 (alpha << RrDefaultAlphaOffset);
802 }
803 }
804
805 pic = g_slice_new(RrImagePic);
806 RrImagePicInit(pic, dstW, dstH, dststart);
807
808 return pic;
809 }
810
811 /*! This draws an RGBA picture into the target, within the rectangle specified
812 by the area parameter. If the area's size differs from the source's then it
813 will be centered within the rectangle */
814 void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h,
815 RrPixel32 *source, gint source_w, gint source_h,
816 gint alpha, RrRect *area)
817 {
818 RrPixel32 *dest;
819 gint col, num_pixels;
820 gint dw, dh;
821
822 g_assert(source_w <= area->width && source_h <= area->height);
823 g_assert(area->x + area->width <= target_w);
824 g_assert(area->y + area->height <= target_h);
825
826 /* keep the aspect ratio */
827 dw = area->width;
828 dh = (gint)(dw * ((gdouble)source_h / source_w));
829 if (dh > area->height) {
830 dh = area->height;
831 dw = (gint)(dh * ((gdouble)source_w / source_h));
832 }
833
834 /* copy source -> dest, and apply the alpha channel.
835 center the image if it is smaller than the area */
836 col = 0;
837 num_pixels = dw * dh;
838 dest = target + area->x + (area->width - dw) / 2 +
839 (target_w * (area->y + (area->height - dh) / 2));
840 while (num_pixels-- > 0) {
841 guchar a, r, g, b, bgr, bgg, bgb;
842
843 /* apply the rgba's opacity as well */
844 a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8;
845 r = *source >> RrDefaultRedOffset;
846 g = *source >> RrDefaultGreenOffset;
847 b = *source >> RrDefaultBlueOffset;
848
849 /* background color */
850 bgr = *dest >> RrDefaultRedOffset;
851 bgg = *dest >> RrDefaultGreenOffset;
852 bgb = *dest >> RrDefaultBlueOffset;
853
854 r = bgr + (((r - bgr) * a) >> 8);
855 g = bgg + (((g - bgg) * a) >> 8);
856 b = bgb + (((b - bgb) * a) >> 8);
857
858 *dest = ((r << RrDefaultRedOffset) |
859 (g << RrDefaultGreenOffset) |
860 (b << RrDefaultBlueOffset));
861
862 dest++;
863 source++;
864
865 if (++col >= dw) {
866 col = 0;
867 dest += target_w - dw;
868 }
869 }
870 }
871
872 /*! Draw an RGBA texture into a target pixel buffer. */
873 void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
874 gint target_w, gint target_h,
875 RrRect *area)
876 {
877 RrImagePic *scaled;
878
879 scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
880 area->width, area->height);
881
882 if (scaled) {
883 #ifdef DEBUG
884 g_warning("Scaling an RGBA! You should avoid this and just make "
885 "it the right size yourself!");
886 #endif
887 DrawRGBA(target, target_w, target_h,
888 scaled->data, scaled->width, scaled->height,
889 rgba->alpha, area);
890 RrImagePicFree(scaled);
891 }
892 else
893 DrawRGBA(target, target_w, target_h,
894 rgba->data, rgba->width, rgba->height,
895 rgba->alpha, area);
896 }
897
898 /*! Draw an RrImage texture into a target pixel buffer. If the RrImage does
899 not contain a picture of the appropriate size, then one of its "original"
900 pictures will be resized and used (and stored in the RrImage as a "resized"
901 picture).
902 */
903 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
904 gint target_w, gint target_h,
905 RrRect *area)
906 {
907 gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
908 RrImage *self;
909 RrImageSet *set;
910 RrImagePic *pic;
911 gboolean free_pic;
912
913 self = img->image;
914 set = self->set;
915 pic = NULL;
916 free_pic = FALSE;
917
918 /* is there an original of this size? (only the larger of
919 w or h has to be right cuz we maintain aspect ratios) */
920 for (i = 0; i < set->n_original; ++i)
921 if ((set->original[i]->width >= set->original[i]->height &&
922 set->original[i]->width == area->width) ||
923 (set->original[i]->width <= set->original[i]->height &&
924 set->original[i]->height == area->height))
925 {
926 pic = set->original[i];
927 break;
928 }
929
930 /* is there a resize of this size? */
931 for (i = 0; i < set->n_resized; ++i)
932 if ((set->resized[i]->width >= set->resized[i]->height &&
933 set->resized[i]->width == area->width) ||
934 (set->resized[i]->width <= set->resized[i]->height &&
935 set->resized[i]->height == area->height))
936 {
937 gint j;
938 RrImagePic *saved;
939
940 /* save the selected one */
941 saved = set->resized[i];
942
943 /* shift all the others down */
944 for (j = i; j > 0; --j)
945 set->resized[j] = set->resized[j-1];
946
947 /* and move the selected one to the top of the list */
948 set->resized[0] = saved;
949
950 pic = set->resized[0];
951 break;
952 }
953
954 if (!pic) {
955 gdouble aspect;
956 RrImageSet *cache_set;
957
958 /* find an original with a close size */
959 min_diff = min_aspect_diff = -1;
960 min_i = min_aspect_i = 0;
961 aspect = ((gdouble)area->width) / area->height;
962 for (i = 0; i < set->n_original; ++i) {
963 gint diff;
964 gint wdiff, hdiff;
965 gdouble myasp;
966
967 /* our size difference metric.. */
968 wdiff = set->original[i]->width - area->width;
969 if (wdiff < 0) wdiff *= 2; /* prefer scaling down than up */
970 hdiff = set->original[i]->height - area->height;
971 if (hdiff < 0) hdiff *= 2; /* prefer scaling down than up */
972 diff = (wdiff * wdiff) + (hdiff * hdiff);
973
974 /* find the smallest difference */
975 if (min_diff < 0 || diff < min_diff) {
976 min_diff = diff;
977 min_i = i;
978 }
979 /* and also find the smallest difference with the same aspect
980 ratio (and prefer this one) */
981 myasp = ((gdouble)set->original[i]->width) /
982 set->original[i]->height;
983 if (ABS(aspect - myasp) < 0.0000001 &&
984 (min_aspect_diff < 0 || diff < min_aspect_diff))
985 {
986 min_aspect_diff = diff;
987 min_aspect_i = i;
988 }
989 }
990
991 /* use the aspect ratio correct source if there is one */
992 if (min_aspect_i >= 0)
993 min_i = min_aspect_i;
994
995 /* resize the original to the given area */
996 pic = ResizeImage(set->original[min_i]->data,
997 set->original[min_i]->width,
998 set->original[min_i]->height,
999 area->width, area->height);
1000
1001 /* is it already in the cache ? */
1002 cache_set = g_hash_table_lookup(set->cache->pic_table, pic);
1003 if (cache_set) {
1004 /* merge this set with the one found in the cache - they are
1005 apparently the same image ! then next time we won't have to do
1006 this resizing, we will use the cache_set's pic instead. */
1007 set = RrImageSetMergeSets(set, cache_set);
1008 free_pic = TRUE;
1009 }
1010 else {
1011 /* add the resized image to the image, as the first in the resized
1012 list */
1013 while (set->n_resized >= set->cache->max_resized_saved)
1014 /* remove the last one (last used one) to make space for
1015 adding our resized picture */
1016 RrImageSetRemovePictureAt(set, set->n_resized-1, FALSE);
1017 if (set->cache->max_resized_saved)
1018 /* add it to the resized list */
1019 RrImageSetAddPicture(set, pic, FALSE);
1020 else
1021 free_pic = TRUE; /* don't leak mem! */
1022 }
1023 }
1024
1025 /* The RrImageSet may have changed if we merged it with another, so the
1026 RrImage object needs to be updated to use the new merged RrImageSet. */
1027 self->set = set;
1028
1029 g_assert(pic != NULL);
1030
1031 DrawRGBA(target, target_w, target_h,
1032 pic->data, pic->width, pic->height,
1033 img->alpha, area);
1034 if (free_pic)
1035 RrImagePicFree(pic);
1036 }
This page took 0.074918 seconds and 4 git commands to generate.