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