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