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 20 static 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 39 SDL_Surface* 40 sdl_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 52 static void* 53 rotate_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 146 static void 147 blend_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 172 static unsigned 173 skin_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 189 static int 190 skin_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 206 enum { 207 SKIN_IMAGE_CLONE = (1 << 0) /* this image is a clone */ 208 }; 209 210 struct 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 226 static const SkinImage _no_image[1] = { 227 { 0, NULL, 0, NULL, NULL, NULL, 0, 0, 0, NULL, { "<none>", SKIN_ROTATION_0, 0 } } 228 }; 229 230 SkinImage* SKIN_IMAGE_NONE = (SkinImage*)&_no_image; 231 232 static void 233 skin_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 252 static SkinImage* 253 skin_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 272 extern void *loadpng(const char *fn, unsigned *_width, unsigned *_height); 273 extern void *readpng(const unsigned char* base, size_t size, unsigned *_width, unsigned *_height); 274 275 static int 276 skin_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 346 typedef 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 356 static void 357 skin_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 369 static void 370 skin_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 401 static SkinImage* 402 skin_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 423 static void 424 skin_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 445 static SkinImage** 446 skin_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 466 static SkinImage* 467 skin_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 531 static SkinImageCache _image_cache[1]; 532 static int _image_cache_init; 533 534 SkinImage* 535 skin_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 573 SkinImage* 574 skin_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 586 SkinImage* 587 skin_image_ref( SkinImage* image ) 588 { 589 if (image && image != _no_image) 590 image->ref_count += 1; 591 592 return image; 593 } 594 595 596 void 597 skin_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 612 SkinImage* 613 skin_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 629 SkinImage* 630 skin_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; 656 Fail: 657 if (image != NULL) 658 skin_image_free(image); 659 return SKIN_IMAGE_NONE; 660 } 661 662 SkinImage* 663 skin_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 */ 691 extern void 692 skin_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 700 int 701 skin_image_w( SkinImage* image ) 702 { 703 return image ? image->w : 0; 704 } 705 706 int 707 skin_image_h( SkinImage* image ) 708 { 709 return image ? image->h : 0; 710 } 711 712 int 713 skin_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 725 int 726 skin_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 738 SDL_Surface* 739 skin_image_surface( SkinImage* image ) 740 { 741 return image ? image->surface : NULL; 742 } 743