gif: support transparent gif animation

if no background is set, gif will generate transparent version.

Issue: https://github.com/thorvg/thorvg/issues/1769
This commit is contained in:
Hermet Park 2023-11-14 18:35:50 +09:00
parent dfbb3893b0
commit 34f47671b1
3 changed files with 67 additions and 54 deletions

View file

@ -279,7 +279,7 @@ void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt, i
} }
// Finds all pixels that have changed from the previous image and // Finds all pixels that have changed from the previous image and
// moves them to the fromt of th buffer. // moves them to the from of the buffer.
// This allows us to build a palette optimized for the colors of the // This allows us to build a palette optimized for the colors of the
// changed pixels only. // changed pixels only.
int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixels ) int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixels )
@ -289,10 +289,7 @@ int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixel
for (int ii=0; ii<numPixels; ++ii) for (int ii=0; ii<numPixels; ++ii)
{ {
if(lastFrame[0] != frame[0] || if ((frame[3] == 255) && (lastFrame[0] != frame[0] || lastFrame[1] != frame[1] || lastFrame[2] != frame[2])) {
lastFrame[1] != frame[1] ||
lastFrame[2] != frame[2])
{
writeIter[0] = frame[0]; writeIter[0] = frame[0];
writeIter[1] = frame[1]; writeIter[1] = frame[1];
writeIter[2] = frame[2]; writeIter[2] = frame[2];
@ -337,41 +334,55 @@ void GifMakePalette( const uint8_t* lastFrame, const uint8_t* nextFrame, uint32_
pPal->r[0] = pPal->g[0] = pPal->b[0] = 0; pPal->r[0] = pPal->g[0] = pPal->b[0] = 0;
} }
void GifPalettizePixel(const uint8_t* nextFrame, uint8_t* outFrame, GifPalette* pPal)
{
int32_t bestDiff = 1000000;
int32_t bestInd = 1;
GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], &bestInd, &bestDiff, 1);
// Write the resulting color to the output buffer
outFrame[0] = pPal->r[bestInd];
outFrame[1] = pPal->g[bestInd];
outFrame[2] = pPal->b[bestInd];
outFrame[3] = (uint8_t)bestInd;
}
// Picks palette colors for the image using simple thresholding, no dithering // Picks palette colors for the image using simple thresholding, no dithering
void GifThresholdImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal ) void GifThresholdImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal, bool transparent)
{ {
uint32_t numPixels = width*height; uint32_t numPixels = width*height;
for( uint32_t ii=0; ii<numPixels; ++ii )
{
// if a previous color is available, and it matches the current color,
// set the pixel to transparent
if(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, 1);
// Write the resulting color to the output buffer if (transparent) {
outFrame[0] = pPal->r[bestInd]; for (uint32_t ii = 0; ii < numPixels; ++ii) {
outFrame[1] = pPal->g[bestInd]; if (nextFrame[3] < 255) {
outFrame[2] = pPal->b[bestInd]; outFrame[0] = 0;
outFrame[3] = (uint8_t)bestInd; outFrame[1] = 0;
outFrame[2] = 0;
outFrame[3] = kGifTransIndex;
} else {
GifPalettizePixel(nextFrame, outFrame, pPal);
}
if(lastFrame) lastFrame += 4;
outFrame += 4;
nextFrame += 4;
}
} else {
for (uint32_t ii = 0; ii < numPixels; ++ii) {
// if a previous color is available, and it matches the current color,
// set the pixel to transparent
if( 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 {
GifPalettizePixel(nextFrame, outFrame, pPal);
}
if(lastFrame) lastFrame += 4;
outFrame += 4;
nextFrame += 4;
} }
if(lastFrame) lastFrame += 4;
outFrame += 4;
nextFrame += 4;
} }
} }
@ -456,13 +467,13 @@ void GifWritePalette( const GifPalette* pPal, FILE* f )
} }
// write the image header, LZW-compress and write out the image // write the image header, LZW-compress and write out the image
void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal) void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal, bool transparent)
{ {
// graphics control extension // graphics control extension
fputc(0x21, f); fputc(0x21, f);
fputc(0xf9, f); fputc(0xf9, f);
fputc(0x04, f); fputc(0x04, f);
fputc(0x05, f); // leave prev frame in place, this frame has transparency fputc((transparent ? 0x09 : 0x05), f); //clear prev frame or not.
fputc(delay & 0xff, f); fputc(delay & 0xff, f);
fputc((delay >> 8) & 0xff, f); fputc((delay >> 8) & 0xff, f);
fputc(kGifTransIndex, f); // transparent color index fputc(kGifTransIndex, f); // transparent color index
@ -644,7 +655,7 @@ bool GifBegin( GifWriter* writer, const char* filename, uint32_t width, uint32_t
// The GIFWriter should have been created by GIFBegin. // The GIFWriter should have been created by GIFBegin.
// AFAIK, it is legal to use different bit depths for different frames of an image - // AFAIK, it is legal to use different bit depths for different frames of an image -
// this may be handy to save bits in animations that don't change much. // this may be handy to save bits in animations that don't change much.
bool GifWriteFrame(GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, int bitDepth = 8) bool GifWriteFrame(GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, bool transparent)
{ {
if(!writer->f) return false; if(!writer->f) return false;
@ -652,11 +663,11 @@ bool GifWriteFrame(GifWriter* writer, const uint8_t* image, uint32_t width, uint
writer->firstFrame = false; writer->firstFrame = false;
GifPalette pal; GifPalette pal;
GifMakePalette(oldImage, image, width, height, bitDepth, &pal); GifMakePalette(oldImage, image, width, height, 8, &pal);
GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal); GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal, transparent);
GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal); GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal, transparent);
return true; return true;
} }

