From 1ef631e23888a0356387c4cd5cf4730c4a2fa8fa Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Wed, 13 Mar 2013 21:20:58 +0000 Subject: move librosprite to core buildsystem some fixmes exist for unbuilt tools --- .gitignore | 1 + Makefile | 88 ++--- example.c | 177 ----------- examples/example.c | 177 +++++++++++ include/librosprite.h | 185 +++++++++++ librosprite.c | 865 ------------------------------------------------- librosprite.h | 185 ----------- librosprite.pc.in | 2 +- palette2c.c | 48 --- src/Makefile | 4 + src/librosprite.c | 866 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/palette2c.c | 48 +++ 12 files changed, 1317 insertions(+), 1329 deletions(-) create mode 100644 .gitignore delete mode 100644 example.c create mode 100644 examples/example.c create mode 100644 include/librosprite.h delete mode 100644 librosprite.c delete mode 100644 librosprite.h delete mode 100644 palette2c.c create mode 100644 src/Makefile create mode 100644 src/librosprite.c create mode 100644 src/palette2c.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd07bff --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build-* diff --git a/Makefile b/Makefile index b32bace..f9e802b 100644 --- a/Makefile +++ b/Makefile @@ -1,63 +1,45 @@ -AR = ar -CC = gcc -LD = gcc -DOXYGEN = doxygen -INSTALL = install -SED = sed -MKDIR = mkdir -PKG_CONFIG = pkg-config - -ARFLAGS = -cru -CFLAGS = -g -Wall -Wextra -Wundef -Wpointer-arith -Wcast-align \ +# Component settings +COMPONENT := rosprite +COMPONENT_VERSION := 0.0.1 +# Default to a static library +COMPONENT_TYPE ?= lib-static + +# FIXME +# palette2c tool needs building and installing as binary target +# examples/example.c needs a build target + +# Setup the tooling +PREFIX ?= /opt/netsurf +NSSHARED ?= $(PREFIX)/share/netsurf-buildsystem +include $(NSSHARED)/makefiles/Makefile.tools + +# Toolchain flags +WARNFLAGS := -Wall -Wextra -Wundef -Wpointer-arith -Wcast-align \ -Wwrite-strings -Wstrict-prototypes \ - -Wnested-externs -Werror -pedantic -std=c99 \ + -Wnested-externs -pedantic -std=c99 \ -Wno-format-zero-length -Wformat-security -Wstrict-aliasing=2 \ -Wmissing-format-attribute -Wunused -Wunreachable-code \ -Wformat=2 -Werror-implicit-function-declaration \ -Wmissing-declarations -Wmissing-prototypes -LDFLAGS = -g -L./ -# Installation prefix, if not already defined (e.g. on command line) -PREFIX ?= /usr/local -DESTDIR ?= +# BeOS/Haiku standard library headers create warnings +ifneq ($(TARGET),beos) + WARNFLAGS := $(WARNFLAGS) -Werror +endif -.PHONY: all clean docs install uninstall +CFLAGS := -I$(CURDIR)/include/ $(WARNFLAGS) $(CFLAGS) -all: librosprite.a +ifneq ($(GCCVER),2) + CFLAGS := $(CFLAGS) -std=c99 +else + # __inline__ is a GCCism + CFLAGS := $(CFLAGS) -Dinline="__inline__" +endif -example: librosprite.a example.o - ${LD} -o $@ example.o ${LDFLAGS} \ - $(shell PKG_CONFIG_PATH=.:$(PKG_CONFIG_PATH) $(PKG_CONFIG) --cflags --libs sdl librosprite) +include $(NSBUILD)/Makefile.top -palette2c: librosprite.a palette2c.o - ${LD} -o $@ palette2c.o ${LDFLAGS} \ - $(shell PKG_CONFIG_PATH=.:$(PKG_CONFIG_PATH) $(PKG_CONFIG) --cflags --libs librosprite) - -librosprite.a: librosprite.o librosprite.pc - ${AR} ${ARFLAGS} librosprite.a librosprite.o - -librosprite.pc: librosprite.pc.in - $(SED) -e 's#PREFIX#$(PREFIX)#' librosprite.pc.in > librosprite.pc - -%.o: %.c - ${CC} -c ${CFLAGS} -o $@ $< - -docs: - ${DOXYGEN} - -clean: - rm -f $(wildcard *.o) $(wildcard *.a) example palette2c librosprite.pc - rm -rf doc - -install: librosprite.a librosprite.pc - $(MKDIR) -p $(DESTDIR)$(PREFIX)/lib/pkgconfig - $(MKDIR) -p $(DESTDIR)$(PREFIX)/lib - $(MKDIR) -p $(DESTDIR)$(PREFIX)/include - $(INSTALL) --mode=644 -t $(DESTDIR)$(PREFIX)/lib librosprite.a - $(INSTALL) --mode=644 -t $(DESTDIR)$(PREFIX)/include librosprite.h - $(INSTALL) --mode=644 -t $(DESTDIR)$(PREFIX)/lib/pkgconfig librosprite.pc - -uninstall: - rm $(DESTDIR)$(PREFIX)/lib/librosprite.a - rm $(DESTDIR)$(PREFIX)/include/librosprite.h - rm $(DESTDIR)$(PREFIX)/lib/pkgconfig/librosprite.pc +# Extra installation rules +I := /include +INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):include/librosprite.h +INSTALL_ITEMS := $(INSTALL_ITEMS) /lib/pkgconfig:lib$(COMPONENT).pc.in +INSTALL_ITEMS := $(INSTALL_ITEMS) /lib:$(OUTPUT) diff --git a/example.c b/example.c deleted file mode 100644 index 4f9755e..0000000 --- a/example.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - * This file is part of librosprite. - * Licensed under the MIT License, - * http://www.opensource.org/licenses/mit-license.php - * Copyright 2008 James Shaw - */ - -/** - * \file - * A test harness using SDL to display all sprites in a sprite file. - * - * Usage: example \ - */ - -#include -#include -#include - -#include "librosprite.h" - -void sdl_draw_pixel(SDL_Surface* surface, uint32_t x, uint32_t y, uint32_t color); -void sdl_blank(SDL_Surface* surface); -int load_file_to_memory(const char *filename, uint8_t **result); -int create_file_context(char* filename, void** result); -int create_mem_context(char* filename, void** result); - -int main(int argc, char *argv[]) -{ - if (argc < 2) { - fprintf(stderr, "Usage: example spritefile\n"); - exit(EXIT_FAILURE); - } - - if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { - fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError()); - exit(EXIT_FAILURE); - } - atexit(SDL_Quit); - - char* filename = argv[1]; - void* ctx; - if (create_file_context(filename, &ctx) < 0) { - fprintf(stderr, "Unable to create file reader context\n"); - exit(EXIT_FAILURE); - } - - printf("Loading %s\n", filename); - - struct rosprite_area* sprite_area; - if (rosprite_load(rosprite_file_reader, ctx, &sprite_area) != ROSPRITE_OK) { - fprintf(stderr, "Error loading spritefile\n"); - exit(EXIT_FAILURE); - }; - printf("sprite_count %u\n", sprite_area->sprite_count); - printf("extension_size %u\n", sprite_area->extension_size); - - SDL_Surface *screen; - screen = SDL_SetVideoMode(800, 600, 32, SDL_ANYFORMAT); - SDL_SetAlpha(screen, SDL_SRCALPHA, 0); - - for (unsigned int i = 0; i < sprite_area->sprite_count; i++) { - struct rosprite* sprite = sprite_area->sprites[i]; - printf("\nname %s\n", sprite->name); - printf("color_model %s\n", sprite->mode.color_model == ROSPRITE_RGB ? "RGB" : "CMYK"); - printf("colorbpp %u\n", sprite->mode.colorbpp); - printf("xdpi %u\n", sprite->mode.xdpi); - printf("ydpi %u\n", sprite->mode.ydpi); - printf("width %u px\n", sprite->width); - printf("height %u px\n", sprite->height); - - printf("hasPalette %s\n", sprite->has_palette ? "YES" : "NO"); - if (sprite->has_palette) printf("paletteSize %u\n", sprite->palettesize); - - printf("hasMask %s\n", sprite->has_mask ? "YES" : "NO"); - if (sprite->has_mask) printf("mask_width %u\n", sprite->mode.mask_width); - if (sprite->has_mask) printf("maskbpp %u\n", sprite->mode.maskbpp); - - sdl_blank(screen); - - for (uint32_t y = 0; y < sprite->height; y++) { - for (uint32_t x = 0; x < sprite->width; x++) { - sdl_draw_pixel(screen, x, y, sprite->image[y*sprite->width + x]); - } - } - - SDL_UpdateRect(screen, 0, 0, 0, 0); - fgetc(stdin); - } - - rosprite_destroy_mem_context(ctx); - rosprite_destroy_sprite_area(sprite_area); - - return EXIT_SUCCESS; -} - -int create_file_context(char* filename, void** result) -{ - FILE *f = fopen(filename, "rb"); - if (!f) { - *result = NULL; - return -1; - } - - struct rosprite_file_context* ctx; - if (rosprite_create_file_context(f, &ctx) != ROSPRITE_OK) { - return -1; - } - *result = ctx; - - return 0; -} - -int create_mem_context(char* filename, void** result) -{ - uint8_t* content; - - int size = load_file_to_memory(filename, &content); - if (size < 0) return -1; - struct rosprite_mem_context* ctx; - if (rosprite_create_mem_context(content, size, &ctx) != ROSPRITE_OK) { - return -1; - } - *result = ctx; - - return 0; -} - -int load_file_to_memory(const char *filename, uint8_t **result) -{ - int size = 0; - FILE *f = fopen(filename, "rb"); - if (f == NULL) - { - *result = NULL; - return -1; // -1 means file opening fail - } - fseek(f, 0, SEEK_END); - size = ftell(f); - fseek(f, 0, SEEK_SET); - *result = (uint8_t *)malloc(size+1); - if ((unsigned int) size != fread(*result, sizeof(char), size, f)) - { - free(*result); - return -2; // -2 means file reading fail - } - fclose(f); - (*result)[size] = 0; - return size; -} - -/* color is 0xrrggbbaa */ -void sdl_draw_pixel(SDL_Surface* surface, uint32_t x, uint32_t y, uint32_t color) -{ - uint32_t* pixel = ((uint32_t*) (surface->pixels)) + (y * surface->pitch/4) + x; - /* pretty sure SDL can do this, but can't figure out how */ - uint32_t bg_color = ((int) (x / 4.0) + ((int)(y / 4.0) % 2)) % 2 ? 0x99 : 0x66; - - uint32_t alpha = color & 0x000000ff; - uint32_t r = (color & 0xff000000) >> 24; - uint32_t g = (color & 0x00ff0000) >> 16; - uint32_t b = (color & 0x0000ff00) >> 8; - r = ((alpha / 255.0) * r) + (((255-alpha) / 255.0) * bg_color); - g = ((alpha / 255.0) * g) + (((255-alpha) / 255.0) * bg_color); - b = ((alpha / 255.0) * b) + (((255-alpha) / 255.0) * bg_color); - uint32_t mapped_color = SDL_MapRGB(surface->format, r, g, b); - - *pixel = mapped_color; -} - -void sdl_blank(SDL_Surface* surface) -{ - for (uint32_t y = 0; y < (uint32_t) surface->h; y++) { - for (uint32_t x = 0; x < (uint32_t) surface->w; x++) { - sdl_draw_pixel(surface, x, y, (uint32_t) ((int) (x / 4.0) + ((int)(y / 4.0) % 2)) % 2 ? 0x999999ff : 0x666666ff); - } - } -} diff --git a/examples/example.c b/examples/example.c new file mode 100644 index 0000000..4f9755e --- /dev/null +++ b/examples/example.c @@ -0,0 +1,177 @@ +/* + * This file is part of librosprite. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2008 James Shaw + */ + +/** + * \file + * A test harness using SDL to display all sprites in a sprite file. + * + * Usage: example \ + */ + +#include +#include +#include + +#include "librosprite.h" + +void sdl_draw_pixel(SDL_Surface* surface, uint32_t x, uint32_t y, uint32_t color); +void sdl_blank(SDL_Surface* surface); +int load_file_to_memory(const char *filename, uint8_t **result); +int create_file_context(char* filename, void** result); +int create_mem_context(char* filename, void** result); + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + fprintf(stderr, "Usage: example spritefile\n"); + exit(EXIT_FAILURE); + } + + if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { + fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } + atexit(SDL_Quit); + + char* filename = argv[1]; + void* ctx; + if (create_file_context(filename, &ctx) < 0) { + fprintf(stderr, "Unable to create file reader context\n"); + exit(EXIT_FAILURE); + } + + printf("Loading %s\n", filename); + + struct rosprite_area* sprite_area; + if (rosprite_load(rosprite_file_reader, ctx, &sprite_area) != ROSPRITE_OK) { + fprintf(stderr, "Error loading spritefile\n"); + exit(EXIT_FAILURE); + }; + printf("sprite_count %u\n", sprite_area->sprite_count); + printf("extension_size %u\n", sprite_area->extension_size); + + SDL_Surface *screen; + screen = SDL_SetVideoMode(800, 600, 32, SDL_ANYFORMAT); + SDL_SetAlpha(screen, SDL_SRCALPHA, 0); + + for (unsigned int i = 0; i < sprite_area->sprite_count; i++) { + struct rosprite* sprite = sprite_area->sprites[i]; + printf("\nname %s\n", sprite->name); + printf("color_model %s\n", sprite->mode.color_model == ROSPRITE_RGB ? "RGB" : "CMYK"); + printf("colorbpp %u\n", sprite->mode.colorbpp); + printf("xdpi %u\n", sprite->mode.xdpi); + printf("ydpi %u\n", sprite->mode.ydpi); + printf("width %u px\n", sprite->width); + printf("height %u px\n", sprite->height); + + printf("hasPalette %s\n", sprite->has_palette ? "YES" : "NO"); + if (sprite->has_palette) printf("paletteSize %u\n", sprite->palettesize); + + printf("hasMask %s\n", sprite->has_mask ? "YES" : "NO"); + if (sprite->has_mask) printf("mask_width %u\n", sprite->mode.mask_width); + if (sprite->has_mask) printf("maskbpp %u\n", sprite->mode.maskbpp); + + sdl_blank(screen); + + for (uint32_t y = 0; y < sprite->height; y++) { + for (uint32_t x = 0; x < sprite->width; x++) { + sdl_draw_pixel(screen, x, y, sprite->image[y*sprite->width + x]); + } + } + + SDL_UpdateRect(screen, 0, 0, 0, 0); + fgetc(stdin); + } + + rosprite_destroy_mem_context(ctx); + rosprite_destroy_sprite_area(sprite_area); + + return EXIT_SUCCESS; +} + +int create_file_context(char* filename, void** result) +{ + FILE *f = fopen(filename, "rb"); + if (!f) { + *result = NULL; + return -1; + } + + struct rosprite_file_context* ctx; + if (rosprite_create_file_context(f, &ctx) != ROSPRITE_OK) { + return -1; + } + *result = ctx; + + return 0; +} + +int create_mem_context(char* filename, void** result) +{ + uint8_t* content; + + int size = load_file_to_memory(filename, &content); + if (size < 0) return -1; + struct rosprite_mem_context* ctx; + if (rosprite_create_mem_context(content, size, &ctx) != ROSPRITE_OK) { + return -1; + } + *result = ctx; + + return 0; +} + +int load_file_to_memory(const char *filename, uint8_t **result) +{ + int size = 0; + FILE *f = fopen(filename, "rb"); + if (f == NULL) + { + *result = NULL; + return -1; // -1 means file opening fail + } + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + *result = (uint8_t *)malloc(size+1); + if ((unsigned int) size != fread(*result, sizeof(char), size, f)) + { + free(*result); + return -2; // -2 means file reading fail + } + fclose(f); + (*result)[size] = 0; + return size; +} + +/* color is 0xrrggbbaa */ +void sdl_draw_pixel(SDL_Surface* surface, uint32_t x, uint32_t y, uint32_t color) +{ + uint32_t* pixel = ((uint32_t*) (surface->pixels)) + (y * surface->pitch/4) + x; + /* pretty sure SDL can do this, but can't figure out how */ + uint32_t bg_color = ((int) (x / 4.0) + ((int)(y / 4.0) % 2)) % 2 ? 0x99 : 0x66; + + uint32_t alpha = color & 0x000000ff; + uint32_t r = (color & 0xff000000) >> 24; + uint32_t g = (color & 0x00ff0000) >> 16; + uint32_t b = (color & 0x0000ff00) >> 8; + r = ((alpha / 255.0) * r) + (((255-alpha) / 255.0) * bg_color); + g = ((alpha / 255.0) * g) + (((255-alpha) / 255.0) * bg_color); + b = ((alpha / 255.0) * b) + (((255-alpha) / 255.0) * bg_color); + uint32_t mapped_color = SDL_MapRGB(surface->format, r, g, b); + + *pixel = mapped_color; +} + +void sdl_blank(SDL_Surface* surface) +{ + for (uint32_t y = 0; y < (uint32_t) surface->h; y++) { + for (uint32_t x = 0; x < (uint32_t) surface->w; x++) { + sdl_draw_pixel(surface, x, y, (uint32_t) ((int) (x / 4.0) + ((int)(y / 4.0) % 2)) % 2 ? 0x999999ff : 0x666666ff); + } + } +} diff --git a/include/librosprite.h b/include/librosprite.h new file mode 100644 index 0000000..f54eaf2 --- /dev/null +++ b/include/librosprite.h @@ -0,0 +1,185 @@ +/* + * This file is part of librosprite. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2008 James Shaw + */ + +/** + * \mainpage + * + * librosprite is a library for reading RISC OS sprite and palette files. The following subformats are supported: + *
    + *
  • 1-bit and 8-bit transparency masks
  • + *
  • Sprites with custom palettes
  • + *
  • Standard RISC OS 1-bit, 2-bit palettes, and 4-bit and 8-bit colour palettes
  • + *
  • Old-style sprites with most screen modes from 0-49 supported
  • + *
  • 32bpp CMYK
  • + *
  • Inline alpha channel, as used by Photodesk and Tinct
  • + *
