mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
1530 lines
34 KiB
C++
1530 lines
34 KiB
C++
#include "gif.h"
|
|
|
|
|
|
NAMESPACE_UPP
|
|
|
|
struct GifGlobalInfo // information about gif file
|
|
{
|
|
void Serialize(Stream& stream);
|
|
|
|
byte tag[6];
|
|
word width;
|
|
word height;
|
|
byte flags;
|
|
|
|
static const int GCT_PRESENT = 0x80;
|
|
static const int GCT_SORTED = 0x08;
|
|
static const int GCT_BITS_MASK = 0x07;
|
|
static const int GCT_BPP_MASK = 0x07;
|
|
static const int GCT_BPP_SHIFT = 4;
|
|
|
|
byte bgnd; // background color index
|
|
byte raw_aspect;
|
|
|
|
bool gct_flag; // true = GCT is present
|
|
bool gct_sort; // true = GCT is sorted
|
|
int gct_bits; // log2(records in GCT)
|
|
int gct_recs; // records in GCT = 1 << gct_bits
|
|
int gct_size; // bytes in GCT = 3 * gct_rect
|
|
int bpp; // bits per pixel
|
|
int pixels; // pixel types = 1 << bpp
|
|
int aspect; // aspect ratio * 1000, 0 = undefined
|
|
|
|
int64 glo_fpos; // file position of this global info
|
|
int64 gct_fpos; // file position of gct
|
|
int64 loc_fpos; // address of 1st local subimage descriptor
|
|
};
|
|
|
|
void GifGlobalInfo::Serialize(Stream& stream)
|
|
{
|
|
glo_fpos = stream.GetPos();
|
|
stream.SerializeRaw(tag, 6);
|
|
if(stream.IsError() || memcmp(tag, "GIF8", 4)) {
|
|
stream.SetError();
|
|
return;
|
|
}
|
|
stream % width % height % flags % bgnd % raw_aspect;
|
|
if(stream.IsLoading()) {
|
|
gct_flag = flags & GCT_PRESENT;
|
|
gct_sort = flags & GCT_SORTED;
|
|
gct_bits = (flags & GCT_BITS_MASK) + 1;
|
|
gct_recs = 1 << gct_bits;
|
|
gct_size = 3 * gct_recs;
|
|
bpp = ((flags >> GCT_BPP_SHIFT) & GCT_BPP_MASK) + 1;
|
|
pixels = 1 << bpp;
|
|
if(raw_aspect == 0)
|
|
aspect = 0;
|
|
else
|
|
aspect = ((raw_aspect + 15) * 125u) >> 3;
|
|
}
|
|
|
|
loc_fpos = gct_fpos = stream.GetPos();
|
|
if(gct_flag)
|
|
loc_fpos += gct_size;
|
|
}
|
|
|
|
struct GifLocalInfo // subimage info
|
|
{
|
|
bool Load(Stream& stream);
|
|
|
|
word x;
|
|
word y;
|
|
word width;
|
|
word height;
|
|
byte lct_flag; // 1 = LCT is present
|
|
byte lct_sort; // 1 = LCT is sorted
|
|
word lct_bits; // log2(records in LCT)
|
|
word lct_recs; // records in LCT = 1 << lct_bits
|
|
word lct_size; // bytes in LCT = 3 * lct_recs
|
|
byte interlace; // 1 = interlaced
|
|
|
|
int transparent; // transparent color index, -1 = unused
|
|
String comment; // comment string
|
|
|
|
int64 loc_fpos; // file address of this structure
|
|
int64 lct_fpos; // file address of LCT
|
|
int64 dat_fpos; // data position (1st byte after LCT)
|
|
};
|
|
|
|
bool GifLocalInfo::Load(Stream& stream)
|
|
{
|
|
byte flags;
|
|
int c1;
|
|
|
|
transparent = -1;
|
|
|
|
while((c1 = stream.Get()) != 0x2C)
|
|
if(c1 == 0x00)
|
|
;
|
|
else if(c1 == 0x21) {
|
|
int code = stream.Get();
|
|
int len = stream.Get();
|
|
int64 next = stream.GetPos() + len;
|
|
if(code == 0xF9) { // read transparent color index
|
|
byte flags = stream.Get(); // packed fields
|
|
stream.Get16le(); // delay time
|
|
transparent = stream.Get();
|
|
if(!(flags & 1))
|
|
transparent = -1;
|
|
}
|
|
else if(code == 0xFE) { // comment
|
|
stream.Seek(next);
|
|
while((len = stream.Get()) != 0) {
|
|
int old = comment.GetLength();
|
|
StringBuffer b(old + len);
|
|
memcpy(b, comment, old);
|
|
bool res = stream.GetAll(~b + old, len);
|
|
comment = b;
|
|
if(!res)
|
|
return false;
|
|
}
|
|
next = 0;
|
|
}
|
|
if(next) {
|
|
stream.Seek(next);
|
|
while((len = stream.Get()) > 0)
|
|
stream.SeekCur(len);
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
|
|
loc_fpos = stream.GetPos() - 1;
|
|
x = stream.Get16le();
|
|
y = stream.Get16le();
|
|
width = stream.Get16le();
|
|
height = stream.Get16le();
|
|
flags = stream.Get();
|
|
lct_flag = (flags & 0x80) != 0;
|
|
lct_sort = (flags & 0x20) != 0;
|
|
lct_bits = (flags & 0x07) + 1;
|
|
lct_recs = 1 << lct_bits;
|
|
lct_size = 3 * lct_recs;
|
|
interlace = (flags & 0x40) != 0;
|
|
lct_fpos = stream.GetPos();
|
|
dat_fpos = lct_fpos;
|
|
if(lct_flag)
|
|
dat_fpos += lct_size;
|
|
return true;
|
|
}
|
|
|
|
#if 0
|
|
class GifProcessor
|
|
{
|
|
public:
|
|
GifProcessor(Stream& stream, const AlphaArray *input_image);
|
|
|
|
AlphaArray Load();
|
|
void Save(bool optimize_palette = false, const String& comment = Null);
|
|
|
|
public:
|
|
String comment;
|
|
// int transparent_index;
|
|
bool optimize_palette;
|
|
|
|
private:
|
|
bool LoadSubimage(int& transparent_index);
|
|
void SaveSubimage();
|
|
void ClearTable(bool save = false);
|
|
bool Fetch(byte *&ptr, const byte *end);
|
|
bool FetchDataBlock();
|
|
void FlushDataBlock();
|
|
|
|
private:
|
|
static const int MAX_BITS = 12;
|
|
static const int MAX_COUNT = 1 << MAX_BITS;
|
|
static const int HASH_COUNT = 5331;
|
|
|
|
struct Item {
|
|
union {
|
|
const Item *prefix;
|
|
Item *child;
|
|
};
|
|
Item *left, *right;
|
|
short length;
|
|
byte character;
|
|
};
|
|
|
|
Item items[MAX_COUNT];
|
|
int item_count;
|
|
const AlphaArray *raw_image;
|
|
AlphaArray temp_image;
|
|
|
|
Stream& stream;
|
|
byte data_block[256];
|
|
byte old_first;
|
|
byte *data_ptr;
|
|
byte *data_end;
|
|
dword old_byte;
|
|
int old_shift;
|
|
int old_code;
|
|
int old_bits;
|
|
int max_bit_count;
|
|
int start_bpp;
|
|
};
|
|
|
|
GifProcessor::GifProcessor(Stream& stream, const AlphaArray *raw_image)
|
|
: stream(stream), raw_image(raw_image)
|
|
{
|
|
// ASSERT(stream.IsOpen() && stream.IsStoring());
|
|
// ASSERT(raw_image.size.cx > 0 && raw_image.size.cy > 0);
|
|
}
|
|
|
|
void GifProcessor::Save(bool optimize_palette, const String& cm)
|
|
{
|
|
const AlphaArray *src_image = raw_image;
|
|
if(src_image->pixel.bpp > 8 || optimize_palette || src_image->HasAlpha()) {
|
|
temp_image.pixel = CreatePalette(src_image->pixel, 8, 255);
|
|
raw_image = &temp_image;
|
|
}
|
|
if(optimize_palette) {
|
|
if(raw_image != &temp_image) {
|
|
temp_image.pixel <<= src_image->pixel;
|
|
raw_image = &temp_image;
|
|
}
|
|
OptimizePalette(temp_image.pixel);
|
|
}
|
|
int transparent_index = -1;
|
|
if(src_image->HasAlpha()) {
|
|
ASSERT(temp_image.pixel.palette.GetCount() < 256);
|
|
transparent_index = temp_image.pixel.palette.GetCount();
|
|
temp_image.pixel.palette.Add(Black());
|
|
PixelKillMask(temp_image.pixel, src_image->alpha, transparent_index);
|
|
}
|
|
|
|
comment = cm;
|
|
for(start_bpp = 1; start_bpp < 8 && raw_image->pixel.palette.GetCount() > (1 << start_bpp); start_bpp++)
|
|
;
|
|
|
|
GifGlobalInfo g;
|
|
memcpy(g.tag, !IsNull(comment) ? "GIF89a" : "GIF87a", 6);
|
|
g.width = raw_image->GetWidth();
|
|
g.height = raw_image->GetHeight();
|
|
g.flags = g.GCT_PRESENT | ((start_bpp - 1) << g.GCT_BPP_SHIFT) | (start_bpp - 1);
|
|
if(optimize_palette)
|
|
g.flags |= g.GCT_SORTED;
|
|
g.bgnd = 0;
|
|
g.raw_aspect = 0;
|
|
|
|
stream % g;
|
|
{ // write palette
|
|
for(int i = 0, n = 1 << start_bpp; i < n; i++) {
|
|
Color color = Black;
|
|
if(i < raw_image->pixel.palette.GetCount())
|
|
color = raw_image->pixel.palette[i];
|
|
stream.Put(color.GetR());
|
|
stream.Put(color.GetG());
|
|
stream.Put(color.GetB());
|
|
}
|
|
}
|
|
|
|
// write comment block
|
|
if(!IsNull(comment)) {
|
|
stream.Put(0x21);
|
|
stream.Put(0xFE);
|
|
for(int t = 0, n; (n = min(comment.GetLength() - t, 255)) > 0; t += n) {
|
|
stream.Put(n);
|
|
stream.Put(comment.Begin() + t, n);
|
|
}
|
|
stream.Put(0x00);
|
|
}
|
|
|
|
// store transparent color index
|
|
if(transparent_index >= 0) {
|
|
stream.Put(0x21);
|
|
stream.Put(0xF9);
|
|
stream.Put(4);
|
|
stream.Put(1); // transparent index present
|
|
stream.Put16le(0); // delay time = 0
|
|
stream.Put(transparent_index);
|
|
stream.Put(0);
|
|
}
|
|
|
|
if(start_bpp == 1)
|
|
start_bpp++;
|
|
SaveSubimage();
|
|
|
|
stream.Put(0x00);
|
|
stream.Put(0x3B);
|
|
}
|
|
|
|
void GifProcessor::SaveSubimage()
|
|
{
|
|
// TIMING("GifProcessor::SaveSubimage");
|
|
|
|
// subimage header
|
|
stream.Put(0x2C);
|
|
stream.Put16le(0);
|
|
stream.Put16le(0);
|
|
stream.Put16le(raw_image->GetWidth());
|
|
stream.Put16le(raw_image->GetHeight());
|
|
stream.Put(0x00); // no LCT, non-interlaced, no sort, LCT size-1 = 0
|
|
|
|
data_ptr = data_block;
|
|
data_end = data_ptr + 255;
|
|
ClearTable(true);
|
|
|
|
stream.Put(start_bpp); // start bits
|
|
|
|
int y = raw_image->GetHeight();
|
|
const byte *sp = 0, *se = 0;
|
|
|
|
int _old_shift = 0;
|
|
int _old_byte = 0;
|
|
int _old_bits = old_bits;
|
|
// int _lsh = start_bpp, _rsh = 32 - start_bpp;
|
|
|
|
// static TimingInspector ti_put("PUT_CODE");
|
|
|
|
#define PUT_CODE(code) \
|
|
do \ { \
|
|
/*TimingInspector::Routine r(ti_put);*/ \
|
|
_old_byte |= (code) << _old_shift; \
|
|
_old_shift += _old_bits; \
|
|
if(_old_shift >= 16) \ { \
|
|
if(data_ptr >= data_end) \
|
|
FlushDataBlock(); \
|
|
*data_ptr++ = _old_byte; \
|
|
if(data_ptr >= data_end) \
|
|
FlushDataBlock(); \
|
|
*data_ptr++ = _old_byte >>= 8; \
|
|
_old_byte >>= 8; \
|
|
_old_shift -= 16; \
|
|
} \
|
|
else if(_old_shift >= 8) \ { \
|
|
if(data_ptr >= data_end) \
|
|
FlushDataBlock(); \
|
|
*data_ptr++ = _old_byte; \
|
|
_old_byte >>= 8; \
|
|
_old_shift -= 8; \
|
|
} \
|
|
} \
|
|
while(false)
|
|
|
|
int restart_code = 1 << start_bpp;
|
|
PUT_CODE(restart_code);
|
|
|
|
for(;;) {
|
|
if(sp == se) {
|
|
if(--y < 0)
|
|
break;
|
|
sp = raw_image->GetPixelUpScan(y);
|
|
se = sp + raw_image->GetWidth();
|
|
}
|
|
Item *i = &items[*sp++], *p;
|
|
Item *new_item = &items[item_count];
|
|
ASSERT(i >= items && i <= &items[MAX_COUNT]);
|
|
|
|
SEEK:
|
|
if(sp == se) {
|
|
if(--y < 0) {
|
|
PUT_CODE(i - items);
|
|
break;
|
|
}
|
|
sp = raw_image->GetPixelUpScan(y);
|
|
se = sp + raw_image->GetWidth();
|
|
}
|
|
byte c = *sp++;
|
|
|
|
if((i = (p = i)->child) == 0)
|
|
p->child = new_item;
|
|
else
|
|
for(;;) {
|
|
if(i->character == c)
|
|
goto SEEK;
|
|
if(c < i->character) {
|
|
if(i->left)
|
|
i = i->left;
|
|
else {
|
|
i->left = new_item;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if(i->right)
|
|
i = i->right;
|
|
else {
|
|
i = i->right = new_item;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// code not found - rewind last char & output current prefix, add new code to table
|
|
sp--;
|
|
PUT_CODE(p - items);
|
|
if(item_count < MAX_COUNT) { // add new entry to item table
|
|
if(item_count >= max_bit_count) {
|
|
max_bit_count <<= 1;
|
|
_old_bits++;
|
|
}
|
|
new_item->character = c;
|
|
new_item->left = new_item->right = new_item->child = 0;
|
|
item_count++;
|
|
}
|
|
else { // restart table
|
|
PUT_CODE(restart_code);
|
|
ClearTable(true);
|
|
_old_bits = old_bits;
|
|
}
|
|
}
|
|
|
|
// put terminator code & close output block
|
|
PUT_CODE(restart_code + 1);
|
|
if(_old_shift > 0) {
|
|
if(data_ptr >= data_end)
|
|
FlushDataBlock();
|
|
*data_ptr++ = _old_byte;
|
|
}
|
|
FlushDataBlock();
|
|
}
|
|
|
|
bool GifProcessor::FetchDataBlock()
|
|
{
|
|
// TIMING("GifEncoder::FetchDataBlock");
|
|
|
|
int count = stream.Term();
|
|
if(count <= 0)
|
|
return false;
|
|
stream.Get();
|
|
if(!stream.GetAll(data_block, count))
|
|
return false;
|
|
data_ptr = data_block;
|
|
data_end = data_ptr + count;
|
|
return true;
|
|
}
|
|
|
|
void GifProcessor::FlushDataBlock()
|
|
{
|
|
// TIMING("GifEncoder::FlushDataBlock");
|
|
|
|
int count = data_ptr - data_block;
|
|
if(count) {
|
|
ASSERT(count <= 255);
|
|
stream.Put(count);
|
|
stream.Put(data_block, count);
|
|
data_ptr = data_block;
|
|
}
|
|
}
|
|
|
|
void GifProcessor::ClearTable(bool save)
|
|
{
|
|
for(int i = 0, n = 1 << start_bpp; i < n; i++) {
|
|
Item& item = items[i];
|
|
item.character = i;
|
|
item.length = 1;
|
|
if(save)
|
|
item.left = item.right = item.child = 0;
|
|
else
|
|
item.prefix = 0;
|
|
}
|
|
item_count = (1 << start_bpp);
|
|
items[item_count++].length = -1;
|
|
items[item_count++].length = -2;
|
|
old_code = -1;
|
|
old_bits = start_bpp;
|
|
max_bit_count = 1 << old_bits;
|
|
while(item_count > max_bit_count) {
|
|
old_bits++;
|
|
max_bit_count <<= 1;
|
|
}
|
|
}
|
|
|
|
bool GifProcessor::Fetch(byte *&cptr, const byte *cend)
|
|
{
|
|
// TIMING("GifEncoder::Fetch");
|
|
byte *cp = cptr;
|
|
byte _old_shift = old_shift;
|
|
byte _old_bits = old_bits;
|
|
byte *_data_ptr = data_ptr;
|
|
byte *_data_end = data_end;
|
|
dword _old_byte = old_byte;
|
|
while(cp < cend) {
|
|
// static TimingInspector ti("Fetch-Get");
|
|
// ti.Start();
|
|
int code;
|
|
if(_old_shift + _old_bits <= 8) {
|
|
code = (_old_byte >> _old_shift) & ((1 << _old_bits) - 1);
|
|
_old_shift += _old_bits;
|
|
}
|
|
else {
|
|
if(_data_ptr >= _data_end) {
|
|
if(!FetchDataBlock()) {
|
|
// ti.End();
|
|
cptr = cp;
|
|
return false;
|
|
}
|
|
_data_ptr = data_ptr;
|
|
_data_end = data_end;
|
|
}
|
|
_old_byte |= *_data_ptr++ << 8;
|
|
if(_old_shift + _old_bits <= 16) {
|
|
code = (_old_byte >> _old_shift) & ((1 << _old_bits) - 1);
|
|
_old_shift += _old_bits - 8;
|
|
_old_byte >>= 8;
|
|
}
|
|
else {
|
|
if(_data_ptr >= _data_end) {
|
|
if(!FetchDataBlock()) {
|
|
// ti.End();
|
|
cptr = cp;
|
|
return false;
|
|
}
|
|
_data_ptr = data_ptr;
|
|
_data_end = data_end;
|
|
}
|
|
_old_byte |= *_data_ptr++ << 16;
|
|
ASSERT(_old_shift + _old_bits <= 24);
|
|
code = (_old_byte >> _old_shift) & ((1 << _old_bits) - 1);
|
|
_old_shift += _old_bits - 16;
|
|
_old_byte >>= 16;
|
|
}
|
|
}
|
|
// ti.End();
|
|
|
|
if(code < item_count) {
|
|
// TIMING("Fetch-existing code");
|
|
const Item& item = items[code];
|
|
if(item.length < 0)
|
|
if(item.length == -1) {
|
|
ClearTable();
|
|
_old_bits = old_bits;
|
|
}
|
|
else {
|
|
cptr = cp;
|
|
return false; // end of stream
|
|
}
|
|
else { // copy new pixels to output
|
|
byte *p = (cp += item.length);
|
|
byte first;
|
|
/*
|
|
|
|
__asm {
|
|
mov ebx, [p]
|
|
mov edx, [item]
|
|
dec ebx
|
|
mov al, [edx]item.character
|
|
mov [ebx], al
|
|
movzx ecx, [edx]item.length
|
|
dec ecx
|
|
cmp ecx, 8
|
|
jb __1
|
|
}
|
|
__2:
|
|
#define BYTE_AT(off) \
|
|
__asm mov edx, [edx]item.prefix \
|
|
__asm mov al, [edx]item.character \
|
|
__asm mov [ebx - 1], al
|
|
|
|
BYTE_AT(-1) BYTE_AT(-2) BYTE_AT(-3) BYTE_AT(-4)
|
|
BYTE_AT(-5) BYTE_AT(-6) BYTE_AT(-7) BYTE_AT(-8)
|
|
|
|
__asm {
|
|
sub ebx, 8
|
|
sub ecx, 8
|
|
cmp ecx, 8
|
|
jae __2
|
|
__1:
|
|
test cl, 4
|
|
je __no4
|
|
}
|
|
BYTE_AT(-1) BYTE_AT(-2) BYTE_AT(-3) BYTE_AT(-4)
|
|
|
|
__asm {
|
|
sub ebx, 4
|
|
__no4:
|
|
test cl, 2
|
|
je __no2
|
|
}
|
|
BYTE_AT(-2) BYTE_AT(-2)
|
|
|
|
__asm {
|
|
dec ebx
|
|
dec ebx
|
|
__no2:
|
|
test cl, 1
|
|
je __no1
|
|
}
|
|
BYTE_AT(-1)
|
|
__asm {
|
|
dec ebx
|
|
__no1:
|
|
mov al, [ebx]
|
|
mov [first], al
|
|
}
|
|
//*/
|
|
|
|
const Item *i = &item;
|
|
*--p = i->character;
|
|
int left = item.length;
|
|
if(--left > 0) {
|
|
for(; left >= 8; p -= 8, left -= 8) {
|
|
p[-1] = (i = i->prefix)->character;
|
|
p[-2] = (i = i->prefix)->character;
|
|
p[-3] = (i = i->prefix)->character;
|
|
p[-4] = (i = i->prefix)->character;
|
|
p[-5] = (i = i->prefix)->character;
|
|
p[-6] = (i = i->prefix)->character;
|
|
p[-7] = (i = i->prefix)->character;
|
|
p[-8] = (i = i->prefix)->character;
|
|
}
|
|
if(left & 4) {
|
|
p[-1] = (i = i->prefix)->character;
|
|
p[-2] = (i = i->prefix)->character;
|
|
p[-3] = (i = i->prefix)->character;
|
|
p[-4] = (i = i->prefix)->character;
|
|
p -= 4;
|
|
}
|
|
if(left & 2) {
|
|
p[-1] = (i = i->prefix)->character;
|
|
p[-2] = (i = i->prefix)->character;
|
|
p -= 2;
|
|
}
|
|
if(left & 1)
|
|
p[-1] = (i = i->prefix)->character;
|
|
}
|
|
first = i->character;
|
|
//*/
|
|
// while(i->prefix)
|
|
// *--p = (i = i->prefix)->character;
|
|
if(old_code >= 0 && item_count < MAX_COUNT) { // add to string table
|
|
Item& item = items[item_count++];
|
|
item.character = first;
|
|
item.prefix = &items[old_code];
|
|
item.length = item.prefix->length + 1;
|
|
}
|
|
if(item_count >= max_bit_count && item_count < MAX_COUNT) {
|
|
max_bit_count <<= 1;
|
|
++_old_bits;
|
|
}
|
|
old_code = code;
|
|
old_first = first;
|
|
}
|
|
}
|
|
else { // special case: duplicate first character of old_code
|
|
// TIMING("Fetch-new code");
|
|
if(old_code < 0 || code > item_count) { // decoder error
|
|
stream.SetError();
|
|
cptr = cp;
|
|
return false;
|
|
}
|
|
ASSERT(item_count < MAX_COUNT);
|
|
Item& item = items[item_count++];
|
|
item.prefix = &items[old_code];
|
|
item.length = item.prefix->length + 1;
|
|
item.character = old_first;
|
|
const Item *i = &item;
|
|
byte *p = (cp += item.length);
|
|
*--p = i->character;
|
|
while(i->prefix)
|
|
*--p = (i = i->prefix)->character;
|
|
if(item_count >= max_bit_count && item_count < MAX_COUNT) {
|
|
max_bit_count <<= 1;
|
|
++_old_bits;
|
|
}
|
|
old_code = code;
|
|
}
|
|
}
|
|
cptr = cp;
|
|
old_shift = _old_shift;
|
|
old_bits = _old_bits;
|
|
data_ptr = _data_ptr;
|
|
data_end = _data_end;
|
|
old_byte = _old_byte;
|
|
return true;
|
|
}
|
|
|
|
bool GifProcessor::LoadSubimage(int& transparent_index)
|
|
{
|
|
// TIMING("GifEncoder::LoadSubimage");
|
|
GifLocalInfo l;
|
|
if(!l.Load(stream))
|
|
return false;
|
|
|
|
if(l.transparent >= 0)
|
|
transparent_index = l.transparent;
|
|
|
|
if(l.x + l.width > temp_image.GetWidth()
|
|
|| l.y + l.height > temp_image.GetHeight())
|
|
return false; // out of bitmap rectangle
|
|
|
|
// ignore LCT
|
|
stream.Seek(l.dat_fpos);
|
|
|
|
if((start_bpp = stream.Get()) <= 0 || start_bpp > 12)
|
|
return false;
|
|
|
|
data_ptr = data_end = data_block; // empty data block
|
|
old_shift = 8;
|
|
old_byte = 0;
|
|
ClearTable();
|
|
|
|
PixelReader8 reader(temp_image.pixel);
|
|
PixelWriter8 writer(temp_image.pixel);
|
|
|
|
int cache = max(temp_image.GetWidth(), 1000);
|
|
Buffer<byte> data(cache + MAX_COUNT);
|
|
bool more = true;
|
|
byte *data_ptr = data, *data_end = data;
|
|
int pass = 0;
|
|
Size size = temp_image.GetSize();
|
|
for(int y = 0; y < size.cy;) {
|
|
if(more && data_ptr + size.cx > data_end) { // fetch more data
|
|
if(data_end > data_ptr)
|
|
Copy((byte *)data, data_ptr, data_end);
|
|
data_end -= (data_ptr - data);
|
|
data_ptr = data;
|
|
more = Fetch(data_end, data + cache);
|
|
}
|
|
int avail = min(size.cx, (int)intptr_t(data_end - data_ptr));
|
|
if(avail == 0)
|
|
break;
|
|
byte *wrt = writer[y];
|
|
if(avail < temp_image.GetWidth()) {
|
|
const byte *rd = reader[y];
|
|
if(rd != wrt)
|
|
memcpy(wrt, rd, temp_image.GetWidth());
|
|
}
|
|
memcpy(wrt, data_ptr, avail);
|
|
writer.Write();
|
|
data_ptr += avail;
|
|
if(l.interlace)
|
|
switch(pass) {
|
|
case 0: if((y += 8) < size.cy) break; y = 4 - 8; pass++;
|
|
case 1: if((y += 8) < size.cy) break; y = 2 - 4; pass++;
|
|
case 2: if((y += 4) < size.cy) break; y = 1 - 2; pass++;
|
|
case 3: y += 2; break;
|
|
}
|
|
else
|
|
y++;
|
|
}
|
|
while(FetchDataBlock()) // flush remaining data blocks
|
|
;
|
|
stream.Get(); // fetch terminating empty subblock
|
|
return true;
|
|
}
|
|
|
|
AlphaArray GifProcessor::Load()
|
|
{
|
|
ASSERT(stream.IsLoading());
|
|
int transparent_index = -1;
|
|
GifGlobalInfo g;
|
|
stream % g;
|
|
if(stream.IsError() || g.width <= 0 || g.height <= 0)
|
|
return temp_image;
|
|
temp_image.pixel.Create(g.width, g.height, g.gct_recs <= 2 ? 1 : g.gct_recs <= 16 ? 4 : 8);
|
|
stream.Seek(g.gct_fpos);
|
|
for(int i = 0; i < g.gct_recs; i++) {
|
|
int r = stream.Get(), g = stream.Get(), b = stream.Get();
|
|
temp_image.pixel.palette.Add(Color(r, g, b));
|
|
}
|
|
stream.Seek(g.loc_fpos);
|
|
while(LoadSubimage(transparent_index))
|
|
;
|
|
if(stream.Term() == 0x3B)
|
|
stream.Get();
|
|
|
|
if(transparent_index >= 0) {
|
|
temp_image.pixel.palette.DoIndex(transparent_index) = Black;
|
|
temp_image.alpha.CreateMono(g.width, g.height, 1);
|
|
byte xlat[256];
|
|
ZeroArray(xlat);
|
|
xlat[transparent_index] = 1;
|
|
PixelReader8 reader(temp_image.pixel);
|
|
PixelWriter8 writer(temp_image.alpha);
|
|
for(int y = 0; y < g.height; y++) {
|
|
BltXlatB(writer[y], reader[y], g.width, xlat);
|
|
writer.Write();
|
|
}
|
|
}
|
|
|
|
return temp_image;
|
|
}
|
|
|
|
GifEncoder::GifEncoder(bool optimize_palette, String comment)
|
|
: optimize_palette(optimize_palette), comment(comment) {}
|
|
|
|
GifEncoder::~GifEncoder() {}
|
|
|
|
Array<AlphaArray> GifEncoder::LoadRaw(Stream& _stream, const Vector<int>& page_index)
|
|
{
|
|
// RTIMING("GifEncoder::LoadRaw");
|
|
static PixelArray dummy;
|
|
Array<AlphaArray> out;
|
|
out.Add() = GifProcessor(_stream, NULL).Load();
|
|
return out;
|
|
}
|
|
|
|
void GifEncoder::SaveRaw(Stream& stream, const Vector<const AlphaArray *>& pages)
|
|
{
|
|
if(pages.GetCount() != 1) {
|
|
stream.SetError();
|
|
return;
|
|
}
|
|
GifProcessor(stream, pages[0]).Save(optimize_palette, comment);
|
|
}
|
|
|
|
Array<ImageInfo> GifEncoder::InfoRaw(Stream& stream)
|
|
{
|
|
Array<ImageInfo> out;
|
|
out.SetCount(1);
|
|
ImageInfo& info = out[0];
|
|
if(!stream.IsOpen())
|
|
return Array<ImageInfo>();
|
|
ASSERT(stream.IsLoading());
|
|
GifGlobalInfo g;
|
|
stream % g;
|
|
if(stream.IsError())
|
|
return Array<ImageInfo>();
|
|
info.size = Size(g.width, g.height);
|
|
info.bits_per_pixel = g.bpp;
|
|
return out;
|
|
}
|
|
#endif
|
|
|
|
class GIFRaster::Data {
|
|
public:
|
|
Data(GIFRaster& owner, Stream& stream);
|
|
~Data();
|
|
|
|
bool Create();
|
|
Raster::Line GetLine(int line);
|
|
const RasterFormat *GetFormat() const { return &format; }
|
|
int GetPaletteCount() { return 256; }
|
|
RGBA *GetPalette() { return palette; }
|
|
|
|
public:
|
|
Size size;
|
|
Raster::Info info;
|
|
|
|
private:
|
|
void ClearTable();
|
|
bool LoadSubimage(int& transparent_index);
|
|
bool FetchDataBlock();
|
|
bool Fetch(byte *&cptr, const byte *cend);
|
|
|
|
private:
|
|
GIFRaster& owner;
|
|
Stream& stream;
|
|
RasterFormat format;
|
|
RGBA palette[256];
|
|
GifGlobalInfo ggi;
|
|
Vector<byte> temp;
|
|
bool first_row;
|
|
|
|
static const int MAX_BITS = 12;
|
|
static const int MAX_COUNT = 1 << MAX_BITS;
|
|
static const int HASH_COUNT = 5331;
|
|
|
|
struct Item {
|
|
union {
|
|
const Item *prefix;
|
|
Item *child;
|
|
};
|
|
Item *left, *right;
|
|
short length;
|
|
byte character;
|
|
};
|
|
|
|
Item items[MAX_COUNT];
|
|
int item_count;
|
|
byte old_first;
|
|
int old_code;
|
|
int old_bits;
|
|
int max_bit_count;
|
|
int start_bpp;
|
|
byte *data_ptr;
|
|
byte *data_end;
|
|
byte data_block[256];
|
|
dword old_byte;
|
|
int old_shift;
|
|
};
|
|
|
|
GIFRaster::Data::Data(GIFRaster& owner, Stream& stream)
|
|
: owner(owner), stream(stream)
|
|
{
|
|
}
|
|
|
|
GIFRaster::Data::~Data()
|
|
{
|
|
}
|
|
|
|
void GIFRaster::Data::ClearTable()
|
|
{
|
|
for(int i = 0, n = 1 << start_bpp; i < n; i++) {
|
|
Item& item = items[i];
|
|
item.character = i;
|
|
item.length = 1;
|
|
item.prefix = 0;
|
|
}
|
|
item_count = (1 << start_bpp);
|
|
items[item_count++].length = -1;
|
|
items[item_count++].length = -2;
|
|
old_code = -1;
|
|
old_bits = start_bpp;
|
|
max_bit_count = 1 << old_bits;
|
|
while(item_count > max_bit_count) {
|
|
old_bits++;
|
|
max_bit_count <<= 1;
|
|
}
|
|
}
|
|
|
|
bool GIFRaster::Data::FetchDataBlock()
|
|
{
|
|
// TIMING("GifEncoder::FetchDataBlock");
|
|
|
|
int count = stream.Term();
|
|
if(count <= 0)
|
|
return false;
|
|
stream.Get();
|
|
if(!stream.GetAll(data_block, count))
|
|
return false;
|
|
data_ptr = data_block;
|
|
data_end = data_ptr + count;
|
|
return true;
|
|
}
|
|
|
|
bool GIFRaster::Data::Fetch(byte *&cptr, const byte *cend)
|
|
{
|
|
// TIMING("GifEncoder::Fetch");
|
|
byte *cp = cptr;
|
|
byte _old_shift = old_shift;
|
|
byte _old_bits = old_bits;
|
|
byte *_data_ptr = data_ptr;
|
|
byte *_data_end = data_end;
|
|
dword _old_byte = old_byte;
|
|
while(cp < cend) {
|
|
// static TimingInspector ti("Fetch-Get");
|
|
// ti.Start();
|
|
int code;
|
|
if(_old_shift + _old_bits <= 8) {
|
|
code = (_old_byte >> _old_shift) & ((1 << _old_bits) - 1);
|
|
_old_shift += _old_bits;
|
|
}
|
|
else {
|
|
if(_data_ptr >= _data_end) {
|
|
if(!FetchDataBlock()) {
|
|
// ti.End();
|
|
cptr = cp;
|
|
return false;
|
|
}
|
|
_data_ptr = data_ptr;
|
|
_data_end = data_end;
|
|
}
|
|
_old_byte |= *_data_ptr++ << 8;
|
|
if(_old_shift + _old_bits <= 16) {
|
|
code = (_old_byte >> _old_shift) & ((1 << _old_bits) - 1);
|
|
_old_shift += _old_bits - 8;
|
|
_old_byte >>= 8;
|
|
}
|
|
else {
|
|
if(_data_ptr >= _data_end) {
|
|
if(!FetchDataBlock()) {
|
|
// ti.End();
|
|
cptr = cp;
|
|
return false;
|
|
}
|
|
_data_ptr = data_ptr;
|
|
_data_end = data_end;
|
|
}
|
|
_old_byte |= *_data_ptr++ << 16;
|
|
ASSERT(_old_shift + _old_bits <= 24);
|
|
code = (_old_byte >> _old_shift) & ((1 << _old_bits) - 1);
|
|
_old_shift += _old_bits - 16;
|
|
_old_byte >>= 16;
|
|
}
|
|
}
|
|
// ti.End();
|
|
|
|
if(code < item_count) {
|
|
// TIMING("Fetch-existing code");
|
|
const Item& item = items[code];
|
|
if(item.length < 0)
|
|
if(item.length == -1) {
|
|
ClearTable();
|
|
_old_bits = old_bits;
|
|
}
|
|
else {
|
|
cptr = cp;
|
|
return false; // end of stream
|
|
}
|
|
else { // copy new pixels to output
|
|
byte *p = (cp += item.length);
|
|
byte first;
|
|
const Item *i = &item;
|
|
*--p = i->character;
|
|
int left = item.length;
|
|
if(--left > 0) {
|
|
for(; left >= 8; p -= 8, left -= 8) {
|
|
p[-1] = (i = i->prefix)->character;
|
|
p[-2] = (i = i->prefix)->character;
|
|
p[-3] = (i = i->prefix)->character;
|
|
p[-4] = (i = i->prefix)->character;
|
|
p[-5] = (i = i->prefix)->character;
|
|
p[-6] = (i = i->prefix)->character;
|
|
p[-7] = (i = i->prefix)->character;
|
|
p[-8] = (i = i->prefix)->character;
|
|
}
|
|
if(left & 4) {
|
|
p[-1] = (i = i->prefix)->character;
|
|
p[-2] = (i = i->prefix)->character;
|
|
p[-3] = (i = i->prefix)->character;
|
|
p[-4] = (i = i->prefix)->character;
|
|
p -= 4;
|
|
}
|
|
if(left & 2) {
|
|
p[-1] = (i = i->prefix)->character;
|
|
p[-2] = (i = i->prefix)->character;
|
|
p -= 2;
|
|
}
|
|
if(left & 1)
|
|
p[-1] = (i = i->prefix)->character;
|
|
}
|
|
first = i->character;
|
|
//*/
|
|
// while(i->prefix)
|
|
// *--p = (i = i->prefix)->character;
|
|
if(old_code >= 0 && item_count < MAX_COUNT) { // add to string table
|
|
Item& item = items[item_count++];
|
|
item.character = first;
|
|
item.prefix = &items[old_code];
|
|
item.length = item.prefix->length + 1;
|
|
}
|
|
if(item_count >= max_bit_count && item_count < MAX_COUNT) {
|
|
max_bit_count <<= 1;
|
|
++_old_bits;
|
|
}
|
|
old_code = code;
|
|
old_first = first;
|
|
}
|
|
}
|
|
else { // special case: duplicate first character of old_code
|
|
// TIMING("Fetch-new code");
|
|
if(old_code < 0 || code > item_count) { // decoder error
|
|
stream.SetError();
|
|
cptr = cp;
|
|
return false;
|
|
}
|
|
ASSERT(item_count < MAX_COUNT);
|
|
Item& item = items[item_count++];
|
|
item.prefix = &items[old_code];
|
|
item.length = item.prefix->length + 1;
|
|
item.character = old_first;
|
|
const Item *i = &item;
|
|
byte *p = (cp += item.length);
|
|
*--p = i->character;
|
|
while(i->prefix)
|
|
*--p = (i = i->prefix)->character;
|
|
if(item_count >= max_bit_count && item_count < MAX_COUNT) {
|
|
max_bit_count <<= 1;
|
|
++_old_bits;
|
|
}
|
|
old_code = code;
|
|
}
|
|
}
|
|
cptr = cp;
|
|
old_shift = _old_shift;
|
|
old_bits = _old_bits;
|
|
data_ptr = _data_ptr;
|
|
data_end = _data_end;
|
|
old_byte = _old_byte;
|
|
return true;
|
|
}
|
|
|
|
bool GIFRaster::Data::LoadSubimage(int& transparent_index)
|
|
{
|
|
// TIMING("GifEncoder::LoadSubimage");
|
|
GifLocalInfo l;
|
|
if(!l.Load(stream))
|
|
return false;
|
|
|
|
if(l.transparent >= 0)
|
|
transparent_index = l.transparent;
|
|
|
|
if(l.x + l.width > size.cx || l.y + l.height > size.cy)
|
|
return false; // out of bitmap rectangle
|
|
|
|
// ignore LCT
|
|
stream.Seek(l.dat_fpos);
|
|
|
|
if((start_bpp = stream.Get()) <= 0 || start_bpp > 12)
|
|
return false;
|
|
|
|
data_ptr = data_end = data_block; // empty data block
|
|
old_shift = 8;
|
|
old_byte = 0;
|
|
ClearTable();
|
|
|
|
int cache = max(size.cx, 1000);
|
|
Buffer<byte> data(cache + MAX_COUNT);
|
|
bool more = true;
|
|
byte *data_ptr = data, *data_end = data;
|
|
int pass = 0;
|
|
for(int y = 0; y < size.cy;) {
|
|
if(more && data_ptr + size.cx > data_end) { // fetch more data
|
|
if(data_end > data_ptr)
|
|
Copy((byte *)data, data_ptr, data_end);
|
|
data_end -= (data_ptr - data);
|
|
data_ptr = data;
|
|
more = Fetch(data_end, data + cache);
|
|
}
|
|
int avail = min(size.cx, (int)intptr_t(data_end - data_ptr));
|
|
if(avail == 0)
|
|
break;
|
|
byte *wrt = &temp[y * size.cx];
|
|
memcpy(wrt, data_ptr, avail);
|
|
data_ptr += avail;
|
|
if(l.interlace)
|
|
switch(pass) {
|
|
case 0: if((y += 8) < size.cy) break; y = 4 - 8; pass++;
|
|
case 1: if((y += 8) < size.cy) break; y = 2 - 4; pass++;
|
|
case 2: if((y += 4) < size.cy) break; y = 1 - 2; pass++;
|
|
case 3: y += 2; break;
|
|
}
|
|
else
|
|
y++;
|
|
}
|
|
while(FetchDataBlock()) // flush remaining data blocks
|
|
;
|
|
stream.Get(); // fetch terminating empty subblock
|
|
return true;
|
|
}
|
|
|
|
bool GIFRaster::Data::Create()
|
|
{
|
|
stream % ggi;
|
|
if(stream.IsError() || ggi.width <= 0 || ggi.height <= 0)
|
|
return false;
|
|
size.cx = ggi.width;
|
|
size.cy = ggi.height;
|
|
info.bpp = 8;
|
|
info.colors = ggi.gct_recs;
|
|
info.dots = size;
|
|
info.hotspot = Null;
|
|
info.kind = IMAGE_UNKNOWN;
|
|
temp.Clear();
|
|
first_row = true;
|
|
|
|
stream.Seek(ggi.gct_fpos);
|
|
for(int i = 0; i < ggi.gct_recs; i++) {
|
|
palette[i].r = stream.Get();
|
|
palette[i].g = stream.Get();
|
|
palette[i].b = stream.Get();
|
|
palette[i].a = 255;
|
|
}
|
|
format.Set8();
|
|
return true;
|
|
}
|
|
|
|
Raster::Line GIFRaster::Data::GetLine(int line)
|
|
{
|
|
if(first_row) {
|
|
first_row = false;
|
|
temp.SetCount(size.cx * size.cy, 0);
|
|
|
|
stream.Seek(ggi.loc_fpos);
|
|
int transparent_index = -1;
|
|
while(LoadSubimage(transparent_index))
|
|
;
|
|
if(stream.Term() == 0x3B)
|
|
stream.Get();
|
|
|
|
if(transparent_index >= 0 && transparent_index < 256)
|
|
palette[transparent_index] = RGBAZero();
|
|
}
|
|
return Raster::Line(&temp[size.cx * line], &owner, false);
|
|
}
|
|
|
|
GIFRaster::GIFRaster()
|
|
{
|
|
}
|
|
|
|
GIFRaster::~GIFRaster()
|
|
{
|
|
}
|
|
|
|
bool GIFRaster::Create()
|
|
{
|
|
data = new Data(*this, GetStream());
|
|
return data->Create();
|
|
}
|
|
|
|
Size GIFRaster::GetSize()
|
|
{
|
|
return data->size;
|
|
}
|
|
|
|
Raster::Info GIFRaster::GetInfo()
|
|
{
|
|
return data->info;
|
|
}
|
|
|
|
Raster::Line GIFRaster::GetLine(int line)
|
|
{
|
|
return data->GetLine(line);
|
|
}
|
|
|
|
const RasterFormat *GIFRaster::GetFormat()
|
|
{
|
|
return data->GetFormat();
|
|
}
|
|
|
|
int GIFRaster::GetPaletteCount()
|
|
{
|
|
return data->GetPaletteCount();
|
|
}
|
|
|
|
RGBA *GIFRaster::GetPalette()
|
|
{
|
|
return data->GetPalette();
|
|
}
|
|
|
|
class GIFEncoder::Data {
|
|
public:
|
|
Data(Stream& stream, RasterFormat& format);
|
|
~Data();
|
|
|
|
void Start(Size sz, bool ignore_alpha, String comment, const RGBA *palette);
|
|
void WriteLineRaw(const byte *s);
|
|
|
|
private:
|
|
void FlushDataBlock();
|
|
void ClearTable();
|
|
|
|
private:
|
|
static const int MAX_BITS = 12;
|
|
static const int MAX_COUNT = 1 << MAX_BITS;
|
|
static const int HASH_COUNT = 5331;
|
|
|
|
RasterFormat& format;
|
|
Stream& stream;
|
|
|
|
struct Item {
|
|
union {
|
|
const Item *prefix;
|
|
Item *child;
|
|
};
|
|
Item *left, *right;
|
|
short length;
|
|
byte character;
|
|
};
|
|
|
|
Item items[MAX_COUNT];
|
|
int item_count;
|
|
int start_bpp;
|
|
byte data_block[256];
|
|
byte *data_ptr;
|
|
byte *data_end;
|
|
int old_code;
|
|
int old_bits;
|
|
int max_bit_count;
|
|
|
|
int _old_shift;
|
|
int _old_byte;
|
|
int _old_bits;
|
|
int restart_code;
|
|
|
|
Size size;
|
|
int ypos;
|
|
|
|
Item *i;
|
|
Item *p;
|
|
Item *new_item;
|
|
};
|
|
|
|
GIFEncoder::Data::Data(Stream& stream, RasterFormat& format)
|
|
: format(format), stream(stream)
|
|
{
|
|
}
|
|
|
|
GIFEncoder::Data::~Data()
|
|
{
|
|
}
|
|
|
|
void GIFEncoder::Data::FlushDataBlock()
|
|
{
|
|
// TIMING("GifEncoder::FlushDataBlock");
|
|
|
|
int count = (int)(uintptr_t)(data_ptr - data_block);
|
|
if(count) {
|
|
ASSERT(count <= 255);
|
|
stream.Put(count);
|
|
stream.Put(data_block, count);
|
|
data_ptr = data_block;
|
|
}
|
|
}
|
|
|
|
void GIFEncoder::Data::ClearTable()
|
|
{
|
|
for(int i = 0, n = 1 << start_bpp; i < n; i++) {
|
|
Item& item = items[i];
|
|
item.character = i;
|
|
item.length = 1;
|
|
item.left = item.right = item.child = 0;
|
|
}
|
|
item_count = (1 << start_bpp);
|
|
items[item_count++].length = -1;
|
|
items[item_count++].length = -2;
|
|
old_code = -1;
|
|
old_bits = start_bpp;
|
|
max_bit_count = 1 << old_bits;
|
|
while(item_count > max_bit_count) {
|
|
old_bits++;
|
|
max_bit_count <<= 1;
|
|
}
|
|
}
|
|
|
|
void GIFEncoder::Data::Start(Size sz, bool ignore_alpha, String comment, const RGBA *palette)
|
|
{
|
|
size = sz;
|
|
format.Set8();
|
|
|
|
int transparent_index = -1;
|
|
if(!ignore_alpha)
|
|
transparent_index = 255;
|
|
start_bpp = 8;
|
|
// for(start_bpp = 1; start_bpp < 8 && raw_image->pixel.palette.GetCount() > (1 << start_bpp); start_bpp++)
|
|
// ;
|
|
|
|
GifGlobalInfo g;
|
|
memcpy(g.tag, !IsNull(comment) ? "GIF89a" : "GIF87a", 6);
|
|
g.width = sz.cx;
|
|
g.height = sz.cy;
|
|
g.flags = g.GCT_PRESENT | ((start_bpp - 1) << g.GCT_BPP_SHIFT) | (start_bpp - 1);
|
|
// if(optimize_palette)
|
|
// g.flags |= g.GCT_SORTED;
|
|
g.bgnd = 0;
|
|
g.raw_aspect = 0;
|
|
|
|
stream % g;
|
|
{ // write palette
|
|
for(int i = 0, n = 1 << start_bpp; i < n; i++) {
|
|
RGBA rgba = (i == transparent_index ? RGBAZero() : palette[i]);
|
|
stream.Put(rgba.r);
|
|
stream.Put(rgba.g);
|
|
stream.Put(rgba.b);
|
|
}
|
|
}
|
|
|
|
// write comment block
|
|
if(!IsNull(comment)) {
|
|
stream.Put(0x21);
|
|
stream.Put(0xFE);
|
|
for(int t = 0, n; (n = min(comment.GetLength() - t, 255)) > 0; t += n) {
|
|
stream.Put(n);
|
|
stream.Put(comment.Begin() + t, n);
|
|
}
|
|
stream.Put(0x00);
|
|
}
|
|
|
|
// store transparent color index
|
|
if(transparent_index >= 0) {
|
|
stream.Put(0x21);
|
|
stream.Put(0xF9);
|
|
stream.Put(4);
|
|
stream.Put(1); // transparent index present
|
|
stream.Put16le(0); // delay time = 0
|
|
stream.Put(transparent_index);
|
|
stream.Put(0);
|
|
}
|
|
|
|
if(start_bpp == 1)
|
|
start_bpp++;
|
|
|
|
stream.Put(0x2C);
|
|
stream.Put16le(0);
|
|
stream.Put16le(0);
|
|
stream.Put16le(sz.cx);
|
|
stream.Put16le(sz.cy);
|
|
stream.Put(0x00); // no LCT, non-interlaced, no sort, LCT size-1 = 0
|
|
|
|
data_ptr = data_block;
|
|
data_end = data_ptr + 255;
|
|
ClearTable();
|
|
|
|
stream.Put(start_bpp); // start bits
|
|
|
|
ypos = 0;
|
|
_old_shift = 0;
|
|
_old_byte = 0;
|
|
_old_bits = old_bits;
|
|
|
|
// int _lsh = start_bpp, _rsh = 32 - start_bpp;
|
|
|
|
// static TimingInspector ti_put("PUT_CODE");
|
|
|
|
#define PUT_CODE(code) \
|
|
do { \
|
|
/*TimingInspector::Routine r(ti_put);*/ \
|
|
_old_byte |= (code) << _old_shift; \
|
|
_old_shift += _old_bits; \
|
|
if(_old_shift >= 16) { \
|
|
if(data_ptr >= data_end) \
|
|
FlushDataBlock(); \
|
|
*data_ptr++ = _old_byte; \
|
|
if(data_ptr >= data_end) \
|
|
FlushDataBlock(); \
|
|
*data_ptr++ = _old_byte >>= 8; \
|
|
_old_byte >>= 8; \
|
|
_old_shift -= 16; \
|
|
} \
|
|
else if(_old_shift >= 8) { \
|
|
if(data_ptr >= data_end) \
|
|
FlushDataBlock(); \
|
|
*data_ptr++ = _old_byte; \
|
|
_old_byte >>= 8; \
|
|
_old_shift -= 8; \
|
|
} \
|
|
} \
|
|
while(false)
|
|
|
|
restart_code = 1 << start_bpp;
|
|
PUT_CODE(restart_code);
|
|
|
|
i = new_item = NULL;
|
|
}
|
|
|
|
void GIFEncoder::Data::WriteLineRaw(const byte *sp)
|
|
{
|
|
const byte *se = sp + size.cx;
|
|
|
|
if(!i) {
|
|
i = &items[*sp++];
|
|
new_item = &items[item_count];
|
|
ASSERT(i >= items && i <= &items[MAX_COUNT]);
|
|
}
|
|
|
|
for(;;) {
|
|
SEEK:
|
|
if(sp >= se)
|
|
break;
|
|
byte c = *sp++;
|
|
if((i = (p = i)->child) == 0)
|
|
p->child = new_item;
|
|
else
|
|
for(;;) {
|
|
if(i->character == c)
|
|
goto SEEK;
|
|
if(c < i->character) {
|
|
if(i->left)
|
|
i = i->left;
|
|
else {
|
|
i->left = new_item;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if(i->right)
|
|
i = i->right;
|
|
else {
|
|
i = i->right = new_item;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// code not found - rewind last char & output current prefix, add new code to table
|
|
sp--;
|
|
PUT_CODE(p - items);
|
|
if(item_count < MAX_COUNT) { // add new entry to item table
|
|
if(item_count >= max_bit_count) {
|
|
max_bit_count <<= 1;
|
|
_old_bits++;
|
|
}
|
|
new_item->character = c;
|
|
new_item->left = new_item->right = new_item->child = 0;
|
|
item_count++;
|
|
}
|
|
else { // restart table
|
|
PUT_CODE(restart_code);
|
|
ClearTable();
|
|
_old_bits = old_bits;
|
|
}
|
|
|
|
i = &items[*sp++];
|
|
new_item = &items[item_count];
|
|
ASSERT(i >= items && i <= &items[MAX_COUNT]);
|
|
}
|
|
|
|
if(++ypos >= size.cy) {
|
|
PUT_CODE(i - items);
|
|
// put terminator code & close output block
|
|
PUT_CODE(restart_code + 1);
|
|
if(_old_shift > 0) {
|
|
if(data_ptr >= data_end)
|
|
FlushDataBlock();
|
|
*data_ptr++ = _old_byte;
|
|
}
|
|
FlushDataBlock();
|
|
|
|
stream.Put(0x00);
|
|
stream.Put(0x3B);
|
|
}
|
|
}
|
|
|
|
GIFEncoder::GIFEncoder(bool ignore_alpha_, String comment_)
|
|
: ignore_alpha(ignore_alpha_), comment(comment_)
|
|
{
|
|
}
|
|
|
|
GIFEncoder::~GIFEncoder()
|
|
{
|
|
}
|
|
|
|
int GIFEncoder::GetPaletteCount()
|
|
{
|
|
return 255;
|
|
}
|
|
|
|
void GIFEncoder::Start(Size sz)
|
|
{
|
|
data = new Data(GetStream(), format);
|
|
data->Start(sz, ignore_alpha, comment, GetPalette());
|
|
}
|
|
|
|
void GIFEncoder::WriteLineRaw(const byte *s)
|
|
{
|
|
data->WriteLineRaw(s);
|
|
}
|
|
|
|
END_UPP_NAMESPACE
|