[mac] ImageBuffer should create accelerated buffers for small canvases, but we should...
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / ImageBufferCG.cpp
index 75a36e5..7e0434c 100644 (file)
 #include "config.h"
 #include "ImageBuffer.h"
 
-#include "Base64.h"
 #include "BitmapImage.h"
 #include "GraphicsContext.h"
 #include "GraphicsContextCG.h"
+#include "ImageData.h"
 #include "MIMETypeRegistry.h"
 #include <ApplicationServices/ApplicationServices.h>
+#include <math.h>
 #include <wtf/Assertions.h>
-#include <wtf/text/StringConcatenate.h>
+#include <wtf/CheckedArithmetic.h>
+#include <wtf/MainThread.h>
 #include <wtf/OwnArrayPtr.h>
 #include <wtf/RetainPtr.h>
-#include <wtf/Threading.h>
-#include <math.h>
+#include <wtf/UnusedParam.h>
+#include <wtf/text/Base64.h>
+#include <wtf/text/WTFString.h>
+
+#if PLATFORM(MAC) || PLATFORM(CHROMIUM)
+#include "WebCoreSystemInterface.h"
+#endif
 
 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
 #include <IOSurface/IOSurface.h>
 #endif
 
-#if PLATFORM(MAC) || PLATFORM(CHROMIUM)
-#include "WebCoreSystemInterface.h"
+#if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1070
+#include <wtf/CurrentTime.h>
 #endif
 
 using namespace std;
@@ -54,6 +61,8 @@ using namespace std;
 namespace WebCore {
 
 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
+static const int maxIOSurfaceDimension = 4096;
+
 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
 {
     unsigned pixelFormat = 'BGRA';
@@ -97,32 +106,40 @@ static void releaseImageData(void*, const void* data, size_t)
     fastFree(const_cast<void*>(data));
 }
 
-ImageBufferData::ImageBufferData(const IntSize&)
-    : m_data(0)
-#if USE(IOSURFACE_CANVAS_BACKING_STORE)
-    , m_surface(0)
-#endif
+ImageBuffer::ImageBuffer(const IntSize& size, float resolutionScale, ColorSpace imageColorSpace, RenderingMode renderingMode, DeferralMode, bool& success)
+    : m_data(size) // NOTE: The input here isn't important as ImageBufferDataCG's constructor just ignores it.
+    , m_logicalSize(size)
+    , m_resolutionScale(resolutionScale)
 {
-}
+    float scaledWidth = ceilf(resolutionScale * size.width());
+    float scaledHeight = ceilf(resolutionScale * size.height());
+
+    // FIXME: Should we automatically use a lower resolution?
+    if (!FloatSize(scaledWidth, scaledHeight).isExpressibleAsIntSize())
+        return;
+
+    m_size = IntSize(scaledWidth, scaledHeight);
 
-ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
-    : m_data(size)
-    , m_size(size)
-    , m_accelerateRendering(renderingMode == Accelerated)
-{
-#if !USE(IOSURFACE_CANVAS_BACKING_STORE)
-    ASSERT(renderingMode == Unaccelerated);
-#endif
     success = false;  // Make early return mean failure.
-    if (size.width() < 0 || size.height() < 0)
+    bool accelerateRendering = renderingMode == Accelerated;
+    if (m_size.width() <= 0 || m_size.height() <= 0)
         return;
 
-    unsigned bytesPerRow = size.width();
-    if (bytesPerRow > 0x3FFFFFFF) // Protect against overflow
+    Checked<int, RecordOverflow> width = m_size.width();
+    Checked<int, RecordOverflow> height = m_size.height();
+
+    // Prevent integer overflows
+    m_data.m_bytesPerRow = 4 * width;
+    Checked<size_t, RecordOverflow> numBytes = height * m_data.m_bytesPerRow;
+    if (numBytes.hasOverflowed())
         return;
-    bytesPerRow *= 4;
-    m_data.m_bytesPerRow = bytesPerRow;
-    size_t dataSize = size.height() * bytesPerRow;
+
+#if USE(IOSURFACE_CANVAS_BACKING_STORE)
+    if (width.unsafeGet() >= maxIOSurfaceDimension || height.unsafeGet() >= maxIOSurfaceDimension)
+        accelerateRendering = false;
+#else
+    ASSERT(renderingMode == Unaccelerated);
+#endif
 
     switch (imageColorSpace) {
     case ColorSpaceDeviceRGB:
@@ -137,30 +154,37 @@ ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, Render
     }
 
     RetainPtr<CGContextRef> cgContext;
-    if (!m_accelerateRendering) {
-        if (!tryFastCalloc(size.height(), bytesPerRow).getValue(m_data.m_data))
+    if (accelerateRendering) {
+#if USE(IOSURFACE_CANVAS_BACKING_STORE)
+        m_data.m_surface = createIOSurface(m_size);
+        cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), width.unsafeGet(), height.unsafeGet(), m_data.m_colorSpace));
+#endif
+        if (!cgContext)
+            accelerateRendering = false; // If allocation fails, fall back to non-accelerated path.
+    }
+
+    if (!accelerateRendering) {
+        if (!tryFastCalloc(height.unsafeGet(), m_data.m_bytesPerRow.unsafeGet()).getValue(m_data.m_data))
             return;
         ASSERT(!(reinterpret_cast<size_t>(m_data.m_data) & 2));
 
         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
-        cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, size.width(), size.height(), 8, bytesPerRow, m_data.m_colorSpace, m_data.m_bitmapInfo));
+        cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, width.unsafeGet(), height.unsafeGet(), 8, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo));
         // Create a live image that wraps the data.
