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