/*

gbmspr.c - Archimedes Sprite from RiscOS Format support

In Archimedes terminology, a sprite is a bitmap with an optional mask plane.

Reads a sprite from file created by *ScreenSave or *SSave command.
Will also write such a file containing a single sprite.

Input options: index=# (default: 0)

*/

/*...sincludes:0:*/
#include <stdio.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "gbm.h"
#include "gbmhelp.h"

/*...vgbm\46\h:0:*/
/*...vgbmhelp\46\h:0:*/

#ifndef min
#define	min(a,b)	(((a)<(b))?(a):(b))
#endif
/*...e*/
/*...suseful:0:*/
/*...sread_u16:0:*/
static gbm_boolean read_u16(int fd, gbm_u16 *w)
	{
	gbm_u8 low, high;
	gbm_file_read(fd, (char *) &low, 1);
	gbm_file_read(fd, (char *) &high, 1);
	*w = (gbm_u16) (low + ((gbm_u16) high << 8));
	return GBM_TRUE;
	}
/*...e*/
/*...sread_u32:0:*/
static gbm_boolean read_u32(int fd, gbm_u32 *d)
	{
	gbm_u16 low, high;
	read_u16(fd, &low);
	read_u16(fd, &high);
	*d = low + ((gbm_u32) high << 16);
	return GBM_TRUE;
	}
/*...e*/
/*...swrite_u16:0:*/
static gbm_boolean write_u16(int fd, gbm_u16 w)
	{
	gbm_u8 low  = (gbm_u8) w;
	gbm_u8 high = (gbm_u8) (w >> 8);
	return gbm_file_write(fd, &low, 1) == 1 && gbm_file_write(fd, &high, 1) == 1;
	}
/*...e*/
/*...swrite_u32:0:*/
static gbm_boolean write_u32(int fd, gbm_u32 d)
	{
	return write_u16(fd, (gbm_u16) d) && write_u16(fd, (gbm_u16) (d >> 16));
	}
/*...e*/
/*...e*/

static GBMFT spr_gbmft =
	{
	"Sprite",
	"Archimedes Sprite from RiscOS",
	"SPR SPRITE",
	GBM_FT_R1|GBM_FT_R4|GBM_FT_R8|
	GBM_FT_W1|GBM_FT_W4|GBM_FT_W8,
	};

#define	GBM_ERR_SPR_FIRST	((GBM_ERR) 1400)
#define	GBM_ERR_SPR_MODE	((GBM_ERR) 1401)
#define	GBM_ERR_SPR_OFFSET	((GBM_ERR) 1402)
#define	GBM_ERR_SPR_PAL8	((GBM_ERR) 1403)

typedef struct
	{
	long pos_palette, pos_image, pos_mask;
	gbm_u32 bytes_per_line, first_bit, last_bit, actual_bpp;
	} SPR_PRIV;

/*...sbpp_of_mode\44\ mode_of_bpp:0:*/
static int bpp_of_mode[] =
	{
	1,	/*  0: 640x256 */
	2,	/*  1: 320x256 */
	4,	/*  2: 160x256 */
	-1,	/*  3: Text only */
	1,	/*  4: 320x256 */
	2,	/*  5: 160x256 */
	2,	/*  6: 160x256 */
	-1,	/*  7: Teletext */
	2,	/*  8: 640x256 */
	4,	/*  9: 320x256 */
	8,	/* 10: 160x256 */
	2,	/* 11: 640x250 */
	4,	/* 12: 640x256 */
	8,	/* 13: 320x256 */
	4,	/* 14: 640x250 */
	8,	/* 15: 640x256 */
	4,	/* 16: 1056x250 */
	4,	/* 17: 1056x256 */
	1,	/* 18: 640x512 multisync-monitor */
	2,	/* 19: 640x512 multisync-monitor */
	4,	/* 20: 640x512 multisync-monitor */
	8,	/* 21: 640x512 multisync-monitor */
	-1,	/* 22: ? */
	1,	/* 23: 1152x896 61.2Hz-hires-montor */
	8,	/* 24: 1056x256 */
	1,	/* 25: 640x480 multisync-or-60Hz-VGA-monitor */
	2,	/* 26: 640x480 multisync-or-60Hz-VGA-monitor */
	4,	/* 27: 640x480 multisync-or-60Hz-VGA-monitor */
	8,	/* 28: 640x480 multisync-or-60Hz-VGA-monitor */
	};

