Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
GraphicsDisplayGIF.cpp
- Committer:
- WiredHome
- Date:
- 2020-02-11
- Revision:
- 197:853d08e2fb53
- Parent:
- 196:56820026701b
File content as of revision 197:853d08e2fb53:
// 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
size_t bytes = sizeof(dictionary_entry_t) * (1 << (code_length + 1));
INFO("uncompress_gif malloc(%d)", bytes);
dictionary = (dictionary_entry_t *) malloc(bytes);
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", 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");
INFO(" left: %d", imageDescriptor->image_left_position);
INFO(" top: %d", imageDescriptor->image_top_position);
INFO(" width: %d", imageDescriptor->image_width);
INFO(" height: %d", imageDescriptor->image_height);
INFO(" fields: %02Xx", imageDescriptor->fields);
INFO(" lct: %2d", (imageDescriptor->fields & 0x80) ? 1 : 0);
INFO(" res: %2d", (imageDescriptor->fields & 0x40) ? 1 : 0);
INFO(" sort: %2d", (imageDescriptor->fields & 0x20) ? 1 : 0);
INFO(" res: %2d", ((imageDescriptor->fields & 0x18) >> 3));
INFO("locclrtbl siz: %2d", 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;
}
}
INFO("gif_image_descriptor end.");
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 %d", 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);
INFO("lzw_code_size end %d", res);
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");
INFO(" code: %d", extension.extension_code);
INFO(" block size: %d", 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");
INFO(" fields: %d", gce.fields);
INFO(" delay time: %d", gce.delay_time);
INFO(" transparent: %d", 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);
RetCode_t r_pgid = noerror;
switch (block_type) {
case IMAGE_DESCRIPTOR:
if (readGIFImageDescriptor(fh, &gif_image_descriptor) != noerror) {
INFO("not supported format");
return not_supported_format;
}
r_pgid = process_gif_image_descriptor(fh, &uncompress_gifed_data, gif_image_descriptor.image_width, gif_image_descriptor.image_height);
if (r_pgid != noerror) {
INFO("error %d", r_pgid);
return r_pgid;
} else {
if (uncompress_gifed_data) {
// Ready to render to the screen
INFO("Render to (%d,%d)", 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) {
INFO("not enough ram");
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)",
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("");
}
#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;
}
}
break;
case EXTENSION_INTRODUCER:
if (!process_gif_extension(fh)) {
INFO("not supported format");
return not_supported_format;
}
break;
case TRAILER:
break;
default:
INFO("not supported format");
return not_supported_format;
}
}
INFO("no error");
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");
INFO(" width: %d", screen_descriptor.width);
INFO(" height: %d", screen_descriptor.height);
INFO(" fields: %02Xx", screen_descriptor.fields);
INFO(" gct: %2d", (screen_descriptor.fields & 0x80) ? 1 : 0);
INFO(" res: %2d", ((screen_descriptor.fields & 0x70) >> 4) + 1);
INFO(" sort: %2d", (screen_descriptor.fields & 0x08) ? 1 : 0);
INFO(" gct siz: %2d", 1 << ((screen_descriptor.fields & 0x07) + 1));
INFO(" back clr: %d", screen_descriptor.background_color_index);
INFO(" pix rat: %d", 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;
}