Updated standard library
Diff: GraphicsDisplayGIF.cpp
- Revision:
- 152:a013ac0133e4
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GraphicsDisplayGIF.cpp Mon Nov 06 01:41:55 2017 +0000 @@ -0,0 +1,582 @@ + + + +// GIFRender.cpp : Defines the entry point for the console application. +// +// The code in this file was initially found online, in a tutorial. +// It has been revised significantly in this derivative. +// No copyright claim was found in the code. +// http://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art011 +// + +#include "mbed.h" + +#include "GraphicsDisplay.h" + + +#define DEBUG "GIF_" +// +// INFO("Stuff to show %d", var); // new-line is automatically appended +// +#if (defined(DEBUG) && !defined(TARGET_LPC11U24)) +#define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define ERR(x, ...) std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +static void HexDump(const char * title, const uint8_t * p, int count) { + int i; + char buf[100] = "0000: "; + + if (*title) + INFO("%s", title); + for (i = 0; i<count; ) { + sprintf(buf + strlen(buf), "%02X ", *(p + i)); + if ((++i & 0x0F) == 0x00) { + INFO("%s", buf); + if (i < count) + sprintf(buf, "%04X: ", i); + else + buf[0] = '\0'; + } + } + if (strlen(buf)) + INFO("%s", buf); +} +#else +#define INFO(x, ...) +#define WARN(x, ...) +#define ERR(x, ...) +#define HexDump(a, b, c) +#endif + + +#define EXTENSION_INTRODUCER 0x21 +#define IMAGE_DESCRIPTOR 0x2C +#define TRAILER 0x3B + +#define GRAPHIC_CONTROL 0xF9 +#define APPLICATION_EXTENSION 0xFF +#define COMMENT_EXTENSION 0xFE +#define PLAINTEXT_EXTENSION 0x01 + + +typedef struct { + unsigned char byte; + int prev; + int len; +} dictionary_entry_t; + +typedef struct { + unsigned char extension_code; + unsigned char block_size; +} extension_t; +const uint16_t extension_size = 2; + +typedef struct { + unsigned char fields; + unsigned short delay_time; + unsigned char transparent_color_index; +} graphic_control_extension_t; +const uint16_t graphic_control_extension_size = 4; + +typedef struct { + unsigned char application_id[8]; + unsigned char version[3]; +} application_extension_t; +const uint16_t application_extension_size = 11; + +typedef struct { + unsigned short left; + unsigned short top; + unsigned short width; + unsigned short height; + unsigned char cell_width; + unsigned char cell_height; + unsigned char foreground_color; + unsigned char background_color; +} plaintext_extension_t; +const uint16_t plaintext_extension_size = 12; + + +size_t GraphicsDisplay::read_filesystem_bytes(void * buffer, int numBytes, FILE * fh) { + size_t bytesRead; + + bytesRead = fread(buffer, 1, numBytes, fh); + HexDump("mem", (const uint8_t *) buffer, bytesRead); + return bytesRead; +} + + +/// uncompress_gif the data to memory +/// +/// @param[in] code_length is the size of this descriptor field +/// @param[in] input is a pointer to the input data +/// @param[in] input_length is the number of bytes in the input. +/// @param[out] out is where the uncompress_gifed information is written. +/// @return noerror on success, or failure code +/// +RetCode_t GraphicsDisplay::uncompress_gif(int code_length, const unsigned char *input, int input_length, unsigned char *out) { + int i, bit; + int code, prev = -1; + dictionary_entry_t * dictionary; + int dictionary_ind; + unsigned int mask = 0x01; + int reset_code_length; + int clear_code; // This varies depending on code_length + int stop_code; // one more than clear code + int match_len; + + clear_code = 1 << (code_length); + stop_code = clear_code + 1; + reset_code_length = code_length; + + // Create a dictionary large enough to hold "code_length" entries. + // Once the dictionary overflows, code_length increases + dictionary = (dictionary_entry_t *) malloc(sizeof(dictionary_entry_t) * (1 << (code_length + 1))); + if (dictionary == NULL) + return not_enough_ram; + // Initialize the first 2^code_len entries of the dictionary with their + // indices. The rest of the entries will be built up dynamically. + + // Technically, it shouldn't be necessary to initialize the + // dictionary. The spec says that the encoder "should output a + // clear code as the first code in the image data stream". It doesn't + // say must, though... + for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) { + dictionary[dictionary_ind].byte = (uint8_t) dictionary_ind; + // XXX this only works because prev is a 32-bit int (> 12 bits) + dictionary[dictionary_ind].prev = -1; + dictionary[dictionary_ind].len = 1; + } + // 2^code_len + 1 is the special "end" code; don't give it an entry here + dictionary_ind++; + dictionary_ind++; + + // TODO verify that the very last byte is clear_code + 1 + while (input_length) { + code = 0x0; + // Always read one more bit than the code length + for (i = 0; i < (code_length + 1); i++) { + // This is different than in the file read example; that + // was a call to "next_bit" + bit = (*input & mask) ? 1 : 0; + mask <<= 1; + if (mask == 0x100) { + mask = 0x01; + input++; + input_length--; + } + code = code | (bit << i); + } + if (code == clear_code) { + code_length = reset_code_length; + dictionary = (dictionary_entry_t *) realloc(dictionary, + sizeof(dictionary_entry_t) * (1 << (code_length + 1))); + for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) { + dictionary[dictionary_ind].byte = (uint8_t) dictionary_ind; + // XXX this only works because prev is a 32-bit int (> 12 bits) + dictionary[dictionary_ind].prev = -1; + dictionary[dictionary_ind].len = 1; + } + dictionary_ind++; + dictionary_ind++; + prev = -1; + continue; + } else if (code == stop_code) { + if (input_length > 1) { + free(dictionary); + return not_supported_format; + } + break; + } + + // Update the dictionary with this character plus the _entry_ + // (character or string) that came before it + if ((prev > -1) && (code_length < 12)) { + if (code > dictionary_ind) { + //fprintf(stderr, "code = %.02x, but dictionary_ind = %.02x\n", code, dictionary_ind); + free(dictionary); + return not_supported_format; + } + if (code == dictionary_ind) { + int ptr = prev; + + while (dictionary[ptr].prev != -1) { + ptr = dictionary[ptr].prev; + } + dictionary[dictionary_ind].byte = dictionary[ptr].byte; + } else { + int ptr = code; + while (dictionary[ptr].prev != -1) { + ptr = dictionary[ptr].prev; + } + dictionary[dictionary_ind].byte = dictionary[ptr].byte; + } + dictionary[dictionary_ind].prev = prev; + dictionary[dictionary_ind].len = dictionary[prev].len + 1; + dictionary_ind++; + // GIF89a mandates that this stops at 12 bits + if ((dictionary_ind == (1 << (code_length + 1))) && + (code_length < 11)) { + code_length++; + dictionary = (dictionary_entry_t *) realloc(dictionary, + sizeof(dictionary_entry_t) * (1 << (code_length + 1))); + } + } + prev = code; + // Now copy the dictionary entry backwards into "out" + match_len = dictionary[code].len; + while (code != -1) { + int pos = dictionary[code].len - 1; + out[pos] = dictionary[code].byte; + //INFO("%p out[%d] = %02X\r\n", out, pos, out[pos]); + if (dictionary[code].prev == code) { + free(dictionary); + return not_supported_format; + } + code = dictionary[code].prev; + } + out += match_len; + } + free(dictionary); + return noerror; +} + + +/// read a block +/// +/// @param[in] fh is the handle to the gif file. +/// @param[in] data is a pointer to a pointer to the data being processed. +/// @return -1 on failure, 0 when done, or >0 as the length of a processed data block +/// +int GraphicsDisplay::read_gif_sub_blocks(FILE * fh, unsigned char **data) { + int data_length; + int index; + unsigned char block_size; + + // Everything following are data sub-blocks, until a 0-sized block is encountered. + data_length = 0; + *data = NULL; + index = 0; + + while (1) { + if (read_filesystem_bytes(&block_size, 1, fh) < 1) { + return -1; + } + if (block_size == 0) { + break; + } + data_length += block_size; + *data = (unsigned char *) realloc(*data, data_length); + if (data) { + if (read_filesystem_bytes(*data + index, block_size, fh) < block_size) { + return -1; + } + index += block_size; + } else { + return -1; + } + } + return data_length; +} + +// color table is encoded as 24-bit values, but we don't need that much space, since +// the RA8875 is configured as either 8 or 16-bit color. +// +RetCode_t GraphicsDisplay::readColorTable(color_t * colorTable, int colorTableSize, FILE * fh) { + struct { + uint8_t r; + uint8_t g; + uint8_t b; + } rgb; + while (colorTableSize--) { + if (read_filesystem_bytes(&rgb, 3, fh) < 3) + return not_supported_format; + *colorTable++ = RGB(rgb.r, rgb.g, rgb.b); + } + return noerror; +} + +RetCode_t GraphicsDisplay::readGIFImageDescriptor(FILE * fh, gif_image_descriptor_t * imageDescriptor) { + if (read_filesystem_bytes(imageDescriptor, gif_image_descriptor_size, fh) < gif_image_descriptor_size) + return not_supported_format; + INFO("gif_image_descriptor\r\n"); + INFO(" left: %d\r\n", imageDescriptor->image_left_position); + INFO(" top: %d\r\n", imageDescriptor->image_top_position); + INFO(" width: %d\r\n", imageDescriptor->image_width); + INFO(" height: %d\r\n", imageDescriptor->image_height); + INFO(" fields: %02Xx\r\n", imageDescriptor->fields); + INFO(" lct: %2d\r\n", (imageDescriptor->fields & 0x80) ? 1 : 0); + INFO(" res: %2d\r\n", (imageDescriptor->fields & 0x40) ? 1 : 0); + INFO(" sort: %2d\r\n", (imageDescriptor->fields & 0x20) ? 1 : 0); + INFO(" res: %2d\r\n", ((imageDescriptor->fields & 0x18) >> 3)); + INFO(" lct siz: %2d\r\n", 1 << ((imageDescriptor->fields & 0x07) + 1)); + if (imageDescriptor->fields & 0x80) { + local_color_table_size = 1 << ((imageDescriptor->fields & 0x07) + 1); + local_color_table = (color_t *) malloc(sizeof(color_t) * local_color_table_size); + if (local_color_table == NULL) + return not_enough_ram; + if (readColorTable(local_color_table, local_color_table_size, fh) != noerror) { + free(local_color_table); + local_color_table = NULL; + return not_supported_format; + } + } + return noerror; +} + +/// Process the image section of the GIF file +/// +/// @param[in] fh is the handle to the file. +/// @param[in] uncompress_gifed_data is a pointer to a pointer to where the data will be placed. +/// @returns true if all went well. +/// +RetCode_t GraphicsDisplay::process_gif_image_descriptor(FILE * fh, uint8_t ** uncompress_gifed_data, int width, int height) { + int compressed_data_length; + unsigned char *compressed_data = NULL; + unsigned char lzw_code_size; + int uncompress_gifed_data_length = 0; + RetCode_t res = not_supported_format; + local_color_table_size = 0; + + if (read_filesystem_bytes(&lzw_code_size, 1, fh) < 1) + goto done; + INFO("lzw_code_size\r\n"); + INFO(" lzw code: %d\r\n", lzw_code_size); + compressed_data_length = read_gif_sub_blocks(fh, &compressed_data); + if (compressed_data_length > 0) { + uncompress_gifed_data_length = width * height; + *uncompress_gifed_data = (unsigned char *) malloc(uncompress_gifed_data_length); + if (*uncompress_gifed_data == NULL) + return not_enough_ram; + res = uncompress_gif(lzw_code_size, compressed_data, compressed_data_length, *uncompress_gifed_data); + } +done: + if (compressed_data) + free(compressed_data); + return res; +} + + +int GraphicsDisplay::process_gif_extension(FILE * fh) { + extension_t extension; + graphic_control_extension_t gce; + application_extension_t application; + plaintext_extension_t plaintext; + unsigned char *extension_data = NULL; + /* int extension_data_length; */ + + if (read_filesystem_bytes(&extension, extension_size, fh) < extension_size) { + return 0; + } + INFO("extension\r\n"); + INFO(" code: %d\r\n", extension.extension_code); + INFO(" block size: %d\r\n", extension.block_size); + switch (extension.extension_code) { + case GRAPHIC_CONTROL: + if (read_filesystem_bytes(&gce, graphic_control_extension_size, fh) < graphic_control_extension_size) { + return 0; + } + INFO("graphic_control_extension\r\n"); + INFO(" fields: %d\r\n", gce.fields); + INFO(" delay time: %d\r\n", gce.delay_time); + INFO(" transparent: %d\r\n", gce.transparent_color_index); + break; + case APPLICATION_EXTENSION: + if (read_filesystem_bytes(&application, application_extension_size, fh) < application_extension_size) { + return 0; + } + HexDump("application", (const uint8_t *) &application, sizeof(application)); + break; + case COMMENT_EXTENSION: + // comment extension; do nothing - all the data is in the + // sub-blocks that follow. + break; + case PLAINTEXT_EXTENSION: + if (read_filesystem_bytes(&plaintext, plaintext_extension_size, fh) < plaintext_extension_size) { + return 0; + } + HexDump("plaintext", (const uint8_t *) &plaintext, sizeof(plaintext)); + break; + default: + return (0); + } + // All extensions are followed by data sub-blocks; even if it's + // just a single data sub-block of length 0 + /* extension_data_length = */ read_gif_sub_blocks(fh, &extension_data); + if (extension_data != NULL) + free(extension_data); + return 1; +} + + +RetCode_t GraphicsDisplay::_RenderGIF(loc_t ScreenX, loc_t ScreenY, FILE * fh) { + //int color_resolution_bits; + global_color_table_size = 0; + local_color_table_size = 0; + + if (GetGIFHeader(fh) != noerror) + return not_supported_format; + //color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1; + if (screen_descriptor.fields & 0x80) { + // If bit 7 is set, the next block is a global color table; read it + global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1); + global_color_table = (color_t *) malloc(sizeof(color_t) * global_color_table_size); + if (global_color_table == NULL) + return not_enough_ram; + // XXX this could conceivably return a short count... + if (readColorTable(global_color_table, global_color_table_size, fh) != noerror) { + return not_supported_format; + } + HexDump("Global Color Table", (const uint8_t *) global_color_table, 3 * global_color_table_size); + } + unsigned char block_type = 0x0; + uint8_t * uncompress_gifed_data = NULL; + gif_image_descriptor_t gif_image_descriptor; // the image fragment + while (block_type != TRAILER) { + if (read_filesystem_bytes(&block_type, sizeof(block_type), fh) < sizeof(block_type)) + return not_supported_format; + INFO("block type: %02X", block_type); + + switch (block_type) { + case IMAGE_DESCRIPTOR: + if (readGIFImageDescriptor(fh, &gif_image_descriptor) != noerror) + return not_supported_format; + if (process_gif_image_descriptor(fh, &uncompress_gifed_data, gif_image_descriptor.image_width, gif_image_descriptor.image_height) == noerror) { + if (uncompress_gifed_data) { + // Ready to render to the screen + INFO("Render to (%d,%d)\r\n", ScreenX, ScreenY); + color_t * active_color_table = (local_color_table) ? local_color_table : global_color_table; + + // create a local image buffer for this fragment + color_t * cbmp = (color_t *) malloc(sizeof(color_t) * gif_image_descriptor.image_width * gif_image_descriptor.image_height); + if (cbmp == NULL) + return not_enough_ram; + for (int i = 0; i < gif_image_descriptor.image_width * gif_image_descriptor.image_height; i++) { + int cIndex = uncompress_gifed_data[i]; + cbmp[i] = active_color_table[cIndex]; + } + // Write Fragment to Screen + #if 0 + INFO("Render fragment: (%d,%d), offset: (%d,%d), w x h (%d,%d)\r\n", + ScreenX, ScreenY, + gif_image_descriptor.image_left_position, + gif_image_descriptor.image_top_position, + gif_image_descriptor.image_width, + gif_image_descriptor.image_height); + for (uint16_t y = 0; y < gif_image_descriptor.image_height; y++) { + for (uint16_t x = 0; x < gif_image_descriptor.image_width; x++) { + INFO("%04X ", cbmp[y * gif_image_descriptor.image_height + x]); + } + INFO("\r\n"); + } + #else + rect_t restore = windowrect; + window(ScreenX + gif_image_descriptor.image_left_position, + ScreenY + gif_image_descriptor.image_top_position, + gif_image_descriptor.image_width, + gif_image_descriptor.image_height); + pixelStream(cbmp, screen_descriptor.width * screen_descriptor.height, + ScreenX + gif_image_descriptor.image_left_position, + ScreenY + gif_image_descriptor.image_top_position); + window(restore); + #endif + // end write + free(cbmp); + } + if (local_color_table) { + free(local_color_table); + local_color_table = NULL; + } + } else { + return not_supported_format; + } + break; + case EXTENSION_INTRODUCER: + if (!process_gif_extension(fh)) + return not_supported_format; + break; + case TRAILER: + break; + default: + return not_supported_format; + } + } + return noerror; +} + + +// hasGIFHeader determines if it is a GIF file +// +// This reads a few bytes of the file and determines if they have the +// GIF89a signature. GIF87a is not supported. +// +// @param fh is a file handle. +// @returns true if it is GIF89a. +// +bool GraphicsDisplay::hasGIFHeader(FILE * fh) { + char GIF_Header[6]; + if (read_filesystem_bytes(GIF_Header, sizeof(GIF_Header), fh) != sizeof(GIF_Header)) + return false; + if (strncmp("GIF89a", GIF_Header, sizeof(GIF_Header))) + return false; + return true; +} + + +RetCode_t GraphicsDisplay::GetGIFHeader(FILE * fh) { + if (read_filesystem_bytes(&screen_descriptor, gif_screen_descriptor_size, fh) < gif_screen_descriptor_size) { + return not_supported_format; + } + screen_descriptor_isvalid = true; + INFO("screen_descriptor\r\n"); + INFO(" width: %d\r\n", screen_descriptor.width); + INFO(" height: %d\r\n", screen_descriptor.height); + INFO(" fields: %02Xx\r\n", screen_descriptor.fields); + INFO(" gct: %2d\r\n", (screen_descriptor.fields & 0x80) ? 1 : 0); + INFO(" res: %2d\r\n", ((screen_descriptor.fields & 0x70) >> 4) + 1); + INFO(" sort: %2d\r\n", (screen_descriptor.fields & 0x08) ? 1 : 0); + INFO(" gct siz: %2d\r\n", 1 << ((screen_descriptor.fields & 0x07) + 1)); + INFO(" back clr: %d\r\n", screen_descriptor.background_color_index); + INFO(" pix rat: %d\r\n", screen_descriptor.pixel_aspect_ratio); + return noerror; +} + + +RetCode_t GraphicsDisplay::GetGIFMetrics(gif_screen_descriptor_t * imageDescriptor, const char * Name_GIF) { + RetCode_t ret = not_supported_format; + + if (screen_descriptor_isvalid) { + ret = noerror; + } else { + FILE *fh = fopen(Name_GIF, "rb"); + if (fh) { + ret = GetGIFHeader(fh); + fclose(fh); + } + } + if (ret == noerror) + *imageDescriptor = screen_descriptor; + return ret; +} + + +RetCode_t GraphicsDisplay::RenderGIFFile(loc_t x, loc_t y, const char *Name_GIF) { + RetCode_t rt = file_not_found; + + INFO("Opening {%s}", Name_GIF); + screen_descriptor_isvalid = false; + FILE *fh = fopen(Name_GIF, "rb"); + if (fh) { + if (hasGIFHeader(fh)) { + rt = _RenderGIF(x, y, fh); + } + fclose(fh); + if (global_color_table) + free(global_color_table); + if (local_color_table) + free(local_color_table); + } + return rt; +} + +