1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 image.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
23 #include "imagecache.h"
28 #define FLOOR(i) ((i) & (~0UL << FRACTION))
29 #define AVERAGE(a, b) (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)))
31 void RrImagePicInit(RrImagePic *pic, gint w, gint h, RrPixel32 *data)
39 for (i = w*h; i > 0; --i)
40 pic->sum += *(data++);
43 static void RrImagePicFree(RrImagePic *pic)
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
54 static void AddPicture(RrImage *self, RrImagePic ***list, gint *len,
59 g_assert(pic->width > 0 && pic->height > 0);
61 g_assert(g_hash_table_lookup(self->cache->table, pic) == NULL);
64 *list = g_renew(RrImagePic*, *list, ++*len);
66 /* move everything else down one */
67 for (i = *len-1; i > 0; --i)
68 (*list)[i] = (*list)[i-1];
70 /* set the new picture up at the front of the list */
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);
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));
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,
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]));
99 /* remove the picture as a key in the cache */
100 g_hash_table_remove(self->cache->table, (*list)[i]);
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);
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
116 static RrImagePic* ResizeImage(RrPixel32 *src,
117 gulong srcW, gulong srcH,
118 gulong dstW, gulong dstH)
120 RrPixel32 *dst, *dststart;
122 gulong dstX, dstY, srcX, srcY;
123 gulong srcX1, srcX2, srcY1, srcY2;
124 gulong ratioX, ratioY;
125 gulong aspectW, aspectH;
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;
133 /* keep the aspect ratio */
135 aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
136 if (aspectH > dstH) {
138 aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
140 dstW = aspectW ? aspectW : 1;
141 dstH = aspectH ? aspectH : 1;
143 if (srcW == dstW && srcH == dstH)
144 return NULL; /* no scaling needed! */
146 dststart = dst = g_new(RrPixel32, dstW * dstH);
148 ratioX = (srcW << FRACTION) / dstW;
149 ratioY = (srcH << FRACTION) / dstH;
152 for (dstY = 0; dstY < dstH; dstY++) {
157 for (dstX = 0; dstX < dstW; dstX++) {
158 gulong red = 0, green = 0, blue = 0, alpha = 0;
159 gulong portionX, portionY, portionXY, sumXY = 0;
165 for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) {
168 portionY = (1UL << FRACTION) - (srcY1 - srcY);
169 if (portionY > srcY2 - srcY1)
170 portionY = srcY2 - srcY1;
172 else if (srcY == FLOOR(srcY2))
173 portionY = srcY2 - srcY;
175 portionY = (1UL << FRACTION);
177 for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) {
180 portionX = (1UL << FRACTION) - (srcX1 - srcX);
181 if (portionX > srcX2 - srcX1)
182 portionX = srcX2 - srcX1;
184 else if (srcX == FLOOR(srcX2))
185 portionX = srcX2 - srcX;
187 portionX = (1UL << FRACTION);
189 portionXY = (portionX * portionY) >> FRACTION;
192 pixel = *(src + (srcY >> FRACTION) * srcW
193 + (srcX >> FRACTION));
194 red += ((pixel >> RrDefaultRedOffset) & 0xFF)
196 green += ((pixel >> RrDefaultGreenOffset) & 0xFF)
198 blue += ((pixel >> RrDefaultBlueOffset) & 0xFF)
200 alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF)
205 g_assert(sumXY != 0);
211 *dst++ = (red << RrDefaultRedOffset) |
212 (green << RrDefaultGreenOffset) |
213 (blue << RrDefaultBlueOffset) |
214 (alpha << RrDefaultAlphaOffset);
218 pic = g_new(RrImagePic, 1);
219 RrImagePicInit(pic, dstW, dstH, dststart);
224 /*! This drawns 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)
232 gint col, num_pixels;
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);
239 /* keep the aspect ratio */
241 dh = (gint)(dw * ((gdouble)source_h / source_w));
242 if (dh > area->height) {
244 dw = (gint)(dh * ((gdouble)source_w / source_h));
247 /* copy source -> dest, and apply the alpha channel.
248 center the image if it is smaller than the area */
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;
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;
262 /* background color */
263 bgr = *dest >> RrDefaultRedOffset;
264 bgg = *dest >> RrDefaultGreenOffset;
265 bgb = *dest >> RrDefaultBlueOffset;
267 r = bgr + (((r - bgr) * a) >> 8);
268 g = bgg + (((g - bgg) * a) >> 8);
269 b = bgb + (((b - bgb) * a) >> 8);
271 *dest = ((r << RrDefaultRedOffset) |
272 (g << RrDefaultGreenOffset) |
273 (b << RrDefaultBlueOffset));
280 dest += target_w - dw;
285 /*! Draw an RGBA texture into a target pixel buffer. */
286 void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
287 gint target_w, gint target_h,
292 scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
293 area->width, area->height);
297 g_warning("Scaling an RGBA! You should avoid this and just make "
298 "it the right size yourself!");
300 DrawRGBA(target, target_w, target_h,
301 scaled->data, scaled->width, scaled->height,
303 RrImagePicFree(scaled);
306 DrawRGBA(target, target_w, target_h,
307 rgba->data, rgba->width, rgba->height,
311 /*! Create a new RrImage, which is linked to an image cache */
312 RrImage* RrImageNew(RrImageCache *cache)
316 g_assert(cache != NULL);
318 self = g_new0(RrImage, 1);
324 /*! Set function that will be called just before RrImage is destroyed. */
325 void RrImageSetDestroyFunc(RrImage *image, RrImageDestroyFunc func)
327 image->destroy_func = func;
330 void RrImageRef(RrImage *self)
335 void RrImageUnref(RrImage *self)
337 if (self && --self->ref == 0) {
339 g_debug("Refcount to 0, removing ALL pictures from the cache:\n "
340 "Image 0x%x", (guint)self);
342 if (self->destroy_func)
343 self->destroy_func(self);
344 while (self->n_original > 0)
345 RemovePicture(self, &self->original, 0, &self->n_original);
346 while (self->n_resized > 0)
347 RemovePicture(self, &self->resized, 0, &self->n_resized);
352 /*! Add a new picture with the given RGBA pixel data and dimensions into the
353 RrImage. This adds an "original" picture to the image.
355 void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h)
360 /* make sure we don't already have this size.. */
361 for (i = 0; i < self->n_original; ++i)
362 if (self->original[i]->width == w && self->original[i]->height == h) {
364 g_debug("Found duplicate ORIGINAL image:\n "
365 "Image 0x%x, w %d h %d", (guint)self, w, h);
370 /* remove any resized pictures of this same size */
371 for (i = 0; i < self->n_resized; ++i)
372 if (self->resized[i]->width == w || self->resized[i]->height == h) {
373 RemovePicture(self, &self->resized, i, &self->n_resized);
377 /* add the new picture */
378 pic = g_new(RrImagePic, 1);
379 RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32)));
380 AddPicture(self, &self->original, &self->n_original, pic);
383 /*! Remove the picture from the RrImage which has the given dimensions. This
384 removes an "original" picture from the image.
386 void RrImageRemovePicture(RrImage *self, gint w, gint h)
390 /* remove any resized pictures of this same size */
391 for (i = 0; i < self->n_original; ++i)
392 if (self->original[i]->width == w && self->original[i]->height == h) {
393 RemovePicture(self, &self->original, i, &self->n_original);
398 /*! Draw an RrImage texture into a target pixel buffer. If the RrImage does
399 not contain a picture of the appropriate size, then one of its "original"
400 pictures will be resized and used (and stored in the RrImage as a "resized"
403 void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
404 gint target_w, gint target_h,
407 gint i, min_diff, min_i, min_aspect_diff, min_aspect_i;
416 /* is there an original of this size? (only w or h has to be right cuz
417 we maintain aspect ratios) */
418 for (i = 0; i < self->n_original; ++i)
419 if (self->original[i]->width == area->width ||
420 self->original[i]->height == area->height)
422 pic = self->original[i];
426 /* is there a resize of this size? */
427 for (i = 0; i < self->n_resized; ++i)
428 if (self->resized[i]->width == area->width ||
429 self->resized[i]->height == area->height)
434 /* save the selected one */
435 saved = self->resized[i];
437 /* shift all the others down */
438 for (j = i; j > 0; --j)
439 self->resized[j] = self->resized[j-1];
441 /* and move the selected one to the top of the list */
442 self->resized[0] = saved;
444 pic = self->resized[0];
451 /* find an original with a close size */
452 min_diff = min_aspect_diff = -1;
453 min_i = min_aspect_i = 0;
454 aspect = ((gdouble)area->width) / area->height;
455 for (i = 0; i < self->n_original; ++i) {
460 /* our size difference metric.. */
461 wdiff = self->original[i]->width - area->width;
462 hdiff = self->original[i]->height - area->height;
463 diff = (wdiff * wdiff) + (hdiff * hdiff);
465 /* find the smallest difference */
466 if (min_diff < 0 || diff < min_diff) {
470 /* and also find the smallest difference with the same aspect
471 ratio (and prefer this one) */
472 myasp = ((gdouble)self->original[i]->width) /
473 self->original[i]->height;
474 if (ABS(aspect - myasp) < 0.0000001 &&
475 (min_aspect_diff < 0 || diff < min_aspect_diff))
477 min_aspect_diff = diff;
482 /* use the aspect ratio correct source if there is one */
483 if (min_aspect_i >= 0)
484 min_i = min_aspect_i;
486 /* resize the original to the given area */
487 pic = ResizeImage(self->original[min_i]->data,
488 self->original[min_i]->width,
489 self->original[min_i]->height,
490 area->width, area->height);
492 /* add the resized image to the image, as the first in the resized
494 if (self->n_resized >= self->cache->max_resized_saved)
495 /* remove the last one (last used one) */
496 RemovePicture(self, &self->resized, self->n_resized - 1,
498 if (self->cache->max_resized_saved)
499 /* add it to the top of the resized list */
500 AddPicture(self, &self->resized, &self->n_resized, pic);
502 free_pic = TRUE; /* don't leak mem! */
505 g_assert(pic != NULL);
507 DrawRGBA(target, target_w, target_h,
508 pic->data, pic->width, pic->height,