X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=render%2Fimage.c;h=924504fdf9c1e3a8973191852a40ce854c71179e;hb=50d662681160c309ea86268c0d05794b87b75593;hp=5bd3285b19b8cc67cf1a05771911f84b77d40127;hpb=c0568cd7ef9542540b11068c002bb6a8d35f3d84;p=chaz%2Fopenbox diff --git a/render/image.c b/render/image.c index 5bd3285b..924504fd 100644 --- a/render/image.c +++ b/render/image.c @@ -1,64 +1,516 @@ -#include -#include "../kernel/geom.h" +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + image.c for the Openbox window manager + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "geom.h" #include "image.h" +#include "color.h" +#include "imagecache.h" + +#include + +#define FRACTION 12 +#define FLOOR(i) ((i) & (~0UL << FRACTION)) +#define AVERAGE(a, b) (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b))) + +void RrImagePicInit(RrImagePic *pic, gint w, gint h, RrPixel32 *data) +{ + gint i; + + pic->width = w; + pic->height = h; + pic->data = data; + pic->sum = 0; + for (i = w*h; i > 0; --i) + pic->sum += *(data++); +} + +static void RrImagePicFree(RrImagePic *pic) +{ + if (pic) { + g_free(pic->data); + g_free(pic); + } +} + +/*! 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) +{ + gint i; + + g_assert(pic->width > 0 && pic->height > 0); + + g_assert(g_hash_table_lookup(self->cache->table, pic) == NULL); + + /* grow the list */ + *list = g_renew(RrImagePic*, *list, ++*len); + + /* move everything else down one */ + for (i = *len-1; i > 0; --i) + (*list)[i] = (*list)[i-1]; + + /* set the new picture up at the front of the list */ + (*list)[0] = pic; + + /* 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 +*/ +} + +/*! 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) +{ + 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 +*/ + + /* remove the picture as a key in the cache */ + g_hash_table_remove(self->cache->table, (*list)[i]); + + /* 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); +} + +/*! 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); + } + } + + pic = g_new(RrImagePic, 1); + RrImagePicInit(pic, dstW, dstH, dststart); + + return pic; +} -void image_draw(pixel32 *target, TextureRGBA *rgba, Rect *position) +/*! 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) { - unsigned long *draw = rgba->data; - int c, sfw, sfh; - unsigned int i, e, bgi; - sfw = position->width; - sfh = position->height; - - g_assert(rgba->data != NULL); - - if ((rgba->width != sfw || rgba->height != sfh) && - (rgba->width != rgba->cwidth || rgba->height != rgba->cheight)) { - double dx = rgba->width / (double)sfw; - double dy = rgba->height / (double)sfh; - double px = 0.0; - double py = 0.0; - int iy = 0; - - /* scale it and cache it */ - if (rgba->cache != NULL) - g_free(rgba->cache); - rgba->cache = g_new(unsigned long, sfw * sfh); - rgba->cwidth = sfw; - rgba->cheight = sfh; - for (i = 0, c = 0, e = sfw*sfh; i < e; ++i) { - rgba->cache[i] = rgba->data[(int)px + iy]; - if (++c >= sfw) { - c = 0; - px = 0; - py += dy; - iy = (int)py * rgba->width; - } else - px += dx; + RrPixel32 *dest; + gint col, num_pixels; + gint dw, dh; + + 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 aspect ratio */ + dw = area->width; + dh = (gint)(dw * ((gdouble)source_h / source_w)); + if (dh > area->height) { + dh = area->height; + dw = (gint)(dh * ((gdouble)source_w / source_h)); } -/* do we use the cache we may have just created, or the original? */ - if (rgba->width != sfw || rgba->height != sfh) - draw = rgba->cache; + /* 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 + (area->width - dw) / 2 + + (target_w * (area->y + (area->height - dh) / 2)); + while (num_pixels-- > 0) { + guchar a, r, g, b, bgr, bgg, bgb; + + /* 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) * a) >> 8); + g = bgg + (((g - bgg) * a) >> 8); + b = bgb + (((b - bgb) * a) >> 8); + + *dest = ((r << RrDefaultRedOffset) | + (g << RrDefaultGreenOffset) | + (b << RrDefaultBlueOffset)); - /* apply the alpha channel */ - for (i = 0, c = 0, e = sfw*sfh; i < e; ++i, ++bgi) { - unsigned char alpha = draw[i] >> 24; - unsigned char r = draw[i] >> 16; - unsigned char g = draw[i] >> 8; - unsigned char b = draw[i]; + dest++; + source++; - /* background color */ - unsigned char bgr = target[i] >> default_red_shift; - unsigned char bgg = target[i] >> default_green_shift; - unsigned char bgb = target[i] >> default_blue_shift; + 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; - r = bgr + (((r - bgr) * alpha) >> 8); - g = bgg + (((g - bgg) * alpha) >> 8); - b = bgb + (((b - bgb) * alpha) >> 8); + scaled = ResizeImage(rgba->data, rgba->width, rgba->height, + area->width, area->height); - target[i] = (r << default_red_shift) | (g << default_green_shift) | - (b << default_blue_shift); + 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); }