#define	N_MODES	29

/* Return highest resolution mode for given bits per pixel. */

static int mode_of_bpp[] = { -1,23,-1,-1,17,-1,-1,-1,24 };
/*...e*/
/*...squick tables:0:*/
/* These are to account for the reverse ordering of pixels in a scan line. */

static gbm_u8 nibble_swap[0x100] =
	{
	0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,
	0x80,0x90,0xa0,0xb0,0xc0,0xd0,0xe0,0xf0,
	0x01,0x11,0x21,0x31,0x41,0x51,0x61,0x71,
	0x81,0x91,0xa1,0xb1,0xc1,0xd1,0xe1,0xf1,
	0x02,0x12,0x22,0x32,0x42,0x52,0x62,0x72,
	0x82,0x92,0xa2,0xb2,0xc2,0xd2,0xe2,0xf2,
	0x03,0x13,0x23,0x33,0x43,0x53,0x63,0x73,
	0x83,0x93,0xa3,0xb3,0xc3,0xd3,0xe3,0xf3,
	0x04,0x14,0x24,0x34,0x44,0x54,0x64,0x74,
	0x84,0x94,0xa4,0xb4,0xc4,0xd4,0xe4,0xf4,
	0x05,0x15,0x25,0x35,0x45,0x55,0x65,0x75,
	0x85,0x95,0xa5,0xb5,0xc5,0xd5,0xe5,0xf5,
	0x06,0x16,0x26,0x36,0x46,0x56,0x66,0x76,
	0x86,0x96,0xa6,0xb6,0xc6,0xd6,0xe6,0xf6,
	0x07,0x17,0x27,0x37,0x47,0x57,0x67,0x77,
	0x87,0x97,0xa7,0xb7,0xc7,0xd7,0xe7,0xf7,
	0x08,0x18,0x28,0x38,0x48,0x58,0x68,0x78,
	0x88,0x98,0xa8,0xb8,0xc8,0xd8,0xe8,0xf8,
	0x09,0x19,0x29,0x39,0x49,0x59,0x69,0x79,
	0x89,0x99,0xa9,0xb9,0xc9,0xd9,0xe9,0xf9,
	0x0a,0x1a,0x2a,0x3a,0x4a,0x5a,0x6a,0x7a,
	0x8a,0x9a,0xaa,0xba,0xca,0xda,0xea,0xfa,
	0x0b,0x1b,0x2b,0x3b,0x4b,0x5b,0x6b,0x7b,
	0x8b,0x9b,0xab,0xbb,0xcb,0xdb,0xeb,0xfb,
	0x0c,0x1c,0x2c,0x3c,0x4c,0x5c,0x6c,0x7c,
	0x8c,0x9c,0xac,0xbc,0xcc,0xdc,0xec,0xfc,
	0x0d,0x1d,0x2d,0x3d,0x4d,0x5d,0x6d,0x7d,
	0x8d,0x9d,0xad,0xbd,0xcd,0xdd,0xed,0xfd,
	0x0e,0x1e,0x2e,0x3e,0x4e,0x5e,0x6e,0x7e,
	0x8e,0x9e,0xae,0xbe,0xce,0xde,0xee,0xfe,
	0x0f,0x1f,0x2f,0x3f,0x4f,0x5f,0x6f,0x7f,
	0x8f,0x9f,0xaf,0xbf,0xcf,0xdf,0xef,0xff,
	};
