REGRESSION (r160152): Selection drag snapshot doesn't appear or has the wrong content...
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / ImageBufferCG.cpp
1 /*
2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2008 Apple Inc. All rights reserved.
4  * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "ImageBuffer.h"
30
31 #include "BitmapImage.h"
32 #include "GraphicsContext.h"
33 #include "GraphicsContextCG.h"
34 #include "ImageData.h"
35 #include "MIMETypeRegistry.h"
36 #include <ApplicationServices/ApplicationServices.h>
37 #include <math.h>
38 #include <wtf/Assertions.h>
39 #include <wtf/CheckedArithmetic.h>
40 #include <wtf/MainThread.h>
41 #include <wtf/RetainPtr.h>
42 #include <wtf/text/Base64.h>
43 #include <wtf/text/WTFString.h>
44
45 #if PLATFORM(MAC)
46 #include "WebCoreSystemInterface.h"
47 #endif
48
49 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
50 #include <IOSurface/IOSurface.h>
51 #endif
52
53 namespace WebCore {
54
55 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
56 static const int maxIOSurfaceDimension = 4096;
57
58 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
59 {
60     unsigned pixelFormat = 'BGRA';
61     unsigned bytesPerElement = 4;
62     int width = size.width();
63     int height = size.height();
64
65     unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
66     if (!bytesPerRow)
67         return 0;
68
69     unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
70     if (!allocSize)
71         return 0;
72
73     const void *keys[6];
74     const void *values[6];
75     keys[0] = kIOSurfaceWidth;
76     values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
77     keys[1] = kIOSurfaceHeight;
78     values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
79     keys[2] = kIOSurfacePixelFormat;
80     values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
81     keys[3] = kIOSurfaceBytesPerElement;
82     values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
83     keys[4] = kIOSurfaceBytesPerRow;
84     values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
85     keys[5] = kIOSurfaceAllocSize;
86     values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
87
88     RetainPtr<CFDictionaryRef> dict = adoptCF(CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
89     for (unsigned i = 0; i < 6; i++)
90         CFRelease(values[i]);
91
92     return adoptCF(IOSurfaceCreate(dict.get()));
93 }
94 #endif
95
96 static void releaseImageData(void*, const void* data, size_t)
97 {
98     fastFree(const_cast<void*>(data));
99 }
100
101 static FloatSize scaleSizeToUserSpace(const FloatSize& logicalSize, const IntSize& backingStoreSize, const IntSize& internalSize)
102 {
103     float xMagnification = static_cast<float>(backingStoreSize.width()) / internalSize.width();
104     float yMagnification = static_cast<float>(backingStoreSize.height()) / internalSize.height();
105     return FloatSize(logicalSize.width() * xMagnification, logicalSize.height() * yMagnification);
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     m_data.m_backingStoreSize = m_size;
122
123     success = false;  // Make early return mean failure.
124     bool accelerateRendering = renderingMode == Accelerated;
125     if (m_size.width() <= 0 || m_size.height() <= 0)
126         return;
127
128     Checked<int, RecordOverflow> width = m_size.width();
129     Checked<int, RecordOverflow> height = m_size.height();
130
131     // Prevent integer overflows
132     m_data.m_bytesPerRow = 4 * Checked<unsigned, RecordOverflow>(m_data.m_backingStoreSize.width());
133     Checked<size_t, RecordOverflow> numBytes = Checked<unsigned, RecordOverflow>(m_data.m_backingStoreSize.height()) * m_data.m_bytesPerRow;
134     if (numBytes.hasOverflowed())
135         return;
136
137 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
138     if (width.unsafeGet() > maxIOSurfaceDimension || height.unsafeGet() > maxIOSurfaceDimension)
139         accelerateRendering = false;
140 #else
141     ASSERT(renderingMode == Unaccelerated);
142 #endif
143
144     switch (imageColorSpace) {
145     case ColorSpaceDeviceRGB:
146         m_data.m_colorSpace = deviceRGBColorSpaceRef();
147         break;
148     case ColorSpaceSRGB:
149         m_data.m_colorSpace = sRGBColorSpaceRef();
150         break;
151     case ColorSpaceLinearRGB:
152         m_data.m_colorSpace = linearRGBColorSpaceRef();
153         break;
154     }
155
156     RetainPtr<CGContextRef> cgContext;
157     if (accelerateRendering) {
158 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
159         m_data.m_surface = createIOSurface(m_data.m_backingStoreSize);
160         FloatSize userBounds = scaleSizeToUserSpace(FloatSize(width.unsafeGet(), height.unsafeGet()), m_data.m_backingStoreSize, m_size);
161         cgContext = adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), userBounds.width(), userBounds.height(), m_data.m_colorSpace));
162 #endif
163         if (!cgContext)
164             accelerateRendering = false; // If allocation fails, fall back to non-accelerated path.
165     }
166
167     if (!accelerateRendering) {
168         if (!tryFastCalloc(m_data.m_backingStoreSize.height(), m_data.m_bytesPerRow.unsafeGet()).getValue(m_data.m_data))
169             return;
170         ASSERT(!(reinterpret_cast<intptr_t>(m_data.m_data) & 3));
171
172         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
173         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));
174         // Create a live image that wraps the data.
175         m_data.m_dataProvider = adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, numBytes.unsafeGet(), releaseImageData));
176     }
177
178     if (!cgContext)
179         return;
180
181     m_context = adoptPtr(new GraphicsContext(cgContext.get()));
182     m_context->scale(FloatSize(1, -1));
183     m_context->translate(0, -m_data.m_backingStoreSize.height());
184     m_context->applyDeviceScaleFactor(m_resolutionScale);
185     m_context->setIsAcceleratedContext(accelerateRendering);
186     success = true;
187 }
188
189 ImageBuffer::~ImageBuffer()
190 {
191 }
192
193 GraphicsContext* ImageBuffer::context() const
194 {
195     return m_context.get();
196 }
197
198 void ImageBuffer::flushContext() const
199 {
200     CGContextFlush(m_context->platformContext());
201 }
202
203 static RetainPtr<CGImageRef> createCroppedImageIfNecessary(CGImageRef image, const IntSize& bounds)
204 {
205     if (image && (CGImageGetWidth(image) != static_cast<size_t>(bounds.width())
206         || CGImageGetHeight(image) != static_cast<size_t>(bounds.height()))) {
207         return adoptCF(CGImageCreateWithImageInRect(image, CGRectMake(0, 0, bounds.width(), bounds.height())));
208     }
209     return image;
210 }
211
212 PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior scaleBehavior) const
213 {
214     RetainPtr<CGImageRef> image;
215     if (m_resolutionScale == 1 || scaleBehavior == Unscaled) {
216         image = copyNativeImage(copyBehavior);
217         image = createCroppedImageIfNecessary(image.get(), internalSize());
218     } else {
219         image = copyNativeImage(DontCopyBackingStore);
220         RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
221         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
222         CGContextClipToRect(context.get(), FloatRect(FloatPoint::zero(), logicalSize()));
223         FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize(), m_data.m_backingStoreSize, internalSize());
224         CGContextDrawImage(context.get(), FloatRect(FloatPoint::zero(), imageSizeInUserSpace), image.get());
225         image = adoptCF(CGBitmapContextCreateImage(context.get()));
226     }
227
228     if (!image)
229         return nullptr;
230
231     RefPtr<BitmapImage> bitmapImage = BitmapImage::create(image.get());
232     bitmapImage->setSpaceSize(spaceSize());
233
234     return bitmapImage.release();
235 }
236
237 BackingStoreCopy ImageBuffer::fastCopyImageMode()
238 {
239     return DontCopyBackingStore;
240 }
241
242 RetainPtr<CGImageRef> ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
243 {
244     CGImageRef image = 0;
245     if (!m_context->isAcceleratedContext()) {
246         switch (copyBehavior) {
247         case DontCopyBackingStore:
248             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);
249             break;
250         case CopyBackingStore:
251             image = CGBitmapContextCreateImage(context()->platformContext());
252             break;
253         default:
254             ASSERT_NOT_REACHED();
255             break;
256         }
257     }
258 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
259     else
260         image = wkIOSurfaceContextCreateImage(context()->platformContext());
261 #endif
262
263     return adoptCF(image);
264 }
265
266 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, bool useLowQualityScale)
267 {
268     UNUSED_PARAM(useLowQualityScale);
269     ColorSpace colorSpace = (destContext == m_context) ? ColorSpaceDeviceRGB : styleColorSpace;
270
271     RetainPtr<CGImageRef> image;
272     if (destContext == m_context || destContext->isAcceleratedContext())
273         image = copyNativeImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
274     else
275         image = copyNativeImage(DontCopyBackingStore);
276
277     FloatRect adjustedSrcRect = srcRect;
278     adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
279     destContext->drawNativeImage(image.get(), m_data.m_backingStoreSize, colorSpace, destRect, adjustedSrcRect, op, blendMode);
280 }
281
282 void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
283 {
284     FloatRect adjustedSrcRect = srcRect;
285     adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
286
287     if (!m_context->isAcceleratedContext()) {
288         if (destContext == m_context || destContext->isAcceleratedContext()) {
289             RefPtr<Image> copy = copyImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
290             copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
291         } else {
292             RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore);
293             imageForRendering->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
294         }
295     } else {
296         RefPtr<Image> copy = copyImage(CopyBackingStore);
297         copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect);
298     }
299 }
300
301 void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
302 {
303     FloatSize backingStoreSizeInUserSpace = scaleSizeToUserSpace(rect.size(), m_data.m_backingStoreSize, internalSize());
304
305     CGContextRef platformContextToClip = contextToClip->platformContext();
306     // FIXME: This image needs to be grayscale to be used as an alpha mask here.
307     RetainPtr<CGImageRef> image = copyNativeImage(DontCopyBackingStore);
308     CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + backingStoreSizeInUserSpace.height());
309     CGContextScaleCTM(platformContextToClip, 1, -1);
310     CGContextClipToRect(platformContextToClip, FloatRect(FloatPoint(0, backingStoreSizeInUserSpace.height() - rect.height()), rect.size()));
311     CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), backingStoreSizeInUserSpace), image.get());
312     CGContextScaleCTM(platformContextToClip, 1, -1);
313     CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
314 }
315
316 PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
317 {
318     if (m_context->isAcceleratedContext())
319         flushContext();
320
321     return m_data.getData(rect, internalSize(), m_context->isAcceleratedContext(), true, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
322 }
323
324 PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
325 {
326     if (m_context->isAcceleratedContext())
327         flushContext();
328
329     return m_data.getData(rect, internalSize(), m_context->isAcceleratedContext(), false, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
330 }
331
332 void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem)
333 {
334     if (!m_context->isAcceleratedContext()) {
335         m_data.putData(source, sourceSize, sourceRect, destPoint, internalSize(), m_context->isAcceleratedContext(), multiplied == Unmultiplied, coordinateSystem == LogicalCoordinateSystem ? m_resolutionScale : 1);
336         return;
337     }
338
339 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
340     // Make a copy of the source to ensure the bits don't change before being drawn
341     IntSize sourceCopySize(sourceRect.width(), sourceRect.height());
342     std::unique_ptr<ImageBuffer> sourceCopy = ImageBuffer::create(sourceCopySize, 1, ColorSpaceDeviceRGB, Unaccelerated);
343     if (!sourceCopy)
344         return;
345
346     sourceCopy->m_data.putData(source, sourceSize, sourceRect, IntPoint(-sourceRect.x(), -sourceRect.y()), sourceCopy->internalSize(), sourceCopy->context()->isAcceleratedContext(), multiplied == Unmultiplied, 1);
347
348     // Set up context for using drawImage as a direct bit copy
349     CGContextRef destContext = context()->platformContext();
350     CGContextSaveGState(destContext);
351     if (coordinateSystem == LogicalCoordinateSystem)
352         CGContextConcatCTM(destContext, AffineTransform(wkGetUserToBaseCTM(destContext)).inverse());
353     else
354         CGContextConcatCTM(destContext, AffineTransform(CGContextGetCTM(destContext)).inverse());
355     wkCGContextResetClip(destContext);
356     CGContextSetInterpolationQuality(destContext, kCGInterpolationNone);
357     CGContextSetAlpha(destContext, 1.0);
358     CGContextSetBlendMode(destContext, kCGBlendModeCopy);
359     CGContextSetShadowWithColor(destContext, CGSizeZero, 0, 0);
360
361     // Draw the image in CG coordinate space
362     FloatSize scaledDestSize = scaleSizeToUserSpace(coordinateSystem == LogicalCoordinateSystem ? logicalSize() : internalSize(), m_data.m_backingStoreSize, internalSize());
363     IntPoint destPointInCGCoords(destPoint.x() + sourceRect.x(), scaledDestSize.height() - (destPoint.y() + sourceRect.y()) - sourceRect.height());
364     IntRect destRectInCGCoords(destPointInCGCoords, sourceCopySize);
365     CGContextClipToRect(destContext, destRectInCGCoords);
366
367     RetainPtr<CGImageRef> sourceCopyImage = sourceCopy->copyNativeImage();
368     FloatRect backingStoreInDestRect = FloatRect(FloatPoint(destPointInCGCoords.x(), destPointInCGCoords.y() + sourceCopySize.height() - (int)CGImageGetHeight(sourceCopyImage.get())), FloatSize(CGImageGetWidth(sourceCopyImage.get()), CGImageGetHeight(sourceCopyImage.get())));
369     CGContextDrawImage(destContext, backingStoreInDestRect, sourceCopyImage.get());
370     CGContextRestoreGState(destContext);
371 #endif
372 }
373
374 static inline CFStringRef jpegUTI()
375 {
376 #if PLATFORM(WIN)
377     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
378 #endif
379     return kUTTypeJPEG;
380 }
381     
382 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
383 {
384 #if PLATFORM(MAC)
385     return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0));
386 #else
387     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
388
389     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
390     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
391     static const CFStringRef kUTTypePNG = CFSTR("public.png");
392     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
393
394     if (equalIgnoringCase(mimeType, "image/png"))
395         return kUTTypePNG;
396     if (equalIgnoringCase(mimeType, "image/jpeg"))
397         return jpegUTI();
398     if (equalIgnoringCase(mimeType, "image/gif"))
399         return kUTTypeGIF;
400
401     ASSERT_NOT_REACHED();
402     return kUTTypePNG;
403 #endif
404 }
405
406 static bool CGImageEncodeToData(CGImageRef image, CFStringRef uti, const double* quality, CFMutableDataRef data)
407 {
408     if (!image || !uti || !data)
409         return false;
410
411     RetainPtr<CGImageDestinationRef> destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0));
412     if (!destination)
413         return false;
414
415     RetainPtr<CFDictionaryRef> imageProperties = 0;
416     if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
417         // Apply the compression quality to the JPEG image destination.
418         RetainPtr<CFNumberRef> compressionQuality = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
419         const void* key = kCGImageDestinationLossyCompressionQuality;
420         const void* value = compressionQuality.get();
421         imageProperties = adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
422     }
423
424     // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math
425     // in the calling functions, but it doesn't seem to work.
426
427     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
428     return CGImageDestinationFinalize(destination.get());
429 }
430
431 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
432 {
433     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
434     ASSERT(uti);
435
436     RetainPtr<CFMutableDataRef> data = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
437     if (!CGImageEncodeToData(image, uti.get(), quality, data.get()))
438         return "data:,";
439
440     Vector<char> base64Data;
441     base64Encode(CFDataGetBytePtr(data.get()), CFDataGetLength(data.get()), base64Data);
442
443     return "data:" + mimeType + ";base64," + base64Data;
444 }
445
446 String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const
447 {
448     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
449
450     if (m_context->isAcceleratedContext())
451         flushContext();
452
453     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
454     ASSERT(uti);
455
456     RefPtr<Uint8ClampedArray> premultipliedData;
457     RetainPtr<CGImageRef> image;
458
459     if (CFEqual(uti.get(), jpegUTI())) {
460         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
461         premultipliedData = getPremultipliedImageData(IntRect(IntPoint(0, 0), logicalSize()));
462         if (!premultipliedData)
463             return "data:,";
464
465         RetainPtr<CGDataProviderRef> dataProvider;
466         dataProvider = adoptCF(CGDataProviderCreateWithData(0, premultipliedData->data(), 4 * logicalSize().width() * logicalSize().height(), 0));
467         if (!dataProvider)
468             return "data:,";
469
470         image = adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(),
471                                     deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast,
472                                     dataProvider.get(), 0, false, kCGRenderingIntentDefault));
473     } else if (m_resolutionScale == 1) {
474         image = copyNativeImage(CopyBackingStore);
475         image = createCroppedImageIfNecessary(image.get(), internalSize());
476     } else {
477         image = copyNativeImage(DontCopyBackingStore);
478         RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
479         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
480         CGContextClipToRect(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height()));
481         FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize(), m_data.m_backingStoreSize, internalSize());
482         CGContextDrawImage(context.get(), CGRectMake(0, 0, imageSizeInUserSpace.width(), imageSizeInUserSpace.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