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