static gbm_u8 bit_swap[0x100] =
	{
	0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,
	0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0,
	0x08,0x88,0x48,0xc8,0x28,0xa8,0x68,0xe8,
	0x18,0x98,0x58,0xd8,0x38,0xb8,0x78,0xf8,
	0x04,0x84,0x44,0xc4,0x24,0xa4,0x64,0xe4,
	0x14,0x94,0x54,0xd4,0x34,0xb4,0x74,0xf4,
	0x0c,0x8c,0x4c,0xcc,0x2c,0xac,0x6c,0xec,
	0x1c,0x9c,0x5c,0xdc,0x3c,0xbc,0x7c,0xfc,
	0x02,0x82,0x42,0xc2,0x22,0xa2,0x62,0xe2,
	0x12,0x92,0x52,0xd2,0x32,0xb2,0x72,0xf2,
	0x0a,0x8a,0x4a,0xca,0x2a,0xaa,0x6a,0xea,
	0x1a,0x9a,0x5a,0xda,0x3a,0xba,0x7a,0xfa,
	0x06,0x86,0x46,0xc6,0x26,0xa6,0x66,0xe6,
	0x16,0x96,0x56,0xd6,0x36,0xb6,0x76,0xf6,
	0x0e,0x8e,0x4e,0xce,0x2e,0xae,0x6e,0xee,
	0x1e,0x9e,0x5e,0xde,0x3e,0xbe,0x7e,0xfe,
	0x01,0x81,0x41,0xc1,0x21,0xa1,0x61,0xe1,
	0x11,0x91,0x51,0xd1,0x31,0xb1,0x71,0xf1,
	0x09,0x89,0x49,0xc9,0x29,0xa9,0x69,0xe9,
	0x19,0x99,0x59,0xd9,0x39,0xb9,0x79,0xf9,
	0x05,0x85,0x45,0xc5,0x25,0xa5,0x65,0xe5,
	0x15,0x95,0x55,0xd5,0x35,0xb5,0x75,0xf5,
	0x0d,0x8d,0x4d,0xcd,0x2d,0xad,0x6d,0xed,
	0x1d,0x9d,0x5d,0xdd,0x3d,0xbd,0x7d,0xfd,
	0x03,0x83,0x43,0xc3,0x23,0xa3,0x63,0xe3,
	0x13,0x93,0x53,0xd3,0x33,0xb3,0x73,0xf3,
	0x0b,0x8b,0x4b,0xcb,0x2b,0xab,0x6b,0xeb,
	0x1b,0x9b,0x5b,0xdb,0x3b,0xbb,0x7b,0xfb,
	0x07,0x87,0x47,0xc7,0x27,0xa7,0x67,0xe7,
	0x17,0x97,0x57,0xd7,0x37,0xb7,0x77,0xf7,
	0x0f,0x8f,0x4f,0xcf,0x2f,0xaf,0x6f,0xef,
	0x1f,0x9f,0x5f,0xdf,0x3f,0xbf,0x7f,0xff,
	};

static gbm_u8 pair_swap[0x10] =
	{
	0x00,0x10,0x20,0x30,0x01,0x11,0x21,0x31,
	0x02,0x12,0x22,0x32,0x03,0x13,0x23,0x33,
	};
/*...e*/

/*...sspr_qft:0:*/
GBM_ERR spr_qft(GBMFT *gbmft)
	{
	*gbmft = spr_gbmft;
	return GBM_ERR_OK;
	}
