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