d89641989bebf3b7d944def0c9317d9d729bf70d
[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 "ImageData.h"
36 #include "MIMETypeRegistry.h"
37 #include <ApplicationServices/ApplicationServices.h>
38 #include <math.h>
39 #include <wtf/Assertions.h>
40 #include <wtf/CheckedArithmetic.h>
41 #include <wtf/MainThread.h>
42 #include <wtf/OwnArrayPtr.h>
43 #include <wtf/RetainPtr.h>
44 #include <wtf/UnusedParam.h>
45 #include <wtf/text/WTFString.h>
46
47 #if PLATFORM(MAC) || PLATFORM(CHROMIUM)
48 #include "WebCoreSystemInterface.h"
49 #endif
50
51 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
52 #include <IOSurface/IOSurface.h>
53 #endif
54
55 #if defined(BUILDING_ON_LION)
56 #include <wtf/CurrentTime.h>
57 #endif
58
59 using namespace std;
60
61 namespace WebCore {
62
63 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
64 static const int maxIOSurfaceDimension = 4096;
65 static const int minIOSurfaceArea = 50 * 100;
66
67 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
68 {
69     unsigned pixelFormat = 'BGRA';
70     unsigned bytesPerElement = 4;
71     int width = size.width();
72     int height = size.height();
73
74     unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
75     if (!bytesPerRow)
76         return 0;
77
78     unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
79     if (!allocSize)
80         return 0;
81
82     const void *keys[6];
83     const void *values[6];
84     keys[0] = kIOSurfaceWidth;
85     values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
86     keys[1] = kIOSurfaceHeight;
87     values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
88     keys[2] = kIOSurfacePixelFormat;
89     values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
90     keys[3] = kIOSurfaceBytesPerElement;
91     values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
92     keys[4] = kIOSurfaceBytesPerRow;
93     values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
94     keys[5] = kIOSurfaceAllocSize;
95     values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
96
97     RetainPtr<CFDictionaryRef> dict(AdoptCF, CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
98     for (unsigned i = 0; i < 6; i++)
99         CFRelease(values[i]);
100
101     return RetainPtr<IOSurfaceRef>(AdoptCF, IOSurfaceCreate(dict.get()));
102 }
103 #endif
104
105 static void releaseImageData(void*, const void* data, size_t)
106 {
107     fastFree(const_cast<void*>(data));
108 }
109
110 ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
111     : m_data(size)
112     , m_size(size)
113 {
114     success = false;  // Make early return mean failure.
115     bool accelerateRendering = renderingMode == Accelerated;
116     if (size.width() <= 0 || size.height() <= 0)
117         return;
118
119     Checked<int, RecordOverflow> width = size.width();
120     Checked<int, RecordOverflow> height = size.height();
121
122     // Prevent integer overflows
123     m_data.m_bytesPerRow = 4 * width;
124     Checked<size_t, RecordOverflow> dataSize = height * m_data.m_bytesPerRow;
125     if (dataSize.hasOverflowed())
126         return;
127
128 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
129     if (width.unsafeGet() >= maxIOSurfaceDimension || height.unsafeGet() >= maxIOSurfaceDimension || (width * height).unsafeGet() < minIOSurfaceArea)
130         accelerateRendering = false;
131 #else
132     ASSERT(renderingMode == Unaccelerated);
133 #endif
134
135     switch (imageColorSpace) {
136     case ColorSpaceDeviceRGB:
137         m_data.m_colorSpace = deviceRGBColorSpaceRef();
138         break;
139     case ColorSpaceSRGB:
140         m_data.m_colorSpace = sRGBColorSpaceRef();
141         break;
142     case ColorSpaceLinearRGB:
143         m_data.m_colorSpace = linearRGBColorSpaceRef();
144         break;
145     }
146
147     RetainPtr<CGContextRef> cgContext;
148     if (accelerateRendering) {
149 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
150         m_data.m_surface = createIOSurface(size);
151         cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), width.unsafeGet(), height.unsafeGet(), m_data.m_colorSpace));
152 #endif
153         if (!cgContext)
154             accelerateRendering = false; // If allocation fails, fall back to non-accelerated path.
155     }
156
157     if (!accelerateRendering) {
158         if (!tryFastCalloc(height.unsafeGet(), m_data.m_bytesPerRow.unsafeGet()).getValue(m_data.m_data))
159             return;
160         ASSERT(!(reinterpret_cast<size_t>(m_data.m_data) & 2));
161
162         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
163         cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, width.unsafeGet(), height.unsafeGet(), 8, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo));
164         // Create a live image that wraps the data.
165         m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, dataSize.unsafeGet(), releaseImageData));
166     }
167
168     if (!cgContext)
169         return;
170
171     m_context = adoptPtr(new GraphicsContext(cgContext.get()));
172     m_context->scale(FloatSize(1, -1));
173     m_context->translate(0, -height.unsafeGet());
174     m_context->setIsAcceleratedContext(accelerateRendering);
175 #if defined(BUILDING_ON_LION)
176     m_data.m_lastFlushTime = currentTimeMS();
177 #endif
178     success = true;
179 }
180
181 ImageBuffer::~ImageBuffer()
182 {
183 }
184
185 size_t ImageBuffer::dataSize() const
186 {
187     return m_size.height() * m_data.m_bytesPerRow.unsafeGet();
188 }
189
190 GraphicsContext* ImageBuffer::context() const
191 {
192 #if defined(BUILDING_ON_LION)
193     // Force a flush if last flush was more than 20ms ago
194     if (m_context->isAcceleratedContext()) {
195         double elapsedTime = currentTimeMS() - m_data.m_lastFlushTime;
196         double maxFlushInterval = 20; // in ms
197
198         if (elapsedTime > maxFlushInterval) {
199             CGContextRef context = m_context->platformContext();
200             CGContextFlush(context);
201             m_data.m_lastFlushTime = currentTimeMS();
202         }
203     }
204 #endif
205
206     return m_context.get();
207 }
208
209 PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior) const
210 {
211     RetainPtr<CGImageRef> image = copyNativeImage(copyBehavior);
212
213     if (!image)
214         return 0;
215
216     return BitmapImage::create(image.get());
217 }
218
219 NativeImagePtr ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
220 {
221     CGImageRef image = 0;
222     if (!m_context->isAcceleratedContext()) {
223         switch (copyBehavior) {
224         case DontCopyBackingStore:
225             image = CGImageCreate(m_size.width(), m_size.height(), 8, 32, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo, m_data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
226             break;
227         case CopyBackingStore:
228             image = CGBitmapContextCreateImage(context()->platformContext());
229             break;
230         default:
231             ASSERT_NOT_REACHED();
232             break;
233         }
234     }
235 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
236     else {
237         image = wkIOSurfaceContextCreateImage(context()->platformContext());
238 #if defined(BUILDING_ON_LION)
239         m_data.m_lastFlushTime = currentTimeMS();
240 #endif
241     }
242 #endif
243
244     return image;
245 }
246
247 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, bool useLowQualityScale)
248 {
249     UNUSED_PARAM(useLowQualityScale);
250     ColorSpace colorSpace = (destContext == m_context) ? ColorSpaceDeviceRGB : styleColorSpace;
251
252     RetainPtr<CGImageRef> image;
253     if (destContext == m_context || destContext->isAcceleratedContext())
254         image.adoptCF(copyNativeImage(CopyBackingStore)); // Drawing into our own buffer, need to deep copy.
255     else
256         image.adoptCF(copyNativeImage(DontCopyBackingStore));
257
258     destContext->drawNativeImage(image.get(), m_size, colorSpace, destRect, srcRect, op);
259 }
260
261 void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
262 {
263     if (!m_context->isAcceleratedContext()) {
264         if (destContext == m_context || destContext->isAcceleratedContext()) {
265             RefPtr<Image> copy = copyImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
266             copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
267         } else {
268             RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore);
269             imageForRendering->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
270         }
271     } else {
272         RefPtr<Image> copy = copyImage(CopyBackingStore);
273         copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
274     }
275 }
276
277 void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
278 {
279     CGContextRef platformContextToClip = contextToClip->platformContext();
280     RetainPtr<CGImageRef> image(AdoptCF, copyNativeImage(DontCopyBackingStore));
281     CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + rect.height());
282     CGContextScaleCTM(platformContextToClip, 1, -1);
283     CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), rect.size()), image.get());
284     CGContextScaleCTM(platformContextToClip, 1, -1);
285     CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
286 }
287
288 PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
289 {
290     if (m_context->isAcceleratedContext()) {
291         CGContextFlush(context()->platformContext());
292 #if defined(BUILDING_ON_LION)
293         m_data.m_lastFlushTime = currentTimeMS();
294 #endif
295     }
296     return m_data.getData(rect, m_size, m_context->isAcceleratedContext(), true);
297 }
298
299 PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
300 {
301     if (m_context->isAcceleratedContext()) {
302         CGContextFlush(context()->platformContext());
303 #if defined(BUILDING_ON_LION)
304         m_data.m_lastFlushTime = currentTimeMS();
305 #endif
306     }
307     return m_data.getData(rect, m_size, m_context->isAcceleratedContext(), false);
308 }
309
310 void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
311 {
312     if (m_context->isAcceleratedContext()) {
313         CGContextFlush(context()->platformContext());
314 #if defined(BUILDING_ON_LION)
315         m_data.m_lastFlushTime = currentTimeMS();
316 #endif
317     }
318     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_context->isAcceleratedContext(), true);
319 }
320
321 void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
322 {
323     if (m_context->isAcceleratedContext()) {
324         CGContextFlush(context()->platformContext());
325 #if defined(BUILDING_ON_LION)
326         m_data.m_lastFlushTime = currentTimeMS();
327 #endif
328     }
329     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_context->isAcceleratedContext(), false);
330 }
331
332 static inline CFStringRef jpegUTI()
333 {
334 #if PLATFORM(WIN)
335     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
336 #endif
337     return kUTTypeJPEG;
338 }
339     
340 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
341 {
342 #if PLATFORM(MAC)
343     RetainPtr<CFStringRef> mimeTypeCFString(AdoptCF, mimeType.createCFString());
344     return RetainPtr<CFStringRef>(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCFString.get(), 0));
345 #else
346     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
347
348     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
349     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
350     static const CFStringRef kUTTypePNG = CFSTR("public.png");
351     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
352
353     if (equalIgnoringCase(mimeType, "image/png"))
354         return kUTTypePNG;
355     if (equalIgnoringCase(mimeType, "image/jpeg"))
356         return jpegUTI();
357     if (equalIgnoringCase(mimeType, "image/gif"))
358         return kUTTypeGIF;
359
360     ASSERT_NOT_REACHED();
361     return kUTTypePNG;
362 #endif
363 }
364
365 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
366 {
367     RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
368     if (!data)
369         return "data:,";
370
371     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
372     ASSERT(uti);
373
374     RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
375     if (!destination)
376         return "data:,";
377
378     RetainPtr<CFDictionaryRef> imageProperties = 0;
379     if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
380         // Apply the compression quality to the image destination.
381         RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
382         const void* key = kCGImageDestinationLossyCompressionQuality;
383         const void* value = compressionQuality.get();
384         imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
385     }
386
387     // Setting kCGImageDestinationBackgroundColor to black in imageProperties would allow saving some math in the
388     // calling functions, but it doesn't seem to work.
389
390     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
391     CGImageDestinationFinalize(destination.get());
392
393     Vector<char> out;
394     base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
395
396     return "data:" + mimeType + ";base64," + out;
397 }
398
399 String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const
400 {
401     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
402     RetainPtr<CGImageRef> image;
403     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
404     ASSERT(uti);
405     RefPtr<ByteArray> arr;
406
407     if (CFEqual(uti.get(), jpegUTI())) {
408         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
409         arr = getPremultipliedImageData(IntRect(IntPoint(0, 0), m_size));
410
411         unsigned char *data = arr->data();
412         for (int i = 0; i < width() * height(); i++)
413             data[i * 4 + 3] = 255; // The image is already premultiplied, so we just need to make it opaque.
414
415         RetainPtr<CGDataProviderRef> dataProvider;
416     
417         dataProvider.adoptCF(CGDataProviderCreateWithData(0, data,
418                                                           4 * width() * height(), 0));
419     
420         if (!dataProvider)
421             return "data:,";
422
423         image.adoptCF(CGImageCreate(width(), height(), 8, 32, 4 * width(),
424                                     CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast,
425                                     dataProvider.get(), 0, false, kCGRenderingIntentDefault));
426     } else
427         image.adoptCF(copyNativeImage(CopyBackingStore));
428
429     if (!image)
430         return "data:,";
431
432     return CGImageToDataURL(image.get(), mimeType, quality);
433 }
434
435 String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
436 {
437     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
438         
439     RetainPtr<CGImageRef> image;
440     RetainPtr<CGDataProviderRef> dataProvider;
441
442     unsigned char* data = source.data()->data()->data();
443     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
444     ASSERT(uti);
445     Vector<uint8_t> dataVector;
446     if (CFEqual(uti.get(), jpegUTI())) {
447         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
448         dataVector.resize(4 * source.width() * source.height());
449         unsigned char *out = dataVector.data();
450         
451         for (int i = 0; i < source.width() * source.height(); i++) {
452             // Multiply color data by alpha, and set alpha to 255.
453             int alpha = data[4 * i + 3];
454             if (alpha != 255) {
455                 out[4 * i + 0] = data[4 * i + 0] * alpha / 255;
456                 out[4 * i + 1] = data[4 * i + 1] * alpha / 255;
457                 out[4 * i + 2] = data[4 * i + 2] * alpha / 255;
458             } else {
459                 out[4 * i + 0] = data[4 * i + 0];
460                 out[4 * i + 1] = data[4 * i + 1];
461                 out[4 * i + 2] = data[4 * i + 2];
462             }
463             out[4 * i + 3] = 255;
464         }
465
466         data = out;
467     }
468     
469     dataProvider.adoptCF(CGDataProviderCreateWithData(0, data,
470                                                       4 * source.width() * source.height(), 0));
471     
472     if (!dataProvider)
473         return "data:,";
474
475     image.adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
476                                 CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast,
477                                 dataProvider.get(), 0, false, kCGRenderingIntentDefault));
478                                 
479         
480     if (!image)
481         return "data:,";
482
483     return CGImageToDataURL(image.get(), mimeType, quality);
484 }
485 } // namespace WebCore