/*...e*/
/*...sspr_rhdr:0:*/
GBM_ERR spr_rhdr(const char *fn, int fd, GBM *gbm, const char *opt)
	{
	SPR_PRIV *priv = (SPR_PRIV *) gbm->priv;
	const char *index;
	int i;
	gbm_u32 num_sprites, offset_sprite, pos_sprite;
	gbm_u32 gbm_u32_w, scans_h, first_bit, last_bit, bits_per_line;
	gbm_u32 offset_image, offset_mask, mode;

	fn=fn; /* Suppress 'unref arg' compiler warnings */

	if ( (index = gbm_find_word_prefix(opt, "index=")) != NULL )
		sscanf(index + 6, "%d", &i);
	else
		i = 0;

	read_u32(fd, &num_sprites);
	if ( (gbm_u32) i >= num_sprites )
		return GBM_ERR_SPR_OFFSET;
	read_u32(fd, &offset_sprite);
	pos_sprite = gbm_file_lseek(fd, offset_sprite - 4L, SEEK_SET);

	while ( i-- > 0 )
		{
		read_u32(fd, &offset_sprite);
		pos_sprite = gbm_file_lseek(fd, pos_sprite + offset_sprite, SEEK_SET);
		}

	gbm_file_lseek(fd, 16, SEEK_CUR);	/* Skip 4 gbm_u8 next-ptr + 12 gbm_u8 name */
	read_u32(fd, &gbm_u32_w);	/* Width in gbm_u32s - 1 */
	read_u32(fd, &scans_h);	/* Scan lines high - 1 */
	read_u32(fd, &first_bit);	/* First bit used (left end of row) */
	read_u32(fd, &last_bit);	/* Last bit used (right end of row) */
	read_u32(fd, &offset_image);	/* Offset of image data */
	read_u32(fd, &offset_mask);	/* Offset of mask plane or above if none */
	read_u32(fd, &mode);		/* Mode sprite defined in */

	if ( first_bit & 7 )
		return GBM_ERR_SPR_FIRST;
	if ( mode >= N_MODES )
		return GBM_ERR_SPR_MODE;
	if ( (gbm->bpp = priv->actual_bpp = bpp_of_mode[mode]) == -1 )
		return GBM_ERR_SPR_MODE;

	gbm->h = (int) (scans_h+1);
	if ( gbm->h < 0 || gbm->h > 10000 )
		return GBM_ERR_BAD_SIZE;

	if ( gbm->bpp == 2 )
		gbm->bpp = 4;

	bits_per_line = ((gbm_u32_w+1) * 32) - first_bit - (32 - (last_bit+1));
	gbm->w = (int) (bits_per_line / priv->actual_bpp);
	if ( gbm->w < 0 || gbm->w > 10000 )
		return GBM_ERR_BAD_SIZE;

	priv->pos_palette    = gbm_file_lseek(fd, 0L, SEEK_CUR);
	priv->pos_image      = (long) pos_sprite + offset_image;
	priv->pos_mask       = (long) pos_sprite + offset_mask;
	priv->bytes_per_line = (gbm_u32_w + 1) * 4;
	priv->first_bit      = first_bit;
	priv->last_bit       = last_bit;

	return GBM_ERR_OK;
	}
/*...e*/
/*...sspr_rpal:0:*/
/*...sread_pal:0:*/
/* Palette entry is 2 gbm_u32s, which are same if no flashing */
/* We will simply use first gbm_u32 in each case */

static gbm_boolean read_pal(int fd, GBMRGB *gbmrgb)
	{
	gbm_u8 pal[8];
	if ( gbm_file_read(fd, pal, 8) != 8 )
		return GBM_FALSE;
	gbmrgb->r = pal[1];
	gbmrgb->g = pal[2];
	gbmrgb->b = pal[3];
	return GBM_TRUE;
	}
/*...e*/

/*...sgbmrgb_1bpp:0:*/
/*
I do not expect the palette to be missing from a file defined in a mode with 2
colours because the Wimp uses 16 colours, and I am led to beleive it is the
only thing that saves files without the palette. However, if I am wrong...
*/

static GBMRGB gbmrgb_1bpp[2] =
	{
	{0xff,0xff,0xff},	/* 0=White */
	{0x00,0x00,0x00},	/* 1=black */
	};
/*...e*/
/*...sgbmrgb_2bpp:0:*/
/*
I do not expect the palette to be missing from a file defined in a mode with 4
colours because the Wimp uses 16 colours, and I am led to beleive it is the
only thing that saves files without the palette. However, if I am wrong...
*/

static GBMRGB gbmrgb_2bpp[4] =
	{
	{0xff,0xff,0xff},
	{0xaa,0xaa,0xaa},
	{0x55,0x55,0x55},
	{0x00,0x00,0x00},
	};
/*...e*/
/*...sgbmrgb_4bpp:0:*/
/*
This is the default Wimp defined 16 colour palette.
The exact r,g,b values are not known so some nice bold examples of each,
according to their descriptions in the RiscOS books have been found manually.
*/

static GBMRGB gbmrgb_4bpp[16] =
	{
	{0xff,0xff,0xff},	/* 0=white */
	{0xdb,0xdb,0xdb},	/* 1=grey */
	{0xb6,0xb6,0xb6},	/* 2=grey */
	{0x92,0x92,0x92},	/* 3=grey */
	{0x6d,0x6d,0x6d},	/* 4=grey */
	{0x49,0x49,0x49},	/* 5=grey */
	{0x24,0x24,0x24},	/* 6=grey */
	{0x00,0x00,0x00},	/* 7=black */
	{0x00,0x00,0xff},	/* 8=dark blue */
	{0xff,0xff,0x00},	/* 9=yellow */
	{0x00,0xff,0x00},	/* a=green */
	{0xff,0x00,0x00},	/* b=red */
	{0xff,0xd8,0xd8},	/* c=cream */
	{0x40,0x80,0x40},	/* d=army green */
	{0xff,0x9c,0x00},	/* e=orange */
	{0x00,0xb9,0xff},	/* f=light blue */
	};
