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