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