/*...e*/
/*...sexpand_0x10:0:*/
/*
The Archimedes does not have 0x100 palette registers, it has 0x10 VIDC registers.
Given an 8 bit pixel :-
	Bits 3-0 are bits 3-0 of palette index
	Bit 4 overrides red bit 7
	Bit 5 overrides green bit 6
	Bit 6 overrides green bit 7
	Bit 7 overrides blue bit 7
So we duplicate the 0x10 values we have read, and make the others from them.
*/

static void expand_0x10(GBMRGB *gbmrgb)
	{
	int i, bank;

	for ( bank = 0x10; bank < 0x100; bank += 0x10 )
		{
		gbm_u8 override_r = ((bank & 0x10) << 3);
		gbm_u8 override_g = ((bank & 0x60) << 1);
		gbm_u8 override_b =  (bank & 0x80)      ;
		for ( i = 0; i < 0x10; i++ )
			{
			gbmrgb[bank + i].r = ((gbmrgb[i].r & 0x7f) | override_r);
			gbmrgb[bank + i].g = ((gbmrgb[i].g & 0x3f) | override_g);
			gbmrgb[bank + i].b = ((gbmrgb[i].b & 0x7f) | override_b);
			}
		}
	}
/*...e*/
/*...sexpand_0x40:0:*/
/*
The Archimedes does not have 0x100 palette registers, it has 0x10.
Planning for the future, files can be written with 0x40 palette entries.
Given an 8 bit pixel :-
	Bits 5-0 are bits 5-0 of palette index
	Bit 6 overrides green bit 7
	Bit 7 overrides blue bit 7
So we duplicate the 0x40 values we have read, and make the others from them.
Now, although we have seen files with 0x40 entries, when you take the first
0x10, and perform expand_0x10 on them, the result is the same as this routine.
Clearly Acorn are looking forward to a day when the VIDC chip has 0x40
registers.
*/

static void expand_0x40(GBMRGB *gbmrgb)
	{
	int i, bank;

	for ( bank = 0; bank < 0x100; bank += 0x40 )
		{
		gbm_u8 override_g = ((bank & 0x40) << 1);
		gbm_u8 override_b =  (bank & 0x80)      ;
		for ( i = 0; i < 0x40; i++ )
			{
			gbmrgb[bank + i].r =   gbmrgb[i].r;
			gbmrgb[bank + i].g = ((gbmrgb[i].g & 0x7f) | override_g);
			gbmrgb[bank + i].b = ((gbmrgb[i].b & 0x7f) | override_b);
			}
		}
	}
/*...e*/

GBM_ERR spr_rpal(int fd, GBM *gbm, GBMRGB *gbmrgb)
	{
	SPR_PRIV *priv = (SPR_PRIV *) gbm->priv;
	int npal = (priv->pos_image - priv->pos_palette) / 8;

	if ( npal == 0 )
/*...sno palette in file\44\ use the default Wimp one:16:*/
switch ( priv->actual_bpp )
	{
	case 1: memcpy(gbmrgb, gbmrgb_1bpp, sizeof(gbmrgb_1bpp)); break;
	case 2: memcpy(gbmrgb, gbmrgb_2bpp, sizeof(gbmrgb_2bpp)); break;
	case 4: memcpy(gbmrgb, gbmrgb_4bpp, sizeof(gbmrgb_4bpp)); break;
	case 8:
/*...sdefault palette:32:*/
{
int i;
for ( i = 0; i < 0x10; i++ )
	{
	gbm_u8 tint = ((i & 0x03) << 4);
	gbmrgb[i].r = tint + ((i & 0x04) << 4);
	gbmrgb[i].g = tint;
	gbmrgb[i].b = tint + ((i & 0x08) << 3);
	}
expand_0x10(gbmrgb);
}
/*...e*/
		break;
	}

/*...e*/
	else
/*...sread palette from disk:16:*/
{
int i;
gbm_file_lseek(fd, priv->pos_palette, SEEK_SET);
for ( i = 0; i < npal; i++ )
	if ( !read_pal(fd, gbmrgb + i) )
		return GBM_ERR_READ;

if ( gbm->bpp == 8 )
	/* Handle getting too few palette entries */
	{
	if ( npal == 0x40 )
		expand_0x40(gbmrgb);
	else if ( npal == 0x10 )
		expand_0x10(gbmrgb);
	else
		return GBM_ERR_SPR_PAL8;
	}
}
/*...e*/

	return GBM_ERR_OK;
	}
