1/* Copyright (C) 2007-2008 The Android Open Source Project 2** 3** This software is licensed under the terms of the GNU General Public 4** License version 2, as published by the Free Software Foundation, and 5** may be copied, distributed, and modified under those terms. 6** 7** This program is distributed in the hope that it will be useful, 8** but WITHOUT ANY WARRANTY; without even the implied warranty of 9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10** GNU General Public License for more details. 11*/ 12#include "android/skin/image.h" 13#include "android/resource.h" 14#include <assert.h> 15#include <limits.h> 16 17#define DEBUG 0 18 19#if DEBUG 20static void D(const char* fmt, ...) 21{ 22 va_list args; 23 va_start(args, fmt); 24 vfprintf(stderr, fmt, args); 25 va_end(args); 26} 27#else 28#define D(...) do{}while(0) 29#endif 30 31/********************************************************************************/ 32/********************************************************************************/ 33/***** *****/ 34/***** U T I L I T Y F U N C T I O N S *****/ 35/***** *****/ 36/********************************************************************************/ 37/********************************************************************************/ 38 39SDL_Surface* 40sdl_surface_from_argb32( void* base, int w, int h ) 41{ 42 return SDL_CreateRGBSurfaceFrom( 43 base, w, h, 32, w*4, 44#if HOST_WORDS_BIGENDIAN 45 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 46#else 47 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 48#endif 49 ); 50} 51 52static void* 53rotate_image( void* data, unsigned width, unsigned height, SkinRotation rotation ) 54{ 55 void* result; 56 57 result = malloc( width*height*4 ); 58 if (result == NULL) 59 return NULL; 60 61 switch (rotation & 3) 62 { 63 case SKIN_ROTATION_0: 64 memcpy( (char*)result, (const char*)data, width*height*4 ); 65 break; 66 67 case SKIN_ROTATION_270: 68 { 69 unsigned* start = (unsigned*)data; 70 unsigned* src_line = start + (width-1); 71 unsigned* dst_line = (unsigned*)result; 72 unsigned hh; 73 74 for (hh = width; hh > 0; hh--) 75 { 76 unsigned* src = src_line; 77 unsigned* dst = dst_line; 78 unsigned count = height; 79 80 for ( ; count > 0; count-- ) { 81 dst[0] = src[0]; 82 dst += 1; 83 src += width; 84 } 85 86 src_line -= 1; 87 dst_line += height; 88 } 89 } 90 break; 91 92 case SKIN_ROTATION_180: 93 { 94 unsigned* start = (unsigned*)data; 95 unsigned* src_line = start + width*(height-1); 96 unsigned* dst_line = (unsigned*)result; 97 unsigned hh; 98 99 for (hh = height; hh > 0; hh--) 100 { 101 unsigned* src = src_line + (width-1); 102 unsigned* dst = dst_line; 103 104 while (src >= src_line) 105 *dst++ = *src--; 106 107 dst_line += width; 108 src_line -= width; 109 } 110 } 111 break; 112 113 case SKIN_ROTATION_90: 114 { 115 unsigned* start = (unsigned*)data; 116 unsigned* src_line = start + width*(height-1); 117 unsigned* dst_line = (unsigned*)result ; 118 unsigned hh; 119 120 for (hh = width; hh > 0; hh--) 121 { 122 unsigned* src = src_line; 123 unsigned* dst = dst_line; 124 unsigned count; 125 126 for (count = height; count > 0; count--) { 127 dst[0] = src[0]; 128 dst += 1; 129 src -= width; 130 } 131 132 dst_line += height; 133 src_line += 1; 134 } 135 } 136 break; 137 138 default: 139 ; 140 } 141 142 return result; 143} 144 145 146static void 147blend_image( unsigned* dst_pixels, 148 unsigned* src_pixels, 149 unsigned w, 150 unsigned h, 151 int alpha ) 152{ 153 unsigned* dst = dst_pixels; 154 unsigned* dst_end = dst + w*h; 155 unsigned* src = src_pixels; 156 157 for ( ; dst < dst_end; dst++, src++ ) 158 { 159 { 160 unsigned ag = (src[0] >> 8) & 0xff00ff; 161 unsigned rb = src[0] & 0xff00ff; 162 163 ag = (ag*alpha) & 0xff00ff00; 164 rb = ((rb*alpha) >> 8) & 0x00ff00ff; 165 166 dst[0] = ag | rb; 167 } 168 } 169} 170 171 172static unsigned 173skin_image_desc_hash( SkinImageDesc* desc ) 174{ 175 unsigned h = 0; 176 int n; 177 178 for (n = 0; desc->path[n] != 0; n++) { 179 int c = desc->path[n]; 180 h = h*33 + c; 181 } 182 h += desc->rotation*1573; 183 h += desc->blend * 7; 184 185 return h; 186} 187 188 189static int 190skin_image_desc_equal( SkinImageDesc* a, 191 SkinImageDesc* b ) 192{ 193 return (a->rotation == b->rotation && 194 a->blend == b->blend && 195 !strcmp(a->path, b->path)); 196} 197 198/********************************************************************************/ 199/********************************************************************************/ 200/***** *****/ 201/***** S K I N I M A G E S *****/ 202/***** *****/ 203/********************************************************************************/ 204/********************************************************************************/ 205 206enum { 207 SKIN_IMAGE_CLONE = (1 << 0) /* this image is a clone */ 208}; 209 210struct SkinImage { 211 unsigned hash; 212 SkinImage* link; 213 int ref_count; 214 SkinImage* next; 215 SkinImage* prev; 216 SDL_Surface* surface; 217 unsigned flags; 218 unsigned w, h; 219 void* pixels; /* 32-bit ARGB */ 220 SkinImageDesc desc; 221}; 222 223 224 225 226static const SkinImage _no_image[1] = { 227 { 0, NULL, 0, NULL, NULL, NULL, 0, 0, 0, NULL, { "<none>", SKIN_ROTATION_0, 0 } } 228}; 229 230SkinImage* SKIN_IMAGE_NONE = (SkinImage*)&_no_image; 231 232static void 233skin_image_free( SkinImage* image ) 234{ 235 if (image && image != _no_image) 236 { 237 if (image->surface) { 238 SDL_FreeSurface(image->surface); 239 image->surface = NULL; 240 } 241 242 if (image->pixels) { 243 free( image->pixels ); 244 image->pixels = NULL; 245 } 246 247 free(image); 248 } 249} 250 251 252static SkinImage* 253skin_image_alloc( SkinImageDesc* desc, unsigned hash ) 254{ 255 int len = strlen(desc->path); 256 SkinImage* image = calloc(1, sizeof(*image) + len + 1); 257 258 if (image) { 259 image->desc = desc[0]; 260 image->desc.path = (const char*)(image + 1); 261 memcpy( (char*)image->desc.path, desc->path, len ); 262 ((char*)image->desc.path)[len] = 0; 263 264 image->hash = hash; 265 image->next = image->prev = image; 266 image->ref_count = 1; 267 } 268 return image; 269} 270 271 272extern void *loadpng(const char *fn, unsigned *_width, unsigned *_height); 273extern void *readpng(const unsigned char* base, size_t size, unsigned *_width, unsigned *_height); 274 275static int 276skin_image_load( SkinImage* image ) 277{ 278 void* data; 279 unsigned w, h; 280 const char* path = image->desc.path; 281 282 if (path[0] == ':') { 283 size_t size; 284 const unsigned char* base; 285 286 if (path[1] == '/' || path[1] == '\\') 287 path += 1; 288 289 base = android_resource_find( path+1, &size ); 290 if (base == NULL) { 291 fprintf(stderr, "failed to locate built-in image file '%s'\n", path ); 292 return -1; 293 } 294 295 data = readpng(base, size, &w, &h); 296 if (data == NULL) { 297 fprintf(stderr, "failed to load built-in image file '%s'\n", path ); 298 return -1; 299 } 300 } else { 301 data = loadpng(path, &w, &h); 302 if (data == NULL) { 303 fprintf(stderr, "failed to load image file '%s'\n", path ); 304 return -1; 305 } 306 } 307 308 /* the data is loaded into memory as RGBA bytes by libpng. we want to manage 309 * the values as 32-bit ARGB pixels, so swap the bytes accordingly depending 310 * on our CPU endianess 311 */ 312 { 313 unsigned* d = data; 314 unsigned* d_end = d + w*h; 315 316 for ( ; d < d_end; d++ ) { 317 unsigned pix = d[0]; 318#if HOST_WORDS_BIGENDIAN 319 /* R,G,B,A read as RGBA => ARGB */ 320 pix = ((pix >> 8) & 0xffffff) | (pix << 24); 321#else 322 /* R,G,B,A read as ABGR => ARGB */ 323 pix = (pix & 0xff00ff00) | ((pix >> 16) & 0xff) | ((pix & 0xff) << 16); 324#endif 325 d[0] = pix; 326 } 327 } 328 329 image->pixels = data; 330 image->w = w; 331 image->h = h; 332 333 image->surface = sdl_surface_from_argb32( image->pixels, w, h ); 334 if (image->surface == NULL) { 335 fprintf(stderr, "failed to create SDL surface for '%s' image\n", path); 336 return -1; 337 } 338 return 0; 339} 340 341 342/* simple hash table for images */ 343 344#define NUM_BUCKETS 64 345 346typedef struct { 347 SkinImage* buckets[ NUM_BUCKETS ]; 348 SkinImage mru_head; 349 int num_images; 350 unsigned long total_pixels; 351 unsigned long max_pixels; 352 unsigned long total_images; 353} SkinImageCache; 354 355 356static void 357skin_image_cache_init( SkinImageCache* cache ) 358{ 359 memset(cache, 0, sizeof(*cache)); 360#if DEBUG 361 cache->max_pixels = 1; 362#else 363 cache->max_pixels = 4*1024*1024; /* limit image cache to 4 MB */ 364#endif 365 cache->mru_head.next = cache->mru_head.prev = &cache->mru_head; 366} 367 368 369static void 370skin_image_cache_remove( SkinImageCache* cache, 371 SkinImage* image ) 372{ 373 /* remove from hash table */ 374 SkinImage** pnode = cache->buckets + (image->hash & (NUM_BUCKETS-1)); 375 SkinImage* node; 376 377 for (;;) { 378 node = *pnode; 379 assert(node != NULL); 380 if (node == NULL) /* should not happen */ 381 break; 382 if (node == image) { 383 *pnode = node->link; 384 break; 385 } 386 pnode = &node->link; 387 } 388 389 D( "skin_image_cache: remove '%s' (rot=%d), %d pixels\n", 390 node->desc.path, node->desc.rotation, node->w*node->h ); 391 392 /* remove from mru list */ 393 image->prev->next = image->next; 394 image->next->prev = image->prev; 395 396 cache->total_pixels -= image->w*image->h; 397 cache->total_images -= 1; 398} 399 400 401static SkinImage* 402skin_image_cache_raise( SkinImageCache* cache, 403 SkinImage* image ) 404{ 405 if (image != cache->mru_head.next) { 406 SkinImage* prev = image->prev; 407 SkinImage* next = image->next; 408 409 /* remove from mru list */ 410 prev->next = next; 411 next->prev = prev; 412 413 /* add to top */ 414 image->prev = &cache->mru_head; 415 image->next = image->prev->next; 416 image->prev->next = image; 417 image->next->prev = image; 418 } 419 return image; 420} 421 422 423static void 424skin_image_cache_flush( SkinImageCache* cache ) 425{ 426 SkinImage* image = cache->mru_head.prev; 427 int count = 0; 428 429 D("skin_image_cache_flush: starting\n"); 430 while (cache->total_pixels > cache->max_pixels && 431 image != &cache->mru_head) 432 { 433 SkinImage* prev = image->prev; 434 435 if (image->ref_count == 0) { 436 skin_image_cache_remove(cache, image); 437 count += 1; 438 } 439 image = prev; 440 } 441 D("skin_image_cache_flush: finished, %d images flushed\n", count); 442} 443 444 445static SkinImage** 446skin_image_lookup_p( SkinImageCache* cache, 447 SkinImageDesc* desc, 448 unsigned *phash ) 449{ 450 unsigned h = skin_image_desc_hash(desc); 451 unsigned index = h & (NUM_BUCKETS-1); 452 SkinImage** pnode = &cache->buckets[index]; 453 for (;;) { 454 SkinImage* node = *pnode; 455 if (node == NULL) 456 break; 457 if (node->hash == h && skin_image_desc_equal(desc, &node->desc)) 458 break; 459 pnode = &node->link; 460 } 461 *phash = h; 462 return pnode; 463} 464 465 466static SkinImage* 467skin_image_create( SkinImageDesc* desc, unsigned hash ) 468{ 469 SkinImage* node; 470 471 node = skin_image_alloc( desc, hash ); 472 if (node == NULL) 473 return SKIN_IMAGE_NONE; 474 475 if (desc->rotation == SKIN_ROTATION_0 && 476 desc->blend == SKIN_BLEND_FULL) 477 { 478 if (skin_image_load(node) < 0) { 479 skin_image_free(node); 480 return SKIN_IMAGE_NONE; 481 } 482 } 483 else 484 { 485 SkinImageDesc desc0 = desc[0]; 486 SkinImage* parent; 487 488 desc0.rotation = SKIN_ROTATION_0; 489 desc0.blend = SKIN_BLEND_FULL; 490 491 parent = skin_image_find( &desc0 ); 492 if (parent == SKIN_IMAGE_NONE) 493 return SKIN_IMAGE_NONE; 494 495 SDL_LockSurface(parent->surface); 496 497 if (desc->rotation == SKIN_ROTATION_90 || 498 desc->rotation == SKIN_ROTATION_270) 499 { 500 node->w = parent->h; 501 node->h = parent->w; 502 } else { 503 node->w = parent->w; 504 node->h = parent->h; 505 } 506 507 node->pixels = rotate_image( parent->pixels, parent->w, parent->h, 508 desc->rotation ); 509 510 SDL_UnlockSurface(parent->surface); 511 skin_image_unref(&parent); 512 513 if (node->pixels == NULL) { 514 skin_image_free(node); 515 return SKIN_IMAGE_NONE; 516 } 517 518 if (desc->blend != SKIN_BLEND_FULL) 519 blend_image( node->pixels, node->pixels, node->w, node->h, desc->blend ); 520 521 node->surface = sdl_surface_from_argb32( node->pixels, node->w, node->h ); 522 if (node->surface == NULL) { 523 skin_image_free(node); 524 return SKIN_IMAGE_NONE; 525 } 526 } 527 return node; 528} 529 530 531static SkinImageCache _image_cache[1]; 532static int _image_cache_init; 533 534SkinImage* 535skin_image_find( SkinImageDesc* desc ) 536{ 537 SkinImageCache* cache = _image_cache; 538 unsigned hash; 539 SkinImage** pnode = skin_image_lookup_p( cache, desc, &hash ); 540 SkinImage* node = *pnode; 541 542 if (!_image_cache_init) { 543 _image_cache_init = 1; 544 skin_image_cache_init(cache); 545 } 546 547 if (node) { 548 node->ref_count += 1; 549 return skin_image_cache_raise( cache, node ); 550 } 551 node = skin_image_create( desc, hash ); 552 if (node == SKIN_IMAGE_NONE) 553 return node; 554 555 /* add to hash table */ 556 node->link = *pnode; 557 *pnode = node; 558 559 /* add to mru list */ 560 skin_image_cache_raise( cache, node ); 561 562 D( "skin_image_cache: add '%s' (rot=%d), %d pixels\n", 563 node->desc.path, node->desc.rotation, node->w*node->h ); 564 565 cache->total_pixels += node->w*node->h; 566 if (cache->total_pixels > cache->max_pixels) 567 skin_image_cache_flush( cache ); 568 569 return node; 570} 571 572 573SkinImage* 574skin_image_find_simple( const char* path ) 575{ 576 SkinImageDesc desc; 577 578 desc.path = path; 579 desc.rotation = SKIN_ROTATION_0; 580 desc.blend = SKIN_BLEND_FULL; 581 582 return skin_image_find( &desc ); 583} 584 585 586SkinImage* 587skin_image_ref( SkinImage* image ) 588{ 589 if (image && image != _no_image) 590 image->ref_count += 1; 591 592 return image; 593} 594 595 596void 597skin_image_unref( SkinImage** pimage ) 598{ 599 SkinImage* image = *pimage; 600 601 if (image) { 602 if (image != _no_image && --image->ref_count == 0) { 603 if ((image->flags & SKIN_IMAGE_CLONE) != 0) { 604 skin_image_free(image); 605 } 606 } 607 *pimage = NULL; 608 } 609} 610 611 612SkinImage* 613skin_image_rotate( SkinImage* source, SkinRotation rotation ) 614{ 615 SkinImageDesc desc; 616 SkinImage* image; 617 618 if (source == _no_image || source->desc.rotation == rotation) 619 return source; 620 621 desc = source->desc; 622 desc.rotation = rotation; 623 image = skin_image_find( &desc ); 624 skin_image_unref( &source ); 625 return image; 626} 627 628 629SkinImage* 630skin_image_clone( SkinImage* source ) 631{ 632 SkinImage* image; 633 634 if (source == NULL || source == _no_image) 635 return SKIN_IMAGE_NONE; 636 637 image = calloc(1,sizeof(*image)); 638 if (image == NULL) 639 goto Fail; 640 641 image->desc = source->desc; 642 image->hash = source->hash; 643 image->flags = SKIN_IMAGE_CLONE; 644 image->w = source->w; 645 image->h = source->h; 646 image->pixels = rotate_image( source->pixels, source->w, source->h, 647 SKIN_ROTATION_0 ); 648 if (image->pixels == NULL) 649 goto Fail; 650 651 image->surface = sdl_surface_from_argb32( image->pixels, image->w, image->h ); 652 if (image->surface == NULL) 653 goto Fail; 654 655 return image; 656Fail: 657 if (image != NULL) 658 skin_image_free(image); 659 return SKIN_IMAGE_NONE; 660} 661 662SkinImage* 663skin_image_clone_full( SkinImage* source, 664 SkinRotation rotation, 665 int blend ) 666{ 667 SkinImageDesc desc; 668 SkinImage* clone; 669 670 if (source == NULL || source == SKIN_IMAGE_NONE) 671 return SKIN_IMAGE_NONE; 672 673 if (rotation == SKIN_ROTATION_0 && 674 blend == SKIN_BLEND_FULL) 675 { 676 return skin_image_clone(source); 677 } 678 679 desc.path = source->desc.path; 680 desc.rotation = rotation; 681 desc.blend = blend; 682 683 clone = skin_image_create( &desc, 0 ); 684 if (clone != SKIN_IMAGE_NONE) 685 clone->flags |= SKIN_IMAGE_CLONE; 686 687 return clone; 688} 689 690/* apply blending to a source skin image and copy the result to a target clone image */ 691extern void 692skin_image_blend_clone( SkinImage* clone, SkinImage* source, int blend ) 693{ 694 SDL_LockSurface( clone->surface ); 695 blend_image( clone->pixels, source->pixels, source->w, source->h, blend ); 696 SDL_UnlockSurface( clone->surface ); 697 SDL_SetAlpha( clone->surface, SDL_SRCALPHA, 255 ); 698} 699 700int 701skin_image_w( SkinImage* image ) 702{ 703 return image ? image->w : 0; 704} 705 706int 707skin_image_h( SkinImage* image ) 708{ 709 return image ? image->h : 0; 710} 711 712int 713skin_image_org_w( SkinImage* image ) 714{ 715 if (image) { 716 if (image->desc.rotation == SKIN_ROTATION_90 || 717 image->desc.rotation == SKIN_ROTATION_270) 718 return image->h; 719 else 720 return image->w; 721 } 722 return 0; 723} 724 725int 726skin_image_org_h( SkinImage* image ) 727{ 728 if (image) { 729 if (image->desc.rotation == SKIN_ROTATION_90 || 730 image->desc.rotation == SKIN_ROTATION_270) 731 return image->w; 732 else 733 return image->h; 734 } 735 return 0; 736} 737 738SDL_Surface* 739skin_image_surface( SkinImage* image ) 740{ 741 return image ? image->surface : NULL; 742} 743