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