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 "ImageOrientation.h"
33#include "IntPoint.h"
34#include "IntSize.h"
35#include "MIMETypeRegistry.h"
36#include "SharedBuffer.h"
37#include <ApplicationServices/ApplicationServices.h>
38
39using namespace std;
40
41namespace WebCore {
42
43const CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32");
44const CFStringRef kCGImageSourceSkipMetaData = CFSTR("kCGImageSourceSkipMetaData");
45
46// kCGImagePropertyGIFUnclampedDelayTime is available in the ImageIO framework headers on some versions
47// of SnowLeopard. It's not possible to detect whether the constant is available so we define our own here
48// that won't conflict with ImageIO's version when it is available.
49const CFStringRef WebCoreCGImagePropertyGIFUnclampedDelayTime = CFSTR("UnclampedDelayTime");
50
51#if !PLATFORM(MAC)
52size_t sharedBufferGetBytesAtPosition(void* info, void* buffer, off_t position, size_t count)
53{
54    SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
55    size_t sourceSize = sharedBuffer->size();
56    if (position >= sourceSize)
57        return 0;
58
59    const char* source = sharedBuffer->data() + position;
60    size_t amount = min<size_t>(count, sourceSize - position);
61    memcpy(buffer, source, amount);
62    return amount;
63}
64
65void sharedBufferRelease(void* info)
66{
67    SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
68    sharedBuffer->deref();
69}
70#endif
71
72ImageSource::ImageSource(ImageSource::AlphaOption, ImageSource::GammaAndColorProfileOption)
73    : m_decoder(0)
74{
75    // FIXME: AlphaOption and GammaAndColorProfileOption are ignored.
76}
77
78ImageSource::~ImageSource()
79{
80    clear(true);
81}
82
83void ImageSource::clear(bool destroyAllFrames, size_t, SharedBuffer* data, bool allDataReceived)
84{
85    // Recent versions of ImageIO discard previously decoded image frames if the client
86    // application no longer holds references to them, so there's no need to throw away
87    // the decoder unless we're explicitly asked to destroy all of the frames.
88    if (!destroyAllFrames)
89        return;
90
91    if (m_decoder) {
92        CFRelease(m_decoder);
93        m_decoder = 0;
94    }
95    if (data)
96        setData(data, allDataReceived);
97}
98
99static CFDictionaryRef imageSourceOptions(ImageSource::ShouldSkipMetadata skipMetadata)
100{
101    static CFDictionaryRef options;
102
103    if (!options) {
104        const unsigned numOptions = 3;
105
106#if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED <= 1070
107        // Lion and Snow Leopard only return Orientation when kCGImageSourceSkipMetaData is false,
108        // and incorrectly return cached metadata if an image is queried once with kCGImageSourceSkipMetaData true
109        // and then subsequently with kCGImageSourceSkipMetaData false.
110        // <rdar://problem/11148192>
111        UNUSED_PARAM(skipMetadata);
112        const CFBooleanRef imageSourceSkipMetadata = kCFBooleanFalse;
113#else
114        const CFBooleanRef imageSourceSkipMetadata = (skipMetadata == ImageSource::SkipMetadata) ? kCFBooleanTrue : kCFBooleanFalse;
115#endif
116        const void* keys[numOptions] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32, kCGImageSourceSkipMetaData };
117        const void* values[numOptions] = { kCFBooleanTrue, kCFBooleanTrue, imageSourceSkipMetadata };
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(SkipMetadata)));
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
186static ImageOrientation orientationFromProperties(CFDictionaryRef imageProperties)
187{
188    ASSERT(imageProperties);
189    CFNumberRef orientationProperty = (CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation);
190    if (!orientationProperty)
191        return DefaultImageOrientation;
192
193    int exifValue;
194    CFNumberGetValue(orientationProperty, kCFNumberIntType, &exifValue);
195    return ImageOrientation::fromEXIFValue(exifValue);
196}
197
198IntSize ImageSource::frameSizeAtIndex(size_t index, RespectImageOrientationEnum shouldRespectOrientation) const
199{
200    RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
201
202    if (!properties)
203        return IntSize();
204
205    int w = 0, h = 0;
206    CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth);
207    if (num)
208        CFNumberGetValue(num, kCFNumberIntType, &w);
209    num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight);
210    if (num)
211        CFNumberGetValue(num, kCFNumberIntType, &h);
212
213    if ((shouldRespectOrientation == RespectImageOrientation) && orientationFromProperties(properties.get()).usesWidthAsHeight())
214        return IntSize(h, w);
215
216    return IntSize(w, h);
217}
218
219ImageOrientation ImageSource::orientationAtIndex(size_t index) const
220{
221    RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
222    if (!properties)
223        return DefaultImageOrientation;
224
225    return orientationFromProperties(properties.get());
226}
227
228IntSize ImageSource::size(RespectImageOrientationEnum shouldRespectOrientation) const
229{
230    return frameSizeAtIndex(0, shouldRespectOrientation);
231}
232
233bool ImageSource::getHotSpot(IntPoint& hotSpot) const
234{
235    RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions(SkipMetadata)));
236    if (!properties)
237        return false;
238
239    int x = -1, y = -1;
240    CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotX"));
241    if (!num || !CFNumberGetValue(num, kCFNumberIntType, &x))
242        return false;
243
244    num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotY"));
245    if (!num || !CFNumberGetValue(num, kCFNumberIntType, &y))
246        return false;
247
248    if (x < 0 || y < 0)
249        return false;
250
251    hotSpot = IntPoint(x, y);
252    return true;
253}
254
255size_t ImageSource::bytesDecodedToDetermineProperties() const
256{
257    // Measured by tracing malloc/calloc calls on Mac OS 10.6.6, x86_64.
258    // A non-zero value ensures cached images with no decoded frames still enter
259    // the live decoded resources list when the CGImageSource decodes image
260    // properties, allowing the cache to prune the partially decoded image.
261    // This value is likely to be inaccurate on other platforms, but the overall
262    // behavior is unchanged.
263    return 13088;
264}
265
266int ImageSource::repetitionCount()
267{
268    int result = cAnimationLoopOnce; // No property means loop once.
269    if (!initialized())
270        return result;
271
272    RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyProperties(m_decoder, imageSourceOptions(SkipMetadata)));
273    if (properties) {
274        CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
275        if (gifProperties) {
276            CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount);
277            if (num) {
278                // A property with value 0 means loop forever.
279                CFNumberGetValue(num, kCFNumberIntType, &result);
280                if (!result)
281                    result = cAnimationLoopInfinite;
282            }
283        } else
284            result = cAnimationNone; // Turns out we're not a GIF after all, so we don't animate.
285    }
286
287    return result;
288}
289
290size_t ImageSource::frameCount() const
291{
292    return m_decoder ? CGImageSourceGetCount(m_decoder) : 0;
293}
294
295CGImageRef ImageSource::createFrameAtIndex(size_t index)
296{
297    if (!initialized())
298        return 0;
299
300    RetainPtr<CGImageRef> image = adoptCF(CGImageSourceCreateImageAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
301    CFStringRef imageUTI = CGImageSourceGetType(m_decoder);
302    static const CFStringRef xbmUTI = CFSTR("public.xbitmap-image");
303    if (!imageUTI || !CFEqual(imageUTI, xbmUTI))
304        return image.leakRef();
305
306    // If it is an xbm image, mask out all the white areas to render them transparent.
307    const CGFloat maskingColors[6] = {255, 255,  255, 255, 255, 255};
308    RetainPtr<CGImageRef> maskedImage = adoptCF(CGImageCreateWithMaskingColors(image.get(), maskingColors));
309    if (!maskedImage)
310        return image.leakRef();
311
312    return maskedImage.leakRef();
313}
314
315bool ImageSource::frameIsCompleteAtIndex(size_t index)
316{
317    ASSERT(frameCount());
318
319    // CGImageSourceGetStatusAtIndex claims that all frames of a multi-frame image are incomplete
320    // when we've not yet received the complete data for an image that is using an incremental data
321    // source (<rdar://problem/7679174>). We work around this by special-casing all frames except the
322    // last in an image and treating them as complete if they are present and reported as being
323    // incomplete. We do this on the assumption that loading new data can only modify the existing last
324    // frame or append new frames. The last frame is only treated as being complete if the image source
325    // reports it as such. This ensures that it is truly the last frame of the image rather than just
326    // the last that we currently have data for.
327
328    CGImageSourceStatus frameStatus = CGImageSourceGetStatusAtIndex(m_decoder, index);
329    if (index < frameCount() - 1)
330        return frameStatus >= kCGImageStatusIncomplete;
331
332    return frameStatus == kCGImageStatusComplete;
333}
334
335float ImageSource::frameDurationAtIndex(size_t index)
336{
337    if (!initialized())
338        return 0;
339
340    float duration = 0;
341    RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
342    if (properties) {
343        CFDictionaryRef typeProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
344        if (typeProperties) {
345            if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, WebCoreCGImagePropertyGIFUnclampedDelayTime)) {
346                // Use the unclamped frame delay if it exists.
347                CFNumberGetValue(num, kCFNumberFloatType, &duration);
348            } else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, kCGImagePropertyGIFDelayTime)) {
349                // Fall back to the clamped frame delay if the unclamped frame delay does not exist.
350                CFNumberGetValue(num, kCFNumberFloatType, &duration);
351            }
352        }
353    }
354
355    // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
356    // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
357    // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
358    // for more information.
359    if (duration < 0.011f)
360        return 0.100f;
361    return duration;
362}
363
364bool ImageSource::frameHasAlphaAtIndex(size_t index)
365{
366    if (!m_decoder)
367        return false; // FIXME: why doesn't this return true?
368
369    if (!frameIsCompleteAtIndex(index))
370        return true;
371
372    CFStringRef imageType = CGImageSourceGetType(m_decoder);
373
374    // Return false if there is no image type or the image type is JPEG, because
375    // JPEG does not support alpha transparency.
376    if (!imageType || CFEqual(imageType, CFSTR("public.jpeg")))
377        return false;
378
379    // FIXME: Could return false for other non-transparent image formats.
380    // FIXME: Could maybe return false for a GIF Frame if we have enough info in the GIF properties dictionary
381    // to determine whether or not a transparent color was defined.
382    return true;
383}
384
385unsigned ImageSource::frameBytesAtIndex(size_t index) const
386{
387    IntSize frameSize = frameSizeAtIndex(index, RespectImageOrientation);
388    return frameSize.width() * frameSize.height() * 4;
389}
390
391}
392
393#endif // USE(CG)
394