#include <stdlib.h>
#include <math.h>
#include <assert.h>

#include <flex.h>

#include <oslib/osspriteop.h>
#include <oslib/colourtrans.h>
#include <oslib/osfile.h>

#include "png.h"

#include "ztypes.h"
#include "osdepend.h"
#include "riscosio.h"
#include "v6.h"
#include "v6ro.h"
#include "graphics.h"
#include "pngro.h"

int have_adaptive;

typedef struct
{
    int num;
    png_color PLTE[16];
} PLTE_info;

static png_color last_PLTE[16];

static png_byte screen_to_1[256], screen_from_1[256];

/* The basic RISC OS 256-colour palette */
static const char pal256[256][3] =
{
    0x00, 0x00, 0x00,
    0x11, 0x11, 0x11,
    0x22, 0x22, 0x22,
    0x33, 0x33, 0x33,
    0x44, 0x00, 0x00,
    0x55, 0x11, 0x11,
    0x66, 0x22, 0x22,
    0x77, 0x33, 0x33,
    0x00, 0x00, 0x44,
    0x11, 0x11, 0x55,
    0x22, 0x22, 0x66,
    0x33, 0x33, 0x77,
    0x44, 0x00, 0x44,
    0x55, 0x11, 0x55,
    0x66, 0x22, 0x66,
    0x77, 0x33, 0x77,
    0x88, 0x00, 0x00,
    0x99, 0x11, 0x11,
    0xAA, 0x22, 0x22,
    0xBB, 0x33, 0x33,
    0xCC, 0x00, 0x00,
    0xDD, 0x11, 0x11,
    0xEE, 0x22, 0x22,
    0xFF, 0x33, 0x33,
    0x88, 0x00, 0x44,
    0x99, 0x11, 0x55,
    0xAA, 0x22, 0x66,
    0xBB, 0x33, 0x77,
    0xCC, 0x00, 0x44,
    0xDD, 0x11, 0x55,
    0xEE, 0x22, 0x66,
    0xFF, 0x33, 0x77,
    0x00, 0x44, 0x00,
    0x11, 0x55, 0x11,
    0x22, 0x66, 0x22,
    0x33, 0x77, 0x33,
    0x44, 0x44, 0x00,
    0x55, 0x55, 0x11,
    0x66, 0x66, 0x22,
    0x77, 0x77, 0x33,
    0x00, 0x44, 0x44,
    0x11, 0x55, 0x55,
    0x22, 0x66, 0x66,
    0x33, 0x77, 0x77,
    0x44, 0x44, 0x44,
    0x55, 0x55, 0x55,
    0x66, 0x66, 0x66,
    0x77, 0x77, 0x77,
    0x88, 0x44, 0x00,
    0x99, 0x55, 0x11,
    0xAA, 0x66, 0x22,
    0xBB, 0x77, 0x33,
    0xCC, 0x44, 0x00,
    0xDD, 0x55, 0x11,
    0xEE, 0x66, 0x22,
    0xFF, 0x77, 0x33,
    0x88, 0x44, 0x44,
    0x99, 0x55, 0x55,
    0xAA, 0x66, 0x66,
    0xBB, 0x77, 0x77,
    0xCC, 0x44, 0x44,
    0xDD, 0x55, 0x55,
    0xEE, 0x66, 0x66,
    0xFF, 0x77, 0x77,
    0x00, 0x88, 0x00,
    0x11, 0x99, 0x11,
    0x22, 0xAA, 0x22,
    0x33, 0xBB, 0x33,
    0x44, 0x88, 0x00,
    0x55, 0x99, 0x11,
    0x66, 0xAA, 0x22,
    0x77, 0xBB, 0x33,
    0x00, 0x88, 0x44,
    0x11, 0x99, 0x55,
    0x22, 0xAA, 0x66,
    0x33, 0xBB, 0x77,
    0x44, 0x88, 0x44,
    0x55, 0x99, 0x55,
    0x66, 0xAA, 0x66,
    0x77, 0xBB, 0x77,
    0x88, 0x88, 0x00,
    0x99, 0x99, 0x11,
    0xAA, 0xAA, 0x22,
    0xBB, 0xBB, 0x33,
    0xCC, 0x88, 0x00,
    0xDD, 0x99, 0x11,
    0xEE, 0xAA, 0x22,
    0xFF, 0xBB, 0x33,
    0x88, 0x88, 0x44,
    0x99, 0x99, 0x55,
    0xAA, 0xAA, 0x66,
    0xBB, 0xBB, 0x77,
    0xCC, 0x88, 0x44,
    0xDD, 0x99, 0x55,
    0xEE, 0xAA, 0x66,
    0xFF, 0xBB, 0x77,
    0x00, 0xCC, 0x00,
    0x11, 0xDD, 0x11,
    0x22, 0xEE, 0x22,
    0x33, 0xFF, 0x33,
    0x44, 0xCC, 0x00,
    0x55, 0xDD, 0x11,
    0x66, 0xEE, 0x22,
    0x77, 0xFF, 0x33,
    0x00, 0xCC, 0x44,
    0x11, 0xDD, 0x55,
    0x22, 0xEE, 0x66,
    0x33, 0xFF, 0x77,
    0x44, 0xCC, 0x44,
    0x55, 0xDD, 0x55,
    0x66, 0xEE, 0x66,
    0x77, 0xFF, 0x77,
    0x88, 0xCC, 0x00,
    0x99, 0xDD, 0x11,
    0xAA, 0xEE, 0x22,
    0xBB, 0xFF, 0x33,
    0xCC, 0xCC, 0x00,
    0xDD, 0xDD, 0x11,
    0xEE, 0xEE, 0x22,
    0xFF, 0xFF, 0x33,
    0x88, 0xCC, 0x44,
    0x99, 0xDD, 0x55,
    0xAA, 0xEE, 0x66,
    0xBB, 0xFF, 0x77,
    0xCC, 0xCC, 0x44,
    0xDD, 0xDD, 0x55,
    0xEE, 0xEE, 0x66,
    0xFF, 0xFF, 0x77,
    0x00, 0x00, 0x88,
    0x11, 0x11, 0x99,
    0x22, 0x22, 0xAA,
    0x33, 0x33, 0xBB,
    0x44, 0x00, 0x88,
    0x55, 0x11, 0x99,
    0x66, 0x22, 0xAA,
    0x77, 0x33, 0xBB,
    0x00, 0x00, 0xCC,
    0x11, 0x11, 0xDD,
    0x22, 0x22, 0xEE,
    0x33, 0x33, 0xFF,
    0x44, 0x00, 0xCC,
    0x55, 0x11, 0xDD,
    0x66, 0x22, 0xEE,
    0x77, 0x33, 0xFF,
    0x88, 0x00, 0x88,
    0x99, 0x11, 0x99,
    0xAA, 0x22, 0xAA,
    0xBB, 0x33, 0xBB,
    0xCC, 0x00, 0x88,
    0xDD, 0x11, 0x99,
    0xEE, 0x22, 0xAA,
    0xFF, 0x33, 0xBB,
    0x88, 0x00, 0xCC,
    0x99, 0x11, 0xDD,
    0xAA, 0x22, 0xEE,
    0xBB, 0x33, 0xFF,
    0xCC, 0x00, 0xCC,
    0xDD, 0x11, 0xDD,
    0xEE, 0x22, 0xEE,
    0xFF, 0x33, 0xFF,
    0x00, 0x44, 0x88,
    0x11, 0x55, 0x99,
    0x22, 0x66, 0xAA,
    0x33, 0x77, 0xBB,
    0x44, 0x44, 0x88,
    0x55, 0x55, 0x99,
    0x66, 0x66, 0xAA,
    0x77, 0x77, 0xBB,
    0x00, 0x44, 0xCC,
    0x11, 0x55, 0xDD,
    0x22, 0x66, 0xEE,
    0x33, 0x77, 0xFF,
    0x44, 0x44, 0xCC,
    0x55, 0x55, 0xDD,
    0x66, 0x66, 0xEE,
    0x77, 0x77, 0xFF,
    0x88, 0x44, 0x88,
    0x99, 0x55, 0x99,
    0xAA, 0x66, 0xAA,
    0xBB, 0x77, 0xBB,
    0xCC, 0x44, 0x88,
    0xDD, 0x55, 0x99,
    0xEE, 0x66, 0xAA,
    0xFF, 0x77, 0xBB,
    0x88, 0x44, 0xCC,
    0x99, 0x55, 0xDD,
    0xAA, 0x66, 0xEE,
    0xBB, 0x77, 0xFF,
    0xCC, 0x44, 0xCC,
    0xDD, 0x55, 0xDD,
    0xEE, 0x66, 0xEE,
    0xFF, 0x77, 0xFF,
    0x00, 0x88, 0x88,
    0x11, 0x99, 0x99,
    0x22, 0xAA, 0xAA,
    0x33, 0xBB, 0xBB,
    0x44, 0x88, 0x88,
    0x55, 0x99, 0x99,
    0x66, 0xAA, 0xAA,
    0x77, 0xBB, 0xBB,
    0x00, 0x88, 0xCC,
    0x11, 0x99, 0xDD,
    0x22, 0xAA, 0xEE,
    0x33, 0xBB, 0xFF,
    0x44, 0x88, 0xCC,
    0x55, 0x99, 0xDD,
    0x66, 0xAA, 0xEE,
    0x77, 0xBB, 0xFF,
    0x88, 0x88, 0x88,
    0x99, 0x99, 0x99,
    0xAA, 0xAA, 0xAA,
    0xBB, 0xBB, 0xBB,
    0xCC, 0x88, 0x88,
    0xDD, 0x99, 0x99,
    0xEE, 0xAA, 0xAA,
    0xFF, 0xBB, 0xBB,
    0x88, 0x88, 0xCC,
    0x99, 0x99, 0xDD,
    0xAA, 0xAA, 0xEE,
    0xBB, 0xBB, 0xFF,
    0xCC, 0x88, 0xCC,
    0xDD, 0x99, 0xDD,
    0xEE, 0xAA, 0xEE,
    0xFF, 0xBB, 0xFF,
    0x00, 0xCC, 0x88,
    0x11, 0xDD, 0x99,
    0x22, 0xEE, 0xAA,
    0x33, 0xFF, 0xBB,
    0x44, 0xCC, 0x88,
    0x55, 0xDD, 0x99,
    0x66, 0xEE, 0xAA,
    0x77, 0xFF, 0xBB,
    0x00, 0xCC, 0xCC,
    0x11, 0xDD, 0xDD,
    0x22, 0xEE, 0xEE,
    0x33, 0xFF, 0xFF,
    0x44, 0xCC, 0xCC,
    0x55, 0xDD, 0xDD,
    0x66, 0xEE, 0xEE,
    0x77, 0xFF, 0xFF,
    0x88, 0xCC, 0x88,
    0x99, 0xDD, 0x99,
    0xAA, 0xEE, 0xAA,
    0xBB, 0xFF, 0xBB,
    0xCC, 0xCC, 0x88,
    0xDD, 0xDD, 0x99,
    0xEE, 0xEE, 0xAA,
    0xFF, 0xFF, 0xBB,
    0x88, 0xCC, 0xCC,
    0x99, 0xDD, 0xDD,
    0xAA, 0xEE, 0xEE,
    0xBB, 0xFF, 0xFF,
    0xCC, 0xCC, 0xCC,
    0xDD, 0xDD, 0xDD,
    0xEE, 0xEE, 0xEE,
    0xFF, 0xFF, 0xFF
};