+ * + */ + +/** + * \file librosprite.h + * + * Sprite file reading is performed by rosprite_load(), and palette file reading by rosprite_load_palette(). + * + * Retrieving sprite or palette data is performed by a reader. + * librosprite implements file and memory readers. + * To use a reader, create a context by calling the + * rosprite_create_file_context() or rosprite_create_mem_context(). + * Pass the reader function, and the context you have created, to the load function, + * typically rosprite_load(). + */ + +#ifndef ROSPRITE_H +#define ROSPRITE_H + +#include +#include +#include + +typedef enum { ROSPRITE_OK, ROSPRITE_NOMEM, ROSPRITE_EOF, ROSPRITE_BADMODE } rosprite_error; + +typedef enum { ROSPRITE_RGB, ROSPRITE_CMYK } rosprite_color_model; + +/** + * A reader interface used to load sprite files. + */ +typedef int (*reader)(uint8_t* buf, size_t count, void* ctx); + +struct rosprite_file_context; + +/** + * A sprite area comprises zero or more rosprites. Optionally, it may also have an extension_words block. + */ +struct rosprite_area { + uint32_t extension_size; /* size of extension_words in bytes */ + uint8_t* extension_words; + uint32_t sprite_count; + struct rosprite** sprites; /* array of length sprite_count */ +}; + +/** + * A sprite mode defines the colour depth, colour model and bitmap resolution of a sprite. + */ +struct rosprite_mode { + /** + * The bits per colour channel. Legal values are 1, 2, 4, 8, 16, 24 and 32. + */ + uint32_t colorbpp; + /* maskbpp denotes the amount of alpha bpp used + * while mask_width is the bits used to store the mask. + * Old modes have the same mask_width as their colorbpp, but the value + * is always all-zeroes or all-ones. + * New modes can have 1bpp or 8bpp masks + */ + uint32_t maskbpp; + uint32_t mask_width; /* in pixels */ + + /** + * Horizontal dots per inch. Typical values are 22, 45, 90 and 180. + */ + uint32_t xdpi; + + /** + * Vertical dots per inch. Typical values are 22, 45, 90 and 180. + */ + uint32_t ydpi; + rosprite_color_model color_model; +}; + +struct rosprite_palette { + uint32_t size; /* in number of entries (each entry is a word) */ + uint32_t* palette; +}; + +/** + * A sprite is a bitmap image which has a mode, width and height. + */ +struct rosprite { + + /** + * The sprite name. This may be up to 12 characters long, and must be zero terminated. + */ + unsigned char name[13]; + struct rosprite_mode mode; + bool has_mask; + bool has_palette; + uint32_t palettesize; /* in number of entries (each entry is a word) */ + uint32_t* palette; + + /** + * Width in pixels + */ + uint32_t width; + + /** + * Height in pixels + */ + uint32_t height; + + /** + * Image data is a series of words, appearing on screen left-to-right, top-to-bottom. + * Each word takes the form 0xRRGGBBAA. A is the alpha channel, where 0 is transparent, and 255 is opaque. + */ + uint32_t* image; /* image data in 0xRRGGBBAA words */ +}; + +struct rosprite_file_context; + +/** + * Create a file reader context using the specified file handle. + * Clients must call rosprite_destroy_file_context() to dispose of the context. + * + * \param[out] result + */ +rosprite_error rosprite_create_file_context(FILE* f, struct rosprite_file_context** result); +void rosprite_destroy_file_context(struct rosprite_file_context* ctx); +int rosprite_file_reader(uint8_t* buf, size_t count, void* ctx); + +struct rosprite_mem_context; + +/** + * Create a memory reader context using the specified memory pointer. + * Clients must call rosprite_destroy_mem_context() to dispose of the context. + * + * \param[in] p pointer to the start of the memory block + * \param[in] total_size the size of the block pointed to by p, in bytes + * \param[out] result + */ +rosprite_error rosprite_create_mem_context(uint8_t* p, unsigned long total_size, struct rosprite_mem_context** result); +void rosprite_destroy_mem_context(struct rosprite_mem_context* ctx); +int rosprite_mem_reader(uint8_t* buf, size_t count, void* ctx); + +/** + * Load a rosprite_area using the reader provided. + * Clients must call rosprite_destroy_sprite_area() to dispose of the rosprite_area. + * + * \param[out] result The pointer to be populated by this function. + */ +rosprite_error rosprite_load(reader reader, void* ctx, struct rosprite_area** result); + +/** + * Dispose of a rosprite_area and its children. + */ +void rosprite_destroy_sprite_area(struct rosprite_area *); + +/** + * Load a RISC OS palette file. A palette file has RISC OS filetype 0xFED, + * and is a series of VDU 19 Set Palette commands, each command being 6 bytes long. + * + * Clients must call rosprite_destroy_palette() to dispose of the rosprite_palette. + * + * \param[out] result The pointer to be populated by this function. + * \see http://www.drobe.co.uk/show_manual.php?manual=/sh-cgi?manual=Vdu%26page=19 + */ +rosprite_error rosprite_load_palette(reader reader, void* ctx, struct rosprite_palette** result); + +/** + * Dispose of a rosprite_palette and its children. + */ +void rosprite_destroy_palette(struct rosprite_palette *); + +#endif diff --git a/librosprite.c b/librosprite.c deleted file mode 100644 index 04498fe..0000000 --- a/librosprite.c +++ /dev/null @@ -1,865 +0,0 @@ -/* - * This file is part of librosprite. - * Licensed under the MIT License, - * http://www.opensource.org/licenses/mit-license.php - * Copyright 2008 James Shaw - */ - -/** - * \file - */ - -#include -#include -#include -#include -#include -#include - -#include "librosprite.h" - -/** - * Reads four bytes, 00, 11, 22 and 33, of a byte array b to give 0x33221100. - */ -#define BTUINT(b) (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) - -/** - * Reverse the byte order of a word such that 0xAABBCCDD becomes 0xDDCCBBAA. - */ -#define BSWAP(word) (((word & (0x000000ff)) << 24) | ((word & 0x0000ff00) << 8) | ((word & 0x00ff0000) >> 8) | ((word & 0xff000000) >> 24)) - -#define ERRCHK(x) do { \ - rosprite_error err = x; \ - if (err != ROSPRITE_OK) return err; \ -} while(0) - -struct rosprite_header { - uint32_t width_words; /* width in words */ - /* height defined in sprite struct */ - uint32_t first_used_bit; /* old format only (spriteType = 0) */ - uint32_t last_used_bit; - - /** - * Image size in bytes - */ - uint32_t image_size; - - /** - * Mask size in bytes - */ - uint32_t mask_size; -}; - -struct rosprite_mask_state { - uint32_t x; - uint32_t y; - uint32_t first_used_bit; - uint32_t row_max_bit; - uint32_t height; - uint32_t current_byte_index; - uint32_t current_word; - uint32_t bpp; -}; - -struct rosprite_file_context { - FILE* f; -}; - -struct rosprite_mem_context { - uint8_t* base; - unsigned long offset; - bool known_size; - unsigned long size; -}; - -static const struct rosprite_mode oldmodes[] = { -/*0*/{ .colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*1*/{ .colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*2*/{ .colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 22, .ydpi = 45, .color_model = ROSPRITE_RGB }, - -/*3*/{ .colorbpp = 0, .maskbpp = 0, .mask_width = 0, .xdpi = 0, .ydpi = 0, .color_model = ROSPRITE_RGB }, - -/*4*/{ .colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*5*/{ .colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 22, .ydpi = 45, .color_model = ROSPRITE_RGB }, - -/*6*/{ .colorbpp = 0, .maskbpp = 0, .mask_width = 0, .xdpi = 0, .ydpi = 0, .color_model = ROSPRITE_RGB }, -/*7*/{ .colorbpp = 0, .maskbpp = 0, .mask_width = 0, .xdpi = 0, .ydpi = 0, .color_model = ROSPRITE_RGB }, - -/*8*/{ .colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*9*/{ .colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*10*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 22, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*11*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*12*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*13*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*14*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*15*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*16*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*17*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*18*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*19*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*20*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*21*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*22*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi =180, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*23*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*24*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*25*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*26*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*27*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*28*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*29*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*30*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*31*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*32*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*33*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*34*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*35*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*36*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*37*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*38*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*39*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*40*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*41*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*42*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*43*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*44*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*45*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*46*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*47*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, -/*48*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 45, .ydpi = 90, .color_model = ROSPRITE_RGB }, -/*49*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 45, .ydpi = 90, .color_model = ROSPRITE_RGB } -}; - -/* table for converting a 5bit channel into an 8bit channel (used for 16bpp to 32bpp conversion) */ -static const uint8_t sprite_16bpp_translate[] = { - 0x00, 0x08, 0x10, 0x18, 0x20, 0x29, 0x31, 0x39, - 0x41, 0x4a, 0x52, 0x5a, 0x62, 0x6a, 0x73, 0x7b, - 0x83, 0x8b, 0x94, 0x9c, 0xa4, 0xac, 0xb4, 0xbd, - 0xc5, 0xcd, 0xd5, 0xde, 0xe6, 0xee, 0xf6, 0xff -}; - -/* palettes generated with palette2c.c - * which in turn requires rosprite_load_palette(FILE* f) - * defined in this file - */ -static const uint32_t sprite_1bpp_palette[] = { 0xffffffff, 0xff }; - -static const uint32_t sprite_2bpp_palette[] = { 0xffffffff, 0xbbbbbbff, 0x777777ff, 0xff }; - -static const uint32_t sprite_4bpp_palette[] = { -0xffffffff, 0xddddddff, 0xbbbbbbff, 0x999999ff, -0x777777ff, 0x555555ff, 0x333333ff, 0xff, -0x4499ff, 0xeeee00ff, 0xcc00ff, 0xdd0000ff, -0xeeeebbff, 0x558800ff, 0xffbb00ff, 0xbbffff -}; - -static const uint32_t sprite_8bpp_palette[] = { -0xff, 0x111111ff, 0x222222ff, 0x333333ff, -0x440000ff, 0x551111ff, 0x662222ff, 0x773333ff, -0x44ff, 0x111155ff, 0x222266ff, 0x333377ff, -0x440044ff, 0x551155ff, 0x662266ff, 0x773377ff, -0x880000ff, 0x991111ff, 0xaa2222ff, 0xbb3333ff, -0xcc0000ff, 0xdd1111ff, 0xee2222ff, 0xff3333ff, -0x880044ff, 0x991155ff, 0xaa2266ff, 0xbb3377ff, -0xcc0044ff, 0xdd1155ff, 0xee2266ff, 0xff3377ff, -0x4400ff, 0x115511ff, 0x226622ff, 0x337733ff, -0x444400ff, 0x555511ff, 0x666622ff, 0x777733ff, -0x4444ff, 0x115555ff, 0x226666ff, 0x337777ff, -0x444444ff, 0x555555ff, 0x666666ff, 0x777777ff, -0x884400ff, 0x995511ff, 0xaa6622ff, 0xbb7733ff, -0xcc4400ff, 0xdd5511ff, 0xee6622ff, 0xff7733ff, -0x884444ff, 0x995555ff, 0xaa6666ff, 0xbb7777ff, -0xcc4444ff, 0xdd5555ff, 0xee6666ff, 0xff7777ff, -0x8800ff, 0x119911ff, 0x22aa22ff, 0x33bb33ff, -0x448800ff, 0x559911ff, 0x66aa22ff, 0x77bb33ff, -0x8844ff, 0x119955ff, 0x22aa66ff, 0x33bb77ff, -0x448844ff, 0x559955ff, 0x66aa66ff, 0x77bb77ff, -0x888800ff, 0x999911ff, 0xaaaa22ff, 0xbbbb33ff, -0xcc8800ff, 0xdd9911ff, 0xeeaa22ff, 0xffbb33ff, -0x888844ff, 0x999955ff, 0xaaaa66ff, 0xbbbb77ff, -0xcc8844ff, 0xdd9955ff, 0xeeaa66ff, 0xffbb77ff, -0xcc00ff, 0x11dd11ff, 0x22ee22ff, 0x33ff33ff, -0x44cc00ff, 0x55dd11ff, 0x66ee22ff, 0x77ff33ff, -0xcc44ff, 0x11dd55ff, 0x22ee66ff, 0x33ff77ff, -0x44cc44ff, 0x55dd55ff, 0x66ee66ff, 0x77ff77ff, -0x88cc00ff, 0x99dd11ff, 0xaaee22ff, 0xbbff33ff, -0xcccc00ff, 0xdddd11ff, 0xeeee22ff, 0xffff33ff, -0x88cc44ff, 0x99dd55ff, 0xaaee66ff, 0xbbff77ff, -0xcccc44ff, 0xdddd55ff, 0xeeee66ff, 0xffff77ff, -0x88ff, 0x111199ff, 0x2222aaff, 0x3333bbff, -0x440088ff, 0x551199ff, 0x6622aaff, 0x7733bbff, -0xccff, 0x1111ddff, 0x2222eeff, 0x3333ffff, -0x4400ccff, 0x5511ddff, 0x6622eeff, 0x7733ffff, -0x880088ff, 0x991199ff, 0xaa22aaff, 0xbb33bbff, -0xcc0088ff, 0xdd1199ff, 0xee22aaff, 0xff33bbff, -0x8800ccff, 0x9911ddff, 0xaa22eeff, 0xbb33ffff, -0xcc00ccff, 0xdd11ddff, 0xee22eeff, 0xff33ffff, -0x4488ff, 0x115599ff, 0x2266aaff, 0x3377bbff, -0x444488ff, 0x555599ff, 0x6666aaff, 0x7777bbff, -0x44ccff, 0x1155ddff, 0x2266eeff, 0x3377ffff, -0x4444ccff, 0x5555ddff, 0x6666eeff, 0x7777ffff, -0x884488ff, 0x995599ff, 0xaa66aaff, 0xbb77bbff, -0xcc4488ff, 0xdd5599ff, 0xee66aaff, 0xff77bbff, -0x8844ccff, 0x9955ddff, 0xaa66eeff, 0xbb77ffff, -0xcc44ccff, 0xdd55ddff, 0xee66eeff, 0xff77ffff, -0x8888ff, 0x119999ff, 0x22aaaaff, 0x33bbbbff, -0x448888ff, 0x559999ff, 0x66aaaaff, 0x77bbbbff, -0x88ccff, 0x1199ddff, 0x22aaeeff, 0x33bbffff, -0x4488ccff, 0x5599ddff, 0x66aaeeff, 0x77bbffff, -0x888888ff, 0x999999ff, 0xaaaaaaff, 0xbbbbbbff, -0xcc8888ff, 0xdd9999ff, 0xeeaaaaff, 0xffbbbbff, -0x8888ccff, 0x9999ddff, 0xaaaaeeff, 0xbbbbffff, -0xcc88ccff, 0xdd99ddff, 0xeeaaeeff, 0xffbbffff, -0xcc88ff, 0x11dd99ff, 0x22eeaaff, 0x33ffbbff, -0x44cc88ff, 0x55dd99ff, 0x66eeaaff, 0x77ffbbff, -0xccccff, 0x11ddddff, 0x22eeeeff, 0x33ffffff, -0x44ccccff, 0x55ddddff, 0x66eeeeff, 0x77ffffff, -0x88cc88ff, 0x99dd99ff, 0xaaeeaaff, 0xbbffbbff, -0xcccc88ff, 0xdddd99ff, 0xeeeeaaff, 0xffffbbff, -0x88ccccff, 0x99ddddff, 0xaaeeeeff, 0xbbffffff, -0xccccccff, 0xddddddff, 0xeeeeeeff, 0xffffffff -}; - -static inline rosprite_error rosprite_read_word(reader reader, void* ctx, uint32_t* result); -static rosprite_error rosprite_get_mode(uint32_t spriteMode, struct rosprite_mode* result); -static uint32_t rosprite_palette_lookup(struct rosprite* sprite, uint32_t pixel); -static inline uint32_t rosprite_cmyk_to_rgb(uint32_t cmyk); -static uint32_t rosprite_next_mask_pixel(uint8_t* mask, struct rosprite_mask_state* mask_state); -static rosprite_error rosprite_load_high_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header); -static rosprite_error rosprite_load_low_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header); -rosprite_error rosprite_load_sprite(reader reader, void* ctx, struct rosprite** result); -static rosprite_error rosprite_init_mask_state(struct rosprite* sprite, struct rosprite_header* header, uint8_t* mask, struct rosprite_mask_state** result); -static uint32_t rosprite_upscale_color(uint32_t pixel, struct rosprite_mode* mode, bool* has_alpha_pixel_data); -static inline void rosprite_fix_alpha(uint32_t* image, unsigned long pixels); - -rosprite_error rosprite_load(reader reader, void* ctx, struct rosprite_area** result) -{ - uint32_t firstSpriteOffset, firstFreeWordOffset; - int bytes_read; - uint32_t i; - - struct rosprite_area* sprite_area = malloc(sizeof(struct rosprite_area)); - - ERRCHK(rosprite_read_word(reader, ctx, &(sprite_area->sprite_count))); - - ERRCHK(rosprite_read_word(reader, ctx, &firstSpriteOffset)); - ERRCHK(rosprite_read_word(reader, ctx, &firstFreeWordOffset)); /* TODO: use this for some sanity checking? */ - sprite_area->extension_size = 16 - firstSpriteOffset; - - sprite_area->extension_words = NULL; - if (sprite_area->extension_size > 0) { - sprite_area->extension_words = malloc(sprite_area->extension_size); - bytes_read = reader(sprite_area->extension_words, (size_t) (sprite_area->extension_size), ctx); - if (bytes_read < (signed long) sprite_area->extension_size) { - return ROSPRITE_EOF; - } - } - - sprite_area->sprites = malloc(sizeof(struct rosprite*) * sprite_area->sprite_count); /* allocate array of pointers */ - for (i = 0; i < sprite_area->sprite_count; i++) { - struct rosprite* sprite; - ERRCHK(rosprite_load_sprite(reader, ctx, &sprite)); - sprite_area->sprites[i] = sprite; - } - - *result = sprite_area; - - return ROSPRITE_OK; -} - -void rosprite_destroy_sprite_area(struct rosprite_area* sprite_area) -{ - uint32_t i; - for (i = 0; i < sprite_area->sprite_count; i++) { - struct rosprite* sprite = sprite_area->sprites[i]; - if (sprite->has_palette) free(sprite->palette); - free(sprite->image); - free(sprite); - } - - free(sprite_area->sprites); - if (sprite_area->extension_size > 0) free(sprite_area->extension_words); - free(sprite_area); -} - -rosprite_error rosprite_load_palette(reader reader, void* ctx, struct rosprite_palette** result) -{ - uint32_t c; - uint8_t b[6]; - unsigned int bytesRead; - - /* TODO: currently assume palette has linear entries (2nd byte in is 00, 01, 02 etc) */ - struct rosprite_palette* palette = malloc(sizeof(struct rosprite_palette)); - - palette->palette = malloc(sizeof(uint32_t) * 256); /* allocate 256 whether we need them all or not */ - - c = 0; - - bytesRead = reader(b, 6, ctx); - assert(bytesRead % 6 == 0); - while (bytesRead == 6) { - assert(b[0] == 19); /* VDU 19 */ - - /* only process logical colours */ - if (b[2] == 16) { - /*assert(c == b[1]);*/ - - uint32_t entry = (b[3] << 24) | (b[4] << 16) | (b[5] << 8) | 0xff; /* last byte is alpha */ - palette->palette[c] = entry; - - c++; - assert(c <= 256); - } - - bytesRead = reader(b, 6, ctx); - assert(bytesRead % 6 == 0); - } - - palette->size = c; - - *result = palette; - - return ROSPRITE_OK; -} - -void rosprite_destroy_palette(struct rosprite_palette* palette) -{ - free(palette->palette); - free(palette); -} - -rosprite_error rosprite_create_file_context(FILE* f, struct rosprite_file_context** result) -{ - struct rosprite_file_context* ctx = malloc(sizeof(struct rosprite_file_context)); - if (!ctx) return ROSPRITE_NOMEM; - ctx->f = f; - *result = ctx; - - return ROSPRITE_OK; -} - -void rosprite_destroy_file_context(struct rosprite_file_context* ctx) -{ - free(ctx); -} - -int rosprite_file_reader(uint8_t* buf, size_t count, void* ctx) -{ - return fread(buf, 1 /*size*/, count, ((struct rosprite_file_context*) ctx)->f); -} - -rosprite_error rosprite_create_mem_context(uint8_t* p, unsigned long total_size, struct rosprite_mem_context** result) -{ - struct rosprite_mem_context* ctx = malloc(sizeof(struct rosprite_mem_context)); - if (!ctx) return ROSPRITE_NOMEM; - - ctx->base = p; - ctx->offset = 0; - ctx->size = total_size; - *result = ctx; - - return ROSPRITE_OK; -} - -void rosprite_destroy_mem_context(struct rosprite_mem_context* ctx) -{ - free(ctx); -} - -int rosprite_mem_reader(uint8_t* buf, size_t count, void* ctx) -{ - size_t copy_size; - struct rosprite_mem_context* memctx = (struct rosprite_mem_context*) ctx; - if (memctx->offset + count > memctx->size) { - return -1; - } - - // if we're asked for more memory than the block contains, only copy as much as we can - if ((memctx->offset + count) > memctx->size) { - copy_size = memctx->size - memctx->offset; - } else { - copy_size = count; - } - memcpy(buf, memctx->base + memctx->offset, count); - memctx->offset += count; - return copy_size; -} - -/** - * Load a single sprite. - * - * \param[out] result - */ -rosprite_error rosprite_load_sprite(reader reader, void* ctx, struct rosprite** result) -{ - uint32_t nextSpriteOffset; - uint32_t imageOffset; - uint32_t maskOffset, spriteModeWord; - uint32_t paletteEntries; - uint8_t* image; - uint8_t* mask = NULL; - - struct rosprite* sprite = malloc(sizeof(struct rosprite)); - struct rosprite_header* header = malloc(sizeof(struct rosprite_header)); - - ERRCHK(rosprite_read_word(reader, ctx, &nextSpriteOffset)); - - reader(sprite->name, 12, ctx); - sprite->name[12] = '\0'; - - ERRCHK(rosprite_read_word(reader, ctx, &header->width_words)); /* file has width - 1 and height - 1 */ - header->width_words += 1; - ERRCHK(rosprite_read_word(reader, ctx, &(sprite->height))); - sprite->height += 1; - ERRCHK(rosprite_read_word(reader, ctx, &(header->first_used_bit))); /* old format only (spriteType = 0) */ - ERRCHK(rosprite_read_word(reader, ctx, &(header->last_used_bit))); - - ERRCHK(rosprite_read_word(reader, ctx, &imageOffset)); - assert(imageOffset >= 44); /* should never be smaller than the size of the header) */ - - ERRCHK(rosprite_read_word(reader, ctx, &maskOffset)); - ERRCHK(rosprite_read_word(reader, ctx, &spriteModeWord)); - - ERRCHK(rosprite_get_mode(spriteModeWord, &(sprite->mode))); - - /* TODO left-hand wastage */ - - assert((header->last_used_bit + 1) % sprite->mode.colorbpp == 0); - /*assert(header->width_words % sprite->mode->colorbpp == 0);*/ - sprite->width = (header->width_words * 32 - header->first_used_bit - (31 - header->last_used_bit)) / sprite->mode.colorbpp; - - sprite->palettesize = imageOffset - 44; - sprite->has_palette = (sprite->palettesize > 0); - - /* sprite has no mask if imageOffset == maskOffset */ - if (imageOffset == maskOffset) { - sprite->has_mask = false; - header->image_size = nextSpriteOffset - 44 - sprite->palettesize; - header->mask_size = 0; - } else { - sprite->has_mask = true; - header->image_size = maskOffset - imageOffset; - header->mask_size = nextSpriteOffset - 44 - sprite->palettesize - header->image_size; - } - - if (sprite->has_palette) { - uint32_t j, word1, word2, entry; - assert(sprite->palettesize % 8 == 0); - sprite->palette = malloc(sizeof(uint32_t) * sprite->palettesize); - paletteEntries = sprite->palettesize / 8; - - /* Each palette entry is two words big - * The second word is a duplicate of the first - * I think this is in case you ever wanted flashing colours - * PRM1-730 - */ - for (j = 0; j < paletteEntries; j++) { - ERRCHK(rosprite_read_word(reader, ctx, &word1)); - ERRCHK(rosprite_read_word(reader, ctx, &word2)); - assert(word1 == word2); /* if they aren't equal, flashing colours are desired, which we don't support */ - - /* swap rr and bb parts -- PRM1-731 */ - entry = ((word1 & 0xff000000) >> 16) | (word1 & 0x00ff0000) | ((word1 & 0x0000ff00) << 16) | 0xff; - sprite->palette[j] = entry; - } - } - - image = malloc(header->image_size); - reader(image, header->image_size, ctx); - - if (sprite->has_mask) { - mask = malloc(header->mask_size); - reader(mask, header->mask_size, ctx); - } - - /* sanity check image_size */ - assert((header->width_words) * 4 * (sprite->height) == header->image_size); - /* TODO: sanity check mask_size */ - if (sprite->mode.colorbpp > 8) { - ERRCHK(rosprite_load_high_color(image, mask, sprite, header)); - } else { - ERRCHK(rosprite_load_low_color(image, mask, sprite, header)); - } - - free(image); - free(mask); - free(header); - - *result = sprite; - - return ROSPRITE_OK; -} - -/** - * Determine the sprite_mode for the specified sprite_mode_word. - * - * \param[out] result - */ -static rosprite_error rosprite_get_mode(uint32_t sprite_mode_word, struct rosprite_mode* result) -{ - struct rosprite_mode mode; - - uint32_t spriteType = (sprite_mode_word & (15 << 27)) >> 27; /* preserve bits 27-30 only */ - - if (spriteType != 0) { - bool hasEightBitAlpha = sprite_mode_word >> 31; /* bit 31 */ - /* new modes have 1bpp masks (PRM5a-111) - * unless bit 31 is set (http://select.riscos.com/prm/graphics/sprites/alphachannel.html) - */ - mode.maskbpp = (hasEightBitAlpha ? 8 : 1); - mode.mask_width = mode.maskbpp; - mode.xdpi = (sprite_mode_word & (8191 << 14)) >> 14; /* preserve bits 14-26 only */ - mode.ydpi = (sprite_mode_word & (8191 << 1)) >> 1; /* preserve bits 1-13 only */ - - mode.color_model = ROSPRITE_RGB; - switch (spriteType) { - case 1: - mode.colorbpp = 1; break; - case 2: - mode.colorbpp = 2; break; - case 3: - mode.colorbpp = 4; break; - case 4: - mode.colorbpp = 8; break; - case 5: - mode.colorbpp = 16; break; - case 6: - mode.colorbpp = 32; break; - case 7: - mode.colorbpp = 32; - mode.color_model = ROSPRITE_CMYK; - break; - case 8: - mode.colorbpp = 24; break; - default: - return ROSPRITE_BADMODE; - } - } else { - assert(sprite_mode_word < 256); /* don't think you can have modes over 255? */ - - mode = oldmodes[sprite_mode_word]; - } - - /* illegal mode check */ - if ((mode.colorbpp == 0) || (mode.xdpi == 0) || (mode.ydpi == 0)) { - return ROSPRITE_BADMODE; - } - - memcpy(result, &mode, sizeof(struct rosprite_mode)); - - return ROSPRITE_OK; -} - -/** - * Load a sprite image with 16 or more bpp. - * - * \param[out] sprite On exit, sprite.image will be populated - */ -static rosprite_error rosprite_load_high_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) -{ - struct rosprite_mask_state* mask_state = NULL; - uint32_t currentByteIndex = 0; - uint32_t j, x, y, x_pixels, pixel; - bool has_alpha_pixel_data = false; - uint8_t b; - bool old_has_alpha; - - if (sprite->has_mask) { - ERRCHK(rosprite_init_mask_state(sprite, header, mask, &mask_state)); - } - - sprite->image = malloc(sprite->width * sprite->height * 4); /* all image data is 32bpp going out */ - - /* Spec says that there must be no left-hand wastage */ - assert(header->first_used_bit == 0); - - { - const uint32_t bpp = sprite->mode.colorbpp; - const uint32_t bytesPerPixel = bpp / 8; - const uint32_t row_max_bit = header->width_words * 32 - (31 - header->last_used_bit); /* Last used bit in row */ - - for (y = 0; y < sprite->height; y++) { - x_pixels = 0; - for (x = 0; x < row_max_bit; x += bpp) { - pixel = 0; - for (j = 0; j < bytesPerPixel; j++) { - b = image_in[currentByteIndex++]; - pixel = pixel | (b << (j * 8)); - } - - old_has_alpha = has_alpha_pixel_data; - pixel = rosprite_upscale_color(pixel, &(sprite->mode), &has_alpha_pixel_data); - if (old_has_alpha != has_alpha_pixel_data && (y > 0 || x_pixels > 0)) { - rosprite_fix_alpha(sprite->image, (y * sprite->width) + x_pixels - 1); - } - if (sprite->has_mask) { - uint8_t mask_pixel = rosprite_next_mask_pixel(mask, mask_state); - pixel = (pixel & 0xffffff00) | mask_pixel; - } - sprite->image[y*sprite->width + x_pixels] = pixel; - x_pixels++; - } - - /* Ensure byte index is pointing at start of next row */ - if (y + 1 < sprite->height) { - currentByteIndex = (currentByteIndex + 3) & ~3; /* Round up to next multiple of 4 */ - } - } - } - if (sprite->has_mask) free(mask_state); - return ROSPRITE_OK; -} - -/** - * Iterate over the specified number of pixels, starting at the image pointer, - * and set the alpha channel to 0x00. - */ -static inline void rosprite_fix_alpha(uint32_t* image, unsigned long pixels) -{ - uint32_t i; - for (i = 0; i <= pixels; i++) { - image[i] = image[i] & 0xffffff00; - } -} - -/** - * Load a sprite image with 8 or fewer bpp. - * - * \param[out] sprite On exit, sprite.image will be populated - */ -static rosprite_error rosprite_load_low_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) -{ - uint32_t current_byte_index, currentword; - uint32_t x, y, x_pixels, pixel; - uint8_t mask_pixel; - - struct rosprite_mask_state* mask_state = NULL; - if (sprite->has_mask) { - ERRCHK(rosprite_init_mask_state(sprite, header, mask, &mask_state)); - } - - sprite->image = malloc(sprite->width * sprite->height * 4); /* all image data is 32bpp going out */ - - { - const uint32_t bpp = sprite->mode.colorbpp; - const uint32_t row_max_bit = header->width_words * 32 - (31 - header->last_used_bit); /* Last used bit in row */ - const uint32_t bitmask = (1 << bpp) - 1; /* creates a mask of 1s that is bpp bits wide */ - - current_byte_index = 0; - currentword = BTUINT((image_in + current_byte_index)); - current_byte_index += 4; - - for (y = 0; y < sprite->height; y++) { - x_pixels = 0; - for (x = header->first_used_bit; x < row_max_bit ; x += bpp) { - const uint32_t offset_into_word = x % 32; - - pixel = (currentword & (bitmask << offset_into_word)) >> offset_into_word; - pixel = rosprite_palette_lookup(sprite, pixel); /* lookup returns 32bpp */ - if (sprite->has_mask) { - mask_pixel = rosprite_next_mask_pixel(mask, mask_state); - pixel = (pixel & 0xffffff00) | mask_pixel; - } - sprite->image[y*sprite->width + x_pixels] = pixel; - x_pixels++; - - /* If we're not at the end of the row and we've processed all of this word, fetch the next one */ - if (x + bpp < row_max_bit && offset_into_word + bpp == 32) { - currentword = BTUINT((image_in + current_byte_index)); - current_byte_index += 4; - } - } - - /* Advance to first word of next row */ - if (y + 1 < sprite->height) { - currentword = BTUINT((image_in + current_byte_index)); - current_byte_index += 4; - } - } - } - if (sprite->has_mask) free(mask_state); - - return ROSPRITE_OK; -} - -static uint32_t rosprite_palette_lookup(struct rosprite* sprite, uint32_t pixel) -{ - uint32_t translated_pixel; - /* because we're dealing with 8bpp or less */ - if (sprite->has_palette) { - - if(pixel < (sprite->palettesize / 8)) { - translated_pixel = sprite->palette[pixel]; - } else { - translated_pixel = sprite_8bpp_palette[pixel]; - } - } else { - switch (sprite->mode.colorbpp) { - case 8: - assert(pixel < 256); - translated_pixel = sprite_8bpp_palette[pixel]; - break; - case 4: - assert(pixel < 16); - translated_pixel = sprite_4bpp_palette[pixel]; - break; - case 2: - assert(pixel < 4); - translated_pixel = sprite_2bpp_palette[pixel]; - break; - case 1: - assert(pixel < 2); - translated_pixel = sprite_1bpp_palette[pixel]; - break; - default: - assert(false); - } - } - return translated_pixel; -} - -static rosprite_error rosprite_init_mask_state(struct rosprite* sprite, struct rosprite_header* header, uint8_t* mask, struct rosprite_mask_state** result) -{ - struct rosprite_mask_state* mask_state = malloc(sizeof(struct rosprite_mask_state)); - if (!mask_state) return ROSPRITE_NOMEM; - - mask_state->x = header->first_used_bit; - mask_state->y = 0; - mask_state->first_used_bit = header->first_used_bit; - mask_state->row_max_bit = sprite->width * sprite->mode.mask_width; - mask_state->height = sprite->height; - mask_state->bpp = sprite->mode.mask_width; - mask_state->current_word = BTUINT(mask); - mask_state->current_byte_index = 4; - - *result = mask_state; - - return ROSPRITE_OK; -} - -/** - * Get the next mask byte. - * A mask of 0xff denotes 100% opaque, 0x00 denotes 100% transparent. - * - * \param[in,out] mask_state - */ -static uint32_t rosprite_next_mask_pixel(uint8_t* mask, struct rosprite_mask_state* mask_state) -{ - /* a 1bpp mask (for new mode sprites), each row is word aligned (therefore potential righthand wastage */ - const uint32_t bitmask = (1 << mask_state->bpp) - 1; - const uint32_t offset_into_word = mask_state->x % 32; - uint32_t pixel = (mask_state->current_word & (bitmask << offset_into_word)) >> offset_into_word; - - if (mask_state->x + mask_state->bpp < mask_state->row_max_bit && offset_into_word + mask_state->bpp == 32) { - mask_state->current_word = BTUINT((mask + mask_state->current_byte_index)); - mask_state->current_byte_index += 4; - } - - mask_state->x += mask_state->bpp; - if (mask_state->x >= mask_state->row_max_bit) { - mask_state->x = mask_state->first_used_bit; - - if (mask_state->y + 1 < mask_state->height) { - mask_state->current_word = BTUINT((mask + mask_state->current_byte_index)); - mask_state->current_byte_index += 4; - } - - mask_state->y++; - } - - /* - * if the mask is 1bpp, make sure the whole byte is 0x00 or 0xff - */ - if (mask_state->bpp < 8) { - pixel = (pixel & 1) ? 0xff : 0x00; - } - - return pixel; -} - -/** - * Upscale a 16, 24 or 32bpp sprite to the 0xRRGGBBAA representation. - * Do not call this function with a sprite with less than 16bpp, - * but use a palette lookup instead. - */ -static uint32_t rosprite_upscale_color(uint32_t pixel, struct rosprite_mode* mode, bool* has_alpha_pixel_data) -{ - uint8_t alpha; - switch (mode->colorbpp) { - case 32: - if (mode->color_model == ROSPRITE_RGB) { - /* swap from 0xAABBGGRR to 0xRRGGBBAA */ - pixel = BSWAP(pixel); - } else { - pixel = rosprite_cmyk_to_rgb(pixel); - } - break; - case 24: - /* reverse byte order */ - pixel = BSWAP(pixel); - break; - case 16: - /* incoming format is b_00000000000000000bbbbbgggggrrrrr */ - { - uint8_t red = pixel & 31; - uint8_t green = (pixel & (31 << 5)) >> 5; - uint8_t blue = (pixel & (31 << 10)) >> 10; - - /* sanity check */ - assert(red < 32); - assert(green < 32); - assert(blue < 32); - - pixel = (sprite_16bpp_translate[red] << 24) - | (sprite_16bpp_translate[green] << 16) - | (sprite_16bpp_translate[blue] << 8); - } - break; - case 8: - case 4: - case 2: - case 1: - assert(false); /* shouldn't need to call for <= 8bpp, since a palette lookup will return 32bpp */ - default: - assert(false); /* unknown bpp */ - } - - alpha = pixel & 0xff; - if (alpha == 0x00) { - if (!(*has_alpha_pixel_data)) { - pixel = pixel | 0xff; - } - } else { - *has_alpha_pixel_data = true; - } - return pixel; -} - -static inline uint32_t rosprite_cmyk_to_rgb(uint32_t cmyk) -{ - uint8_t c = cmyk & 0xff; - uint8_t m = (cmyk & 0xff00) >> 8; - uint8_t y = (cmyk & 0xff0000) >> 16; - uint8_t k = cmyk >> 24; - - /* Convert to CMY colourspace */ - uint8_t C = c + k; - uint8_t M = m + k; - uint8_t Y = y + k; - - /* And to RGB */ - uint8_t r = 255 - C; - uint8_t g = 255 - M; - uint8_t b = 255 - Y; - - return r << 24 | g << 16 | b << 8; -} - -static inline rosprite_error rosprite_read_word(reader reader, void* ctx, uint32_t* result) -{ - unsigned char b[4]; - if (reader(b, 4, ctx) < 4) { - return ROSPRITE_EOF; - } - *result = BTUINT(b); - return ROSPRITE_OK; -} diff --git a/librosprite.h b/librosprite.h deleted file mode 100644 index f54eaf2..0000000 --- a/librosprite.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - * This file is part of librosprite. - * Licensed under the MIT License, - * http://www.opensource.org/licenses/mit-license.php - * Copyright 2008 James Shaw - */ - -/** - * \mainpage - * - * librosprite is a library for reading RISC OS sprite and palette files. The following subformats are supported: - *
    - *
  • 1-bit and 8-bit transparency masks
  • - *
  • Sprites with custom palettes
  • - *
  • Standard RISC OS 1-bit, 2-bit palettes, and 4-bit and 8-bit colour palettes
  • - *
  • Old-style sprites with most screen modes from 0-49 supported
  • - *
  • 32bpp CMYK
  • - *
  • Inline alpha channel, as used by Photodesk and Tinct
  • - *
