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