#define os_MODE16BPP90X90 ((os_mode) ((5 << 27) | (90 << 14) | (90 << 1) | 1))
#define os_MODE32BPP90X90 ((os_mode) ((6 << 27) | (90 << 14) | (90 << 1) | 1))
#define INDEX15(r,g,b) (((b & 0xF8) << 7) | ((g & 0xF8) << 2) | (r >> 3))

int dithering;

static char *get_inverse_table(void)
{
    static char *buff[3];
    static char * restrict loaded;

    if (loaded) return loaded;

    if (xcolourtrans_generate_table(os_MODE32BPP90X90, colourtrans_DEFAULT_PALETTE,
                                    os_CURRENT_MODE, colourtrans_CURRENT_PALETTE,
                                    (osspriteop_trans_tab *) buff,
                                    NONE, NULL, NULL, NULL))
    {
        loaded = malloc(32768);
        if (!loaded)
            return NULL;
        osfile_load_stamped("<Zip2000$Dir>.Resources.8desktop", (byte *) loaded,
                            NULL, NULL, NULL, NULL);
        return loaded;
    }

    return buff[1];
}

void pngro_note_palette(osspriteop_area **area)
{
    PLTE_info *plte = (PLTE_info *) (*area + 1);
    int num = plte->num;
    for (int i = 0; i < num; i++)
        last_PLTE[i] = plte->PLTE[i];
}

