]> Dogcows Code - chaz/openbox/blob - obrender/image.c
Merge branch 'backport' into work
[chaz/openbox] / obrender / 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 #ifdef USE_IMLIB2
25 #include <Imlib2.h>
26 #endif
27
28 #include <glib.h>
29
30 #define FRACTION 12
31 #define FLOOR(i) ((i) & (~0UL << FRACTION))
32 #define AVERAGE(a, b) (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)))
33
34 void RrImagePicInit(RrImagePic *pic, const gchar *name,
35 gint w, gint h, RrPixel32 *data)
36 {
37 gint i;
38
39 pic->width = w;
40 pic->height = h;
41 pic->data = data;
42 pic->sum = 0;
43 for (i = w*h; i > 0; --i)
44 pic->sum += *(data++);
45 pic->name = g_strdup(name);
46 }
47
48 static void RrImagePicFree(RrImagePic *pic)
49 {
50 if (pic) {
51 g_free(pic->data);
52 g_free(pic->name);
53 g_free(pic);
54 }
55 }
56
57 /*! Add a picture to an Image, that is, add another copy of the image at
58 another size. This may add it to the "originals" list or to the
59 "resized" list. */
60 static void AddPicture(RrImage *self, RrImagePic ***list, gint *len,
61 RrImagePic *pic)
62 {
63 gint i;
64
65 g_assert(pic->width > 0 && pic->height > 0);
66
67 if (pic->name)
68 g_assert(!g_hash_table_lookup(self->cache->name_table, pic->name));
69 else
70 g_assert(!g_hash_table_lookup(self->cache->pic_table, pic));
71
72 /* grow the list */
73 *list = g_renew(RrImagePic*, *list, ++*len);
74
75 /* move everything else down one */
76 for (i = *len-1; i > 0; --i)
77 (*list)[i] = (*list)[i-1];
78
79 /* set the new picture up at the front of the list */
80 (*list)[0] = pic;
81
82 /* add the name or the picture as a key to point to this image in the
83 cache */
84 if (pic->name)
85 g_hash_table_insert(self->cache->name_table, pic->name, self);
86 else
87 g_hash_table_insert(self->cache->pic_table, (*list)[0], self);
88
89 /*
90 #ifdef DEBUG
91 g_debug("Adding %s picture to the cache:\n "
92 "Image 0x%lx, w %d h %d Hash %u",
93 (*list == self->original ? "ORIGINAL" : "RESIZED"),
94 (gulong)self, pic->width, pic->height, RrImagePicHash(pic));
95 #endif
96 */
97 }
98
99 /*! Remove a picture from an Image. This may remove it from the "originals"
100 list or the "resized" list. */
101 static void RemovePicture(RrImage *self, RrImagePic ***list,
102 gint i, gint *len)
103 {
104 gint j;
105
106 /*
107 #ifdef DEBUG
108 g_debug("Removing %s picture from the cache:\n "
109 "Image 0x%lx, w %d h %d Hash %u",
110 (*list == self->original ? "ORIGINAL" : "RESIZED"),
111 (gulong)self, (*list)[i]->width, (*list)[i]->height,
112 RrImagePicHash((*list)[i]));
113 #endif
114 */
115
116 /* remove the name or picture as a key in the cache */
117 if ((*list)[i]->name)
118 g_hash_table_remove(self->cache->name_table, (*list)[i]->name);
119 else
120 g_hash_table_remove(self->cache->pic_table, (*list)[i]);
121
122 /* free the picture */
123 RrImagePicFree((*list)[i]);
124 /* shift everything down one */
125 for (j = i; j < *len-1; ++j)
126 (*list)[j] = (*list)[j+1];
127 /* shrink the list */
128 *list = g_renew(RrImagePic*, *list, --*len);
129 }
130
131 /*! Given a picture in RGBA format, of a specified size, resize it to the new
132 requested size (but keep its aspect ratio). If the image does not need to
133 be resized (it is already the right size) then this returns NULL. Otherwise
134 it returns a newly allocated RrImagePic with the resized picture inside it
135 */
136 static RrImagePic* ResizeImage(RrPixel32 *src,
137 gulong srcW, gulong srcH,
138 gulong dstW, gulong dstH)
139 {
140 RrPixel32 *dst, *dststart;
141 RrImagePic *pic;
142 gulong dstX, dstY, srcX, srcY;
143 gulong srcX1, srcX2, srcY1, srcY2;
144 gulong ratioX, ratioY;
145 gulong aspectW, aspectH;
146
147 /* XXX should these variables be ensured to not be zero in the callers? */
148 srcW = srcW ? srcW : 1;
149 srcH = srcH ? srcH : 1;
150 dstW = dstW ? dstW : 1;
151 dstH = dstH ? dstH : 1;
152
153 /* keep the aspect ratio */
154 aspectW = dstW;
155 aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
156 if (aspectH > dstH) {
157 aspectH = dstH;
158 aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
159 }
160 dstW = aspectW ? aspectW : 1;
161 dstH = aspectH ? aspectH : 1;
162
163 if (srcW == dstW && srcH == dstH)
164 return NULL; /* no scaling needed! */
165
166 dststart = dst = g_new(RrPixel32, dstW * dstH);
167
168 ratioX = (srcW << FRACTION) / dstW;
169 ratioY = (srcH << FRACTION) / dstH;
170
171 srcY2 = 0;
172 for (dstY = 0; dstY < dstH; dstY++) {
173 srcY1 = srcY2;
174 srcY2 += ratioY;
175
176 srcX2 = 0;
177 for (dstX = 0; dstX < dstW; dstX++) {
178 gulong red = 0, green = 0, blue = 0, alpha = 0;
179 gulong portionX, portionY, portionXY, sumXY = 0;
180 RrPixel32 pixel;
181
182 srcX1 = srcX2;
183 srcX2 += ratioX;
184
185 for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) {
186 if (srcY == srcY1) {
187 srcY = FLOOR(srcY);
188 portionY = (1UL << FRACTION) - (srcY1 - srcY);
189 if (portionY > srcY2 - srcY1)
190 portionY = srcY2 - srcY1;
191 }
192 else if (srcY == FLOOR(srcY2))
193 portionY = srcY2 - srcY;
194 else
195 portionY = (1UL << FRACTION);
196
197 for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) {
198 if (srcX == srcX1) {
199 srcX = FLOOR(srcX);
200 portionX = (1UL << FRACTION) - (srcX1 - srcX);
201 if (portionX > srcX2 - srcX1)
202 portionX = srcX2 - srcX1;
203 }
204 else if (srcX == FLOOR(srcX2))
205 portionX = srcX2 - srcX;
206 else
207 portionX = (1UL << FRACTION);
208
209 portionXY = (portionX * portionY) >> FRACTION;
210 sumXY += portionXY;
211
212 pixel = *(src + (srcY >> FRACTION) * srcW
213 + (srcX >> FRACTION));
214 red += ((pixel >> RrDefaultRedOffset) & 0xFF)
215 * portionXY;
216 green += ((pixel >> RrDefaultGreenOffset) & 0xFF)
217 * portionXY;
218 blue += ((pixel >> RrDefaultBlueOffset) & 0xFF)
219 * portionXY;
220 alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF)
221 * portionXY;
222 }
223 }
224
225 g_assert(sumXY != 0);
226 red /= sumXY;
227 green /= sumXY;
228 blue /= sumXY;
229 alpha /= sumXY;
230
231 *dst++ = (red << RrDefaultRedOffset) |
232 (green << RrDefaultGreenOffset) |
233 (blue << RrDefaultBlueOffset) |
234 (alpha << RrDefaultAlphaOffset);
235 }
236 }
237
238 pic = g_new(RrImagePic, 1);
239 RrImagePicInit(pic, NULL, dstW, dstH, dststart);
240
241 return pic;
242 }
243
244 /*! This draws an RGBA picture into the target, within the rectangle specified
245 by the area parameter. If the area's size differs from the source's then it
246 will be centered within the rectangle */
247 void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h,
248 RrPixel32 *source, gint source_w, gint source_h,
249 gint alpha, RrRect *area)
250 {
251 RrPixel32 *dest;
252 gint col, num_pixels;
253 gint dw, dh;
254
255 g_assert(source_w <= area->width && source_h <= area->height);
256 g_assert(area->x + area->width <= target_w);
257 g_assert(area->y + area->height <= target_h);
258
259 /* keep the aspect ratio */
260 dw = area->width;
261 dh = (gint)(dw * ((gdouble)source_h / source_w));
262 if (dh > area->height) {
263 dh = area->height;
264 dw = (gint)(dh * ((gdouble)source_w / source_h));
265 }
266
267 /* copy source -> dest, and apply the alpha channel.
268 center the image if it is smaller than the area */
269 col = 0;
270 num_pixels = dw * dh;
271 dest = target + area->x + (area->width - dw) / 2 +
272 (target_w * (area->y + (area->height - dh) / 2));
273 while (num_pixels-- > 0) {
274 guchar a, r, g, b, bgr, bgg, bgb;
275
276 /* apply the rgba's opacity as well */
277 a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8;
278 r = *source >> RrDefaultRedOffset;
279 g = *source >> RrDefaultGreenOffset;
280 b = *source >> RrDefaultBlueOffset;
281
282 /* background color */
283 bgr = *dest >> RrDefaultRedOffset;
284 bgg = *dest >> RrDefaultGreenOffset;
285 bgb = *dest >> RrDefaultBlueOffset;
286
287 r = bgr + (((r - bgr) * a) >> 8);
288 g = bgg + (((g - bgg) * a) >> 8);
289 b = bgb + (((b - bgb) * a) >> 8);
290
291 *dest = ((r << RrDefaultRedOffset) |
292 (g << RrDefaultGreenOffset) |
293 (b << RrDefaultBlueOffset));
294
295 dest++;
296 source++;
297
298 if (++col >= dw) {
299 col = 0;
300 dest += target_w - dw;
301 }
302 }
303 }
304
305 /*! Draw an RGBA texture into a target pixel buffer. */
306 void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
307 gint target_w, gint target_h,
308 RrRect *area)
309 {
310 RrImagePic *scaled;
311
312 scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
313 area->width, area->height);
314
315 if (scaled) {
316 #ifdef DEBUG
317 g_warning("Scaling an RGBA! You should avoid this and just make "
318 "it the right size yourself!");
319 #endif
320 DrawRGBA(target, target_w, target_h,
321 scaled->data, scaled->width, scaled->height,
322 rgba->alpha, area);
323 RrImagePicFree(scaled);
324 }
325 else
326 DrawRGBA(target, target_w, target_h,
327 rgba->data, rgba->width, rgba->height,
328 rgba->alpha, area);
329 }
330
331 /*! Create a new RrImage, which is linked to an image cache */
332 RrImage* RrImageNew(RrImageCache *cache)
333 {
334 RrImage *self;
335
336 g_assert(cache != NULL);
337
338 self = g_new0(RrImage, 1);
339 self->ref = 1;
340 self->cache = cache;
341 return self;
342 }
343
344 /*! Set function that will be called just before RrImage is destroyed. */
345 void RrImageSetDestroyFunc(RrImage *image, RrImageDestroyFunc func,
346 gpointer data)
347 {
348 image->destroy_func = func;
349 image->destroy_data = data;
350 }
351
352 void RrImageRef(RrImage *self)
353 {
354 ++self->ref;
355 }
356
357 void RrImageUnref(RrImage *self)
358 {
359 if (self && --self->ref == 0) {
360 /*
361 #ifdef DEBUG
362 g_debug("Refcount to 0, removing ALL pictures from the cache:\n "
363 "Image 0x%lx", (gulong)self);
364 #endif
365 */
366 if (self->destroy_func)
367 self->destroy_func(self, self->destroy_data);
368 while (self->n_original > 0)
369 RemovePicture(self, &self->original, 0, &self->n_original);
370 while (self->n_resized > 0)
371 RemovePicture(self, &self->resized, 0, &self->n_resized);
372 g_free(self);
373 }
374 }
375
376 static void AddPictureFromData(RrImage *self, const char *name,
377 const RrPixel32 *data, gint w, gint h)
378 {
379 gint i;
380 RrImagePic *pic;
381
382 /* make sure we don't already have this size.. */
383 for (i = 0; i < self->n_original; ++i)
384 if (self->original[i]->width == w && self->original[i]->height == h) {
385 /*
386 #ifdef DEBUG
387 g_debug("Found duplicate ORIGINAL image:\n "
388 "Image 0x%lx, w %d h %d", (gulong)self, w, h);
389 #endif
390 */
391 return;
392 }
393
394 /* remove any resized pictures of this same size */
395 for (i = 0; i < self->n_resized; ++i)
396 if (self->resized[i]->width == w || self->resized[i]->height == h) {
397 RemovePicture(self, &self->resized, i, &self->n_resized);
398 break;
399 }
400
401 /* add the new picture */
402 pic = g_new(RrImagePic, 1);
403 RrImagePicInit(pic, name, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
404 AddPicture(self, &self->original, &self->n_original, pic);
405 }
406
407 gboolean RrImageAddPictureName(RrImage *self, const gchar *name)
408 {
409 #ifdef USE_IMLIB2
410 Imlib_Image img;
411 gint w, h;
412 RrPixel32 *data;
413 gchar *path;
414
415 /* XXX find the path via freedesktop icon spec (use obt) ! */
416 path = g_strdup(name);
417
418 if (!(img = imlib_load_image(path)))
419 g_message("Cannot load image \"%s\" from file \"%s\"", name, path);
420 g_free(path);
421
422 if (!img)
423 return FALSE; /* failed to load it */
424
425 /* Get data and dimensions of the image.
426
427 WARNING: This stuff is NOT threadsafe !!
428 */
429 imlib_context_set_image(img);
430 data = imlib_image_get_data_for_reading_only();
431 w = imlib_image_get_width();
432 h = imlib_image_get_height();
433
434 /* add it to the RrImage, and set its name */
435 AddPictureFromData(self, name, data, w, h);
436
437 imlib_free_image();
438 return TRUE;
439 #else
440 return FALSE;
441 #endif
442 }
443
444 /*! Add a new picture with the given RGBA pixel data and dimensions into the
445 RrImage. This adds an "original" picture to the image.
446 */
447 void RrImageAddPicture(RrImage *self, const RrPixel32 *data, gint w, gint h)
448 {
449 AddPictureFromData(self, NULL, data, w, h);
450 }
451
452 /*! Remove the picture from the RrImage which has the given dimensions. This
453 removes an "original" picture from the image.
454 */
455 void RrImageRemovePicture(RrImage *self, gint w, gint h)
456 {
457 gint i;
458
459 /* remove any resized pictures of this same size */
460 for (i = 0; i < self->n_original; ++i)
461 if (self->original[i]->width == w && self->original[i]->height == h) {
462 RemovePicture(self, &self->original, i, &self->n_original);
463 break;
464 }
465 }
466
467 /*! Draw an RrImage texture into a target pixel buffer. If the RrImage does
468 not contain a picture of the appropriate size, then one of its "original"
469 pictures will be resized and used (and stored in the RrImage as a "resized"
470 picture).
471 */
472 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
473 gint target_w, gint target_h,
474 RrRect *area)
475 {
476 gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
477 RrImage *self;
478 RrImagePic *pic;
479 gboolean free_pic;
480
481 self = img->image;
482 pic = NULL;
483 free_pic = FALSE;
484
485 /* is there an original of this size? (only the larger of
486 w or h has to be right cuz we maintain aspect ratios) */
487 for (i = 0; i < self->n_original; ++i)
488 if ((self->original[i]->width >= self->original[i]->height &&
489 self->original[i]->width == area->width) ||
490 (self->original[i]->width <= self->original[i]->height &&
491 self->original[i]->height == area->height))
492 {
493 pic = self->original[i];
494 break;
495 }
496
497 /* is there a resize of this size? */
498 for (i = 0; i < self->n_resized; ++i)
499 if ((self->resized[i]->width >= self->resized[i]->height &&
500 self->resized[i]->width == area->width) ||
501 (self->resized[i]->width <= self->resized[i]->height &&
502 self->resized[i]->height == area->height))
503 {
504 gint j;
505 RrImagePic *saved;
506
507 /* save the selected one */
508 saved = self->resized[i];
509
510 /* shift all the others down */
511 for (j = i; j > 0; --j)
512 self->resized[j] = self->resized[j-1];
513
514 /* and move the selected one to the top of the list */
515 self->resized[0] = saved;
516
517 pic = self->resized[0];
518 break;
519 }
520
521 if (!pic) {
522 gdouble aspect;
523
524 /* find an original with a close size */
525 min_diff = min_aspect_diff = -1;
526 min_i = min_aspect_i = 0;
527 aspect = ((gdouble)area->width) / area->height;
528 for (i = 0; i < self->n_original; ++i) {
529 gint diff;
530 gint wdiff, hdiff;
531 gdouble myasp;
532
533 /* our size difference metric.. */
534 wdiff = self->original[i]->width - area->width;
535 if (wdiff < 0) wdiff *= 2; /* prefer scaling down than up */
536 hdiff = self->original[i]->height - area->height;
537 if (hdiff < 0) hdiff *= 2; /* prefer scaling down than up */
538 diff = (wdiff * wdiff) + (hdiff * hdiff);
539
540 /* find the smallest difference */
541 if (min_diff < 0 || diff < min_diff) {
542 min_diff = diff;
543 min_i = i;
544 }
545 /* and also find the smallest difference with the same aspect
546 ratio (and prefer this one) */
547 myasp = ((gdouble)self->original[i]->width) /
548 self->original[i]->height;
549 if (ABS(aspect - myasp) < 0.0000001 &&
550 (min_aspect_diff < 0 || diff < min_aspect_diff))
551 {
552 min_aspect_diff = diff;
553 min_aspect_i = i;
554 }
555 }
556
557 /* use the aspect ratio correct source if there is one */
558 if (min_aspect_i >= 0)
559 min_i = min_aspect_i;
560
561 /* resize the original to the given area */
562 pic = ResizeImage(self->original[min_i]->data,
563 self->original[min_i]->width,
564 self->original[min_i]->height,
565 area->width, area->height);
566
567 /* add the resized image to the image, as the first in the resized
568 list */
569 if (self->n_resized >= self->cache->max_resized_saved)
570 /* remove the last one (last used one) */
571 RemovePicture(self, &self->resized, self->n_resized - 1,
572 &self->n_resized);
573 if (self->cache->max_resized_saved)
574 /* add it to the top of the resized list */
575 AddPicture(self, &self->resized, &self->n_resized, pic);
576 else
577 free_pic = TRUE; /* don't leak mem! */
578 }
579
580 g_assert(pic != NULL);
581
582 DrawRGBA(target, target_w, target_h,
583 pic->data, pic->width, pic->height,
584 img->alpha, area);
585 if (free_pic)
586 RrImagePicFree(pic);
587 }
This page took 0.062126 seconds and 4 git commands to generate.