]> icculus.org git repositories - dana/openbox.git/blob - render/image.c
When icons had a very small aspect ratio, we crashed, don't.
[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 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     /* XXX should these variables be ensured to not be zero in the callers? */
128     srcW = srcW ? srcW : 1;
129     srcH = srcH ? srcH : 1;
130     dstW = dstW ? dstW : 1;
131     dstH = dstH ? dstH : 1;
132
133     /* keep the aspect ratio */
134     aspectW = dstW;
135     aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
136     if (aspectH > dstH) {
137         aspectH = dstH;
138         aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
139     }
140     dstW = aspectW ? aspectW : 1;
141     dstH = aspectH ? aspectH : 1;
142
143     if (srcW == dstW && srcH == dstH)
144         return NULL; /* no scaling needed! */
145
146     dststart = dst = g_new(RrPixel32, dstW * dstH);
147
148     ratioX = (srcW << FRACTION) / dstW;
149     ratioY = (srcH << FRACTION) / dstH;
150
151     srcY2 = 0;
152     for (dstY = 0; dstY < dstH; dstY++) {
153         srcY1 = srcY2;
154         srcY2 += ratioY;
155
156         srcX2 = 0;
157         for (dstX = 0; dstX < dstW; dstX++) {
158             gulong red = 0, green = 0, blue = 0, alpha = 0;
159             gulong portionX, portionY, portionXY, sumXY = 0;
160             RrPixel32 pixel;
161
162             srcX1 = srcX2;
163             srcX2 += ratioX;
164
165             for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) {
166                 if (srcY == srcY1) {
167                     srcY = FLOOR(srcY);
168                     portionY = (1UL << FRACTION) - (srcY1 - srcY);
169                     if (portionY > srcY2 - srcY1)
170                         portionY = srcY2 - srcY1;
171                 }
172                 else if (srcY == FLOOR(srcY2))
173                     portionY = srcY2 - srcY;
174                 else
175                     portionY = (1UL << FRACTION);
176
177                 for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) {
178                     if (srcX == srcX1) {
179                         srcX = FLOOR(srcX);
180                         portionX = (1UL << FRACTION) - (srcX1 - srcX);
181                         if (portionX > srcX2 - srcX1)
182                             portionX = srcX2 - srcX1;
183                     }
184                     else if (srcX == FLOOR(srcX2))
185                         portionX = srcX2 - srcX;
186                     else
187                         portionX = (1UL << FRACTION);
188
189                     portionXY = (portionX * portionY) >> FRACTION;
190                     sumXY += portionXY;
191
192                     pixel = *(src + (srcY >> FRACTION) * srcW
193                             + (srcX >> FRACTION));
194                     red   += ((pixel >> RrDefaultRedOffset)   & 0xFF)
195                              * portionXY;
196                     green += ((pixel >> RrDefaultGreenOffset) & 0xFF)
197                              * portionXY;
198                     blue  += ((pixel >> RrDefaultBlueOffset)  & 0xFF)
199                              * portionXY;
200                     alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF)
201                              * portionXY;
202                 }
203             }
204
205             g_assert(sumXY != 0);
206             red   /= sumXY;
207             green /= sumXY;
208             blue  /= sumXY;
209             alpha /= sumXY;
210
211             *dst++ = (red   << RrDefaultRedOffset)   |
212                      (green << RrDefaultGreenOffset) |
213                      (blue  << RrDefaultBlueOffset)  |
214                      (alpha << RrDefaultAlphaOffset);
215         }
216     }
217
218     pic = g_new(RrImagePic, 1);
219     RrImagePicInit(pic, dstW, dstH, dststart);
220
221     return pic;
222 }
223
224 /*! This drawns an RGBA picture into the target, within the rectangle specified
225   by the area parameter.  If the area's size differs from the source's then it
226   will be centered within the rectangle */
227 void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h,
228               RrPixel32 *source, gint source_w, gint source_h,
229               gint alpha, RrRect *area)
230 {
231     RrPixel32 *dest;
232     gint col, num_pixels;
233     gint dw, dh;
234
235     g_assert(source_w <= area->width && source_h <= area->height);
236     g_assert(area->x + area->width <= target_w);
237     g_assert(area->y + area->height <= target_h);
238
239     /* keep the aspect ratio */
240     dw = area->width;
241     dh = (gint)(dw * ((gdouble)source_h / source_w));
242     if (dh > area->height) {
243         dh = area->height;
244         dw = (gint)(dh * ((gdouble)source_w / source_h));
245     }
246
247     /* copy source -> dest, and apply the alpha channel.
248        center the image if it is smaller than the area */
249     col = 0;
250     num_pixels = dw * dh;
251     dest = target + area->x + (area->width - dw) / 2 +
252         (target_w * (area->y + (area->height - dh) / 2));
253     while (num_pixels-- > 0) {
254         guchar a, r, g, b, bgr, bgg, bgb;
255
256         /* apply the rgba's opacity as well */
257         a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8;
258         r = *source >> RrDefaultRedOffset;
259         g = *source >> RrDefaultGreenOffset;
260         b = *source >> RrDefaultBlueOffset;
261
262         /* background color */
263         bgr = *dest >> RrDefaultRedOffset;
264         bgg = *dest >> RrDefaultGreenOffset;
265         bgb = *dest >> RrDefaultBlueOffset;
266
267         r = bgr + (((r - bgr) * a) >> 8);
268         g = bgg + (((g - bgg) * a) >> 8);
269         b = bgb + (((b - bgb) * a) >> 8);
270
271         *dest = ((r << RrDefaultRedOffset) |
272                  (g << RrDefaultGreenOffset) |
273                  (b << RrDefaultBlueOffset));
274
275         dest++;
276         source++;
277
278         if (++col >= dw) {
279             col = 0;
280             dest += target_w - dw;
281         }
282     }
283 }
284
285 /*! Draw an RGBA texture into a target pixel buffer. */
286 void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
287                      gint target_w, gint target_h,
288                      RrRect *area)
289 {
290     RrImagePic *scaled;
291
292     scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
293                          area->width, area->height);
294
295     if (scaled) {
296 #ifdef DEBUG
297             g_warning("Scaling an RGBA! You should avoid this and just make "
298                       "it the right size yourself!");
299 #endif
300             DrawRGBA(target, target_w, target_h,
301                      scaled->data, scaled->width, scaled->height,
302                      rgba->alpha, area);
303             RrImagePicFree(scaled);
304     }
305     else
306         DrawRGBA(target, target_w, target_h,
307                  rgba->data, rgba->width, rgba->height,
308                  rgba->alpha, area);
309 }
310
311 /*! Create a new RrImage, which is linked to an image cache */
312 RrImage* RrImageNew(RrImageCache *cache)
313 {
314     RrImage *self;
315
316     g_assert(cache != NULL);
317
318     self = g_new0(RrImage, 1);
319     self->ref = 1;
320     self->cache = cache;
321     return self;
322 }
323
324 void RrImageRef(RrImage *self)
325 {
326     ++self->ref;
327 }
328
329 void RrImageUnref(RrImage *self)
330 {
331     if (self && --self->ref == 0) {
332 #ifdef DEBUG
333         g_message("Refcount to 0, removing ALL pictures from the cache:\n    "
334                   "Image 0x%x", (guint)self);
335 #endif
336         while (self->n_original > 0)
337             RemovePicture(self, &self->original, 0, &self->n_original);
338         while (self->n_resized > 0)
339             RemovePicture(self, &self->resized, 0, &self->n_resized);
340         g_free(self);
341     }
342 }
343
344 /*! Add a new picture with the given RGBA pixel data and dimensions into the
345   RrImage.  This adds an "original" picture to the image.
346 */
347 void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h)
348 {
349     gint i;
350     RrImagePic *pic;
351
352     /* make sure we don't already have this size.. */
353     for (i = 0; i < self->n_original; ++i)
354         if (self->original[i]->width == w && self->original[i]->height == h) {
355 #ifdef DEBUG
356             g_message("Found duplicate ORIGINAL image:\n    "
357                       "Image 0x%x, w %d h %d", (guint)self, w, h);
358 #endif
359             return;
360         }
361
362     /* remove any resized pictures of this same size */
363     for (i = 0; i < self->n_resized; ++i)
364         if (self->resized[i]->width == w || self->resized[i]->height == h) {
365             RemovePicture(self, &self->resized, i, &self->n_resized);
366             break;
367         }
368
369     /* add the new picture */
370     pic = g_new(RrImagePic, 1);
371     RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
372     AddPicture(self, &self->original, &self->n_original, pic);
373 }
374
375 /*! Remove the picture from the RrImage which has the given dimensions. This
376  removes an "original" picture from the image.
377 */
378 void RrImageRemovePicture(RrImage *self, gint w, gint h)
379 {
380     gint i;
381
382     /* remove any resized pictures of this same size */
383     for (i = 0; i < self->n_original; ++i)
384         if (self->original[i]->width == w && self->original[i]->height == h) {
385             RemovePicture(self, &self->original, i, &self->n_original);
386             break;
387         }
388 }
389
390 /*! Draw an RrImage texture into a target pixel buffer.  If the RrImage does
391   not contain a picture of the appropriate size, then one of its "original"
392   pictures will be resized and used (and stored in the RrImage as a "resized"
393   picture).
394  */
395 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
396                       gint target_w, gint target_h,
397                       RrRect *area)
398 {
399     gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
400     RrImage *self;
401     RrImagePic *pic;
402     gboolean free_pic;
403
404     self = img->image;
405     pic = NULL;
406     free_pic = FALSE;
407
408     /* is there an original of this size? (only w or h has to be right cuz
409        we maintain aspect ratios) */
410     for (i = 0; i < self->n_original; ++i)
411         if (self->original[i]->width == area->width ||
412             self->original[i]->height == area->height)
413         {
414             pic = self->original[i];
415             break;
416         }
417
418     /* is there a resize of this size? */
419     for (i = 0; i < self->n_resized; ++i)
420         if (self->resized[i]->width == area->width ||
421             self->resized[i]->height == area->height)
422         {
423             gint j;
424             RrImagePic *saved;
425
426             /* save the selected one */
427             saved = self->resized[i];
428
429             /* shift all the others down */
430             for (j = i; j > 0; --j)
431                 self->resized[j] = self->resized[j-1];
432
433             /* and move the selected one to the top of the list */
434             self->resized[0] = saved;
435
436             pic = self->resized[0];
437             break;
438         }
439
440     if (!pic) {
441         gdouble aspect;
442
443         /* find an original with a close size */
444         min_diff = min_aspect_diff = -1;
445         min_i = min_aspect_i = 0;
446         aspect = ((gdouble)area->width) / area->height;
447         for (i = 0; i < self->n_original; ++i) {
448             gint diff;
449             gint wdiff, hdiff;
450             gdouble myasp;
451
452             /* our size difference metric.. */
453             wdiff = self->original[i]->width - area->width;
454             hdiff = self->original[i]->height - area->height;
455             diff = (wdiff * wdiff) + (hdiff * hdiff);
456
457             /* find the smallest difference */
458             if (min_diff < 0 || diff < min_diff) {
459                 min_diff = diff;
460                 min_i = i;
461             }
462             /* and also find the smallest difference with the same aspect
463                ratio (and prefer this one) */
464             myasp = ((gdouble)self->original[i]->width) /
465                 self->original[i]->height;
466             if (ABS(aspect - myasp) < 0.0000001 &&
467                 (min_aspect_diff < 0 || diff < min_aspect_diff))
468             {
469                 min_aspect_diff = diff;
470                 min_aspect_i = i;
471             }
472         }
473
474         /* use the aspect ratio correct source if there is one */
475         if (min_aspect_i >= 0)
476             min_i = min_aspect_i;
477
478         /* resize the original to the given area */
479         pic = ResizeImage(self->original[min_i]->data,
480                           self->original[min_i]->width,
481                           self->original[min_i]->height,
482                           area->width, area->height);
483
484         /* add the resized image to the image, as the first in the resized
485            list */
486         if (self->n_resized >= self->cache->max_resized_saved)
487             /* remove the last one (last used one) */
488             RemovePicture(self, &self->resized, self->n_resized - 1,
489                           &self->n_resized);
490         if (self->cache->max_resized_saved)
491             /* add it to the top of the resized list */
492             AddPicture(self, &self->resized, &self->n_resized, pic);
493         else
494             free_pic = TRUE; /* don't leak mem! */
495     }
496
497     g_assert(pic != NULL);
498
499     DrawRGBA(target, target_w, target_h,
500              pic->data, pic->width, pic->height,
501              img->alpha, area);
502     if (free_pic)
503         RrImagePicFree(pic);
504 }