summaryrefslogtreecommitdiffstats
path: root/WebCore/platform/image-decoders/skia/ICOImageDecoder.cpp
blob: c340896b98d1647f5837c3a0f76f29cbf7668886 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
 * 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 "config.h"
#include "ICOImageDecoder.h"

namespace WebCore {

// 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 sizeOfDirectory = 6;
static const size_t sizeOfDirEntry = 16;

void ICOImageDecoder::decodeImage(SharedBuffer* data)
{
    // Read and process directory.
    if ((m_decodedOffset < sizeOfDirectory) && !processDirectory(data))
        return;

    // Read and process directory entries.
    if ((m_decodedOffset < (sizeOfDirectory + (m_directory.idCount * sizeOfDirEntry)))
        && !processDirectoryEntries(data))
        return;

    // Check if this entry is a PNG; we need 4 bytes to check the magic number.
    if (m_imageType == Unknown) {
        if (data->size() < (m_dirEntry.dwImageOffset + 4))
            return;
        m_imageType =
            strncmp(&data->data()[m_dirEntry.dwImageOffset], "\x89PNG", 4) ?
            BMP : PNG;
    }

    // Decode selected entry.
    if (m_imageType == PNG)
        decodePNG(data);
    else {
        // Note that we don't try to limit the bytes we give to the decoder to
        // just the size specified in the icon directory.  If the size given in
        // the directory is insufficient to decode the whole image, the image is
        // corrupt anyway, so whatever we do may be wrong.  The easiest choice
        // (which we do here) is to simply aggressively consume bytes until we
        // run out of bytes, finish decoding, or hit a sequence that makes the
        // decoder fail.
        decodeBMP(data);
    }
}

RGBA32Buffer* ICOImageDecoder::frameBufferAtIndex(size_t index)
{
    return (m_imageType == PNG) ? m_pngDecoder.frameBufferAtIndex(0) :
        BMPImageReader::frameBufferAtIndex(0);
}

bool ICOImageDecoder::isSizeAvailable() const
{
    return (m_imageType == PNG) ? m_pngDecoder.isSizeAvailable() :
        BMPImageReader::isSizeAvailable();
}

IntSize ICOImageDecoder::size() const
{
    return (m_imageType == PNG) ? m_pngDecoder.size() : BMPImageReader::size();
}

bool ICOImageDecoder::processDirectory(SharedBuffer* data)
{
    // Read directory.
    ASSERT(!m_decodedOffset);
    if (data->size() < sizeOfDirectory)
        return false;
    const uint16_t fileType = readUint16(data, 2);
    m_directory.idCount = readUint16(data, 4);
    m_decodedOffset = sizeOfDirectory;

    // See if this is an icon filetype we understand, and make sure we have at
    // least one entry in the directory.
    enum {
        ICON = 1,
        CURSOR = 2,
    };
    if (((fileType != ICON) && (fileType != CURSOR)) ||
            (m_directory.idCount == 0))
        m_failed = true;

    return !m_failed;
}

bool ICOImageDecoder::processDirectoryEntries(SharedBuffer* data)
{
    // Read directory entries.
    ASSERT(m_decodedOffset == sizeOfDirectory);
    if ((m_decodedOffset > data->size()) || (data->size() - m_decodedOffset) <
            (m_directory.idCount * sizeOfDirEntry))
        return false;
    for (int i = 0; i < m_directory.idCount; ++i) {
        const IconDirectoryEntry dirEntry = readDirectoryEntry(data);
        if ((i == 0) || isBetterEntry(dirEntry))
            m_dirEntry = dirEntry;
    }

    // Make sure the specified image offset is past the end of the directory
    // entries, and that the offset isn't so large that it overflows when we add
    // 4 bytes to it (which we do in decodeImage() while ensuring it's safe to
    // examine the first 4 bytes of the image data).
    if ((m_dirEntry.dwImageOffset < m_decodedOffset) ||
            ((m_dirEntry.dwImageOffset + 4) < m_dirEntry.dwImageOffset)) {
      m_failed = true;
      return false;
    }

    // Ready to decode the image at the specified offset.
    m_decodedOffset = m_headerOffset = m_dirEntry.dwImageOffset;
    return true;
}

ICOImageDecoder::IconDirectoryEntry ICOImageDecoder::readDirectoryEntry(
    SharedBuffer* data)
{
    // Read icon data.
    IconDirectoryEntry entry;
    entry.bWidth = static_cast<uint8_t>(data->data()[m_decodedOffset]);
    if (entry.bWidth == 0)
        entry.bWidth = 256;
    entry.bHeight = static_cast<uint8_t>(data->data()[m_decodedOffset + 1]);
    if (entry.bHeight == 0)
        entry.bHeight = 256;
    entry.wBitCount = readUint16(data, 6);
    entry.dwImageOffset = readUint32(data, 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.wBitCount) {
        uint8_t colorCount = 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()) {
        // An icon of exactly the preferred size is best.
        if (entrySize == m_preferredIconSize)
            return true;
        if (dirEntrySize == m_preferredIconSize)
            return false;

        // The icon closest to the preferred area without being smaller is
        // better.
        if (entryArea != dirEntryArea) {
            return (entryArea < dirEntryArea)
                && (entryArea >= (m_preferredIconSize.width() * m_preferredIconSize.height()));
        }
    }

    // Larger icons are better.
    if (entryArea != dirEntryArea)
        return (entryArea > dirEntryArea);

    // Higher bit-depth icons are better.
    return (entry.wBitCount > m_dirEntry.wBitCount);
}

void ICOImageDecoder::decodePNG(SharedBuffer* data)
{
    // Copy out PNG data to a separate vector and instantiate PNG decoder.
    // It would be nice to save this copy, if I could figure out how to just
    // offset the perceived start of |data| by |m_dirEntry.dwImageOffset| when
    // passing it to setData()...
    RefPtr<SharedBuffer> pngData(
        SharedBuffer::create(&data->data()[m_dirEntry.dwImageOffset],
                             data->size() - m_dirEntry.dwImageOffset));
    m_pngDecoder.setData(pngData.get(), true);

    // Decode PNG as a side effect of asking for the frame.  Strangely, it's
    // seemingly unsafe to call decode() or isSizeAvailable() before calling
    // this, as this is the only function that enlarges the framebuffer to
    // nonzero size, and before this happens any decoded image data is silently
    // thrown away and never decoded again (!).
    m_pngDecoder.frameBufferAtIndex(0);
    m_failed = m_pngDecoder.failed();

    // Sanity-check that the size is what we expected.
    if (isSizeAvailable() && ((size().width() != m_dirEntry.bWidth) ||
            (size().height() != m_dirEntry.bHeight)))
        m_failed = true;
}

}