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