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 PLATFORM(CG) 30 #include "ImageSourceCG.h" 31 32 #include "IntSize.h" 33 #include "MIMETypeRegistry.h" 34 #include "SharedBuffer.h" 35 #include <ApplicationServices/ApplicationServices.h> 36 #include <wtf/UnusedParam.h> 37 38 using namespace std; 39 40 namespace WebCore { 41 42 static const CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32"); 43 44 #if !PLATFORM(MAC) 45 size_t sharedBufferGetBytesAtPosition(void* info, void* buffer, off_t position, size_t count) 46 { 47 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info); 48 size_t sourceSize = sharedBuffer->size(); 49 if (position >= sourceSize) 50 return 0; 51 52 const char* source = sharedBuffer->data() + position; 53 size_t amount = min<size_t>(count, sourceSize - position); 54 memcpy(buffer, source, amount); 55 return amount; 56 } 57 58 void sharedBufferRelease(void* info) 59 { 60 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info); 61 sharedBuffer->deref(); 62 } 63 #endif 64 65 ImageSource::ImageSource() 66 : m_decoder(0) 67 { 68 } 69 70 ImageSource::~ImageSource() 71 { 72 clear(true); 73 } 74 75 void ImageSource::clear(bool destroyAllFrames, size_t, SharedBuffer* data, bool allDataReceived) 76 { 77 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 78 // Recent versions of ImageIO discard previously decoded image frames if the client 79 // application no longer holds references to them, so there's no need to throw away 80 // the decoder unless we're explicitly asked to destroy all of the frames. 81 82 if (!destroyAllFrames) 83 return; 84 #else 85 // Older versions of ImageIO hold references to previously decoded image frames. 86 // There is no API to selectively release some of the frames it is holding, and 87 // if we don't release the frames we use too much memory on large images. 88 // Destroying the decoder is the only way to release previous frames. 89 90 UNUSED_PARAM(destroyAllFrames); 91 #endif 92 93 if (m_decoder) { 94 CFRelease(m_decoder); 95 m_decoder = 0; 96 } 97 if (data) 98 setData(data, allDataReceived); 99 } 100 101 static CFDictionaryRef imageSourceOptions() 102 { 103 static CFDictionaryRef options; 104 105 if (!options) { 106 const unsigned numOptions = 2; 107 const void* keys[numOptions] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32 }; 108 const void* values[numOptions] = { kCFBooleanTrue, kCFBooleanTrue }; 109 options = CFDictionaryCreate(NULL, keys, values, numOptions, 110 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 111 } 112 return options; 113 } 114 115 bool ImageSource::initialized() const 116 { 117 return m_decoder; 118 } 119 120 void ImageSource::setData(SharedBuffer* data, bool allDataReceived) 121 { 122 if (!m_decoder) 123 m_decoder = CGImageSourceCreateIncremental(NULL); 124 #if PLATFORM(MAC) 125 // On Mac the NSData inside the SharedBuffer can be secretly appended to without the SharedBuffer's knowledge. We use SharedBuffer's ability 126 // to wrap itself inside CFData to get around this, ensuring that ImageIO is really looking at the SharedBuffer. 127 RetainPtr<CFDataRef> cfData(AdoptCF, data->createCFData()); 128 CGImageSourceUpdateData(m_decoder, cfData.get(), allDataReceived); 129 #else 130 // Create a CGDataProvider to wrap the SharedBuffer. 131 data->ref(); 132 // We use the GetBytesAtPosition callback rather than the GetBytePointer one because SharedBuffer 133 // does not provide a way to lock down the byte pointer and guarantee that it won't move, which 134 // is a requirement for using the GetBytePointer callback. 135 CGDataProviderDirectCallbacks providerCallbacks = { 0, 0, 0, sharedBufferGetBytesAtPosition, sharedBufferRelease }; 136 RetainPtr<CGDataProviderRef> dataProvider(AdoptCF, CGDataProviderCreateDirect(data, data->size(), &providerCallbacks)); 137 CGImageSourceUpdateDataProvider(m_decoder, dataProvider.get(), allDataReceived); 138 #endif 139 } 140 141 String ImageSource::filenameExtension() const 142 { 143 if (!m_decoder) 144 return String(); 145 CFStringRef imageSourceType = CGImageSourceGetType(m_decoder); 146 return WebCore::preferredExtensionForImageSourceType(imageSourceType); 147 } 148 149 bool ImageSource::isSizeAvailable() 150 { 151 bool result = false; 152 CGImageSourceStatus imageSourceStatus = CGImageSourceGetStatus(m_decoder); 153 154 // Ragnaros yells: TOO SOON! You have awakened me TOO SOON, Executus! 155 if (imageSourceStatus >= kCGImageStatusIncomplete) { 156 RetainPtr<CFDictionaryRef> image0Properties(AdoptCF, CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions())); 157 if (image0Properties) { 158 CFNumberRef widthNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelWidth); 159 CFNumberRef heightNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelHeight); 160 result = widthNumber && heightNumber; 161 } 162 } 163 164 return result; 165 } 166 167 IntSize ImageSource::frameSizeAtIndex(size_t index) const 168 { 169 IntSize result; 170 RetainPtr<CFDictionaryRef> properties(AdoptCF, CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions())); 171 if (properties) { 172 int w = 0, h = 0; 173 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth); 174 if (num) 175 CFNumberGetValue(num, kCFNumberIntType, &w); 176 num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight); 177 if (num) 178 CFNumberGetValue(num, kCFNumberIntType, &h); 179 result = IntSize(w, h); 180 } 181 return result; 182 } 183 184 IntSize ImageSource::size() const 185 { 186 return frameSizeAtIndex(0); 187 } 188 189 int ImageSource::repetitionCount() 190 { 191 int result = cAnimationLoopOnce; // No property means loop once. 192 if (!initialized()) 193 return result; 194 195 // A property with value 0 means loop forever. 196 RetainPtr<CFDictionaryRef> properties(AdoptCF, CGImageSourceCopyProperties(m_decoder, imageSourceOptions())); 197 if (properties) { 198 CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary); 199 if (gifProperties) { 200 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount); 201 if (num) 202 CFNumberGetValue(num, kCFNumberIntType, &result); 203 } else 204 result = cAnimationNone; // Turns out we're not a GIF after all, so we don't animate. 205 } 206 207 return result; 208 } 209 210 size_t ImageSource::frameCount() const 211 { 212 return m_decoder ? CGImageSourceGetCount(m_decoder) : 0; 213 } 214 215 CGImageRef ImageSource::createFrameAtIndex(size_t index) 216 { 217 if (!initialized()) 218 return 0; 219 220 RetainPtr<CGImageRef> image(AdoptCF, CGImageSourceCreateImageAtIndex(m_decoder, index, imageSourceOptions())); 221 CFStringRef imageUTI = CGImageSourceGetType(m_decoder); 222 static const CFStringRef xbmUTI = CFSTR("public.xbitmap-image"); 223 if (!imageUTI || !CFEqual(imageUTI, xbmUTI)) 224 return image.releaseRef(); 225 226 // If it is an xbm image, mask out all the white areas to render them transparent. 227 const CGFloat maskingColors[6] = {255, 255, 255, 255, 255, 255}; 228 RetainPtr<CGImageRef> maskedImage(AdoptCF, CGImageCreateWithMaskingColors(image.get(), maskingColors)); 229 if (!maskedImage) 230 return image.releaseRef(); 231 232 return maskedImage.releaseRef(); 233 } 234 235 bool ImageSource::frameIsCompleteAtIndex(size_t index) 236 { 237 return CGImageSourceGetStatusAtIndex(m_decoder, index) == kCGImageStatusComplete; 238 } 239 240 float ImageSource::frameDurationAtIndex(size_t index) 241 { 242 if (!initialized()) 243 return 0; 244 245 float duration = 0; 246 RetainPtr<CFDictionaryRef> properties(AdoptCF, CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions())); 247 if (properties) { 248 CFDictionaryRef typeProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary); 249 if (typeProperties) { 250 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(typeProperties, kCGImagePropertyGIFDelayTime); 251 if (num) 252 CFNumberGetValue(num, kCFNumberFloatType, &duration); 253 } 254 } 255 256 // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. 257 // We follow WinIE's behavior and use a duration of 100 ms for any frames that specify 258 // a duration of <= 50 ms. See <http://bugs.webkit.org/show_bug.cgi?id=14413> or Radar 4051389 for more. 259 if (duration < 0.051f) 260 return 0.100f; 261 return duration; 262 } 263 264 bool ImageSource::frameHasAlphaAtIndex(size_t) 265 { 266 if (!m_decoder) 267 return false; 268 269 CFStringRef imageType = CGImageSourceGetType(m_decoder); 270 271 // Return false if there is no image type or the image type is JPEG, because 272 // JPEG does not support alpha transparency. 273 if (!imageType || CFEqual(imageType, CFSTR("public.jpeg"))) 274 return false; 275 276 // FIXME: Could return false for other non-transparent image formats. 277 // FIXME: Could maybe return false for a GIF Frame if we have enough info in the GIF properties dictionary 278 // to determine whether or not a transparent color was defined. 279 return true; 280 } 281 282 } 283 284 #endif // PLATFORM(CG) 285