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 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 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#if !PLATFORM(IOS) 38#include <ApplicationServices/ApplicationServices.h> 39#else 40#include <CoreGraphics/CGImagePrivate.h> 41#include <CoreGraphics/CoreGraphics.h> 42#include <ImageIO/CGImageSourcePrivate.h> 43#include <ImageIO/ImageIO.h> 44#include <wtf/RetainPtr.h> 45#endif 46 47namespace WebCore { 48 49const CFStringRef WebCoreCGImagePropertyAPNGUnclampedDelayTime = CFSTR("UnclampedDelayTime"); 50const CFStringRef WebCoreCGImagePropertyAPNGDelayTime = CFSTR("DelayTime"); 51const CFStringRef WebCoreCGImagePropertyAPNGLoopCount = CFSTR("LoopCount"); 52 53const CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32"); 54const CFStringRef kCGImageSourceSkipMetadata = CFSTR("kCGImageSourceSkipMetadata"); 55 56#if !PLATFORM(COCOA) 57size_t sharedBufferGetBytesAtPosition(void* info, void* buffer, off_t position, size_t count) 58{ 59 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info); 60 size_t sourceSize = sharedBuffer->size(); 61 if (position >= sourceSize) 62 return 0; 63 64 const char* source = sharedBuffer->data() + position; 65 size_t amount = std::min<size_t>(count, sourceSize - position); 66 memcpy(buffer, source, amount); 67 return amount; 68} 69 70void sharedBufferRelease(void* info) 71{ 72 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info); 73 sharedBuffer->deref(); 74} 75#endif 76 77ImageSource::ImageSource(ImageSource::AlphaOption, ImageSource::GammaAndColorProfileOption) 78 : m_decoder(0) 79#if PLATFORM(IOS) 80 , m_baseSubsampling(0) 81 , m_isProgressive(false) 82#endif 83{ 84 // FIXME: AlphaOption and GammaAndColorProfileOption are ignored. 85} 86 87ImageSource::~ImageSource() 88{ 89 clear(true); 90} 91 92void ImageSource::clear(bool destroyAllFrames, size_t, SharedBuffer* data, bool allDataReceived) 93{ 94 // Recent versions of ImageIO discard previously decoded image frames if the client 95 // application no longer holds references to them, so there's no need to throw away 96 // the decoder unless we're explicitly asked to destroy all of the frames. 97 if (!destroyAllFrames) 98 return; 99 100 if (m_decoder) { 101 CFRelease(m_decoder); 102 m_decoder = 0; 103 } 104 if (data) 105 setData(data, allDataReceived); 106} 107 108#if !PLATFORM(IOS) 109static CFDictionaryRef imageSourceOptions(ImageSource::ShouldSkipMetadata skipMetaData) 110{ 111 static CFDictionaryRef options; 112 113 if (!options) { 114 const unsigned numOptions = 3; 115 const CFBooleanRef imageSourceSkipMetadata = (skipMetaData == ImageSource::SkipMetadata) ? kCFBooleanTrue : kCFBooleanFalse; 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#else 124CFDictionaryRef ImageSource::imageSourceOptions(ShouldSkipMetadata skipMetaData, int requestedSubsampling) const 125{ 126 static CFDictionaryRef options[4] = {nullptr, nullptr, nullptr, nullptr}; 127 int subsampling = std::min(3, m_isProgressive || requestedSubsampling < 0 ? 0 : (requestedSubsampling + m_baseSubsampling)); 128 129 if (!options[subsampling]) { 130 int subsampleInt = 1 << subsampling; // [0..3] => [1, 2, 4, 8] 131 RetainPtr<CFNumberRef> subsampleNumber = adoptCF(CFNumberCreate(nullptr, kCFNumberIntType, &subsampleInt)); 132 const CFIndex numOptions = 4; 133 const CFBooleanRef imageSourceSkipMetaData = (skipMetaData == ImageSource::SkipMetadata) ? kCFBooleanTrue : kCFBooleanFalse; 134 const void* keys[numOptions] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32, kCGImageSourceSubsampleFactor, kCGImageSourceSkipMetadata }; 135 const void* values[numOptions] = { kCFBooleanTrue, kCFBooleanTrue, subsampleNumber.get(), imageSourceSkipMetaData }; 136 options[subsampling] = CFDictionaryCreate(nullptr, keys, values, numOptions, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 137 } 138 return options[subsampling]; 139} 140#endif 141 142bool ImageSource::initialized() const 143{ 144 return m_decoder; 145} 146 147void ImageSource::setData(SharedBuffer* data, bool allDataReceived) 148{ 149#if PLATFORM(COCOA) 150 if (!m_decoder) 151 m_decoder = CGImageSourceCreateIncremental(0); 152 // On Mac the NSData inside the SharedBuffer can be secretly appended to without the SharedBuffer's knowledge. We use SharedBuffer's ability 153 // to wrap itself inside CFData to get around this, ensuring that ImageIO is really looking at the SharedBuffer. 154 CGImageSourceUpdateData(m_decoder, data->createCFData().get(), allDataReceived); 155#else 156 if (!m_decoder) { 157 m_decoder = CGImageSourceCreateIncremental(0); 158 } else if (allDataReceived) { 159#if !PLATFORM(WIN) 160 // 10.6 bug workaround: image sources with final=false fail to draw into PDF contexts, so re-create image source 161 // when data is complete. <rdar://problem/7874035> (<http://openradar.appspot.com/7874035>) 162 CFRelease(m_decoder); 163 m_decoder = CGImageSourceCreateIncremental(0); 164#endif 165 } 166 // Create a CGDataProvider to wrap the SharedBuffer. 167 data->ref(); 168 // We use the GetBytesAtPosition callback rather than the GetBytePointer one because SharedBuffer 169 // does not provide a way to lock down the byte pointer and guarantee that it won't move, which 170 // is a requirement for using the GetBytePointer callback. 171 CGDataProviderDirectCallbacks providerCallbacks = { 0, 0, 0, sharedBufferGetBytesAtPosition, sharedBufferRelease }; 172 RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateDirect(data, data->size(), &providerCallbacks)); 173 CGImageSourceUpdateDataProvider(m_decoder, dataProvider.get(), allDataReceived); 174#endif 175} 176 177String ImageSource::filenameExtension() const 178{ 179 if (!m_decoder) 180 return String(); 181 CFStringRef imageSourceType = CGImageSourceGetType(m_decoder); 182 return WebCore::preferredExtensionForImageSourceType(imageSourceType); 183} 184 185bool ImageSource::isSizeAvailable() 186{ 187 bool result = false; 188 CGImageSourceStatus imageSourceStatus = CGImageSourceGetStatus(m_decoder); 189 190 // Ragnaros yells: TOO SOON! You have awakened me TOO SOON, Executus! 191 if (imageSourceStatus >= kCGImageStatusIncomplete) { 192 RetainPtr<CFDictionaryRef> image0Properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions(SkipMetadata))); 193 if (image0Properties) { 194 CFNumberRef widthNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelWidth); 195 CFNumberRef heightNumber = (CFNumberRef)CFDictionaryGetValue(image0Properties.get(), kCGImagePropertyPixelHeight); 196 result = widthNumber && heightNumber; 197 } 198 } 199 200 return result; 201} 202 203static ImageOrientation orientationFromProperties(CFDictionaryRef imageProperties) 204{ 205 ASSERT(imageProperties); 206 CFNumberRef orientationProperty = (CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation); 207 if (!orientationProperty) 208 return DefaultImageOrientation; 209 210 int exifValue; 211 CFNumberGetValue(orientationProperty, kCFNumberIntType, &exifValue); 212 return ImageOrientation::fromEXIFValue(exifValue); 213} 214 215IntSize ImageSource::frameSizeAtIndex(size_t index, ImageOrientationDescription description) const 216{ 217 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 218 219 if (!properties) 220 return IntSize(); 221 222 int w = 0, h = 0; 223 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth); 224 if (num) 225 CFNumberGetValue(num, kCFNumberIntType, &w); 226 num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight); 227 if (num) 228 CFNumberGetValue(num, kCFNumberIntType, &h); 229 230#if PLATFORM(IOS) 231 if (!m_isProgressive) { 232 CFDictionaryRef jfifProperties = static_cast<CFDictionaryRef>(CFDictionaryGetValue(properties.get(), kCGImagePropertyJFIFDictionary)); 233 if (jfifProperties) { 234 CFBooleanRef isProgCFBool = static_cast<CFBooleanRef>(CFDictionaryGetValue(jfifProperties, kCGImagePropertyJFIFIsProgressive)); 235 if (isProgCFBool) 236 m_isProgressive = CFBooleanGetValue(isProgCFBool); 237 // Workaround for <rdar://problem/5184655> - Hang rendering very large progressive JPEG. Decoding progressive 238 // images hangs for a very long time right now. Until this is fixed, don't sub-sample progressive images. This 239 // will cause them to fail our large image check and they won't be decoded. 240 // FIXME: Remove once underlying issue is fixed (<rdar://problem/5191418>) 241 } 242 } 243 244 if ((m_baseSubsampling == 0) && !m_isProgressive) { 245 IntSize subsampledSize(w, h); 246 const int cMaximumImageSizeBeforeSubsampling = 5 * 1024 * 1024; 247 while ((m_baseSubsampling < 3) && subsampledSize.width() * subsampledSize.height() > cMaximumImageSizeBeforeSubsampling) { 248 // We know the size, but the actual image is very large and should be sub-sampled. 249 // Increase the base subsampling and ask for the size again. If the image can be subsampled, the size will be 250 // greatly reduced. 4x sub-sampling will make us support up to 320MP (5MP * 4^3) images, which should be plenty. 251 // There's no callback from ImageIO when the size is available, so we do the check when we happen 252 // to check the size and its non - zero. 253 // Note: Some clients of this class don't call isSizeAvailable() so we can't rely on that. 254 ++m_baseSubsampling; 255 subsampledSize = frameSizeAtIndex(index, description.respectImageOrientation()); 256 } 257 w = subsampledSize.width(); 258 h = subsampledSize.height(); 259 } 260#endif 261 262 if ((description.respectImageOrientation() == RespectImageOrientation) && orientationFromProperties(properties.get()).usesWidthAsHeight()) 263 return IntSize(h, w); 264 265 return IntSize(w, h); 266} 267 268ImageOrientation ImageSource::orientationAtIndex(size_t index) const 269{ 270 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 271 if (!properties) 272 return DefaultImageOrientation; 273 274 return orientationFromProperties(properties.get()); 275} 276 277#if PLATFORM(IOS) 278IntSize ImageSource::originalSize(RespectImageOrientationEnum shouldRespectOrientation) const 279{ 280 frameSizeAtIndex(0, shouldRespectOrientation); 281 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions(SkipMetadata, -1))); 282 283 if (!properties) 284 return IntSize(); 285 286 int width = 0; 287 int height = 0; 288 CFNumberRef number = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth); 289 if (number) 290 CFNumberGetValue(number, kCFNumberIntType, &width); 291 number = static_cast<CFNumberRef>(CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight)); 292 if (number) 293 CFNumberGetValue(number, kCFNumberIntType, &height); 294 295 if ((shouldRespectOrientation == RespectImageOrientation) && orientationFromProperties(properties.get()).usesWidthAsHeight()) 296 return IntSize(height, width); 297 298 return IntSize(width, height); 299} 300#endif 301 302IntSize ImageSource::size(ImageOrientationDescription description) const 303{ 304 return frameSizeAtIndex(0, description); 305} 306 307bool ImageSource::getHotSpot(IntPoint& hotSpot) const 308{ 309 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, 0, imageSourceOptions(SkipMetadata))); 310 if (!properties) 311 return false; 312 313 int x = -1, y = -1; 314 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotX")); 315 if (!num || !CFNumberGetValue(num, kCFNumberIntType, &x)) 316 return false; 317 318 num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotY")); 319 if (!num || !CFNumberGetValue(num, kCFNumberIntType, &y)) 320 return false; 321 322 if (x < 0 || y < 0) 323 return false; 324 325 hotSpot = IntPoint(x, y); 326 return true; 327} 328 329size_t ImageSource::bytesDecodedToDetermineProperties() const 330{ 331 // Measured by tracing malloc/calloc calls on Mac OS 10.6.6, x86_64. 332 // A non-zero value ensures cached images with no decoded frames still enter 333 // the live decoded resources list when the CGImageSource decodes image 334 // properties, allowing the cache to prune the partially decoded image. 335 // This value is likely to be inaccurate on other platforms, but the overall 336 // behavior is unchanged. 337 return 13088; 338} 339 340int ImageSource::repetitionCount() 341{ 342 if (!initialized()) 343 return cAnimationLoopOnce; 344 345 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyProperties(m_decoder, imageSourceOptions(SkipMetadata))); 346 if (!properties) 347 return cAnimationLoopOnce; 348 349 CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary); 350 if (gifProperties) { 351 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount); 352 353 // No property means loop once. 354 if (!num) 355 return cAnimationLoopOnce; 356 357 int loopCount; 358 CFNumberGetValue(num, kCFNumberIntType, &loopCount); 359 360 // A property with value 0 means loop forever. 361 return loopCount ? loopCount : cAnimationLoopInfinite; 362 } 363 364 CFDictionaryRef pngProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPNGDictionary); 365 if (pngProperties) { 366 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(pngProperties, WebCoreCGImagePropertyAPNGLoopCount); 367 if (!num) 368 return cAnimationLoopOnce; 369 370 int loopCount; 371 CFNumberGetValue(num, kCFNumberIntType, &loopCount); 372 return loopCount ? loopCount : cAnimationLoopInfinite; 373 } 374 375 // Turns out we're not an animated image after all, so we don't animate. 376 return cAnimationNone; 377} 378 379size_t ImageSource::frameCount() const 380{ 381 return m_decoder ? CGImageSourceGetCount(m_decoder) : 0; 382} 383 384CGImageRef ImageSource::createFrameAtIndex(size_t index, float* scale) 385{ 386 UNUSED_PARAM(scale); 387 388 if (!initialized()) 389 return 0; 390 391#if !PLATFORM(IOS) 392 UNUSED_PARAM(scale); 393 RetainPtr<CGImageRef> image = adoptCF(CGImageSourceCreateImageAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 394#else 395 // Subsampling can be 1, 2 or 3, which means quarter-, sixteenth- and sixty-fourth-size, respectively. 396 // A zero or negative value means no subsampling. 397 int subsampling = scale ? static_cast<int>(log2f(1.0f / std::max(0.1f, std::min(1.0f, *scale)))) : -1; 398 RetainPtr<CGImageRef> image = adoptCF(CGImageSourceCreateImageAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata, subsampling))); 399 400 // <rdar://problem/7371198> - CoreGraphics changed the default caching behaviour in iOS 4.0 to kCGImageCachingTransient 401 // which caused a performance regression for us since the images had to be resampled/recreated every time we called 402 // CGContextDrawImage. We now tell CG to cache the drawn images. See also <rdar://problem/14366755> - 403 // CoreGraphics needs to un-deprecate kCGImageCachingTemporary since it's still not the default. 404#if COMPILER(CLANG) 405#pragma clang diagnostic push 406#pragma clang diagnostic ignored "-Wdeprecated-declarations" 407#endif 408 CGImageSetCachingFlags(image.get(), kCGImageCachingTemporary); 409#if COMPILER(CLANG) 410#pragma clang diagnostic pop 411#endif 412 if (scale) { 413 if (subsampling > 0) 414 *scale = static_cast<float>(CGImageGetWidth(image.get())) / size(DoNotRespectImageOrientation).width(); 415 else { 416 ASSERT(static_cast<int>(CGImageGetWidth(image.get())) == size(DoNotRespectImageOrientation).width()); 417 *scale = 1; 418 } 419 } 420#endif // !PLATFORM(IOS) 421 CFStringRef imageUTI = CGImageSourceGetType(m_decoder); 422 static const CFStringRef xbmUTI = CFSTR("public.xbitmap-image"); 423 if (!imageUTI || !CFEqual(imageUTI, xbmUTI)) 424 return image.leakRef(); 425 426 // If it is an xbm image, mask out all the white areas to render them transparent. 427 const CGFloat maskingColors[6] = {255, 255, 255, 255, 255, 255}; 428 RetainPtr<CGImageRef> maskedImage = adoptCF(CGImageCreateWithMaskingColors(image.get(), maskingColors)); 429 if (!maskedImage) 430 return image.leakRef(); 431 432 return maskedImage.leakRef(); 433} 434 435bool ImageSource::frameIsCompleteAtIndex(size_t index) 436{ 437 ASSERT(frameCount()); 438 439 // CGImageSourceGetStatusAtIndex claims that all frames of a multi-frame image are incomplete 440 // when we've not yet received the complete data for an image that is using an incremental data 441 // source (<rdar://problem/7679174>). We work around this by special-casing all frames except the 442 // last in an image and treating them as complete if they are present and reported as being 443 // incomplete. We do this on the assumption that loading new data can only modify the existing last 444 // frame or append new frames. The last frame is only treated as being complete if the image source 445 // reports it as such. This ensures that it is truly the last frame of the image rather than just 446 // the last that we currently have data for. 447 448 CGImageSourceStatus frameStatus = CGImageSourceGetStatusAtIndex(m_decoder, index); 449 if (index < frameCount() - 1) 450 return frameStatus >= kCGImageStatusIncomplete; 451 452 return frameStatus == kCGImageStatusComplete; 453} 454 455float ImageSource::frameDurationAtIndex(size_t index) 456{ 457 if (!initialized()) 458 return 0; 459 460 float duration = 0; 461 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_decoder, index, imageSourceOptions(SkipMetadata))); 462 if (properties) { 463 CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary); 464 if (gifProperties) { 465 if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime)) { 466 // Use the unclamped frame delay if it exists. 467 CFNumberGetValue(num, kCFNumberFloatType, &duration); 468 } else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime)) { 469 // Fall back to the clamped frame delay if the unclamped frame delay does not exist. 470 CFNumberGetValue(num, kCFNumberFloatType, &duration); 471 } 472 } 473 474 CFDictionaryRef pngProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPNGDictionary); 475 if (pngProperties) { 476 if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(pngProperties, WebCoreCGImagePropertyAPNGUnclampedDelayTime)) 477 CFNumberGetValue(num, kCFNumberFloatType, &duration); 478 else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(pngProperties, WebCoreCGImagePropertyAPNGDelayTime)) 479 CFNumberGetValue(num, kCFNumberFloatType, &duration); 480 } 481 } 482 483 // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. 484 // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify 485 // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082> 486 // for more information. 487 if (duration < 0.011f) 488 return 0.100f; 489 return duration; 490} 491 492bool ImageSource::frameHasAlphaAtIndex(size_t index) 493{ 494 if (!m_decoder) 495 return false; // FIXME: why doesn't this return true? 496 497 if (!frameIsCompleteAtIndex(index)) 498 return true; 499 500 CFStringRef imageType = CGImageSourceGetType(m_decoder); 501 502 // Return false if there is no image type or the image type is JPEG, because 503 // JPEG does not support alpha transparency. 504 if (!imageType || CFEqual(imageType, CFSTR("public.jpeg"))) 505 return false; 506 507 // FIXME: Could return false for other non-transparent image formats. 508 // FIXME: Could maybe return false for a GIF Frame if we have enough info in the GIF properties dictionary 509 // to determine whether or not a transparent color was defined. 510 return true; 511} 512 513unsigned ImageSource::frameBytesAtIndex(size_t index) const 514{ 515 IntSize frameSize = frameSizeAtIndex(index, ImageOrientationDescription(RespectImageOrientation)); 516 return frameSize.width() * frameSize.height() * 4; 517} 518 519} 520 521#endif // USE(CG) 522