Garbage on page background while http://canberraballoons.com.au is loading
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / ImageSourceCG.cpp
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
39 using namespace std;
40
41 namespace WebCore {
42
43 const CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32");
44 const 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.
49 const CFStringRef WebCoreCGImagePropertyGIFUnclampedDelayTime = CFSTR("UnclampedDelayTime");
50
51 #if !PLATFORM(MAC)
52 size_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
65 void sharedBufferRelease(void* info)
66 {
67     SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
68     sharedBuffer->deref();
69 }
70 #endif
71
72 ImageSource::ImageSource(ImageSource::AlphaOption, ImageSource::GammaAndColorProfileOption)
73     : m_decoder(0)
74 {
75     // FIXME: AlphaOption and GammaAndColorProfileOption are ignored.
76 }
77
78 ImageSource::~ImageSource()
79 {
80     clear(true);
81 }
82
83 void 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
99 static 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
124 bool ImageSource::initialized() const
125 {
126     return m_decoder;
127 }
128
129 void 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
160 String ImageSource::filenameExtension() const
161 {
162     if (!m_decoder)
163         return String();
164     CFStringRef imageSourceType = CGImageSourceGetType(m_decoder);
165     return WebCore::preferredExtensionForImageSourceType(imageSourceType);
166 }
167
168 bool 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
186 static 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
198 IntSize 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
219 ImageOrientation 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
228 IntSize ImageSource::size(RespectImageOrientationEnum shouldRespectOrientation) const
229 {
230     return frameSizeAtIndex(0, shouldRespectOrientation);
231 }
232
233 bool 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
255 size_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     
266 int 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
290 size_t ImageSource::frameCount() const
291 {
292     return m_decoder ? CGImageSourceGetCount(m_decoder) : 0;
293 }
294
295 CGImageRef 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
315 bool 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
335 float 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
364 bool 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
385 unsigned 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)