circuitpython/lib/AnimatedGIF/AnimatedGIF.cpp

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() */