/*
   (c) Copyright 2001-2009  The world wide DirectFB Open Source Community (directfb.org)
   (c) Copyright 2000-2004  Convergence (integrated media) GmbH

   All rights reserved.

   Written by Denis Oliver Kropp <dok@directfb.org>,
              Andreas Hundt <andi@fischlustig.de>,
              Sven Neumann <neo@directfb.org>,
              Ville Syrjälä <syrjala@sci.fi> and
              Claudio Ciccani <klan@users.sf.net>.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <config.h>

#include <dlfcn.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <png.h>
#include <string.h>
#include <stdarg.h>

#include <directfb.h>

#include <display/idirectfbsurface.h>

#include <media/idirectfbimageprovider.h>

#include <core/coredefs.h>
#include <core/coretypes.h>
#include <core/core.h>

#include <core/layers.h>
#include <core/palette.h>
#include <core/surface.h>

#include <misc/gfx_util.h>
#include <misc/util.h>

#include <gfx/clip.h>
#include <gfx/convert.h>

#include <direct/interface.h>
#include <direct/mem.h>
#include <direct/memcpy.h>
#include <direct/messages.h>
#include <direct/util.h>

#include "config.h"
#define GPD_INTERNAL_BUFFER_SIZE        (1024*1024)
#if (HW_SUPPORT_PNG_DECODER)

#define PHOTO_DECODE_PNG_MAX_WIDTH      (1920)
#define PHOTO_DECODE_PNG_MAX_HEIGHT     (1200)
#define GPD_INTERNAL_BUFFER_SIZE        (1024*1024)


//#include <media/idirectfbdatabuffer.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <MsTypes.h>
#include <MsIRQ.h>
#include <MsOS.h>
#include <drvMMIO.h>
#define U8 MS_U8
#define U16 MS_U16
#define U32 MS_U32
#define S8 MS_S8
#define S16 MS_S16
#define S32 MS_S32

#include <apiGPD.h>

#endif

static void* pHandle;

static DFBResult
Probe( IDirectFBImageProvider_ProbeContext *ctx );

static DFBResult
Construct( IDirectFBImageProvider *thiz,
           ... );

#include <direct/interface_implementation.h>

DIRECT_INTERFACE_IMPLEMENTATION( IDirectFBImageProvider, PNG )

 void AddPNGImageProviderInterface()
 {
 }
enum {
     STAGE_ABORT = -2,
     STAGE_ERROR = -1,
     STAGE_START =  0,
     STAGE_INFO,
     STAGE_IMAGE,
     STAGE_END
};

/*
 * private data struct of IDirectFBImageProvider_PNG
 */
typedef struct {
     int                  ref;      /* reference counter */
     IDirectFBDataBuffer *buffer;

     int                  stage;
     int                  rows;

     png_structp          png_ptr;
     png_infop            info_ptr;

     png_uint_32          width;
     png_uint_32          height;
     int                  bpp;
     int                  color_type;
     png_uint_32          color_key;
     bool                 color_keyed;

     void                *image;
     int                  pitch;
     u32                  palette[256];
     DFBColor             colors[256];

     DIRenderCallback     render_callback;
     void                *render_callback_context;

     CoreDFB             *core;
} IDirectFBImageProvider_PNG_data;

static DirectResult
IDirectFBImageProvider_PNG_AddRef  ( IDirectFBImageProvider *thiz );

static DirectResult
IDirectFBImageProvider_PNG_Release ( IDirectFBImageProvider *thiz );

static DFBResult
IDirectFBImageProvider_PNG_RenderTo( IDirectFBImageProvider *thiz,
                                     IDirectFBSurface       *destination,
                                     const DFBRectangle     *destination_rect );

static DFBResult
IDirectFBImageProvider_PNG_SetRenderCallback( IDirectFBImageProvider *thiz,
                                              DIRenderCallback        callback,
                                              void                   *context );

static DFBResult
IDirectFBImageProvider_PNG_GetSurfaceDescription( IDirectFBImageProvider *thiz,
                                                  DFBSurfaceDescription  *dsc );

#if (HW_SUPPORT_PNG_DECODER)
static DFBResult
IDirectFBImageProvider_PNG_GetSurfaceDescription_HW( IDirectFBImageProvider *thiz,
                                                  DFBSurfaceDescription  *dsc );

#endif

static DFBResult
IDirectFBImageProvider_PNG_GetImageDescription( IDirectFBImageProvider *thiz,
                                                DFBImageDescription    *dsc );

#if (HW_SUPPORT_PNG_DECODER)
static DFBResult
IDirectFBImageProvider_PNG_RenderTo_HW( IDirectFBImageProvider *thiz,
                                     IDirectFBSurface       *destination,
                                     const DFBRectangle     *destination_rect );
#endif
/* Called at the start of the progressive load, once we have image info */
static void
png_info_callback (png_structp png_read_ptr,
                   png_infop   png_info_ptr);

/* Called for each row; note that you will get duplicate row numbers
   for interlaced PNGs */
static void
png_row_callback  (png_structp png_read_ptr,
                   png_bytep   new_row,
                   png_uint_32 row_num,
                   int         pass_num);

/* Called after reading the entire image */
static void
png_end_callback  (png_structp png_read_ptr,
                   png_infop   png_info_ptr);

/* Pipes data into libpng until stage is different from the one specified. */
static DFBResult
push_data_until_stage (IDirectFBImageProvider_PNG_data *data,
                       int                              stage,
                       int                              buffer_size);

/**********************************************************************************************************************/

static DFBResult
Probe( IDirectFBImageProvider_ProbeContext *ctx )
{
     if (png_check_sig( ctx->header, 8 ))
          return DFB_OK;

     return DFB_UNSUPPORTED;
}


/*
Wrap the IDirectFBImageProvider_PNG_xxx to _IDirectFBImageProvider_PNG_xxx for safe call
*/

static DirectResult
_IDirectFBImageProvider_PNG_AddRef( IDirectFBImageProvider *thiz )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_AddRef(thiz);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}

static DirectResult
_IDirectFBImageProvider_PNG_Release( IDirectFBImageProvider *thiz )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_Release(thiz);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}

/**********************************************************************************************************************/

static DFBResult
_IDirectFBImageProvider_PNG_RenderTo( IDirectFBImageProvider *thiz,
                                     IDirectFBSurface       *destination,
                                     const DFBRectangle     *dest_rect )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_RenderTo(thiz,destination,dest_rect);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}

