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