]> 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%lx, w %d h %d Hash %u",
79 (*list == self->original ? "ORIGINAL" : "RESIZED"),
80 (gulong)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%lx, w %d h %d Hash %u",
94 (*list == self->original ? "ORIGINAL" : "RESIZED"),
95 (gulong)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_debug("Refcount to 0, removing ALL pictures from the cache:\n "
334 "Image 0x%lx", (gulong)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_debug("Found duplicate ORIGINAL image:\n "
357 "Image 0x%lx, w %d h %d", (gulong)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 }
This page took 0.050605 seconds and 4 git commands to generate.