#if (HW_SUPPORT_PNG_DECODER)
static DFBResult
_IDirectFBImageProvider_PNG_RenderTo_HW( IDirectFBImageProvider *thiz,
                                     IDirectFBSurface       *destination,
                                     const DFBRectangle     *dest_rect )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_RenderTo_HW(thiz,destination,dest_rect);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}
#endif

static DFBResult
_IDirectFBImageProvider_PNG_SetRenderCallback( IDirectFBImageProvider *thiz,
                                              DIRenderCallback        callback,
                                              void                   *context )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_SetRenderCallback(thiz,callback,context);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}

static DFBResult
_IDirectFBImageProvider_PNG_GetSurfaceDescription( IDirectFBImageProvider *thiz,
                                                  DFBSurfaceDescription *dsc )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_GetSurfaceDescription(thiz,dsc);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}

#if (HW_SUPPORT_PNG_DECODER)
static DFBResult
_IDirectFBImageProvider_PNG_GetSurfaceDescription_HW( IDirectFBImageProvider *thiz,
                                                  DFBSurfaceDescription *dsc )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_GetSurfaceDescription_HW(thiz,dsc);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}
#endif

static DFBResult
_IDirectFBImageProvider_PNG_GetImageDescription( IDirectFBImageProvider *thiz,
                                                DFBImageDescription    *dsc )
{
    DFBResult ret = DFB_OK;
    DFB_GLOBAL_LOCK(DFB_LOCK_FOR_EXEC);
    ret = IDirectFBImageProvider_PNG_GetImageDescription(thiz,dsc);
    DFB_GLOBAL_UNLOCK(DFB_LOCK_FOR_EXEC);
    return ret;
}

static DFBResult
_IDirectFBImageprovider_PNG_MappingPool(IDirectFBImageProvider *thiz)
{
    bool            bHWDecoder = false;
    DFBResult   ret = DFB_OK;

#if HW_SUPPORT_PNG_DECODER
    if(dfb_config->mst_png_hwdecode)
    {
        void *pvirtual_address = NULL;
        unsigned long miu_offset;
        unsigned long offset;
        unsigned int  len;

        pvirtual_address = (void *)MsOS_MPool_PA2KSEG1( dfb_config->video_phys_hal );
        if(NULL == pvirtual_address)
        {
            if ( dfb_config->video_phys_hal >= dfb_config->mst_miu1_hal_offset )  
                miu_offset = dfb_config->mst_miu1_hal_offset;
            else
                miu_offset = dfb_config->mst_miu0_hal_offset;
            
            //offset = (dfb_config->video_phys_hal-miu_offset) & ~0xFFFFF;
            //len = ((dfb_config->video_length >> 20) + 1) << 20;
            offset = (dfb_config->video_phys_hal-miu_offset); // THE MMAP already 4k alignment
            len = dfb_config->video_length ;

            if( !MsOS_MPool_Mapping( (miu_offset== dfb_config->mst_miu0_hal_offset) ? 0 : 1, offset, len, 1 ) )
            {
                printf("Waring!HW PNG decode map buf error! \n");
                ret = DFB_FAILURE;
            }
        }
    }

#endif
    return  ret;
}

static DFBResult
Construct( IDirectFBImageProvider *thiz,
           ... )
{
     DFBResult ret = DFB_FAILURE;

     IDirectFBDataBuffer *buffer;
     CoreDFB             *core;
     va_list              tag;

     CoreDFBShared    *shared;
     DIRECT_ALLOCATE_INTERFACE_DATA(thiz, IDirectFBImageProvider_PNG)

     va_start( tag, thiz );
     buffer = va_arg( tag, IDirectFBDataBuffer * );
     core = va_arg( tag, CoreDFB * );
     shared = core->shared;
     va_end( tag );

     data->ref    = 1;
     data->buffer = buffer;
     data->core   = core;

     /* Increase the data buffer reference counter. */
     buffer->AddRef( buffer );

     /* Create the PNG read handle. */
     data->png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
                                             NULL, NULL, NULL );
     if (!data->png_ptr)
          goto error;

     if (setjmp( data->png_ptr->jmpbuf )) {
          D_ERROR( "ImageProvider/PNG: Error reading header!\n" );
          goto error;
     }

     /* Create the PNG info handle. */
     data->info_ptr = png_create_info_struct( data->png_ptr );
     if (!data->info_ptr)
          goto error;

     /* Setup progressive image loading. */
     png_set_progressive_read_fn( data->png_ptr, data,
                                  png_info_callback,
                                  png_row_callback,
                                  png_end_callback );


     /* Read until info callback is called. */
     ret = push_data_until_stage( data, STAGE_INFO, 64 );
     if (ret)
          goto error;

#if 0
	{
		printf("====================================data===============================================\n");
        printf("width, height,   bpp, color_type, color_key, color_keyed, pitch, palette[0], colors[0]\n");
        printf("%04d,  %04d,    %04d,       %04d,       %04d,        %04d,  %04d,       %04d,      %04d\n",
            data->width, data->height, data->bpp, data->color_type, data->color_key, data->color_keyed, data->pitch, data->palette[0], data->palette[0]);
		printf("=======================================================================================\n");
	}
#endif

    thiz->AddRef                = _IDirectFBImageProvider_PNG_AddRef;
    thiz->Release               = _IDirectFBImageProvider_PNG_Release;
    thiz->RenderTo              = _IDirectFBImageProvider_PNG_RenderTo;
    thiz->SetRenderCallback     = _IDirectFBImageProvider_PNG_SetRenderCallback;
    thiz->GetImageDescription   = _IDirectFBImageProvider_PNG_GetImageDescription;
    thiz->GetSurfaceDescription = _IDirectFBImageProvider_PNG_GetSurfaceDescription;

