GIFImageReader.cpp [plain text]
#include "config.h"
#include "GIFImageReader.h"
#include <string.h>
#include "GIFImageDecoder.h"
using WebCore::GIFImageDecoder;
#define GETN(n, s) \
do { \
m_bytesToConsume = (n); \
m_state = (s); \
} while (0)
#define GETINT16(p) ((p)[1]<<8|(p)[0])
bool GIFLZWContext::outputRow()
{
int drowStart = irow;
int drowEnd = irow;
if (m_frameContext->progressiveDisplay && m_frameContext->interlaced && ipass < 4) {
unsigned rowDup = 0;
unsigned rowShift = 0;
switch (ipass) {
case 1:
rowDup = 7;
rowShift = 3;
break;
case 2:
rowDup = 3;
rowShift = 1;
break;
case 3:
rowDup = 1;
rowShift = 0;
break;
default:
break;
}
drowStart -= rowShift;
drowEnd = drowStart + rowDup;
if (((m_frameContext->height - 1) - drowEnd) <= rowShift)
drowEnd = m_frameContext->height - 1;
if (drowStart < 0)
drowStart = 0;
if ((unsigned)drowEnd >= m_frameContext->height)
drowEnd = m_frameContext->height - 1;
}
if ((unsigned)drowStart >= m_frameContext->height)
return true;
if (!m_client->haveDecodedRow(m_frameContext->frameId, rowBuffer, m_frameContext->width,
drowStart, drowEnd - drowStart + 1, m_frameContext->progressiveDisplay && m_frameContext->interlaced && ipass > 1))
return false;
if (!m_frameContext->interlaced)
irow++;
else {
do {
switch (ipass) {
case 1:
irow += 8;
if (irow >= m_frameContext->height) {
ipass++;
irow = 4;
}
break;
case 2:
irow += 8;
if (irow >= m_frameContext->height) {
ipass++;
irow = 2;
}
break;
case 3:
irow += 4;
if (irow >= m_frameContext->height) {
ipass++;
irow = 1;
}
break;
case 4:
irow += 2;
if (irow >= m_frameContext->height) {
ipass++;
irow = 0;
}
break;
default:
break;
}
} while (irow > (m_frameContext->height - 1));
}
return true;
}
bool GIFLZWContext::doLZW(const unsigned char* block, size_t bytesInBlock)
{
int code;
int incode;
const unsigned char *ch;
if (rowPosition == rowBuffer.size())
return true;
#define OUTPUT_ROW \
do { \
if (!outputRow()) \
return false; \
rowsRemaining--; \
rowPosition = 0; \
if (!rowsRemaining) \
return true; \
} while (0)
for (ch = block; bytesInBlock-- > 0; ch++) {
datum += ((int) *ch) << bits;
bits += 8;
while (bits >= codesize) {
code = datum & codemask;
datum >>= codesize;
bits -= codesize;
if (code == clearCode) {
codesize = m_frameContext->datasize + 1;
codemask = (1 << codesize) - 1;
avail = clearCode + 2;
oldcode = -1;
continue;
}
if (code == (clearCode + 1)) {
if (!rowsRemaining)
return true;
return false;
}
if (oldcode == -1) {
rowBuffer[rowPosition++] = suffix[code];
if (rowPosition == rowBuffer.size())
OUTPUT_ROW;
firstchar = oldcode = code;
continue;
}
incode = code;
if (code >= avail) {
stack[stackp++] = firstchar;
code = oldcode;
if (stackp == MAX_BYTES)
return false;
}
while (code >= clearCode) {
if (code >= MAX_BYTES || code == prefix[code])
return false;
stack[stackp++] = suffix[code];
code = prefix[code];
if (stackp == MAX_BYTES)
return false;
}
stack[stackp++] = firstchar = suffix[code];
if (avail < 4096) {
prefix[avail] = oldcode;
suffix[avail] = firstchar;
avail++;
if ((!(avail & codemask)) && (avail < 4096)) {
codesize++;
codemask += avail;
}
}
oldcode = incode;
do {
rowBuffer[rowPosition++] = stack[--stackp];
if (rowPosition == rowBuffer.size())
OUTPUT_ROW;
} while (stackp > 0);
}
}
return true;
}
bool GIFFrameContext::decode(const unsigned char* data, size_t length, WebCore::GIFImageDecoder* client, bool* frameDecoded)
{
*frameDecoded = false;
if (!m_lzwContext) {
if (!isDataSizeDefined() || !isHeaderDefined())
return true;
m_lzwContext = std::make_unique<GIFLZWContext>(client, this);
if (!m_lzwContext->prepareToDecode()) {
m_lzwContext = nullptr;
return false;
}
m_currentLzwBlock = 0;
}
while (m_currentLzwBlock < m_lzwBlocks.size() && m_lzwContext->hasRemainingRows()) {
size_t blockPosition = m_lzwBlocks[m_currentLzwBlock].blockPosition;
size_t blockSize = m_lzwBlocks[m_currentLzwBlock].blockSize;
if (blockPosition + blockSize > length)
return false;
if (!m_lzwContext->doLZW(data + blockPosition, blockSize))
return false;
++m_currentLzwBlock;
}
if (isComplete()) {
*frameDecoded = true;
m_lzwContext = nullptr;
}
return true;
}
bool GIFImageReader::decode(GIFImageDecoder::GIFQuery query, unsigned haltAtFrame)
{
ASSERT(m_bytesRead <= m_data->size());
if (!parse(m_bytesRead, m_data->size() - m_bytesRead, query == GIFImageDecoder::GIFSizeQuery))
return false;
if (query != GIFImageDecoder::GIFFullQuery)
return true;
m_currentDecodingFrame = std::min(m_currentDecodingFrame, static_cast<size_t>(haltAtFrame) - 1);
while (m_currentDecodingFrame < std::min(m_frames.size(), static_cast<size_t>(haltAtFrame))) {
bool frameDecoded = false;
GIFFrameContext* currentFrame = m_frames[m_currentDecodingFrame].get();
if (!currentFrame->decode(data(0), m_data->size(), m_client, &frameDecoded))
return false;
if (!frameDecoded)
break;
if (!m_client->frameComplete(m_currentDecodingFrame, currentFrame->delayTime, currentFrame->disposalMethod))
return false;
++m_currentDecodingFrame;
}
if (m_currentDecodingFrame == m_frames.size() && m_parseCompleted)
m_client->gifComplete();
return true;
}
bool GIFImageReader::parse(size_t dataPosition, size_t len, bool parseSizeOnly)
{
if (!len) {
return true;
}
if (len < m_bytesToConsume)
return true;
while (len >= m_bytesToConsume) {
const size_t currentComponentPosition = dataPosition;
const unsigned char* currentComponent = data(dataPosition);
dataPosition += m_bytesToConsume;
len -= m_bytesToConsume;
switch (m_state) {
case GIFLZW:
ASSERT(!m_frames.isEmpty());
m_frames.last()->addLzwBlock(currentComponentPosition, m_bytesToConsume);
GETN(1, GIFSubBlock);
break;
case GIFLZWStart: {
ASSERT(!m_frames.isEmpty());
m_frames.last()->setDataSize(*currentComponent);
GETN(1, GIFSubBlock);
break;
}
case GIFType: {
if (!strncmp((char*)currentComponent, "GIF89a", 6))
m_version = 89;
else if (!strncmp((char*)currentComponent, "GIF87a", 6))
m_version = 87;
else
return false;
GETN(7, GIFGlobalHeader);
break;
}
case GIFGlobalHeader: {
m_screenWidth = GETINT16(currentComponent);
m_screenHeight = GETINT16(currentComponent + 2);
if (m_client && !m_client->setSize(WebCore::IntSize(m_screenWidth, m_screenHeight)))
return false;
m_screenBgcolor = currentComponent[5];
m_globalColormapSize = 2 << (currentComponent[4] & 0x07);
if ((currentComponent[4] & 0x80) && m_globalColormapSize > 0) {
const size_t globalColormapBytes = 3 * m_globalColormapSize;
m_globalColormapPosition = dataPosition;
if (len < globalColormapBytes) {
GETN(globalColormapBytes, GIFGlobalColormap);
break;
}
m_isGlobalColormapDefined = true;
dataPosition += globalColormapBytes;
len -= globalColormapBytes;
}
GETN(1, GIFImageStart);
break;
}
case GIFGlobalColormap: {
m_isGlobalColormapDefined = true;
GETN(1, GIFImageStart);
break;
}
case GIFImageStart: {
if (*currentComponent == ';') { GETN(0, GIFDone);
break;
}
if (*currentComponent == '!') { GETN(2, GIFExtension);
break;
}
if (*currentComponent != ',')
return false;
GETN(9, GIFImageHeader);
break;
}
case GIFExtension: {
size_t bytesInBlock = currentComponent[1];
GIFState es = GIFSkipBlock;
switch (*currentComponent) {
case 0xf9:
es = GIFControlExtension;
bytesInBlock = std::max(bytesInBlock, static_cast<size_t>(4));
break;
case 0x01:
break;
case 0xff:
es = GIFApplicationExtension;
break;
case 0xfe:
es = GIFConsumeComment;
break;
}
if (bytesInBlock)
GETN(bytesInBlock, es);
else
GETN(1, GIFImageStart);
break;
}
case GIFConsumeBlock: {
if (!*currentComponent)
GETN(1, GIFImageStart);
else
GETN(*currentComponent, GIFSkipBlock);
break;
}
case GIFSkipBlock: {
GETN(1, GIFConsumeBlock);
break;
}
case GIFControlExtension: {
addFrameIfNecessary();
GIFFrameContext* currentFrame = m_frames.last().get();
currentFrame->isTransparent = *currentComponent & 0x1;
if (currentFrame->isTransparent)
currentFrame->tpixel = currentComponent[3];
int disposalMethod = ((*currentComponent) >> 2) & 0x7;
currentFrame->disposalMethod = static_cast<WebCore::ScalableImageDecoderFrame::DisposalMethod>(disposalMethod);
if (disposalMethod == 4)
currentFrame->disposalMethod = WebCore::ScalableImageDecoderFrame::DisposalMethod::RestoreToPrevious;
currentFrame->delayTime = GETINT16(currentComponent + 1) * 10;
GETN(1, GIFConsumeBlock);
break;
}
case GIFCommentExtension: {
if (*currentComponent)
GETN(*currentComponent, GIFConsumeComment);
else
GETN(1, GIFImageStart);
break;
}
case GIFConsumeComment: {
GETN(1, GIFCommentExtension);
break;
}
case GIFApplicationExtension: {
if (m_bytesToConsume == 11
&& (!strncmp((char*)currentComponent, "NETSCAPE2.0", 11) || !strncmp((char*)currentComponent, "ANIMEXTS1.0", 11)))
GETN(1, GIFNetscapeExtensionBlock);
else
GETN(1, GIFConsumeBlock);
break;
}
case GIFNetscapeExtensionBlock: {
if (*currentComponent)
GETN(std::max(3, static_cast<int>(*currentComponent)), GIFConsumeNetscapeExtension);
else
GETN(1, GIFImageStart);
break;
}
case GIFConsumeNetscapeExtension: {
int netscapeExtension = currentComponent[0] & 7;
if (netscapeExtension == 1) {
m_loopCount = GETINT16(currentComponent + 1);
if (!m_loopCount)
m_loopCount = WebCore::RepetitionCountInfinite;
GETN(1, GIFNetscapeExtensionBlock);
} else if (netscapeExtension == 2) {
GETN(1, GIFNetscapeExtensionBlock);
} else {
return false;
}
break;
}
case GIFImageHeader: {
unsigned height, width, xOffset, yOffset;
xOffset = GETINT16(currentComponent);
yOffset = GETINT16(currentComponent + 2);
width = GETINT16(currentComponent + 4);
height = GETINT16(currentComponent + 6);
if (currentFrameIsFirstFrame()
&& ((m_screenHeight < height) || (m_screenWidth < width) || (m_version == 87))) {
m_screenHeight = height;
m_screenWidth = width;
xOffset = 0;
yOffset = 0;
if (m_client && !m_client->setSize(WebCore::IntSize(m_screenWidth, m_screenHeight)))
return false;
}
if (!height || !width) {
height = m_screenHeight;
width = m_screenWidth;
if (!height || !width)
return false;
}
if (parseSizeOnly) {
setRemainingBytes(len + 9);
GETN(9, GIFImageHeader);
return true;
}
addFrameIfNecessary();
GIFFrameContext* currentFrame = m_frames.last().get();
currentFrame->setHeaderDefined();
currentFrame->xOffset = xOffset;
currentFrame->yOffset = yOffset;
currentFrame->height = height;
currentFrame->width = width;
m_screenWidth = std::max(m_screenWidth, width);
m_screenHeight = std::max(m_screenHeight, height);
currentFrame->interlaced = currentComponent[8] & 0x40;
currentFrame->progressiveDisplay = currentFrameIsFirstFrame();
const bool isLocalColormapDefined = currentComponent[8] & 0x80;
if (isLocalColormapDefined) {
int numColors = 2 << (currentComponent[8] & 0x7);
const size_t localColormapBytes = 3 * numColors;
currentFrame->localColormapPosition = dataPosition;
currentFrame->localColormapSize = numColors;
if (len < localColormapBytes) {
GETN(localColormapBytes, GIFImageColormap);
break;
}
currentFrame->isLocalColormapDefined = true;
dataPosition += localColormapBytes;
len -= localColormapBytes;
} else {
currentFrame->isLocalColormapDefined = false;
}
GETN(1, GIFLZWStart);
break;
}
case GIFImageColormap: {
ASSERT(!m_frames.isEmpty());
m_frames.last()->isLocalColormapDefined = true;
GETN(1, GIFLZWStart);
break;
}
case GIFSubBlock: {
const size_t bytesInBlock = *currentComponent;
if (bytesInBlock)
GETN(bytesInBlock, GIFLZW);
else {
ASSERT(!m_frames.isEmpty());
m_frames.last()->setComplete();
GETN(1, GIFImageStart);
}
break;
}
case GIFDone: {
m_parseCompleted = true;
return true;
}
default:
return false;
break;
}
}
setRemainingBytes(len);
return true;
}
void GIFImageReader::setRemainingBytes(size_t remainingBytes)
{
ASSERT(remainingBytes <= m_data->size());
m_bytesRead = m_data->size() - remainingBytes;
}
void GIFImageReader::addFrameIfNecessary()
{
if (m_frames.isEmpty() || m_frames.last()->isComplete())
m_frames.append(std::make_unique<GIFFrameContext>(m_frames.size()));
}
bool GIFLZWContext::prepareToDecode()
{
ASSERT(m_frameContext->isDataSizeDefined() && m_frameContext->isHeaderDefined());
if (m_frameContext->datasize >= MAX_LZW_BITS)
return false;
clearCode = 1 << m_frameContext->datasize;
if (clearCode >= MAX_BYTES)
return false;
avail = clearCode + 2;
oldcode = -1;
codesize = m_frameContext->datasize + 1;
codemask = (1 << codesize) - 1;
datum = bits = 0;
ipass = m_frameContext->interlaced ? 1 : 0;
irow = 0;
suffix.resize(MAX_BYTES);
stack.resize(MAX_BYTES);
prefix.resize(MAX_BYTES);
rowBuffer.resize(m_frameContext->width);
rowPosition = 0;
rowsRemaining = m_frameContext->height;
suffix.fill(0);
for (int i = 0; i < clearCode; i++)
suffix[i] = i;
stackp = 0;
return true;
}