#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)
{
g_hash_table_insert(self->cache->table, (*list)[0], self);
#ifdef DEBUG
- g_print("Adding %s picture to the cache: "
- "Image 0x%x, w %d h %d Hash %u\n",
+ g_debug("Adding %s picture to the cache:\n "
+ "Image 0x%x, w %d h %d Hash %u",
(*list == self->original ? "ORIGINAL" : "RESIZED"),
(guint)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_print("Removing %s picture from the cache: "
- "Image 0x%x, w %d h %d Hash %u\n",
+ g_debug("Removing %s picture from the cache:\n "
+ "Image 0x%x, w %d h %d Hash %u",
(*list == self->original ? "ORIGINAL" : "RESIZED"),
(guint)self, (*list)[i]->width, (*list)[i]->height,
RrImagePicHash((*list)[i]));
/* remove the picture as a key in the cache */
g_hash_table_remove(self->cache->table, (*list)[i]);
- /* free the picture (and its rgba data) */
- g_free((*list)[i]);
- g_free((*list)[i]->data);
+ /* free the picture */
+ RrImagePicFree((*list)[i]);
/* shift everything down one */
for (j = i; j < *len-1; ++j)
(*list)[j] = (*list)[j+1];
*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)
+ gulong srcW, gulong srcH,
+ gulong dstW, gulong dstH)
{
- RrPixel32 *dst;
+ 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));
aspectH = dstH;
aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
}
- dstW = aspectW;
- dstH = aspectH;
+ dstW = aspectW ? aspectW : 1;
+ dstH = aspectH ? aspectH : 1;
if (srcW == dstW && srcH == dstH)
- return NULL; /* no scaling needed ! */
+ return NULL; /* no scaling needed! */
- pic = g_new(RrImagePic, 1);
- dst = g_new(RrPixel32, dstW * dstH);
- pic->width = dstW;
- pic->height = dstH;
- pic->data = dst;
+ dststart = dst = g_new(RrPixel32, dstW * dstH);
ratioX = (srcW << FRACTION) / dstW;
ratioY = (srcH << FRACTION) / dstH;
}
}
+ pic = g_new(RrImagePic, 1);
+ RrImagePicInit(pic, dstW, dstH, dststart);
+
return pic;
}
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;
}
}
+/*! Draw an RGBA texture into a target pixel buffer. */
void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
gint target_w, gint target_h,
RrRect *area)
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->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;
{
if (self && --self->ref == 0) {
#ifdef DEBUG
- g_print("Refcount to 0, removing ALL pictures from the cache: "
- "Image 0x%x\n", (guint)self);
+ g_debug("Refcount to 0, removing ALL pictures from the cache:\n "
+ "Image 0x%x", (guint)self);
#endif
while (self->n_original > 0)
RemovePicture(self, &self->original, 0, &self->n_original);
}
}
+/*! 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;
for (i = 0; i < self->n_original; ++i)
if (self->original[i]->width == w && self->original[i]->height == h) {
#ifdef DEBUG
- g_print("Found duplicate ORIGINAL image: "
- "Image 0x%x, w %d h %d\n", (guint)self, w, h);
+ g_debug("Found duplicate ORIGINAL image:\n "
+ "Image 0x%x, w %d h %d", (guint)self, w, h);
#endif
return;
}
/* add the new picture */
pic = g_new(RrImagePic, 1);
- pic->width = w;
- pic->height = h;
- pic->data = g_memdup(data, w*h*sizeof(RrPixel32));
+ 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;
}
}
+/*! 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;
+ 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 w or h has to be right cuz
we maintain aspect ratios) */
}
if (!pic) {
+ gdouble aspect;
+
/* find an original with a close size */
- min_diff = -1;
- min_i = 0;
+ 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,
/* add the resized image to the image, as the first in the resized
list */
- if (self->n_resized >= MAX_CACHE_RESIZED) {
+ 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);
- }
- /* add it to the top of the resized list */
- AddPicture(self, &self->resized, &self->n_resized, pic);
+ 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);
}