#if (HW_SUPPORT_PNG_DECODER)
    //If chip is T12, it does not have libapiGPD.so, go to SW
    static bool initdone = false;
    pHandle = 0;
    
    if ((!initdone) && dfb_config->mst_png_hwdecode)
    {
        initdone = true;
        pHandle = dlopen ("libapiGPD.so", RTLD_LAZY);    
        if(pHandle)
        {
            if (dfb_config->mst_register_gpd)            
            {
                //Register the GPD module for mips utopia 2.0 interface case.  
		        void*  (*GPDRegisterToUtopia)();
		        GPDRegisterToUtopia = (void*(*)())dlsym(pHandle, "GPDRegisterToUtopia");
		        
		        if((dlerror() != 0) || (!GPDRegisterToUtopia))
		            printf("Init dlsym FAIL (GPDRegisterToUtopia)!!!!\n");
		        else
		        {
		            GPDRegisterToUtopia(NULL);                
		            D_INFO("HW PNG GPDRegisterToUtopia\n");
		        }
            }
        }
        else
        {
            dfb_config->mst_png_hwdecode = false;        
        }  
    }
    
    if (dfb_config->mst_png_hwdecode && pHandle &&
        data->width * data->height <= PHOTO_DECODE_PNG_MAX_WIDTH*PHOTO_DECODE_PNG_MAX_HEIGHT) {
        thiz->AddRef                = _IDirectFBImageProvider_PNG_AddRef;
        thiz->Release               = _IDirectFBImageProvider_PNG_Release;
        thiz->RenderTo              = _IDirectFBImageProvider_PNG_RenderTo_HW;
        thiz->SetRenderCallback     = _IDirectFBImageProvider_PNG_SetRenderCallback;
        thiz->GetImageDescription   = _IDirectFBImageProvider_PNG_GetImageDescription;
        thiz->GetSurfaceDescription = _IDirectFBImageProvider_PNG_GetSurfaceDescription_HW;

        //MsOS_MPool_Init();
        MsOS_MPool_Get(0,0,0, true);

        _IDirectFBImageprovider_PNG_MappingPool(thiz);

        //MDrv_MMIO_Init();
        //MsOS_Init();

	}


#endif

    return DFB_OK;

error:
    if (data->png_ptr)
        png_destroy_read_struct( &data->png_ptr, &data->info_ptr, NULL );
    buffer->Release( buffer );
    if (data->image)
        D_FREE( data->image );
    DIRECT_DEALLOCATE_INTERFACE(thiz);

    return ret;
}

/**********************************************************************************************************************/

static void
IDirectFBImageProvider_PNG_Destruct( IDirectFBImageProvider *thiz )
{
     IDirectFBImageProvider_PNG_data *data =
                              (IDirectFBImageProvider_PNG_data*)thiz->priv;

     png_destroy_read_struct( &data->png_ptr, &data->info_ptr, NULL );

     /* Decrease the data buffer reference counter. */
     data->buffer->Release( data->buffer );

     /* Deallocate image data. */
     if (data->image)
          D_FREE( data->image );

     DIRECT_DEALLOCATE_INTERFACE( thiz );
}

static DirectResult
IDirectFBImageProvider_PNG_AddRef( IDirectFBImageProvider *thiz )
{
     DIRECT_INTERFACE_GET_DATA (IDirectFBImageProvider_PNG)

     data->ref++;

     return DFB_OK;
}

static DirectResult
IDirectFBImageProvider_PNG_Release( IDirectFBImageProvider *thiz )
{
     DIRECT_INTERFACE_GET_DATA (IDirectFBImageProvider_PNG)

     if (--data->ref == 0) {
          IDirectFBImageProvider_PNG_Destruct( thiz );
     }

     return DFB_OK;
}

/**********************************************************************************************************************/
static unsigned int align(unsigned int p, unsigned int alignment)
{
    return ((p + (alignment - 1)) & ~(alignment - 1));
}
static u32 _mstarGFXAddr(u32 cpuPhysicalAddr, u32 mst_miu1_cpu_offset, u32 mst_miu1_hal_offset)
{
   if(cpuPhysicalAddr >= mst_miu1_cpu_offset)
      return (cpuPhysicalAddr-mst_miu1_cpu_offset+mst_miu1_hal_offset);
   else
      return (cpuPhysicalAddr-dfb_config->mst_miu0_cpu_offset+dfb_config->mst_miu0_hal_offset);

   return cpuPhysicalAddr;
}

static  u32 _mstarCPUPhyAddr(u32 halPhysicalAddr, u32 mst_miu1_cpu_offset, u32 mst_miu1_hal_offset)
{
    if(halPhysicalAddr >= mst_miu1_hal_offset)
        return mst_miu1_cpu_offset + halPhysicalAddr - mst_miu1_hal_offset;
    else
        return dfb_config->mst_miu0_cpu_offset + halPhysicalAddr - dfb_config->mst_miu0_hal_offset;

    return halPhysicalAddr;
}



