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);
78
g_message("Adding %s picture to the cache:\n "
79
"Image 0x%x, w %d h %d Hash %u",
80
(*list == self->original ? "ORIGINAL" : "RESIZED"),
81
(guint)self, pic->width, pic->height, RrImagePicHash(pic));
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,
95
g_message("Removing %s picture from the cache:\n "
96
"Image 0x%x, w %d h %d Hash %u",
97
(*list == self->original ? "ORIGINAL" : "RESIZED"),
98
(guint)self, (*list)[i]->width, (*list)[i]->height,
99
RrImagePicHash((*list)[i]));
103
/* remove the picture as a key in the cache */
104
g_hash_table_remove(self->cache->table, (*list)[i]);
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);
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
120
static RrImagePic* ResizeImage(RrPixel32 *src,
121
gulong srcW, gulong srcH,
122
gulong dstW, gulong dstH)
124
RrPixel32 *dst, *dststart;
126
gulong dstX, dstY, srcX, srcY;
127
gulong srcX1, srcX2, srcY1, srcY2;
128
gulong ratioX, ratioY;
129
gulong aspectW, aspectH;
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;
137
/* keep the aspect ratio */
139
aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
140
if (aspectH > dstH) {
142
aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
144
dstW = aspectW ? aspectW : 1;
145
dstH = aspectH ? aspectH : 1;
147
if (srcW == dstW && srcH == dstH)
148
return NULL; /* no scaling needed! */
150
dststart = dst = g_new(RrPixel32, dstW * dstH);
152
ratioX = (srcW << FRACTION) / dstW;
153
ratioY = (srcH << FRACTION) / dstH;
156
for (dstY = 0; dstY < dstH; dstY++) {
161
for (dstX = 0; dstX < dstW; dstX++) {
162
gulong red = 0, green = 0, blue = 0, alpha = 0;
163
gulong portionX, portionY, portionXY, sumXY = 0;
169
for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) {
172
portionY = (1UL << FRACTION) - (srcY1 - srcY);
173
if (portionY > srcY2 - srcY1)
174
portionY = srcY2 - srcY1;
176
else if (srcY == FLOOR(srcY2))
177
portionY = srcY2 - srcY;
179
portionY = (1UL << FRACTION);
181
for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) {
184
portionX = (1UL << FRACTION) - (srcX1 - srcX);
185
if (portionX > srcX2 - srcX1)
186
portionX = srcX2 - srcX1;
188
else if (srcX == FLOOR(srcX2))
189
portionX = srcX2 - srcX;
191
portionX = (1UL << FRACTION);
193
portionXY = (portionX * portionY) >> FRACTION;
196
pixel = *(src + (srcY >> FRACTION) * srcW
197
+ (srcX >> FRACTION));
198
red += ((pixel >> RrDefaultRedOffset) & 0xFF)
200
green += ((pixel >> RrDefaultGreenOffset) & 0xFF)
202
blue += ((pixel >> RrDefaultBlueOffset) & 0xFF)
204
alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF)
209
g_assert(sumXY != 0);
215
*dst++ = (red << RrDefaultRedOffset) |
216
(green << RrDefaultGreenOffset) |
217
(blue << RrDefaultBlueOffset) |
218
(alpha << RrDefaultAlphaOffset);
222
pic = g_new(RrImagePic, 1);
223
RrImagePicInit(pic, dstW, dstH, dststart);
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)
236
gint col, num_pixels;
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);
243
/* keep the aspect ratio */
245
dh = (gint)(dw * ((gdouble)source_h / source_w));
246
if (dh > area->height) {
248
dw = (gint)(dh * ((gdouble)source_w / source_h));
251
/* copy source -> dest, and apply the alpha channel.
252
center the image if it is smaller than the area */
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;
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;
266
/* background color */
267
bgr = *dest >> RrDefaultRedOffset;
268
bgg = *dest >> RrDefaultGreenOffset;
269
bgb = *dest >> RrDefaultBlueOffset;
271
r = bgr + (((r - bgr) * a) >> 8);
272
g = bgg + (((g - bgg) * a) >> 8);
273
b = bgb + (((b - bgb) * a) >> 8);
275
*dest = ((r << RrDefaultRedOffset) |
276
(g << RrDefaultGreenOffset) |
277
(b << RrDefaultBlueOffset));
284
dest += target_w - dw;
289
/*! Draw an RGBA texture into a target pixel buffer. */
290
void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
291
gint target_w, gint target_h,
296
scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
297
area->width, area->height);
301
g_warning("Scaling an RGBA! You should avoid this and just make "
302
"it the right size yourself!");
304
DrawRGBA(target, target_w, target_h,
305
scaled->data, scaled->width, scaled->height,
307
RrImagePicFree(scaled);
310
DrawRGBA(target, target_w, target_h,
311
rgba->data, rgba->width, rgba->height,
315
/*! Create a new RrImage, which is linked to an image cache */
316
RrImage* RrImageNew(RrImageCache *cache)
320
g_assert(cache != NULL);
322
self = g_new0(RrImage, 1);
328
void RrImageRef(RrImage *self)
333
void RrImageUnref(RrImage *self)
335
if (self && --self->ref == 0) {
338
g_message("Refcount to 0, removing ALL pictures from the cache:\n "
339
"Image 0x%x", (guint)self);
342
while (self->n_original > 0)
343
RemovePicture(self, &self->original, 0, &self->n_original);
344
while (self->n_resized > 0)
345
RemovePicture(self, &self->resized, 0, &self->n_resized);
350
/*! Add a new picture with the given RGBA pixel data and dimensions into the
351
RrImage. This adds an "original" picture to the image.
353
void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h)
358
/* make sure we don't already have this size.. */
359
for (i = 0; i < self->n_original; ++i)
360
if (self->original[i]->width == w && self->original[i]->height == h) {
363
g_message("Found duplicate ORIGINAL image:\n "
364
"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 the larger of
417
w or h has to be right cuz we maintain aspect ratios) */
418
for (i = 0; i < self->n_original; ++i)
419
if ((self->original[i]->width >= self->original[i]->height &&
420
self->original[i]->width == area->width) ||
421
(self->original[i]->width <= self->original[i]->height &&
422
self->original[i]->height == area->height))
424
pic = self->original[i];
428
/* is there a resize of this size? */
429
for (i = 0; i < self->n_resized; ++i)
430
if ((self->resized[i]->width >= self->resized[i]->height &&
431
self->resized[i]->width == area->width) ||
432
(self->resized[i]->width <= self->resized[i]->height &&
433
self->resized[i]->height == area->height))
438
/* save the selected one */
439
saved = self->resized[i];
441
/* shift all the others down */
442
for (j = i; j > 0; --j)
443
self->resized[j] = self->resized[j-1];
445
/* and move the selected one to the top of the list */
446
self->resized[0] = saved;
448
pic = self->resized[0];
455
/* find an original with a close size */
456
min_diff = min_aspect_diff = -1;
457
min_i = min_aspect_i = 0;
458
aspect = ((gdouble)area->width) / area->height;
459
for (i = 0; i < self->n_original; ++i) {
464
/* our size difference metric.. */
465
wdiff = self->original[i]->width - area->width;
466
if (wdiff < 0) wdiff *= 2; /* prefer scaling down than up */
467
hdiff = self->original[i]->height - area->height;
468
if (hdiff < 0) hdiff *= 2; /* prefer scaling down than up */
469
diff = (wdiff * wdiff) + (hdiff * hdiff);
471
/* find the smallest difference */
472
if (min_diff < 0 || diff < min_diff) {
476
/* and also find the smallest difference with the same aspect
477
ratio (and prefer this one) */
478
myasp = ((gdouble)self->original[i]->width) /
479
self->original[i]->height;
480
if (ABS(aspect - myasp) < 0.0000001 &&
481
(min_aspect_diff < 0 || diff < min_aspect_diff))
483
min_aspect_diff = diff;
488
/* use the aspect ratio correct source if there is one */
489
if (min_aspect_i >= 0)
490
min_i = min_aspect_i;
492
/* resize the original to the given area */
493
pic = ResizeImage(self->original[min_i]->data,
494
self->original[min_i]->width,
495
self->original[min_i]->height,
496
area->width, area->height);
498
/* add the resized image to the image, as the first in the resized
500
if (self->n_resized >= self->cache->max_resized_saved)
501
/* remove the last one (last used one) */
502
RemovePicture(self, &self->resized, self->n_resized - 1,
504
if (self->cache->max_resized_saved)
505
/* add it to the top of the resized list */
506
AddPicture(self, &self->resized, &self->n_resized, pic);
508
free_pic = TRUE; /* don't leak mem! */
511
g_assert(pic != NULL);
513
DrawRGBA(target, target_w, target_h,
514
pic->data, pic->width, pic->height,