2
0
mirror of https://github.com/ars3niy/tdlib-purple synced 2025-08-22 01:49:29 +00:00

Sped up gif compression for animated stickers close to 1.5x

This commit is contained in:
Arseniy Lartsev 2020-10-13 22:13:57 +02:00
parent 35ea0d0c1b
commit 581bf83a06

319
gif.h
View File

@ -135,25 +135,9 @@ static void GifGetClosestPaletteColor(GifPalette* pPal, int r, int g, int b, int
static void GifSwapPixels(uint8_t* image, int pixA, int pixB) static void GifSwapPixels(uint8_t* image, int pixA, int pixB)
{ {
uint8_t rA = image[pixA*4]; uint32_t *pA = reinterpret_cast<uint32_t *>(image) + pixA;
uint8_t gA = image[pixA*4+1]; uint32_t *pB = reinterpret_cast<uint32_t *>(image) + pixB;
uint8_t bA = image[pixA*4+2]; std::swap(*pA, *pB);
uint8_t aA = image[pixA*4+3];
uint8_t rB = image[pixB*4];
uint8_t gB = image[pixB*4+1];
uint8_t bB = image[pixB*4+2];
uint8_t aB = image[pixA*4+3];
image[pixA*4] = rB;
image[pixA*4+1] = gB;
image[pixA*4+2] = bB;
image[pixA*4+3] = aB;
image[pixB*4] = rA;
image[pixB*4+1] = gA;
image[pixB*4+2] = bA;
image[pixB*4+3] = aA;
} }
// just the partition operation from quicksort // just the partition operation from quicksort
@ -255,21 +239,23 @@ static void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int las
} }
// otherwise, take the average of all colors in this subcube // otherwise, take the average of all colors in this subcube
uint64_t r=0, g=0, b=0; uint32_t r=0, g=0, b=0;
const uint8_t *pixel = image;
for(int ii=0; ii<numPixels; ++ii) for(int ii=0; ii<numPixels; ++ii)
{ {
r += image[ii*4+0]; r += pixel[0];
g += image[ii*4+1]; g += pixel[1];
b += image[ii*4+2]; b += pixel[2];
pixel += 4;
} }
r += (uint64_t)numPixels / 2; // round to nearest r += numPixels / 2; // round to nearest
g += (uint64_t)numPixels / 2; g += numPixels / 2;
b += (uint64_t)numPixels / 2; b += numPixels / 2;
r /= (uint64_t)numPixels; r /= (uint32_t)numPixels;
g /= (uint64_t)numPixels; g /= (uint32_t)numPixels;
b /= (uint64_t)numPixels; b /= (uint32_t)numPixels;
pal->r[firstElt] = (uint8_t)r; pal->r[firstElt] = (uint8_t)r;
pal->g[firstElt] = (uint8_t)g; pal->g[firstElt] = (uint8_t)g;
@ -279,14 +265,16 @@ static void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int las
} }
// Find the axis with the largest range // Find the axis with the largest range
int minR = 255, maxR = 0; uint8_t minR = 255, maxR = 0;
int minG = 255, maxG = 0; uint8_t minG = 255, maxG = 0;
int minB = 255, maxB = 0; uint8_t minB = 255, maxB = 0;
const uint8_t *pixel = image;
for(int ii=0; ii<numPixels; ++ii) for(int ii=0; ii<numPixels; ++ii)
{ {
int r = image[ii*4+0]; uint8_t r = pixel[0];
int g = image[ii*4+1]; uint8_t g = pixel[1];
int b = image[ii*4+2]; uint8_t b = pixel[2];
pixel += 4;
if(r > maxR) maxR = r; if(r > maxR) maxR = r;
if(r < minR) minR = r; if(r < minR) minR = r;
@ -298,9 +286,9 @@ static void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int las
if(b < minB) minB = b; if(b < minB) minB = b;
} }
int rRange = maxR - minR; uint8_t rRange = maxR - minR;
int gRange = maxG - minG; uint8_t gRange = maxG - minG;
int bRange = maxB - minB; uint8_t bRange = maxB - minB;
// and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it) // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it)
int splitCom = 1; int splitCom = 1;
@ -328,7 +316,7 @@ static int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int n
int numChanged = 0; int numChanged = 0;
uint8_t* writeIter = frame; uint8_t* writeIter = frame;
for (int ii=0; ii<numPixels; ++ii) for (int ii=0; ii<numPixels; ii += 4)
{ {
if(lastFrame[0] != frame[0] || if(lastFrame[0] != frame[0] ||
lastFrame[1] != frame[1] || lastFrame[1] != frame[1] ||
@ -340,8 +328,8 @@ static int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int n
++numChanged; ++numChanged;
writeIter += 4; writeIter += 4;
} }
lastFrame += 4; lastFrame += 16;
frame += 4; frame += 16;
} }
return numChanged; return numChanged;
@ -487,49 +475,29 @@ static void GifDitherImage( const uint8_t* lastFrame, const uint8_t* nextFrame,
GIF_TEMP_FREE(quantPixels); GIF_TEMP_FREE(quantPixels);
} }
// Picks palette colors for the image using simple thresholding, no dithering // The LZW dictionary is a 256-ary tree constructed as the file is encoded,
static void GifThresholdImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, bool transparent, GifPalette* pPal ) // this is one node
struct GifLzwNode
{ {
uint32_t numPixels = width*height; uint16_t m_next[256];
for( uint32_t ii=0; ii<numPixels; ++ii ) };
// write a 256-color (8-bit) image palette to the file
static void GifWritePalette( const GifPalette* pPal, FILE* f )
{
fputc(0, f); // first color: transparency
fputc(0, f);
fputc(0, f);
for(int ii=1; ii<(1 << pPal->bitDepth); ++ii)
{ {
if (transparent && (nextFrame[3] == 0)) uint32_t r = pPal->r[ii];
{ uint32_t g = pPal->g[ii];
outFrame[0] = nextFrame[0]; uint32_t b = pPal->b[ii];
outFrame[1] = nextFrame[1];
outFrame[2] = nextFrame[2];
outFrame[3] = kGifTransIndex;
}
// For non-transparet background:
// if a previous color is available, and it matches the current color,
// set the pixel to transparent
else if(!transparent && lastFrame &&
lastFrame[0] == nextFrame[0] &&
lastFrame[1] == nextFrame[1] &&
lastFrame[2] == nextFrame[2])
{
outFrame[0] = lastFrame[0];
outFrame[1] = lastFrame[1];
outFrame[2] = lastFrame[2];
outFrame[3] = kGifTransIndex;
}
else
{
// palettize the pixel
int32_t bestDiff = 1000000;
int32_t bestInd = 1;
GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], bestInd, bestDiff);
// Write the resulting color to the output buffer fputc((int)r, f);
outFrame[0] = pPal->r[bestInd]; fputc((int)g, f);
outFrame[1] = pPal->g[bestInd]; fputc((int)b, f);
outFrame[2] = pPal->b[bestInd];
outFrame[3] = (uint8_t)bestInd;
}
if(lastFrame) lastFrame += 4;
outFrame += 4;
nextFrame += 4;
} }
} }
@ -567,55 +535,38 @@ static void GifWriteChunk( FILE* f, GifBitStatus& stat )
{ {
fputc((int)stat.chunkIndex, f); fputc((int)stat.chunkIndex, f);
fwrite(stat.chunk, 1, stat.chunkIndex, f); fwrite(stat.chunk, 1, stat.chunkIndex, f);
stat.bitIndex = 0;
stat.byte = 0;
stat.chunkIndex = 0; stat.chunkIndex = 0;
} }
static void GifWriteCode( FILE* f, GifBitStatus& stat, uint32_t code, uint32_t length ) static void GifWriteCode( FILE* f, GifBitStatus& stat, uint32_t code, uint32_t length )
{ {
for( uint32_t ii=0; ii<length; ++ii ) uint8_t value = code << stat.bitIndex;
{ if (stat.bitIndex + length < 8) {
GifWriteBit(stat, code); stat.byte |= value;
code = code >> 1; } else {
stat.chunk[stat.chunkIndex++] = stat.byte | value;
if( stat.chunkIndex == 255 ) uint8_t part = 8-stat.bitIndex;
{ length -= part;
GifWriteChunk(f, stat); code = code >> part;
while (length >= 8) {
stat.chunk[stat.chunkIndex++] = code;
code = code >> 8;
length -= 8;
} }
stat.byte = code;
stat.bitIndex = length;
} }
if (stat.chunkIndex >= 250)
GifWriteChunk(f, stat);
} }
// The LZW dictionary is a 256-ary tree constructed as the file is encoded, // Picks palette colors for the image using simple thresholding, no dithering
// this is one node static void GifThresholdImageAndWrite(FILE* f, const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, uint32_t delay, bool transparent, GifPalette* pPal )
struct GifLzwNode
{
uint16_t m_next[256];
};
// write a 256-color (8-bit) image palette to the file
static void GifWritePalette( const GifPalette* pPal, FILE* f )
{
fputc(0, f); // first color: transparency
fputc(0, f);
fputc(0, f);
for(int ii=1; ii<(1 << pPal->bitDepth); ++ii)
{
uint32_t r = pPal->r[ii];
uint32_t g = pPal->g[ii];
uint32_t b = pPal->b[ii];
fputc((int)r, f);
fputc((int)g, f);
fputc((int)b, f);
}
}
// write the image header, LZW-compress and write out the image
static void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, bool transparent, GifPalette* pPal)
{ {
enum {left = 0, top = 0};
// graphics control extension // graphics control extension
fputc(0x21, f); fputc(0x21, f);
fputc(0xf9, f); fputc(0xf9, f);
@ -652,9 +603,10 @@ static void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t to
fputc(minCodeSize, f); // min code size 8 bits fputc(minCodeSize, f); // min code size 8 bits
GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode)*4096); enum {DICT_SIZE = 1024};
GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode)*DICT_SIZE);
memset(codetree, 0, sizeof(GifLzwNode)*4096); memset(codetree, 0, sizeof(GifLzwNode)*DICT_SIZE);
int32_t curCode = -1; int32_t curCode = -1;
uint32_t codeSize = (uint32_t)minCodeSize + 1; uint32_t codeSize = (uint32_t)minCodeSize + 1;
uint32_t maxCode = clearCode+1; uint32_t maxCode = clearCode+1;
@ -666,58 +618,87 @@ static void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t to
GifWriteCode(f, stat, clearCode, codeSize); // start with a fresh LZW dictionary GifWriteCode(f, stat, clearCode, codeSize); // start with a fresh LZW dictionary
for(uint32_t yy=0; yy<height; ++yy) uint32_t numPixels = width*height;
for( uint32_t ii=0; ii<numPixels; ++ii )
{ {
for(uint32_t xx=0; xx<width; ++xx) uint8_t nextValue;
if (transparent && (nextFrame[3] == 0))
{ {
#ifdef GIF_FLIP_VERT outFrame[0] = nextFrame[0];
// bottom-left origin image (such as an OpenGL capture) outFrame[1] = nextFrame[1];
uint8_t nextValue = image[((height-1-yy)*width+xx)*4+3]; outFrame[2] = nextFrame[2];
#else nextValue = kGifTransIndex;
// top-left origin }
uint8_t nextValue = image[(yy*width+xx)*4+3]; // For non-transparet background:
#endif // if a previous color is available, and it matches the current color,
// set the pixel to transparent
else if(!transparent && lastFrame &&
lastFrame[0] == nextFrame[0] &&
lastFrame[1] == nextFrame[1] &&
lastFrame[2] == nextFrame[2])
{
outFrame[0] = lastFrame[0];
outFrame[1] = lastFrame[1];
outFrame[2] = lastFrame[2];
nextValue = kGifTransIndex;
}
else
{
// palettize the pixel
int32_t bestDiff = 1000000;
int32_t bestInd = 1;
GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], bestInd, bestDiff);
// "loser mode" - no compression, every single code is followed immediately by a clear // Write the resulting color to the output buffer
//WriteCode( f, stat, nextValue, codeSize ); outFrame[0] = pPal->r[bestInd];
//WriteCode( f, stat, 256, codeSize ); outFrame[1] = pPal->g[bestInd];
outFrame[2] = pPal->b[bestInd];
nextValue = bestInd;
}
if( curCode < 0 ) if(lastFrame) lastFrame += 4;
outFrame += 4;
nextFrame += 4;
// "loser mode" - no compression, every single code is followed immediately by a clear
//WriteCode( f, stat, nextValue, codeSize );
//WriteCode( f, stat, 256, codeSize );
if( curCode < 0 )
{
// first value in a new run
curCode = nextValue;
}
else if( codetree[curCode].m_next[nextValue] )
{
// current run already in the dictionary
curCode = codetree[curCode].m_next[nextValue];
}
else
{
// finish the current run, write a code
GifWriteCode(f, stat, (uint32_t)curCode, codeSize);
// insert the new run into the dictionary
codetree[curCode].m_next[nextValue] = (uint16_t)++maxCode;
if( maxCode >= (1ul << codeSize) )
{ {
// first value in a new run // dictionary entry count has broken a size barrier,
curCode = nextValue; // we need more bits for codes
codeSize++;
} }
else if( codetree[curCode].m_next[nextValue] ) if( maxCode == DICT_SIZE-1 )
{ {
// current run already in the dictionary // the dictionary is full, clear it out and begin anew
curCode = codetree[curCode].m_next[nextValue]; GifWriteCode(f, stat, clearCode, codeSize); // clear tree
memset(codetree, 0, sizeof(GifLzwNode)*DICT_SIZE);
codeSize = (uint32_t)(minCodeSize + 1);
maxCode = clearCode+1;
} }
else
{
// finish the current run, write a code
GifWriteCode(f, stat, (uint32_t)curCode, codeSize);
// insert the new run into the dictionary curCode = nextValue;
codetree[curCode].m_next[nextValue] = (uint16_t)++maxCode;
if( maxCode >= (1ul << codeSize) )
{
// dictionary entry count has broken a size barrier,
// we need more bits for codes
codeSize++;
}
if( maxCode == 4095 )
{
// the dictionary is full, clear it out and begin anew
GifWriteCode(f, stat, clearCode, codeSize); // clear tree
memset(codetree, 0, sizeof(GifLzwNode)*4096);
codeSize = (uint32_t)(minCodeSize + 1);
maxCode = clearCode+1;
}
curCode = nextValue;
}
} }
} }
@ -811,12 +792,12 @@ static bool GifWriteFrame( GifWriter* writer, const uint8_t* image, uint32_t wid
GifPalette pal; GifPalette pal;
GifMakePalette((dither? NULL : oldImage), image, width, height, bitDepth, transparent, dither, &pal); GifMakePalette((dither? NULL : oldImage), image, width, height, bitDepth, transparent, dither, &pal);
if(dither) if(dither) {
// Broken - no output
GifDitherImage(oldImage, image, writer->oldImage.get(), width, height, &pal); GifDitherImage(oldImage, image, writer->oldImage.get(), width, height, &pal);
else return false;
GifThresholdImage(oldImage, image, writer->oldImage.get(), width, height, transparent, &pal); } else
GifThresholdImageAndWrite(writer->f, oldImage, image, writer->oldImage.get(), width, height, delay, transparent, &pal);
GifWriteLzwImage(writer->f, writer->oldImage.get(), 0, 0, width, height, delay, transparent, &pal);
return true; return true;
} }