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
D(const char * fmt,...)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*
sdl_surface_from_argb32(void * base,int w,int h)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*
rotate_image(void * data,unsigned width,unsigned height,SkinRotation rotation)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
blend_image(unsigned * dst_pixels,unsigned * src_pixels,unsigned w,unsigned h,int alpha)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
skin_image_desc_hash(SkinImageDesc * desc)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
skin_image_desc_equal(SkinImageDesc * a,SkinImageDesc * b)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
skin_image_free(SkinImage * image)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*
skin_image_alloc(SkinImageDesc * desc,unsigned hash)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
skin_image_load(SkinImage * image)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
skin_image_cache_init(SkinImageCache * cache)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
skin_image_cache_remove(SkinImageCache * cache,SkinImage * image)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*
skin_image_cache_raise(SkinImageCache * cache,SkinImage * image)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
skin_image_cache_flush(SkinImageCache * cache)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**
skin_image_lookup_p(SkinImageCache * cache,SkinImageDesc * desc,unsigned * phash)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*
skin_image_create(SkinImageDesc * desc,unsigned hash)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*
skin_image_find(SkinImageDesc * desc)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*
skin_image_find_simple(const char * path)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*
skin_image_ref(SkinImage * image)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
skin_image_unref(SkinImage ** pimage)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*
skin_image_rotate(SkinImage * source,SkinRotation rotation)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*
skin_image_clone(SkinImage * source)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*
skin_image_clone_full(SkinImage * source,SkinRotation rotation,int blend)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
skin_image_blend_clone(SkinImage * clone,SkinImage * source,int blend)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
skin_image_w(SkinImage * image)701 skin_image_w( SkinImage* image )
702 {
703 return image ? image->w : 0;
704 }
705
706 int
skin_image_h(SkinImage * image)707 skin_image_h( SkinImage* image )
708 {
709 return image ? image->h : 0;
710 }
711
712 int
skin_image_org_w(SkinImage * image)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
skin_image_org_h(SkinImage * image)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*
skin_image_surface(SkinImage * image)739 skin_image_surface( SkinImage* image )
740 {
741 return image ? image->surface : NULL;
742 }
743