Allow ImageBuffer to re-use IOSurfaces
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / ImageBufferCG.cpp
1 /*
2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@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 "BitmapImage.h"
32 #include "GraphicsContext.h"
33 #include "GraphicsContextCG.h"
34 #include "ImageData.h"
35 #include "MIMETypeRegistry.h"
36 #include <ApplicationServices/ApplicationServices.h>
37 #include <math.h>
38 #include <wtf/Assertions.h>
39 #include <wtf/CheckedArithmetic.h>
40 #include <wtf/MainThread.h>
41 #include <wtf/RetainPtr.h>
42 #include <wtf/text/Base64.h>
43 #include <wtf/text/WTFString.h>
44
45 #if PLATFORM(MAC)
46 #include "WebCoreSystemInterface.h"
47 #endif
48
49 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
50 #include "ImageBufferBackingStoreCache.h"
51 #include <IOSurface/IOSurface.h>
52 #endif
53
54 namespace WebCore {
55
56 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
57 static const int maxIOSurfaceDimension = 4096;
58 #endif
59
60 static void releaseImageData(void*, const void* data, size_t)
61 {
62     fastFree(const_cast<void*>(data));
63 }
64
65 static FloatSize scaleSizeToUserSpace(const FloatSize& logicalSize, const IntSize& backingStoreSize, const IntSize& internalSize)
66 {
67     float xMagnification = static_cast<float>(backingStoreSize.width()) / internalSize.width();
68     float yMagnification = static_cast<float>(backingStoreSize.height()) / internalSize.height();
69     return FloatSize(logicalSize.width() * xMagnification, logicalSize.height() * yMagnification);
70 }
71
72 ImageBuffer::ImageBuffer(const IntSize& size, float resolutionScale, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
73     : m_data(size) // NOTE: The input here isn't important as ImageBufferDataCG's constructor just ignores it.
74     , m_logicalSize(size)
75     , m_resolutionScale(resolutionScale)
76 {
77     float scaledWidth = ceilf(resolutionScale * size.width());
78     float scaledHeight = ceilf(resolutionScale * size.height());
79
80     // FIXME: Should we automatically use a lower resolution?
81     if (!FloatSize(scaledWidth, scaledHeight).isExpressibleAsIntSize())
82         return;
83
84     m_size = IntSize(scaledWidth, scaledHeight);
85     m_data.m_backingStoreSize = m_size;
86
87     success = false;  // Make early return mean failure.
88     bool accelerateRendering = renderingMode == Accelerated;
89     if (m_size.width() <= 0 || m_size.height() <= 0)
90         return;
91
92     Checked<int, RecordOverflow> width = m_size.width();
93     Checked<int, RecordOverflow> height = m_size.height();
94
95     // Prevent integer overflows
96     m_data.m_bytesPerRow = 4 * Checked<unsigned, RecordOverflow>(m_data.m_backingStoreSize.width());
97     Checked<size_t, RecordOverflow> numBytes = Checked<unsigned, RecordOverflow>(m_data.m_backingStoreSize.height()) * m_data.m_bytesPerRow;
98     if (numBytes.hasOverflowed())
99         return;
100
101 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
102     if (width.unsafeGet() > maxIOSurfaceDimension || height.unsafeGet() > maxIOSurfaceDimension)
103         accelerateRendering = false;
104 #else
105     ASSERT(renderingMode == Unaccelerated);
106 #endif
107
108     switch (imageColorSpace) {
109     case ColorSpaceDeviceRGB:
110         m_data.m_colorSpace = deviceRGBColorSpaceRef();
111         break;
112     case ColorSpaceSRGB:
113         m_data.m_colorSpace = sRGBColorSpaceRef();
114         break;
115     case ColorSpaceLinearRGB:
116         m_data.m_colorSpace = linearRGBColorSpaceRef();
117         break;
118     }
119
120     RetainPtr<CGContextRef> cgContext;
121     if (accelerateRendering) {
122 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
123         ImageBufferBackingStoreCache::IOSurfaceAndContext infoFromPool = ImageBufferBackingStoreCache::get().getOrAllocate(
124             internalSize(), m_data.m_colorSpace, false);
125         cgContext = infoFromPool.context;
126         if (cgContext) {
127             m_data.m_surface = infoFromPool.surface;
128             m_data.m_backingStoreSize.setWidth(IOSurfaceGetWidth(m_data.m_surface.get()));
129             m_data.m_backingStoreSize.setHeight(IOSurfaceGetHeight(m_data.m_surface.get()));
130             m_data.m_bytesPerRow = IOSurfaceGetBytesPerRow(m_data.m_surface.get());
131         }
132 #endif
133         if (!cgContext)
134             accelerateRendering = false; // If allocation fails, fall back to non-accelerated path.
135     }
136
137     if (!accelerateRendering) {
138         if (!tryFastCalloc(m_data.m_backingStoreSize.height(), m_data.m_bytesPerRow.unsafeGet()).getValue(m_data.m_data))
139             return;
140         ASSERT(!(reinterpret_cast<intptr_t>(m_data.m_data) & 3));
141
142         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
143         cgContext = adoptCF(CGBitmapContextCreate(m_data.m_data, m_data.m_backingStoreSize.width(), m_data.m_backingStoreSize.height(), 8, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo));
144         // Create a live image that wraps the data.
145         m_data.m_dataProvider = adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, numBytes.unsafeGet(), releaseImageData));
146     }
147
148     if (!cgContext)
149         return;
150
151     m_context = adoptPtr(new GraphicsContext(cgContext.get()));
152     m_context->scale(FloatSize(1, -1));
153     m_context->translate(0, -m_data.m_backingStoreSize.height());
154     m_context->applyDeviceScaleFactor(m_resolutionScale);
155     m_context->setIsAcceleratedContext(accelerateRendering);
156     success = true;
157 }
158
159 ImageBuffer::~ImageBuffer()
160 {
161 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
162     if (m_data.m_surface)
163         ImageBufferBackingStoreCache::get().deallocate(m_data.m_surface.get());
164 #endif
165 }
166
167 GraphicsContext* ImageBuffer::context() const
168 {
169     return m_context.get();
170 }
171
172 void ImageBuffer::flushContext() const
173 {
174     CGContextFlush(m_context->platformContext());
175 }
176
177 static RetainPtr<CGImageRef> createCroppedImageIfNecessary(CGImageRef image, const IntSize& bounds)
178 {
179     if (image && (CGImageGetWidth(image) != static_cast<size_t>(bounds.width())
180         || CGImageGetHeight(image) != static_cast<size_t>(bounds.height()))) {
181         return adoptCF(CGImageCreateWithImageInRect(image, CGRectMake(0, 0, bounds.width(), bounds.height())));
182     }
183     return image;
184 }
185
186 PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior scaleBehavior) const
187 {
188     RetainPtr<CGImageRef> image;
189     if (m_resolutionScale == 1 || scaleBehavior == Unscaled)
190         image = copyNativeImage(copyBehavior);
191     else {
192         image = copyNativeImage(DontCopyBackingStore);
193         RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
194         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
195         CGContextDrawImage(context.get(), CGRectMake(0, 0, m_data.m_backingStoreSize.width(), m_data.m_backingStoreSize.height()), image.get());
196         image = adoptCF(CGBitmapContextCreateImage(context.get()));
197     }
198     
199     image = createCroppedImageIfNecessary(image.get(), internalSize());
200
201     if (!image)
202         return 0;
203
204     ASSERT(CGImageGetWidth(image.get()) == static_cast<size_t>(m_logicalSize.width()));
205     ASSERT(CGImageGetHeight(image.get()) == static_cast<size_t>(m_logicalSize.height()));
206
207     RefPtr<BitmapImage> bitmapImage = BitmapImage::create(image.get());
208     bitmapImage->setSpaceSize(spaceSize());
209
210     return bitmapImage.release();
211 }
212
213 BackingStoreCopy ImageBuffer::fastCopyImageMode()
214 {
215     return DontCopyBackingStore;
216 }
217
218 RetainPtr<CGImageRef> ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
219 {
220     CGImageRef image = 0;
221     if (!m_context->isAcceleratedContext()) {
222         switch (copyBehavior) {
223         case DontCopyBackingStore:
224             image = CGImageCreate(m_data.m_backingStoreSize.width(), m_data.m_backingStoreSize.height(), 8, 32, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo, m_data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
225             break;
226         case CopyBackingStore:
227             image = CGBitmapContextCreateImage(context()->platformContext());
228             break;
229         default:
230             ASSERT_NOT_REACHED();
231             break;
232         }
233     }
234 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
235     else
236         image = wkIOSurfaceContextCreateImage(context()->platformContext());
237 #endif
238
239     return adoptCF(image);
240 }
241
242 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, bool useLowQualityScale)
243 {
244     UNUSED_PARAM(useLowQualityScale);
245     ColorSpace colorSpace = (destContext == m_context) ? ColorSpaceDeviceRGB : styleColorSpace;
246
247     RetainPtr<CGImageRef> image;
248     if (destContext == m_context || destContext->isAcceleratedContext())
249         image = copyNativeImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
250     else
251         image = copyNativeImage(DontCopyBackingStore);
252
253     FloatRect adjustedSrcRect = srcRect;
254     adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
255     destContext->drawNativeImage(image.get(), m_data.m_backingStoreSize, colorSpace, destRect, adjustedSrcRect, op, blendMode);
256 }
257
258 void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
259 {
260     FloatRect adjustedSrcRect = srcRect;
261     adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
262
263     if (!m_context->isAcceleratedContext()) {
264         if (destContext == m_context || destContext->isAcceleratedContext()) {
265             RefPtr<Image> copy = copyImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
266             copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
267         } else {
268             RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore);
269             imageForRendering->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
270         }
271     } else {
272         RefPtr<Image> copy = copyImage(CopyBackingStore);
273         copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
274     }
275 }
276
277 void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
278 {
279     FloatSize backingStoreSizeInUserSpace = scaleSizeToUserSpace(rect.size(), m_data.m_backingStoreSize, internalSize());
280
281     CGContextRef platformContextToClip = contextToClip->platformContext();
282     // FIXME: This image needs to be grayscale to be used as an alpha mask here.
283     RetainPtr<CGImageRef> image = copyNativeImage(DontCopyBackingStore);
284     CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + backingStoreSizeInUserSpace.height());
285     CGContextScaleCTM(platformContextToClip, 1, -1);
286     CGContextClipToRect(platformContextToClip, FloatRect(FloatPoint(0, backingStoreSizeInUserSpace.height() - rect.height()), rect.size()));
287     CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), backingStoreSizeInUserSpace), image.get());
288     CGContextScaleCTM(platformContextToClip, 1, -1);
289     CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
290 }
291
292 PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
293 {
294     if (m_context->isAcceleratedContext())
295         flushContext();
296
297     return m_data.getData(rect, internalSize(), m_context->isAcceleratedContext(), true, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
298 }
299
300 PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
301 {
302     if (m_context->isAcceleratedContext())
303         flushContext();
304
305     return m_data.getData(rect, internalSize(), m_context->isAcceleratedContext(), false, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
306 }
307
308 void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem)
309 {
310     if (!m_context->isAcceleratedContext()) {
311         m_data.putData(source, sourceSize, sourceRect, destPoint, internalSize(), m_context->isAcceleratedContext(), multiplied == Unmultiplied, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
312         return;
313     }
314
315 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
316     // Make a copy of the source to ensure the bits don't change before being drawn
317     IntSize sourceCopySize(sourceRect.width(), sourceRect.height());
318     std::unique_ptr<ImageBuffer> sourceCopy = ImageBuffer::create(sourceCopySize, 1, ColorSpaceDeviceRGB, Unaccelerated);
319     if (!sourceCopy)
320         return;
321
322     sourceCopy->m_data.putData(source, sourceSize, sourceRect, IntPoint(-sourceRect.x(), -sourceRect.y()), sourceCopy->internalSize(), sourceCopy->context()->isAcceleratedContext(), multiplied == Unmultiplied, 1);
323
324     // Set up context for using drawImage as a direct bit copy
325     CGContextRef destContext = context()->platformContext();
326     CGContextSaveGState(destContext);
327     if (coordinateSystem == LogicalCoordinateSystem)
328         CGContextConcatCTM(destContext, AffineTransform(wkGetUserToBaseCTM(destContext)).inverse());
329     else
330         CGContextConcatCTM(destContext, AffineTransform(CGContextGetCTM(destContext)).inverse());
331     wkCGContextResetClip(destContext);
332     CGContextSetInterpolationQuality(destContext, kCGInterpolationNone);
333     CGContextSetAlpha(destContext, 1.0);
334     CGContextSetBlendMode(destContext, kCGBlendModeCopy);
335     CGContextSetShadowWithColor(destContext, CGSizeZero, 0, 0);
336
337     // Draw the image in CG coordinate space
338     FloatSize scaledDestSize = scaleSizeToUserSpace(coordinateSystem == LogicalCoordinateSystem ? logicalSize() : internalSize(), m_data.m_backingStoreSize, internalSize());
339     IntPoint destPointInCGCoords(destPoint.x() + sourceRect.x(), scaledDestSize.height() - (destPoint.y() + sourceRect.y()) - sourceRect.height());
340     IntRect destRectInCGCoords(destPointInCGCoords, sourceCopySize);
341     CGContextClipToRect(destContext, destRectInCGCoords);
342
343     RetainPtr<CGImageRef> sourceCopyImage = sourceCopy->copyNativeImage();
344     FloatRect backingStoreInDestRect = FloatRect(FloatPoint(destPointInCGCoords.x(), destPointInCGCoords.y() + sourceCopySize.height() - (int)CGImageGetHeight(sourceCopyImage.get())), FloatSize(CGImageGetWidth(sourceCopyImage.get()), CGImageGetHeight(sourceCopyImage.get())));
345     CGContextDrawImage(destContext, backingStoreInDestRect, sourceCopyImage.get());
346     CGContextRestoreGState(destContext);
347 #endif
348 }
349
350 static inline CFStringRef jpegUTI()
351 {
352 #if PLATFORM(WIN)
353     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
354 #endif
355     return kUTTypeJPEG;
356 }
357     
358 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
359 {
360 #if PLATFORM(MAC)
361     return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0));
362 #else
363     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
364
365     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
366     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
367     static const CFStringRef kUTTypePNG = CFSTR("public.png");
368     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
369
370     if (equalIgnoringCase(mimeType, "image/png"))
371         return kUTTypePNG;
372     if (equalIgnoringCase(mimeType, "image/jpeg"))
373         return jpegUTI();
374     if (equalIgnoringCase(mimeType, "image/gif"))
375         return kUTTypeGIF;
376
377     ASSERT_NOT_REACHED();
378     return kUTTypePNG;
379 #endif
380 }
381
382 static bool CGImageEncodeToData(CGImageRef image, CFStringRef uti, const double* quality, CFMutableDataRef data)
383 {
384     if (!image || !uti || !data)
385         return false;
386
387     RetainPtr<CGImageDestinationRef> destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0));
388     if (!destination)
389         return false;
390
391     RetainPtr<CFDictionaryRef> imageProperties = 0;
392     if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
393         // Apply the compression quality to the JPEG image destination.
394         RetainPtr<CFNumberRef> compressionQuality = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
395         const void* key = kCGImageDestinationLossyCompressionQuality;
396         const void* value = compressionQuality.get();
397         imageProperties = adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
398     }
399
400     // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math
401     // in the calling functions, but it doesn't seem to work.
402
403     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
404     return CGImageDestinationFinalize(destination.get());
405 }
406
407 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
408 {
409     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
410     ASSERT(uti);
411
412     RetainPtr<CFMutableDataRef> data = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
413     if (!CGImageEncodeToData(image, uti.get(), quality, data.get()))
414         return "data:,";
415
416     Vector<char> base64Data;
417     base64Encode(CFDataGetBytePtr(data.get()), CFDataGetLength(data.get()), base64Data);
418
419     return "data:" + mimeType + ";base64," + base64Data;
420 }
421
422 String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const
423 {
424     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
425
426     if (m_context->isAcceleratedContext())
427         flushContext();
428
429     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
430     ASSERT(uti);
431
432     RefPtr<Uint8ClampedArray> premultipliedData;
433     RetainPtr<CGImageRef> image;
434
435     if (CFEqual(uti.get(), jpegUTI())) {
436         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
437         premultipliedData = getPremultipliedImageData(IntRect(IntPoint(0, 0), logicalSize()));
438         if (!premultipliedData)
439             return "data:,";
440
441         RetainPtr<CGDataProviderRef> dataProvider;
442         dataProvider = adoptCF(CGDataProviderCreateWithData(0, premultipliedData->data(), 4 * logicalSize().width() * logicalSize().height(), 0));
443         if (!dataProvider)
444             return "data:,";
445
446         image = adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(),
447                                     deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast,
448                                     dataProvider.get(), 0, false, kCGRenderingIntentDefault));
449     } else if (m_resolutionScale == 1) {
450         image = copyNativeImage(CopyBackingStore);
451         image = createCroppedImageIfNecessary(image.get(), internalSize());
452     } else {
453         image = copyNativeImage(DontCopyBackingStore);
454         RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
455         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
456         CGContextClipToRect(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height()));
457         FloatSize imageRectInUserBounds = scaleSizeToUserSpace(logicalSize(), m_data.m_backingStoreSize, internalSize());
458         CGContextDrawImage(context.get(), CGRectMake(0, 0, imageRectInUserBounds.width(), imageRectInUserBounds.height()), image.get());
459         image = adoptCF(CGBitmapContextCreateImage(context.get()));
460     }
461
462     return CGImageToDataURL(image.get(), mimeType, quality);
463 }
464
465 String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
466 {
467     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
468
469     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
470     ASSERT(uti);
471
472     CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast;
473     unsigned char* data = source.data()->data();
474     Vector<uint8_t> premultipliedData;
475
476     if (CFEqual(uti.get(), jpegUTI())) {
477         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
478         size_t size = 4 * source.width() * source.height();
479         if (!premultipliedData.tryReserveCapacity(size))
480             return "data:,";
481
482         unsigned char *buffer = premultipliedData.data();
483         for (size_t i = 0; i < size; i += 4) {
484             unsigned alpha = data[i + 3];
485             if (alpha != 255) {
486                 buffer[i + 0] = data[i + 0] * alpha / 255;
487                 buffer[i + 1] = data[i + 1] * alpha / 255;
488                 buffer[i + 2] = data[i + 2] * alpha / 255;
489             } else {
490                 buffer[i + 0] = data[i + 0];
491                 buffer[i + 1] = data[i + 1];
492                 buffer[i + 2] = data[i + 2];
493             }
494         }
495
496         dataAlphaInfo = kCGImageAlphaNoneSkipLast; // Ignore the alpha channel.
497         data = premultipliedData.data();
498     }
499
500     RetainPtr<CGDataProviderRef> dataProvider;
501     dataProvider = adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0));
502     if (!dataProvider)
503         return "data:,";
504
505     RetainPtr<CGImageRef> image;
506     image = adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
507                                 deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | dataAlphaInfo,
508                                 dataProvider.get(), 0, false, kCGRenderingIntentDefault));
509
510     return CGImageToDataURL(image.get(), mimeType, quality);
511 }
512
513 } // namespace WebCore