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