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