237 lines
7.1 KiB
C++
237 lines
7.1 KiB
C++
//
|
|
// 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() */
|