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 
39 using namespace std;
40 
41 namespace WebCore {
42 
43 const 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.
48 const CFStringRef WebCoreCGImagePropertyGIFUnclampedDelayTime = CFSTR("UnclampedDelayTime");
49 
50 #if !PLATFORM(MAC)
sharedBufferGetBytesAtPosition(void * info,void * buffer,off_t position,size_t count)51 size_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 
sharedBufferRelease(void * info)64 void sharedBufferRelease(void* info)
65 {
66     SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
67     sharedBuffer->deref();
68 }
69 #endif
70 
ImageSource(ImageSource::AlphaOption alphaOption,ImageSource::GammaAndColorProfileOption gammaAndColorProfileOption)71 ImageSource::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 
~ImageSource()79 ImageSource::~ImageSource()
80 {
81     clear(true);
82 }
83 
clear(bool destroyAllFrames,size_t,SharedBuffer * data,bool allDataReceived)84 void ImageSource::clear(bool destroyAllFrames, size_t, SharedBuffer* data, bool allDataReceived)
85 {
86 #if !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 
imageSourceOptions()110 static 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 
initialized() const124 bool ImageSource::initialized() const
125 {
126     return m_decoder;
127 }
128 
setData(SharedBuffer * data,bool allDataReceived)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 
filenameExtension() const160 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 
isSizeAvailable()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()));
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 
frameSizeAtIndex(size_t index) const186 IntSize 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 
size() const203 IntSize ImageSource::size() const
204 {
205     return frameSizeAtIndex(0);
206 }
207 
getHotSpot(IntPoint & hotSpot) const208 bool 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 
bytesDecodedToDetermineProperties() const230 size_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 
repetitionCount()241 int 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 
frameCount() const265 size_t ImageSource::frameCount() const
266 {
267     return m_decoder ? CGImageSourceGetCount(m_decoder) : 0;
268 }
269 
createFrameAtIndex(size_t index)270 CGImageRef 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 
frameIsCompleteAtIndex(size_t index)290 bool 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 
frameDurationAtIndex(size_t index)310 float 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 
frameHasAlphaAtIndex(size_t)339 bool 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