/*...e*/
/*...sspr_rdata:0:*/
GBM_ERR spr_rdata(int fd, GBM *gbm, gbm_u8 *data)
	{
	int stride = ((gbm->bpp * gbm->w + 31) / 32) * 4;
	SPR_PRIV *priv = (SPR_PRIV *) gbm->priv;
	int scan_stride = priv->bytes_per_line;
	int scan_first = ((priv->first_bit) >> 3);
	int scan_gbm_u8s, i, j;
	gbm_u8 *datal = data + (gbm->h - 1) * stride;

	gbm_file_lseek(fd, priv->pos_image, SEEK_SET);

	switch ( priv->actual_bpp )
		{
/*...s8 \45\ read mapped pixels:16:*/
case 8:
	scan_gbm_u8s = gbm->w;
	for ( j = 0; j < gbm->h; j++, datal -= stride )
		{
		gbm_file_lseek(fd, priv->pos_image + j * scan_stride + scan_first, SEEK_SET);
		if ( gbm_file_read(fd, datal, scan_gbm_u8s) != scan_gbm_u8s )
			return GBM_ERR_READ;
		}
	break;
/*...e*/
/*...s4 \45\ read pixels\44\ nibble swapping:16:*/
case 4:
	scan_gbm_u8s = ((unsigned)(gbm->w + 1) >> 1);
	for ( j = 0; j < gbm->h; j++, datal -= stride )
		{
		gbm_file_lseek(fd, priv->pos_image + j * scan_stride + scan_first, SEEK_SET);
		if ( gbm_file_read(fd, datal, scan_gbm_u8s) != scan_gbm_u8s )
			return GBM_ERR_READ;
		for ( i = 0; i < scan_gbm_u8s; i++ )
			datal[i] = nibble_swap[datal[i]];
		}
	break;
/*...e*/
/*...s2 \45\ read pixels\44\ bit\45\pair reversing and expanding:16:*/
/*
Data is coming in a 2bpp, but we don't actually support this.
So we will expand the data to 4bpp as we read it.
We will do this inline, by reading into the second half.
*/

case 2:
	scan_gbm_u8s = ((unsigned)(gbm->w + 3) >> 2);
	for ( j = 0; j < gbm->h; j++, datal -= stride )
		{
		gbm_file_lseek(fd, priv->pos_image + j * scan_stride + scan_first, SEEK_SET);
		if ( gbm_file_read(fd, datal + scan_gbm_u8s, scan_gbm_u8s) != scan_gbm_u8s )
			return GBM_ERR_READ;
		for ( i = 0; i < scan_gbm_u8s; i++ )
			{
			datal[i * 2    ] = pair_swap[datal[scan_gbm_u8s + i] & 0x0f];
			datal[i * 2 + 1] = pair_swap[datal[scan_gbm_u8s + i] >> 4  ];
			}
		}
	break;
/*...e*/
/*...s1 \45\ read pixels\44\ bit reversing:16:*/
case 1:
	scan_gbm_u8s = ((unsigned)(gbm->w + 7) >> 3);
	for ( j = 0; j < gbm->h; j++, datal -= stride )
		{
		gbm_file_lseek(fd, priv->pos_image + j * scan_stride + scan_first, SEEK_SET);
		if ( gbm_file_read(fd, datal, scan_gbm_u8s) != scan_gbm_u8s )
			return GBM_ERR_READ;
		for ( i = 0; i < scan_gbm_u8s; i++ )
			datal[i] = bit_swap[datal[i]];
		}
	break;
/*...e*/
		}

	return GBM_ERR_OK;
	}