- * - */ - -/** - * \file librosprite.h - * - * Sprite file reading is performed by rosprite_load(), and palette file reading by rosprite_load_palette(). - * - * Retrieving sprite or palette data is performed by a reader. - * librosprite implements file and memory readers. - * To use a reader, create a context by calling the - * rosprite_create_file_context() or rosprite_create_mem_context(). - * Pass the reader function, and the context you have created, to the load function, - * typically rosprite_load(). - */ - -#ifndef ROSPRITE_H -#define ROSPRITE_H - -#include -#include -#include - -typedef enum { ROSPRITE_OK, ROSPRITE_NOMEM, ROSPRITE_EOF, ROSPRITE_BADMODE } rosprite_error; - -typedef enum { ROSPRITE_RGB, ROSPRITE_CMYK } rosprite_color_model; - -/** - * A reader interface used to load sprite files. - */ -typedef int (*reader)(uint8_t* buf, size_t count, void* ctx); - -struct rosprite_file_context; - -/** - * A sprite area comprises zero or more rosprites. Optionally, it may also have an extension_words block. - */ -struct rosprite_area { - uint32_t extension_size; /* size of extension_words in bytes */ - uint8_t* extension_words; - uint32_t sprite_count; - struct rosprite** sprites; /* array of length sprite_count */ -}; - -/** - * A sprite mode defines the colour depth, colour model and bitmap resolution of a sprite. - */ -struct rosprite_mode { - /** - * The bits per colour channel. Legal values are 1, 2, 4, 8, 16, 24 and 32. - */ - uint32_t colorbpp; - /* maskbpp denotes the amount of alpha bpp used - * while mask_width is the bits used to store the mask. - * Old modes have the same mask_width as their colorbpp, but the value - * is always all-zeroes or all-ones. - * New modes can have 1bpp or 8bpp masks - */ - uint32_t maskbpp; - uint32_t mask_width; /* in pixels */ - - /** - * Horizontal dots per inch. Typical values are 22, 45, 90 and 180. - */ - uint32_t xdpi; - - /** - * Vertical dots per inch. Typical values are 22, 45, 90 and 180. - */ - uint32_t ydpi; - rosprite_color_model color_model; -}; - -struct rosprite_palette { - uint32_t size; /* in number of entries (each entry is a word) */ - uint32_t* palette; -}; - -/** - * A sprite is a bitmap image which has a mode, width and height. - */ -struct rosprite { - - /** - * The sprite name. This may be up to 12 characters long, and must be zero terminated. - */ - unsigned char name[13]; - struct rosprite_mode mode; - bool has_mask; - bool has_palette; - uint32_t palettesize; /* in number of entries (each entry is a word) */ - uint32_t* palette; - - /** - * Width in pixels - */ - uint32_t width; - - /** - * Height in pixels - */ - uint32_t height; - - /** - * Image data is a series of words, appearing on screen left-to-right, top-to-bottom. - * Each word takes the form 0xRRGGBBAA. A is the alpha channel, where 0 is transparent, and 255 is opaque. - */ - uint32_t* image; /* image data in 0xRRGGBBAA words */ -}; - -struct rosprite_file_context; - -/** - * Create a file reader context using the specified file handle. - * Clients must call rosprite_destroy_file_context() to dispose of the context. - * - * \param[out] result - */ -rosprite_error rosprite_create_file_context(FILE* f, struct rosprite_file_context** result); -void rosprite_destroy_file_context(struct rosprite_file_context* ctx); -int rosprite_file_reader(uint8_t* buf, size_t count, void* ctx); - -struct rosprite_mem_context; - -/** - * Create a memory reader context using the specified memory pointer. - * Clients must call rosprite_destroy_mem_context() to dispose of the context. - * - * \param[in] p pointer to the start of the memory block - * \param[in] total_size the size of the block pointed to by p, in bytes - * \param[out] result - */ -rosprite_error rosprite_create_mem_context(uint8_t* p, unsigned long total_size, struct rosprite_mem_context** result); -void rosprite_destroy_mem_context(struct rosprite_mem_context* ctx); -int rosprite_mem_reader(uint8_t* buf, size_t count, void* ctx); - -/** - * Load a rosprite_area using the reader provided. - * Clients must call rosprite_destroy_sprite_area() to dispose of the rosprite_area. - * - * \param[out] result The pointer to be populated by this function. - */ -rosprite_error rosprite_load(reader reader, void* ctx, struct rosprite_area** result); - -/** - * Dispose of a rosprite_area and its children. - */ -void rosprite_destroy_sprite_area(struct rosprite_area *); - -/** - * Load a RISC OS palette file. A palette file has RISC OS filetype 0xFED, - * and is a series of VDU 19 Set Palette commands, each command being 6 bytes long. - * - * Clients must call rosprite_destroy_palette() to dispose of the rosprite_palette. - * - * \param[out] result The pointer to be populated by this function. - * \see http://www.drobe.co.uk/show_manual.php?manual=/sh-cgi?manual=Vdu%26page=19 - */ -rosprite_error rosprite_load_palette(reader reader, void* ctx, struct rosprite_palette** result); - -/** - * Dispose of a rosprite_palette and its children. - */ -void rosprite_destroy_palette(struct rosprite_palette *); - -#endif diff --git a/librosprite.pc.in b/librosprite.pc.in index 322ca21..391bc08 100644 --- a/librosprite.pc.in +++ b/librosprite.pc.in @@ -5,6 +5,6 @@ includedir=${prefix}/include Name: librosprite Description: Loads RISC OS sprite files -Version: 0.0.1 +Version: VERSION Libs: -L${libdir} -lrosprite Cflags: -I${includedir} diff --git a/palette2c.c b/palette2c.c deleted file mode 100644 index bbe272c..0000000 --- a/palette2c.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of librosprite. - * Licensed under the MIT License, - * http://www.opensource.org/licenses/mit-license.php - * Copyright 2008 James Shaw - */ - -#include -#include - -#include "librosprite.h" - -int main(int argc, char *argv[]) -{ - if (argc < 2) { - printf("Usage: palette2c palettefile\n"); - exit(EXIT_FAILURE); - } - - char* filename = argv[1]; - - FILE* f = fopen(filename, "rb"); - if (f == NULL) { - printf("Can't load palettefile %s\n", filename); - exit(EXIT_FAILURE); - } - - struct rosprite_file_context* ctx; - if (rosprite_create_file_context(f, &ctx) != ROSPRITE_OK) { - exit(EXIT_FAILURE); - } - - struct rosprite_palette* palette; - if (rosprite_load_palette(rosprite_file_reader, ctx, &palette) != ROSPRITE_OK) { - exit(EXIT_FAILURE); - } - - for (uint32_t i = 0; i < palette->size; i++) { - printf("0x%x, ", palette->palette[i]); - } - - fclose(f); - - rosprite_destroy_file_context(ctx); - rosprite_destroy_palette(palette); - - return EXIT_SUCCESS; -} diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..f7c7c7a --- /dev/null +++ b/src/Makefile @@ -0,0 +1,4 @@ +# Sources +DIR_SOURCES := librosprite.c + +include $(NSBUILD)/Makefile.subdir diff --git a/src/librosprite.c b/src/librosprite.c new file mode 100644 index 0000000..9252510 --- /dev/null +++ b/src/librosprite.c @@ -0,0 +1,866 @@ +/* + * This file is part of librosprite. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2008 James Shaw + */ + +/** + * \file + */ + +#include +#include +#include +#include +#include +#include + +#include "librosprite.h" + +/** + * Reads four bytes, 00, 11, 22 and 33, of a byte array b to give 0x33221100. + */ +#define BTUINT(b) (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) + +/** + * Reverse the byte order of a word such that 0xAABBCCDD becomes 0xDDCCBBAA. + */ +#define BSWAP(word) (((word & (0x000000ff)) << 24) | ((word & 0x0000ff00) << 8) | ((word & 0x00ff0000) >> 8) | ((word & 0xff000000) >> 24)) + +#define ERRCHK(x) do { \ + rosprite_error err = x; \ + if (err != ROSPRITE_OK) return err; \ +} while(0) + +struct rosprite_header { + uint32_t width_words; /* width in words */ + /* height defined in sprite struct */ + uint32_t first_used_bit; /* old format only (spriteType = 0) */ + uint32_t last_used_bit; + + /** + * Image size in bytes + */ + uint32_t image_size; + + /** + * Mask size in bytes + */ + uint32_t mask_size; +}; + +struct rosprite_mask_state { + uint32_t x; + uint32_t y; + uint32_t first_used_bit; + uint32_t row_max_bit; + uint32_t height; + uint32_t current_byte_index; + uint32_t current_word; + uint32_t bpp; +}; + +struct rosprite_file_context { + FILE* f; +}; + +struct rosprite_mem_context { + uint8_t* base; + unsigned long offset; + bool known_size; + unsigned long size; +}; + +static const struct rosprite_mode oldmodes[] = { +/*0*/{ .colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*1*/{ .colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*2*/{ .colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 22, .ydpi = 45, .color_model = ROSPRITE_RGB }, + +/*3*/{ .colorbpp = 0, .maskbpp = 0, .mask_width = 0, .xdpi = 0, .ydpi = 0, .color_model = ROSPRITE_RGB }, + +/*4*/{ .colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*5*/{ .colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 22, .ydpi = 45, .color_model = ROSPRITE_RGB }, + +/*6*/{ .colorbpp = 0, .maskbpp = 0, .mask_width = 0, .xdpi = 0, .ydpi = 0, .color_model = ROSPRITE_RGB }, +/*7*/{ .colorbpp = 0, .maskbpp = 0, .mask_width = 0, .xdpi = 0, .ydpi = 0, .color_model = ROSPRITE_RGB }, + +/*8*/{ .colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*9*/{ .colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*10*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 22, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*11*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*12*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*13*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*14*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*15*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*16*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*17*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*18*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*19*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*20*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*21*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*22*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi =180, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*23*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*24*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*25*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*26*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*27*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*28*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*29*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*30*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*31*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*32*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*33*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*34*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*35*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*36*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*37*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*38*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*39*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*40*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*41*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*42*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*43*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*44*/{.colorbpp = 1, .maskbpp = 1, .mask_width = 1, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*45*/{.colorbpp = 2, .maskbpp = 1, .mask_width = 2, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*46*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 90, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*47*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 45, .ydpi = 45, .color_model = ROSPRITE_RGB }, +/*48*/{.colorbpp = 4, .maskbpp = 1, .mask_width = 4, .xdpi = 45, .ydpi = 90, .color_model = ROSPRITE_RGB }, +/*49*/{.colorbpp = 8, .maskbpp = 1, .mask_width = 8, .xdpi = 45, .ydpi = 90, .color_model = ROSPRITE_RGB } +}; + +/* table for converting a 5bit channel into an 8bit channel (used for 16bpp to 32bpp conversion) */ +static const uint8_t sprite_16bpp_translate[] = { + 0x00, 0x08, 0x10, 0x18, 0x20, 0x29, 0x31, 0x39, + 0x41, 0x4a, 0x52, 0x5a, 0x62, 0x6a, 0x73, 0x7b, + 0x83, 0x8b, 0x94, 0x9c, 0xa4, 0xac, 0xb4, 0xbd, + 0xc5, 0xcd, 0xd5, 0xde, 0xe6, 0xee, 0xf6, 0xff +}; + +/* palettes generated with palette2c.c + * which in turn requires rosprite_load_palette(FILE* f) + * defined in this file + */ +static const uint32_t sprite_1bpp_palette[] = { 0xffffffff, 0xff }; + +static const uint32_t sprite_2bpp_palette[] = { 0xffffffff, 0xbbbbbbff, 0x777777ff, 0xff }; + +static const uint32_t sprite_4bpp_palette[] = { +0xffffffff, 0xddddddff, 0xbbbbbbff, 0x999999ff, +0x777777ff, 0x555555ff, 0x333333ff, 0xff, +0x4499ff, 0xeeee00ff, 0xcc00ff, 0xdd0000ff, +0xeeeebbff, 0x558800ff, 0xffbb00ff, 0xbbffff +}; + +static const uint32_t sprite_8bpp_palette[] = { +0xff, 0x111111ff, 0x222222ff, 0x333333ff, +0x440000ff, 0x551111ff, 0x662222ff, 0x773333ff, +0x44ff, 0x111155ff, 0x222266ff, 0x333377ff, +0x440044ff, 0x551155ff, 0x662266ff, 0x773377ff, +0x880000ff, 0x991111ff, 0xaa2222ff, 0xbb3333ff, +0xcc0000ff, 0xdd1111ff, 0xee2222ff, 0xff3333ff, +0x880044ff, 0x991155ff, 0xaa2266ff, 0xbb3377ff, +0xcc0044ff, 0xdd1155ff, 0xee2266ff, 0xff3377ff, +0x4400ff, 0x115511ff, 0x226622ff, 0x337733ff, +0x444400ff, 0x555511ff, 0x666622ff, 0x777733ff, +0x4444ff, 0x115555ff, 0x226666ff, 0x337777ff, +0x444444ff, 0x555555ff, 0x666666ff, 0x777777ff, +0x884400ff, 0x995511ff, 0xaa6622ff, 0xbb7733ff, +0xcc4400ff, 0xdd5511ff, 0xee6622ff, 0xff7733ff, +0x884444ff, 0x995555ff, 0xaa6666ff, 0xbb7777ff, +0xcc4444ff, 0xdd5555ff, 0xee6666ff, 0xff7777ff, +0x8800ff, 0x119911ff, 0x22aa22ff, 0x33bb33ff, +0x448800ff, 0x559911ff, 0x66aa22ff, 0x77bb33ff, +0x8844ff, 0x119955ff, 0x22aa66ff, 0x33bb77ff, +0x448844ff, 0x559955ff, 0x66aa66ff, 0x77bb77ff, +0x888800ff, 0x999911ff, 0xaaaa22ff, 0xbbbb33ff, +0xcc8800ff, 0xdd9911ff, 0xeeaa22ff, 0xffbb33ff, +0x888844ff, 0x999955ff, 0xaaaa66ff, 0xbbbb77ff, +0xcc8844ff, 0xdd9955ff, 0xeeaa66ff, 0xffbb77ff, +0xcc00ff, 0x11dd11ff, 0x22ee22ff, 0x33ff33ff, +0x44cc00ff, 0x55dd11ff, 0x66ee22ff, 0x77ff33ff, +0xcc44ff, 0x11dd55ff, 0x22ee66ff, 0x33ff77ff, +0x44cc44ff, 0x55dd55ff, 0x66ee66ff, 0x77ff77ff, +0x88cc00ff, 0x99dd11ff, 0xaaee22ff, 0xbbff33ff, +0xcccc00ff, 0xdddd11ff, 0xeeee22ff, 0xffff33ff, +0x88cc44ff, 0x99dd55ff, 0xaaee66ff, 0xbbff77ff, +0xcccc44ff, 0xdddd55ff, 0xeeee66ff, 0xffff77ff, +0x88ff, 0x111199ff, 0x2222aaff, 0x3333bbff, +0x440088ff, 0x551199ff, 0x6622aaff, 0x7733bbff, +0xccff, 0x1111ddff, 0x2222eeff, 0x3333ffff, +0x4400ccff, 0x5511ddff, 0x6622eeff, 0x7733ffff, +0x880088ff, 0x991199ff, 0xaa22aaff, 0xbb33bbff, +0xcc0088ff, 0xdd1199ff, 0xee22aaff, 0xff33bbff, +0x8800ccff, 0x9911ddff, 0xaa22eeff, 0xbb33ffff, +0xcc00ccff, 0xdd11ddff, 0xee22eeff, 0xff33ffff, +0x4488ff, 0x115599ff, 0x2266aaff, 0x3377bbff, +0x444488ff, 0x555599ff, 0x6666aaff, 0x7777bbff, +0x44ccff, 0x1155ddff, 0x2266eeff, 0x3377ffff, +0x4444ccff, 0x5555ddff, 0x6666eeff, 0x7777ffff, +0x884488ff, 0x995599ff, 0xaa66aaff, 0xbb77bbff, +0xcc4488ff, 0xdd5599ff, 0xee66aaff, 0xff77bbff, +0x8844ccff, 0x9955ddff, 0xaa66eeff, 0xbb77ffff, +0xcc44ccff, 0xdd55ddff, 0xee66eeff, 0xff77ffff, +0x8888ff, 0x119999ff, 0x22aaaaff, 0x33bbbbff, +0x448888ff, 0x559999ff, 0x66aaaaff, 0x77bbbbff, +0x88ccff, 0x1199ddff, 0x22aaeeff, 0x33bbffff, +0x4488ccff, 0x5599ddff, 0x66aaeeff, 0x77bbffff, +0x888888ff, 0x999999ff, 0xaaaaaaff, 0xbbbbbbff, +0xcc8888ff, 0xdd9999ff, 0xeeaaaaff, 0xffbbbbff, +0x8888ccff, 0x9999ddff, 0xaaaaeeff, 0xbbbbffff, +0xcc88ccff, 0xdd99ddff, 0xeeaaeeff, 0xffbbffff, +0xcc88ff, 0x11dd99ff, 0x22eeaaff, 0x33ffbbff, +0x44cc88ff, 0x55dd99ff, 0x66eeaaff, 0x77ffbbff, +0xccccff, 0x11ddddff, 0x22eeeeff, 0x33ffffff, +0x44ccccff, 0x55ddddff, 0x66eeeeff, 0x77ffffff, +0x88cc88ff, 0x99dd99ff, 0xaaeeaaff, 0xbbffbbff, +0xcccc88ff, 0xdddd99ff, 0xeeeeaaff, 0xffffbbff, +0x88ccccff, 0x99ddddff, 0xaaeeeeff, 0xbbffffff, +0xccccccff, 0xddddddff, 0xeeeeeeff, 0xffffffff +}; + +static inline rosprite_error rosprite_read_word(reader reader, void* ctx, uint32_t* result); +static rosprite_error rosprite_get_mode(uint32_t spriteMode, struct rosprite_mode* result); +static uint32_t rosprite_palette_lookup(struct rosprite* sprite, uint32_t pixel); +static inline uint32_t rosprite_cmyk_to_rgb(uint32_t cmyk); +static uint32_t rosprite_next_mask_pixel(uint8_t* mask, struct rosprite_mask_state* mask_state); +static rosprite_error rosprite_load_high_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header); +static rosprite_error rosprite_load_low_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header); +rosprite_error rosprite_load_sprite(reader reader, void* ctx, struct rosprite** result); +static rosprite_error rosprite_init_mask_state(struct rosprite* sprite, struct rosprite_header* header, uint8_t* mask, struct rosprite_mask_state** result); +static uint32_t rosprite_upscale_color(uint32_t pixel, struct rosprite_mode* mode, bool* has_alpha_pixel_data); +static inline void rosprite_fix_alpha(uint32_t* image, unsigned long pixels); + +rosprite_error rosprite_load(reader reader, void* ctx, struct rosprite_area** result) +{ + uint32_t firstSpriteOffset, firstFreeWordOffset; + int bytes_read; + uint32_t i; + + struct rosprite_area* sprite_area = malloc(sizeof(struct rosprite_area)); + + ERRCHK(rosprite_read_word(reader, ctx, &(sprite_area->sprite_count))); + + ERRCHK(rosprite_read_word(reader, ctx, &firstSpriteOffset)); + ERRCHK(rosprite_read_word(reader, ctx, &firstFreeWordOffset)); /* TODO: use this for some sanity checking? */ + sprite_area->extension_size = 16 - firstSpriteOffset; + + sprite_area->extension_words = NULL; + if (sprite_area->extension_size > 0) { + sprite_area->extension_words = malloc(sprite_area->extension_size); + bytes_read = reader(sprite_area->extension_words, (size_t) (sprite_area->extension_size), ctx); + if (bytes_read < (signed long) sprite_area->extension_size) { + return ROSPRITE_EOF; + } + } + + sprite_area->sprites = malloc(sizeof(struct rosprite*) * sprite_area->sprite_count); /* allocate array of pointers */ + for (i = 0; i < sprite_area->sprite_count; i++) { + struct rosprite* sprite; + ERRCHK(rosprite_load_sprite(reader, ctx, &sprite)); + sprite_area->sprites[i] = sprite; + } + + *result = sprite_area; + + return ROSPRITE_OK; +} + +void rosprite_destroy_sprite_area(struct rosprite_area* sprite_area) +{ + uint32_t i; + for (i = 0; i < sprite_area->sprite_count; i++) { + struct rosprite* sprite = sprite_area->sprites[i]; + if (sprite->has_palette) free(sprite->palette); + free(sprite->image); + free(sprite); + } + + free(sprite_area->sprites); + if (sprite_area->extension_size > 0) free(sprite_area->extension_words); + free(sprite_area); +} + +rosprite_error rosprite_load_palette(reader reader, void* ctx, struct rosprite_palette** result) +{ + uint32_t c; + uint8_t b[6]; + unsigned int bytesRead; + + /* TODO: currently assume palette has linear entries (2nd byte in is 00, 01, 02 etc) */ + struct rosprite_palette* palette = malloc(sizeof(struct rosprite_palette)); + + palette->palette = malloc(sizeof(uint32_t) * 256); /* allocate 256 whether we need them all or not */ + + c = 0; + + bytesRead = reader(b, 6, ctx); + assert(bytesRead % 6 == 0); + while (bytesRead == 6) { + assert(b[0] == 19); /* VDU 19 */ + + /* only process logical colours */ + if (b[2] == 16) { + /*assert(c == b[1]);*/ + + uint32_t entry = (b[3] << 24) | (b[4] << 16) | (b[5] << 8) | 0xff; /* last byte is alpha */ + palette->palette[c] = entry; + + c++; + assert(c <= 256); + } + + bytesRead = reader(b, 6, ctx); + assert(bytesRead % 6 == 0); + } + + palette->size = c; + + *result = palette; + + return ROSPRITE_OK; +} + +void rosprite_destroy_palette(struct rosprite_palette* palette) +{ + free(palette->palette); + free(palette); +} + +rosprite_error rosprite_create_file_context(FILE* f, struct rosprite_file_context** result) +{ + struct rosprite_file_context* ctx = malloc(sizeof(struct rosprite_file_context)); + if (!ctx) return ROSPRITE_NOMEM; + ctx->f = f; + *result = ctx; + + return ROSPRITE_OK; +} + +void rosprite_destroy_file_context(struct rosprite_file_context* ctx) +{ + free(ctx); +} + +int rosprite_file_reader(uint8_t* buf, size_t count, void* ctx) +{ + return fread(buf, 1 /*size*/, count, ((struct rosprite_file_context*) ctx)->f); +} + +rosprite_error rosprite_create_mem_context(uint8_t* p, unsigned long total_size, struct rosprite_mem_context** result) +{ + struct rosprite_mem_context* ctx = malloc(sizeof(struct rosprite_mem_context)); + if (!ctx) return ROSPRITE_NOMEM; + + ctx->base = p; + ctx->offset = 0; + ctx->size = total_size; + *result = ctx; + + return ROSPRITE_OK; +} + +void rosprite_destroy_mem_context(struct rosprite_mem_context* ctx) +{ + free(ctx); +} + +int rosprite_mem_reader(uint8_t* buf, size_t count, void* ctx) +{ + size_t copy_size; + struct rosprite_mem_context* memctx = (struct rosprite_mem_context*) ctx; + if (memctx->offset + count > memctx->size) { + return -1; + } + + // if we're asked for more memory than the block contains, only copy as much as we can + if ((memctx->offset + count) > memctx->size) { + copy_size = memctx->size - memctx->offset; + } else { + copy_size = count; + } + memcpy(buf, memctx->base + memctx->offset, count); + memctx->offset += count; + return copy_size; +} + +/** + * Load a single sprite. + * + * \param[out] result + */ +rosprite_error rosprite_load_sprite(reader reader, void* ctx, struct rosprite** result) +{ + uint32_t nextSpriteOffset; + uint32_t imageOffset; + uint32_t maskOffset, spriteModeWord; + uint32_t paletteEntries; + uint8_t* image; + uint8_t* mask = NULL; + + struct rosprite* sprite = malloc(sizeof(struct rosprite)); + struct rosprite_header* header = malloc(sizeof(struct rosprite_header)); + + ERRCHK(rosprite_read_word(reader, ctx, &nextSpriteOffset)); + + reader(sprite->name, 12, ctx); + sprite->name[12] = '\0'; + + ERRCHK(rosprite_read_word(reader, ctx, &header->width_words)); /* file has width - 1 and height - 1 */ + header->width_words += 1; + ERRCHK(rosprite_read_word(reader, ctx, &(sprite->height))); + sprite->height += 1; + ERRCHK(rosprite_read_word(reader, ctx, &(header->first_used_bit))); /* old format only (spriteType = 0) */ + ERRCHK(rosprite_read_word(reader, ctx, &(header->last_used_bit))); + + ERRCHK(rosprite_read_word(reader, ctx, &imageOffset)); + assert(imageOffset >= 44); /* should never be smaller than the size of the header) */ + + ERRCHK(rosprite_read_word(reader, ctx, &maskOffset)); + ERRCHK(rosprite_read_word(reader, ctx, &spriteModeWord)); + + ERRCHK(rosprite_get_mode(spriteModeWord, &(sprite->mode))); + + /* TODO left-hand wastage */ + + assert((header->last_used_bit + 1) % sprite->mode.colorbpp == 0); + /*assert(header->width_words % sprite->mode->colorbpp == 0);*/ + sprite->width = (header->width_words * 32 - header->first_used_bit - (31 - header->last_used_bit)) / sprite->mode.colorbpp; + + sprite->palettesize = imageOffset - 44; + sprite->has_palette = (sprite->palettesize > 0); + + /* sprite has no mask if imageOffset == maskOffset */ + if (imageOffset == maskOffset) { + sprite->has_mask = false; + header->image_size = nextSpriteOffset - 44 - sprite->palettesize; + header->mask_size = 0; + } else { + sprite->has_mask = true; + header->image_size = maskOffset - imageOffset; + header->mask_size = nextSpriteOffset - 44 - sprite->palettesize - header->image_size; + } + + if (sprite->has_palette) { + uint32_t j, word1, word2, entry; + assert(sprite->palettesize % 8 == 0); + sprite->palette = malloc(sizeof(uint32_t) * sprite->palettesize); + paletteEntries = sprite->palettesize / 8; + + /* Each palette entry is two words big + * The second word is a duplicate of the first + * I think this is in case you ever wanted flashing colours + * PRM1-730 + */ + for (j = 0; j < paletteEntries; j++) { + ERRCHK(rosprite_read_word(reader, ctx, &word1)); + ERRCHK(rosprite_read_word(reader, ctx, &word2)); + assert(word1 == word2); /* if they aren't equal, flashing colours are desired, which we don't support */ + + /* swap rr and bb parts -- PRM1-731 */ + entry = ((word1 & 0xff000000) >> 16) | (word1 & 0x00ff0000) | ((word1 & 0x0000ff00) << 16) | 0xff; + sprite->palette[j] = entry; + } + } + + image = malloc(header->image_size); + reader(image, header->image_size, ctx); + + if (sprite->has_mask) { + mask = malloc(header->mask_size); + reader(mask, header->mask_size, ctx); + } + + /* sanity check image_size */ + assert((header->width_words) * 4 * (sprite->height) == header->image_size); + /* TODO: sanity check mask_size */ + if (sprite->mode.colorbpp > 8) { + ERRCHK(rosprite_load_high_color(image, mask, sprite, header)); + } else { + ERRCHK(rosprite_load_low_color(image, mask, sprite, header)); + } + + free(image); + free(mask); + free(header); + + *result = sprite; + + return ROSPRITE_OK; +} + +/** + * Determine the sprite_mode for the specified sprite_mode_word. + * + * \param[out] result + */ +static rosprite_error rosprite_get_mode(uint32_t sprite_mode_word, struct rosprite_mode* result) +{ + struct rosprite_mode mode; + + uint32_t spriteType = (sprite_mode_word & (15 << 27)) >> 27; /* preserve bits 27-30 only */ + + if (spriteType != 0) { + bool hasEightBitAlpha = sprite_mode_word >> 31; /* bit 31 */ + /* new modes have 1bpp masks (PRM5a-111) + * unless bit 31 is set (http://select.riscos.com/prm/graphics/sprites/alphachannel.html) + */ + mode.maskbpp = (hasEightBitAlpha ? 8 : 1); + mode.mask_width = mode.maskbpp; + mode.xdpi = (sprite_mode_word & (8191 << 14)) >> 14; /* preserve bits 14-26 only */ + mode.ydpi = (sprite_mode_word & (8191 << 1)) >> 1; /* preserve bits 1-13 only */ + + mode.color_model = ROSPRITE_RGB; + switch (spriteType) { + case 1: + mode.colorbpp = 1; break; + case 2: + mode.colorbpp = 2; break; + case 3: + mode.colorbpp = 4; break; + case 4: + mode.colorbpp = 8; break; + case 5: + mode.colorbpp = 16; break; + case 6: + mode.colorbpp = 32; break; + case 7: + mode.colorbpp = 32; + mode.color_model = ROSPRITE_CMYK; + break; + case 8: + mode.colorbpp = 24; break; + default: + return ROSPRITE_BADMODE; + } + } else { + assert(sprite_mode_word < 256); /* don't think you can have modes over 255? */ + + mode = oldmodes[sprite_mode_word]; + } + + /* illegal mode check */ + if ((mode.colorbpp == 0) || (mode.xdpi == 0) || (mode.ydpi == 0)) { + return ROSPRITE_BADMODE; + } + + memcpy(result, &mode, sizeof(struct rosprite_mode)); + + return ROSPRITE_OK; +} + +/** + * Load a sprite image with 16 or more bpp. + * + * \param[out] sprite On exit, sprite.image will be populated + */ +static rosprite_error rosprite_load_high_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) +{ + struct rosprite_mask_state* mask_state = NULL; + uint32_t currentByteIndex = 0; + uint32_t j, x, y, x_pixels, pixel; + bool has_alpha_pixel_data = false; + uint8_t b; + bool old_has_alpha; + + if (sprite->has_mask) { + ERRCHK(rosprite_init_mask_state(sprite, header, mask, &mask_state)); + } + + sprite->image = malloc(sprite->width * sprite->height * 4); /* all image data is 32bpp going out */ + + /* Spec says that there must be no left-hand wastage */ + assert(header->first_used_bit == 0); + + { + const uint32_t bpp = sprite->mode.colorbpp; + const uint32_t bytesPerPixel = bpp / 8; + const uint32_t row_max_bit = header->width_words * 32 - (31 - header->last_used_bit); /* Last used bit in row */ + + for (y = 0; y < sprite->height; y++) { + x_pixels = 0; + for (x = 0; x < row_max_bit; x += bpp) { + pixel = 0; + for (j = 0; j < bytesPerPixel; j++) { + b = image_in[currentByteIndex++]; + pixel = pixel | (b << (j * 8)); + } + + old_has_alpha = has_alpha_pixel_data; + pixel = rosprite_upscale_color(pixel, &(sprite->mode), &has_alpha_pixel_data); + if (old_has_alpha != has_alpha_pixel_data && (y > 0 || x_pixels > 0)) { + rosprite_fix_alpha(sprite->image, (y * sprite->width) + x_pixels - 1); + } + if (sprite->has_mask) { + uint8_t mask_pixel = rosprite_next_mask_pixel(mask, mask_state); + pixel = (pixel & 0xffffff00) | mask_pixel; + } + sprite->image[y*sprite->width + x_pixels] = pixel; + x_pixels++; + } + + /* Ensure byte index is pointing at start of next row */ + if (y + 1 < sprite->height) { + currentByteIndex = (currentByteIndex + 3) & ~3; /* Round up to next multiple of 4 */ + } + } + } + if (sprite->has_mask) free(mask_state); + return ROSPRITE_OK; +} + +/** + * Iterate over the specified number of pixels, starting at the image pointer, + * and set the alpha channel to 0x00. + */ +static inline void rosprite_fix_alpha(uint32_t* image, unsigned long pixels) +{ + uint32_t i; + for (i = 0; i <= pixels; i++) { + image[i] = image[i] & 0xffffff00; + } +} + +/** + * Load a sprite image with 8 or fewer bpp. + * + * \param[out] sprite On exit, sprite.image will be populated + */ +static rosprite_error rosprite_load_low_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) +{ + uint32_t current_byte_index, currentword; + uint32_t x, y, x_pixels, pixel; + uint8_t mask_pixel; + + struct rosprite_mask_state* mask_state = NULL; + if (sprite->has_mask) { + ERRCHK(rosprite_init_mask_state(sprite, header, mask, &mask_state)); + } + + sprite->image = malloc(sprite->width * sprite->height * 4); /* all image data is 32bpp going out */ + + { + const uint32_t bpp = sprite->mode.colorbpp; + const uint32_t row_max_bit = header->width_words * 32 - (31 - header->last_used_bit); /* Last used bit in row */ + const uint32_t bitmask = (1 << bpp) - 1; /* creates a mask of 1s that is bpp bits wide */ + + current_byte_index = 0; + currentword = BTUINT((image_in + current_byte_index)); + current_byte_index += 4; + + for (y = 0; y < sprite->height; y++) { + x_pixels = 0; + for (x = header->first_used_bit; x < row_max_bit ; x += bpp) { + const uint32_t offset_into_word = x % 32; + + pixel = (currentword & (bitmask << offset_into_word)) >> offset_into_word; + pixel = rosprite_palette_lookup(sprite, pixel); /* lookup returns 32bpp */ + if (sprite->has_mask) { + mask_pixel = rosprite_next_mask_pixel(mask, mask_state); + pixel = (pixel & 0xffffff00) | mask_pixel; + } + sprite->image[y*sprite->width + x_pixels] = pixel; + x_pixels++; + + /* If we're not at the end of the row and we've processed all of this word, fetch the next one */ + if (x + bpp < row_max_bit && offset_into_word + bpp == 32) { + currentword = BTUINT((image_in + current_byte_index)); + current_byte_index += 4; + } + } + + /* Advance to first word of next row */ + if (y + 1 < sprite->height) { + currentword = BTUINT((image_in + current_byte_index)); + current_byte_index += 4; + } + } + } + if (sprite->has_mask) free(mask_state); + + return ROSPRITE_OK; +} + +static uint32_t rosprite_palette_lookup(struct rosprite* sprite, uint32_t pixel) +{ + uint32_t translated_pixel; + /* because we're dealing with 8bpp or less */ + if (sprite->has_palette) { + + if(pixel < (sprite->palettesize / 8)) { + translated_pixel = sprite->palette[pixel]; + } else { + translated_pixel = sprite_8bpp_palette[pixel]; + } + } else { + switch (sprite->mode.colorbpp) { + case 8: + assert(pixel < 256); + translated_pixel = sprite_8bpp_palette[pixel]; + break; + case 4: + assert(pixel < 16); + translated_pixel = sprite_4bpp_palette[pixel]; + break; + case 2: + assert(pixel < 4); + translated_pixel = sprite_2bpp_palette[pixel]; + break; + case 1: + assert(pixel < 2); + translated_pixel = sprite_1bpp_palette[pixel]; + break; + default: + translated_pixel = 0; + assert(false); + } + } + return translated_pixel; +} + +static rosprite_error rosprite_init_mask_state(struct rosprite* sprite, struct rosprite_header* header, uint8_t* mask, struct rosprite_mask_state** result) +{ + struct rosprite_mask_state* mask_state = malloc(sizeof(struct rosprite_mask_state)); + if (!mask_state) return ROSPRITE_NOMEM; + + mask_state->x = header->first_used_bit; + mask_state->y = 0; + mask_state->first_used_bit = header->first_used_bit; + mask_state->row_max_bit = sprite->width * sprite->mode.mask_width; + mask_state->height = sprite->height; + mask_state->bpp = sprite->mode.mask_width; + mask_state->current_word = BTUINT(mask); + mask_state->current_byte_index = 4; + + *result = mask_state; + + return ROSPRITE_OK; +} + +/** + * Get the next mask byte. + * A mask of 0xff denotes 100% opaque, 0x00 denotes 100% transparent. + * + * \param[in,out] mask_state + */ +static uint32_t rosprite_next_mask_pixel(uint8_t* mask, struct rosprite_mask_state* mask_state) +{ + /* a 1bpp mask (for new mode sprites), each row is word aligned (therefore potential righthand wastage */ + const uint32_t bitmask = (1 << mask_state->bpp) - 1; + const uint32_t offset_into_word = mask_state->x % 32; + uint32_t pixel = (mask_state->current_word & (bitmask << offset_into_word)) >> offset_into_word; + + if (mask_state->x + mask_state->bpp < mask_state->row_max_bit && offset_into_word + mask_state->bpp == 32) { + mask_state->current_word = BTUINT((mask + mask_state->current_byte_index)); + mask_state->current_byte_index += 4; + } + + mask_state->x += mask_state->bpp; + if (mask_state->x >= mask_state->row_max_bit) { + mask_state->x = mask_state->first_used_bit; + + if (mask_state->y + 1 < mask_state->height) { + mask_state->current_word = BTUINT((mask + mask_state->current_byte_index)); + mask_state->current_byte_index += 4; + } + + mask_state->y++; + } + + /* + * if the mask is 1bpp, make sure the whole byte is 0x00 or 0xff + */ + if (mask_state->bpp < 8) { + pixel = (pixel & 1) ? 0xff : 0x00; + } + + return pixel; +} + +/** + * Upscale a 16, 24 or 32bpp sprite to the 0xRRGGBBAA representation. + * Do not call this function with a sprite with less than 16bpp, + * but use a palette lookup instead. + */ +static uint32_t rosprite_upscale_color(uint32_t pixel, struct rosprite_mode* mode, bool* has_alpha_pixel_data) +{ + uint8_t alpha; + switch (mode->colorbpp) { + case 32: + if (mode->color_model == ROSPRITE_RGB) { + /* swap from 0xAABBGGRR to 0xRRGGBBAA */ + pixel = BSWAP(pixel); + } else { + pixel = rosprite_cmyk_to_rgb(pixel); + } + break; + case 24: + /* reverse byte order */ + pixel = BSWAP(pixel); + break; + case 16: + /* incoming format is b_00000000000000000bbbbbgggggrrrrr */ + { + uint8_t red = pixel & 31; + uint8_t green = (pixel & (31 << 5)) >> 5; + uint8_t blue = (pixel & (31 << 10)) >> 10; + + /* sanity check */ + assert(red < 32); + assert(green < 32); + assert(blue < 32); + + pixel = (sprite_16bpp_translate[red] << 24) + | (sprite_16bpp_translate[green] << 16) + | (sprite_16bpp_translate[blue] << 8); + } + break; + case 8: + case 4: + case 2: + case 1: + assert(false); /* shouldn't need to call for <= 8bpp, since a palette lookup will return 32bpp */ + default: + assert(false); /* unknown bpp */ + } + + alpha = pixel & 0xff; + if (alpha == 0x00) { + if (!(*has_alpha_pixel_data)) { + pixel = pixel | 0xff; + } + } else { + *has_alpha_pixel_data = true; + } + return pixel; +} + +static inline uint32_t rosprite_cmyk_to_rgb(uint32_t cmyk) +{ + uint8_t c = cmyk & 0xff; + uint8_t m = (cmyk & 0xff00) >> 8; + uint8_t y = (cmyk & 0xff0000) >> 16; + uint8_t k = cmyk >> 24; + + /* Convert to CMY colourspace */ + uint8_t C = c + k; + uint8_t M = m + k; + uint8_t Y = y + k; + + /* And to RGB */ + uint8_t r = 255 - C; + uint8_t g = 255 - M; + uint8_t b = 255 - Y; + + return r << 24 | g << 16 | b << 8; +} + +static inline rosprite_error rosprite_read_word(reader reader, void* ctx, uint32_t* result) +{ + unsigned char b[4]; + if (reader(b, 4, ctx) < 4) { + return ROSPRITE_EOF; + } + *result = BTUINT(b); + return ROSPRITE_OK; +} diff --git a/src/palette2c.c b/src/palette2c.c new file mode 100644 index 0000000..bbe272c --- /dev/null +++ b/src/palette2c.c @@ -0,0 +1,48 @@ +/* + * This file is part of librosprite. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2008 James Shaw + */ + +#include +#include + +#include "librosprite.h" + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + printf("Usage: palette2c palettefile\n"); + exit(EXIT_FAILURE); + } + + char* filename = argv[1]; + + FILE* f = fopen(filename, "rb"); + if (f == NULL) { + printf("Can't load palettefile %s\n", filename); + exit(EXIT_FAILURE); + } + + struct rosprite_file_context* ctx; + if (rosprite_create_file_context(f, &ctx) != ROSPRITE_OK) { + exit(EXIT_FAILURE); + } + + struct rosprite_palette* palette; + if (rosprite_load_palette(rosprite_file_reader, ctx, &palette) != ROSPRITE_OK) { + exit(EXIT_FAILURE); + } + + for (uint32_t i = 0; i < palette->size; i++) { + printf("0x%x, ", palette->palette[i]); + } + + fclose(f); + + rosprite_destroy_file_context(ctx); + rosprite_destroy_palette(palette); + + return EXIT_SUCCESS; +} -- cgit v1.2.3