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