static DFBResult
IDirectFBImageProvider_PNG_RenderTo( IDirectFBImageProvider *thiz,
                                     IDirectFBSurface       *destination,
                                     const DFBRectangle     *dest_rect )
{
     DFBResult              ret = DFB_OK;
     IDirectFBSurface_data *dst_data;
     CoreSurface           *dst_surface, *dst_surface_org;
	 IDirectFBDataBuffer 	*buffer ;
     CoreSurfaceBufferLock  lock;

     DFBRegion              clip;
     DFBRectangle           srect,rect;
     png_infop              info;
     int                    x, y;
     DFBRectangle           clipped;

     DFBSurfaceCapabilities caps;

     CoreSurface *surface_tmp = NULL;
     CoreSurfaceConfig config_tmp;
     int width_tmp, height_tmp; 
     
     DIRECT_INTERFACE_GET_DATA (IDirectFBImageProvider_PNG)

     CoreDFB            *core;
     CoreDFBShared  *shared;
     
     core = data->core;
     shared = core->shared;
     fusion_skirmish_prevail(&shared->lock_sw_png_Decoder);
     
     info = data->info_ptr;

     dst_data = (IDirectFBSurface_data*) destination->priv;
     if (!dst_data)
     {
          fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
          return DFB_DEAD;
     }
     
     dst_surface_org = dst_data->surface;
     if (!dst_surface_org)
     {
          fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
          return DFB_DESTROYED;
     }

     destination->GetCapabilities( destination, &caps );
     if (caps & DSCAPS_PREMULTIPLIED)
     {
          //create tmp Surface
          destination->GetSize(destination, &width_tmp, &height_tmp);

          config_tmp.flags    = CSCONF_SIZE | CSCONF_FORMAT |  CSCONF_CAPS;
          config_tmp.size.w   = width_tmp;
          config_tmp.size.h   = height_tmp;
          config_tmp.format   = DSPF_ARGB;
          config_tmp.caps     = DSCAPS_VIDEOONLY;
          ret = dfb_surface_create(data->core, &config_tmp, CSTF_EXTERNAL, 0, NULL, &surface_tmp);
          if (ret) {
              printf("[DFB]%s:%04d, :CreateSurface Failed!!\n", __FUNCTION__, __LINE__);
              fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
              return ret;
          }

          dst_surface = surface_tmp;  //set
     }
     else
     {
          dst_surface = dst_surface_org;  //set
     }


     dfb_region_from_rectangle( &clip, &dst_data->area.current );

     if (dest_rect) {
          if (dest_rect->w < 1 || dest_rect->h < 1)
          {
               if (surface_tmp)  dfb_surface_unref( surface_tmp );
               fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
               return DFB_INVARG;
          }
          rect = *dest_rect;
          rect.x += dst_data->area.wanted.x;
          rect.y += dst_data->area.wanted.y;
     }
     else {
          rect = dst_data->area.wanted;
     }

     if (setjmp( data->png_ptr->jmpbuf )) {
          D_ERROR( "ImageProvider/PNG: Error during decoding!\n" );

          if (data->stage < STAGE_IMAGE)
          {
               if (surface_tmp)  dfb_surface_unref( surface_tmp );
               fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
               return DFB_FAILURE;
          }

          data->stage = STAGE_ERROR;
     }

     /* Read until image is completely decoded. */
     if (data->stage != STAGE_ERROR) {
          ret = push_data_until_stage( data, STAGE_END, 16384 );
          if (ret)
          {
               if (surface_tmp)  dfb_surface_unref( surface_tmp );
               fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
               return ret;
          }
     }

     clipped = rect;

     if (!dfb_rectangle_intersect_by_region( &clipped, &clip ))
     {
          if (surface_tmp)  dfb_surface_unref( surface_tmp );
          fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
          return DFB_INVAREA;
     }
     
     /* actual rendering */
     if (rect.w == data->width && rect.h == data->height &&
         (data->color_type == PNG_COLOR_TYPE_RGB || data->color_type == PNG_COLOR_TYPE_RGBA) &&
         (dst_surface->config.format == DSPF_RGB32 || dst_surface->config.format == DSPF_ARGB))
     {
          ret = dfb_surface_write_buffer( dst_surface, CSBR_BACK,
                                          data->image +
                                             (clipped.x - rect.x) * 4 +
                                             (clipped.y - rect.y) * data->width * 4,
                                          data->width * 4, &clipped );
     }
     else {
          CoreSurfaceBufferLock lock;

          ret = dfb_surface_lock_buffer( dst_surface, CSBR_BACK, CSAID_CPU, CSAF_WRITE, &lock );
          if (ret)
          {
               if (surface_tmp)  dfb_surface_unref( surface_tmp );
               fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
               return ret;
          }
          
          switch (data->color_type) {
               case PNG_COLOR_TYPE_PALETTE:
                    if (dst_surface->config.format == DSPF_LUT8 && data->info_ptr->bit_depth == 8) {
                         /*
                          * Special indexed PNG to LUT8 loading.
                          */

                         /* FIXME: Limitation for LUT8 is to load complete surface only. */
                         dfb_clip_rectangle( &clip, &rect );
                         if (rect.x == 0 && rect.y == 0 &&
                             rect.w == dst_surface->config.size.w  &&
                             rect.h == dst_surface->config.size.h &&
                             rect.w == data->width         &&
                             rect.h == data->height)
                         {
                              for (y=0; y<data->height; y++)
                                   direct_memcpy( lock.addr + lock.pitch * y,
                                                  data->image + data->pitch * y,
                                                  data->width );

                              break;
                         }
                    }
                    /* fall through */

               case PNG_COLOR_TYPE_GRAY: {
                    /*
                     * Convert to ARGB and use generic loading code.
                     */

                    // FIXME: allocates four additional bytes because the scaling functions
                    //        in src/misc/gfx_util.c have an off-by-one bug which causes
                    //        segfaults on darwin/osx (not on linux)
                    int size = data->width * data->height * 4 + 4;

                    /* allocate image data */
                    void *image_argb = D_MALLOC( size );

                    if (!image_argb) {
                         D_ERROR( "DirectFB/ImageProvider_PNG: Could not "
                                  "allocate %d bytes of system memory!\n", size );
                         ret = DFB_NOSYSTEMMEMORY;
                    }
                    else {
                         if (data->color_type == PNG_COLOR_TYPE_GRAY) {
                              int num = 1 << data->info_ptr->bit_depth;

                              for (x=0; x<num; x++) {
                                   int value = x * 255 / (num - 1);

                                   data->palette[x] = 0xff000000 | (value << 16) | (value << 8) | value;
                              }
                         }

                         switch (data->info_ptr->bit_depth) {
                              case 8:
                                   for (y=0; y<data->height; y++) {
                                        u8  *S = data->image + data->pitch * y;
                                        u32 *D = image_argb  + data->width * y * 4;

                                        for (x=0; x<data->width; x++)
                                             D[x] = data->palette[ S[x] ];
                                   }
                                   break;

                              case 4:
                                   for (y=0; y<data->height; y++) {
                                        u8  *S = data->image + data->pitch * y;
                                        u32 *D = image_argb  + data->width * y * 4;

                                        for (x=0; x<data->width; x++) {
                                             if (x & 1)
                                                  D[x] = data->palette[ S[x>>1] & 0xf ];
                                             else
                                                  D[x] = data->palette[ S[x>>1] >> 4 ];
                                        }
                                   }
                                   break;

                              case 2:
                                   for (y=0; y<data->height; y++) {
                                        int  n = 6;
                                        u8  *S = data->image + data->pitch * y;
                                        u32 *D = image_argb  + data->width * y * 4;

                                        for (x=0; x<data->width; x++) {
                                             D[x] = data->palette[ (S[x>>2] >> n) & 3 ];

                                             n = (n ? n - 2 : 6);
                                        }
                                   }
                                   break;

                              case 1:
                                   for (y=0; y<data->height; y++) {
                                        int  n = 7;
                                        u8  *S = data->image + data->pitch * y;
                                        u32 *D = image_argb  + data->width * y * 4;

                                        for (x=0; x<data->width; x++) {
                                             D[x] = data->palette[ (S[x>>3] >> n) & 1 ];

                                             n = (n ? n - 1 : 7);
                                        }
                                   }
                                   break;

                              default:
                                   D_ERROR( "ImageProvider/PNG: Unsupported indexed bit depth %d!\n",
                                            data->info_ptr->bit_depth );
                         }

                         dfb_scale_linear_32( image_argb, data->width, data->height,
                                              lock.addr, lock.pitch, &rect, dst_surface, &clip );

                         D_FREE( image_argb );
                    }
                    break;
               }
               default:
                    /*
                     * Generic loading code.
                     */
                    dfb_scale_linear_32( data->image, data->width, data->height,
                                         lock.addr, lock.pitch, &rect, dst_surface, &clip );
                    break;
          }

          dfb_surface_unlock_buffer( dst_surface, &lock );
     }

     if (data->stage != STAGE_END)
          ret = DFB_INCOMPLETE;

     //stretch blt to destsurface   
     if (caps & DSCAPS_PREMULTIPLIED)
     {
          DFBSurfaceBlittingFlags  old_blittingflags = dst_data->state.blittingflags;
          destination->SetBlittingFlags( destination, DSBLIT_SRC_PREMULTIPLY );        
       
          dfb_state_set_source( &dst_data->state, dst_surface );
          //set srcrect
          srect.x = 0;
          srect.y = 0;
          srect.w = data->width;
          srect.h = data->height;

          dfb_gfxcard_stretchblit( &srect, &rect, &dst_data->state );

          dfb_state_set_source( &dst_data->state, NULL );
          destination->SetBlittingFlags( destination, old_blittingflags );
     }    

     if (surface_tmp)  dfb_surface_unref( surface_tmp );
     fusion_skirmish_dismiss(&shared->lock_sw_png_Decoder);
     
     return ret;
     
}

