image.c revision 8b23a6c7e1aee255004dd19098d4c2462b61b849
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 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 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