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