static DFBResult
IDirectFBImageProvider_PNG_SetRenderCallback( IDirectFBImageProvider *thiz,
                                              DIRenderCallback        callback,
                                              void                   *context )
{
     DIRECT_INTERFACE_GET_DATA (IDirectFBImageProvider_PNG)

     data->render_callback         = callback;
     data->render_callback_context = context;

     return DFB_OK;
}

static DFBResult
IDirectFBImageProvider_PNG_GetSurfaceDescription( IDirectFBImageProvider *thiz,
                                                  DFBSurfaceDescription *dsc )
{
     DFBSurfacePixelFormat primary_format = dfb_primary_layer_pixelformat();

     DIRECT_INTERFACE_GET_DATA (IDirectFBImageProvider_PNG)

     dsc->flags  = DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_PIXELFORMAT;
     dsc->width  = data->width;
     dsc->height = data->height;
     dsc->origwidth  = data->width;
     dsc->origheight = data->height;

     if (data->color_type & PNG_COLOR_MASK_ALPHA)
          dsc->pixelformat = DFB_PIXELFORMAT_HAS_ALPHA(primary_format) ? primary_format : DSPF_ARGB;
     else
          dsc->pixelformat = primary_format;

     if (data->color_type == PNG_COLOR_TYPE_PALETTE) {
          dsc->flags  |= DSDESC_PALETTE;

          dsc->palette.entries = data->colors;  /* FIXME */
          dsc->palette.size    = 256;
     }

     return DFB_OK;
}

#if (HW_SUPPORT_PNG_DECODER)
static DFBResult
IDirectFBImageProvider_PNG_GetSurfaceDescription_HW( IDirectFBImageProvider *thiz,
                                                  DFBSurfaceDescription *dsc )
{
     DFBSurfacePixelFormat primary_format = dfb_primary_layer_pixelformat();

     DIRECT_INTERFACE_GET_DATA (IDirectFBImageProvider_PNG)

     dsc->flags  = DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_PIXELFORMAT;
     dsc->width  = data->width;
     dsc->height = data->height;

     dsc->pixelformat = DSPF_ARGB ;

     return DFB_OK;
}
#endif

static DFBResult
IDirectFBImageProvider_PNG_GetImageDescription( IDirectFBImageProvider *thiz,
                                                DFBImageDescription    *dsc )
{
     DIRECT_INTERFACE_GET_DATA(IDirectFBImageProvider_PNG)

     if (!dsc)
          return DFB_INVARG;

     dsc->caps = DICAPS_NONE;

     if (data->color_type & PNG_COLOR_MASK_ALPHA)
          dsc->caps |= DICAPS_ALPHACHANNEL;

     if (data->color_keyed) {
          dsc->caps |= DICAPS_COLORKEY;

          dsc->colorkey_r = (data->color_key & 0xff0000) >> 16;
          dsc->colorkey_g = (data->color_key & 0x00ff00) >>  8;
          dsc->colorkey_b = (data->color_key & 0x0000ff);
     }

     return DFB_OK;
}

#if (HW_SUPPORT_PNG_DECODER)
static inline long myclock()
{
  struct timeval tv;

  gettimeofday (&tv, NULL);
  return (tv.tv_sec * 1000 + tv.tv_usec / 1000);
}

