75a36e540dcad6eba8285db1c607a3fadb2d5e53
[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 "Base64.h"
32 #include "BitmapImage.h"
33 #include "GraphicsContext.h"
34 #include "GraphicsContextCG.h"
35 #include "MIMETypeRegistry.h"
36 #include <ApplicationServices/ApplicationServices.h>
37 #include <wtf/Assertions.h>
38 #include <wtf/text/StringConcatenate.h>
39 #include <wtf/OwnArrayPtr.h>
40 #include <wtf/RetainPtr.h>
41 #include <wtf/Threading.h>
42 #include <math.h>
43
44 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
45 #include <IOSurface/IOSurface.h>
46 #endif
47
48 #if PLATFORM(MAC) || PLATFORM(CHROMIUM)
49 #include "WebCoreSystemInterface.h"
50 #endif
51
52 using namespace std;
53
54 namespace WebCore {
55
56 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
57 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
58 {
59     unsigned pixelFormat = 'BGRA';
60     unsigned bytesPerElement = 4;
61     int width = size.width();
62     int height = size.height();
63
64     unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
65     if (!bytesPerRow)
66         return 0;
67
68     unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
69     if (!allocSize)
70         return 0;
71
72     const void *keys[6];
73     const void *values[6];
74     keys[0] = kIOSurfaceWidth;
75     values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
76     keys[1] = kIOSurfaceHeight;
77     values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
78     keys[2] = kIOSurfacePixelFormat;
79     values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
80     keys[3] = kIOSurfaceBytesPerElement;
81     values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
82     keys[4] = kIOSurfaceBytesPerRow;
83     values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
84     keys[5] = kIOSurfaceAllocSize;
85     values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
86
87     RetainPtr<CFDictionaryRef> dict(AdoptCF, CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
88     for (unsigned i = 0; i < 6; i++)
89         CFRelease(values[i]);
90
91     return RetainPtr<IOSurfaceRef>(AdoptCF, IOSurfaceCreate(dict.get()));
92 }
93 #endif
94
95 static void releaseImageData(void*, const void* data, size_t)
96 {
97     fastFree(const_cast<void*>(data));
98 }
99
100 ImageBufferData::ImageBufferData(const IntSize&)
101     : m_data(0)
102 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
103     , m_surface(0)
104 #endif
105 {
106 }
107
108 ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
109     : m_data(size)
110     , m_size(size)
111     , m_accelerateRendering(renderingMode == Accelerated)
112 {
113 #if !USE(IOSURFACE_CANVAS_BACKING_STORE)
114     ASSERT(renderingMode == Unaccelerated);
115 #endif
116     success = false;  // Make early return mean failure.
117     if (size.width() < 0 || size.height() < 0)
118         return;
119
120     unsigned bytesPerRow = size.width();
121     if (bytesPerRow > 0x3FFFFFFF) // Protect against overflow
122         return;
123     bytesPerRow *= 4;
124     m_data.m_bytesPerRow = bytesPerRow;
125     size_t dataSize = size.height() * bytesPerRow;
126
127     switch (imageColorSpace) {
128     case ColorSpaceDeviceRGB:
129         m_data.m_colorSpace = deviceRGBColorSpaceRef();
130         break;
131     case ColorSpaceSRGB:
132         m_data.m_colorSpace = sRGBColorSpaceRef();
133         break;
134     case ColorSpaceLinearRGB:
135         m_data.m_colorSpace = linearRGBColorSpaceRef();
136         break;
137     }
138
139     RetainPtr<CGContextRef> cgContext;
140     if (!m_accelerateRendering) {
141         if (!tryFastCalloc(size.height(), bytesPerRow).getValue(m_data.m_data))
142             return;
143         ASSERT(!(reinterpret_cast<size_t>(m_data.m_data) & 2));
144
145         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
146         cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, size.width(), size.height(), 8, bytesPerRow, m_data.m_colorSpace, m_data.m_bitmapInfo));
147         // Create a live image that wraps the data.
148         m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, dataSize, releaseImageData));
149     } else {
150 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
151         m_data.m_surface = createIOSurface(size);
152         cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), size.width(), size.height(), m_data.m_colorSpace));
153 #else
154         m_accelerateRendering = false; // Force to false on older platforms
155 #endif
156     }
157
158     if (!cgContext)
159         return;
160
161     m_context.set(new GraphicsContext(cgContext.get()));
162     m_context->scale(FloatSize(1, -1));
163     m_context->translate(0, -size.height());
164     success = true;
165 }
166
167 ImageBuffer::~ImageBuffer()
168 {
169 }
170
171 GraphicsContext* ImageBuffer::context() const
172 {
173     return m_context.get();
174 }
175
176 bool ImageBuffer::drawsUsingCopy() const
177 {
178     return false;
179 }
180
181 PassRefPtr<Image> ImageBuffer::copyImage() const
182 {
183     // BitmapImage will release the passed in CGImage on destruction
184     CGImageRef ctxImage = 0;
185     if (!m_accelerateRendering)
186         ctxImage = CGBitmapContextCreateImage(context()->platformContext());
187 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
188     else
189         ctxImage = wkIOSurfaceContextCreateImage(context()->platformContext());
190 #endif
191     return BitmapImage::create(ctxImage);
192 }
193
194 static CGImageRef cgImage(const IntSize& size, const ImageBufferData& data)
195 {
196     return CGImageCreate(size.width(), size.height(), 8, 32, data.m_bytesPerRow,
197                          data.m_colorSpace, data.m_bitmapInfo, data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
198 }
199
200 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
201                        CompositeOperator op, bool useLowQualityScale)
202 {
203     if (!m_accelerateRendering) {
204         if (destContext == context()) {
205             // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
206             RefPtr<Image> copy = copyImage();
207             destContext->drawImage(copy.get(), ColorSpaceDeviceRGB, destRect, srcRect, op, useLowQualityScale);
208         } else {
209             RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
210             destContext->drawImage(imageForRendering.get(), styleColorSpace, destRect, srcRect, op, useLowQualityScale);
211         }
212     } else {
213         RefPtr<Image> copy = copyImage();
214         ColorSpace colorSpace = (destContext == context()) ? ColorSpaceDeviceRGB : styleColorSpace;
215         destContext->drawImage(copy.get(), colorSpace, destRect, srcRect, op, useLowQualityScale);
216     }
217 }
218
219 void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform,
220                               const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
221 {
222     if (!m_accelerateRendering) {
223         if (destContext == context()) {
224             // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
225             RefPtr<Image> copy = copyImage();
226             copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
227         } else {
228             RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
229             imageForRendering->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
230         }
231     } else {
232         RefPtr<Image> copy = copyImage();
233         copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
234     }
235 }
236
237 void ImageBuffer::clip(GraphicsContext* context, const FloatRect& rect) const
238 {
239     CGContextRef platformContext = context->platformContext();
240     RetainPtr<CGImageRef> image;
241     if (!m_accelerateRendering)
242         image.adoptCF(cgImage(m_size, m_data));
243 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
244     else
245         image.adoptCF(wkIOSurfaceContextCreateImage(platformContext));
246 #endif
247     CGContextTranslateCTM(platformContext, rect.x(), rect.y() + rect.height());
248     CGContextScaleCTM(platformContext, 1, -1);
249     CGContextClipToMask(platformContext, FloatRect(FloatPoint(), rect.size()), image.get());
250     CGContextScaleCTM(platformContext, 1, -1);
251     CGContextTranslateCTM(platformContext, -rect.x(), -rect.y() - rect.height());
252 }
253
254 template <Multiply multiplied>
255 PassRefPtr<ByteArray> getImageData(const IntRect& rect, const ImageBufferData& imageData, const IntSize& size, bool accelerateRendering)
256 {
257     RefPtr<ByteArray> result = ByteArray::create(rect.width() * rect.height() * 4);
258     unsigned char* data = result->data();
259
260     if (rect.x() < 0 || rect.y() < 0 || rect.right() > size.width() || rect.bottom() > size.height())
261         memset(data, 0, result->length());
262
263     int originx = rect.x();
264     int destx = 0;
265     if (originx < 0) {
266         destx = -originx;
267         originx = 0;
268     }
269     int endx = rect.right();
270     if (endx > size.width())
271         endx = size.width();
272     int numColumns = endx - originx;
273
274     int originy = rect.y();
275     int desty = 0;
276     if (originy < 0) {
277         desty = -originy;
278         originy = 0;
279     }
280     int endy = rect.bottom();
281     if (endy > size.height())
282         endy = size.height();
283     int numRows = endy - originy;
284     
285     unsigned destBytesPerRow = 4 * rect.width();
286     unsigned char* destRows = data + desty * destBytesPerRow + destx * 4;
287
288     unsigned srcBytesPerRow;
289     unsigned char* srcRows;
290
291     if (!accelerateRendering) {
292         srcBytesPerRow = 4 * size.width();
293         srcRows = reinterpret_cast<unsigned char*>(imageData.m_data) + originy * srcBytesPerRow + originx * 4;
294         
295         for (int y = 0; y < numRows; ++y) {
296             for (int x = 0; x < numColumns; x++) {
297                 int basex = x * 4;
298                 unsigned char alpha = srcRows[basex + 3];
299                 if (multiplied == Unmultiplied && alpha) {
300                     destRows[basex] = (srcRows[basex] * 255) / alpha;
301                     destRows[basex + 1] = (srcRows[basex + 1] * 255) / alpha;
302                     destRows[basex + 2] = (srcRows[basex + 2] * 255) / alpha;
303                     destRows[basex + 3] = alpha;
304                 } else
305                     reinterpret_cast<uint32_t*>(destRows + basex)[0] = reinterpret_cast<uint32_t*>(srcRows + basex)[0];
306             }
307             srcRows += srcBytesPerRow;
308             destRows += destBytesPerRow;
309         }
310     } else {
311 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
312         IOSurfaceRef surface = imageData.m_surface.get();
313         IOSurfaceLock(surface, kIOSurfaceLockReadOnly, 0);
314         srcBytesPerRow = IOSurfaceGetBytesPerRow(surface);
315         srcRows = (unsigned char*)(IOSurfaceGetBaseAddress(surface)) + originy * srcBytesPerRow + originx * 4;
316         
317         for (int y = 0; y < numRows; ++y) {
318             for (int x = 0; x < numColumns; x++) {
319                 int basex = x * 4;
320                 unsigned char alpha = srcRows[basex + 3];
321                 if (multiplied == Unmultiplied && alpha) {
322                     destRows[basex] = (srcRows[basex + 2] * 255) / alpha;
323                     destRows[basex + 1] = (srcRows[basex + 1] * 255) / alpha;
324                     destRows[basex + 2] = (srcRows[basex] * 255) / alpha;
325                     destRows[basex + 3] = alpha;
326                 } else {
327                     destRows[basex] = srcRows[basex + 2];
328                     destRows[basex + 1] = srcRows[basex + 1];
329                     destRows[basex + 2] = srcRows[basex];
330                     destRows[basex + 3] = alpha;
331                 }
332             }
333             srcRows += srcBytesPerRow;
334             destRows += destBytesPerRow;
335         }
336         IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly, 0);
337 #else
338         ASSERT_NOT_REACHED();
339 #endif
340     }
341     
342     return result.release();
343 }
344
345 PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
346 {
347     if (m_accelerateRendering)
348         CGContextFlush(context()->platformContext());
349     return getImageData<Unmultiplied>(rect, m_data, m_size, m_accelerateRendering);
350 }
351
352 PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
353 {
354     if (m_accelerateRendering)
355         CGContextFlush(context()->platformContext());
356     return getImageData<Premultiplied>(rect, m_data, m_size, m_accelerateRendering);
357 }
358
359 template <Multiply multiplied>
360 void putImageData(ByteArray*& source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, ImageBufferData& imageData, const IntSize& size, bool accelerateRendering)
361 {
362     ASSERT(sourceRect.width() > 0);
363     ASSERT(sourceRect.height() > 0);
364
365     int originx = sourceRect.x();
366     int destx = destPoint.x() + sourceRect.x();
367     ASSERT(destx >= 0);
368     ASSERT(destx < size.width());
369     ASSERT(originx >= 0);
370     ASSERT(originx <= sourceRect.right());
371
372     int endx = destPoint.x() + sourceRect.right();
373     ASSERT(endx <= size.width());
374
375     int numColumns = endx - destx;
376
377     int originy = sourceRect.y();
378     int desty = destPoint.y() + sourceRect.y();
379     ASSERT(desty >= 0);
380     ASSERT(desty < size.height());
381     ASSERT(originy >= 0);
382     ASSERT(originy <= sourceRect.bottom());
383
384     int endy = destPoint.y() + sourceRect.bottom();
385     ASSERT(endy <= size.height());
386     int numRows = endy - desty;
387
388     unsigned srcBytesPerRow = 4 * sourceSize.width();
389     unsigned char* srcRows = source->data() + originy * srcBytesPerRow + originx * 4;
390     unsigned destBytesPerRow;
391     unsigned char* destRows;
392
393     if (!accelerateRendering) {
394         destBytesPerRow = 4 * size.width();
395         destRows = reinterpret_cast<unsigned char*>(imageData.m_data) + desty * destBytesPerRow + destx * 4;
396         for (int y = 0; y < numRows; ++y) {
397             for (int x = 0; x < numColumns; x++) {
398                 int basex = x * 4;
399                 unsigned char alpha = srcRows[basex + 3];
400                 if (multiplied == Unmultiplied && alpha != 255) {
401                     destRows[basex] = (srcRows[basex] * alpha + 254) / 255;
402                     destRows[basex + 1] = (srcRows[basex + 1] * alpha + 254) / 255;
403                     destRows[basex + 2] = (srcRows[basex + 2] * alpha + 254) / 255;
404                     destRows[basex + 3] = alpha;
405                 } else
406                     reinterpret_cast<uint32_t*>(destRows + basex)[0] = reinterpret_cast<uint32_t*>(srcRows + basex)[0];
407             }
408             destRows += destBytesPerRow;
409             srcRows += srcBytesPerRow;
410         }
411     } else {
412 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
413         IOSurfaceRef surface = imageData.m_surface.get();
414         IOSurfaceLock(surface, 0, 0);
415         destBytesPerRow = IOSurfaceGetBytesPerRow(surface);
416         destRows = (unsigned char*)(IOSurfaceGetBaseAddress(surface)) + desty * destBytesPerRow + destx * 4;
417         
418         for (int y = 0; y < numRows; ++y) {
419             for (int x = 0; x < numColumns; x++) {
420                 int basex = x * 4;
421                 unsigned char alpha = srcRows[basex + 3];
422                 if (multiplied == Unmultiplied && alpha != 255) {
423                     destRows[basex] = (srcRows[basex + 2] * alpha + 254) / 255;
424                     destRows[basex + 1] = (srcRows[basex + 1] * alpha + 254) / 255;
425                     destRows[basex + 2] = (srcRows[basex] * alpha + 254) / 255;
426                     destRows[basex + 3] = alpha;
427                 } else {
428                     destRows[basex] = srcRows[basex + 2];
429                     destRows[basex + 1] = srcRows[basex + 1];
430                     destRows[basex + 2] = srcRows[basex];
431                     destRows[basex + 3] = alpha;
432                 }
433             }
434             destRows += destBytesPerRow;
435             srcRows += srcBytesPerRow;
436         }
437         IOSurfaceUnlock(surface, 0, 0);
438 #else
439         ASSERT_NOT_REACHED();
440 #endif
441     }
442 }
443
444 void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
445 {
446     if (m_accelerateRendering)
447         CGContextFlush(context()->platformContext());
448     putImageData<Unmultiplied>(source, sourceSize, sourceRect, destPoint, m_data, m_size, m_accelerateRendering);
449 }
450
451 void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
452 {
453     if (m_accelerateRendering)
454         CGContextFlush(context()->platformContext());
455     putImageData<Premultiplied>(source, sourceSize, sourceRect, destPoint, m_data, m_size, m_accelerateRendering);
456 }
457
458 static inline CFStringRef jpegUTI()
459 {
460 #if PLATFORM(WIN)
461     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
462 #endif
463     return kUTTypeJPEG;
464 }
465     
466 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
467 {
468 #if PLATFORM(MAC)
469     RetainPtr<CFStringRef> mimeTypeCFString(AdoptCF, mimeType.createCFString());
470     return RetainPtr<CFStringRef>(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCFString.get(), 0));
471 #else
472     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
473
474     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
475     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
476     static const CFStringRef kUTTypePNG = CFSTR("public.png");
477     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
478
479     if (equalIgnoringCase(mimeType, "image/png"))
480         return kUTTypePNG;
481     if (equalIgnoringCase(mimeType, "image/jpeg"))
482         return jpegUTI();
483     if (equalIgnoringCase(mimeType, "image/gif"))
484         return kUTTypeGIF;
485
486     ASSERT_NOT_REACHED();
487     return kUTTypePNG;
488 #endif
489 }
490
491 String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const
492 {
493     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
494
495     RetainPtr<CGImageRef> image;
496     if (!m_accelerateRendering)
497         image.adoptCF(CGBitmapContextCreateImage(context()->platformContext()));
498 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
499     else
500         image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
501 #endif
502
503     if (!image)
504         return "data:,";
505
506     RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
507     if (!data)
508         return "data:,";
509
510     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
511     ASSERT(uti);
512
513     RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
514     if (!destination)
515         return "data:,";
516
517     RetainPtr<CFDictionaryRef> imageProperties = 0;
518     if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
519         // Apply the compression quality to the image destination.
520         RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
521         const void* key = kCGImageDestinationLossyCompressionQuality;
522         const void* value = compressionQuality.get();
523         imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
524     }
525
526     CGImageDestinationAddImage(destination.get(), image.get(), imageProperties.get());
527     CGImageDestinationFinalize(destination.get());
528
529     Vector<char> out;
530     base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
531
532     return makeString("data:", mimeType, ";base64,", out);
533 }
534 } // namespace WebCore