image-rendering: -webkit-optimize-contrast not working for background images
[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 = sizeForDestinationSize(FloatSize(width.unsafeGet(), height.unsafeGet()));
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 FloatSize ImageBuffer::sizeForDestinationSize(FloatSize destinationSize) const
159 {
160     return scaleSizeToUserSpace(destinationSize, m_data.backingStoreSize, internalSize());
161 }
162
163 GraphicsContext& ImageBuffer::context() const
164 {
165 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
166     if (m_data.surface)
167         return m_data.surface->ensureGraphicsContext();
168 #endif
169     return *m_data.context;
170 }
171
172 void ImageBuffer::flushContext() const
173 {
174     CGContextFlush(context().platformContext());
175 }
176
177 static RetainPtr<CGImageRef> createCroppedImageIfNecessary(CGImageRef image, const IntSize& bounds)
178 {
179     if (image && (CGImageGetWidth(image) != static_cast<size_t>(bounds.width())
180         || CGImageGetHeight(image) != static_cast<size_t>(bounds.height()))) {
181         return adoptCF(CGImageCreateWithImageInRect(image, CGRectMake(0, 0, bounds.width(), bounds.height())));
182     }
183     return image;
184 }
185
186 static RefPtr<Image> createBitmapImageAfterScalingIfNeeded(RetainPtr<CGImageRef>&& image, IntSize internalSize, IntSize logicalSize, IntSize backingStoreSize, float resolutionScale, ScaleBehavior scaleBehavior)
187 {
188     if (resolutionScale == 1 || scaleBehavior == Unscaled)
189         image = createCroppedImageIfNecessary(image.get(), internalSize);
190     else {
191         RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize.width(), logicalSize.height(), 8, 4 * logicalSize.width(), sRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
192         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
193         CGContextClipToRect(context.get(), FloatRect(FloatPoint::zero(), logicalSize));
194         FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize, backingStoreSize, internalSize);
195         CGContextDrawImage(context.get(), FloatRect(FloatPoint::zero(), imageSizeInUserSpace), image.get());
196         image = adoptCF(CGBitmapContextCreateImage(context.get()));
197     }
198
199     if (!image)
200         return nullptr;
201
202     return BitmapImage::create(image.get());
203 }
204
205 RefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior scaleBehavior) const
206 {
207     RetainPtr<CGImageRef> image;
208     if (m_resolutionScale == 1 || scaleBehavior == Unscaled)
209         image = copyNativeImage(copyBehavior);
210     else
211         image = copyNativeImage(DontCopyBackingStore);
212
213     return createBitmapImageAfterScalingIfNeeded(WTFMove(image), internalSize(), logicalSize(), m_data.backingStoreSize, m_resolutionScale, scaleBehavior);
214 }
215
216 RefPtr<Image> ImageBuffer::sinkIntoImage(std::unique_ptr<ImageBuffer> imageBuffer, ScaleBehavior scaleBehavior)
217 {
218     IntSize internalSize = imageBuffer->internalSize();
219     IntSize logicalSize = imageBuffer->logicalSize();
220     IntSize backingStoreSize = imageBuffer->m_data.backingStoreSize;
221     float resolutionScale = imageBuffer->m_resolutionScale;
222
223     return createBitmapImageAfterScalingIfNeeded(sinkIntoNativeImage(WTFMove(imageBuffer)), internalSize, logicalSize, backingStoreSize, resolutionScale, scaleBehavior);
224 }
225
226 BackingStoreCopy ImageBuffer::fastCopyImageMode()
227 {
228     return DontCopyBackingStore;
229 }
230
231 RetainPtr<CGImageRef> ImageBuffer::sinkIntoNativeImage(std::unique_ptr<ImageBuffer> imageBuffer)
232 {
233 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
234     if (!imageBuffer->m_data.surface)
235         return imageBuffer->copyNativeImage(DontCopyBackingStore);
236
237     return IOSurface::sinkIntoImage(IOSurface::createFromImageBuffer(WTFMove(imageBuffer)));
238 #else
239     return imageBuffer->copyNativeImage(DontCopyBackingStore);
240 #endif
241 }
242
243 RetainPtr<CGImageRef> ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
244 {
245     RetainPtr<CGImageRef> image;
246     if (!context().isAcceleratedContext()) {
247         switch (copyBehavior) {
248         case DontCopyBackingStore:
249             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));
250             break;
251         case CopyBackingStore:
252             image = adoptCF(CGBitmapContextCreateImage(context().platformContext()));
253             break;
254         default:
255             ASSERT_NOT_REACHED();
256             break;
257         }
258     }
259 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
260     else
261         image = m_data.surface->createImage();
262 #endif
263
264     return image;
265 }
266
267 void ImageBuffer::drawConsuming(std::unique_ptr<ImageBuffer> imageBuffer, GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode)
268 {
269 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
270     if (!imageBuffer->m_data.surface) {
271         imageBuffer->draw(destContext, destRect, srcRect, op, blendMode);
272         return;
273     }
274     
275     ASSERT(destContext.isAcceleratedContext());
276     
277     float resolutionScale = imageBuffer->m_resolutionScale;
278     IntSize backingStoreSize = imageBuffer->m_data.backingStoreSize;
279
280     RetainPtr<CGImageRef> image = IOSurface::sinkIntoImage(IOSurface::createFromImageBuffer(WTFMove(imageBuffer)));
281     
282     FloatRect adjustedSrcRect = srcRect;
283     adjustedSrcRect.scale(resolutionScale, resolutionScale);
284     destContext.drawNativeImage(image.get(), backingStoreSize, destRect, adjustedSrcRect, op, blendMode);
285 #else
286     imageBuffer->draw(destContext, destRect, srcRect, op, blendMode);
287 #endif
288 }
289
290 void ImageBuffer::draw(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode)
291 {
292     RetainPtr<CGImageRef> image;
293     if (&destContext == &context() || destContext.isAcceleratedContext())
294         image = copyNativeImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
295     else
296         image = copyNativeImage(DontCopyBackingStore);
297
298     FloatRect adjustedSrcRect = srcRect;
299     adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
300     destContext.drawNativeImage(image.get(), m_data.backingStoreSize, destRect, adjustedSrcRect, op, blendMode);
301 }
302
303 void ImageBuffer::drawPattern(GraphicsContext& destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, const FloatRect& destRect, BlendMode blendMode)
304 {
305     FloatRect adjustedSrcRect = srcRect;
306     adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale);
307
308     if (!context().isAcceleratedContext()) {
309         if (&destContext == &context() || destContext.isAcceleratedContext()) {
310             if (RefPtr<Image> copy = copyImage(CopyBackingStore)) // Drawing into our own buffer, need to deep copy.
311                 copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, spacing, op, destRect, blendMode);
312         } else {
313             if (RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore))
314                 imageForRendering->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, spacing, op, destRect, blendMode);
315         }
316     } else {
317         if (RefPtr<Image> copy = copyImage(CopyBackingStore))
318             copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, spacing, op, destRect, blendMode);
319     }
320 }
321
322 PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
323 {
324     if (context().isAcceleratedContext())
325         flushContext();
326
327     IntRect srcRect = rect;
328     if (coordinateSystem == LogicalCoordinateSystem)
329         srcRect.scale(m_resolutionScale);
330
331     return m_data.getData(srcRect, internalSize(), context().isAcceleratedContext(), true, 1);
332 }
333
334 PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
335 {
336     if (context().isAcceleratedContext())
337         flushContext();
338
339     IntRect srcRect = rect;
340     if (coordinateSystem == LogicalCoordinateSystem)
341         srcRect.scale(m_resolutionScale);
342
343     return m_data.getData(srcRect, internalSize(), context().isAcceleratedContext(), false, 1);
344 }
345
346 void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem)
347 {
348     if (!context().isAcceleratedContext()) {
349         IntRect scaledSourceRect = sourceRect;
350         IntSize scaledSourceSize = sourceSize;
351         if (coordinateSystem == LogicalCoordinateSystem) {
352             scaledSourceRect.scale(m_resolutionScale);
353             scaledSourceSize.scale(m_resolutionScale);
354         }
355
356         m_data.putData(source, scaledSourceSize, scaledSourceRect, destPoint, internalSize(), false, multiplied == Unmultiplied, 1);
357         return;
358     }
359
360 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
361     // Make a copy of the source to ensure the bits don't change before being drawn
362     IntSize sourceCopySize(sourceRect.width(), sourceRect.height());
363     // FIXME (149431): Should this ImageBuffer be unconditionally unaccelerated? Making it match the context seems to break putData().
364     std::unique_ptr<ImageBuffer> sourceCopy = ImageBuffer::create(sourceCopySize, Unaccelerated, 1, ColorSpaceSRGB);
365     if (!sourceCopy)
366         return;
367
368     sourceCopy->m_data.putData(source, sourceSize, sourceRect, IntPoint(-sourceRect.x(), -sourceRect.y()), sourceCopy->internalSize(), sourceCopy->context().isAcceleratedContext(), multiplied == Unmultiplied, 1);
369
370     // Set up context for using drawImage as a direct bit copy
371     CGContextRef destContext = context().platformContext();
372     CGContextSaveGState(destContext);
373     
374     if (coordinateSystem == LogicalCoordinateSystem) {
375         if (auto inverse = AffineTransform(getUserToBaseCTM(destContext)).inverse())
376             CGContextConcatCTM(destContext, inverse.value());
377     } else {
378         if (auto inverse = AffineTransform(CGContextGetCTM(destContext)).inverse())
379             CGContextConcatCTM(destContext, inverse.value());
380     }
381     CGContextResetClip(destContext);
382     CGContextSetInterpolationQuality(destContext, kCGInterpolationNone);
383     CGContextSetAlpha(destContext, 1.0);
384     CGContextSetBlendMode(destContext, kCGBlendModeCopy);
385     CGContextSetShadowWithColor(destContext, CGSizeZero, 0, 0);
386
387     // Draw the image in CG coordinate space
388     FloatSize scaledDestSize = sizeForDestinationSize(coordinateSystem == LogicalCoordinateSystem ? logicalSize() : internalSize());
389     IntPoint destPointInCGCoords(destPoint.x() + sourceRect.x(), scaledDestSize.height() - (destPoint.y() + sourceRect.y()) - sourceRect.height());
390     IntRect destRectInCGCoords(destPointInCGCoords, sourceCopySize);
391     CGContextClipToRect(destContext, destRectInCGCoords);
392
393     RetainPtr<CGImageRef> sourceCopyImage = sourceCopy->copyNativeImage();
394     FloatRect backingStoreInDestRect = FloatRect(FloatPoint(destPointInCGCoords.x(), destPointInCGCoords.y() + sourceCopySize.height() - (int)CGImageGetHeight(sourceCopyImage.get())), FloatSize(CGImageGetWidth(sourceCopyImage.get()), CGImageGetHeight(sourceCopyImage.get())));
395     CGContextDrawImage(destContext, backingStoreInDestRect, sourceCopyImage.get());
396     CGContextRestoreGState(destContext);
397 #endif
398 }
399
400 static inline CFStringRef jpegUTI()
401 {
402 #if PLATFORM(IOS) || PLATFORM(WIN)
403     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
404 #endif
405     return kUTTypeJPEG;
406 }
407     
408 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
409 {
410 #if PLATFORM(MAC)
411     return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0));
412 #else
413     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
414
415     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
416     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
417     static const CFStringRef kUTTypePNG = CFSTR("public.png");
418     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
419
420     if (equalLettersIgnoringASCIICase(mimeType, "image/png"))
421         return kUTTypePNG;
422     if (equalLettersIgnoringASCIICase(mimeType, "image/jpeg"))
423         return jpegUTI();
424     if (equalLettersIgnoringASCIICase(mimeType, "image/gif"))
425         return kUTTypeGIF;
426
427     ASSERT_NOT_REACHED();
428     return kUTTypePNG;
429 #endif
430 }
431
432 static bool CGImageEncodeToData(CGImageRef image, CFStringRef uti, const double* quality, CFMutableDataRef data)
433 {
434     if (!image || !uti || !data)
435         return false;
436
437     RetainPtr<CGImageDestinationRef> destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0));
438     if (!destination)
439         return false;
440
441     RetainPtr<CFDictionaryRef> imageProperties = 0;
442     if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
443         // Apply the compression quality to the JPEG image destination.
444         RetainPtr<CFNumberRef> compressionQuality = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
445         const void* key = kCGImageDestinationLossyCompressionQuality;
446         const void* value = compressionQuality.get();
447         imageProperties = adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
448     }
449
450     // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math
451     // in the calling functions, but it doesn't seem to work.
452
453     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
454     return CGImageDestinationFinalize(destination.get());
455 }
456
457 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
458 {
459     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
460     ASSERT(uti);
461
462     RetainPtr<CFMutableDataRef> data = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
463     if (!CGImageEncodeToData(image, uti.get(), quality, data.get()))
464         return "data:,";
465
466     Vector<char> base64Data;
467     base64Encode(CFDataGetBytePtr(data.get()), CFDataGetLength(data.get()), base64Data);
468
469     return "data:" + mimeType + ";base64," + base64Data;
470 }
471
472 String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const
473 {
474     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
475
476     if (context().isAcceleratedContext())
477         flushContext();
478
479     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
480     ASSERT(uti);
481
482     RefPtr<Uint8ClampedArray> premultipliedData;
483     RetainPtr<CGImageRef> image;
484
485     if (CFEqual(uti.get(), jpegUTI())) {
486         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
487         premultipliedData = getPremultipliedImageData(IntRect(IntPoint(0, 0), logicalSize()));
488         if (!premultipliedData)
489             return "data:,";
490
491         RetainPtr<CGDataProviderRef> dataProvider;
492         dataProvider = adoptCF(CGDataProviderCreateWithData(0, premultipliedData->data(), 4 * logicalSize().width() * logicalSize().height(), 0));
493         if (!dataProvider)
494             return "data:,";
495
496         image = adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(),
497                                     sRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast,
498                                     dataProvider.get(), 0, false, kCGRenderingIntentDefault));
499     } else if (m_resolutionScale == 1) {
500         image = copyNativeImage(CopyBackingStore);
501         image = createCroppedImageIfNecessary(image.get(), internalSize());
502     } else {
503         image = copyNativeImage(DontCopyBackingStore);
504         RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), sRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
505         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
506         CGContextClipToRect(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height()));
507         FloatSize imageSizeInUserSpace = sizeForDestinationSize(logicalSize());
508         CGContextDrawImage(context.get(), CGRectMake(0, 0, imageSizeInUserSpace.width(), imageSizeInUserSpace.height()), image.get());
509         image = adoptCF(CGBitmapContextCreateImage(context.get()));
510     }
511
512     return CGImageToDataURL(image.get(), mimeType, quality);
513 }
514
515 String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
516 {
517     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
518
519     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
520     ASSERT(uti);
521
522     CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast;
523     unsigned char* data = source.data()->data();
524     Vector<uint8_t> premultipliedData;
525
526     if (CFEqual(uti.get(), jpegUTI())) {
527         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
528         size_t size = 4 * source.width() * source.height();
529         if (!premultipliedData.tryReserveCapacity(size))
530             return "data:,";
531
532         premultipliedData.resize(size);
533         unsigned char *buffer = premultipliedData.data();
534         for (size_t i = 0; i < size; i += 4) {
535             unsigned alpha = data[i + 3];
536             if (alpha != 255) {
537                 buffer[i + 0] = data[i + 0] * alpha / 255;
538                 buffer[i + 1] = data[i + 1] * alpha / 255;
539                 buffer[i + 2] = data[i + 2] * alpha / 255;
540             } else {
541                 buffer[i + 0] = data[i + 0];
542                 buffer[i + 1] = data[i + 1];
543                 buffer[i + 2] = data[i + 2];
544             }
545         }
546
547         dataAlphaInfo = kCGImageAlphaNoneSkipLast; // Ignore the alpha channel.
548         data = premultipliedData.data();
549     }
550
551     RetainPtr<CGDataProviderRef> dataProvider;
552     dataProvider = adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0));
553     if (!dataProvider)
554         return "data:,";
555
556     RetainPtr<CGImageRef> image;
557     image = adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
558                                 sRGBColorSpaceRef(), kCGBitmapByteOrderDefault | dataAlphaInfo,
559                                 dataProvider.get(), 0, false, kCGRenderingIntentDefault));
560
561     return CGImageToDataURL(image.get(), mimeType, quality);
562 }
563
564 } // namespace WebCore