View file

@ -41,7 +41,7 @@ void GifSaver::run(unsigned tid)
auto h = static_cast<uint32_t>(vsize[1]); auto h = static_cast<uint32_t>(vsize[1]);
buffer = (uint32_t*)realloc(buffer, sizeof(uint32_t) * w * h); buffer = (uint32_t*)realloc(buffer, sizeof(uint32_t) * w * h);
canvas->target(buffer, w, w, h, tvg::SwCanvas::ABGR8888); canvas->target(buffer, w, w, h, tvg::SwCanvas::ABGR8888S);
canvas->push(cast(bg)); canvas->push(cast(bg));
canvas->push(cast(animation->picture())); canvas->push(cast(animation->picture()));
@ -52,6 +52,7 @@ void GifSaver::run(unsigned tid)
} }
auto delay = (1.0f / fps); auto delay = (1.0f / fps);
auto transparent = bg ? false : true;
GifWriter writer; GifWriter writer;
if (!GifBegin(&writer, path, w, h, uint32_t(delay * 100.f))) { if (!GifBegin(&writer, path, w, h, uint32_t(delay * 100.f))) {
@ -68,7 +69,7 @@ void GifSaver::run(unsigned tid)
if (canvas->draw() == tvg::Result::Success) { if (canvas->draw() == tvg::Result::Success) {
canvas->sync(); canvas->sync();
} }
if (!GifWriteFrame(&writer, reinterpret_cast<uint8_t*>(buffer), w, h, uint32_t(delay * 100.0f))) { if (!GifWriteFrame(&writer, reinterpret_cast<uint8_t*>(buffer), w, h, uint32_t(delay * 100.0f), transparent)) {
TVGERR("GIF_SAVER", "Failed gif encoding"); TVGERR("GIF_SAVER", "Failed gif encoding");
break; break;
} }

View file

@ -47,7 +47,7 @@ private:
uint32_t fps = 30; uint32_t fps = 30;
uint32_t width = 600; uint32_t width = 600;
uint32_t height = 600; uint32_t height = 600;
uint32_t bgColor = 0xffffffff; //a white by default. unique_ptr<Shape> bg = nullptr; //transparent
void helpMsg() void helpMsg()
{ {
@ -81,15 +81,10 @@ private:
auto saver = Saver::gen(); auto saver = Saver::gen();
//set a background color //set a background color
auto r = (uint8_t)((bgColor & 0xff0000) >> 16); if (bg) {
auto g = (uint8_t)((bgColor & 0x00ff00) >> 8); bg->appendRect(0, 0, width * scale, height * scale);
auto b = (uint8_t)((bgColor & 0x0000ff)); saver->background(std::move(bg));
}
auto bg = tvg::Shape::gen();
bg->fill(r, g, b, 255);
bg->appendRect(0, 0, width * scale, height * scale);
saver->background(std::move(bg));
if (saver->save(std::move(animation), out, 100, fps) != Result::Success) return false; if (saver->save(std::move(animation), out, 100, fps) != Result::Success) return false;
if (saver->sync() != Result::Success) return false; if (saver->sync() != Result::Success) return false;
@ -234,7 +229,13 @@ public:
cout << "Error: Missing background color attribute. Expected eg. -b fa7410." << endl; cout << "Error: Missing background color attribute. Expected eg. -b fa7410." << endl;
return 1; return 1;
} }
bgColor = (uint32_t) strtol(p_arg, NULL, 16); auto bgColor = (uint32_t) strtol(p_arg, NULL, 16);
auto r = (uint8_t)((bgColor & 0xff0000) >> 16);
auto g = (uint8_t)((bgColor & 0x00ff00) >> 8);
auto b = (uint8_t)((bgColor & 0x0000ff));
bg = tvg::Shape::gen();
bg->fill(r, g, b, 255);
} else { } else {
cout << "Warning: Unknown flag (" << p << ")." << endl; cout << "Warning: Unknown flag (" << p << ")." << endl;
} }