[CG] ImageBufferCG should handle IOSurface allocation failure gracefully
[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/MainThread.h>
41 #include <wtf/OwnArrayPtr.h>
42 #include <wtf/RetainPtr.h>
43 #include <wtf/UnusedParam.h>
44 #include <wtf/text/WTFString.h>
45
46 #if PLATFORM(MAC) || PLATFORM(CHROMIUM)
47 #include "WebCoreSystemInterface.h"
48 #endif
49
50 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
51 #include <IOSurface/IOSurface.h>
52 #endif
53
54 using namespace std;
55
56 namespace WebCore {
57
58 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
59 static const int maxIOSurfaceDimension = 4096;
60 static const int minIOSurfaceArea = 50 * 100;
61
62 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
63 {
64     unsigned pixelFormat = 'BGRA';
65     unsigned bytesPerElement = 4;
66     int width = size.width();
67     int height = size.height();
68
69     unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
70     if (!bytesPerRow)
71         return 0;
72
73     unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
74     if (!allocSize)
75         return 0;
76
77     const void *keys[6];
78     const void *values[6];
79     keys[0] = kIOSurfaceWidth;
80     values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
81     keys[1] = kIOSurfaceHeight;
82     values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
83     keys[2] = kIOSurfacePixelFormat;
84     values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
85     keys[3] = kIOSurfaceBytesPerElement;
86     values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
87     keys[4] = kIOSurfaceBytesPerRow;
88     values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
89     keys[5] = kIOSurfaceAllocSize;
90     values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
91
92     RetainPtr<CFDictionaryRef> dict(AdoptCF, CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
93     for (unsigned i = 0; i < 6; i++)
94         CFRelease(values[i]);
95
96     return RetainPtr<IOSurfaceRef>(AdoptCF, IOSurfaceCreate(dict.get()));
97 }
98 #endif
99
100 static void releaseImageData(void*, const void* data, size_t)
101 {
102     fastFree(const_cast<void*>(data));
103 }
104
105 ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
106     : m_data(size)
107     , m_size(size)
108     , m_accelerateRendering(renderingMode == Accelerated)
109 {
110     success = false;  // Make early return mean failure.
111     if (size.width() < 0 || size.height() < 0)
112         return;
113 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
114     if (size.width() >= maxIOSurfaceDimension || size.height() >= maxIOSurfaceDimension || size.width() * size.height() < minIOSurfaceArea)
115         m_accelerateRendering = false;
116 #else
117     ASSERT(renderingMode == Unaccelerated);
118 #endif
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 USE(IOSURFACE_CANVAS_BACKING_STORE)
142         m_data.m_surface = createIOSurface(size);
143         cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), size.width(), size.height(), m_data.m_colorSpace));
144 #endif
145         if (!cgContext)
146             m_accelerateRendering = false; // If allocation fails, fall back to non-accelerated path.
147     }
148
149     if (!m_accelerateRendering) {
150         if (!tryFastCalloc(size.height(), bytesPerRow).getValue(m_data.m_data))
151             return;
152         ASSERT(!(reinterpret_cast<size_t>(m_data.m_data) & 2));
153
154         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
155         cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, size.width(), size.height(), 8, bytesPerRow, m_data.m_colorSpace, m_data.m_bitmapInfo));
156         // Create a live image that wraps the data.
157         m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, dataSize, releaseImageData));
158     }
159
160     if (!cgContext)
161         return;
162
163     m_context= adoptPtr(new GraphicsContext(cgContext.get()));
164     m_context->scale(FloatSize(1, -1));
165     m_context->translate(0, -size.height());
166     success = true;
167 }
168
169 ImageBuffer::~ImageBuffer()
170 {
171 }
172
173 size_t ImageBuffer::dataSize() const
174 {
175     return m_size.height() * m_data.m_bytesPerRow;
176 }
177
178 GraphicsContext* ImageBuffer::context() const
179 {
180     return m_context.get();
181 }
182
183 PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior) const
184 {
185     RetainPtr<CGImageRef> image = copyNativeImage(copyBehavior);
186
187     if (!image)
188         return 0;
189
190     return BitmapImage::create(image.get());
191 }
192
193 NativeImagePtr ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
194 {
195     CGImageRef image = 0;
196     if (!m_accelerateRendering) {
197         switch (copyBehavior) {
198         case DontCopyBackingStore:
199             image = CGImageCreate(m_size.width(), m_size.height(), 8, 32, m_data.m_bytesPerRow, m_data.m_colorSpace, m_data.m_bitmapInfo, m_data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
200             break;
201         case CopyBackingStore:
202             image = CGBitmapContextCreateImage(context()->platformContext());
203             break;
204         default:
205             ASSERT_NOT_REACHED();
206             break;
207         }
208     }
209 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
210     else
211         image = wkIOSurfaceContextCreateImage(context()->platformContext());
212 #endif
213
214     return image;
215 }
216
217 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, bool useLowQualityScale)
218 {
219     UNUSED_PARAM(useLowQualityScale);
220     ColorSpace colorSpace = (destContext == m_context) ? ColorSpaceDeviceRGB : styleColorSpace;
221
222     RetainPtr<CGImageRef> image;
223     if (destContext == m_context)
224         image.adoptCF(copyNativeImage(CopyBackingStore)); // Drawing into our own buffer, need to deep copy.
225     else
226         image.adoptCF(copyNativeImage(DontCopyBackingStore));
227
228     destContext->drawNativeImage(image.get(), m_size, colorSpace, destRect, srcRect, op);
229 }
230
231 void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
232 {
233     if (!m_accelerateRendering) {
234         if (context == m_context) {
235             RefPtr<Image> copy = copyImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
236             copy->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
237         } else {
238             RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore);
239             imageForRendering->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
240         }
241     } else {
242         RefPtr<Image> copy = copyImage(CopyBackingStore);
243         copy->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
244     }
245 }
246
247 void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
248 {
249     CGContextRef platformContextToClip = contextToClip->platformContext();
250     RetainPtr<CGImageRef> image(AdoptCF, copyNativeImage(DontCopyBackingStore));
251     CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + rect.height());
252     CGContextScaleCTM(platformContextToClip, 1, -1);
253     CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), rect.size()), image.get());
254     CGContextScaleCTM(platformContextToClip, 1, -1);
255     CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
256 }
257
258 PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
259 {
260     if (m_accelerateRendering)
261         CGContextFlush(context()->platformContext());
262     return m_data.getData(rect, m_size, m_accelerateRendering, true);
263 }
264
265 PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
266 {
267     if (m_accelerateRendering)
268         CGContextFlush(context()->platformContext());
269     return m_data.getData(rect, m_size, m_accelerateRendering, false);
270 }
271
272 void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
273 {
274     if (m_accelerateRendering)
275         CGContextFlush(context()->platformContext());
276     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, true);
277 }
278
279 void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
280 {
281     if (m_accelerateRendering)
282         CGContextFlush(context()->platformContext());
283     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, false);
284 }
285
286 static inline CFStringRef jpegUTI()
287 {
288 #if PLATFORM(WIN)
289     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
290 #endif
291     return kUTTypeJPEG;
292 }
293     
294 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
295 {
296 #if PLATFORM(MAC)
297     RetainPtr<CFStringRef> mimeTypeCFString(AdoptCF, mimeType.createCFString());
298     return RetainPtr<CFStringRef>(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCFString.get(), 0));
299 #else
300     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
301
302     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
303     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
304     static const CFStringRef kUTTypePNG = CFSTR("public.png");
305     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
306
307     if (equalIgnoringCase(mimeType, "image/png"))
308         return kUTTypePNG;
309     if (equalIgnoringCase(mimeType, "image/jpeg"))
310         return jpegUTI();
311     if (equalIgnoringCase(mimeType, "image/gif"))
312         return kUTTypeGIF;
313
314     ASSERT_NOT_REACHED();
315     return kUTTypePNG;
316 #endif
317 }
318
319 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
320 {
321     RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
322     if (!data)
323         return "data:,";
324
325     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
326     ASSERT(uti);
327
328     RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
329     if (!destination)
330         return "data:,";
331
332     RetainPtr<CFDictionaryRef> imageProperties = 0;
333     if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
334         // Apply the compression quality to the image destination.
335         RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
336         const void* key = kCGImageDestinationLossyCompressionQuality;
337         const void* value = compressionQuality.get();
338         imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
339     }
340
341     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
342     CGImageDestinationFinalize(destination.get());
343
344     Vector<char> out;
345     base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
346
347     return "data:" + mimeType + ";base64," + out;
348 }
349
350 String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const
351 {
352     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
353
354     RetainPtr<CGImageRef> image(AdoptCF, copyNativeImage(CopyBackingStore));
355
356     if (!image)
357         return "data:,";
358
359     return CGImageToDataURL(image.get(), mimeType, quality);
360 }
361
362 String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
363 {
364     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
365         
366     RetainPtr<CGImageRef> image;
367     RetainPtr<CGDataProviderRef> dataProvider;
368     
369     dataProvider.adoptCF(CGDataProviderCreateWithData(0, source.data()->data()->data(),
370                                                       4 * source.width() * source.height(), 0));
371     
372     if (!dataProvider)
373         return "data:,";
374
375     image.adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
376                                 CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast,
377                                 dataProvider.get(), 0, false, kCGRenderingIntentDefault));
378                                 
379         
380     if (!image)
381         return "data:,";
382
383     return CGImageToDataURL(image.get(), mimeType, quality);
384 }
385 } // namespace WebCore