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 39using namespace std; 40 41namespace WebCore { 42 43const CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32"); 44const 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. 49const CFStringRef WebCoreCGImagePropertyGIFUnclampedDelayTime = CFSTR("UnclampedDelayTime"); 50 51#if !PLATFORM(MAC) 52size_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 65void sharedBufferRelease(void* info) 66{ 67 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info); 68 sharedBuffer->deref(); 69} 70#endif 71 72ImageSource::ImageSource(ImageSource::AlphaOption, ImageSource::GammaAndColorProfileOption) 73 : m_decoder(0) 74{ 75 // FIXME: AlphaOption and GammaAndColorProfileOption are ignored. 76} 77 78ImageSource::~ImageSource() 79{ 80 clear(true); 81} 82 83void 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 99static 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 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(SkipMetadata))); 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 186static ImageOrientation orientationFromProperties(CFDictionaryRef imageProperties) 187{ 188 ASSERT(imageProperties); 189 CFNumberRef orientationProperty = (CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation); 190 if (!orientationProperty) 191 return DefaultImageOrientation; 192 193 int exifValue; 194 CFNumberGetValue(orientationProperty, kCFNumberIntType, &exifValue); 195 return ImageOrientation::fromEXIFValue(exifValue); 196} 197 198IntSize ImageSource::frameSizeAtIndex(size_t index, RespectImageOrientationEnum shouldRespectOrientation) const 199{ 200 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 201 202 if (!properties) 203 return IntSize(); 204 205 int w = 0, h = 0; 206 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth); 207 if (num) 208 CFNumberGetValue(num, kCFNumberIntType, &w); 209 num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight); 210 if (num) 211 CFNumberGetValue(num, kCFNumberIntType, &h); 212 213 if ((shouldRespectOrientation == RespectImageOrientation) && orientationFromProperties(properties.get()).usesWidthAsHeight()) 214 return IntSize(h, w); 215 216 return IntSize(w, h); 217} 218 219ImageOrientation ImageSource::orientationAtIndex(size_t index) const 220{ 221 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 222 if (!properties) 223 return DefaultImageOrientation; 224 225 return orientationFromProperties(properties.get()); 226} 227 228IntSize ImageSource::size(RespectImageOrientationEnum shouldRespectOrientation) const 229{ 230 return frameSizeAtIndex(0, shouldRespectOrientation); 231} 232 233bool ImageSource::getHotSpot(IntPoint& hotSpot) const 234{ 235 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions(SkipMetadata))); 236 if (!properties) 237 return false; 238 239 int x = -1, y = -1; 240 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotX")); 241 if (!num || !CFNumberGetValue(num, kCFNumberIntType, &x)) 242 return false; 243 244 num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotY")); 245 if (!num || !CFNumberGetValue(num, kCFNumberIntType, &y)) 246 return false; 247 248 if (x < 0 || y < 0) 249 return false; 250 251 hotSpot = IntPoint(x, y); 252 return true; 253} 254 255size_t ImageSource::bytesDecodedToDetermineProperties() const 256{ 257 // Measured by tracing malloc/calloc calls on Mac OS 10.6.6, x86_64. 258 // A non-zero value ensures cached images with no decoded frames still enter 259 // the live decoded resources list when the CGImageSource decodes image 260 // properties, allowing the cache to prune the partially decoded image. 261 // This value is likely to be inaccurate on other platforms, but the overall 262 // behavior is unchanged. 263 return 13088; 264} 265 266int ImageSource::repetitionCount() 267{ 268 int result = cAnimationLoopOnce; // No property means loop once. 269 if (!initialized()) 270 return result; 271 272 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyProperties(m_decoder, imageSourceOptions(SkipMetadata))); 273 if (properties) { 274 CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary); 275 if (gifProperties) { 276 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount); 277 if (num) { 278 // A property with value 0 means loop forever. 279 CFNumberGetValue(num, kCFNumberIntType, &result); 280 if (!result) 281 result = cAnimationLoopInfinite; 282 } 283 } else 284 result = cAnimationNone; // Turns out we're not a GIF after all, so we don't animate. 285 } 286 287 return result; 288} 289 290size_t ImageSource::frameCount() const 291{ 292 return m_decoder ? CGImageSourceGetCount(m_decoder) : 0; 293} 294 295CGImageRef ImageSource::createFrameAtIndex(size_t index) 296{ 297 if (!initialized()) 298 return 0; 299 300 RetainPtr<CGImageRef> image = adoptCF(CGImageSourceCreateImageAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 301 CFStringRef imageUTI = CGImageSourceGetType(m_decoder); 302 static const CFStringRef xbmUTI = CFSTR("public.xbitmap-image"); 303 if (!imageUTI || !CFEqual(imageUTI, xbmUTI)) 304 return image.leakRef(); 305 306 // If it is an xbm image, mask out all the white areas to render them transparent. 307 const CGFloat maskingColors[6] = {255, 255, 255, 255, 255, 255}; 308 RetainPtr<CGImageRef> maskedImage = adoptCF(CGImageCreateWithMaskingColors(image.get(), maskingColors)); 309 if (!maskedImage) 310 return image.leakRef(); 311 312 return maskedImage.leakRef(); 313} 314 315bool ImageSource::frameIsCompleteAtIndex(size_t index) 316{ 317 ASSERT(frameCount()); 318 319 // CGImageSourceGetStatusAtIndex claims that all frames of a multi-frame image are incomplete 320 // when we've not yet received the complete data for an image that is using an incremental data 321 // source (<rdar://problem/7679174>). We work around this by special-casing all frames except the 322 // last in an image and treating them as complete if they are present and reported as being 323 // incomplete. We do this on the assumption that loading new data can only modify the existing last 324 // frame or append new frames. The last frame is only treated as being complete if the image source 325 // reports it as such. This ensures that it is truly the last frame of the image rather than just 326 // the last that we currently have data for. 327 328 CGImageSourceStatus frameStatus = CGImageSourceGetStatusAtIndex(m_decoder, index); 329 if (index < frameCount() - 1) 330 return frameStatus >= kCGImageStatusIncomplete; 331 332 return frameStatus == kCGImageStatusComplete; 333} 334 335float ImageSource::frameDurationAtIndex(size_t index) 336{ 337 if (!initialized()) 338 return 0; 339 340 float duration = 0; 341 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 342 if (properties) { 343 CFDictionaryRef typeProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary); 344 if (typeProperties) { 345 if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, WebCoreCGImagePropertyGIFUnclampedDelayTime)) { 346 // Use the unclamped frame delay if it exists. 347 CFNumberGetValue(num, kCFNumberFloatType, &duration); 348 } else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, kCGImagePropertyGIFDelayTime)) { 349 // Fall back to the clamped frame delay if the unclamped frame delay does not exist. 350 CFNumberGetValue(num, kCFNumberFloatType, &duration); 351 } 352 } 353 } 354 355 // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. 356 // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify 357 // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082> 358 // for more information. 359 if (duration < 0.011f) 360 return 0.100f; 361 return duration; 362} 363 364bool ImageSource::frameHasAlphaAtIndex(size_t index) 365{ 366 if (!m_decoder) 367 return false; // FIXME: why doesn't this return true? 368 369 if (!frameIsCompleteAtIndex(index)) 370 return true; 371 372 CFStringRef imageType = CGImageSourceGetType(m_decoder); 373 374 // Return false if there is no image type or the image type is JPEG, because 375 // JPEG does not support alpha transparency. 376 if (!imageType || CFEqual(imageType, CFSTR("public.jpeg"))) 377 return false; 378 379 // FIXME: Could return false for other non-transparent image formats. 380 // FIXME: Could maybe return false for a GIF Frame if we have enough info in the GIF properties dictionary 381 // to determine whether or not a transparent color was defined. 382 return true; 383} 384 385unsigned ImageSource::frameBytesAtIndex(size_t index) const 386{ 387 IntSize frameSize = frameSizeAtIndex(index, RespectImageOrientation); 388 return frameSize.width() * frameSize.height() * 4; 389} 390 391} 392 393#endif // USE(CG) 394