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