| /* 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 HOST_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 HOST_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; |
| } |