blob: 26453874cf7bc1b5305e7c9bb816105360018af6 [file] [log] [blame]
/*
* Copyright (c) 2008, 2009, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h"
#include <algorithm>
#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h"
namespace blink {
// Number of bits in .ICO/.CUR used to store the directory and its entries,
// respectively (doesn't match sizeof values for member structs since we omit
// some fields).
static const size_t kSizeOfDirectory = 6;
static const size_t kSizeOfDirEntry = 16;
ICOImageDecoder::ICOImageDecoder(AlphaOption alpha_option,
const ColorBehavior& color_behavior,
size_t max_decoded_bytes)
: ImageDecoder(alpha_option,
ImageDecoder::kDefaultBitDepth,
color_behavior,
max_decoded_bytes) {}
ICOImageDecoder::~ICOImageDecoder() = default;
void ICOImageDecoder::OnSetData(SegmentReader* data) {
fast_reader_.SetData(data);
for (BMPReaders::iterator i(bmp_readers_.begin()); i != bmp_readers_.end();
++i) {
if (*i)
(*i)->SetData(data);
}
for (size_t i = 0; i < png_decoders_.size(); ++i)
SetDataForPNGDecoderAtIndex(i);
}
IntSize ICOImageDecoder::Size() const {
return frame_size_.IsEmpty() ? ImageDecoder::Size() : frame_size_;
}
IntSize ICOImageDecoder::FrameSizeAtIndex(size_t index) const {
return (index && (index < dir_entries_.size())) ? dir_entries_[index].size_
: Size();
}
bool ICOImageDecoder::SetSize(unsigned width, unsigned height) {
// The size calculated inside the BMPImageReader had better match the one in
// the icon directory.
return frame_size_.IsEmpty()
? ImageDecoder::SetSize(width, height)
: ((IntSize(width, height) == frame_size_) || SetFailed());
}
bool ICOImageDecoder::FrameIsReceivedAtIndex(size_t index) const {
if (index >= dir_entries_.size())
return false;
SECURITY_DCHECK(data_);
const IconDirectoryEntry& dir_entry = dir_entries_[index];
return (dir_entry.image_offset_ + dir_entry.byte_size_) <= data_->size();
}
bool ICOImageDecoder::SetFailed() {
bmp_readers_.clear();
png_decoders_.clear();
return ImageDecoder::SetFailed();
}
bool ICOImageDecoder::HotSpot(IntPoint& hot_spot) const {
// When unspecified, the default frame is always frame 0. This is consistent
// with BitmapImage, where CurrentFrame() starts at 0 and only increases when
// animation is requested.
return HotSpotAtIndex(0, hot_spot);
}
bool ICOImageDecoder::HotSpotAtIndex(size_t index, IntPoint& hot_spot) const {
if (index >= dir_entries_.size() || file_type_ != CURSOR)
return false;
hot_spot = dir_entries_[index].hot_spot_;
return true;
}
// static
bool ICOImageDecoder::CompareEntries(const IconDirectoryEntry& a,
const IconDirectoryEntry& b) {
// Larger icons are better. After that, higher bit-depth icons are better.
const int a_entry_area = a.size_.Width() * a.size_.Height();
const int b_entry_area = b.size_.Width() * b.size_.Height();
return (a_entry_area == b_entry_area) ? (a.bit_count_ > b.bit_count_)
: (a_entry_area > b_entry_area);
}
size_t ICOImageDecoder::DecodeFrameCount() {
DecodeSize();
// If DecodeSize() fails, return the existing number of frames. This way
// if we get halfway through the image before decoding fails, we won't
// suddenly start reporting that the image has zero frames.
if (Failed() || !data_)
return frame_buffer_cache_.size();
// If the file is incomplete, return the length of the sequence of completely
// received frames. We don't do this when the file is fully received, since
// some ICOs have entries whose claimed offset + size extends past the end of
// the file, and we still want to display these if they don't trigger decoding
// failures elsewhere.
if (!IsAllDataReceived()) {
for (size_t i = 0; i < dir_entries_.size(); ++i) {
const IconDirectoryEntry& dir_entry = dir_entries_[i];
if ((dir_entry.image_offset_ + dir_entry.byte_size_) > data_->size())
return i;
}
}
return dir_entries_.size();
}
void ICOImageDecoder::SetDataForPNGDecoderAtIndex(size_t index) {
if (!png_decoders_[index])
return;
png_decoders_[index]->SetData(data_.get(), IsAllDataReceived());
}
void ICOImageDecoder::Decode(size_t index, bool only_size) {
if (Failed() || !data_)
return;
// Defensively clear the FastSharedBufferReader's cache, as another caller
// may have called SharedBuffer::MergeSegmentsIntoBuffer().
fast_reader_.ClearCache();
// If we couldn't decode the image but we've received all the data, decoding
// has failed.
if ((!DecodeDirectory() || (!only_size && !DecodeAtIndex(index))) &&
IsAllDataReceived()) {
SetFailed();
// If we're done decoding this frame, we don't need the BMPImageReader or
// PNGImageDecoder anymore. (If we failed, these have already been
// cleared.)
} else if ((frame_buffer_cache_.size() > index) &&
(frame_buffer_cache_[index].GetStatus() ==
ImageFrame::kFrameComplete)) {
bmp_readers_[index].reset();
png_decoders_[index].reset();
}
}
bool ICOImageDecoder::DecodeDirectory() {
// Read and process directory.
if ((decoded_offset_ < kSizeOfDirectory) && !ProcessDirectory())
return false;
// Read and process directory entries.
return (decoded_offset_ >=
(kSizeOfDirectory + (dir_entries_count_ * kSizeOfDirEntry))) ||
ProcessDirectoryEntries();
}
bool ICOImageDecoder::DecodeAtIndex(size_t index) {
SECURITY_DCHECK(index < dir_entries_.size());
const IconDirectoryEntry& dir_entry = dir_entries_[index];
const ImageType image_type = ImageTypeAtIndex(index);
if (image_type == kUnknown)
return false; // Not enough data to determine image type yet.
if (image_type == BMP) {
if (!bmp_readers_[index]) {
bmp_readers_[index] = std::make_unique<BMPImageReader>(
this, dir_entry.image_offset_, 0, true);
bmp_readers_[index]->SetData(data_.get());
}
// Update the pointer to the buffer as it could change after
// frame_buffer_cache_.resize().
bmp_readers_[index]->SetBuffer(&frame_buffer_cache_[index]);
frame_size_ = dir_entry.size_;
bool result = bmp_readers_[index]->DecodeBMP(false);
frame_size_ = IntSize();
return result;
}
if (!png_decoders_[index]) {
AlphaOption alpha_option =
premultiply_alpha_ ? kAlphaPremultiplied : kAlphaNotPremultiplied;
png_decoders_[index] = std::make_unique<PNGImageDecoder>(
alpha_option, ImageDecoder::kDefaultBitDepth, color_behavior_,
max_decoded_bytes_, dir_entry.image_offset_);
SetDataForPNGDecoderAtIndex(index);
}
auto* png_decoder = png_decoders_[index].get();
if (png_decoder->IsSizeAvailable()) {
// Fail if the size the PNGImageDecoder calculated does not match the size
// in the directory.
if (png_decoder->Size() != dir_entry.size_)
return SetFailed();
png_decoder->SetMemoryAllocator(frame_buffer_cache_[index].GetAllocator());
const auto* frame = png_decoder->DecodeFrameBufferAtIndex(0);
png_decoder->SetMemoryAllocator(nullptr);
if (frame)
frame_buffer_cache_[index] = *frame;
}
if (png_decoder->Failed())
return SetFailed();
return frame_buffer_cache_[index].GetStatus() == ImageFrame::kFrameComplete;
}
bool ICOImageDecoder::ProcessDirectory() {
// Read directory.
SECURITY_DCHECK(data_);
DCHECK(!decoded_offset_);
if (data_->size() < kSizeOfDirectory)
return false;
const uint16_t file_type = ReadUint16(2);
dir_entries_count_ = ReadUint16(4);
decoded_offset_ = kSizeOfDirectory;
// See if this is an icon filetype we understand, and make sure we have at
// least one entry in the directory.
if (((file_type != ICON) && (file_type != CURSOR)) || (!dir_entries_count_))
return SetFailed();
file_type_ = static_cast<FileType>(file_type);
return true;
}
bool ICOImageDecoder::ProcessDirectoryEntries() {
// Read directory entries.
SECURITY_DCHECK(data_);
DCHECK_EQ(decoded_offset_, kSizeOfDirectory);
if ((decoded_offset_ > data_->size()) ||
((data_->size() - decoded_offset_) <
(dir_entries_count_ * kSizeOfDirEntry)))
return false;
// Enlarge member vectors to hold all the entries.
dir_entries_.resize(dir_entries_count_);
bmp_readers_.resize(dir_entries_count_);
png_decoders_.resize(dir_entries_count_);
for (IconDirectoryEntries::iterator i(dir_entries_.begin());
i != dir_entries_.end(); ++i)
*i = ReadDirectoryEntry(); // Updates decoded_offset_.
// Make sure the specified image offsets are past the end of the directory
// entries.
for (IconDirectoryEntries::iterator i(dir_entries_.begin());
i != dir_entries_.end(); ++i) {
if (i->image_offset_ < decoded_offset_)
return SetFailed();
}
// Arrange frames in decreasing quality order.
std::sort(dir_entries_.begin(), dir_entries_.end(), CompareEntries);
// The image size is the size of the largest entry.
const IconDirectoryEntry& dir_entry = dir_entries_.front();
// Technically, this next call shouldn't be able to fail, since the width
// and height here are each <= 256, and |frame_size_| is empty.
return SetSize(static_cast<unsigned>(dir_entry.size_.Width()),
static_cast<unsigned>(dir_entry.size_.Height()));
}
ICOImageDecoder::IconDirectoryEntry ICOImageDecoder::ReadDirectoryEntry() {
// Read icon data.
// The following calls to ReadUint8() return a uint8_t, which is appropriate
// because that's the on-disk type of the width and height values. Storing
// them in ints (instead of matching uint8_ts) is so we can record dimensions
// of size 256 (which is what a zero byte really means).
int width = ReadUint8(0);
if (!width)
width = 256;
int height = ReadUint8(1);
if (!height)
height = 256;
IconDirectoryEntry entry;
entry.size_ = IntSize(width, height);
if (file_type_ == CURSOR) {
entry.bit_count_ = 0;
entry.hot_spot_ = IntPoint(ReadUint16(4), ReadUint16(6));
} else {
entry.bit_count_ = ReadUint16(6);
entry.hot_spot_ = IntPoint();
}
entry.byte_size_ = ReadUint32(8);
entry.image_offset_ = ReadUint32(12);
// Some icons don't have a bit depth, only a color count. Convert the
// color count to the minimum necessary bit depth. It doesn't matter if
// this isn't quite what the bitmap info header says later, as we only use
// this value to determine which icon entry is best.
if (!entry.bit_count_) {
int color_count = ReadUint8(2);
if (!color_count)
color_count = 256; // Vague in the spec, needed by real-world icons.
for (--color_count; color_count; color_count >>= 1)
++entry.bit_count_;
}
decoded_offset_ += kSizeOfDirEntry;
return entry;
}
ICOImageDecoder::ImageType ICOImageDecoder::ImageTypeAtIndex(size_t index) {
// Check if this entry is a BMP or a PNG; we need 4 bytes to check the magic
// number.
SECURITY_DCHECK(data_);
SECURITY_DCHECK(index < dir_entries_.size());
const uint32_t image_offset = dir_entries_[index].image_offset_;
if ((image_offset > data_->size()) || ((data_->size() - image_offset) < 4))
return kUnknown;
char buffer[4];
const char* data = fast_reader_.GetConsecutiveData(image_offset, 4, buffer);
return strncmp(data, "\x89PNG", 4) ? BMP : PNG;
}
} // namespace blink