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