ICOImageDecoder.cpp [plain text]
#include "config.h"
#include "ICOImageDecoder.h"
#include "BMPImageReader.h"
#include "PNGImageDecoder.h"
namespace WebCore {
static const size_t sizeOfDirectory = 6;
static const size_t sizeOfDirEntry = 16;
ICOImageDecoder::ICOImageDecoder(const IntSize& preferredIconSize)
: ImageDecoder()
, m_allDataReceived(false)
, m_decodedOffset(0)
, m_preferredIconSize(preferredIconSize)
, m_imageType(Unknown)
{
}
void ICOImageDecoder::setData(SharedBuffer* data, bool allDataReceived)
{
if (failed())
return;
ImageDecoder::setData(data, allDataReceived);
m_allDataReceived = allDataReceived;
if (m_bmpReader)
m_bmpReader->setData(data);
if (m_pngDecoder) {
RefPtr<SharedBuffer> pngData(
SharedBuffer::create(&m_data->data()[m_dirEntry.dwImageOffset],
m_data->size() - m_dirEntry.dwImageOffset));
m_pngDecoder->setData(pngData.get(), m_allDataReceived);
}
}
bool ICOImageDecoder::isSizeAvailable()
{
if (!ImageDecoder::isSizeAvailable() && !failed())
decodeWithCheckForDataEnded(true);
return (m_imageType == PNG) ?
m_pngDecoder->isSizeAvailable() : ImageDecoder::isSizeAvailable();
}
IntSize ICOImageDecoder::size() const
{
return (m_imageType == PNG) ? m_pngDecoder->size() : ImageDecoder::size();
}
RGBA32Buffer* ICOImageDecoder::frameBufferAtIndex(size_t index)
{
if (index)
return 0;
if (m_imageType == Unknown)
decodeWithCheckForDataEnded(true);
if (m_imageType == PNG) {
m_frameBufferCache.clear();
return m_pngDecoder->frameBufferAtIndex(index);
}
if (m_frameBufferCache.isEmpty())
m_frameBufferCache.resize(1);
RGBA32Buffer* buffer = &m_frameBufferCache.first();
if (buffer->status() != RGBA32Buffer::FrameComplete && (m_imageType == BMP)
&& !failed())
decodeWithCheckForDataEnded(false);
return buffer;
}
void ICOImageDecoder::decodeWithCheckForDataEnded(bool onlySize)
{
if (failed())
return;
if (!decode(onlySize) && m_allDataReceived)
setFailed();
}
bool ICOImageDecoder::decode(bool onlySize)
{
if ((m_decodedOffset < sizeOfDirectory) && !processDirectory())
return false;
if ((m_decodedOffset <
(sizeOfDirectory + (m_directory.idCount * sizeOfDirEntry)))
&& !processDirectoryEntries())
return false;
if ((m_imageType == Unknown) && !processImageType())
return false;
if ((m_imageType == PNG) ? !m_pngDecoder : !m_bmpReader) {
if (m_imageType == PNG)
m_pngDecoder.set(new PNGImageDecoder());
else
m_bmpReader.set(new BMPImageReader(this, m_decodedOffset, 0, true));
setData(m_data.get(), m_allDataReceived);
}
if (m_imageType == PNG)
return true;
if (!m_frameBufferCache.isEmpty())
m_bmpReader->setBuffer(&m_frameBufferCache.first());
return m_bmpReader->decodeBMP(onlySize);
}
bool ICOImageDecoder::processDirectory()
{
ASSERT(!m_decodedOffset);
if (m_data->size() < sizeOfDirectory)
return false;
const uint16_t fileType = readUint16(2);
m_directory.idCount = readUint16(4);
m_decodedOffset = sizeOfDirectory;
enum {
ICON = 1,
CURSOR = 2,
};
if (((fileType != ICON) && (fileType != CURSOR)) ||
(m_directory.idCount == 0))
setFailed();
return !failed();
}
bool ICOImageDecoder::processDirectoryEntries()
{
ASSERT(m_decodedOffset == sizeOfDirectory);
if ((m_decodedOffset > m_data->size())
|| ((m_data->size() - m_decodedOffset) <
(m_directory.idCount * sizeOfDirEntry)))
return false;
for (int i = 0; i < m_directory.idCount; ++i) {
const IconDirectoryEntry dirEntry = readDirectoryEntry();
if ((i == 0) || isBetterEntry(dirEntry))
m_dirEntry = dirEntry;
}
if ((m_dirEntry.dwImageOffset < m_decodedOffset) ||
((m_dirEntry.dwImageOffset + 4) < m_dirEntry.dwImageOffset)) {
setFailed();
return false;
}
m_decodedOffset = m_dirEntry.dwImageOffset;
return true;
}
ICOImageDecoder::IconDirectoryEntry ICOImageDecoder::readDirectoryEntry()
{
IconDirectoryEntry entry;
entry.bWidth = static_cast<uint8_t>(m_data->data()[m_decodedOffset]);
if (entry.bWidth == 0)
entry.bWidth = 256;
entry.bHeight = static_cast<uint8_t>(m_data->data()[m_decodedOffset + 1]);
if (entry.bHeight == 0)
entry.bHeight = 256;
entry.wBitCount = readUint16(6);
entry.dwImageOffset = readUint32(12);
if (!entry.wBitCount) {
uint8_t colorCount = m_data->data()[m_decodedOffset + 2];
if (colorCount) {
for (--colorCount; colorCount; colorCount >>= 1)
++entry.wBitCount;
}
}
m_decodedOffset += sizeOfDirEntry;
return entry;
}
bool ICOImageDecoder::isBetterEntry(const IconDirectoryEntry& entry) const
{
const IntSize entrySize(entry.bWidth, entry.bHeight);
const IntSize dirEntrySize(m_dirEntry.bWidth, m_dirEntry.bHeight);
const int entryArea = entry.bWidth * entry.bHeight;
const int dirEntryArea = m_dirEntry.bWidth * m_dirEntry.bHeight;
if ((entrySize != dirEntrySize) && !m_preferredIconSize.isEmpty()) {
if (entrySize == m_preferredIconSize)
return true;
if (dirEntrySize == m_preferredIconSize)
return false;
if (entryArea != dirEntryArea) {
return (entryArea < dirEntryArea) && (entryArea >=
(m_preferredIconSize.width() * m_preferredIconSize.height()));
}
}
if (entryArea != dirEntryArea)
return (entryArea > dirEntryArea);
return (entry.wBitCount > m_dirEntry.wBitCount);
}
bool ICOImageDecoder::processImageType()
{
ASSERT(m_decodedOffset == m_dirEntry.dwImageOffset);
if ((m_decodedOffset > m_data->size())
|| ((m_data->size() - m_decodedOffset) < 4))
return false;
m_imageType =
strncmp(&m_data->data()[m_decodedOffset], "\x89PNG", 4) ? BMP : PNG;
return true;
}
}