imagecore/formats/internal/gif.cpp (261 lines of code) (raw):
/*
* MIT License
*
* Copyright (c) 2017 Twitter
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "gif.h"
#include "imagecore/utils/securemath.h"
#include "imagecore/utils/mathutils.h"
#include "imagecore/image/rgba.h"
namespace imagecore {
REGISTER_IMAGE_READER(ImageReaderGIF);
int gifRead(GifFileType* gif, GifByteType* dest, int numBytes)
{
ImageReader::Storage* source = (ImageReader::Storage*)gif->UserData;
return (int)source->read(dest, numBytes);
}
ImageReader* ImageReaderGIF::Factory::create()
{
return new ImageReaderGIF();
}
bool ImageReaderGIF::Factory::matchesSignature(const uint8_t* sig, unsigned int sigLen)
{
if( sigLen >= 3 && sig[0] == 0x47 && sig[1] == 0x49 && sig[2] == 0x46 ) {
return true;
}
return false;
}
ImageReaderGIF::ImageReaderGIF()
: m_Source(NULL)
, m_Width(0)
, m_Height(0)
, m_CurrentFrame(0)
, m_Gif(NULL)
, m_PrevFrameCopy(NULL)
, m_HasAlpha(true)
{
}
ImageReaderGIF::~ImageReaderGIF()
{
if( m_Gif ) {
DGifCloseFile(m_Gif);
m_Gif = NULL;
}
delete m_PrevFrameCopy;
m_PrevFrameCopy = NULL;
}
bool ImageReaderGIF::initWithStorage(ImageReader::Storage* source)
{
m_Source = source;
return true;
}
bool ImageReaderGIF::readHeader()
{
int err = GIF_OK;
m_Gif = DGifOpen(m_Source, gifRead, &err);
if( err != GIF_OK ) {
return false;
}
if (DGifParseFrames(m_Gif) == GIF_ERROR) {
return false;
}
if( m_Gif->ImageCount < 1 ) {
return false;
}
m_Width = m_Gif->SWidth;
m_Height = m_Gif->SHeight;
return true;
}
bool getGraphicsControlBlock(SavedImage* image, GraphicsControlBlock& gcb)
{
bool found = false;
for( int i = 0; i < image->ExtensionBlockCount; i++ ) {
ExtensionBlock* eb = image->ExtensionBlocks + i;
if( eb->Function == GRAPHICS_EXT_FUNC_CODE ) {
if( DGifExtensionToGCB(eb->ByteCount, eb->Bytes, &gcb) == GIF_OK ) {
found = true;
}
}
}
return found;
}
void getValidRegion(int& left, int& top, int& width, int& height, const GifImageDesc& imageDesc, unsigned int maxWidth, unsigned int maxHeight)
{
left = clamp(0, maxWidth - 1, imageDesc.Left);
top = clamp(0, maxHeight - 1, imageDesc.Top);
width = min((int)maxWidth - left, imageDesc.Width);
height = min((int)maxHeight - top, imageDesc.Height);
}
// We trust libgif not to be stupid when setting up extension blocks.
int getTransparentIndex(SavedImage* image)
{
GraphicsControlBlock gcb;
if( getGraphicsControlBlock(image, gcb) ) {
return gcb.TransparentColor;
}
return -1;
}
int getFrameDelay(SavedImage* image)
{
GraphicsControlBlock gcb;
if( getGraphicsControlBlock(image, gcb) ) {
return gcb.DelayTime;
}
return 0;
}
int getDisposalMode(SavedImage* image)
{
GraphicsControlBlock gcb;
if( getGraphicsControlBlock(image, gcb) ) {
return gcb.DisposalMode;
}
return 0;
}
void ImageReaderGIF::preDecodeFrame(unsigned int frameIndex)
{
if( m_Gif == NULL || (unsigned int)m_Gif->ImageCount <= frameIndex ) {
return;
}
SavedImage* savedImage = &m_Gif->SavedImages[frameIndex];
if( savedImage->RasterBits == NULL ) {
DGifDecodeFrame(m_Gif, frameIndex);
}
}
bool ImageReaderGIF::copyFrameRegion(unsigned int frameIndex, ImageRGBA* destImage, bool writeBackground)
{
if( m_Gif == NULL || (unsigned int)m_Gif->ImageCount <= frameIndex ) {
return false;
}
SavedImage* savedImage = &m_Gif->SavedImages[frameIndex];
if( savedImage->RasterBits == NULL ) {
if( DGifDecodeFrame(m_Gif, frameIndex) != GIF_OK || savedImage->RasterBits == NULL ) {
return false;
}
}
GifImageDesc& imageDesc = savedImage->ImageDesc;
int transparentIndex = getTransparentIndex(savedImage);
uint8_t* regionImage = savedImage->RasterBits;
if( regionImage == NULL ) {
return false;
}
ColorMapObject* colorMap = imageDesc.ColorMap ? imageDesc.ColorMap : m_Gif->SColorMap;
if( colorMap == NULL ) {
return false;
}
int regionX;
int regionY;
int regionWidth;
int regionHeight;
getValidRegion(regionX, regionY, regionWidth, regionHeight, imageDesc, m_Width, m_Height);
unsigned int destPitch = 0;
uint8_t* destBuffer = destImage->lockRect(regionX, regionY, regionWidth, regionHeight, destPitch);
bool hadAlpha = false;
for( unsigned int y = 0; y < regionHeight; y++ ) {
for( unsigned int x = 0; x < regionWidth; x++ ) {
unsigned char paletteIndex = regionImage[y * imageDesc.Width + x];
unsigned int outIndex = y * destPitch + x * 4U;
bool isAlpha = transparentIndex == paletteIndex;
if( !isAlpha ) {
GifColorType* color = &colorMap->Colors[paletteIndex];
destBuffer[outIndex + 0] = color->Red;
destBuffer[outIndex + 1] = color->Green;
destBuffer[outIndex + 2] = color->Blue;
destBuffer[outIndex + 3] = 255;
} else {
if( writeBackground ) {
destBuffer[outIndex + 0] = 255;
destBuffer[outIndex + 1] = 255;
destBuffer[outIndex + 2] = 255;
destBuffer[outIndex + 3] = 0;
}
if( !hadAlpha ) {
hadAlpha = true;
}
}
}
}
// If we previously had a transparent frame, but just wrote a full frame over it, then it no longer has alpha.
if( m_HasAlpha && !hadAlpha && regionWidth == m_Width && regionHeight == m_Height) {
m_HasAlpha = false;
}
destImage->unlockRect();
return true;
}
bool ImageReaderGIF::readImage(Image* dest)
{
if( !supportsOutputColorModel(dest->getColorModel()) ) {
return false;
}
ImageRGBA* destImage = dest->asRGBA();
if( m_Gif == NULL || (unsigned int)m_Gif->ImageCount <= m_CurrentFrame) {
return false;
}
SavedImage* prevFrame = m_CurrentFrame > 0 ? &m_Gif->SavedImages[m_CurrentFrame - 1] : NULL;
SavedImage* currentFrame = &m_Gif->SavedImages[m_CurrentFrame];
int prevDisposalMode = prevFrame != NULL ? getDisposalMode(prevFrame) : DISPOSAL_UNSPECIFIED;
int currDisposalMode = getDisposalMode(currentFrame);
// if this is the 1st frame and it is not equal to the full image size, perform a clear
if((m_CurrentFrame == 0) && ((currentFrame->ImageDesc.Left != 0) || (currentFrame->ImageDesc.Top != 0) || (currentFrame->ImageDesc.Width != m_Width) || (currentFrame->ImageDesc.Height != m_Height))) {
destImage->clear(0, 0, 0, 0);
}
// If consecutive frames use DISPOSE_PREVIOUS, it means keep using the original frame.
// Otherwise, copy the current frame to copy back later.
if( currDisposalMode == DISPOSE_PREVIOUS && prevDisposalMode != DISPOSE_PREVIOUS && prevFrame != NULL ) {
if( m_PrevFrameCopy == NULL ) {
m_PrevFrameCopy = ImageRGBA::create(destImage->getWidth(), destImage->getHeight(), destImage->getColorModel() == kColorModel_RGBA);
if( m_PrevFrameCopy == NULL ) {
return false;
}
}
destImage->copy(m_PrevFrameCopy);
}
// Previous frame asked for it to be cleared by copying back the original frame.
if( prevDisposalMode == DISPOSE_PREVIOUS && prevFrame != NULL && m_PrevFrameCopy != NULL ) {
m_PrevFrameCopy->copy(destImage);
const GifImageDesc& desc = prevFrame->ImageDesc;
int left, top, width, height;
getValidRegion(left, top, width, height, desc, m_Width, m_Height);
destImage->copyRect(m_PrevFrameCopy, left, top, left, top, width, height);
}
// Previous frame specified a background clear, so clear the region covered by that frame.
if( prevDisposalMode == DISPOSE_BACKGROUND && prevFrame != NULL ) {
const GifImageDesc& desc = prevFrame->ImageDesc;
int left, top, width, height;
getValidRegion(left, top, width, height, desc, m_Width, m_Height);
destImage->clearRect(left, top, width, height, 255, 255, 255, 0);
m_HasAlpha = true;
}
return copyFrameRegion(m_CurrentFrame, destImage, m_CurrentFrame == 0);
}
unsigned int ImageReaderGIF::getNumFrames()
{
return m_Gif->ImageCount;
}
bool ImageReaderGIF::advanceFrame()
{
if( m_CurrentFrame < (unsigned int)m_Gif->ImageCount ) {
m_CurrentFrame++;
return true;
}
return false;
}
bool ImageReaderGIF::seekToFirstFrame() {
m_CurrentFrame = 0;
return true;
}
unsigned int ImageReaderGIF::getFrameDelayMs()
{
if( m_Gif == NULL || (unsigned int)m_Gif->ImageCount <= m_CurrentFrame) {
return false;
}
return getFrameDelay(&m_Gif->SavedImages[m_CurrentFrame]) * 10;
}
EImageFormat ImageReaderGIF::getFormat()
{
return kImageFormat_GIF;
}
const char* ImageReaderGIF::getFormatName()
{
return "GIF";
}
unsigned int ImageReaderGIF::getWidth()
{
return m_Width;
}
unsigned int ImageReaderGIF::getHeight()
{
return m_Height;
}
EImageColorModel ImageReaderGIF::getNativeColorModel()
{
return m_HasAlpha ? kColorModel_RGBA : kColorModel_RGBX;
}
}