mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 21:53:41 +00:00
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:
parent
dfbb3893b0
commit
34f47671b1
3 changed files with 67 additions and 54 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue