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 39using namespace std; 40 41namespace WebCore { 42 43const 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. 48const CFStringRef WebCoreCGImagePropertyGIFUnclampedDelayTime = CFSTR("UnclampedDelayTime"); 49 50#if !PLATFORM(MAC) 51size_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 64void sharedBufferRelease(void* info) 65{ 66 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info); 67 sharedBuffer->deref(); 68} 69#endif 70 71ImageSource::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 79ImageSource::~ImageSource() 80{ 81 clear(true); 82} 83 84void ImageSource::clear(bool destroyAllFrames, size_t, SharedBuffer* data, bool allDataReceived) 85{ 86#if !defined(BUILDING_ON_TIGER) && !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 110static 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 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())); 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 186IntSize 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 203IntSize ImageSource::size() const 204{ 205 return frameSizeAtIndex(0); 206} 207 208bool 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 230size_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 241int 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 265size_t ImageSource::frameCount() const 266{ 267 return m_decoder ? CGImageSourceGetCount(m_decoder) : 0; 268} 269 270CGImageRef 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 290bool 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 310float 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 339bool 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