From 270bf2ad00b469eac1e33eb82c3bfea27f015061 Mon Sep 17 00:00:00 2001 From: James Shaw Date: Thu, 27 Mar 2008 21:24:20 +0000 Subject: More docs, add function decls for private functions, put them in a more logical ordering svn path=/import/jshaw/libsprite/; revision=10019 --- trunk/Makefile | 4 +- trunk/example.c | 173 ++++++------ trunk/librosprite.c | 751 +++++++++++++++++++++++++++------------------------- trunk/librosprite.h | 33 ++- 4 files changed, 504 insertions(+), 457 deletions(-) diff --git a/trunk/Makefile b/trunk/Makefile index 873f212..0dcabcb 100644 --- a/trunk/Makefile +++ b/trunk/Makefile @@ -8,9 +8,9 @@ CFLAGS = -Wall -Wextra -Wundef -Wpointer-arith -Wcast-align \ -Wnested-externs -Werror -pedantic -std=c99 \ -Wno-format-zero-length -Wformat-security -Wstrict-aliasing=2 \ -Wmissing-format-attribute -Wunused -Wunreachable-code \ - -Wformat=2 -Werror-implicit-function-declaration + -Wformat=2 -Werror-implicit-function-declaration \ + -Wmissing-declarations -Wmissing-prototypes LDFLAGS = -L./ -#-Wmissing-declarations -Wmissing-prototypes all: example palette2c diff --git a/trunk/example.c b/trunk/example.c index 5de3139..25fff0a 100644 --- a/trunk/example.c +++ b/trunk/example.c @@ -4,93 +4,16 @@ #include "librosprite.h" -/* 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); - } - } -} - -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; -} - -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; -} +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) { - printf("Usage: example spritefile\n"); + fprintf(stderr, "Usage: example spritefile\n"); exit(EXIT_FAILURE); } @@ -106,7 +29,8 @@ int main(int argc, char *argv[]) printf("Loading %s\n", filename); struct rosprite_area* sprite_area; - if (!rosprite_load(rosprite_file_reader, ctx, &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); @@ -151,4 +75,85 @@ int main(int argc, char *argv[]) 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/trunk/librosprite.c b/trunk/librosprite.c index fcea0d0..bc23244 100644 --- a/trunk/librosprite.c +++ b/trunk/librosprite.c @@ -7,10 +7,14 @@ #include "librosprite.h" -/* reads four bytes, 00, 11, 22 and 33, of a byte array b to give 0x33221100 */ +/** + * 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 */ +/** + * 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 { \ @@ -196,6 +200,107 @@ static const uint32_t sprite_8bpp_palette[] = { 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, uint32_t pixels); + +rosprite_error rosprite_load(reader reader, void* ctx, struct rosprite_area** result) +{ + struct rosprite_area* sprite_area = malloc(sizeof(struct rosprite_area)); + + ERRCHK(rosprite_read_word(reader, ctx, &(sprite_area->sprite_count))); + + uint32_t firstSpriteOffset, firstFreeWordOffset; + 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); + int 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 (uint32_t 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) +{ + for (uint32_t 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) +{ + /* 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 */ + + uint32_t c = 0; + uint8_t b[6]; + + unsigned int 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)); @@ -253,17 +358,104 @@ int rosprite_mem_reader(uint8_t* buf, size_t count, void* ctx) return copy_size; } -rosprite_error sprite_read_word(reader reader, void* ctx, uint32_t* result) +rosprite_error rosprite_load_sprite(reader reader, void* ctx, struct rosprite** result) { - unsigned char b[4]; - if (reader(b, 4, ctx) < 4) { - return ROSPRITE_EOF; + uint32_t nextSpriteOffset; + ERRCHK(rosprite_read_word(reader, ctx, &nextSpriteOffset)); + + struct rosprite* sprite = malloc(sizeof(struct rosprite)); + struct rosprite_header* header = malloc(sizeof(struct rosprite_header)); + + 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))); + + uint32_t imageOffset; + ERRCHK(rosprite_read_word(reader, ctx, &imageOffset)); + assert(imageOffset >= 44); /* should never be smaller than the size of the header) */ + + uint32_t maskOffset, spriteModeWord; + 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; } - *result = BTUINT(b); + + if (sprite->has_palette) { + assert(sprite->palettesize % 8 == 0); + sprite->palette = malloc(sizeof(uint32_t) * sprite->palettesize); + uint32_t 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 (uint32_t j = 0; j < paletteEntries; j++) { + uint32_t word1, word2; + 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 */ + uint32_t entry = ((word1 & 0xff000000) >> 16) | (word1 & 0x00ff0000) | ((word1 & 0x0000ff00) << 16) | 0xff; + sprite->palette[j] = entry; + } + } + + uint8_t* image = malloc(header->image_size); + reader(image, header->image_size, ctx); + + uint8_t* mask = NULL; + 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; } -static rosprite_error sprite_get_mode(uint32_t spriteMode, struct rosprite_mode* result) +static rosprite_error rosprite_get_mode(uint32_t spriteMode, struct rosprite_mode* result) { struct rosprite_mode mode; @@ -303,7 +495,6 @@ static rosprite_error sprite_get_mode(uint32_t spriteMode, struct rosprite_mode* return ROSPRITE_BADMODE; } } else { - /* clone station mode and return */ assert(spriteMode < 256); /* don't think you can have modes over 255? */ mode = oldmodes[spriteMode]; @@ -319,116 +510,147 @@ static rosprite_error sprite_get_mode(uint32_t spriteMode, struct rosprite_mode* return ROSPRITE_OK; } -static uint32_t sprite_palette_lookup(struct rosprite* sprite, uint32_t pixel) +static rosprite_error rosprite_load_high_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) { - uint32_t translated_pixel; - /* because we're dealing with 8bpp or less */ - if (sprite->has_palette) { - assert(pixel <= sprite->palettesize); /* TODO: what to do if your color depth is bigger than palette? */ - translated_pixel = sprite->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); + 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 */ + + uint32_t currentByteIndex = 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 */ + + bool has_alpha_pixel_data = false; + + /* Spec says that there must be no left-hand wastage */ + assert(header->first_used_bit == 0); + + for (uint32_t y = 0; y < sprite->height; y++) { + uint32_t x_pixels = 0; + for (uint32_t x = 0; x < row_max_bit; x += bpp) { + uint32_t pixel = 0; + for (uint32_t j = 0; j < bytesPerPixel; j++) { + uint8_t b = image_in[currentByteIndex++]; + pixel = pixel | (b << (j * 8)); + } + + bool 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) { + 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 */ } } - return translated_pixel; + + if (sprite->has_mask) free(mask_state); + return ROSPRITE_OK; } -static inline uint32_t sprite_cmyk_to_rgb(uint32_t cmyk) +static inline void rosprite_fix_alpha(uint32_t* image, uint32_t pixels) { - 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; + for (uint32_t i = 0; i <= pixels; i++) { + image[i] = image[i] & 0xffffff00; + } } -static uint32_t sprite_upscale_color(uint32_t pixel, struct rosprite_mode* mode, bool* has_alpha_pixel_data) +static rosprite_error rosprite_load_low_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) { - switch (mode->colorbpp) { - case 32: - if (mode->color_model == ROSPRITE_RGB) { - /* swap from 0xAABBGGRR to 0xRRGGBBAA */ - pixel = BSWAP(pixel); + struct rosprite_mask_state* mask_state = NULL; + if (sprite->has_mask) { + ERRCHK(rosprite_init_mask_state(sprite, header, mask, &mask_state)); + } - uint8_t alpha = pixel & 0xff; - if (alpha == 0x00) { - if (!(*has_alpha_pixel_data)) { - pixel = pixel | 0xff; - } - } else { - *has_alpha_pixel_data = true; + 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 */ + + uint32_t current_byte_index = 0; + uint32_t currentword = BTUINT((image_in + current_byte_index)); + current_byte_index += 4; + + for (uint32_t y = 0; y < sprite->height; y++) { + uint32_t x_pixels = 0; + for (uint32_t x = header->first_used_bit; x < row_max_bit ; x += bpp) { + const uint32_t offset_into_word = x % 32; + + uint32_t pixel = (currentword & (bitmask << offset_into_word)) >> offset_into_word; + pixel = rosprite_palette_lookup(sprite, pixel); /* lookup returns 32bpp */ + if (sprite->has_mask) { + uint8_t mask_pixel = rosprite_next_mask_pixel(mask, mask_state); + pixel = (pixel & 0xffffff00) | mask_pixel; } - return pixel; - } else { - return sprite_cmyk_to_rgb(pixel); - } - case 24: - /* reverse byte order */ - return BSWAP(pixel); - 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; + sprite->image[y*sprite->width + x_pixels] = pixel; + x_pixels++; - /* sanity check */ - assert(red < 32); - assert(green < 32); - assert(blue < 32); + /* 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; + } + } - pixel = (sprite_16bpp_translate[red] << 24) - | (sprite_16bpp_translate[green] << 16) - | (sprite_16bpp_translate[blue] << 8); - return pixel; + /* Advance to first word of next row */ + if (y + 1 < sprite->height) { + currentword = BTUINT((image_in + current_byte_index)); + current_byte_index += 4; } - 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 */ } + + if (sprite->has_mask) free(mask_state); + + return ROSPRITE_OK; } -static inline void sprite_fix_alpha(uint32_t* image, uint32_t pixels) +static uint32_t rosprite_palette_lookup(struct rosprite* sprite, uint32_t pixel) { - for (uint32_t i = 0; i <= pixels; i++) { - image[i] = image[i] & 0xffffff00; + uint32_t translated_pixel; + /* because we're dealing with 8bpp or less */ + if (sprite->has_palette) { + assert(pixel <= sprite->palettesize); /* TODO: what to do if your color depth is bigger than palette? */ + translated_pixel = sprite->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 sprite_init_mask_state(struct rosprite* sprite, struct rosprite_header* header, uint8_t* mask, struct rosprite_mask_state** result) +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; @@ -450,7 +672,7 @@ static rosprite_error sprite_init_mask_state(struct rosprite* sprite, struct ros /* Get the next mask byte. * Mask of 0xff denotes 100% opaque, 0x00 denotes 100% transparent */ -static uint32_t sprite_next_mask_pixel(uint8_t* mask, struct rosprite_mask_state* 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; @@ -484,289 +706,82 @@ static uint32_t sprite_next_mask_pixel(uint8_t* mask, struct rosprite_mask_state return pixel; } -static rosprite_error sprite_load_high_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) +static uint32_t rosprite_upscale_color(uint32_t pixel, struct rosprite_mode* mode, bool* has_alpha_pixel_data) { - struct rosprite_mask_state* mask_state = NULL; - if (sprite->has_mask) { - ERRCHK(sprite_init_mask_state(sprite, header, mask, &mask_state)); - } - - sprite->image = malloc(sprite->width * sprite->height * 4); /* all image data is 32bpp going out */ - - uint32_t currentByteIndex = 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 */ - - bool has_alpha_pixel_data = false; - - /* Spec says that there must be no left-hand wastage */ - assert(header->first_used_bit == 0); - - for (uint32_t y = 0; y < sprite->height; y++) { - uint32_t x_pixels = 0; - for (uint32_t x = 0; x < row_max_bit; x += bpp) { - uint32_t pixel = 0; - for (uint32_t j = 0; j < bytesPerPixel; j++) { - uint8_t b = image_in[currentByteIndex++]; - pixel = pixel | (b << (j * 8)); - } - - bool old_has_alpha = has_alpha_pixel_data; - pixel = sprite_upscale_color(pixel, &(sprite->mode), &has_alpha_pixel_data); - if (old_has_alpha != has_alpha_pixel_data) { - sprite_fix_alpha(sprite->image, (y * sprite->width) + x_pixels - 1); - } - if (sprite->has_mask) { - uint8_t mask_pixel = sprite_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; -} - -static rosprite_error sprite_load_low_color(uint8_t* image_in, uint8_t* mask, struct rosprite* sprite, struct rosprite_header* header) -{ - struct rosprite_mask_state* mask_state = NULL; - if (sprite->has_mask) { - ERRCHK(sprite_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 */ - - uint32_t current_byte_index = 0; - uint32_t currentword = BTUINT((image_in + current_byte_index)); - current_byte_index += 4; - - for (uint32_t y = 0; y < sprite->height; y++) { - uint32_t x_pixels = 0; - for (uint32_t x = header->first_used_bit; x < row_max_bit ; x += bpp) { - const uint32_t offset_into_word = x % 32; - - uint32_t pixel = (currentword & (bitmask << offset_into_word)) >> offset_into_word; - pixel = sprite_palette_lookup(sprite, pixel); /* lookup returns 32bpp */ - if (sprite->has_mask) { - uint8_t mask_pixel = sprite_next_mask_pixel(mask, mask_state); - pixel = (pixel & 0xffffff00) | mask_pixel; - } - sprite->image[y*sprite->width + x_pixels] = pixel; - x_pixels++; + switch (mode->colorbpp) { + case 32: + if (mode->color_model == ROSPRITE_RGB) { + /* swap from 0xAABBGGRR to 0xRRGGBBAA */ + pixel = BSWAP(pixel); - /* 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; + uint8_t alpha = pixel & 0xff; + if (alpha == 0x00) { + if (!(*has_alpha_pixel_data)) { + pixel = pixel | 0xff; + } + } else { + *has_alpha_pixel_data = true; } + return pixel; + } else { + return rosprite_cmyk_to_rgb(pixel); } + case 24: + /* reverse byte order */ + return BSWAP(pixel); + 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; - /* 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; -} - -rosprite_error sprite_load_sprite(reader reader, void* ctx, struct rosprite** result) -{ - uint32_t nextSpriteOffset; - sprite_read_word(reader, ctx, &nextSpriteOffset); - - struct rosprite* sprite = malloc(sizeof(struct rosprite)); - struct rosprite_header* header = malloc(sizeof(struct rosprite_header)); - - reader(sprite->name, 12, ctx); - sprite->name[12] = '\0'; - - ERRCHK(sprite_read_word(reader, ctx, &header->width_words)); /* file has width - 1 and height - 1 */ - header->width_words += 1; - ERRCHK(sprite_read_word(reader, ctx, &(sprite->height))); - sprite->height += 1; - ERRCHK(sprite_read_word(reader, ctx, &(header->first_used_bit))); /* old format only (spriteType = 0) */ - ERRCHK(sprite_read_word(reader, ctx, &(header->last_used_bit))); - - uint32_t imageOffset; - ERRCHK(sprite_read_word(reader, ctx, &imageOffset)); - assert(imageOffset >= 44); /* should never be smaller than the size of the header) */ - - uint32_t maskOffset, spriteModeWord; - ERRCHK(sprite_read_word(reader, ctx, &maskOffset)); - ERRCHK(sprite_read_word(reader, ctx, &spriteModeWord)); - - ERRCHK(sprite_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) { - assert(sprite->palettesize % 8 == 0); - sprite->palette = malloc(sizeof(uint32_t) * sprite->palettesize); - uint32_t 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 (uint32_t j = 0; j < paletteEntries; j++) { - uint32_t word1, word2; - ERRCHK(sprite_read_word(reader, ctx, &word1)); - ERRCHK(sprite_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 */ - uint32_t entry = ((word1 & 0xff000000) >> 16) | (word1 & 0x00ff0000) | ((word1 & 0x0000ff00) << 16) | 0xff; - sprite->palette[j] = entry; - } - } - - uint8_t* image = malloc(header->image_size); - reader(image, header->image_size, ctx); - - uint8_t* mask = NULL; - 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(sprite_load_high_color(image, mask, sprite, header)); - } else { - ERRCHK(sprite_load_low_color(image, mask, sprite, header)); - } - - free(image); - free(mask); - free(header); - - *result = sprite; - - return ROSPRITE_OK; -} - -rosprite_error rosprite_load(reader reader, void* ctx, struct rosprite_area** result) -{ - struct rosprite_area* sprite_area = malloc(sizeof(struct rosprite_area)); - - ERRCHK(sprite_read_word(reader, ctx, &(sprite_area->sprite_count))); - - uint32_t firstSpriteOffset, firstFreeWordOffset; - ERRCHK(sprite_read_word(reader, ctx, &firstSpriteOffset)); - ERRCHK(sprite_read_word(reader, ctx, &firstFreeWordOffset)); /* TODO: use this for some sanity checking? */ - sprite_area->extension_size = 16 - firstSpriteOffset; + /* sanity check */ + assert(red < 32); + assert(green < 32); + assert(blue < 32); - sprite_area->extension_words = NULL; - if (sprite_area->extension_size > 0) { - sprite_area->extension_words = malloc(sprite_area->extension_size); - int 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; + pixel = (sprite_16bpp_translate[red] << 24) + | (sprite_16bpp_translate[green] << 16) + | (sprite_16bpp_translate[blue] << 8); + return pixel; } + 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 */ } - - sprite_area->sprites = malloc(sizeof(struct rosprite*) * sprite_area->sprite_count); /* allocate array of pointers */ - for (uint32_t i = 0; i < sprite_area->sprite_count; i++) { - struct rosprite* sprite; - ERRCHK(sprite_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) +static inline uint32_t rosprite_cmyk_to_rgb(uint32_t cmyk) { - for (uint32_t 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); + 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; } -rosprite_error rosprite_load_palette(reader reader, void* ctx, struct rosprite_palette** result) +static inline rosprite_error rosprite_read_word(reader reader, void* ctx, uint32_t* result) { - /* 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 */ - - uint32_t c = 0; - uint8_t b[6]; - - unsigned int 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); + unsigned char b[4]; + if (reader(b, 4, ctx) < 4) { + return ROSPRITE_EOF; } - - palette->size = c; - - *result = palette; - + *result = BTUINT(b); return ROSPRITE_OK; } - -void rosprite_destroy_palette(struct rosprite_palette* palette) -{ - free(palette->palette); - free(palette); -} diff --git a/trunk/librosprite.h b/trunk/librosprite.h index 442163c..21bd557 100644 --- a/trunk/librosprite.h +++ b/trunk/librosprite.h @@ -1,7 +1,7 @@ /** * \mainpage * - * librosprite is a library for reading RISC OS sprite files. The following subformats are supported: + * librosprite is a library for reading RISC OS sprite and palette files. The following subformats are supported: * + * */ /** * \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 @@ -35,7 +45,7 @@ typedef int (*reader)(uint8_t* buf, size_t count, void* ctx); struct rosprite_file_context; /** - * A sprite area comprises zero more rosprites. Optionally, it may also have an extension_words block. + * 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 */ @@ -111,11 +121,27 @@ struct rosprite { }; struct rosprite_file_context; -rosprite_error rosprite_create_file_context(FILE* f, struct rosprite_file_context** ctx); + +/** + * 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); @@ -123,6 +149,7 @@ 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); -- cgit v1.2.1