#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 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 GifEncoder::LoadRaw(Stream& _stream, const Vector& page_index) { // RTIMING("GifEncoder::LoadRaw"); static PixelArray dummy; Array out; out.Add() = GifProcessor(_stream, NULL).Load(); return out; } void GifEncoder::SaveRaw(Stream& stream, const Vector& pages) { if(pages.GetCount() != 1) { stream.SetError(); return; } GifProcessor(stream, pages[0]).Save(optimize_palette, comment); } Array GifEncoder::InfoRaw(Stream& stream) { Array out; out.SetCount(1); ImageInfo& info = out[0]; if(!stream.IsOpen()) return Array(); ASSERT(stream.IsLoading()); GifGlobalInfo g; stream % g; if(stream.IsError()) return Array(); 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 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 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