int pngro_match_palette(osspriteop_area **area)
{
    PLTE_info * restrict plte = (PLTE_info *) (*area + 1);
    int num = plte->num;
    for (int i = 0; i < num; i++)
    {
        if (last_PLTE[i].red != plte->PLTE[i].red ||
            last_PLTE[i].green != plte->PLTE[i].green ||
            last_PLTE[i].blue != plte->PLTE[i].blue)
            return FALSE;
    }
    return TRUE;
}

static unsigned char *local_pc;

static void warning_handler(png_struct * restrict png_ptr, const char * restrict message)
{
    char buffer[32];
    gfx_dir *directory = (gfx_dir *) png_get_error_ptr(png_ptr);
    unsigned char *temp;

    if (!directory) return;

    sprintf(buffer, "%d", directory->image_number);

    #ifdef USING_GLOBAL_REGS
    /* warning_lookup wants pc, but it's been corrupted by the library */
    temp = pc;
    pc = local_pc;
    #endif

    warning_lookup_2("PNGErr", message, buffer);

    #ifdef USING_GLOBAL_REGS
    /* put the register back (so we don't break APCS) */
    pc = temp;
    #endif
}

static void error_handler(png_struct * restrict png_ptr, const char * restrict message)
{
    warning_handler(png_ptr, message);
    longjmp(png_ptr->jmpbuf, 1);
}

