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