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