Home | History | Annotate | Download | only in cg
      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