BlinkenArea - GitList
Repositories
Blog
Wiki
BlinkenLib
Code
Commits
Branches
Tags
Search
Tree:
860e91b
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
src
BlinkenGif.c
format
Stefan Schuermans
commited
860e91b
at 2023-08-18 09:55:23
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/BlinkenFrame.h> #include <BlinkenLib/BlinkenMovie.h> #include <BlinkenLib/config.h> #include "BlinkenConstants.h" #include "BlinkenGif.h" #ifdef BLINKENLIB_CFG_GIF5 #define GIF5_err , &giferr #else #define GIF5_err #endif // 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; #ifdef BLINKENLIB_CFG_GIF5 int giferr; #endif if (pFilename == NULL) return NULL; // open GIF file for decoding gif = DGifOpenFileName(pFilename GIF5_err); if (gif == NULL) { return NULL; } // read GIF file if (DGifSlurp(gif) != GIF_OK) { DGifCloseFile(gif GIF5_err); 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 GIF5_err); return NULL; } // create background frame and set it to background color pBack = BlinkenFrameNew(height, width, 3, 255, 1); if (!pBack) { BlinkenMovieFree(pMovie); DGifCloseFile(gif GIF5_err); 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 GIF5_err); return pMovie; } int BlinkenGifSave(stBlinkenMovie *pMovie, const char *pFilename) { GifFileType *gif; int frameCnt, height, width, channels, maxval, channels_new, maxval_new, 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; #ifdef BLINKENLIB_CFG_GIF5 int giferr; #endif 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; break; case 2: maxval_new = 15; break; case 3: maxval_new = 5; break; default: maxval_new = 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 GIF5_err); if (gif == NULL) { BlinkenMovieFree(pOutMovie); return -1; // error } // set global GIF config gif->SWidth = width; gif->SHeight = height; gif->SColorResolution = 256; 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 #ifdef BLINKENLIB_CFG_GIF5 gif->SColorMap = GifMakeMapObject(256, NULL); #else gif->SColorMap = MakeMapObject(256, NULL); #endif if (gif->SColorMap == NULL) { EGifCloseFile(gif GIF5_err); BlinkenMovieFree(pOutMovie); return -1; // error } i = 0; switch (channels) { case 1: for (r = 0; r <= maxval; ++r) { gif->SColorMap->Colors[i].Red = ((int)r * 255 + maxval / 2) / maxval; gif->SColorMap->Colors[i].Green = gif->SColorMap->Colors[i].Red; gif->SColorMap->Colors[i].Blue = gif->SColorMap->Colors[i].Red; ++i; } break; case 2: for (g = 0; g <= maxval; ++g) { for (r = 0; r <= maxval; ++r) { 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 = gif->SColorMap->Colors[i].Green; ++i; } } break; case 3: for (b = 0; b <= maxval; ++b) { for (g = 0; g <= maxval; ++g) { for (r = 0; r <= maxval; ++r) { 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; ++i; } } } break; } // switch channels 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 GIF5_err); 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 GIF5_err); 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 #ifndef BLINKENLIB_CFG_GIF5 pImg->Function = 0; #endif pImg->ExtensionBlockCount = 1; pImg->ExtensionBlocks = calloc(1, sizeof(ExtensionBlock)); if (pImg->ExtensionBlocks == NULL) { EGifCloseFile(gif GIF5_err); 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 GIF5_err); 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 (closes GIF file) ret = EGifSpew(gif) == GIF_OK ? 0 : -1; // free copy of movie BlinkenMovieFree(pOutMovie); return ret; }