1/*
2 * Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ImageSource.h"
28
29#if USE(CG)
30#include "ImageSourceCG.h"
31
32#include "IntPoint.h"
33#include "IntSize.h"
34#include "MIMETypeRegistry.h"
35#include "SharedBuffer.h"
36#include <ApplicationServices/ApplicationServices.h>
37#include <wtf/UnusedParam.h>
38
39using namespace std;
40
41namespace WebCore {
42
43const CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32");
44
45// kCGImagePropertyGIFUnclampedDelayTime is available in the ImageIO framework headers on some versions
46// of SnowLeopard. It's not possible to detect whether the constant is available so we define our own here
47// that won't conflict with ImageIO's version when it is available.
48const CFStringRef WebCoreCGImagePropertyGIFUnclampedDelayTime = CFSTR("UnclampedDelayTime");
49
50#if !PLATFORM(MAC)
51size_t sharedBufferGetBytesAtPosition(void* info, void* buffer, off_t position, size_t count)
52{
53    SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
54    size_t sourceSize = sharedBuffer->size();
55    if (position >= sourceSize)
56        return 0;
57
58    const char* source = sharedBuffer->data() + position;
59    size_t amount = min<size_t>(count, sourceSize - position);
60    memcpy(buffer, source, amount);
61    return amount;
62}
63
64void sharedBufferRelease(void* info)
65{
66    SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
67    sharedBuffer->deref();
68}
69#endif
70
71ImageSource::ImageSource(ImageSource::AlphaOption alphaOption, ImageSource::GammaAndColorProfileOption gammaAndColorProfileOption)
72    : m_decoder(0)
73    // FIXME: m_premultiplyAlpha is ignored in cg at the moment.
74    , m_alphaOption(alphaOption)
75    , m_gammaAndColorProfileOption(gammaAndColorProfileOption)
76{
77}
78
79ImageSource::~ImageSource()
80{
81    clear(true);
82}
83
84void ImageSource::clear(bool destroyAllFrames, size_t, SharedBuffer* data, bool allDataReceived)
85{
86#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
87    // Recent versions of ImageIO discard previously decoded image frames if the client
88    // application no longer holds references to them, so there's no need to throw away
89    // the decoder unless we're explicitly asked to destroy all of the frames.
90
91    if (!destroyAllFrames)
92        return;
93#else
94    // Older versions of ImageIO hold references to previously decoded image frames.
95    // There is no API to selectively release some of the frames it is holding, and
96    // if we don't release the frames we use too much memory on large images.
97    // Destroying the decoder is the only way to release previous frames.
98
99    UNUSED_PARAM(destroyAllFrames);
100#endif
101
102    if (m_decoder) {
103        CFRelease(m_decoder);
104        m_decoder = 0;
105    }
106    if (data)
107        setData(data, allDataReceived);
108}
109
110static CFDictionaryRef imageSourceOptions()
111{
112    static CFDictionaryRef options;
113
114    if (!options) {
115        const unsigned numOptions = 2;
116        const void* keys[numOptions] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32 };
117        const void* values[numOptions] = { kCFBooleanTrue, kCFBooleanTrue };
118        options = CFDictionaryCreate(NULL, keys, values, numOptions,
119            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
120    }
121    return options;
122}
123
124bool ImageSource::initialized() const
125{
126    return m_decoder;
127}
128
129void ImageSource::setData(SharedBuffer* data, bool allDataReceived)
130{
131#if PLATFORM(MAC)
132    if (!m_decoder)
133        m_decoder = CGImageSourceCreateIncremental(0);
134    // On Mac the NSData inside the SharedBuffer can be secretly appended to without the SharedBuffer's knowledge.  We use SharedBuffer's ability
135    // to wrap itself inside CFData to get around this, ensuring that ImageIO is really looking at the SharedBuffer.
136    RetainPtr<CFDataRef> cfData(AdoptCF, data->createCFData());
137    CGImageSourceUpdateData(m_decoder, cfData.get(), allDataReceived);
138#else
139    if (!m_decoder) {
140        m_decoder = CGImageSourceCreateIncremental(0);
141    } else if (allDataReceived) {
142#if !PLATFORM(WIN)
143        // 10.6 bug workaround: image sources with final=false fail to draw into PDF contexts, so re-create image source
144        // when data is complete. <rdar://problem/7874035> (<http://openradar.appspot.com/7874035>)
145        CFRelease(m_decoder);
146        m_decoder = CGImageSourceCreateIncremental(0);
147#endif
148    }
149    // Create a CGDataProvider to wrap the SharedBuffer.
150    data->ref();
151    // We use the GetBytesAtPosition callback rather than the GetBytePointer one because SharedBuffer
152    // does not provide a way to lock down the byte pointer and guarantee that it won't move, which
153    // is a requirement for using the GetBytePointer callback.
154    CGDataProviderDirectCallbacks providerCallbacks = { 0, 0, 0, sharedBufferGetBytesAtPosition, sharedBufferRelease };
155    RetainPtr<CGDataProviderRef> dataProvider(AdoptCF, CGDataProviderCreateDirect(data, data->size(), &providerCallbacks));
156    CGImageSourceUpdateDataProvider(m_decoder, dataProvider.get(), allDataReceived);
157#endif
158}
159
160String ImageSource::filenameExtension() const
161{
162    if (!m_decoder)
163        return String();
164    CFStringRef imageSourceType = CGImageSourceGetType(m_decoder);
165    return WebCore::preferredExtensionForImageSourceType(imageSourceType);
166}
167
168bool ImageSource::isSizeAvailable()
169{
170    bool result = false;
171    CGImageSourceStatus imageSourceStatus = CGImageSourceGetStatus(m_decoder);
172
173    // Ragnaros yells: TOO SOON! You have awakened me TOO SOON, Executus!
174    if (imageSourceStatus >= kCGImageStatusIncomplete) {
175        RetainPtr<CFDictionaryRef> image0Properties(AdoptCF, CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions()));
176        if (image0Properties) {
177            CFNumberRef widthNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelWidth);
178            CFNumberRef heightNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelHeight);
179            result = widthNumber && heightNumber;
180        }
181    }
182
183    return result;
184}
185
186IntSize ImageSource::frameSizeAtIndex(size_t index) const
187{
188    IntSize result;
189    RetainPtr<CFDictionaryRef> properties(AdoptCF, CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions()));
190    if (properties) {
191        int w = 0, h = 0;
192        CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth);
193        if (num)
194            CFNumberGetValue(num, kCFNumberIntType, &w);
195        num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight);
196        if (num)
197            CFNumberGetValue(num, kCFNumberIntType, &h);
198        result = IntSize(w, h);
199    }
200    return result;
201}
202
203IntSize ImageSource::size() const
204{
205    return frameSizeAtIndex(0);
206}
207
208bool ImageSource::getHotSpot(IntPoint& hotSpot) const
209{
210    RetainPtr<CFDictionaryRef> properties(AdoptCF, CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions()));
211    if (!properties)
212        return false;
213
214    int x = -1, y = -1;
215    CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotX"));
216    if (!num || !CFNumberGetValue(num, kCFNumberIntType, &x))
217        return false;
218
219    num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotY"));
220    if (!num || !CFNumberGetValue(num, kCFNumberIntType, &y))
221        return false;
222
223    if (x < 0 || y < 0)
224        return false;
225
226    hotSpot = IntPoint(x, y);
227    return true;
228}
229
230size_t ImageSource::bytesDecodedToDetermineProperties() const
231{
232    // Measured by tracing malloc/calloc calls on Mac OS 10.6.6, x86_64.
233    // A non-zero value ensures cached images with no decoded frames still enter
234    // the live decoded resources list when the CGImageSource decodes image
235    // properties, allowing the cache to prune the partially decoded image.
236    // This value is likely to be inaccurate on other platforms, but the overall
237    // behavior is unchanged.
238    return 13088;
239}
240
241int ImageSource::repetitionCount()
242{
243    int result = cAnimationLoopOnce; // No property means loop once.
244    if (!initialized())
245        return result;
246
247    RetainPtr<CFDictionaryRef> properties(AdoptCF, CGImageSourceCopyProperties(m_decoder, imageSourceOptions()));
248    if (properties) {
249        CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
250        if (gifProperties) {
251            CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount);
252            if (num) {
253                // A property with value 0 means loop forever.
254                CFNumberGetValue(num, kCFNumberIntType, &result);
255                if (!result)
256                    result = cAnimationLoopInfinite;
257            }
258        } else
259            result = cAnimationNone; // Turns out we're not a GIF after all, so we don't animate.
260    }
261
262    return result;
263}
264
265size_t ImageSource::frameCount() const
266{
267    return m_decoder ? CGImageSourceGetCount(m_decoder) : 0;
268}
269
270CGImageRef ImageSource::createFrameAtIndex(size_t index)
271{
272    if (!initialized())
273        return 0;
274
275    RetainPtr<CGImageRef> image(AdoptCF, CGImageSourceCreateImageAtIndex(m_decoder, index, imageSourceOptions()));
276    CFStringRef imageUTI = CGImageSourceGetType(m_decoder);
277    static const CFStringRef xbmUTI = CFSTR("public.xbitmap-image");
278    if (!imageUTI || !CFEqual(imageUTI, xbmUTI))
279        return image.releaseRef();
280
281    // If it is an xbm image, mask out all the white areas to render them transparent.
282    const CGFloat maskingColors[6] = {255, 255,  255, 255, 255, 255};
283    RetainPtr<CGImageRef> maskedImage(AdoptCF, CGImageCreateWithMaskingColors(image.get(), maskingColors));
284    if (!maskedImage)
285        return image.releaseRef();
286
287    return maskedImage.releaseRef();
288}
289
290bool ImageSource::frameIsCompleteAtIndex(size_t index)
291{
292    ASSERT(frameCount());
293
294    // CGImageSourceGetStatusAtIndex claims that all frames of a multi-frame image are incomplete
295    // when we've not yet received the complete data for an image that is using an incremental data
296    // source (<rdar://problem/7679174>). We work around this by special-casing all frames except the
297    // last in an image and treating them as complete if they are present and reported as being
298    // incomplete. We do this on the assumption that loading new data can only modify the existing last
299    // frame or append new frames. The last frame is only treated as being complete if the image source
300    // reports it as such. This ensures that it is truly the last frame of the image rather than just
301    // the last that we currently have data for.
302
303    CGImageSourceStatus frameStatus = CGImageSourceGetStatusAtIndex(m_decoder, index);
304    if (index < frameCount() - 1)
305        return frameStatus >= kCGImageStatusIncomplete;
306
307    return frameStatus == kCGImageStatusComplete;
308}
309
310float ImageSource::frameDurationAtIndex(size_t index)
311{
312    if (!initialized())
313        return 0;
314
315    float duration = 0;
316    RetainPtr<CFDictionaryRef> properties(AdoptCF, CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions()));
317    if (properties) {
318        CFDictionaryRef typeProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
319        if (typeProperties) {
320            if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, WebCoreCGImagePropertyGIFUnclampedDelayTime)) {
321                // Use the unclamped frame delay if it exists.
322                CFNumberGetValue(num, kCFNumberFloatType, &duration);
323            } else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, kCGImagePropertyGIFDelayTime)) {
324                // Fall back to the clamped frame delay if the unclamped frame delay does not exist.
325                CFNumberGetValue(num, kCFNumberFloatType, &duration);
326            }
327        }
328    }
329
330    // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
331    // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
332    // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
333    // for more information.
334    if (duration < 0.011f)
335        return 0.100f;
336    return duration;
337}
338
339bool ImageSource::frameHasAlphaAtIndex(size_t)
340{
341    if (!m_decoder)
342        return false;
343
344    CFStringRef imageType = CGImageSourceGetType(m_decoder);
345
346    // Return false if there is no image type or the image type is JPEG, because
347    // JPEG does not support alpha transparency.
348    if (!imageType || CFEqual(imageType, CFSTR("public.jpeg")))
349        return false;
350
351    // FIXME: Could return false for other non-transparent image formats.
352    // FIXME: Could maybe return false for a GIF Frame if we have enough info in the GIF properties dictionary
353    // to determine whether or not a transparent color was defined.
354    return true;
355}
356
357}
358
359#endif // USE(CG)
360