/*...e*/
/*...sspr_w:0:*/
/*
We have a problem here for 256 colour modes.
The 256 colours we are asked to write can be any colours and unrelated.
There are only 16 VIDC palette registers, and the other 4 bits in an 8 bit
byte override specific palette entry bits.
We will ignore all but the top 4 bits of each colour component. ie: just 7-4.
Therefore we will write a specifically fixed palette :-
	Bits 1 to 0 will be bits 5 and 4 for all 3 guns.
	Bit 2 will be red bit 6
	Bit 3 will be blue bit 6
	Bit 4 will be red bit 7
	Bit 5 will be green bit 6
	Bit 6 will be green bit 7
	Bit 7 will be blue bit 7
This is the default palette used by RiscOS.
We will map all incoming palette entrys first, to give a quick lookup table.
*/

/*...swrite_pal:0:*/
static gbm_boolean write_pal(int fd, GBMRGB gbmrgb)
	{
	gbm_u8 pal[8];
	int j;
	pal[0] = pal[4] = 0x00;
	pal[1] = pal[5] = (gbm_u8) (gbmrgb.r & 0xf0);
	pal[2] = pal[6] = (gbm_u8) (gbmrgb.g & 0xf0); 
	pal[3] = pal[7] = (gbm_u8) (gbmrgb.b & 0xf0); 
	for ( j = 0; j < 8; j++ )
		pal[j] += (pal[j] >> 4);
	return gbm_file_write(fd, pal, 8) == 8;
	}
/*...e*/

