This is the same as SMX_SetLights, but instead of taking one buffer with a fixed size, it takes a separate buffer for each pad, and explicitly includes the size of the buffer rather than assuming it's 864 bytes. SMX_SetLights and SMX_SetLights2 call into the same underlying update.master
parent
d576545266
commit
303283624a
@ -0,0 +1,530 @@ |
||||
#include "SMXGif.h" |
||||
#include <stdint.h> |
||||
#include <string> |
||||
#include <vector> |
||||
using namespace std; |
||||
|
||||
// This is a simple animated GIF decoder. It always decodes to RGBA color,
|
||||
// discarding palettes, and decodes the whole file at once.
|
||||
|
||||
class GIFError: public exception { }; |
||||
|
||||
struct Palette |
||||
{ |
||||
SMXGif::Color color[256]; |
||||
}; |
||||
|
||||
void SMXGif::GIFImage::Init(int width_, int height_) |
||||
{ |
||||
width = width_; |
||||
height = height_; |
||||
image.resize(width * height); |
||||
} |
||||
|
||||
void SMXGif::GIFImage::Clear(const Color &color) |
||||
{ |
||||
for(int y = 0; y < height; ++y) |
||||
for(int x = 0; x < width; ++x) |
||||
get(x,y) = color; |
||||
} |
||||
|
||||
void SMXGif::GIFImage::CropImage(SMXGif::GIFImage &dst, int crop_left, int crop_top, int crop_width, int crop_height) const |
||||
{ |
||||
dst.Init(crop_width, crop_height); |
||||
|
||||
for(int y = 0; y < crop_height; ++y) |
||||
{ |
||||
for(int x = 0; x < crop_width; ++x) |
||||
dst.get(x,y) = get(x + crop_left, y + crop_top); |
||||
} |
||||
} |
||||
|
||||
void SMXGif::GIFImage::Blit(SMXGif::GIFImage &src, int dst_left, int dst_top, int dst_width, int dst_height) |
||||
{ |
||||
for(int y = 0; y < dst_height; ++y) |
||||
{ |
||||
for(int x = 0; x < dst_width; ++x) |
||||
get(x + dst_left, y + dst_top) = src.get(x, y); |
||||
} |
||||
} |
||||
|
||||
class DataStream |
||||
{ |
||||
public: |
||||
DataStream(const string &data_): |
||||
data(data_) |
||||
{ |
||||
} |
||||
|
||||
uint8_t ReadByte() |
||||
{ |
||||
if(pos >= data.size()) |
||||
throw GIFError(); |
||||
|
||||
uint8_t result = data[pos]; |
||||
pos++; |
||||
return result; |
||||
} |
||||
|
||||
uint16_t ReadLE16() |
||||
{ |
||||
uint8_t byte1 = ReadByte(); |
||||
uint8_t byte2 = ReadByte(); |
||||
return byte1 | (byte2 << 8); |
||||
} |
||||
|
||||
void ReadBytes(string &s, int count) |
||||
{ |
||||
s.clear(); |
||||
while(count--) |
||||
s.push_back(ReadByte()); |
||||
} |
||||
|
||||
void skip(int bytes) |
||||
{ |
||||
pos += bytes; |
||||
} |
||||
|
||||
private: |
||||
const string &data; |
||||
uint32_t pos = 0; |
||||
}; |
||||
|
||||
class LWZStream |
||||
{ |
||||
public: |
||||
LWZStream(DataStream &stream_): |
||||
stream(stream_) |
||||
{ |
||||
} |
||||
|
||||
// Read one LZW code from the input data.
|
||||
uint32_t ReadLZWCode(uint32_t bit_count) |
||||
{ |
||||
while(bits_in_buffer < bit_count) |
||||
{ |
||||
if(bytes_remaining == 0) |
||||
{ |
||||
// Read the next block's byte count.
|
||||
bytes_remaining = stream.ReadByte(); |
||||
if(bytes_remaining == 0) |
||||
throw GIFError(); |
||||
} |
||||
|
||||
// Shift in another 8 bits into the end of self.bits.
|
||||
bits |= stream.ReadByte() << bits_in_buffer; |
||||
bits_in_buffer += 8; |
||||
bytes_remaining -= 1; |
||||
} |
||||
|
||||
// Shift out bit_count worth of data from the end.
|
||||
uint32_t result = bits & ((1 << bit_count) - 1); |
||||
bits >>= bit_count; |
||||
bits_in_buffer -= bit_count; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
// Skip the rest of the LZW data.
|
||||
void Flush() |
||||
{ |
||||
stream.skip(bytes_remaining); |
||||
bytes_remaining = 0; |
||||
|
||||
// If there are any blocks past the end of data, skip them.
|
||||
while(1) |
||||
{ |
||||
uint8_t blocksize = stream.ReadByte(); |
||||
stream.skip(blocksize); |
||||
if(bytes_remaining == 0) |
||||
break; |
||||
} |
||||
} |
||||
|
||||
private: |
||||
DataStream &stream; |
||||
uint32_t bits = 0; |
||||
int bytes_remaining = 0; |
||||
int bits_in_buffer = 0; |
||||
}; |
||||
|
||||
struct LWZDecoder |
||||
{ |
||||
LWZDecoder(DataStream &stream): |
||||
lzw_stream(LWZStream(stream)) |
||||
{ |
||||
// Each frame has a single bits field.
|
||||
code_bits = stream.ReadByte(); |
||||
} |
||||
|
||||
string DecodeImage(); |
||||
|
||||
private: |
||||
uint16_t code_bits; |
||||
LWZStream lzw_stream; |
||||
}; |
||||
|
||||
|
||||
static const int GIFBITS = 12; |
||||
|
||||
string LWZDecoder::DecodeImage() |
||||
{ |
||||
uint32_t dictionary_bits = code_bits + 1; |
||||
int prev_code1 = -1; |
||||
int prev_code2 = -1; |
||||
|
||||
uint32_t clear = 1 << code_bits; |
||||
uint32_t end = clear + 1; |
||||
uint32_t next_free_slot = clear + 2; |
||||
|
||||
vector<pair<int,int>> dictionary; |
||||
dictionary.resize(1 << GIFBITS); |
||||
|
||||
// We append to this buffer as we decode data, then append the data in reverse
|
||||
// order.
|
||||
string append_buffer; |
||||
|
||||
string result; |
||||
while(1) |
||||
{ |
||||
// Flush append_buffer.
|
||||
for(int i = append_buffer.size() - 1; i >= 0; --i) |
||||
result.push_back(append_buffer[i]); |
||||
append_buffer.clear(); |
||||
|
||||
int code1 = lzw_stream.ReadLZWCode(dictionary_bits); |
||||
// printf("%02x");
|
||||
if(code1 == end) |
||||
break; |
||||
|
||||
if(code1 == clear) |
||||
{ |
||||
// Clear the dictionary and reset.
|
||||
dictionary_bits = code_bits + 1; |
||||
next_free_slot = clear + 2; |
||||
prev_code1 = -1; |
||||
prev_code2 = -1; |
||||
continue; |
||||
} |
||||
|
||||
int code2; |
||||
if(code1 < next_free_slot) |
||||
code2 = code1; |
||||
else if(code1 == next_free_slot && prev_code2 != -1) |
||||
{ |
||||
append_buffer.push_back(prev_code2); |
||||
code2 = prev_code1; |
||||
} |
||||
else |
||||
throw GIFError(); |
||||
|
||||
// Walk through the linked list of codes in the dictionary and append.
|
||||
while(code2 >= clear + 2) |
||||
{ |
||||
uint8_t append_char = dictionary[code2].first; |
||||
code2 = dictionary[code2].second; |
||||
append_buffer.push_back(append_char); |
||||
} |
||||
append_buffer.push_back(code2); |
||||
|
||||
// If we're already at the last free slot, the dictionary is full and can't be expanded.
|
||||
if(next_free_slot < (1 << dictionary_bits)) |
||||
{ |
||||
// If we have any free dictionary slots, save.
|
||||
if(prev_code1 != -1) |
||||
{ |
||||
dictionary[next_free_slot] = make_pair(code2, prev_code1); |
||||
next_free_slot += 1; |
||||
} |
||||
// If we've just filled the last dictionary slot, expand the dictionary size if possible.
|
||||
if(next_free_slot >= (1 << dictionary_bits) && dictionary_bits < GIFBITS) |
||||
dictionary_bits += 1; |
||||
} |
||||
|
||||
prev_code1 = code1; |
||||
prev_code2 = code2; |
||||
} |
||||
|
||||
// Skip any remaining data in this block.
|
||||
lzw_stream.Flush(); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
struct GlobalGIFData |
||||
{ |
||||
int width = 0, height = 0; |
||||
int background_index = -1; |
||||
bool use_transparency = false; |
||||
int transparency_index = -1; |
||||
int duration = 0; |
||||
int disposal_method = 0; |
||||
bool have_global_palette = false; |
||||
Palette palette; |
||||
}; |
||||
|
||||
class GIFDecoder |
||||
{ |
||||
public: |
||||
GIFDecoder(DataStream &stream_): |
||||
stream(stream_) |
||||
{ |
||||
} |
||||
|
||||
void ReadAllFrames(vector<SMXGif::SMXGifFrame> &frames); |
||||
|
||||
private: |
||||
bool ReadPacket(string &packet); |
||||
Palette ReadPalette(int palette_size); |
||||
void DecodeImage(GlobalGIFData global_data, SMXGif::GIFImage &out); |
||||
|
||||
DataStream &stream; |
||||
SMXGif::GIFImage image; |
||||
int frame; |
||||
}; |
||||
|
||||
// Read a palette with size colors.
|
||||
//
|
||||
// This is a simple string, with 4 RGBA bytes per color.
|
||||
Palette GIFDecoder::ReadPalette(int palette_size) |
||||
{ |
||||
Palette result; |
||||
for(int i = 0; i < palette_size; ++i) |
||||
{ |
||||
result.color[i].color[0] = stream.ReadByte(); // R
|
||||
result.color[i].color[1] = stream.ReadByte(); // G
|
||||
result.color[i].color[2] = stream.ReadByte(); // B
|
||||
result.color[i].color[3] = 0xFF; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
bool GIFDecoder::ReadPacket(string &packet) |
||||
{ |
||||
uint8_t packet_size = stream.ReadByte(); |
||||
if(packet_size == 0) |
||||
return false; |
||||
|
||||
stream.ReadBytes(packet, packet_size); |
||||
return true; |
||||
} |
||||
|
||||
void GIFDecoder::ReadAllFrames(vector<SMXGif::SMXGifFrame> &frames) |
||||
{ |
||||
string header; |
||||
stream.ReadBytes(header, 6); |
||||
|
||||
if(header != "GIF87a" && header != "GIF89a") |
||||
throw GIFError(); |
||||
|
||||
GlobalGIFData global_data; |
||||
|
||||
global_data.width = stream.ReadLE16(); |
||||
global_data.height = stream.ReadLE16(); |
||||
image.Init(global_data.width, global_data.height); |
||||
|
||||
// Ignore the aspect ratio field. (Supporting pixel aspect ratios in a format
|
||||
// this rudimentary was almost ambitious of them...)
|
||||
uint8_t global_flags = stream.ReadByte(); |
||||
global_data.background_index = stream.ReadByte(); |
||||
|
||||
// Ignore the aspect ratio field. (Supporting pixel aspect ratios in a format
|
||||
// this rudimentary was almost ambitious of them...)
|
||||
stream.ReadByte(); |
||||
|
||||
// Decode global_flags.
|
||||
uint8_t global_palette_size = global_flags & 0x7; |
||||
|
||||
global_data.have_global_palette = (global_flags >> 7) & 0x1; |
||||
|
||||
|
||||
// If there's no global palette, leave it empty.
|
||||
if(global_data.have_global_palette) |
||||
global_data.palette = ReadPalette(1 << (global_palette_size + 1)); |
||||
|
||||
frame = 0; |
||||
|
||||
// Save a copy of global data, so we can restore it after each frame.
|
||||
GlobalGIFData saved_global_data = global_data; |
||||
|
||||
// Decode all packets.
|
||||
while(1) |
||||
{ |
||||
uint8_t packet_type = stream.ReadByte(); |
||||
if(packet_type == 0x21) |
||||
{ |
||||
// Extension packet
|
||||
uint8_t extension_type = stream.ReadByte(); |
||||
|
||||
if(extension_type == 0xF9) |
||||
{ |
||||
string packet; |
||||
if(!ReadPacket(packet)) |
||||
throw GIFError(); |
||||
|
||||
DataStream packet_buf(packet); |
||||
|
||||
// Graphics control extension
|
||||
uint8_t gce_flags = packet_buf.ReadByte(); |
||||
global_data.duration = packet_buf.ReadLE16(); |
||||
global_data.transparency_index = packet_buf.ReadByte(); |
||||
|
||||
global_data.use_transparency = bool(gce_flags & 1); |
||||
global_data.disposal_method = (gce_flags >> 2) & 0xF; |
||||
if(!global_data.use_transparency) |
||||
global_data.transparency_index = -1; |
||||
} |
||||
|
||||
// Read any remaining packets in this extension packet.
|
||||
while(1) |
||||
{ |
||||
string packet; |
||||
if(!ReadPacket(packet)) |
||||
break; |
||||
} |
||||
} |
||||
else if(packet_type == 0x2C) |
||||
{ |
||||
// Image data
|
||||
SMXGif::GIFImage frame_image; |
||||
DecodeImage(global_data, frame_image); |
||||
|
||||
SMXGif::SMXGifFrame gif_frame; |
||||
gif_frame.width = global_data.width; |
||||
gif_frame.height = global_data.height; |
||||
gif_frame.milliseconds = global_data.duration * 10; |
||||
gif_frame.frame = frame_image; |
||||
frames.push_back(gif_frame); |
||||
|
||||
frame++; |
||||
|
||||
// Reset GCE (frame-specific) data.
|
||||
global_data = saved_global_data; |
||||
} |
||||
else if(packet_type == 0x3B) |
||||
{ |
||||
// EOF
|
||||
return; |
||||
} |
||||
else |
||||
throw GIFError(); |
||||
} |
||||
} |
||||
|
||||
// Decode a single GIF image into out, leaving this->image ready for
|
||||
// the next frame (with this frame's dispose applied).
|
||||
void GIFDecoder::DecodeImage(GlobalGIFData global_data, SMXGif::GIFImage &out) |
||||
{ |
||||
uint16_t block_left = stream.ReadLE16(); |
||||
uint16_t block_top = stream.ReadLE16(); |
||||
uint16_t block_width = stream.ReadLE16(); |
||||
uint16_t block_height = stream.ReadLE16(); |
||||
uint8_t local_flags = stream.ReadByte(); |
||||
|
||||
// area = (block_left, block_top, block_left + block_width, block_top + block_height)
|
||||
// Extract flags:
|
||||
uint8_t have_local_palette = (local_flags >> 7) & 1; |
||||
// bool interlaced = (local_flags >> 6) & 1;
|
||||
uint8_t local_palette_size = (local_flags >> 0) & 0x7; |
||||
// print 'Interlaced:', interlaced
|
||||
|
||||
// We don't support interlaced GIFs right now.
|
||||
// assert interlaced == 0
|
||||
|
||||
// If this frame has a local palette, use it. Otherwise, use the global palette.
|
||||
Palette active_palette = global_data.palette; |
||||
if(have_local_palette) |
||||
active_palette = ReadPalette(1 << (local_palette_size + 1)); |
||||
|
||||
if(!global_data.have_global_palette && !have_local_palette) |
||||
{ |
||||
// We have no palette. This is an invalid file.
|
||||
throw GIFError(); |
||||
} |
||||
|
||||
if(frame == 0) |
||||
{ |
||||
// On the first frame, clear the buffer. If we have a transparency index,
|
||||
// clear to transparent. Otherwise, clear to the background color.
|
||||
if(global_data.transparency_index != -1) |
||||
image.Clear(SMXGif::Color(0,0,0,0)); |
||||
else |
||||
image.Clear(active_palette.color[global_data.background_index]); |
||||
} |
||||
|
||||
// Decode the compressed image data.
|
||||
LWZDecoder decoder(stream); |
||||
string decompressed_data = decoder.DecodeImage(); |
||||
|
||||
if(decompressed_data.size() < block_width*block_height) |
||||
throw GIFError(); |
||||
|
||||
// Save the region to restore after decoding.
|
||||
SMXGif::GIFImage dispose; |
||||
if(global_data.disposal_method <= 1) |
||||
{ |
||||
// No disposal.
|
||||
} |
||||
else if(global_data.disposal_method == 2) |
||||
{ |
||||
// Clear the region to a background color afterwards.
|
||||
dispose.Init(block_width, block_height); |
||||
|
||||
if(global_data.transparency_index != -1) |
||||
dispose.Clear(SMXGif::Color(0,0,0,0)); |
||||
else |
||||
{ |
||||
uint8_t palette_idx = global_data.background_index; |
||||
dispose.Clear(active_palette.color[palette_idx]); |
||||
} |
||||
|
||||
} |
||||
else if(global_data.disposal_method == 3) |
||||
{ |
||||
// Restore the previous frame afterwards.
|
||||
image.CropImage(dispose, block_left, block_top, block_width, block_height); |
||||
} |
||||
else |
||||
{ |
||||
// Unknown disposal method
|
||||
} |
||||
|
||||
int pos = 0; |
||||
for(int y = block_top; y < block_top + block_height; ++y) |
||||
{ |
||||
for(int x = block_left; x < block_left + block_width; ++x) |
||||
{ |
||||
uint8_t palette_idx = decompressed_data[pos]; |
||||
pos++; |
||||
|
||||
if(palette_idx == global_data.transparency_index) |
||||
{ |
||||
// If this pixel is transparent, leave the existing color in place.
|
||||
} |
||||
else |
||||
{ |
||||
image.get(x,y) = active_palette.color[palette_idx]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Copy the image before we run dispose.
|
||||
out = image; |
||||
|
||||
// Restore the dispose area.
|
||||
if(dispose.width != 0) |
||||
image.Blit(dispose, block_left, block_top, block_width, block_height); |
||||
} |
||||
|
||||
bool SMXGif::DecodeGIF(string buf, vector<SMXGif::SMXGifFrame> &frames) |
||||
{ |
||||
DataStream stream(buf); |
||||
GIFDecoder gif(stream); |
||||
try { |
||||
gif.ReadAllFrames(frames); |
||||
} catch(GIFError &) { |
||||
// We don't return error strings for this, just success or failure.
|
||||
return false; |
||||
} |
||||
return true; |
||||
} |
@ -0,0 +1,70 @@ |
||||
#ifndef SMXGif_h |
||||
#define SMXGif_h |
||||
|
||||
#include <stdint.h> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
// This is a simple internal GIF decoder. It's only meant to be used by
|
||||
// SMXConfig.
|
||||
namespace SMXGif |
||||
{ |
||||
struct Color |
||||
{ |
||||
uint8_t color[4]; |
||||
Color() |
||||
{ |
||||
memset(color, 0, sizeof(color)); |
||||
} |
||||
|
||||
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) |
||||
{ |
||||
color[0] = r; |
||||
color[1] = g; |
||||
color[2] = b; |
||||
color[3] = a; |
||||
} |
||||
bool operator==(const Color &rhs) const |
||||
{ |
||||
return !memcmp(color, rhs.color, sizeof(*color)); |
||||
} |
||||
}; |
||||
|
||||
struct GIFImage |
||||
{ |
||||
int width = 0, height = 0; |
||||
void Init(int width, int height); |
||||
|
||||
Color get(int x, int y) const { return image[y*width+x]; } |
||||
Color &get(int x, int y) { return image[y*width+x]; } |
||||
|
||||
// Clear to a solid color.
|
||||
void Clear(const Color &color); |
||||
|
||||
// Copy a rectangle from this image into dst.
|
||||
void CropImage(GIFImage &dst, int crop_left, int crop_top, int crop_width, int crop_height) const; |
||||
|
||||
// Copy src into a rectangle in this image.
|
||||
void Blit(GIFImage &src, int dst_left, int dst_top, int dst_width, int dst_height); |
||||
|
||||
private: |
||||
std::vector<Color> image; |
||||
}; |
||||
|
||||
struct SMXGifFrame |
||||
{ |
||||
int width = 0, height = 0; |
||||
|
||||
// GIF images have a delay in 10ms units. We use 1ms for clarity.
|
||||
int milliseconds = 0; |
||||
|
||||
GIFImage frame; |
||||
}; |
||||
|
||||
// Decode a GIF into a list of frames.
|
||||
bool DecodeGIF(std::string buf, std::vector<SMXGifFrame> &frames); |
||||
} |
||||
|
||||
void gif_test(); |
||||
|
||||
#endif |
Loading…
Reference in new issue