+++ /dev/null
-/* -*- 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 <glib.h>
-
-#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;
-}
-
-/*! 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;
- 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));
- }
-
- /* 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));
-
- dest++;
- source++;
-
- 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);
-}