static DFBResult
IDirectFBImageProvider_PNG_RenderTo_HW( IDirectFBImageProvider *thiz,
                                     IDirectFBSurface       *destination,
                                     const DFBRectangle     *dest_rect )
{
    DFBResult              ret = DFB_OK;
    IDirectFBSurface_data *dst_data;
    CoreSurface           *dst_surface;
    IDirectFBDataBuffer 	*buffer ;
    CoreSurfaceBufferLock  lock;

    DFBRegion              clip;
    DFBRectangle           srect,drect;
    png_infop              info;
    int                    x, y;
    DFBRectangle           clipped;
    DFBSurfaceBlittingFlags  old_blittingflags = DSBLIT_NOFX;
    DFBSurfaceCapabilities caps;

    DIRECT_INTERFACE_GET_DATA (IDirectFBImageProvider_PNG)
    CoreDFB *core;  
    CoreDFBShared *shared;
    core = data->core;
    shared = core->shared;
    
    fusion_skirmish_prevail(&shared->lock_hw_png_gif_Decoder);
    info = data->info_ptr;

    dst_data = (IDirectFBSurface_data*) destination->priv;
    if (!dst_data)
    {
        fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
        return DFB_DEAD;
    }
    dst_surface = dst_data->surface;
    if (!dst_surface)
    {
        fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
        return DFB_DESTROYED;
    }
    dfb_region_from_rectangle( &clip, &dst_data->area.current );

    if (dest_rect) {
        if (dest_rect->w < 1 || dest_rect->h < 1)
        {
            fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
            return DFB_INVARG;
        }
        drect = *dest_rect;
        drect.x += dst_data->area.wanted.x;
        drect.y += dst_data->area.wanted.y;

        if (!dfb_rectangle_region_intersects( &drect, &clip ))
        {
            fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
            return DFB_OK;
        }
    }
    else {
        drect = dst_data->area.wanted;
    }

    if (setjmp( data->png_ptr->jmpbuf )) {
        D_ERROR( "ImageProvider/PNG: Error during decoding!\n" );

    if (data->stage < STAGE_IMAGE)
    {
        fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
        return DFB_FAILURE;
    }

        data->stage = STAGE_ERROR;
    }

    clipped = drect;

    if (!dfb_rectangle_intersect_by_region( &clipped, &clip ))
    {
        fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
        return DFB_INVAREA;
    }
    //if (!data->image) {
    if (1) {
        #define HW_ALIGN_OVERHEAD_      32
        u32 gpd_internal_phys, gpd_bitstream_phys, gpd_output_phys;
        u32 u32length = 0;
        void *gpd_bitstream_virt = NULL;
        int aligned_width = 0;

        CoreSurface *gpd_surface = NULL;
        CoreSurfaceConfig gpd_config;
        CoreSurfaceBufferLock  gpd_lock;

        CoreSurface *image = NULL;
        CoreSurfaceConfig image_config;
        CoreSurfaceBufferLock  lock;

        aligned_width = align(data->width, 8);

        // set image decoding stage
        data->stage = STAGE_IMAGE;

        // get bitstream size
        buffer = data->buffer;
        buffer->SeekTo(buffer, 0);
        buffer->GetLength(buffer,&u32length);

        // allocate coresurface for the decoder
        int total_size =    GPD_INTERNAL_BUFFER_SIZE +              //Internal
                            align(u32length, 4) +                   //bitstream
                            (aligned_width * data->height)*4 +      //output
                            HW_ALIGN_OVERHEAD_;
        gpd_config.flags    = CSCONF_SIZE | CSCONF_FORMAT |  CSCONF_CAPS;
        gpd_config.size.w   = total_size/4;
        gpd_config.size.h   = 1;
        gpd_config.format   = DSPF_ARGB;
        gpd_config.caps     = DSCAPS_VIDEOONLY;
        ret = dfb_surface_create(data->core, &gpd_config, CSTF_EXTERNAL, 0, NULL, &gpd_surface);
        if (ret) {
            printf("[DFB]%s:%04d, :CreateSurface Failed!!\n", __FUNCTION__, __LINE__);
            fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
            return ret;
        }
        ret = dfb_surface_lock_buffer(gpd_surface, CSBR_BACK, CSAID_CPU, CSAF_WRITE, &gpd_lock);
        if (ret) {
            dfb_surface_unref(gpd_surface);
            printf("[DFB]%s:%04d, :Lock Failed!!\n", __FUNCTION__, __LINE__);
            fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
            return ret;
        }
        gpd_internal_phys   = _mstarGFXAddr(gpd_lock.phys, dfb_config->mst_miu1_cpu_offset, dfb_config->mst_miu1_hal_offset);
        gpd_bitstream_phys  = gpd_internal_phys + GPD_INTERNAL_BUFFER_SIZE;
        gpd_bitstream_virt  = (void *)((u32)gpd_lock.addr + GPD_INTERNAL_BUFFER_SIZE);
        gpd_output_phys     = gpd_bitstream_phys + align(u32length, 4);
        gpd_output_phys     = align(gpd_output_phys, 32);

#if 0  //revert CL#958613, it makes napoli, k2 coredump
        D_INFO(" [DFB] gpd_bitstream_phys=%x \n", gpd_bitstream_phys);
        D_INFO(" [DFB] gpd_internal_phys=%x \n", gpd_internal_phys);
        D_INFO(" [DFB] gpd_output_phys=%x \n", gpd_output_phys);
#endif

        // get bitstream
        buffer->GetData(buffer, u32length, gpd_bitstream_virt, NULL);

        //set GPD use noncache buffer
		void*  (*GPDSetControl)();
		GPDSetControl = (void*(*)())dlsym(pHandle, "MApi_GPD_SetControl");
		if((dlerror() != 0) || (!GPDSetControl))
		    printf("Init dlsym FAIL!!!!\n");
		    
		GPDSetControl(E_GPD_USER_CMD_SET_CACHEABLE, FALSE);

#if 0  //revert CL#958613, it makes napoli, k2 coredump
        gpd_access_region stAccessCfg;
        stAccessCfg.u32PA_StartAddr = (MS_U32)gpd_output_phys;
        stAccessCfg.u32PA_EndAddr = (MS_U32)(gpd_output_phys + (aligned_width * data->height)*4);
        
        GPDSetControl(E_GPD_USER_CMD_SET_ACCESS_REGION, (MS_U32)&stAccessCfg);
#endif

        // call HW routine to decode
		void*  (*GPDInit)();
	    GPDInit = (void*(*)())dlsym(pHandle, "MApi_GPD_Init");
		if((dlerror() != 0) || (!GPDInit))
		    printf("Init dlsym FAIL!!!!\n");
		GPDInit(gpd_internal_phys);
		//MApi_GPD_Init(gpd_internal_phys);

        gpd_pic_info pic_info;

#if 0  //revert CL#958613, it makes napoli, k2 coredump
		D_INFO("[DFB] HW PNG MApi_GPD_InputSource !!\n");
#endif
		//printf("&pic_info=%p gpd_bitstream_phys=%p \n",&pic_info,gpd_bitstream_phys);
		s32  (*GPDInputSource)();
		GPDInputSource = (s32 (*)())dlsym(pHandle, "MApi_GPD_InputSource");
		if((dlerror() != 0) || (!GPDInputSource))
		    printf("InputSource dlsym FAIL!!!!\n");
		GPDInputSource(&pic_info, gpd_bitstream_phys, align(u32length, 4));
        //MApi_GPD_InputSource(&pic_info, gpd_bitstream_phys, align(u32length, 4));

		s32  (*GPDOutputDecode)();
		GPDOutputDecode = (s32 (*)())dlsym(pHandle, "MApi_GPD_OutputDecode");
		if((dlerror() != 0) || (!GPDOutputDecode))
		    printf("OutputDecode dlsym FAIL!!!!\n");
		s32 res = GPDOutputDecode(gpd_output_phys, ARGB8888, aligned_width * data->height * 4);
        //s32 res = MApi_GPD_OutputDecode(gpd_output_phys, ARGB8888, aligned_width * data->height * 4);

        if (res != 0) {
            //dfb_surface_unref(gpd_surface);
            printf("hw decode error: %d, %d, res=%d\n", pic_info.u32Width,pic_info.u32Height,res);
            //return DFB_FAILURE;
        }
		data->pitch = align(pic_info.u32Width*4, 8);  //utopia driver define 8 bytes alignments
        data->color_type = PNG_COLOR_TYPE_RGBA;

        //construct coresurface for the decoder output buffer
        image_config.flags    =  CSCONF_SIZE | CSCONF_FORMAT |  CSCONF_PREALLOCATED_IN_VIDEO;
        image_config.size.w   =  pic_info.u32Width;
        image_config.size.h   =  pic_info.u32Height;
        image_config.format   =  DSPF_ARGB;
        image_config.preallocated[0].addr  = (void *)_mstarCPUPhyAddr(gpd_output_phys, dfb_config->mst_miu1_cpu_offset, dfb_config->mst_miu1_hal_offset);  //bug fix
        image_config.preallocated[0].pitch = data->pitch;
 	 
        ret = dfb_surface_create(data->core, &image_config, CSTF_PREALLOCATED_IN_VIDEO, 0, NULL, &image);
        if (ret)
        {
            dfb_surface_unref(gpd_surface);
            printf("[DFB]%s:%04d, :CreateSurface Failed!!\n", __FUNCTION__, __LINE__);
            fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
            return ret;
        }
        old_blittingflags = dst_data->state.blittingflags;
        destination->GetCapabilities( destination, &caps );
        if (caps & DSCAPS_PREMULTIPLIED)
             destination->SetBlittingFlags( destination, DSBLIT_SRC_PREMULTIPLY );
        else
             destination->SetBlittingFlags( destination, DSBLIT_NOFX );
       
        //stretch blt to destsurface
        dst_data = (IDirectFBSurface_data*)destination->priv;
        dfb_state_set_source( &dst_data->state, image );

        //set srcrect
        srect.x = 0;
        srect.y = 0;
        srect.w = data->width;
        srect.h = data->height;


        //set dstrect
        if (dest_rect) {
            if (dest_rect->w < 1  ||  dest_rect->h < 1)
            {
                fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
                return DFB_INVARG;
            }
            drect = *dest_rect;

            drect.x += dst_data->area.wanted.x;
            drect.y += dst_data->area.wanted.y;
        }
        else
            drect = dst_data->area.wanted;

        //strech blt
        dfb_gfxcard_stretchblit( &srect, &drect, &dst_data->state );
        dfb_state_set_source( &dst_data->state, NULL );
        dfb_surface_unref( image );

        dfb_surface_unlock_buffer(gpd_surface, &gpd_lock);
        dfb_surface_unref(gpd_surface);
        destination->SetBlittingFlags( destination, old_blittingflags );
        data->stage = STAGE_END;

        if (data->render_callback) {
            DFBRectangle r = { 0, 0, data->width, data->height };
            data->render_callback( &r, data->render_callback_context );
        }
	}
    else {
        ret = dfb_surface_lock_buffer( dst_surface, CSBR_BACK, CSAID_CPU, CSAF_WRITE, &lock );
        if (ret)
        {
            fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
            return ret;
        }
        dfb_scale_linear_32( data->image, data->width, data->height,
        lock.addr, lock.pitch, &drect, dst_surface, &clip );
        if (data->render_callback) {

            DFBRectangle r = { 0, 0, data->width, data->height };
            data->render_callback( &r, data->render_callback_context );
        }
        dfb_surface_unlock_buffer( dst_surface, &lock );
    }

    if (data->stage != STAGE_END)
        ret = DFB_INCOMPLETE;

    fusion_skirmish_dismiss(&shared->lock_hw_png_gif_Decoder);
    return ret;

}
#endif

