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

#include "osspriteop.h"
#include "colourtrans.h"
#include "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"

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()
{
    static char *buff[3];
    static char *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];
}

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

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

    warning_lookup_2("PNGErr", message, buffer);
}

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

osspriteop_area *build_sprite_from_png(FILE *fp, gfx_dir *directory)
{
    osspriteop_area * volatile area = NULL;
    osspriteop_header *sprite;
    os_colour *pcol;
    png_uint_32 width, height, have_trns, rowbytes, area_size;
    png_uint_32 maxwidth, maxheight, scaledwidth, scaledheight;
    int bit_depth, colour_type;
    int i;
    unsigned alphacheck;
    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_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, directory,
                                     error_handler, warning_handler);
    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))
    {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        free(area);
        free(rows);
        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);

    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_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) != 0)
        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))
        png_set_gamma(png_ptr, screen_gamma/10.0, gamma);
    else
        png_set_gamma(png_ptr, screen_gamma/10.0, 5.0/11.0);

    png_read_update_info(png_ptr, info_ptr);

    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;

    maxwidth = width >= scaledwidth ? width : scaledwidth;
    maxheight = height >= scaledheight ? height : scaledheight;

    /*
     * Create the sprite
     */

    area_size = width * height * 4 +
                sizeof(osspriteop_area) + sizeof(osspriteop_header);

    if ((colour_type & PNG_COLOR_MASK_ALPHA) && bitmap_depth == 5)
    {
        /* May need extra space for the 32bpp mask */
        png_uint_32 maskwidth = (((width + 31) &~ 31) / 8);
        area_size += maskwidth * height;
    }

    area=malloc((size_t) area_size);
    if (!area)
    	return area;

    rows=calloc((size_t) maxheight, sizeof (png_byte *));
    if (!rows)
    {
        free(area);
        return area;
    }

    area->size=(int) area_size;
    area->first=16;

    osspriteop_clear_sprites(osspriteop_USER_AREA, area);
    /*
     * 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, area, "pic", FALSE,
                             (int) width*4, (int) height,
                             os_MODE8BPP90X90);
    sprite=osspriteop_select_sprite(osspriteop_USER_AREA, area, (osspriteop_id) "pic");

    /*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);

    /*
     * Scale the image if necessary, and do an alpha check
     */

    alphacheck = 0;

    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 (alphacheck == 1 && 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);
        area->used+=(int) (maskwidth*height);
    }


    if (!(alphacheck & alpha_COMPLEX))
    {
        switch (bitmap_depth)
        {
        case 3:
        {
            /*
             * 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.
             */
            pp = rows[0];
            cttable = get_inverse_table();
            rowbytes = (width+3)&~3;

            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;
            area->used = sprite->size + sizeof(osspriteop_area);
            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;
            area->used = sprite->size + sizeof(osspriteop_area);
            realloc(area, area->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 *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;

    if (area->used != area->size)
        area = realloc(area, area->size = area->used);

#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 area;
}

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

#define SCALED
#include "alphaplot.c"

#define DITHERED
#include "alphaplot.c"

#undef SCALED
#include "alphaplot.c"

void build_gamma_tables(int gamma)
{
    double g = gamma / 10.0;
    static int current_gamma = -1;
    int i;

    if (gamma == current_gamma)
        return;

    current_gamma = gamma;

    for (i=0; i<256; i++)
        screen_to_1[i] = (png_byte)(pow((double)i / 255.0, g) * 255.0 + .5);

    g = 1.0/g;

    for (i=0; i<256; i++)
        screen_from_1[i] = (png_byte)(pow((double)i / 255.0, g) * 255.0 + .5);

}
