BlinkenArea - GitList
Repositories
Blog
Wiki
BlinkenLib
Code
Commits
Branches
Tags
Search
Tree:
b268199
Branches
Tags
master
v0.1
v0.2
v0.3
v0.3.1
v0.4
v0.4.1
v0.5
v0.5.1
v0.5.2
v0.5.3
v0.5.4
v0.5.5
v0.6.0
v0.6.1
v0.6.2
v0.6.3
v0.6.4
v0.6.5
v0.6.6
v0.6.7
v0.6.8
v0.6.9
v0.7.0
v0.7.1
v0.7.10
v0.7.2
v0.7.3
v0.7.4
v0.7.5
v0.7.6
v0.7.7
v0.7.8
v0.7.9
v0.8.0
v0.8.1
BlinkenLib
BlinkenLib
BlinkenGif.c
support reading interlaced GIFs
Stefan Schuermans
commited
b268199
at 2016-12-18 20:29:15
BlinkenGif.c
Blame
History
Raw
/* BlinkenLib Copyright 2004-2016 Stefan Schuermans <stefan@schuermans.info> Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html a blinkenarea.org project */ #include <gif_lib.h> #include <stdlib.h> #include <BlinkenLib/config.h> #include <BlinkenLib/BlinkenConstants.h> #include <BlinkenLib/BlinkenFrame.h> #include <BlinkenLib/BlinkenMovie.h> #include <BlinkenLib/BlinkenGif.h> // get color from GIF color map static void BlinkenGifGetColor(ColorMapObject *pMap, int idx, int transparent, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *t) { if (transparent >= 0 && idx == transparent) { *t = 1; } else { *t = 0; } if (pMap != NULL) { if (idx >= 0 && idx < pMap->ColorCount) { *r = pMap->Colors[idx].Red; *g = pMap->Colors[idx].Green; *b = pMap->Colors[idx].Blue; } else { *r = 0; *g = 0; *b = 0; *t = 1; } } else { *r = 0; *g = 0; *b = 0; *t = 1; } } // set pixel to GIF color static void BlinkenGifSetPixel(ColorMapObject *pMap, int idx, int transparent, stBlinkenFrame *pFrame, int y, int x) { unsigned char r, g, b, t; if (pFrame != NULL) { BlinkenGifGetColor(pMap, idx, transparent, &r, &g, &b, &t); if (! t) { BlinkenFrameSetPixel(pFrame, y, x, 0, r); BlinkenFrameSetPixel(pFrame, y, x, 1, g); BlinkenFrameSetPixel(pFrame, y, x, 2, b); } } } // load a GIF file as BlinkenMovie stBlinkenMovie *BlinkenGifLoad(const char *pFilename) { GifFileType *gif; struct sPass { int ofs, stride; }; static struct sPass const passes_linear[] = { { 0, 1 }, { 0, 0 } }; static struct sPass const passes_interlaced[] = { { 0, 8 }, { 4, 8 }, { 2, 4 }, { 1, 2 } }; struct sPass const *pass; int height, width, frameCnt, frameIdx, i, y, x, y1, x1, bg, disposal, delay, transp, idx; ColorMapObject *pGlobalMap, *pMap; SavedImage *pImg; GifImageDesc *pDesc; ExtensionBlock *pEx; stBlinkenMovie *pMovie; stBlinkenFrame *pBack, *pFrame; if (pFilename == NULL) return NULL; // open GIF file for decoding gif = DGifOpenFileName(pFilename); if (gif == NULL) { return NULL; } // read GIF file if (DGifSlurp(gif) != GIF_OK) { DGifCloseFile(gif); return NULL; } height = gif->SHeight; width = gif->SWidth; frameCnt = gif->ImageCount; pGlobalMap = gif->SColorMap; bg = gif->SBackGroundColor; // create movie pMovie = BlinkenMovieNew(height, width, 3, 255); if (! pMovie) { DGifCloseFile(gif); return NULL; } // create background frame and set it to background color pBack = BlinkenFrameNew(height, width, 3, 255, 1); if (! pBack) { BlinkenMovieFree(pMovie); DGifCloseFile(gif); return NULL; } for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { BlinkenGifSetPixel(pGlobalMap, bg, -1, pBack, y, x); } } // process all frames for (frameIdx = 0; frameIdx < frameCnt; ++frameIdx) { pImg = &gif->SavedImages[frameIdx]; pDesc = &pImg->ImageDesc; // get local color map pMap = pDesc->ColorMap != NULL ? pDesc->ColorMap : pGlobalMap; // get disposal mode, delay and transparent color disposal = 0; delay = 100; transp = -1; for (i = 0; i < pImg->ExtensionBlockCount; ++i) { pEx = &pImg->ExtensionBlocks[i]; if (pEx->Function == GRAPHICS_EXT_FUNC_CODE && pEx->ByteCount == 4) { disposal = pEx->Bytes[0] >> 2 & 0x07; delay = (unsigned int)(unsigned char)pEx->Bytes[1] | (unsigned int)(unsigned char)pEx->Bytes[2] << 8; transp = pEx->Bytes[0] & 0x01 ? (int)(unsigned char)pEx->Bytes[3] : -1; } } // draw new frame based on background frame, maybe update background frame if (delay > 0) { pFrame = BlinkenFrameClone(pBack); BlinkenFrameSetDuration(pFrame, delay * 10); // GIF delay is in 10ms } else { // frames with no duration -> only effect on background pFrame = NULL; } for (pass = pDesc->Interlace ? passes_interlaced : passes_linear; pass->stride > 0; ++pass) { for (y = pass->ofs, y1 = pDesc->Top + pass->ofs, i = 0; y < pDesc->Height; y += pass->stride, y1 += pass->stride) { for (x = 0, x1 = pDesc->Left; x < pDesc->Width; ++x, ++x1, ++i) { idx = pImg->RasterBits[i]; switch (disposal) { // undefined case 0: case 4: case 5: case 6: case 7: // do not dispose -> draw to frame and update background case 1: BlinkenGifSetPixel(pMap, idx, transp, pFrame, y1, x1); BlinkenGifSetPixel(pMap, idx, transp, pBack, y1, x1); break; // restore background -> draw to frame and reset background case 2: BlinkenGifSetPixel(pMap, idx, transp, pFrame, y1, x1); BlinkenGifSetPixel(pMap, bg, transp, pBack, y1, x1); break; // restore previous -> draw to frame only case 3: BlinkenGifSetPixel(pMap, idx, transp, pFrame, y1, x1); break; } // switch disposal } // for x } // for y } // for pass // append frame to movie if (pFrame) { if (BlinkenMovieAppendFrame(pMovie, pFrame) != 0) { BlinkenFrameFree(pFrame); } } } // for frameIdx // delete background frame BlinkenFrameFree(pBack); // close GIF file DGifCloseFile(gif); return pMovie; } int BlinkenGifSave(stBlinkenMovie *pMovie, const char *pFilename) { GifFileType *gif; int frameCnt, height, width, channels, maxval, channels_new, maxval_new, colors, r, g, b, i, frame, y, x, c, y1, x1, y2, x2, v, ret; unsigned int delay; stBlinkenMovie *pOutMovie; stBlinkenFrame *pFrame, *pLast; SavedImage *pImg; GifImageDesc *pDesc; ExtensionBlock *pEx; if (pMovie == NULL || pFilename == NULL) return -1; // error frameCnt = BlinkenMovieGetFrameCnt(pMovie); height = BlinkenMovieGetHeight(pMovie); width = BlinkenMovieGetWidth(pMovie); channels = BlinkenMovieGetChannels(pMovie); maxval = BlinkenMovieGetMaxval(pMovie); // convert movie to suitable color depth (i.e. at most 256 colors) if (channels > 3) channels_new = 3; else channels_new = channels; switch (channels) { case 1: maxval_new = 255; colors = 256; break; case 2: maxval_new = 15; colors = 256; break; case 3: maxval_new = 5; colors = 216; break; default: maxval_new = 255; colors = 255; break; } pOutMovie = BlinkenMovieClone(pMovie); if (pOutMovie == NULL) return -1; // error if (channels_new != channels || maxval_new != maxval) { BlinkenMovieResize(pOutMovie, height, width, channels_new, maxval_new); channels = channels_new; maxval = maxval_new; } // open GIF file for encoding gif = EGifOpenFileName(pFilename, 0); if (gif == NULL) { BlinkenMovieFree(pOutMovie); return -1; // error } // set global GIF config gif->SWidth = width; gif->SHeight = height; gif->SColorResolution = colors; gif->SBackGroundColor = 0; gif->SColorMap = NULL; gif->ImageCount = frameCnt; gif->Image.Left = 0; gif->Image.Top = 0; gif->Image.Width = width; gif->Image.Height = height; gif->Image.ColorMap = NULL; gif->SavedImages = NULL; gif->UserData = NULL; // create color map gif->SColorMap = MakeMapObject(256, NULL); if (gif->SColorMap == NULL) { EGifCloseFile(gif); BlinkenMovieFree(pOutMovie); return -1; // error } for (b = 0, i = 0; b <= maxval && i < colors; ++b) { for (g = 0; g <= maxval && i < colors; ++g) { for (r = 0; r <= maxval && i < colors; ++r, ++i) { gif->SColorMap->Colors[i].Red = ((int)r * 255 + maxval / 2) / maxval; gif->SColorMap->Colors[i].Green = ((int)g * 255 + maxval / 2) / maxval; gif->SColorMap->Colors[i].Blue = ((int)b * 255 + maxval / 2) / maxval; } } } for (; i < 256; ++i) { gif->SColorMap->Colors[i].Red = 0; gif->SColorMap->Colors[i].Green = 0; gif->SColorMap->Colors[i].Blue = 0; } // create space for saved images gif->SavedImages = calloc(frameCnt, sizeof(SavedImage)); if (gif->SavedImages == NULL) { EGifCloseFile(gif); BlinkenMovieFree(pOutMovie); return -1; // error } // put frames into GIF structure pLast = NULL; for (frame = 0; frame < frameCnt; ++frame) { pFrame = BlinkenMovieGetFrame(pOutMovie, frame); pImg = &gif->SavedImages[frame]; pDesc = &pImg->ImageDesc; // find smallest rectangle with changes if (pLast != NULL) { y1 = height - 1; x1 = width - 1; y2 = 0; x2 = 0; for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { for (c = 0; c < channels; ++c) { if (BlinkenFrameGetPixel(pFrame, y, x, c) != BlinkenFrameGetPixel(pLast, y, x, c)) break; } if (c < channels) { // change detected at x,y if (y < y1) y1 = y; if (x < x1) x1 = x; if (y > y2) y2 = y; if (x > x2) x2 = x; } } // for x } // for y if (y1 <= y2 && x1 <= x2) { // changes detected -> encode changed rectangle pDesc->Left = x1; pDesc->Top = y1; pDesc->Width = x2 - x1 + 1; pDesc->Height = y2 - y1 + 1; } else { // no changes detected -> encode top left pixel (empty does not work) pDesc->Left = 0; pDesc->Top = 0; pDesc->Width = 1; pDesc->Height = 1; } } else { // if pLast // no last frame -> encode full frame pDesc->Left = 0; pDesc->Top = 0; pDesc->Width = width; pDesc->Height = height; } // if pLast ... else pDesc->Interlace = 0; pDesc->ColorMap = NULL; // allocate pixel buffer pImg->RasterBits = calloc(pDesc->Width * pDesc->Height, sizeof(unsigned char)); if (pImg->RasterBits == NULL) { EGifCloseFile(gif); BlinkenMovieFree(pOutMovie); return -1; // error } // fill pixel buffer for (y = 0, y1 = pDesc->Top, i = 0; y < pDesc->Height; ++y, ++y1) { for (x = 0, x1 = pDesc->Left; x < pDesc->Width; ++x, ++x1, ++i) { v = 0; for (c = channels - 1; c >= 0; --c) { v *= (maxval + 1); v += BlinkenFrameGetPixel(pFrame, y1, x1, c); } pImg->RasterBits[i] = v; } // for x } // for y // allocate and fill extension block with delay and disposal mode pImg->Function = 0; pImg->ExtensionBlockCount = 1; pImg->ExtensionBlocks = calloc(1, sizeof(ExtensionBlock)); if (pImg->ExtensionBlocks == NULL) { EGifCloseFile(gif); BlinkenMovieFree(pOutMovie); return -1; // error } pEx = pImg->ExtensionBlocks; pEx->Function = GRAPHICS_EXT_FUNC_CODE; pEx->ByteCount = 4; pEx->Bytes = calloc(pEx->ByteCount, sizeof (unsigned char)); if (pEx->Bytes == NULL) { EGifCloseFile(gif); BlinkenMovieFree(pOutMovie); return -1; // error } pEx->Bytes[0] = 1 << 2; // disposal mode: do not dispose delay = (BlinkenFrameGetDuration(pFrame) + 5) / 10; if (delay <= 0) delay = 1; pEx->Bytes[1] = delay & 0xFF; pEx->Bytes[2] = delay >> 8 & 0xFF; pEx->Bytes[3] = 0xFF; // transparent color (Bytes[3]) not used, flag 0x01 in Bytes[0] not set pLast = pFrame; } // for frame // encode GIF file ret = EGifSpew(gif) == GIF_OK ? 0 : -1; // close GIF file EGifCloseFile(gif); // free copy of movie BlinkenMovieFree(pOutMovie); return ret; }