X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=render%2Fimage.c;h=924504fdf9c1e3a8973191852a40ce854c71179e;hb=50d662681160c309ea86268c0d05794b87b75593;hp=09d94f17dd7c19c3b4520b3be57c26f318a36b42;hpb=16f46c296d1fcd3f27fc62a18e71c55fb3fd3e88;p=chaz%2Fopenbox diff --git a/render/image.c b/render/image.c index 09d94f17..924504fd 100644 --- a/render/image.c +++ b/render/image.c @@ -1,7 +1,8 @@ /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- image.c for the Openbox window manager - Copyright (c) 2003 Ben Jansens + Copyright (c) 2006 Mikael Magnusson + Copyright (c) 2003-2007 Dana Jansens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,158 +20,257 @@ #include "geom.h" #include "image.h" #include "color.h" +#include "imagecache.h" #include -#define AVERAGE(a, b) ( ((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)) ) +#define FRACTION 12 +#define FLOOR(i) ((i) & (~0UL << FRACTION)) +#define AVERAGE(a, b) (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b))) -static void scale_line(RrPixel32 *dest, RrPixel32 *source, gint w, gint dw) +void RrImagePicInit(RrImagePic *pic, gint w, gint h, RrPixel32 *data) { - gint num_pixels = dw; - gint int_part = w / dw; - gint fract_part = w % dw; - gint err = 0; + gint i; + + pic->width = w; + pic->height = h; + pic->data = data; + pic->sum = 0; + for (i = w*h; i > 0; --i) + pic->sum += *(data++); +} - while (num_pixels-- > 0) { - *dest++ = *source; - source += int_part; - err += fract_part; - if (err >= dw) { - err -= dw; - source++; - } +static void RrImagePicFree(RrImagePic *pic) +{ + if (pic) { + g_free(pic->data); + g_free(pic); } } -static RrPixel32* scale_half(RrPixel32 *source, gint w, gint h) +/*! Add a picture to an Image, that is, add another copy of the image at + another size. This may add it to the "originals" list or to the + "resized" list. */ +static void AddPicture(RrImage *self, RrImagePic ***list, gint *len, + RrImagePic *pic) { - RrPixel32 *out, *dest, *sourceline, *sourceline2; - gint dw, dh, x, y; + gint i; - sourceline = source; - sourceline2 = source + w; + g_assert(pic->width > 0 && pic->height > 0); - dw = w >> 1; - dh = h >> 1; + g_assert(g_hash_table_lookup(self->cache->table, pic) == NULL); - out = dest = g_new(RrPixel32, dw * dh); + /* grow the list */ + *list = g_renew(RrImagePic*, *list, ++*len); - for (y = 0; y < dh; ++y) { - RrPixel32 *s, *s2; + /* move everything else down one */ + for (i = *len-1; i > 0; --i) + (*list)[i] = (*list)[i-1]; - s = sourceline; - s2 = sourceline2; + /* set the new picture up at the front of the list */ + (*list)[0] = pic; - for (x = 0; x < dw; ++x) { - *dest++ = AVERAGE(AVERAGE(*s, *(s+1)), - AVERAGE(*s2, *(s2+1))); - s += 2; - s2 += 2; - } - sourceline += w << 1; - sourceline2 += w << 1; - } - return out; + /* add the picture as a key to point to this image in the cache */ + g_hash_table_insert(self->cache->table, (*list)[0], self); + +/* +#ifdef DEBUG + g_debug("Adding %s picture to the cache:\n " + "Image 0x%lx, w %d h %d Hash %u", + (*list == self->original ? "ORIGINAL" : "RESIZED"), + (gulong)self, pic->width, pic->height, RrImagePicHash(pic)); +#endif +*/ } -static RrPixel32* scale_rect(RrPixel32 *fullsource, - gint w, gint h, gint dw, gint dh) +/*! Remove a picture from an Image. This may remove it from the "originals" + list or the "resized" list. */ +static void RemovePicture(RrImage *self, RrImagePic ***list, + gint i, gint *len) { - RrPixel32 *out, *dest; - RrPixel32 *source = fullsource; - RrPixel32 *oldsource = NULL; - RrPixel32 *prev_source = NULL; - gint num_pixels; - gint int_part; - gint fract_part; - gint err = 0; - - while (dw <= (w >> 1) && dh <= (h >> 1)) { - source = scale_half(source, w, h); - w >>= 1; h >>= 1; - g_free(oldsource); - oldsource = source; - } + gint j; + +/* +#ifdef DEBUG + g_debug("Removing %s picture from the cache:\n " + "Image 0x%lx, w %d h %d Hash %u", + (*list == self->original ? "ORIGINAL" : "RESIZED"), + (gulong)self, (*list)[i]->width, (*list)[i]->height, + RrImagePicHash((*list)[i])); +#endif +*/ - num_pixels = dh; - int_part = (h / dh) * w; - fract_part = h % dh; + /* remove the picture as a key in the cache */ + g_hash_table_remove(self->cache->table, (*list)[i]); - out = dest = g_new(RrPixel32, dw * dh); + /* free the picture */ + RrImagePicFree((*list)[i]); + /* shift everything down one */ + for (j = i; j < *len-1; ++j) + (*list)[j] = (*list)[j+1]; + /* shrink the list */ + *list = g_renew(RrImagePic*, *list, --*len); +} - while (num_pixels-- > 0) { - if (source == prev_source) { - memcpy(dest, dest - dw, dw * sizeof(RrPixel32)); - } else { - scale_line(dest, source, w, dw); - prev_source = source; - } - dest += dw; - source += int_part; - err += fract_part; - if (err >= dh) { - err -= dh; - source += w; +/*! Given a picture in RGBA format, of a specified size, resize it to the new + requested size (but keep its aspect ratio). If the image does not need to + be resized (it is already the right size) then this returns NULL. Otherwise + it returns a newly allocated RrImagePic with the resized picture inside it +*/ +static RrImagePic* ResizeImage(RrPixel32 *src, + gulong srcW, gulong srcH, + gulong dstW, gulong dstH) +{ + RrPixel32 *dst, *dststart; + RrImagePic *pic; + gulong dstX, dstY, srcX, srcY; + gulong srcX1, srcX2, srcY1, srcY2; + gulong ratioX, ratioY; + gulong aspectW, aspectH; + + /* XXX should these variables be ensured to not be zero in the callers? */ + srcW = srcW ? srcW : 1; + srcH = srcH ? srcH : 1; + dstW = dstW ? dstW : 1; + dstH = dstH ? dstH : 1; + + /* keep the aspect ratio */ + aspectW = dstW; + aspectH = (gint)(dstW * ((gdouble)srcH / srcW)); + if (aspectH > dstH) { + aspectH = dstH; + aspectW = (gint)(dstH * ((gdouble)srcW / srcH)); + } + dstW = aspectW ? aspectW : 1; + dstH = aspectH ? aspectH : 1; + + if (srcW == dstW && srcH == dstH) + return NULL; /* no scaling needed! */ + + dststart = dst = g_new(RrPixel32, dstW * dstH); + + ratioX = (srcW << FRACTION) / dstW; + ratioY = (srcH << FRACTION) / dstH; + + srcY2 = 0; + for (dstY = 0; dstY < dstH; dstY++) { + srcY1 = srcY2; + srcY2 += ratioY; + + srcX2 = 0; + for (dstX = 0; dstX < dstW; dstX++) { + gulong red = 0, green = 0, blue = 0, alpha = 0; + gulong portionX, portionY, portionXY, sumXY = 0; + RrPixel32 pixel; + + srcX1 = srcX2; + srcX2 += ratioX; + + for (srcY = srcY1; srcY < srcY2; srcY += (1UL << FRACTION)) { + if (srcY == srcY1) { + srcY = FLOOR(srcY); + portionY = (1UL << FRACTION) - (srcY1 - srcY); + if (portionY > srcY2 - srcY1) + portionY = srcY2 - srcY1; + } + else if (srcY == FLOOR(srcY2)) + portionY = srcY2 - srcY; + else + portionY = (1UL << FRACTION); + + for (srcX = srcX1; srcX < srcX2; srcX += (1UL << FRACTION)) { + if (srcX == srcX1) { + srcX = FLOOR(srcX); + portionX = (1UL << FRACTION) - (srcX1 - srcX); + if (portionX > srcX2 - srcX1) + portionX = srcX2 - srcX1; + } + else if (srcX == FLOOR(srcX2)) + portionX = srcX2 - srcX; + else + portionX = (1UL << FRACTION); + + portionXY = (portionX * portionY) >> FRACTION; + sumXY += portionXY; + + pixel = *(src + (srcY >> FRACTION) * srcW + + (srcX >> FRACTION)); + red += ((pixel >> RrDefaultRedOffset) & 0xFF) + * portionXY; + green += ((pixel >> RrDefaultGreenOffset) & 0xFF) + * portionXY; + blue += ((pixel >> RrDefaultBlueOffset) & 0xFF) + * portionXY; + alpha += ((pixel >> RrDefaultAlphaOffset) & 0xFF) + * portionXY; + } + } + + g_assert(sumXY != 0); + red /= sumXY; + green /= sumXY; + blue /= sumXY; + alpha /= sumXY; + + *dst++ = (red << RrDefaultRedOffset) | + (green << RrDefaultGreenOffset) | + (blue << RrDefaultBlueOffset) | + (alpha << RrDefaultAlphaOffset); } } - g_free(oldsource); + pic = g_new(RrImagePic, 1); + RrImagePicInit(pic, dstW, dstH, dststart); - return out; + return pic; } -void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba, - gint target_w, gint target_h, - RrRect *area) +/*! This draws an RGBA picture into the target, within the rectangle specified + by the area parameter. If the area's size differs from the source's then it + will be centered within the rectangle */ +void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h, + RrPixel32 *source, gint source_w, gint source_h, + gint alpha, RrRect *area) { RrPixel32 *dest; - RrPixel32 *source; - gint sw, sh, dw, dh; gint col, num_pixels; + gint dw, dh; - sw = rgba->width; - sh = rgba->height; + g_assert(source_w <= area->width && source_h <= area->height); + g_assert(area->x + area->width <= target_w); + g_assert(area->y + area->height <= target_h); - /* keep the ratio */ + /* keep the aspect ratio */ dw = area->width; - dh = (int)(dw * ((double)sh / sw)); + dh = (gint)(dw * ((gdouble)source_h / source_w)); if (dh > area->height) { dh = area->height; - dw = (int)(dh * ((double)sw / sh)); - } - - if (sw != dw || sh != dh) { - /*if (!(rgba->cache && dw == rgba->cwidth && dh == rgba->cheight))*/ { - g_free(rgba->cache); - rgba->cache = scale_rect(rgba->data, sw, sh, dw, dh); - rgba->cwidth = dw; - rgba->cheight = dh; - } - source = rgba->cache; - } else { - source = rgba->data; + dw = (gint)(dh * ((gdouble)source_w / source_h)); } - /* copy source -> dest, and apply the alpha channel */ + /* copy source -> dest, and apply the alpha channel. + center the image if it is smaller than the area */ col = 0; num_pixels = dw * dh; - dest = target + area->x + target_w * area->y; + dest = target + area->x + (area->width - dw) / 2 + + (target_w * (area->y + (area->height - dh) / 2)); while (num_pixels-- > 0) { - guchar alpha, r, g, b, bgr, bgg, bgb; + guchar a, r, g, b, bgr, bgg, bgb; - alpha = *source >> RrDefaultAlphaOffset; + /* apply the rgba's opacity as well */ + a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8; r = *source >> RrDefaultRedOffset; g = *source >> RrDefaultGreenOffset; b = *source >> RrDefaultBlueOffset; - + /* background color */ bgr = *dest >> RrDefaultRedOffset; bgg = *dest >> RrDefaultGreenOffset; bgb = *dest >> RrDefaultBlueOffset; - r = bgr + (((r - bgr) * alpha) >> 8); - g = bgg + (((g - bgg) * alpha) >> 8); - b = bgb + (((b - bgb) * alpha) >> 8); + r = bgr + (((r - bgr) * a) >> 8); + g = bgg + (((g - bgg) * a) >> 8); + b = bgb + (((b - bgb) * a) >> 8); *dest = ((r << RrDefaultRedOffset) | (g << RrDefaultGreenOffset) | @@ -179,9 +279,238 @@ void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba, dest++; source++; - if (col++ >= dw) { + if (++col >= dw) { col = 0; dest += target_w - dw; } } } + +/*! Draw an RGBA texture into a target pixel buffer. */ +void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba, + gint target_w, gint target_h, + RrRect *area) +{ + RrImagePic *scaled; + + scaled = ResizeImage(rgba->data, rgba->width, rgba->height, + area->width, area->height); + + if (scaled) { +#ifdef DEBUG + g_warning("Scaling an RGBA! You should avoid this and just make " + "it the right size yourself!"); +#endif + DrawRGBA(target, target_w, target_h, + scaled->data, scaled->width, scaled->height, + rgba->alpha, area); + RrImagePicFree(scaled); + } + else + DrawRGBA(target, target_w, target_h, + rgba->data, rgba->width, rgba->height, + rgba->alpha, area); +} + +/*! Create a new RrImage, which is linked to an image cache */ +RrImage* RrImageNew(RrImageCache *cache) +{ + RrImage *self; + + g_assert(cache != NULL); + + self = g_new0(RrImage, 1); + self->ref = 1; + self->cache = cache; + return self; +} + +void RrImageRef(RrImage *self) +{ + ++self->ref; +} + +void RrImageUnref(RrImage *self) +{ + if (self && --self->ref == 0) { +/* +#ifdef DEBUG + g_debug("Refcount to 0, removing ALL pictures from the cache:\n " + "Image 0x%lx", (gulong)self); +#endif +*/ + while (self->n_original > 0) + RemovePicture(self, &self->original, 0, &self->n_original); + while (self->n_resized > 0) + RemovePicture(self, &self->resized, 0, &self->n_resized); + g_free(self); + } +} + +/*! Add a new picture with the given RGBA pixel data and dimensions into the + RrImage. This adds an "original" picture to the image. +*/ +void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h) +{ + gint i; + RrImagePic *pic; + + /* make sure we don't already have this size.. */ + for (i = 0; i < self->n_original; ++i) + if (self->original[i]->width == w && self->original[i]->height == h) { +/* +#ifdef DEBUG + g_debug("Found duplicate ORIGINAL image:\n " + "Image 0x%lx, w %d h %d", (gulong)self, w, h); +#endif +*/ + return; + } + + /* remove any resized pictures of this same size */ + for (i = 0; i < self->n_resized; ++i) + if (self->resized[i]->width == w || self->resized[i]->height == h) { + RemovePicture(self, &self->resized, i, &self->n_resized); + break; + } + + /* add the new picture */ + pic = g_new(RrImagePic, 1); + RrImagePicInit(pic, w, h, g_memdup(data, w*h*sizeof(RrPixel32))); + AddPicture(self, &self->original, &self->n_original, pic); +} + +/*! Remove the picture from the RrImage which has the given dimensions. This + removes an "original" picture from the image. +*/ +void RrImageRemovePicture(RrImage *self, gint w, gint h) +{ + gint i; + + /* remove any resized pictures of this same size */ + for (i = 0; i < self->n_original; ++i) + if (self->original[i]->width == w && self->original[i]->height == h) { + RemovePicture(self, &self->original, i, &self->n_original); + break; + } +} + +/*! Draw an RrImage texture into a target pixel buffer. If the RrImage does + not contain a picture of the appropriate size, then one of its "original" + pictures will be resized and used (and stored in the RrImage as a "resized" + picture). + */ +void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img, + gint target_w, gint target_h, + RrRect *area) +{ + gint i, min_diff, min_i, min_aspect_diff, min_aspect_i; + RrImage *self; + RrImagePic *pic; + gboolean free_pic; + + self = img->image; + pic = NULL; + free_pic = FALSE; + + /* is there an original of this size? (only the larger of + w or h has to be right cuz we maintain aspect ratios) */ + for (i = 0; i < self->n_original; ++i) + if ((self->original[i]->width >= self->original[i]->height && + self->original[i]->width == area->width) || + (self->original[i]->width <= self->original[i]->height && + self->original[i]->height == area->height)) + { + pic = self->original[i]; + break; + } + + /* is there a resize of this size? */ + for (i = 0; i < self->n_resized; ++i) + if ((self->resized[i]->width >= self->resized[i]->height && + self->resized[i]->width == area->width) || + (self->resized[i]->width <= self->resized[i]->height && + self->resized[i]->height == area->height)) + { + gint j; + RrImagePic *saved; + + /* save the selected one */ + saved = self->resized[i]; + + /* shift all the others down */ + for (j = i; j > 0; --j) + self->resized[j] = self->resized[j-1]; + + /* and move the selected one to the top of the list */ + self->resized[0] = saved; + + pic = self->resized[0]; + break; + } + + if (!pic) { + gdouble aspect; + + /* find an original with a close size */ + min_diff = min_aspect_diff = -1; + min_i = min_aspect_i = 0; + aspect = ((gdouble)area->width) / area->height; + for (i = 0; i < self->n_original; ++i) { + gint diff; + gint wdiff, hdiff; + gdouble myasp; + + /* our size difference metric.. */ + wdiff = self->original[i]->width - area->width; + hdiff = self->original[i]->height - area->height; + diff = (wdiff * wdiff) + (hdiff * hdiff); + + /* find the smallest difference */ + if (min_diff < 0 || diff < min_diff) { + min_diff = diff; + min_i = i; + } + /* and also find the smallest difference with the same aspect + ratio (and prefer this one) */ + myasp = ((gdouble)self->original[i]->width) / + self->original[i]->height; + if (ABS(aspect - myasp) < 0.0000001 && + (min_aspect_diff < 0 || diff < min_aspect_diff)) + { + min_aspect_diff = diff; + min_aspect_i = i; + } + } + + /* use the aspect ratio correct source if there is one */ + if (min_aspect_i >= 0) + min_i = min_aspect_i; + + /* resize the original to the given area */ + pic = ResizeImage(self->original[min_i]->data, + self->original[min_i]->width, + self->original[min_i]->height, + area->width, area->height); + + /* add the resized image to the image, as the first in the resized + list */ + if (self->n_resized >= self->cache->max_resized_saved) + /* remove the last one (last used one) */ + RemovePicture(self, &self->resized, self->n_resized - 1, + &self->n_resized); + if (self->cache->max_resized_saved) + /* add it to the top of the resized list */ + AddPicture(self, &self->resized, &self->n_resized, pic); + else + free_pic = TRUE; /* don't leak mem! */ + } + + g_assert(pic != NULL); + + DrawRGBA(target, target_w, target_h, + pic->data, pic->width, pic->height, + img->alpha, area); + if (free_pic) + RrImagePicFree(pic); +}