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) 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 64 void sharedBufferRelease(void* info) 65 { 66 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info); 67 sharedBuffer->deref(); 68 } 69 #endif 70 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 79 ImageSource::~ImageSource() 80 { 81 clear(true); 82 } 83 84 void 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 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 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())); 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 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 203 IntSize ImageSource::size() const 204 { 205 return frameSizeAtIndex(0); 206 } 207 208 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 230 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 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 265 size_t ImageSource::frameCount() const 266 { 267 return m_decoder ? CGImageSourceGetCount(m_decoder) : 0; 268 } 269 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 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 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 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