65f2f8ff56752737bda032e7e517106262ee276c
[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     CGImageSourceUpdateData(m_decoder, data->createCFData().get(), allDataReceived);
137 #else
138     if (!m_decoder) {
139         m_decoder = CGImageSourceCreateIncremental(0);
140     } else if (allDataReceived) {
141 #if !PLATFORM(WIN)
142         // 10.6 bug workaround: image sources with final=false fail to draw into PDF contexts, so re-create image source
143         // when data is complete. <rdar://problem/7874035> (<http://openradar.appspot.com/7874035>)
144         CFRelease(m_decoder);
145         m_decoder = CGImageSourceCreateIncremental(0);
146 #endif
147     }
148     // Create a CGDataProvider to wrap the SharedBuffer.
149     data->ref();
150     // We use the GetBytesAtPosition callback rather than the GetBytePointer one because SharedBuffer
151     // does not provide a way to lock down the byte pointer and guarantee that it won't move, which
152     // is a requirement for using the GetBytePointer callback.
153     CGDataProviderDirectCallbacks providerCallbacks = { 0, 0, 0, sharedBufferGetBytesAtPosition, sharedBufferRelease };
154     RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateDirect(data, data->size(), &providerCallbacks));
155     CGImageSourceUpdateDataProvider(m_decoder, dataProvider.get(), allDataReceived);
156 #endif
157 }
158
159 String ImageSource::filenameExtension() const
160 {
161     if (!m_decoder)
162         return String();
163     CFStringRef imageSourceType = CGImageSourceGetType(m_decoder);
164     return WebCore::preferredExtensionForImageSourceType(imageSourceType);
165 }
166
167 bool ImageSource::isSizeAvailable()
168 {
169     bool result = false;
170     CGImageSourceStatus imageSourceStatus = CGImageSourceGetStatus(m_decoder);
171
172     // Ragnaros yells: TOO SOON! You have awakened me TOO SOON, Executus!
173     if (imageSourceStatus >= kCGImageStatusIncomplete) {
174         RetainPtr<CFDictionaryRef> image0Properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions(SkipMetadata)));
175         if (image0Properties) {
176             CFNumberRef widthNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelWidth);
177             CFNumberRef heightNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelHeight);
178             result = widthNumber && heightNumber;
179         }
180     }
181     
182     return result;
183 }
184
185 static ImageOrientation orientationFromProperties(CFDictionaryRef imageProperties)
186 {
187     ASSERT(imageProperties);
188     CFNumberRef orientationProperty = (CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation);
189     if (!orientationProperty)
190         return DefaultImageOrientation;
191
192     int exifValue;
193     CFNumberGetValue(orientationProperty, kCFNumberIntType, &exifValue);
194     return ImageOrientation::fromEXIFValue(exifValue);
195 }
196
197 IntSize ImageSource::frameSizeAtIndex(size_t index, ImageOrientationDescription description) const
198 {
199     RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
200
201     if (!properties)
202         return IntSize();
203
204     int w = 0, h = 0;
205     CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth);
206     if (num)
207         CFNumberGetValue(num, kCFNumberIntType, &w);
208     num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight);
209     if (num)
210         CFNumberGetValue(num, kCFNumberIntType, &h);
211
212     if ((description.respectImageOrientation() == RespectImageOrientation) && orientationFromProperties(properties.get()).usesWidthAsHeight())
213         return IntSize(h, w);
214
215     return IntSize(w, h);
216 }
217
218 ImageOrientation ImageSource::orientationAtIndex(size_t index) const
219 {
220     RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
221     if (!properties)
222         return DefaultImageOrientation;
223
224     return orientationFromProperties(properties.get());
225 }
226
227 IntSize ImageSource::size(ImageOrientationDescription description) const
228 {
229     return frameSizeAtIndex(0, description);
230 }
231
232 bool ImageSource::getHotSpot(IntPoint& hotSpot) const
233 {
234     RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions(SkipMetadata)));
235     if (!properties)
236         return false;
237
238     int x = -1, y = -1;
239     CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotX"));
240     if (!num || !CFNumberGetValue(num, kCFNumberIntType, &x))
241         return false;
242
243     num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotY"));
244     if (!num || !CFNumberGetValue(num, kCFNumberIntType, &y))
245         return false;
246
247     if (x < 0 || y < 0)
248         return false;
249
250     hotSpot = IntPoint(x, y);
251     return true;
252 }
253
254 size_t ImageSource::bytesDecodedToDetermineProperties() const
255 {
256     // Measured by tracing malloc/calloc calls on Mac OS 10.6.6, x86_64.
257     // A non-zero value ensures cached images with no decoded frames still enter
258     // the live decoded resources list when the CGImageSource decodes image
259     // properties, allowing the cache to prune the partially decoded image.
260     // This value is likely to be inaccurate on other platforms, but the overall
261     // behavior is unchanged.
262     return 13088;
263 }
264     
265 int ImageSource::repetitionCount()
266 {
267     int result = cAnimationLoopOnce; // No property means loop once.
268     if (!initialized())
269         return result;
270
271     RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyProperties(m_decoder, imageSourceOptions(SkipMetadata)));
272     if (properties) {
273         CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
274         if (gifProperties) {
275             CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount);
276             if (num) {
277                 // A property with value 0 means loop forever.
278                 CFNumberGetValue(num, kCFNumberIntType, &result);
279                 if (!result)
280                     result = cAnimationLoopInfinite;
281             }
282         } else
283             result = cAnimationNone; // Turns out we're not a GIF after all, so we don't animate.
284     }
285     
286     return result;
287 }
288
289 size_t ImageSource::frameCount() const
290 {
291     return m_decoder ? CGImageSourceGetCount(m_decoder) : 0;
292 }
293
294 CGImageRef ImageSource::createFrameAtIndex(size_t index)
295 {
296     if (!initialized())
297         return 0;
298
299     RetainPtr<CGImageRef> image = adoptCF(CGImageSourceCreateImageAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
300     CFStringRef imageUTI = CGImageSourceGetType(m_decoder);
301     static const CFStringRef xbmUTI = CFSTR("public.xbitmap-image");
302     if (!imageUTI || !CFEqual(imageUTI, xbmUTI))
303         return image.leakRef();
304     
305     // If it is an xbm image, mask out all the white areas to render them transparent.
306     const CGFloat maskingColors[6] = {255, 255,  255, 255, 255, 255};
307     RetainPtr<CGImageRef> maskedImage = adoptCF(CGImageCreateWithMaskingColors(image.get(), maskingColors));
308     if (!maskedImage)
309         return image.leakRef();
310
311     return maskedImage.leakRef();
312 }
313
314 bool ImageSource::frameIsCompleteAtIndex(size_t index)
315 {
316     ASSERT(frameCount());
317
318     // CGImageSourceGetStatusAtIndex claims that all frames of a multi-frame image are incomplete
319     // when we've not yet received the complete data for an image that is using an incremental data
320     // source (<rdar://problem/7679174>). We work around this by special-casing all frames except the
321     // last in an image and treating them as complete if they are present and reported as being
322     // incomplete. We do this on the assumption that loading new data can only modify the existing last
323     // frame or append new frames. The last frame is only treated as being complete if the image source
324     // reports it as such. This ensures that it is truly the last frame of the image rather than just
325     // the last that we currently have data for.
326
327     CGImageSourceStatus frameStatus = CGImageSourceGetStatusAtIndex(m_decoder, index);
328     if (index < frameCount() - 1)
329         return frameStatus >= kCGImageStatusIncomplete;
330
331     return frameStatus == kCGImageStatusComplete;
332 }
333
334 float ImageSource::frameDurationAtIndex(size_t index)
335 {
336     if (!initialized())
337         return 0;
338
339     float duration = 0;
340     RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata)));
341     if (properties) {
342         CFDictionaryRef typeProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
343         if (typeProperties) {
344             if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, WebCoreCGImagePropertyGIFUnclampedDelayTime)) {
345                 // Use the unclamped frame delay if it exists.
346                 CFNumberGetValue(num, kCFNumberFloatType, &duration);
347             } else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, kCGImagePropertyGIFDelayTime)) {
348                 // Fall back to the clamped frame delay if the unclamped frame delay does not exist.
349                 CFNumberGetValue(num, kCFNumberFloatType, &duration);
350             }
351         }
352     }
353
354     // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
355     // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
356     // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
357     // for more information.
358     if (duration < 0.011f)
359         return 0.100f;
360     return duration;
361 }
362
363 bool ImageSource::frameHasAlphaAtIndex(size_t index)
364 {
365     if (!m_decoder)
366         return false; // FIXME: why doesn't this return true?
367
368     if (!frameIsCompleteAtIndex(index))
369         return true;
370
371     CFStringRef imageType = CGImageSourceGetType(m_decoder);
372
373     // Return false if there is no image type or the image type is JPEG, because
374     // JPEG does not support alpha transparency.
375     if (!imageType || CFEqual(imageType, CFSTR("public.jpeg")))
376         return false;
377
378     // FIXME: Could return false for other non-transparent image formats.
379     // FIXME: Could maybe return false for a GIF Frame if we have enough info in the GIF properties dictionary
380     // to determine whether or not a transparent color was defined.
381     return true;
382 }
383
384 unsigned ImageSource::frameBytesAtIndex(size_t index) const
385 {
386     IntSize frameSize = frameSizeAtIndex(index, ImageOrientationDescription(RespectImageOrientation));
387     return frameSize.width() * frameSize.height() * 4;
388 }
389
390 }
391
392 #endif // USE(CG)