/**********************************************************************************************************************/

#define MAXCOLORMAPSIZE 256

static int SortColors (const void *a, const void *b)
{
     return (*((const u8 *) a) - *((const u8 *) b));
}

/*  looks for a color that is not in the colormap and ideally not
    even close to the colors used in the colormap  */
static u32 FindColorKey( int n_colors, u8 *cmap )
{
     u32   color = 0xFF000000;
     u8    csort[n_colors];
     int   i, j, index, d;

     if (n_colors < 1)
          return color;

     for (i = 0; i < 3; i++) {
          direct_memcpy( csort, cmap + (n_colors * i), n_colors );
          qsort( csort, n_colors, 1, SortColors );

          for (j = 1, index = 0, d = 0; j < n_colors; j++) {
               if (csort[j] - csort[j-1] > d) {
                    d = csort[j] - csort[j-1];
                    index = j;
               }
          }
          if ((csort[0] - 0x0) > d) {
               d = csort[0] - 0x0;
               index = n_colors;
          }
          if (0xFF - (csort[n_colors - 1]) > d) {
               index = n_colors + 1;
          }

          if (index < n_colors)
               csort[0] = csort[index] - (d/2);
          else if (index == n_colors)
               csort[0] = 0x0;
          else
               csort[0] = 0xFF;

          color |= (csort[0] << (8 * (2 - i)));
     }

     return color;
}

