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