GBM_ERR spr_w(const char *fn, int fd, const GBM *gbm, const GBMRGB *gbmrgb, const gbm_u8 *data, const char *opt)
	{
	int stride = ((gbm->bpp * gbm->w + 31) / 32) * 4;
	int i, j, npal = ( 1 << gbm->bpp );
	gbm_u32 gbm_u32_w, last_bit, offset_image, mode;
	char name[12];
	gbm_u8 qpal[0x100], *buf;
	gbm_boolean ok;

	opt=opt; /* Suppress 'unref arg' warning */

	if ( gbm->bpp == 24 )
		return GBM_ERR_NOT_SUPP;

	if ( npal == 0x100 )
		npal = 0x10;

	ok  = write_u32(fd, 1);			/* Number of sprites */
	ok &= write_u32(fd, 12 + 4);		/* Offset to sprite */
	ok &= write_u32(fd, (12 + 44 + npal * 8 + stride * gbm->h) + 4); 
						/* Offset to free gbm_u16 (beyond sprites) */
	ok &= write_u32(fd, 44 + npal * 8 + stride * gbm->h);

/*...sbuild a name:8:*/
/*
We will use the supplied filename to build the name field.
We will ignore the path part (if any present).
Also we will chop of the file extension of .spr if present.
(This change was by customer request).
Also, map to lower case to keep RiscOS programs happy.
This isn't documented, but appears to be the case.
*/

{
const char *p = fn;
char *q;
if ( (q = strrchr(p, '\\')) != NULL )
	p = q + 1;
if ( (q = strrchr(p, '/')) != NULL )
	p = q + 1;
memset(name, 0, 12);
strncpy(name, p, 11);
if ( (q = strrchr(name, '.')) != NULL && gbm_same(q + 1, "spr", 4) )
	memset(q, '\0', 4);
for ( q = name; (q - name) < 12 && *q != '\0'; q++ )
	*q = tolower(*q);
}
/*...e*/
	ok &= ( gbm_file_write(fd, name, 12) == 12 );

	gbm_u32_w = ((gbm->w * gbm->bpp + 31) / 32) - 1;
	ok &= write_u32(fd, gbm_u32_w);		/* Width in gbm_u32s - 1 */
	ok &= write_u32(fd, gbm->h - 1);	/* Scan lines high - 1 */
	ok &= write_u32(fd, 0);			/* First bit used (left end of row) */
	last_bit = ((gbm->w * gbm->bpp - 1) & 0x1f);
	ok &= write_u32(fd, last_bit);		/* Last bit used (right end of row) */
	offset_image = ( 44 + npal * 8 );
	ok &= write_u32(fd, offset_image);	/* Offset of image data */
	ok &= write_u32(fd, offset_image);	/* Offset of mask plane or above if none */
	mode = mode_of_bpp[gbm->bpp];
	ok &= write_u32(fd, mode);		/* Mode sprite defined in */

	if ( !ok )
		return GBM_ERR_WRITE;

	/* Write palette */

	switch ( gbm->bpp )
		{
/*...s8 \45\ write RiscOS default palette\44\ and work out mapping:16:*/
case 8:

	/* Write the fixed RiscOS default palette */
	for ( i = 0; i < 0x10; i++ )
		{
		GBMRGB gbmrgb_def;
		gbm_u8 tint = ((i & 0x03) << 4);
		gbmrgb_def.r = tint + ((i & 0x04) << 4);
		gbmrgb_def.g = tint;
		gbmrgb_def.b = tint + ((i & 0x08) << 3);
		if ( !write_pal(fd, gbmrgb_def) )
			return GBM_ERR_WRITE;
		}

	/* Determine palette mapping */
	for ( i = 0; i < 0x100; i++ )
		{
		gbm_u8 r = gbmrgb[i].r;
		gbm_u8 g = gbmrgb[i].g;
		gbm_u8 b = gbmrgb[i].b;
		gbm_u8 k32 = ((((r & 0x30) + (g & 0x30) + (b & 0x30)) / 3) & 0x30);
		qpal[i] =  (b & 0x80U)       +
			  ((g & 0xc0U) >> 1) +
			  ((r & 0x80U) >> 3) +
			  ((b & 0x40U) >> 3) +
			  ((r & 0x40U) >> 4) +
			   (k32 >> 4);
		}
	break;
/*...e*/
/*...s4\44\1 \45\ write 16 or 2 entry palette:16:*/
case 4:
case 1:
	/* Write the palette */
	for ( i = 0; i < (1 << gbm->bpp); i++ )
		if ( !write_pal(fd, gbmrgb[i]) )
			return GBM_ERR_WRITE;
	break;
/*...e*/
		}

	/* Write data */

	if ( (buf = malloc((size_t) stride)) == NULL )
		return GBM_ERR_MEM;
	memset(buf, 0, stride);

	data += (gbm->h - 1) * stride; /* Start at the top */
	switch ( gbm->bpp )
		{
/*...s8 \45\ write mapped pixels\44\ funny order:16:*/
case 8:
	for ( j = 0; j < gbm->h; j++, data -= stride )
		{
		for ( i = 0; i < stride; i++ )
			buf[i] = qpal[data[i]];
		if ( gbm_file_write(fd, buf, stride) != stride )
			{
			free(buf);
			return GBM_ERR_WRITE;
			}
		}
	break;
/*...e*/
/*...s4 \45\ write pixels\44\ funny order:16:*/
case 4:
	for ( j = 0; j < gbm->h; j++, data -= stride )
		{
		for ( i = 0; i < stride; i++ )
			buf[i] = nibble_swap[data[i]];
		if ( gbm_file_write(fd, buf, stride) != stride )
			{
			free(buf);
			return GBM_ERR_WRITE;
			}
		}
	break;
/*...e*/
/*...s1 \45\ write pixels\44\ funny order:16:*/
case 1:
	for ( j = 0; j < gbm->h; j++, data -= stride )
		{
		for ( i = 0; i < stride; i++ )
			buf[i] = bit_swap[data[i]];
		if ( gbm_file_write(fd, buf, stride) != stride )
			{
			free(buf);
			return GBM_ERR_WRITE;
			}
		}
	break;
/*...e*/
		}

	free(buf);

	return GBM_ERR_OK;
	}
/*...e*/
/*...sspr_err:0:*/
const char *spr_err(GBM_ERR rc)
	{
	switch ( (int) rc )
		{
		case GBM_ERR_SPR_FIRST:
			return "sprite has first bit that is not a multiple of 8";
		case GBM_ERR_SPR_MODE:
			return "sprite defined in unknown mode";
		case GBM_ERR_SPR_OFFSET:
			return "less sprites in file than index requested";
		case GBM_ERR_SPR_PAL8:
			return "8 bit file does not have 0, 16 or 64 palette entries in it";
		}
	return NULL;
	}
/*...e*/
