Allow the user to bind more than one keycode to a keysym for Ob Menus/Move/Resize
[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",
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 draws 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 the larger of
409        w or h has to be right cuz we maintain aspect ratios) */
410     for (i = 0; i < self->n_original; ++i)
411         if ((self->original[i]->width >= self->original[i]->height &&
412              self->original[i]->width == area->width) ||
413             (self->original[i]->width <= self->original[i]->height &&
414              self->original[i]->height == area->height))
415         {
416             pic = self->original[i];
417             break;
418         }
419
420     /* is there a resize of this size? */
421     for (i = 0; i < self->n_resized; ++i)
422         if ((self->resized[i]->width >= self->resized[i]->height &&
423              self->resized[i]->width == area->width) ||
424             (self->resized[i]->width <= self->resized[i]->height &&
425              self->resized[i]->height == area->height))
426         {
427             gint j;
428             RrImagePic *saved;
429
430             /* save the selected one */
431             saved = self->resized[i];
432
433             /* shift all the others down */
434             for (j = i; j > 0; --j)
435                 self->resized[j] = self->resized[j-1];
436
437             /* and move the selected one to the top of the list */
438             self->resized[0] = saved;
439
440             pic = self->resized[0];
441             break;
442         }
443
444     if (!pic) {
445         gdouble aspect;
446
447         /* find an original with a close size */
448         min_diff = min_aspect_diff = -1;
449         min_i = min_aspect_i = 0;
450         aspect = ((gdouble)area->width) / area->height;
451         for (i = 0; i < self->n_original; ++i) {
452             gint diff;
453             gint wdiff, hdiff;
454             gdouble myasp;
455
456             /* our size difference metric.. */
457             wdiff = self->original[i]->width - area->width;
458             hdiff = self->original[i]->height - area->height;
459             diff = (wdiff * wdiff) + (hdiff * hdiff);
460
461             /* find the smallest difference */
462             if (min_diff < 0 || diff < min_diff) {
463                 min_diff = diff;
464                 min_i = i;
465             }
466             /* and also find the smallest difference with the same aspect
467                ratio (and prefer this one) */
468             myasp = ((gdouble)self->original[i]->width) /
469                 self->original[i]->height;
470             if (ABS(aspect - myasp) < 0.0000001 &&
471                 (min_aspect_diff < 0 || diff < min_aspect_diff))
472             {
473                 min_aspect_diff = diff;
474                 min_aspect_i = i;
475             }
476         }
477
478         /* use the aspect ratio correct source if there is one */
479         if (min_aspect_i >= 0)
480             min_i = min_aspect_i;
481
482         /* resize the original to the given area */
483         pic = ResizeImage(self->original[min_i]->data,
484                           self->original[min_i]->width,
485                           self->original[min_i]->height,
486                           area->width, area->height);
487
488         /* add the resized image to the image, as the first in the resized
489            list */
490         if (self->n_resized >= self->cache->max_resized_saved)
491             /* remove the last one (last used one) */
492             RemovePicture(self, &self->resized, self->n_resized - 1,
493                           &self->n_resized);
494         if (self->cache->max_resized_saved)
495             /* add it to the top of the resized list */
496             AddPicture(self, &self->resized, &self->n_resized, pic);
497         else
498             free_pic = TRUE; /* don't leak mem! */
499     }
500
501     g_assert(pic != NULL);
502
503     DrawRGBA(target, target_w, target_h,
504              pic->data, pic->width, pic->height,
505              img->alpha, area);
506     if (free_pic)
507         RrImagePicFree(pic);
508 }