Merge branch 'backport' into work
[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 void RrImagePicInit(RrImagePic *pic, const gchar *name,
35                     gint w, gint h, RrPixel32 *data)
36 {
37     gint i;
38
39     pic->width = w;
40     pic->height = h;
41     pic->data = data;
42     pic->sum = 0;
43     for (i = w*h; i > 0; --i)
44         pic->sum += *(data++);
45     pic->name = g_strdup(name);
46 }
47
48 static void RrImagePicFree(RrImagePic *pic)
49 {
50     if (pic) {
51         g_free(pic->data);
52         g_free(pic->name);
53         g_free(pic);
54     }
55 }
56
57 /*! Add a picture to an Image, that is, add another copy of the image at
58   another size.  This may add it to the "originals" list or to the
59   "resized" list. */
60 static void AddPicture(RrImage *self, RrImagePic ***list, gint *len,
61                        RrImagePic *pic)
62 {
63     gint i;
64
65     g_assert(pic->width > 0 && pic->height > 0);
66
67     if (pic->name)
68         g_assert(!g_hash_table_lookup(self->cache->name_table, pic->name));
69     else
70         g_assert(!g_hash_table_lookup(self->cache->pic_table, pic));
71
72     /* grow the list */
73     *list = g_renew(RrImagePic*, *list, ++*len);
74
75     /* move everything else down one */
76     for (i = *len-1; i > 0; --i)
77         (*list)[i] = (*list)[i-1];
78
79     /* set the new picture up at the front of the list */
80     (*list)[0] = pic;
81
82     /* add the name or the picture as a key to point to this image in the
83        cache */
84     if (pic->name)
85         g_hash_table_insert(self->cache->name_table, pic->name, self);
86     else
87         g_hash_table_insert(self->cache->pic_table, (*list)[0], self);
88
89 /*
90 #ifdef DEBUG
91     g_debug("Adding %s picture to the cache:\n    "
92             "Image 0x%lx, w %d h %d Hash %u",
93             (*list == self->original ? "ORIGINAL" : "RESIZED"),
94             (gulong)self, pic->width, pic->height, RrImagePicHash(pic));
95 #endif
96 */
97 }
98
99 /*! Remove a picture from an Image.  This may remove it from the "originals"
100   list or the "resized" list. */
101 static void RemovePicture(RrImage *self, RrImagePic ***list,
102                           gint i, gint *len)
103 {
104     gint j;
105
106 /*
107 #ifdef DEBUG
108     g_debug("Removing %s picture from the cache:\n    "
109             "Image 0x%lx, w %d h %d Hash %u",
110             (*list == self->original ? "ORIGINAL" : "RESIZED"),
111             (gulong)self, (*list)[i]->width, (*list)[i]->height,
112             RrImagePicHash((*list)[i]));
113 #endif
114 */
115
116     /* remove the name or picture as a key in the cache */
117     if ((*list)[i]->name)
118         g_hash_table_remove(self->cache->name_table, (*list)[i]->name);
119     else
120         g_hash_table_remove(self->cache->pic_table, (*list)[i]);
121
122     /* free the picture */
123     RrImagePicFree((*list)[i]);
124     /* shift everything down one */
125     for (j = i; j < *len-1; ++j)
126         (*list)[j] = (*list)[j+1];
127     /* shrink the list */
128     *list = g_renew(RrImagePic*, *list, --*len);
129 }
130
131 /*! Given a picture in RGBA format, of a specified size, resize it to the new
132   requested size (but keep its aspect ratio).  If the image does not need to
133   be resized (it is already the right size) then this returns NULL.  Otherwise
134   it returns a newly allocated RrImagePic with the resized picture inside it
135 */
136 static RrImagePic* ResizeImage(RrPixel32 *src,
137                                gulong srcW, gulong srcH,
138                                gulong dstW, gulong dstH)
139 {
140     RrPixel32 *dst, *dststart;
141     RrImagePic *pic;
142     gulong dstX, dstY, srcX, srcY;
143     gulong srcX1, srcX2, srcY1, srcY2;
144     gulong ratioX, ratioY;
145     gulong aspectW, aspectH;
146
147     /* XXX should these variables be ensured to not be zero in the callers? */
148     srcW = srcW ? srcW : 1;
149     srcH = srcH ? srcH : 1;
150     dstW = dstW ? dstW : 1;
151     dstH = dstH ? dstH : 1;
152
153     /* keep the aspect ratio */
154     aspectW = dstW;
155     aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
156     if (aspectH > dstH) {
157         aspectH = dstH;
158         aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
159     }
160     dstW = aspectW ? aspectW : 1;
161     dstH = aspectH ? aspectH : 1;
162
163     if (srcW == dstW && srcH == dstH)
164         return NULL; /* no scaling needed! */
165
166     dststart = dst = g_new(RrPixel32, dstW * dstH);
167
168     ratioX = (srcW << FRACTION) / dstW;
169     ratioY = (srcH << FRACTION) / dstH;
170
171     srcY2 = 0;
172     for (dstY = 0; dstY < dstH; dstY++) {
173         srcY1 = srcY2;
174         srcY2 += ratioY;
175
176         srcX2 = 0;
177         for (dstX = 0; dstX < dstW; dstX++) {
178             gulong red = 0, green = 0, blue = 0, alpha = 0;
179             gulong portionX, portionY, portionXY, sumXY = 0;
180             RrPixel32 pixel;
181
182             srcX1 = srcX2;
183             srcX2 += ratioX;
184
185             for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) {
186                 if (srcY == srcY1) {
187                     srcY = FLOOR(srcY);
188                     portionY = (1UL << FRACTION) - (srcY1 - srcY);
189                     if (portionY > srcY2 - srcY1)
190                         portionY = srcY2 - srcY1;
191                 }
192                 else if (srcY == FLOOR(srcY2))
193                     portionY = srcY2 - srcY;
194                 else
195                     portionY = (1UL << FRACTION);
196
197                 for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) {
198                     if (srcX == srcX1) {
199                         srcX = FLOOR(srcX);
200                         portionX = (1UL << FRACTION) - (srcX1 - srcX);
201                         if (portionX > srcX2 - srcX1)
202                             portionX = srcX2 - srcX1;
203                     }
204                     else if (srcX == FLOOR(srcX2))
205                         portionX = srcX2 - srcX;
206                     else
207                         portionX = (1UL << FRACTION);
208
209                     portionXY = (portionX * portionY) >> FRACTION;
210                     sumXY += portionXY;
211
212                     pixel = *(src + (srcY >> FRACTION) * srcW
213                             + (srcX >> FRACTION));
214                     red   += ((pixel >> RrDefaultRedOffset)   & 0xFF)
215                              * portionXY;
216                     green += ((pixel >> RrDefaultGreenOffset) & 0xFF)
217                              * portionXY;
218                     blue  += ((pixel >> RrDefaultBlueOffset)  & 0xFF)
219                              * portionXY;
220                     alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF)
221                              * portionXY;
222                 }
223             }
224
225             g_assert(sumXY != 0);
226             red   /= sumXY;
227             green /= sumXY;
228             blue  /= sumXY;
229             alpha /= sumXY;
230
231             *dst++ = (red   << RrDefaultRedOffset)   |
232                      (green << RrDefaultGreenOffset) |
233                      (blue  << RrDefaultBlueOffset)  |
234                      (alpha << RrDefaultAlphaOffset);
235         }
236     }
237
238     pic = g_new(RrImagePic, 1);
239     RrImagePicInit(pic, NULL, dstW, dstH, dststart);
240
241     return pic;
242 }
243
244 /*! This draws an RGBA picture into the target, within the rectangle specified
245   by the area parameter.  If the area's size differs from the source's then it
246   will be centered within the rectangle */
247 void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h,
248               RrPixel32 *source, gint source_w, gint source_h,
249               gint alpha, RrRect *area)
250 {
251     RrPixel32 *dest;
252     gint col, num_pixels;
253     gint dw, dh;
254
255     g_assert(source_w <= area->width && source_h <= area->height);
256     g_assert(area->x + area->width <= target_w);
257     g_assert(area->y + area->height <= target_h);
258
259     /* keep the aspect ratio */
260     dw = area->width;
261     dh = (gint)(dw * ((gdouble)source_h / source_w));
262     if (dh > area->height) {
263         dh = area->height;
264         dw = (gint)(dh * ((gdouble)source_w / source_h));
265     }
266
267     /* copy source -> dest, and apply the alpha channel.
268        center the image if it is smaller than the area */
269     col = 0;
270     num_pixels = dw * dh;
271     dest = target + area->x + (area->width - dw) / 2 +
272         (target_w * (area->y + (area->height - dh) / 2));
273     while (num_pixels-- > 0) {
274         guchar a, r, g, b, bgr, bgg, bgb;
275
276         /* apply the rgba's opacity as well */
277         a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8;
278         r = *source >> RrDefaultRedOffset;
279         g = *source >> RrDefaultGreenOffset;
280         b = *source >> RrDefaultBlueOffset;
281
282         /* background color */
283         bgr = *dest >> RrDefaultRedOffset;
284         bgg = *dest >> RrDefaultGreenOffset;
285         bgb = *dest >> RrDefaultBlueOffset;
286
287         r = bgr + (((r - bgr) * a) >> 8);
288         g = bgg + (((g - bgg) * a) >> 8);
289         b = bgb + (((b - bgb) * a) >> 8);
290
291         *dest = ((r << RrDefaultRedOffset) |
292                  (g << RrDefaultGreenOffset) |
293                  (b << RrDefaultBlueOffset));
294
295         dest++;
296         source++;
297
298         if (++col >= dw) {
299             col = 0;
300             dest += target_w - dw;
301         }
302     }
303 }
304
305 /*! Draw an RGBA texture into a target pixel buffer. */
306 void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
307                      gint target_w, gint target_h,
308                      RrRect *area)
309 {
310     RrImagePic *scaled;
311
312     scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
313                          area->width, area->height);
314
315     if (scaled) {
316 #ifdef DEBUG
317             g_warning("Scaling an RGBA! You should avoid this and just make "
318                       "it the right size yourself!");
319 #endif
320             DrawRGBA(target, target_w, target_h,
321                      scaled->data, scaled->width, scaled->height,
322                      rgba->alpha, area);
323             RrImagePicFree(scaled);
324     }
325     else
326         DrawRGBA(target, target_w, target_h,
327                  rgba->data, rgba->width, rgba->height,
328                  rgba->alpha, area);
329 }
330
331 /*! Create a new RrImage, which is linked to an image cache */
332 RrImage* RrImageNew(RrImageCache *cache)
333 {
334     RrImage *self;
335
336     g_assert(cache != NULL);
337
338     self = g_new0(RrImage, 1);
339     self->ref = 1;
340     self->cache = cache;
341     return self;
342 }
343
344 /*! Set function that will be called just before RrImage is destroyed. */
345 void RrImageSetDestroyFunc(RrImage *image, RrImageDestroyFunc func,
346                            gpointer data)
347 {
348     image->destroy_func = func;
349     image->destroy_data = data;
350 }
351
352 void RrImageRef(RrImage *self)
353 {
354     ++self->ref;
355 }
356
357 void RrImageUnref(RrImage *self)
358 {
359     if (self && --self->ref == 0) {
360 /*
361 #ifdef DEBUG
362         g_debug("Refcount to 0, removing ALL pictures from the cache:\n    "
363                 "Image 0x%lx", (gulong)self);
364 #endif
365 */
366         if (self->destroy_func)
367             self->destroy_func(self, self->destroy_data);
368         while (self->n_original > 0)
369             RemovePicture(self, &self->original, 0, &self->n_original);
370         while (self->n_resized > 0)
371             RemovePicture(self, &self->resized, 0, &self->n_resized);
372         g_free(self);
373     }
374 }
375
376 static void AddPictureFromData(RrImage *self, const char *name,
377                                const RrPixel32 *data, gint w, gint h)
378 {
379     gint i;
380     RrImagePic *pic;
381
382     /* make sure we don't already have this size.. */
383     for (i = 0; i < self->n_original; ++i)
384         if (self->original[i]->width == w && self->original[i]->height == h) {
385 /*
386 #ifdef DEBUG
387             g_debug("Found duplicate ORIGINAL image:\n    "
388                     "Image 0x%lx, w %d h %d", (gulong)self, w, h);
389 #endif
390 */
391             return;
392         }
393
394     /* remove any resized pictures of this same size */
395     for (i = 0; i < self->n_resized; ++i)
396         if (self->resized[i]->width == w || self->resized[i]->height == h) {
397             RemovePicture(self, &self->resized, i, &self->n_resized);
398             break;
399         }
400
401     /* add the new picture */
402     pic = g_new(RrImagePic, 1);
403     RrImagePicInit(pic, name, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
404     AddPicture(self, &self->original, &self->n_original, pic);
405 }
406
407 gboolean RrImageAddPictureName(RrImage *self, const gchar *name)
408 {
409 #ifdef USE_IMLIB2
410     Imlib_Image img;
411     gint w, h;
412     RrPixel32 *data;
413     gchar *path;
414
415     /* XXX find the path via freedesktop icon spec (use obt) ! */
416     path = g_strdup(name);
417
418     if (!(img = imlib_load_image(path)))
419         g_message("Cannot load image \"%s\" from file \"%s\"", name, path);
420     g_free(path);
421
422     if (!img)
423         return FALSE; /* failed to load it */
424
425     /* Get data and dimensions of the image.
426
427        WARNING: This stuff is NOT threadsafe !!
428     */
429     imlib_context_set_image(img);
430     data = imlib_image_get_data_for_reading_only();
431     w = imlib_image_get_width();
432     h = imlib_image_get_height();
433
434     /* add it to the RrImage, and set its name */
435     AddPictureFromData(self, name, data, w, h);
436
437     imlib_free_image();
438     return TRUE;
439 #else
440     return FALSE;
441 #endif
442 }
443
444 /*! Add a new picture with the given RGBA pixel data and dimensions into the
445   RrImage.  This adds an "original" picture to the image.
446 */
447 void RrImageAddPicture(RrImage *self, const RrPixel32 *data, gint w, gint h)
448 {
449     AddPictureFromData(self, NULL, data, w, h);    
450 }
451
452 /*! Remove the picture from the RrImage which has the given dimensions. This
453  removes an "original" picture from the image.
454 */
455 void RrImageRemovePicture(RrImage *self, gint w, gint h)
456 {
457     gint i;
458
459     /* remove any resized pictures of this same size */
460     for (i = 0; i < self->n_original; ++i)
461         if (self->original[i]->width == w && self->original[i]->height == h) {
462             RemovePicture(self, &self->original, i, &self->n_original);
463             break;
464         }
465 }
466
467 /*! Draw an RrImage texture into a target pixel buffer.  If the RrImage does
468   not contain a picture of the appropriate size, then one of its "original"
469   pictures will be resized and used (and stored in the RrImage as a "resized"
470   picture).
471  */
472 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
473                       gint target_w, gint target_h,
474                       RrRect *area)
475 {
476     gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
477     RrImage *self;
478     RrImagePic *pic;
479     gboolean free_pic;
480
481     self = img->image;
482     pic = NULL;
483     free_pic = FALSE;
484
485     /* is there an original of this size? (only the larger of
486        w or h has to be right cuz we maintain aspect ratios) */
487     for (i = 0; i < self->n_original; ++i)
488         if ((self->original[i]->width >= self->original[i]->height &&
489              self->original[i]->width == area->width) ||
490             (self->original[i]->width <= self->original[i]->height &&
491              self->original[i]->height == area->height))
492         {
493             pic = self->original[i];
494             break;
495         }
496
497     /* is there a resize of this size? */
498     for (i = 0; i < self->n_resized; ++i)
499         if ((self->resized[i]->width >= self->resized[i]->height &&
500              self->resized[i]->width == area->width) ||
501             (self->resized[i]->width <= self->resized[i]->height &&
502              self->resized[i]->height == area->height))
503         {
504             gint j;
505             RrImagePic *saved;
506
507             /* save the selected one */
508             saved = self->resized[i];
509
510             /* shift all the others down */
511             for (j = i; j > 0; --j)
512                 self->resized[j] = self->resized[j-1];
513
514             /* and move the selected one to the top of the list */
515             self->resized[0] = saved;
516
517             pic = self->resized[0];
518             break;
519         }
520
521     if (!pic) {
522         gdouble aspect;
523
524         /* find an original with a close size */
525         min_diff = min_aspect_diff = -1;
526         min_i = min_aspect_i = 0;
527         aspect = ((gdouble)area->width) / area->height;
528         for (i = 0; i < self->n_original; ++i) {
529             gint diff;
530             gint wdiff, hdiff;
531             gdouble myasp;
532
533             /* our size difference metric.. */
534             wdiff = self->original[i]->width - area->width;
535             if (wdiff < 0) wdiff *= 2; /* prefer scaling down than up */
536             hdiff = self->original[i]->height - area->height;
537             if (hdiff < 0) hdiff *= 2; /* prefer scaling down than up */
538             diff = (wdiff * wdiff) + (hdiff * hdiff);
539
540             /* find the smallest difference */
541             if (min_diff < 0 || diff < min_diff) {
542                 min_diff = diff;
543                 min_i = i;
544             }
545             /* and also find the smallest difference with the same aspect
546                ratio (and prefer this one) */
547             myasp = ((gdouble)self->original[i]->width) /
548                 self->original[i]->height;
549             if (ABS(aspect - myasp) < 0.0000001 &&
550                 (min_aspect_diff < 0 || diff < min_aspect_diff))
551             {
552                 min_aspect_diff = diff;
553                 min_aspect_i = i;
554             }
555         }
556
557         /* use the aspect ratio correct source if there is one */
558         if (min_aspect_i >= 0)
559             min_i = min_aspect_i;
560
561         /* resize the original to the given area */
562         pic = ResizeImage(self->original[min_i]->data,
563                           self->original[min_i]->width,
564                           self->original[min_i]->height,
565                           area->width, area->height);
566
567         /* add the resized image to the image, as the first in the resized
568            list */
569         if (self->n_resized >= self->cache->max_resized_saved)
570             /* remove the last one (last used one) */
571             RemovePicture(self, &self->resized, self->n_resized - 1,
572                           &self->n_resized);
573         if (self->cache->max_resized_saved)
574             /* add it to the top of the resized list */
575             AddPicture(self, &self->resized, &self->n_resized, pic);
576         else
577             free_pic = TRUE; /* don't leak mem! */
578     }
579
580     g_assert(pic != NULL);
581
582     DrawRGBA(target, target_w, target_h,
583              pic->data, pic->width, pic->height,
584              img->alpha, area);
585     if (free_pic)
586         RrImagePicFree(pic);
587 }