// // GIF Animator // written by Larry Bank // bitbank@pobox.com // Arduino port started 7/5/2020 // Original GIF code written 20+ years ago :) // The goal of this code is to decode images up to 480x320 // using no more than 22K of RAM (if sent directly to an LCD display) // // Copyright 2020 BitBank Software, Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //=========================================================================== #include "AnimatedGIF.h" // Here is all of the actual code... #include "gif.inl" // // Memory initialization // int AnimatedGIF::open(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) { _gif.iError = GIF_SUCCESS; _gif.pfnRead = readMem; _gif.pfnSeek = seekMem; _gif.pfnDraw = pfnDraw; _gif.pfnOpen = NULL; _gif.pfnClose = NULL; _gif.GIFFile.iSize = iDataSize; _gif.GIFFile.pData = pData; return GIFInit(&_gif); } /* open() */ int AnimatedGIF::openFLASH(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) { _gif.iError = GIF_SUCCESS; _gif.pfnRead = readFLASH; _gif.pfnSeek = seekMem; _gif.pfnDraw = pfnDraw; _gif.pfnOpen = NULL; _gif.pfnClose = NULL; _gif.GIFFile.iSize = iDataSize; _gif.GIFFile.pData = pData; return GIFInit(&_gif); } /* openFLASH() */ // // Returns the first comment block found (if any) // int AnimatedGIF::getComment(char *pDest) { int32_t iOldPos; iOldPos = _gif.GIFFile.iPos; // keep old position (*_gif.pfnSeek)(&_gif.GIFFile, _gif.iCommentPos); (*_gif.pfnRead)(&_gif.GIFFile, (uint8_t *)pDest, _gif.sCommentLen); (*_gif.pfnSeek)(&_gif.GIFFile, iOldPos); pDest[_gif.sCommentLen] = 0; // zero terminate the string return (int)_gif.sCommentLen; } /* getComment() */ // // Allocate a block of memory to hold the entire canvas (as 8-bpp) // int AnimatedGIF::allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc) { if (_gif.iCanvasWidth > 0 && _gif.iCanvasHeight > 0 && _gif.pFrameBuffer == NULL) { // Allocate a little extra space for the current line // as RGB565 or RGB888 int iCanvasSize = _gif.iCanvasWidth * (_gif.iCanvasHeight+3); _gif.pFrameBuffer = (unsigned char *)(*pfnAlloc)(iCanvasSize); if (_gif.pFrameBuffer == NULL) return GIF_ERROR_MEMORY; return GIF_SUCCESS; } return GIF_INVALID_PARAMETER; } /* allocFrameBuf() */ // // Set the DRAW callback behavior to RAW (default) // or COOKED (requires allocating a frame buffer) // int AnimatedGIF::setDrawType(int iType) { if (iType != GIF_DRAW_RAW && iType != GIF_DRAW_COOKED) return GIF_INVALID_PARAMETER; // invalid drawing mode _gif.ucDrawType = (uint8_t)iType; return GIF_SUCCESS; } /* setDrawType() */ // // Release the memory used by the frame buffer // int AnimatedGIF::freeFrameBuf(GIF_FREE_CALLBACK *pfnFree) { if (_gif.pFrameBuffer) { (*pfnFree)(_gif.pFrameBuffer); _gif.pFrameBuffer = NULL; return GIF_SUCCESS; } return GIF_INVALID_PARAMETER; } /* freeFrameBuf() */ // // Return a pointer to the frame buffer (if it was allocated) // uint8_t * AnimatedGIF::getFrameBuf() { return _gif.pFrameBuffer; } /* getFrameBuf() */ int AnimatedGIF::getCanvasWidth() { return _gif.iCanvasWidth; } /* getCanvasWidth() */ int AnimatedGIF::getCanvasHeight() { return _gif.iCanvasHeight; } /* getCanvasHeight() */ int AnimatedGIF::getLoopCount() { return _gif.iRepeatCount; } /* getLoopCount() */ int AnimatedGIF::getInfo(GIFINFO *pInfo) { return GIF_getInfo(&_gif, pInfo); } /* getInfo() */ int AnimatedGIF::getLastError() { return _gif.iError; } /* getLastError() */ // // File (SD/MMC) based initialization // int AnimatedGIF::open(const char *szFilename, GIF_OPEN_CALLBACK *pfnOpen, GIF_CLOSE_CALLBACK *pfnClose, GIF_READ_CALLBACK *pfnRead, GIF_SEEK_CALLBACK *pfnSeek, GIF_DRAW_CALLBACK *pfnDraw) { _gif.iError = GIF_SUCCESS; _gif.pfnRead = pfnRead; _gif.pfnSeek = pfnSeek; _gif.pfnDraw = pfnDraw; _gif.pfnOpen = pfnOpen; _gif.pfnClose = pfnClose; _gif.GIFFile.fHandle = (*pfnOpen)(szFilename, &_gif.GIFFile.iSize); if (_gif.GIFFile.fHandle == NULL) { _gif.iError = GIF_FILE_NOT_OPEN; return 0; } return GIFInit(&_gif); } /* open() */ void AnimatedGIF::close() { if (_gif.pfnClose) (*_gif.pfnClose)(_gif.GIFFile.fHandle); } /* close() */ void AnimatedGIF::reset() { (*_gif.pfnSeek)(&_gif.GIFFile, 0); } /* reset() */ void AnimatedGIF::begin(unsigned char ucPaletteType) { memset(&_gif, 0, sizeof(_gif)); if (ucPaletteType != GIF_PALETTE_RGB565_LE && ucPaletteType != GIF_PALETTE_RGB565_BE && ucPaletteType != GIF_PALETTE_RGB888) _gif.iError = GIF_INVALID_PARAMETER; _gif.ucPaletteType = ucPaletteType; _gif.ucDrawType = GIF_DRAW_RAW; // assume RAW pixel handling _gif.pFrameBuffer = NULL; } /* begin() */ // // Play a single frame // returns: // 1 = good result and more frames exist // 0 = no more frames exist, a frame may or may not have been played: use getLastError() and look for GIF_SUCCESS to know if a frame was played // -1 = error int AnimatedGIF::playFrame(bool bSync, int *delayMilliseconds, void *pUser) { int rc; #if !defined( __MACH__ ) && !defined( __LINUX__ ) long lTime = millis(); #endif if (_gif.GIFFile.iPos >= _gif.GIFFile.iSize-1) // no more data exists { (*_gif.pfnSeek)(&_gif.GIFFile, 0); // seek to start } if (GIFParseInfo(&_gif, 0)) { _gif.pUser = pUser; if (_gif.iError == GIF_EMPTY_FRAME) // don't try to decode it return 0; rc = DecodeLZW(&_gif, 0); if (rc != 0) // problem return -1; } else { // The file is "malformed" in that there is a bunch of non-image data after // the last frame. Return as if all is well, though if needed getLastError() // can be used to see if a frame was actually processed: // GIF_SUCCESS -> frame processed, GIF_EMPTY_FRAME -> no frame processed if (_gif.iError == GIF_EMPTY_FRAME) { if (delayMilliseconds) *delayMilliseconds = 0; return 0; } return -1; // error parsing the frame info, we may be at the end of the file } // Return 1 for more frames or 0 if this was the last frame if (bSync) { #if !defined( __MACH__ ) && !defined( __LINUX__ ) lTime = millis() - lTime; if (lTime < _gif.iFrameDelay) // need to pause a bit delay(_gif.iFrameDelay - lTime); #endif // __LINUX__ } if (delayMilliseconds) // if not NULL, return the frame delay time *delayMilliseconds = _gif.iFrameDelay; return (_gif.GIFFile.iPos < _gif.GIFFile.iSize-10); } /* playFrame() */