diff options
Diffstat (limited to 'android/skin/image.c')
-rw-r--r-- | android/skin/image.c | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/android/skin/image.c b/android/skin/image.c new file mode 100644 index 0000000..051fc6d --- /dev/null +++ b/android/skin/image.c @@ -0,0 +1,742 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** 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. +*/ +#include "android/skin/image.h" +#include "android/resource.h" +#include <assert.h> +#include <limits.h> + +#define DEBUG 0 + +#if DEBUG +static void D(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} +#else +#define D(...) do{}while(0) +#endif + +/********************************************************************************/ +/********************************************************************************/ +/***** *****/ +/***** U T I L I T Y F U N C T I O N S *****/ +/***** *****/ +/********************************************************************************/ +/********************************************************************************/ + +SDL_Surface* +sdl_surface_from_argb32( void* base, int w, int h ) +{ + return SDL_CreateRGBSurfaceFrom( + base, w, h, 32, w*4, +#if WORDS_BIGENDIAN + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 +#else + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 +#endif + ); +} + +static void* +rotate_image( void* data, unsigned width, unsigned height, SkinRotation rotation ) +{ + void* result; + + result = malloc( width*height*4 ); + if (result == NULL) + return NULL; + + switch (rotation & 3) + { + case SKIN_ROTATION_0: + memcpy( (char*)result, (const char*)data, width*height*4 ); + break; + + case SKIN_ROTATION_270: + { + unsigned* start = (unsigned*)data; + unsigned* src_line = start + (width-1); + unsigned* dst_line = (unsigned*)result; + unsigned hh; + + for (hh = width; hh > 0; hh--) + { + unsigned* src = src_line; + unsigned* dst = dst_line; + unsigned count = height; + + for ( ; count > 0; count-- ) { + dst[0] = src[0]; + dst += 1; + src += width; + } + + src_line -= 1; + dst_line += height; + } + } + break; + + case SKIN_ROTATION_180: + { + unsigned* start = (unsigned*)data; + unsigned* src_line = start + width*(height-1); + unsigned* dst_line = (unsigned*)result; + unsigned hh; + + for (hh = height; hh > 0; hh--) + { + unsigned* src = src_line + (width-1); + unsigned* dst = dst_line; + + while (src >= src_line) + *dst++ = *src--; + + dst_line += width; + src_line -= width; + } + } + break; + + case SKIN_ROTATION_90: + { + unsigned* start = (unsigned*)data; + unsigned* src_line = start + width*(height-1); + unsigned* dst_line = (unsigned*)result ; + unsigned hh; + + for (hh = width; hh > 0; hh--) + { + unsigned* src = src_line; + unsigned* dst = dst_line; + unsigned count; + + for (count = height; count > 0; count--) { + dst[0] = src[0]; + dst += 1; + src -= width; + } + + dst_line += height; + src_line += 1; + } + } + break; + + default: + ; + } + + return result; +} + + +static void +blend_image( unsigned* dst_pixels, + unsigned* src_pixels, + unsigned w, + unsigned h, + int alpha ) +{ + unsigned* dst = dst_pixels; + unsigned* dst_end = dst + w*h; + unsigned* src = src_pixels; + + for ( ; dst < dst_end; dst++, src++ ) + { + { + unsigned ag = (src[0] >> 8) & 0xff00ff; + unsigned rb = src[0] & 0xff00ff; + + ag = (ag*alpha) & 0xff00ff00; + rb = ((rb*alpha) >> 8) & 0x00ff00ff; + + dst[0] = ag | rb; + } + } +} + + +static unsigned +skin_image_desc_hash( SkinImageDesc* desc ) +{ + unsigned h = 0; + int n; + + for (n = 0; desc->path[n] != 0; n++) { + int c = desc->path[n]; + h = h*33 + c; + } + h += desc->rotation*1573; + h += desc->blend * 7; + + return h; +} + + +static int +skin_image_desc_equal( SkinImageDesc* a, + SkinImageDesc* b ) +{ + return (a->rotation == b->rotation && + a->blend == b->blend && + !strcmp(a->path, b->path)); +} + +/********************************************************************************/ +/********************************************************************************/ +/***** *****/ +/***** S K I N I M A G E S *****/ +/***** *****/ +/********************************************************************************/ +/********************************************************************************/ + +enum { + SKIN_IMAGE_CLONE = (1 << 0) /* this image is a clone */ +}; + +struct SkinImage { + unsigned hash; + SkinImage* link; + int ref_count; + SkinImage* next; + SkinImage* prev; + SDL_Surface* surface; + unsigned flags; + unsigned w, h; + void* pixels; /* 32-bit ARGB */ + SkinImageDesc desc; +}; + + + + +static const SkinImage _no_image[1] = { + { 0, NULL, 0, NULL, NULL, NULL, 0, 0, 0, NULL, { "<none>", SKIN_ROTATION_0, 0 } } +}; + +SkinImage* SKIN_IMAGE_NONE = (SkinImage*)&_no_image; + +static void +skin_image_free( SkinImage* image ) +{ + if (image && image != _no_image) + { + if (image->surface) { + SDL_FreeSurface(image->surface); + image->surface = NULL; + } + + if (image->pixels) { + free( image->pixels ); + image->pixels = NULL; + } + + free(image); + } +} + + +static SkinImage* +skin_image_alloc( SkinImageDesc* desc, unsigned hash ) +{ + int len = strlen(desc->path); + SkinImage* image = calloc(1, sizeof(*image) + len + 1); + + if (image) { + image->desc = desc[0]; + image->desc.path = (const char*)(image + 1); + memcpy( (char*)image->desc.path, desc->path, len ); + ((char*)image->desc.path)[len] = 0; + + image->hash = hash; + image->next = image->prev = image; + image->ref_count = 1; + } + return image; +} + + +extern void *loadpng(const char *fn, unsigned *_width, unsigned *_height); +extern void *readpng(const unsigned char* base, size_t size, unsigned *_width, unsigned *_height); + +static int +skin_image_load( SkinImage* image ) +{ + void* data; + unsigned w, h; + const char* path = image->desc.path; + + if (path[0] == ':') { + size_t size; + const unsigned char* base; + + if (path[1] == '/' || path[1] == '\\') + path += 1; + + base = android_resource_find( path+1, &size ); + if (base == NULL) { + fprintf(stderr, "failed to locate built-in image file '%s'\n", path ); + return -1; + } + + data = readpng(base, size, &w, &h); + if (data == NULL) { + fprintf(stderr, "failed to load built-in image file '%s'\n", path ); + return -1; + } + } else { + data = loadpng(path, &w, &h); + if (data == NULL) { + fprintf(stderr, "failed to load image file '%s'\n", path ); + return -1; + } + } + + /* the data is loaded into memory as RGBA bytes by libpng. we want to manage + * the values as 32-bit ARGB pixels, so swap the bytes accordingly depending + * on our CPU endianess + */ + { + unsigned* d = data; + unsigned* d_end = d + w*h; + + for ( ; d < d_end; d++ ) { + unsigned pix = d[0]; +#if WORDS_BIGENDIAN + /* R,G,B,A read as RGBA => ARGB */ + pix = ((pix >> 8) & 0xffffff) | (pix << 24); +#else + /* R,G,B,A read as ABGR => ARGB */ + pix = (pix & 0xff00ff00) | ((pix >> 16) & 0xff) | ((pix & 0xff) << 16); +#endif + d[0] = pix; + } + } + + image->pixels = data; + image->w = w; + image->h = h; + + image->surface = sdl_surface_from_argb32( image->pixels, w, h ); + if (image->surface == NULL) { + fprintf(stderr, "failed to create SDL surface for '%s' image\n", path); + return -1; + } + return 0; +} + + +/* simple hash table for images */ + +#define NUM_BUCKETS 64 + +typedef struct { + SkinImage* buckets[ NUM_BUCKETS ]; + SkinImage mru_head; + int num_images; + unsigned long total_pixels; + unsigned long max_pixels; + unsigned long total_images; +} SkinImageCache; + + +static void +skin_image_cache_init( SkinImageCache* cache ) +{ + memset(cache, 0, sizeof(*cache)); +#if DEBUG + cache->max_pixels = 1; +#else + cache->max_pixels = 4*1024*1024; /* limit image cache to 4 MB */ +#endif + cache->mru_head.next = cache->mru_head.prev = &cache->mru_head; +} + + +static void +skin_image_cache_remove( SkinImageCache* cache, + SkinImage* image ) +{ + /* remove from hash table */ + SkinImage** pnode = cache->buckets + (image->hash & (NUM_BUCKETS-1)); + SkinImage* node; + + for (;;) { + node = *pnode; + assert(node != NULL); + if (node == NULL) /* should not happen */ + break; + if (node == image) { + *pnode = node->link; + break; + } + pnode = &node->link; + } + + D( "skin_image_cache: remove '%s' (rot=%d), %d pixels\n", + node->desc.path, node->desc.rotation, node->w*node->h ); + + /* remove from mru list */ + image->prev->next = image->next; + image->next->prev = image->prev; + + cache->total_pixels -= image->w*image->h; + cache->total_images -= 1; +} + + +static SkinImage* +skin_image_cache_raise( SkinImageCache* cache, + SkinImage* image ) +{ + if (image != cache->mru_head.next) { + SkinImage* prev = image->prev; + SkinImage* next = image->next; + + /* remove from mru list */ + prev->next = next; + next->prev = prev; + + /* add to top */ + image->prev = &cache->mru_head; + image->next = image->prev->next; + image->prev->next = image; + image->next->prev = image; + } + return image; +} + + +static void +skin_image_cache_flush( SkinImageCache* cache ) +{ + SkinImage* image = cache->mru_head.prev; + int count = 0; + + D("skin_image_cache_flush: starting\n"); + while (cache->total_pixels > cache->max_pixels && + image != &cache->mru_head) + { + SkinImage* prev = image->prev; + + if (image->ref_count == 0) { + skin_image_cache_remove(cache, image); + count += 1; + } + image = prev; + } + D("skin_image_cache_flush: finished, %d images flushed\n", count); +} + + +static SkinImage** +skin_image_lookup_p( SkinImageCache* cache, + SkinImageDesc* desc, + unsigned *phash ) +{ + unsigned h = skin_image_desc_hash(desc); + unsigned index = h & (NUM_BUCKETS-1); + SkinImage** pnode = &cache->buckets[index]; + for (;;) { + SkinImage* node = *pnode; + if (node == NULL) + break; + if (node->hash == h && skin_image_desc_equal(desc, &node->desc)) + break; + pnode = &node->link; + } + *phash = h; + return pnode; +} + + +static SkinImage* +skin_image_create( SkinImageDesc* desc, unsigned hash ) +{ + SkinImage* node; + + node = skin_image_alloc( desc, hash ); + if (node == NULL) + return SKIN_IMAGE_NONE; + + if (desc->rotation == SKIN_ROTATION_0 && + desc->blend == SKIN_BLEND_FULL) + { + if (skin_image_load(node) < 0) { + skin_image_free(node); + return SKIN_IMAGE_NONE; + } + } + else + { + SkinImageDesc desc0 = desc[0]; + SkinImage* parent; + + desc0.rotation = SKIN_ROTATION_0; + desc0.blend = SKIN_BLEND_FULL; + + parent = skin_image_find( &desc0 ); + if (parent == SKIN_IMAGE_NONE) + return SKIN_IMAGE_NONE; + + SDL_LockSurface(parent->surface); + + if (desc->rotation == SKIN_ROTATION_90 || + desc->rotation == SKIN_ROTATION_270) + { + node->w = parent->h; + node->h = parent->w; + } else { + node->w = parent->w; + node->h = parent->h; + } + + node->pixels = rotate_image( parent->pixels, parent->w, parent->h, + desc->rotation ); + + SDL_UnlockSurface(parent->surface); + skin_image_unref(&parent); + + if (node->pixels == NULL) { + skin_image_free(node); + return SKIN_IMAGE_NONE; + } + + if (desc->blend != SKIN_BLEND_FULL) + blend_image( node->pixels, node->pixels, node->w, node->h, desc->blend ); + + node->surface = sdl_surface_from_argb32( node->pixels, node->w, node->h ); + if (node->surface == NULL) { + skin_image_free(node); + return SKIN_IMAGE_NONE; + } + } + return node; +} + + +static SkinImageCache _image_cache[1]; +static int _image_cache_init; + +SkinImage* +skin_image_find( SkinImageDesc* desc ) +{ + SkinImageCache* cache = _image_cache; + unsigned hash; + SkinImage** pnode = skin_image_lookup_p( cache, desc, &hash ); + SkinImage* node = *pnode; + + if (!_image_cache_init) { + _image_cache_init = 1; + skin_image_cache_init(cache); + } + + if (node) { + node->ref_count += 1; + return skin_image_cache_raise( cache, node ); + } + node = skin_image_create( desc, hash ); + if (node == SKIN_IMAGE_NONE) + return node; + + /* add to hash table */ + node->link = *pnode; + *pnode = node; + + /* add to mru list */ + skin_image_cache_raise( cache, node ); + + D( "skin_image_cache: add '%s' (rot=%d), %d pixels\n", + node->desc.path, node->desc.rotation, node->w*node->h ); + + cache->total_pixels += node->w*node->h; + if (cache->total_pixels > cache->max_pixels) + skin_image_cache_flush( cache ); + + return node; +} + + +SkinImage* +skin_image_find_simple( const char* path ) +{ + SkinImageDesc desc; + + desc.path = path; + desc.rotation = SKIN_ROTATION_0; + desc.blend = SKIN_BLEND_FULL; + + return skin_image_find( &desc ); +} + + +SkinImage* +skin_image_ref( SkinImage* image ) +{ + if (image && image != _no_image) + image->ref_count += 1; + + return image; +} + + +void +skin_image_unref( SkinImage** pimage ) +{ + SkinImage* image = *pimage; + + if (image) { + if (image != _no_image && --image->ref_count == 0) { + if ((image->flags & SKIN_IMAGE_CLONE) != 0) { + skin_image_free(image); + } + } + *pimage = NULL; + } +} + + +SkinImage* +skin_image_rotate( SkinImage* source, SkinRotation rotation ) +{ + SkinImageDesc desc; + SkinImage* image; + + if (source == _no_image || source->desc.rotation == rotation) + return source; + + desc = source->desc; + desc.rotation = rotation; + image = skin_image_find( &desc ); + skin_image_unref( &source ); + return image; +} + + +SkinImage* +skin_image_clone( SkinImage* source ) +{ + SkinImage* image; + + if (source == NULL || source == _no_image) + return SKIN_IMAGE_NONE; + + image = calloc(1,sizeof(*image)); + if (image == NULL) + goto Fail; + + image->desc = source->desc; + image->hash = source->hash; + image->flags = SKIN_IMAGE_CLONE; + image->w = source->w; + image->h = source->h; + image->pixels = rotate_image( source->pixels, source->w, source->h, + SKIN_ROTATION_0 ); + if (image->pixels == NULL) + goto Fail; + + image->surface = sdl_surface_from_argb32( image->pixels, image->w, image->h ); + if (image->surface == NULL) + goto Fail; + + return image; +Fail: + if (image != NULL) + skin_image_free(image); + return SKIN_IMAGE_NONE; +} + +SkinImage* +skin_image_clone_full( SkinImage* source, + SkinRotation rotation, + int blend ) +{ + SkinImageDesc desc; + SkinImage* clone; + + if (source == NULL || source == SKIN_IMAGE_NONE) + return SKIN_IMAGE_NONE; + + if (rotation == SKIN_ROTATION_0 && + blend == SKIN_BLEND_FULL) + { + return skin_image_clone(source); + } + + desc.path = source->desc.path; + desc.rotation = rotation; + desc.blend = blend; + + clone = skin_image_create( &desc, 0 ); + if (clone != SKIN_IMAGE_NONE) + clone->flags |= SKIN_IMAGE_CLONE; + + return clone; +} + +/* apply blending to a source skin image and copy the result to a target clone image */ +extern void +skin_image_blend_clone( SkinImage* clone, SkinImage* source, int blend ) +{ + SDL_LockSurface( clone->surface ); + blend_image( clone->pixels, source->pixels, source->w, source->h, blend ); + SDL_UnlockSurface( clone->surface ); + SDL_SetAlpha( clone->surface, SDL_SRCALPHA, 255 ); +} + +int +skin_image_w( SkinImage* image ) +{ + return image ? image->w : 0; +} + +int +skin_image_h( SkinImage* image ) +{ + return image ? image->h : 0; +} + +int +skin_image_org_w( SkinImage* image ) +{ + if (image) { + if (image->desc.rotation == SKIN_ROTATION_90 || + image->desc.rotation == SKIN_ROTATION_270) + return image->h; + else + return image->w; + } + return 0; +} + +int +skin_image_org_h( SkinImage* image ) +{ + if (image) { + if (image->desc.rotation == SKIN_ROTATION_90 || + image->desc.rotation == SKIN_ROTATION_270) + return image->w; + else + return image->h; + } + return 0; +} + +SDL_Surface* +skin_image_surface( SkinImage* image ) +{ + return image ? image->surface : NULL; +} |