c7452fceeb263aee3abff0b4d038008869dedd9d
[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\n",
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\n",
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     }
298     else
299         DrawRGBA(target, target_w, target_h,
300                  rgba->data, rgba->width, rgba->height,
301                  rgba->alpha, area);
302 }
303
304 /*! Create a new RrImage, which is linked to an image cache */
305 RrImage* RrImageNew(RrImageCache *cache)
306 {
307     RrImage *self;
308
309     g_assert(cache != NULL);
310
311     self = g_new0(RrImage, 1);
312     self->ref = 1;
313     self->cache = cache;
314     return self;
315 }
316
317 void RrImageRef(RrImage *self)
318 {
319     ++self->ref;
320 }
321
322 void RrImageUnref(RrImage *self)
323 {
324     if (self && --self->ref == 0) {
325 #ifdef DEBUG
326         g_message("Refcount to 0, removing ALL pictures from the cache:\n    "
327                   "Image 0x%x\n", (guint)self);
328 #endif
329         while (self->n_original > 0)
330             RemovePicture(self, &self->original, 0, &self->n_original);
331         while (self->n_resized > 0)
332             RemovePicture(self, &self->resized, 0, &self->n_resized);
333         g_free(self);
334     }
335 }
336
337 /*! Add a new picture with the given RGBA pixel data and dimensions into the
338   RrImage.  This adds an "original" picture to the image.
339 */
340 void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h)
341 {
342     gint i;
343     RrImagePic *pic;
344
345     /* make sure we don't already have this size.. */
346     for (i = 0; i < self->n_original; ++i)
347         if (self->original[i]->width == w && self->original[i]->height == h) {
348 #ifdef DEBUG
349             g_message("Found duplicate ORIGINAL image:\n    "
350                       "Image 0x%x, w %d h %d\n", (guint)self, w, h);
351 #endif
352             return;
353         }
354
355     /* remove any resized pictures of this same size */
356     for (i = 0; i < self->n_resized; ++i)
357         if (self->resized[i]->width == w || self->resized[i]->height == h) {
358             RemovePicture(self, &self->resized, i, &self->n_resized);
359             break;
360         }
361
362     /* add the new picture */
363     pic = g_new(RrImagePic, 1);
364     RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
365     AddPicture(self, &self->original, &self->n_original, pic);
366 }
367
368 /*! Remove the picture from the RrImage which has the given dimensions. This
369  removes an "original" picture from the image.
370 */
371 void RrImageRemovePicture(RrImage *self, gint w, gint h)
372 {
373     gint i;
374
375     /* remove any resized pictures of this same size */
376     for (i = 0; i < self->n_original; ++i)
377         if (self->original[i]->width == w && self->original[i]->height == h) {
378             RemovePicture(self, &self->original, i, &self->n_original);
379             break;
380         }
381 }
382
383 /*! Draw an RrImage texture into a target pixel buffer.  If the RrImage does
384   not contain a picture of the appropriate size, then one of its "original"
385   pictures will be resized and used (and stored in the RrImage as a "resized"
386   picture).
387  */
388 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
389                       gint target_w, gint target_h,
390                       RrRect *area)
391 {
392     gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
393     RrImage *self;
394     RrImagePic *pic;
395     gboolean free_pic;
396
397     self = img->image;
398     pic = NULL;
399     free_pic = FALSE;
400
401     /* is there an original of this size? (only w or h has to be right cuz
402        we maintain aspect ratios) */
403     for (i = 0; i < self->n_original; ++i)
404         if (self->original[i]->width == area->width ||
405             self->original[i]->height == area->height)
406         {
407             pic = self->original[i];
408             break;
409         }
410
411     /* is there a resize of this size? */
412     for (i = 0; i < self->n_resized; ++i)
413         if (self->resized[i]->width == area->width ||
414             self->resized[i]->height == area->height)
415         {
416             gint j;
417             RrImagePic *saved;
418
419             /* save the selected one */
420             saved = self->resized[i];
421
422             /* shift all the others down */
423             for (j = i; j > 0; --j)
424                 self->resized[j] = self->resized[j-1];
425
426             /* and move the selected one to the top of the list */
427             self->resized[0] = saved;
428
429             pic = self->resized[0];
430             break;
431         }
432
433     if (!pic) {
434         gdouble aspect;
435
436         /* find an original with a close size */
437         min_diff = min_aspect_diff = -1;
438         min_i = min_aspect_i = 0;
439         aspect = ((gdouble)area->width) / area->height;
440         for (i = 0; i < self->n_original; ++i) {
441             gint diff;
442             gint wdiff, hdiff;
443             gdouble myasp;
444
445             /* our size difference metric.. */
446             wdiff = self->original[i]->width - area->width;
447             hdiff = self->original[i]->height - area->height;
448             diff = (wdiff * wdiff) + (hdiff * hdiff);
449
450             /* find the smallest difference */
451             if (min_diff < 0 || diff < min_diff) {
452                 min_diff = diff;
453                 min_i = i;
454             }
455             /* and also find the smallest difference with the same aspect
456                ratio (and prefer this one) */
457             myasp = ((gdouble)self->original[i]->width) /
458                 self->original[i]->height;
459             if (ABS(aspect - myasp) < 0.0000001 &&
460                 (min_aspect_diff < 0 || diff < min_aspect_diff))
461             {
462                 min_aspect_diff = diff;
463                 min_aspect_i = i;
464             }
465         }
466
467         /* use the aspect ratio correct source if there is one */
468         if (min_aspect_i >= 0)
469             min_i = min_aspect_i;
470
471         /* resize the original to the given area */
472         pic = ResizeImage(self->original[min_i]->data,
473                           self->original[min_i]->width,
474                           self->original[min_i]->height,
475                           area->width, area->height);
476
477         /* add the resized image to the image, as the first in the resized
478            list */
479         if (self->n_resized >= self->cache->max_resized_saved)
480             /* remove the last one (last used one) */
481             RemovePicture(self, &self->resized, self->n_resized - 1,
482                           &self->n_resized);
483         if (self->cache->max_resized_saved)
484             /* add it to the top of the resized list */
485             AddPicture(self, &self->resized, &self->n_resized, pic);
486         else
487             free_pic = TRUE; /* don't leak mem! */
488     }
489
490     g_assert(pic != NULL);
491
492     DrawRGBA(target, target_w, target_h,
493              pic->data, pic->width, pic->height,
494              img->alpha, area);
495     if (free_pic)
496         RrImagePicFree(pic);
497 }