-        m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, dataSize, releaseImageData));
-    } else {
-#if USE(IOSURFACE_CANVAS_BACKING_STORE)
-        m_data.m_surface = createIOSurface(size);
-        cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), size.width(), size.height(), m_data.m_colorSpace));
-#else
-        m_accelerateRendering = false; // Force to false on older platforms
-#endif
+        m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, numBytes.unsafeGet(), releaseImageData));
     }
 
     if (!cgContext)
         return;
 
-    m_context.set(new GraphicsContext(cgContext.get()));
+    m_context = adoptPtr(new GraphicsContext(cgContext.get()));
+    m_context->applyDeviceScaleFactor(m_resolutionScale);
     m_context->scale(FloatSize(1, -1));
     m_context->translate(0, -size.height());
+    m_context->setIsAcceleratedContext(accelerateRendering);
+#if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1070
+    m_data.m_lastFlushTime = currentTimeMS();
+#endif
     success = true;
 }
 
@@ -170,289 +194,180 @@ ImageBuffer::~ImageBuffer()
 
 GraphicsContext* ImageBuffer::context() const
 {
+#if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1070
+    // Force a flush if last flush was more than 20ms ago
+    if (m_context->isAcceleratedContext()) {
+        double elapsedTime = currentTimeMS() - m_data.m_lastFlushTime;
+        double maxFlushInterval = 20; // in ms
+
+        if (elapsedTime > maxFlushInterval) {
+            CGContextRef context = m_context->platformContext();
+            CGContextFlush(context);
+            m_data.m_lastFlushTime = currentTimeMS();
+        }
+    }
+#endif
+
     return m_context.get();
 }
 
-bool ImageBuffer::drawsUsingCopy() const
+PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior scaleBehavior) const
 {
-    return false;
-}
+    RetainPtr<CGImageRef> image;
+    if (m_resolutionScale == 1 || scaleBehavior == Unscaled)
+        image = copyNativeImage(copyBehavior);
+    else {
+        image.adoptCF(copyNativeImage(DontCopyBackingStore));
+        RetainPtr<CGContextRef> context(AdoptCF, CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
+        CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
+        CGContextDrawImage(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height()), image.get());
+        image = CGBitmapContextCreateImage(context.get());
+    }
 
-PassRefPtr<Image> ImageBuffer::copyImage() const
-{
-    // BitmapImage will release the passed in CGImage on destruction
-    CGImageRef ctxImage = 0;
-    if (!m_accelerateRendering)
-        ctxImage = CGBitmapContextCreateImage(context()->platformContext());
-#if USE(IOSURFACE_CANVAS_BACKING_STORE)
-    else
-        ctxImage = wkIOSurfaceContextCreateImage(context()->platformContext());
-#endif
-    return BitmapImage::create(ctxImage);
-}
+    if (!image)
+        return 0;
 
