Home | History | Annotate | Download | only in cg
      1 /*
      2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann (at) kde.org>
      3  * Copyright (C) 2008 Apple Inc. All rights reserved.
      4  * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  *
     15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include "config.h"
     29 #include "ImageBuffer.h"
     30 
     31 #include "Base64.h"
     32 #include "BitmapImage.h"
     33 #include "GraphicsContext.h"
     34 #include "GraphicsContextCG.h"
     35 #include "ImageData.h"
     36 #include "MIMETypeRegistry.h"
     37 #include <ApplicationServices/ApplicationServices.h>
     38 #include <wtf/Assertions.h>
     39 #include <wtf/text/StringConcatenate.h>
     40 #include <wtf/OwnArrayPtr.h>
     41 #include <wtf/RetainPtr.h>
     42 #include <wtf/Threading.h>
     43 #include <math.h>
     44 
     45 #if PLATFORM(MAC) || PLATFORM(CHROMIUM)
     46 #include "WebCoreSystemInterface.h"
     47 #endif
     48 
     49 using namespace std;
     50 
     51 namespace WebCore {
     52 
     53 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
     54 static const int maxIOSurfaceDimension = 4096;
     55 static const int minIOSurfaceArea = 50 * 100;
     56 
     57 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
     58 {
     59     unsigned pixelFormat = 'BGRA';
     60     unsigned bytesPerElement = 4;
     61     int width = size.width();
     62     int height = size.height();
     63 
     64     unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
     65     if (!bytesPerRow)
     66         return 0;
     67 
     68     unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
     69     if (!allocSize)
     70         return 0;
     71 
     72     const void *keys[6];
     73     const void *values[6];
     74     keys[0] = kIOSurfaceWidth;
     75     values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
     76     keys[1] = kIOSurfaceHeight;
     77     values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
     78     keys[2] = kIOSurfacePixelFormat;
     79     values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
     80     keys[3] = kIOSurfaceBytesPerElement;
     81     values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
     82     keys[4] = kIOSurfaceBytesPerRow;
     83     values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
     84     keys[5] = kIOSurfaceAllocSize;
     85     values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
     86 
     87     RetainPtr<CFDictionaryRef> dict(AdoptCF, CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
     88     for (unsigned i = 0; i < 6; i++)
     89         CFRelease(values[i]);
     90 
     91     return RetainPtr<IOSurfaceRef>(AdoptCF, IOSurfaceCreate(dict.get()));
     92 }
     93 #endif
     94 
     95 static void releaseImageData(void*, const void* data, size_t)
     96 {
     97     fastFree(const_cast<void*>(data));
     98 }
     99 
    100 ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
    101     : m_data(size)
    102     , m_size(size)
    103     , m_accelerateRendering(renderingMode == Accelerated)
    104 {
    105     success = false;  // Make early return mean failure.
    106     if (size.width() < 0 || size.height() < 0)
    107         return;
    108 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
    109     if (size.width() >= maxIOSurfaceDimension || size.height() >= maxIOSurfaceDimension || size.width() * size.height() < minIOSurfaceArea)
    110         m_accelerateRendering = false;
    111 #else
    112     ASSERT(renderingMode == Unaccelerated);
    113 #endif
    114 
    115     unsigned bytesPerRow = size.width();
    116     if (bytesPerRow > 0x3FFFFFFF) // Protect against overflow
    117         return;
    118     bytesPerRow *= 4;
    119     m_data.m_bytesPerRow = bytesPerRow;
    120     size_t dataSize = size.height() * bytesPerRow;
    121 
    122     switch (imageColorSpace) {
    123     case ColorSpaceDeviceRGB:
    124         m_data.m_colorSpace = deviceRGBColorSpaceRef();
    125         break;
    126     case ColorSpaceSRGB:
    127         m_data.m_colorSpace = sRGBColorSpaceRef();
    128         break;
    129     case ColorSpaceLinearRGB:
    130         m_data.m_colorSpace = linearRGBColorSpaceRef();
    131         break;
    132     }
    133 
    134     RetainPtr<CGContextRef> cgContext;
    135     if (!m_accelerateRendering) {
    136         if (!tryFastCalloc(size.height(), bytesPerRow).getValue(m_data.m_data))
    137             return;
    138         ASSERT(!(reinterpret_cast<size_t>(m_data.m_data) & 2));
    139 
    140         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
    141         cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, size.width(), size.height(), 8, bytesPerRow, m_data.m_colorSpace, m_data.m_bitmapInfo));
    142         // Create a live image that wraps the data.
    143         m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, dataSize, releaseImageData));
    144     } else {
    145 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
    146         m_data.m_surface = createIOSurface(size);
    147         cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), size.width(), size.height(), m_data.m_colorSpace));
    148 #else
    149         m_accelerateRendering = false; // Force to false on older platforms
    150 #endif
    151     }
    152 
    153     if (!cgContext)
    154         return;
    155 
    156     m_context.set(new GraphicsContext(cgContext.get()));
    157     m_context->scale(FloatSize(1, -1));
    158     m_context->translate(0, -size.height());
    159     success = true;
    160 }
    161 
    162 ImageBuffer::~ImageBuffer()
    163 {
    164 }
    165 
    166 size_t ImageBuffer::dataSize() const
    167 {
    168     return m_size.height() * m_data.m_bytesPerRow;
    169 }
    170 
    171 GraphicsContext* ImageBuffer::context() const
    172 {
    173     return m_context.get();
    174 }
    175 
    176 bool ImageBuffer::drawsUsingCopy() const
    177 {
    178     return false;
    179 }
    180 
    181 PassRefPtr<Image> ImageBuffer::copyImage() const
    182 {
    183     // BitmapImage will release the passed in CGImage on destruction
    184     CGImageRef ctxImage = 0;
    185     if (!m_accelerateRendering)
    186         ctxImage = CGBitmapContextCreateImage(context()->platformContext());
    187 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
    188     else
    189         ctxImage = wkIOSurfaceContextCreateImage(context()->platformContext());
    190 #endif
    191     return BitmapImage::create(ctxImage);
    192 }
    193 
    194 static CGImageRef cgImage(const IntSize& size, const ImageBufferData& data)
    195 {
    196     return CGImageCreate(size.width(), size.height(), 8, 32, data.m_bytesPerRow,
    197                          data.m_colorSpace, data.m_bitmapInfo, data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
    198 }
    199 
    200 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
    201                        CompositeOperator op, bool useLowQualityScale)
    202 {
    203     if (!m_accelerateRendering) {
    204         if (destContext == context()) {
    205             // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
    206             RefPtr<Image> copy = copyImage();
    207             destContext->drawImage(copy.get(), ColorSpaceDeviceRGB, destRect, srcRect, op, useLowQualityScale);
    208         } else {
    209             RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
    210             destContext->drawImage(imageForRendering.get(), styleColorSpace, destRect, srcRect, op, useLowQualityScale);
    211         }
    212     } else {
    213         RefPtr<Image> copy = copyImage();
    214         ColorSpace colorSpace = (destContext == context()) ? ColorSpaceDeviceRGB : styleColorSpace;
    215         destContext->drawImage(copy.get(), colorSpace, destRect, srcRect, op, useLowQualityScale);
    216     }
    217 }
    218 
    219 void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform,
    220                               const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
    221 {
    222     if (!m_accelerateRendering) {
    223         if (destContext == context()) {
    224             // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
    225             RefPtr<Image> copy = copyImage();
    226             copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
    227         } else {
    228             RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
    229             imageForRendering->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
    230         }
    231     } else {
    232         RefPtr<Image> copy = copyImage();
    233         copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
    234     }
    235 }
    236 
    237 void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
    238 {
    239     CGContextRef platformContextToClip = contextToClip->platformContext();
    240     RetainPtr<CGImageRef> image;
    241     if (!m_accelerateRendering)
    242         image.adoptCF(cgImage(m_size, m_data));
    243 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
    244     else
    245         image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
    246 #endif
    247     CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + rect.height());
    248     CGContextScaleCTM(platformContextToClip, 1, -1);
    249     CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), rect.size()), image.get());
    250     CGContextScaleCTM(platformContextToClip, 1, -1);
    251     CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
    252 }
    253 
    254 PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
    255 {
    256     if (m_accelerateRendering)
    257         CGContextFlush(context()->platformContext());
    258     return m_data.getData(rect, m_size, m_accelerateRendering, true);
    259 }
    260 
    261 PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
    262 {
    263     if (m_accelerateRendering)
    264         CGContextFlush(context()->platformContext());
    265     return m_data.getData(rect, m_size, m_accelerateRendering, false);
    266 }
    267 
    268 void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
    269 {
    270     if (m_accelerateRendering)
    271         CGContextFlush(context()->platformContext());
    272     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, true);
    273 }
    274 
    275 void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
    276 {
    277     if (m_accelerateRendering)
    278         CGContextFlush(context()->platformContext());
    279     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, false);
    280 }
    281 
    282 static inline CFStringRef jpegUTI()
    283 {
    284 #if PLATFORM(WIN)
    285     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
    286 #endif
    287     return kUTTypeJPEG;
    288 }
    289 
    290 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
    291 {
    292 #if PLATFORM(MAC)
    293     RetainPtr<CFStringRef> mimeTypeCFString(AdoptCF, mimeType.createCFString());
    294     return RetainPtr<CFStringRef>(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCFString.get(), 0));
    295 #else
    296     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
    297 
    298     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
    299     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
    300     static const CFStringRef kUTTypePNG = CFSTR("public.png");
    301     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
    302 
    303     if (equalIgnoringCase(mimeType, "image/png"))
    304         return kUTTypePNG;
    305     if (equalIgnoringCase(mimeType, "image/jpeg"))
    306         return jpegUTI();
    307     if (equalIgnoringCase(mimeType, "image/gif"))
    308         return kUTTypeGIF;
    309 
    310     ASSERT_NOT_REACHED();
    311     return kUTTypePNG;
    312 #endif
    313 }
    314 
    315 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
    316 {
    317     RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
    318     if (!data)
    319         return "data:,";
    320 
    321     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
    322     ASSERT(uti);
    323 
    324     RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
    325     if (!destination)
    326         return "data:,";
    327 
    328     RetainPtr<CFDictionaryRef> imageProperties = 0;
    329     if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
    330         // Apply the compression quality to the image destination.
    331         RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
    332         const void* key = kCGImageDestinationLossyCompressionQuality;
    333         const void* value = compressionQuality.get();
    334         imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
    335     }
    336 
    337     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
    338     CGImageDestinationFinalize(destination.get());
    339 
    340     Vector<char> out;
    341     base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
    342 
    343     return makeString("data:", mimeType, ";base64,", out);
    344 }
    345 
    346 String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const
    347 {
    348     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
    349 
    350     RetainPtr<CGImageRef> image;
    351     if (!m_accelerateRendering)
    352         image.adoptCF(CGBitmapContextCreateImage(context()->platformContext()));
    353 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
    354     else
    355         image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
    356 #endif
    357 
    358     if (!image)
    359         return "data:,";
    360 
    361     return CGImageToDataURL(image.get(), mimeType, quality);
    362 }
    363 
    364 String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
    365 {
    366     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
    367 
    368     RetainPtr<CGImageRef> image;
    369     RetainPtr<CGDataProviderRef> dataProvider;
    370 
    371     dataProvider.adoptCF(CGDataProviderCreateWithData(0, source.data()->data()->data(),
    372                                                       4 * source.width() * source.height(), 0));
    373 
    374     if (!dataProvider)
    375         return "data:,";
    376 
    377     image.adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
    378                                 CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast,
    379                                 dataProvider.get(), 0, false, kCGRenderingIntentDefault));
    380 
    381 
    382     if (!image)
    383         return "data:,";
    384 
    385     return CGImageToDataURL(image.get(), mimeType, quality);
    386 }
    387 } // namespace WebCore
    388