static void *pngro_malloc(png_struct *png_ptr, png_size_t size)
{
    int urgent = (int) png_get_mem_ptr(png_ptr);
    void *r;

    do
        r = malloc(size);
    while (r == NULL && urgent && release_memory());

    return r;
}

Bitmap *build_sprite_from_png(FILE * restrict fp, gfx_dir * restrict directory, int urgent)
{
    /* volatiles here for setjmp */
    Bitmap * volatile bmp = NULL;
    osspriteop_header *sprite;
    os_colour *pcol;
    png_uint_32 width, height, have_trns, rowbytes, area_size;
    png_uint_32 scaledwidth, scaledheight;
    int bit_depth, colour_type;
    int i, just_8bpp = FALSE;
    unsigned alphacheck = 0;
    png_int_32 x, y;
    png_struct *png_ptr;
    png_info *info_ptr;
    png_byte ** volatile rows = NULL, *pp;
    double gamma;
    const char *cttable;
    png_byte tt[256], mt[256];

    #ifdef USING_GLOBAL_REGS
    local_pc = pc;
    #endif

    /* Fire up the PNG library */
    png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
                                       urgent ? directory : NULL,
                                       error_handler, warning_handler,
                                       (void *) urgent, pngro_malloc, NULL);
    if (!png_ptr)
        return NULL;

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
    {
        png_destroy_read_struct(&png_ptr, NULL, NULL);
        return NULL;
    }

    if (setjmp(png_ptr->jmpbuf))
    {
        if (bmp) free_bitmap(bmp);
        free(rows);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;
    }

    if (fseek(fp, directory->image_data_addr, SEEK_SET) != 0)
        fatal_lookup("GfxErr");

    png_init_io(png_ptr, fp);

    png_read_info(png_ptr, info_ptr);

    /*
     * Sort out transformations. We'll take the easy, memory-
     * hungry way. Convert everything to RGBA.
     */

    png_get_IHDR(png_ptr, info_ptr, &width, &height,
                 &bit_depth, &colour_type, NULL, NULL, NULL);

    have_trns = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);

    /*
     * Actually, no. An optimisation for the case of undithered,
     * paletted images with simple transparency on an 8-bit display.
     * That's the Infocom graphics on an old machine. The RGBA stuff
     * is way slower than the original MG1 code on an ARM3.
     */
    if (bitmap_depth == 3 && !dithering &&
        colour_type == PNG_COLOR_TYPE_PALETTE)
    {
        png_bytep trans;
        int num, i;
        png_get_tRNS(png_ptr, info_ptr, &trans, &num, NULL);
        memcpy(mt, trans, num);
        memset(mt+num, 255, 256-num);
        just_8bpp = TRUE;
        for (i=0; i<num; i++)
        {
            if (mt[i] == 0)
                alphacheck = 1u << 24;
            else if (mt[i] != 255)
            {
                just_8bpp = FALSE;
                break;
            }
        }
    }

    if (just_8bpp)
    {
        if (bit_depth < 8)
            png_set_packing(png_ptr);
    }
    else
    {
        if (colour_type == PNG_COLOR_TYPE_PALETTE)
            png_set_expand(png_ptr);

        if (!(colour_type & PNG_COLOR_MASK_COLOR))
            png_set_gray_to_rgb(png_ptr);

        if (have_trns)
            png_set_expand(png_ptr);

        if (bit_depth == 16)
            png_set_strip_16(png_ptr);

        if (!(colour_type & PNG_COLOR_MASK_ALPHA) && !have_trns)
            png_set_filler(png_ptr, 255, PNG_FILLER_AFTER);
    }

    if (!png_get_gAMA(png_ptr, info_ptr, &gamma))
        gamma = 0.45455;

    if (fabsf((float) gamma - 1/screen_gamma) >= 0.01F)
        png_set_gamma(png_ptr, screen_gamma, gamma);

    png_read_update_info(png_ptr, info_ptr);

    if (directory->adaptive)
    {
        png_colorp palette;
        int num;
        if (png_get_PLTE(png_ptr, info_ptr, &palette, &num))
            png_set_PLTE(png_ptr, info_ptr, last_PLTE, num);
    }

    png_get_IHDR(png_ptr, info_ptr, &width, &height,
                 &bit_depth, &colour_type, NULL, NULL, NULL);

    scaledwidth = directory->image_width;
    scaledheight = directory->image_height;
    if (!hires_screen) scaledheight /= 2;

    if (just_8bpp)
    {
        /* Make a translation table. Don't use ColourTrans as we
         * want consistent results with my RGBA code.
         */
        png_colorp palette;
        int num, i;
        cttable = get_inverse_table();
        png_get_PLTE(png_ptr, info_ptr, &palette, &num);
        for (i=0; i<num; i++)
        {
            int r,g,b;
            r = palette[i].red;
            g = palette[i].green;
            b = palette[i].blue;
            tt[i]=cttable[INDEX15(r,g,b)];
        }
    }

    /*
     * Create the sprite
     */
    if (just_8bpp)
    {
        area_size = ((width + 3) &~ 3) * height;
        if (alphacheck) area_size*=2;
    }
    else
    {
        area_size = width * height * 4;
        if (bitmap_depth == 5)
        {
            /* May need extra space for the 32bpp mask */
            png_uint_32 maskwidth = (((width + 31) &~ 31) / 8);
            area_size += maskwidth * height;
        }
    }

    area_size += sizeof(osspriteop_area) + sizeof(osspriteop_header);

    if (have_adaptive)
        area_size += sizeof(PLTE_info);

    bmp=png_malloc(png_ptr, sizeof(Bitmap));
    if (!bmp)
    {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    	return NULL;
    }
    bmp->next=NULL;
    bmp->directory=NULL;
    do
    {
        i = flex_alloc((flex_ptr) &bmp->sprite, (int) area_size);
    } while (!i && urgent && release_memory());

    if (!i)
    {
        if (urgent) warning_lookup("NoMemG");
        free(bmp);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;
    }

    rows=png_malloc(png_ptr, (png_uint_32) height * sizeof (png_byte *));
    if (!rows)
    {
        free_bitmap(bmp);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;
    }

    bmp->sprite->size=(int) area_size;
    bmp->sprite->first=have_adaptive ? 16 + sizeof(PLTE_info) : 16;

    osspriteop_clear_sprites(osspriteop_USER_AREA, bmp->sprite);

    if (have_adaptive)
    {
        PLTE_info * restrict plte;
        png_colorp palette;
        int num;

        flex_set_budge(0);
        plte = (PLTE_info *) (bmp->sprite + 1);
        if (png_get_PLTE(png_ptr, info_ptr, &palette, &num))
        {
            if (num > 16) num = 16;
            plte->num = num;
            for (i = 0; i < num; i++)
                plte->PLTE[i] = palette[i];
        }
        else
            plte->num = 0;
        flex_set_budge(1);
    }

    /*
     * The sprite is really 32bpp, but RISC OS 3.1 won't support this. We're not
     * asking it to plot it, so just do 8bpp width 4 times the width.
     */
    osspriteop_create_sprite(osspriteop_USER_AREA, bmp->sprite, "pic", FALSE,
                             just_8bpp ? (int) width : (int) width*4,
                             (int) height,
                             os_MODE8BPP90X90);

    flex_set_budge(0);

    sprite=osspriteop_select_sprite(osspriteop_USER_AREA, bmp->sprite, (osspriteop_id) "pic");

    if (just_8bpp && alphacheck)
        osspriteop_create_mask(osspriteop_PTR, bmp->sprite, (osspriteop_id) sprite);

    /*if ((colour_type & PNG_COLOR_MASK_ALPHA) && bitmap_depth == 5 &&
        height == scaledheight && width == scaledwidth)
        osspriteop_create_mask(osspriteop_PTR, area, (osspriteop_id) sprite);*/

    pcol=(os_colour *)(sprite+1);

    for (i = (int) height - 1; i >= 0; i--)
        rows[i] = (png_byte *) sprite + sprite->image + (sprite->width + 1) * 4 * i;

    /*
     * Read the image data
     */

    png_read_image(png_ptr, rows);

    png_read_end(png_ptr, NULL);

    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

    /*
     * Do an alpha check
     */

    if (colour_type & PNG_COLOR_MASK_ALPHA)
    {
        for (y=height-1; y>=0; y--)
            for (x=width-1; x>=0; x--)
                alphacheck |= ((unsigned *)rows[y])[x]+0x01000000;
    }

    /*
     * By magic, alphacheck will equal 0 if the image is solid, 1 if
     * it has binary transparency, and be >1 if it has partial
     * transparency. If it has any partial transparency, we will
     * keep it as 24bpp.
     */

    alphacheck >>= 24;

    /*
     * Special: if we're in 8bpp, dithering is on, and it's scaled
     * we'll treat it as alpha_COMPLEX. This will allow better dithering
     */
    if (bitmap_depth == 3 && dithering &&
        (scaledheight > height || scaledwidth > width))
        alphacheck = 2;

    directory->alphatype = alphacheck;

    if (alphacheck && bitmap_depth == 5)
    {
        png_uint_32 maskwidth = (((width + 31) &~ 31) / 8);
        sprite->mask=sprite->size;
        sprite->size+=(int) (maskwidth*height);
        bmp->sprite->used+=(int) (maskwidth*height);
    }


    if (!(alphacheck & alpha_COMPLEX))
    {
        switch (bitmap_depth)
        {
        case 3:
        {
            pp = rows[0];
            rowbytes = (width+3)&~3;

            if (just_8bpp)
            {
                /*
                 * We have 8bpp data. Just need to convert the data and
                 * create the mask.
                 */
                restrict png_bytep p = pp + rowbytes*height - 1;
                png_uint_32 mo = rowbytes*height;

                if (alphacheck)
                {
                    for ( ; p >= pp; p--)
                    {
                        unsigned c = *p;
                        *p = tt[c];
                        p[mo] = mt[c];
                    }
                }
                else
                {
                    for ( ; p >= pp; p--)
                    {
                        unsigned c = *p;
                        *p = tt[c];
                    }
                }
            }
            else
            {
                /*
                 * Convert to 8bpp. We are packing down to 8 or 16bpp in the
                 * first pass, within the existing memory. We are certain that
                 * the first two rows will fit within the first 32bpp row, so
                 * we need only do the first row left-to-right. The second can
                 * go right-to-left for dithering.
                 */

                /*
                 * This may claim 32K with budge off, but it must succeed as
                 * zlib used a 32K block for its decompression window.
                 */
                cttable = get_inverse_table();

                if (alphacheck)
                {
                    png_uint_32 rowbytes2 = (width*2+3)&~3;
                    if (dithering)
                    {
                        for (y = 0; y < height; y++)
                        {
                            png_int_32 start, end;
                            int dir;
                            if (y & 1)
                            {
                                start = width-1; end = -1; dir = -1;
                            }
                            else
                            {
                                start = 0; end = width; dir = +1;
                            }

                            for (x = start; x != end; x+=dir)
                            {
                                int r,g,b,a,c;
                                int ar,ag,ab;
                                int er,eg,eb;
                                r=rows[y][x*4];
                                g=rows[y][x*4+1];
                                b=rows[y][x*4+2];
                                a=rows[y][x*4+3];
                                c=cttable[INDEX15(r,g,b)];
                                pp[y*rowbytes2+x*2L]=c;
                                pp[y*rowbytes2+x*2L+1L]=a ? 255 : 0;
                                ar=pal256[c][0];
                                ag=pal256[c][1];
                                ab=pal256[c][2];
                                er=r-ar;
                                eg=g-ag;
                                eb=b-ab;
                                if (x != end - dir)
                                {
                                    r=rows[y][(x+dir)*4]+(7*er/16);
                                    g=rows[y][(x+dir)*4+1]+(7*eg/16);
                                    b=rows[y][(x+dir)*4+2]+(7*eb/16);
                                    if (r<0) r=0; else if (r>255) r=255;
                                    if (g<0) g=0; else if (g>255) g=255;
                                    if (b<0) b=0; else if (b>255) b=255;
                                    rows[y][(x+dir)*4]=r;
                                    rows[y][(x+dir)*4+1]=g;
                                    rows[y][(x+dir)*4+2]=b;
                                }
                                if (y != height-1)
                                {
                                    if (x != start)
                                    {
                                        r=rows[y+1][(x-dir)*4]+(3*er/16);
                                        g=rows[y+1][(x-dir)*4+1]+(3*eg/16);
                                        b=rows[y+1][(x-dir)*4+2]+(3*eb/16);
                                        if (r<0) r=0; else if (r>255) r=255;
                                        if (g<0) g=0; else if (g>255) g=255;
                                        if (b<0) b=0; else if (b>255) b=255;
                                        rows[y+1][(x-dir)*4]=r;
                                        rows[y+1][(x-dir)*4+1]=g;
                                        rows[y+1][(x-dir)*4+2]=b;
                                    }
                                    r=rows[y+1][x*4]+(5*er/16);
                                    g=rows[y+1][x*4+1]+(5*eg/16);
                                    b=rows[y+1][x*4+2]+(5*eb/16);
                                    if (r<0) r=0; else if (r>255) r=255;
                                    if (g<0) g=0; else if (g>255) g=255;
                                    if (b<0) b=0; else if (b>255) b=255;
                                    rows[y+1][x*4]=r;
                                    rows[y+1][x*4+1]=g;
                                    rows[y+1][x*4+2]=b;
                                    if (x != end - dir)
                                    {
                                        r=rows[y+1][(x+dir)*4]+(er/16);
                                        g=rows[y+1][(x+dir)*4+1]+(eg/16);
                                        b=rows[y+1][(x+dir)*4+2]+(eb/16);
                                        if (r<0) r=0; else if (r>255) r=255;
                                        if (g<0) g=0; else if (g>255) g=255;
                                        if (b<0) b=0; else if (b>255) b=255;
                                        rows[y+1][(x+dir)*4]=r;
                                        rows[y+1][(x+dir)*4+1]=g;
                                        rows[y+1][(x+dir)*4+2]=b;
                                    }
                                }
                            }
                        }

                    }
                    else
                    {
                        for (y = 0; y < height; y++)
                            for (x = 0; x < width; x++)
                            {
                                unsigned r,g,b,a;
                                r=rows[y][x*4];
                                g=rows[y][x*4+1];
                                b=rows[y][x*4+2];
                                a=rows[y][x*4+3];
                                pp[y*rowbytes2+x*2L]=cttable[INDEX15(r,g,b)];
                                pp[y*rowbytes2+x*2L+1L]=a ? 255 : 0;
                            }
                    }

                    for (y = 0; y < height; y++)
                        for (x = 0; x < width; x++)
                        {
                            pp[y*rowbytes+x]=pp[y*rowbytes2+x*2L];
                            pp[height*rowbytes2+y*rowbytes+x]=pp[y*rowbytes2+x*2L+1L];
                        }

                    memmove(pp+height*rowbytes, pp+height*rowbytes2, (size_t) (height*rowbytes));

                    sprite->size = (int) (rowbytes * height * 2 + sizeof(osspriteop_header));
                    sprite->mask = (int) (rowbytes * height + sizeof(osspriteop_header));
                }
                else
                {
                    if (dithering)
                    {
                        for (y = 0; y < height; y++)
                        {
                            png_int_32 start, end;
                            int dir;
                            if (y & 1)
                            {
                                start = width-1; end = -1; dir = -1;
                            }
                            else
                            {
                                start = 0; end = width; dir = +1;
                            }

                            for (x = start; x != end; x+=dir)
                            {
                                int r,g,b,c;
                                int ar,ag,ab;
                                int er,eg,eb;
                                r=rows[y][x*4];
                                g=rows[y][x*4+1];
                                b=rows[y][x*4+2];
                                c=cttable[INDEX15(r,g,b)];
                                pp[y*rowbytes+x]=c;
                                ar=pal256[c][0];
                                ag=pal256[c][1];
                                ab=pal256[c][2];
                                er=r-ar;
                                eg=g-ag;
                                eb=b-ab;
                                if (x != end - dir)
                                {
                                    r=rows[y][(x+dir)*4]+(7*er/16);
                                    g=rows[y][(x+dir)*4+1]+(7*eg/16);
                                    b=rows[y][(x+dir)*4+2]+(7*eb/16);
                                    if (r<0) r=0; else if (r>255) r=255;
                                    if (g<0) g=0; else if (g>255) g=255;
                                    if (b<0) b=0; else if (b>255) b=255;
                                    rows[y][(x+dir)*4]=r;
                                    rows[y][(x+dir)*4+1]=g;
                                    rows[y][(x+dir)*4+2]=b;
                                }
                                if (y != height-1)
                                {
                                    if (x != start)
                                    {
                                        r=rows[y+1][(x-dir)*4]+(3*er/16);
                                        g=rows[y+1][(x-dir)*4+1]+(3*eg/16);
                                        b=rows[y+1][(x-dir)*4+2]+(3*eb/16);
                                        if (r<0) r=0; else if (r>255) r=255;
                                        if (g<0) g=0; else if (g>255) g=255;
                                        if (b<0) b=0; else if (b>255) b=255;
                                        rows[y+1][(x-dir)*4]=r;
                                        rows[y+1][(x-dir)*4+1]=g;
                                        rows[y+1][(x-dir)*4+2]=b;
                                    }
                                    r=rows[y+1][x*4]+(5*er/16);
                                    g=rows[y+1][x*4+1]+(5*eg/16);
                                    b=rows[y+1][x*4+2]+(5*eb/16);
                                    if (r<0) r=0; else if (r>255) r=255;
                                    if (g<0) g=0; else if (g>255) g=255;
                                    if (b<0) b=0; else if (b>255) b=255;
                                    rows[y+1][x*4]=r;
                                    rows[y+1][x*4+1]=g;
                                    rows[y+1][x*4+2]=b;
                                    if (x != end - dir)
                                    {
                                        r=rows[y+1][(x+dir)*4]+(er/16);
                                        g=rows[y+1][(x+dir)*4+1]+(eg/16);
                                        b=rows[y+1][(x+dir)*4+2]+(eb/16);
                                        if (r<0) r=0; else if (r>255) r=255;
                                        if (g<0) g=0; else if (g>255) g=255;
                                        if (b<0) b=0; else if (b>255) b=255;
                                        rows[y+1][(x+dir)*4]=r;
                                        rows[y+1][(x+dir)*4+1]=g;
                                        rows[y+1][(x+dir)*4+2]=b;
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        for (y = 0; y < height; y++)
                        {
                            for (x = 0; x < width; x++)
                            {
                                unsigned r,g,b;
                                r=rows[y][x*4];
                                g=rows[y][x*4+1];
                                b=rows[y][x*4+2];
                                pp[y*rowbytes+x]=cttable[INDEX15(r,g,b)];
                            }
                        }
                    }

                    sprite->size = (int) (rowbytes * height + sizeof(osspriteop_header));
                }

                sprite->width = (int) ((rowbytes>>2) - 1);
                sprite->right_bit = (int) ((31+(width&3)*8) & 31);
                sprite->mode = os_MODE8BPP90X90;
                bmp->sprite->used = bmp->sprite->first + sprite->size;
            }
            break;
        }
        case 4:
            /*
             * Convert to 16bpp
             */
            pp = rows[0];
            rowbytes = (width*2+3)&~3;
            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    unsigned r,g,b,a,n;
                    r=rows[y][x*4];
                    g=rows[y][x*4+1];
                    b=rows[y][x*4+2];
                    a=rows[y][x*4+3];
                    n = INDEX15(r,g,b);
                    if (a == 0) n |= 0x8000;
                    *(unsigned short *)(pp+y*rowbytes+x*2)=n;
                }
            }
            if (alphacheck)
            {
                int maskbytes = (int) (((width + 31) &~ 31) / 8);
                sprite->mask = (int) (rowbytes * height + sizeof(osspriteop_header));
                sprite->size = (int) (sprite->mask + maskbytes * height);
                for (y = 0; y < height; y++)
                {
                    unsigned w=0, b=1;
                    unsigned *mp = (unsigned *) ((char *)sprite + sprite->mask + maskbytes * y);

                    for (x = 0; x < width; x++)
                    {
                        unsigned n;
                        n=pp[y*rowbytes+x*2L+1L];
                        if ((n & 0x80) == 0)
                            w |= b;
                        b <<= 1;
                        if (b == 0)
                        {
                            *mp++ = w;
                            w = 0;
                            b = 1;
                        }
                    }
                    if (b != 1) *mp = w;
                }
            }
            else
            {
                sprite->size = (int) (rowbytes * height + sizeof(osspriteop_header));
            }
            sprite->width = (int) ((rowbytes>>2) - 1);
            sprite->right_bit = (width&1)?15:31;
            sprite->mode = os_MODE16BPP90X90;
            bmp->sprite->used = bmp->sprite->first + sprite->size;
            break;
        case 5:
            /*
             * Already in 32bpp. Just need to fill in a mask, maybe.
             */
            if (alphacheck)
            {
                int maskbytes = (int) (((width + 31) &~ 31) / 8);
                for (y = 0; y < height; y++)
                {
                    unsigned w=0, b=1;
                    unsigned * restrict mp = (unsigned *) ((char *)sprite + sprite->mask + maskbytes * y);

                    for (x = 0; x < width; x++)
                    {
                        unsigned a;
                        a=rows[y][x*4+3];
                        if (a > 0)
                            w |= b;
                        b <<= 1;
                        if (b == 0)
                        {
                            *mp++ = w;
                            w = 0;
                            b = 1;
                        }
                    }
                    if (b != 1) *mp = w;
                }
            }
            sprite->mode = os_MODE32BPP90X90;
            break;
        }
    }
    else
        sprite->mode = os_MODE32BPP90X90;

    free(rows);
    rows = NULL;

    flex_set_budge(1);

    if (bmp->sprite->used != bmp->sprite->size)
    {
        bmp->sprite->size = bmp->sprite->used;
        flex_extend((flex_ptr) &bmp->sprite, bmp->sprite->size);
    }

#if 0
    {
        char buffer[256];
        sprintf(buffer, "<Zip2000$Dir>.Debug.%d", directory->image_number);
        xosspriteop_save_sprite_file(osspriteop_USER_AREA, area, buffer);
    }
#endif

    return bmp;
}

#undef DITHERED
#undef SCALED
#include "alphaplot.c"

#define SCALED
#include "alphaplot.c"

#define DITHERED
#include "alphaplot.c"

#undef SCALED
#include "alphaplot.c"

//extern __caller_narrow float powf(float,float);

void build_gamma_tables(float g)
{
    static float current_gamma = 0;
    int i;

    if (g == current_gamma)
        return;

    current_gamma = g;

    for (i=0; i<256; i++)
        screen_to_1[i] = (png_byte)lrintf(powf(i / 255.0F, g) * 255.0F);

    g = 1.0F/g;

    for (i=0; i<256; i++)
        screen_from_1[i] = (png_byte)lrintf(powf(i / 255.0F, g) * 255.0F);

}