-static CGImageRef cgImage(const IntSize& size, const ImageBufferData& data)
-{
-    return CGImageCreate(size.width(), size.height(), 8, 32, data.m_bytesPerRow,
-                         data.m_colorSpace, data.m_bitmapInfo, data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
+    return BitmapImage::create(image.get());
 }
 
-void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
-                       CompositeOperator op, bool useLowQualityScale)
+BackingStoreCopy ImageBuffer::fastCopyImageMode()
 {
-    if (!m_accelerateRendering) {
-        if (destContext == context()) {
-            // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
-            RefPtr<Image> copy = copyImage();
-            destContext->drawImage(copy.get(), ColorSpaceDeviceRGB, destRect, srcRect, op, useLowQualityScale);
-        } else {
-            RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
-            destContext->drawImage(imageForRendering.get(), styleColorSpace, destRect, srcRect, op, useLowQualityScale);
-        }
-    } else {
-        RefPtr<Image> copy = copyImage();
-        ColorSpace colorSpace = (destContext == context()) ? ColorSpaceDeviceRGB : styleColorSpace;
-        destContext->drawImage(copy.get(), colorSpace, destRect, srcRect, op, useLowQualityScale);
-    }
+    return DontCopyBackingStore;
 }
 
-void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform,
-                              const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
+NativeImagePtr ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
 {
-    if (!m_accelerateRendering) {
-        if (destContext == context()) {
-            // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
-            RefPtr<Image> copy = copyImage();
-            copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
-        } else {
-            RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
-            imageForRendering->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
+    CGImageRef image = 0;
+    if (!m_context->isAcceleratedContext()) {
+        switch (copyBehavior) {
+        case DontCopyBackingStore:
+            image = CGImageCreate(internalSize().width(), internalSize().height(), 8, 32, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo, m_data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
+            break;
+        case CopyBackingStore:
+            image = CGBitmapContextCreateImage(context()->platformContext());
+            break;
+        default:
+            ASSERT_NOT_REACHED();
+            break;
         }
-    } else {
-        RefPtr<Image> copy = copyImage();
-        copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
     }
-}
-
-void ImageBuffer::clip(GraphicsContext* context, const FloatRect& rect) const
-{
-    CGContextRef platformContext = context->platformContext();
-    RetainPtr<CGImageRef> image;
-    if (!m_accelerateRendering)
-        image.adoptCF(cgImage(m_size, m_data));
 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
-    else
-        image.adoptCF(wkIOSurfaceContextCreateImage(platformContext));
+    else {
+        image = wkIOSurfaceContextCreateImage(context()->platformContext());
+#if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1070
+        m_data.m_lastFlushTime = currentTimeMS();
+#endif
+    }
 #endif
-    CGContextTranslateCTM(platformContext, rect.x(), rect.y() + rect.height());
-    CGContextScaleCTM(platformContext, 1, -1);
-    CGContextClipToMask(platformContext, FloatRect(FloatPoint(), rect.size()), image.get());
-    CGContextScaleCTM(platformContext, 1, -1);
-    CGContextTranslateCTM(platformContext, -rect.x(), -rect.y() - rect.height());
+
+    return image;
 }
 
-template <Multiply multiplied>
-PassRefPtr<ByteArray> getImageData(const IntRect& rect, const ImageBufferData& imageData, const IntSize& size, bool accelerateRendering)
+void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, bool useLowQualityScale)
 {
-    RefPtr<ByteArray> result = ByteArray::create(rect.width() * rect.height() * 4);
-    unsigned char* data = result->data();
+    UNUSED_PARAM(useLowQualityScale);
+    ColorSpace colorSpace = (destContext == m_context) ? ColorSpaceDeviceRGB : styleColorSpace;
 
-    if (rect.x() < 0 || rect.y() < 0 || rect.right() > size.width() || rect.bottom() > size.height())
-        memset(data, 0, result->length());
+    RetainPtr<CGImageRef> image;
+    if (destContext == m_context || destContext->isAcceleratedContext())
+        image.adoptCF(copyNativeImage(CopyBackingStore)); // Drawing into our own buffer, need to deep copy.
+    else
+        image.adoptCF(copyNativeImage(DontCopyBackingStore));
 
-    int originx = rect.x();
-    int destx = 0;
-    if (originx < 0) {
-        destx = -originx;
-        originx = 0;
-    }
-    int endx = rect.right();
-    if (endx > size.width())
-        endx = size.width();
-    int numColumns = endx - originx;
-
-    int originy = rect.y();
-    int desty = 0;
-    if (originy < 0) {
-        desty = -originy;
-        originy = 0;
-    }
-    int endy = rect.bottom();
-    if (endy > size.height())
-        endy = size.height();
-    int numRows = endy - originy;
-    
-    unsigned destBytesPerRow = 4 * rect.width();
-    unsigned char* destRows = data + desty * destBytesPerRow + destx * 4;
+    FloatRect adjustedSrcRect = srcRect;
+    adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
+    destContext->drawNativeImage(image.get(), internalSize(), colorSpace, destRect, adjustedSrcRect, op, blendMode);
+}
 
-    unsigned srcBytesPerRow;
-    unsigned char* srcRows;
+void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
+{
+    FloatRect adjustedSrcRect = srcRect;
+    adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
 
-    if (!accelerateRendering) {
-        srcBytesPerRow = 4 * size.width();
-        srcRows = reinterpret_cast<unsigned char*>(imageData.m_data) + originy * srcBytesPerRow + originx * 4;
-        
-        for (int y = 0; y < numRows; ++y) {
-            for (int x = 0; x < numColumns; x++) {
-                int basex = x * 4;
-                unsigned char alpha = srcRows[basex + 3];
-                if (multiplied == Unmultiplied && alpha) {
-                    destRows[basex] = (srcRows[basex] * 255) / alpha;
-                    destRows[basex + 1] = (srcRows[basex + 1] * 255) / alpha;
-                    destRows[basex + 2] = (srcRows[basex + 2] * 255) / alpha;
-                    destRows[basex + 3] = alpha;
-                } else
-                    reinterpret_cast<uint32_t*>(destRows + basex)[0] = reinterpret_cast<uint32_t*>(srcRows + basex)[0];
-            }
-            srcRows += srcBytesPerRow;
-            destRows += destBytesPerRow;
+    if (!m_context->isAcceleratedContext()) {
+        if (destContext == m_context || destContext->isAcceleratedContext()) {
+            RefPtr<Image> copy = copyImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
+            copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
+        } else {
+            RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore);
+            imageForRendering->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
         }
     } else {
-#if USE(IOSURFACE_CANVAS_BACKING_STORE)
-        IOSurfaceRef surface = imageData.m_surface.get();
-        IOSurfaceLock(surface, kIOSurfaceLockReadOnly, 0);
-        srcBytesPerRow = IOSurfaceGetBytesPerRow(surface);
-        srcRows = (unsigned char*)(IOSurfaceGetBaseAddress(surface)) + originy * srcBytesPerRow + originx * 4;
-        
-        for (int y = 0; y < numRows; ++y) {
-            for (int x = 0; x < numColumns; x++) {
-                int basex = x * 4;
-                unsigned char alpha = srcRows[basex + 3];
-                if (multiplied == Unmultiplied && alpha) {
-                    destRows[basex] = (srcRows[basex + 2] * 255) / alpha;
-                    destRows[basex + 1] = (srcRows[basex + 1] * 255) / alpha;
-                    destRows[basex + 2] = (srcRows[basex] * 255) / alpha;
-                    destRows[basex + 3] = alpha;
-                } else {
-                    destRows[basex] = srcRows[basex + 2];
-                    destRows[basex + 1] = srcRows[basex + 1];
-                    destRows[basex + 2] = srcRows[basex];
-                    destRows[basex + 3] = alpha;
-                }
-            }
-            srcRows += srcBytesPerRow;
-            destRows += destBytesPerRow;
-        }
-        IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly, 0);
-#else
-        ASSERT_NOT_REACHED();
-#endif
+        RefPtr<Image> copy = copyImage(CopyBackingStore);
+        copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
     }
-    
-    return result.release();
 }
 
-PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
+void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
 {
-    if (m_accelerateRendering)
-        CGContextFlush(context()->platformContext());
-    return getImageData<Unmultiplied>(rect, m_data, m_size, m_accelerateRendering);
+    CGContextRef platformContextToClip = contextToClip->platformContext();
+    // FIXME: This image needs to be grayscale to be used as an alpha mask here.
+    RetainPtr<CGImageRef> image(AdoptCF, copyNativeImage(DontCopyBackingStore));
+    CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + rect.height());
+    CGContextScaleCTM(platformContextToClip, 1, -1);
+    CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), rect.size()), image.get());
+    CGContextScaleCTM(platformContextToClip, 1, -1);
+    CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
 }
 
-PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
+PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
 {
-    if (m_accelerateRendering)
+    if (m_context->isAcceleratedContext()) {
         CGContextFlush(context()->platformContext());
-    return getImageData<Premultiplied>(rect, m_data, m_size, m_accelerateRendering);
-}
-
-template <Multiply multiplied>
-void putImageData(ByteArray*& source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, ImageBufferData& imageData, const IntSize& size, bool accelerateRendering)
-{
-    ASSERT(sourceRect.width() > 0);
-    ASSERT(sourceRect.height() > 0);
-
-    int originx = sourceRect.x();
-    int destx = destPoint.x() + sourceRect.x();
-    ASSERT(destx >= 0);
-    ASSERT(destx < size.width());
-    ASSERT(originx >= 0);
-    ASSERT(originx <= sourceRect.right());
-
-    int endx = destPoint.x() + sourceRect.right();
-    ASSERT(endx <= size.width());
-
-    int numColumns = endx - destx;
-
-    int originy = sourceRect.y();
-    int desty = destPoint.y() + sourceRect.y();
-    ASSERT(desty >= 0);
-    ASSERT(desty < size.height());
-    ASSERT(originy >= 0);
-    ASSERT(originy <= sourceRect.bottom());
-
-    int endy = destPoint.y() + sourceRect.bottom();
-    ASSERT(endy <= size.height());
-    int numRows = endy - desty;
-
-    unsigned srcBytesPerRow = 4 * sourceSize.width();
-    unsigned char* srcRows = source->data() + originy * srcBytesPerRow + originx * 4;
-    unsigned destBytesPerRow;
-    unsigned char* destRows;
-
-    if (!accelerateRendering) {
-        destBytesPerRow = 4 * size.width();
-        destRows = reinterpret_cast<unsigned char*>(imageData.m_data) + desty * destBytesPerRow + destx * 4;
-        for (int y = 0; y < numRows; ++y) {
-            for (int x = 0; x < numColumns; x++) {
-                int basex = x * 4;
-                unsigned char alpha = srcRows[basex + 3];
-                if (multiplied == Unmultiplied && alpha != 255) {
-                    destRows[basex] = (srcRows[basex] * alpha + 254) / 255;
-                    destRows[basex + 1] = (srcRows[basex + 1] * alpha + 254) / 255;
-                    destRows[basex + 2] = (srcRows[basex + 2] * alpha + 254) / 255;
-                    destRows[basex + 3] = alpha;
-                } else
-                    reinterpret_cast<uint32_t*>(destRows + basex)[0] = reinterpret_cast<uint32_t*>(srcRows + basex)[0];
-            }
-            destRows += destBytesPerRow;
-            srcRows += srcBytesPerRow;
-        }
-    } else {
-#if USE(IOSURFACE_CANVAS_BACKING_STORE)
-        IOSurfaceRef surface = imageData.m_surface.get();
-        IOSurfaceLock(surface, 0, 0);
-        destBytesPerRow = IOSurfaceGetBytesPerRow(surface);
-        destRows = (unsigned char*)(IOSurfaceGetBaseAddress(surface)) + desty * destBytesPerRow + destx * 4;
-        
-        for (int y = 0; y < numRows; ++y) {
-            for (int x = 0; x < numColumns; x++) {
-                int basex = x * 4;
-                unsigned char alpha = srcRows[basex + 3];
-                if (multiplied == Unmultiplied && alpha != 255) {
-                    destRows[basex] = (srcRows[basex + 2] * alpha + 254) / 255;
-                    destRows[basex + 1] = (srcRows[basex + 1] * alpha + 254) / 255;
-                    destRows[basex + 2] = (srcRows[basex] * alpha + 254) / 255;
-                    destRows[basex + 3] = alpha;
-                } else {
-                    destRows[basex] = srcRows[basex + 2];
-                    destRows[basex + 1] = srcRows[basex + 1];
-                    destRows[basex + 2] = srcRows[basex];
-                    destRows[basex + 3] = alpha;
-                }
-            }
-            destRows += destBytesPerRow;
-            srcRows += srcBytesPerRow;
-        }
-        IOSurfaceUnlock(surface, 0, 0);
-#else
-        ASSERT_NOT_REACHED();
+#if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1070
+        m_data.m_lastFlushTime = currentTimeMS();
 #endif
     }
+    return m_data.getData(rect, internalSize(), m_context->isAcceleratedContext(), true, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
 }
 
-void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
+PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
 {
-    if (m_accelerateRendering)
+    if (m_context->isAcceleratedContext()) {
         CGContextFlush(context()->platformContext());
-    putImageData<Unmultiplied>(source, sourceSize, sourceRect, destPoint, m_data, m_size, m_accelerateRendering);
+#if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED == 1070
+        m_data.m_lastFlushTime = currentTimeMS();
+#endif
+    }
+    return m_data.getData(rect, internalSize(), m_context->isAcceleratedContext(), false, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
 }
 
-void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
+void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem)
 {
-    if (m_accelerateRendering)
-        CGContextFlush(context()->platformContext());
-    putImageData<Premultiplied>(source, sourceSize, sourceRect, destPoint, m_data, m_size, m_accelerateRendering);
+    if (!m_context->isAcceleratedContext()) {
+        m_data.putData(source, sourceSize, sourceRect, destPoint, internalSize(), m_context->isAcceleratedContext(), multiplied == Unmultiplied, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
+        return;
+    }
+
+#if USE(IOSURFACE_CANVAS_BACKING_STORE)
+    // Make a copy of the source to ensure the bits don't change before being drawn
+    IntSize sourceCopySize(sourceRect.width(), sourceRect.height());
+    OwnPtr<ImageBuffer> sourceCopy = ImageBuffer::create(sourceCopySize, 1, ColorSpaceDeviceRGB, Unaccelerated);
+    if (!sourceCopy)
+        return;
+
+    sourceCopy->m_data.putData(source, sourceSize, sourceRect, IntPoint(-sourceRect.x(), -sourceRect.y()), sourceCopy->internalSize(), sourceCopy->context()->isAcceleratedContext(), multiplied == Unmultiplied, 1);
+
+    // Set up context for using drawImage as a direct bit copy
+    CGContextRef destContext = context()->platformContext();
+    CGContextSaveGState(destContext);
+    if (coordinateSystem == LogicalCoordinateSystem)
+        CGContextConcatCTM(destContext, AffineTransform(wkGetUserToBaseCTM(destContext)).inverse());
+    else
+        CGContextConcatCTM(destContext, AffineTransform(CGContextGetCTM(destContext)).inverse());
+    wkCGContextResetClip(destContext);
+    CGContextSetInterpolationQuality(destContext, kCGInterpolationNone);
+    CGContextSetAlpha(destContext, 1.0);
+    CGContextSetBlendMode(destContext, kCGBlendModeCopy);
+    CGContextSetShadowWithColor(destContext, CGSizeZero, 0, 0);
+
+    // Draw the image in CG coordinate space
+    IntPoint destPointInCGCoords(destPoint.x() + sourceRect.x(), (coordinateSystem == LogicalCoordinateSystem ? logicalSize() : internalSize()).height() - (destPoint.y() + sourceRect.y()) - sourceRect.height());
+    IntRect destRectInCGCoords(destPointInCGCoords, sourceCopySize);
+    RetainPtr<CGImageRef> sourceCopyImage(AdoptCF, sourceCopy->copyNativeImage());
+    CGContextDrawImage(destContext, destRectInCGCoords, sourceCopyImage.get());
+    CGContextRestoreGState(destContext);
+#endif
 }
 
 static inline CFStringRef jpegUTI()
@@ -466,8 +381,7 @@ static inline CFStringRef jpegUTI()
 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
 {
 #if PLATFORM(MAC)
-    RetainPtr<CFStringRef> mimeTypeCFString(AdoptCF, mimeType.createCFString());
-    return RetainPtr<CFStringRef>(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCFString.get(), 0));
+    return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0));
 #else
     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
 
@@ -488,47 +402,129 @@ static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
 #endif
 }
 
