]> icculus.org git repositories - mikachu/openbox.git/blob - render/image.c
Merge branch 'imlib2' into wip/mikabox
[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_debug("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_debug("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 /*! Set function that will be called just before RrImage is destroyed. */
325 void RrImageSetDestroyFunc(RrImage *image, RrImageDestroyFunc func)
326 {
327     image->destroy_func = func;
328 }
329
330 void RrImageRef(RrImage *self)
331 {
332     ++self->ref;
333 }
334
335 void RrImageUnref(RrImage *self)
336 {
337     if (self && --self->ref == 0) {
338 #ifdef DEBUG
339         g_debug("Refcount to 0, removing ALL pictures from the cache:\n    "
340                 "Image 0x%x", (guint)self);
341 #endif
342         if (self->destroy_func)
343             self->destroy_func(self);
344         while (self->n_original > 0)
345             RemovePicture(self, &self->original, 0, &self->n_original);
346         while (self->n_resized > 0)
347             RemovePicture(self, &self->resized, 0, &self->n_resized);
348         g_free(self);
349     }
350 }
351
352 /*! Add a new picture with the given RGBA pixel data and dimensions into the
353   RrImage.  This adds an "original" picture to the image.
354 */
355 void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h)
356 {
357     gint i;
358     RrImagePic *pic;
359
360     /* make sure we don't already have this size.. */
361     for (i = 0; i < self->n_original; ++i)
362         if (self->original[i]->width == w && self->original[i]->height == h) {
363 #ifdef DEBUG
364             g_debug("Found duplicate ORIGINAL image:\n    "
365                     "Image 0x%x, w %d h %d", (guint)self, w, h);
366 #endif
367             return;
368         }
369
370     /* remove any resized pictures of this same size */
371     for (i = 0; i < self->n_resized; ++i)
372         if (self->resized[i]->width == w || self->resized[i]->height == h) {
373             RemovePicture(self, &self->resized, i, &self->n_resized);
374             break;
375         }
376
377     /* add the new picture */
378     pic = g_new(RrImagePic, 1);
379     RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
380     AddPicture(self, &self->original, &self->n_original, pic);
381 }
382
383 /*! Remove the picture from the RrImage which has the given dimensions. This
384  removes an "original" picture from the image.
385 */
386 void RrImageRemovePicture(RrImage *self, gint w, gint h)
387 {
388     gint i;
389
390     /* remove any resized pictures of this same size */
391     for (i = 0; i < self->n_original; ++i)
392         if (self->original[i]->width == w && self->original[i]->height == h) {
393             RemovePicture(self, &self->original, i, &self->n_original);
394             break;
395         }
396 }
397
398 /*! Draw an RrImage texture into a target pixel buffer.  If the RrImage does
399   not contain a picture of the appropriate size, then one of its "original"
400   pictures will be resized and used (and stored in the RrImage as a "resized"
401   picture).
402  */
403 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
404                       gint target_w, gint target_h,
405                       RrRect *area)
406 {
407     gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
408     RrImage *self;
409     RrImagePic *pic;
410     gboolean free_pic;
411
412     self = img->image;
413     pic = NULL;
414     free_pic = FALSE;
415
416     /* is there an original of this size? (only w or h has to be right cuz
417        we maintain aspect ratios) */
418     for (i = 0; i < self->n_original; ++i)
419         if (self->original[i]->width == area->width ||
420             self->original[i]->height == area->height)
421         {
422             pic = self->original[i];
423             break;
424         }
425
426     /* is there a resize of this size? */
427     for (i = 0; i < self->n_resized; ++i)
428         if (self->resized[i]->width == area->width ||
429             self->resized[i]->height == area->height)
430         {
431             gint j;
432             RrImagePic *saved;
433
434             /* save the selected one */
435             saved = self->resized[i];
436
437             /* shift all the others down */
438             for (j = i; j > 0; --j)
439                 self->resized[j] = self->resized[j-1];
440
441             /* and move the selected one to the top of the list */
442             self->resized[0] = saved;
443
444             pic = self->resized[0];
445             break;
446         }
447
448     if (!pic) {
449         gdouble aspect;
450
451         /* find an original with a close size */
452         min_diff = min_aspect_diff = -1;
453         min_i = min_aspect_i = 0;
454         aspect = ((gdouble)area->width) / area->height;
455         for (i = 0; i < self->n_original; ++i) {
456             gint diff;
457             gint wdiff, hdiff;
458             gdouble myasp;
459
460             /* our size difference metric.. */
461             wdiff = self->original[i]->width - area->width;
462             hdiff = self->original[i]->height - area->height;
463             diff = (wdiff * wdiff) + (hdiff * hdiff);
464
465             /* find the smallest difference */
466             if (min_diff < 0 || diff < min_diff) {
467                 min_diff = diff;
468                 min_i = i;
469             }
470             /* and also find the smallest difference with the same aspect
471                ratio (and prefer this one) */
472             myasp = ((gdouble)self->original[i]->width) /
473                 self->original[i]->height;
474             if (ABS(aspect - myasp) < 0.0000001 &&
475                 (min_aspect_diff < 0 || diff < min_aspect_diff))
476             {
477                 min_aspect_diff = diff;
478                 min_aspect_i = i;
479             }
480         }
481
482         /* use the aspect ratio correct source if there is one */
483         if (min_aspect_i >= 0)
484             min_i = min_aspect_i;
485
486         /* resize the original to the given area */
487         pic = ResizeImage(self->original[min_i]->data,
488                           self->original[min_i]->width,
489                           self->original[min_i]->height,
490                           area->width, area->height);
491
492         /* add the resized image to the image, as the first in the resized
493            list */
494         if (self->n_resized >= self->cache->max_resized_saved)
495             /* remove the last one (last used one) */
496             RemovePicture(self, &self->resized, self->n_resized - 1,
497                           &self->n_resized);
498         if (self->cache->max_resized_saved)
499             /* add it to the top of the resized list */
500             AddPicture(self, &self->resized, &self->n_resized, pic);
501         else
502             free_pic = TRUE; /* don't leak mem! */
503     }
504
505     g_assert(pic != NULL);
506
507     DrawRGBA(target, target_w, target_h,
508              pic->data, pic->width, pic->height,
509              img->alpha, area);
510     if (free_pic)
511         RrImagePicFree(pic);
512 }