/* Called at the start of the progressive load, once we have image info */
static void
png_info_callback( png_structp png_read_ptr,
                   png_infop   png_info_ptr )
{
     int                              i;
     IDirectFBImageProvider_PNG_data *data;

     data = png_get_progressive_ptr( png_read_ptr );

     /* error stage? */
     if (data->stage < 0)
          return;

     /* set info stage */
     data->stage = STAGE_INFO;

     png_get_IHDR( data->png_ptr, data->info_ptr,
                   &data->width, &data->height, &data->bpp, &data->color_type,
                   NULL, NULL, NULL );

     if (png_get_valid( data->png_ptr, data->info_ptr, PNG_INFO_tRNS )) {
          data->color_keyed = true;

          /* generate color key based on palette... */
          if (data->color_type == PNG_COLOR_TYPE_PALETTE) {
               u32        key;
               png_colorp palette    = data->info_ptr->palette;
               png_bytep  trans      = data->info_ptr->trans;
               int        num_colors = MIN( MAXCOLORMAPSIZE,
                                            data->info_ptr->num_palette );
               u8         cmap[3][num_colors];

               for (i=0; i<num_colors; i++) {
                    cmap[0][i] = palette[i].red;
                    cmap[1][i] = palette[i].green;
                    cmap[2][i] = palette[i].blue;
               }

               key = FindColorKey( num_colors, &cmap[0][0] );

               for (i=0; i<data->info_ptr->num_trans; i++) {
                    if (!trans[i]) {
                         palette[i].red   = (key & 0xff0000) >> 16;
                         palette[i].green = (key & 0x00ff00) >>  8;
                         palette[i].blue  = (key & 0x0000ff);
                    }
               }

               data->color_key = key;
          }
          else {
               /* ...or based on trans rgb value */
               png_color_16p trans = &data->info_ptr->trans_values;

               data->color_key = (((trans->red & 0xff00) << 8) |
                                  ((trans->green & 0xff00)) |
                                  ((trans->blue & 0xff00) >> 8));
          }
     }

     switch (data->color_type) {
          case PNG_COLOR_TYPE_PALETTE: {
               png_colorp palette    = data->info_ptr->palette;
               png_bytep  trans      = data->info_ptr->trans;
               int        num_trans  = data->info_ptr->num_trans;
               int        num_colors = MIN( MAXCOLORMAPSIZE, data->info_ptr->num_palette );

               for (i=0; i<num_colors; i++) {
                    data->colors[i].a = (i < num_trans) ? trans[i] : 0xff;
                    data->colors[i].r = palette[i].red;
                    data->colors[i].g = palette[i].green;
                    data->colors[i].b = palette[i].blue;

                    data->palette[i] = PIXEL_ARGB( data->colors[i].a,
                                                   data->colors[i].r,
                                                   data->colors[i].g,
                                                   data->colors[i].b );
               }

               data->pitch = (data->width + 7) & ~7;
               break;
          }

          case PNG_COLOR_TYPE_GRAY:
               data->pitch = data->width;

               if (data->bpp == 16)
                    png_set_strip_16( data->png_ptr );

               break;

          case PNG_COLOR_TYPE_GRAY_ALPHA:
               png_set_gray_to_rgb( data->png_ptr );
               /* fall through */

          default:
               data->pitch = data->width * 4;

               if (data->bpp == 16)
                    png_set_strip_16( data->png_ptr );

#ifdef WORDS_BIGENDIAN
               if (!(data->color_type & PNG_COLOR_MASK_ALPHA))
                    png_set_filler( data->png_ptr, 0xFF, PNG_FILLER_BEFORE );

               png_set_swap_alpha( data->png_ptr );
#else
               if (!(data->color_type & PNG_COLOR_MASK_ALPHA))
                    png_set_filler( data->png_ptr, 0xFF, PNG_FILLER_AFTER );

               png_set_bgr( data->png_ptr );
#endif
               break;
     }

     png_set_interlace_handling( data->png_ptr );

     /* Update the info to reflect our transformations */
     png_read_update_info( data->png_ptr, data->info_ptr );
}

/* Called for each row; note that you will get duplicate row numbers
   for interlaced PNGs */
static void
png_row_callback( png_structp png_read_ptr,
                  png_bytep   new_row,
                  png_uint_32 row_num,
                  int         pass_num )
{
     IDirectFBImageProvider_PNG_data *data;

     data = png_get_progressive_ptr( png_read_ptr );

     /* error stage? */
     if (data->stage < 0)
          return;

     /* set image decoding stage */
     data->stage = STAGE_IMAGE;

     /* check image data pointer */
     if (!data->image) {
          // FIXME: allocates four additional bytes because the scaling functions
          //        in src/misc/gfx_util.c have an off-by-one bug which causes
          //        segfaults on darwin/osx (not on linux)
          int size = data->pitch * data->height + 4;

          /* allocate image data */
          data->image = D_CALLOC( 1, size );
          if (!data->image) {
               D_ERROR("DirectFB/ImageProvider_PNG: Could not "
                        "allocate %d bytes of system memory!\n", size);

               /* set error stage */
               data->stage = STAGE_ERROR;

               return;
          }
     }

     /* write to image data */
     png_progressive_combine_row( data->png_ptr, (png_bytep) (data->image +
                                  row_num * data->pitch), new_row );

     /* increase row counter, FIXME: interlaced? */
     data->rows++;

     if (data->render_callback) {
          DIRenderCallbackResult r;
          DFBRectangle rect = { 0, row_num, data->width, 1 };

          r = data->render_callback( &rect, data->render_callback_context );
          if (r != DIRCR_OK)
              data->stage = STAGE_ABORT;
     }
}

/* Called after reading the entire image */
static void
png_end_callback   (png_structp png_read_ptr,
                    png_infop   png_info_ptr)
{
     IDirectFBImageProvider_PNG_data *data;

     data = png_get_progressive_ptr( png_read_ptr );

     /* error stage? */
     if (data->stage < 0)
          return;

     /* set end stage */
     data->stage = STAGE_END;
}

/* Pipes data into libpng until stage is different from the one specified. */
static DFBResult
push_data_until_stage (IDirectFBImageProvider_PNG_data *data,
                       int                              stage,
                       int                              buffer_size)
{
     DFBResult            ret;
     IDirectFBDataBuffer *buffer = data->buffer;

     while (data->stage < stage) {
          unsigned int  len;
          unsigned char buf[buffer_size];

          if (data->stage < 0)
               return DFB_FAILURE;

          while (buffer->HasData( buffer ) == DFB_OK) {
               D_DEBUG( "ImageProvider/PNG: Retrieving data (up to %d bytes)...\n", buffer_size );

               ret = buffer->GetData( buffer, buffer_size, buf, &len );
               if (ret)
                    return ret;

               D_DEBUG( "ImageProvider/PNG: Got %d bytes...\n", len );

               png_process_data( data->png_ptr, data->info_ptr, buf, len );

               D_DEBUG( "ImageProvider/PNG: ...processed %d bytes.\n", len );

               /* are we there yet? */
               if (data->stage < 0 || data->stage >= stage) {
                   switch (data->stage) {
                        case STAGE_ABORT: return DFB_INTERRUPTED;
                        case STAGE_ERROR: return DFB_FAILURE;
                        default:          return DFB_OK;
                   }
               }
          }

          D_DEBUG( "ImageProvider/PNG: Waiting for data...\n" );

          if (buffer->WaitForData( buffer, 1 ) == DFB_EOF)
               return DFB_FAILURE;
     }

     return DFB_OK;
}