-String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const
+static bool CGImageEncodeToData(CGImageRef image, CFStringRef uti, const double* quality, CFMutableDataRef data)
 {
-    ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
+    if (!image || !uti || !data)
+        return false;
 
-    RetainPtr<CGImageRef> image;
-    if (!m_accelerateRendering)
-        image.adoptCF(CGBitmapContextCreateImage(context()->platformContext()));
-#if USE(IOSURFACE_CANVAS_BACKING_STORE)
-    else
-        image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
-#endif
+    RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data, uti, 1, 0));
+    if (!destination)
+        return false;
 
-    if (!image)
-        return "data:,";
+    RetainPtr<CFDictionaryRef> imageProperties = 0;
+    if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
+        // Apply the compression quality to the JPEG image destination.
+        RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
+        const void* key = kCGImageDestinationLossyCompressionQuality;
+        const void* value = compressionQuality.get();
+        imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
+    }
+
+    // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math
+    // in the calling functions, but it doesn't seem to work.
+
+    CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
+    return CGImageDestinationFinalize(destination.get());
+}
+
+static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
+{
+    RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
+    ASSERT(uti);
 
     RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
-    if (!data)
+    if (!CGImageEncodeToData(image, uti.get(), quality, data.get()))
         return "data:,";
 
+    Vector<char> base64Data;
+    base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), base64Data);
+
+    return "data:" + mimeType + ";base64," + base64Data;
+}
+
+String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const
+{
+    ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
+
     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
     ASSERT(uti);
 
-    RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
-    if (!destination)
-        return "data:,";
+    RefPtr<Uint8ClampedArray> premultipliedData;
+    RetainPtr<CGImageRef> image;
 
-    RetainPtr<CFDictionaryRef> imageProperties = 0;
-    if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
-        // Apply the compression quality to the image destination.
-        RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
-        const void* key = kCGImageDestinationLossyCompressionQuality;
-        const void* value = compressionQuality.get();
-        imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
+    if (CFEqual(uti.get(), jpegUTI())) {
+        // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
+        premultipliedData = getPremultipliedImageData(IntRect(IntPoint(0, 0), logicalSize()));
+        if (!premultipliedData)
+            return "data:,";
+
+        RetainPtr<CGDataProviderRef> dataProvider;
+        dataProvider.adoptCF(CGDataProviderCreateWithData(0, premultipliedData->data(), 4 * logicalSize().width() * logicalSize().height(), 0));
+        if (!dataProvider)
+            return "data:,";
+
+        image.adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(),
+                                    deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast,
+                                    dataProvider.get(), 0, false, kCGRenderingIntentDefault));
+    } else if (m_resolutionScale == 1)
+        image.adoptCF(copyNativeImage(CopyBackingStore));
+    else {
+        image.adoptCF(copyNativeImage(DontCopyBackingStore));
+        RetainPtr<CGContextRef> context(AdoptCF, CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
+        CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
+        CGContextDrawImage(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height()), image.get());
+        image.adoptCF(CGBitmapContextCreateImage(context.get()));
+    }
+
+    return CGImageToDataURL(image.get(), mimeType, quality);
+}
+
+String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
+{
+    ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
+
+    RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
+    ASSERT(uti);
+
+    CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast;
+    unsigned char* data = source.data()->data();
+    Vector<uint8_t> premultipliedData;
+
+    if (CFEqual(uti.get(), jpegUTI())) {
+        // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
+        size_t size = 4 * source.width() * source.height();
+        if (!premultipliedData.tryReserveCapacity(size))
+            return "data:,";
+
+        unsigned char *buffer = premultipliedData.data();
+        for (size_t i = 0; i < size; i += 4) {
+            unsigned alpha = data[i + 3];
+            if (alpha != 255) {
+                buffer[i + 0] = data[i + 0] * alpha / 255;
+                buffer[i + 1] = data[i + 1] * alpha / 255;
+                buffer[i + 2] = data[i + 2] * alpha / 255;
+            } else {
+                buffer[i + 0] = data[i + 0];
+                buffer[i + 1] = data[i + 1];
+                buffer[i + 2] = data[i + 2];
+            }
+        }
+
+        dataAlphaInfo = kCGImageAlphaNoneSkipLast; // Ignore the alpha channel.
+        data = premultipliedData.data();
     }
 
-    CGImageDestinationAddImage(destination.get(), image.get(), imageProperties.get());
-    CGImageDestinationFinalize(destination.get());
+    RetainPtr<CGDataProviderRef> dataProvider;
+    dataProvider.adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0));
+    if (!dataProvider)
+        return "data:,";
 
-    Vector<char> out;
-    base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
+    RetainPtr<CGImageRef> image;
+    image.adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
+                                deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | dataAlphaInfo,
+                                dataProvider.get(), 0, false, kCGRenderingIntentDefault));
 
-    return makeString("data:", mimeType, ";base64,", out);
+    return CGImageToDataURL(image.get(), mimeType, quality);
 }
+
 } // namespace WebCore