[GTK] Add accelerated 2D canvas support using cairo-gl
[WebKit-https.git] / Source / WebCore / platform / graphics / cairo / ImageBufferCairo.cpp
1 /*
2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2007 Holger Hans Peter Freyther <zecke@selfish.org>
4  * Copyright (C) 2008, 2009 Dirk Schulze <krit@webkit.org>
5  * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "ImageBuffer.h"
31
32 #include "BitmapImage.h"
33 #include "CairoUtilities.h"
34 #include "Color.h"
35 #include "GraphicsContext.h"
36 #include "ImageData.h"
37 #include "MIMETypeRegistry.h"
38 #include "NotImplemented.h"
39 #include "Pattern.h"
40 #include "PlatformContextCairo.h"
41 #include "RefPtrCairo.h"
42 #include <cairo.h>
43 #include <wtf/Vector.h>
44 #include <wtf/text/Base64.h>
45 #include <wtf/text/WTFString.h>
46
47 #if ENABLE(ACCELERATED_2D_CANVAS)
48 #include "GLContext.h"
49 #include "OpenGLShims.h"
50 #include "TextureMapperGL.h"
51 #include <cairo-gl.h>
52 #endif
53
54 using namespace std;
55
56 namespace WebCore {
57
58 ImageBufferData::ImageBufferData(const IntSize& size)
59     : m_platformContext(0)
60     , m_size(size)
61 #if ENABLE(ACCELERATED_2D_CANVAS)
62     , m_texture(0)
63 #endif
64 {
65 }
66
67 #if ENABLE(ACCELERATED_2D_CANVAS)
68 PassRefPtr<cairo_surface_t> createCairoGLSurface(const IntSize& size, uint32_t& texture)
69 {
70     GLContext::sharingContext()->makeContextCurrent();
71
72     // We must generate the texture ourselves, because there is no Cairo API for extracting it
73     // from a pre-existing surface.
74     glGenTextures(1, &texture);
75     glBindTexture(GL_TEXTURE_2D, texture);
76     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
77     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
78     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
79     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
80
81     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
82
83     glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA8, size.width(), size.height(), 0 /* border */, GL_RGBA, GL_UNSIGNED_BYTE, 0);
84
85     GLContext* context = GLContext::sharingContext();
86     cairo_device_t* device = context->cairoDevice();
87
88     // Thread-awareness is a huge performance hit on non-Intel drivers.
89     cairo_gl_device_set_thread_aware(device, FALSE);
90
91     return adoptRef(cairo_gl_surface_create_for_texture(device, CAIRO_CONTENT_COLOR_ALPHA, texture, size.width(), size.height()));
92 }
93 #endif
94
95 ImageBuffer::ImageBuffer(const IntSize& size, float /* resolutionScale */, ColorSpace, RenderingMode renderingMode, bool& success)
96     : m_data(size)
97     , m_size(size)
98     , m_logicalSize(size)
99 {
100     success = false;  // Make early return mean error.
101
102 #if ENABLE(ACCELERATED_2D_CANVAS)
103     if (renderingMode == Accelerated)
104         m_data.m_surface = createCairoGLSurface(size, m_data.m_texture);
105     else
106 #endif
107         m_data.m_surface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.width(), size.height()));
108
109     if (cairo_surface_status(m_data.m_surface.get()) != CAIRO_STATUS_SUCCESS)
110         return;  // create will notice we didn't set m_initialized and fail.
111
112     RefPtr<cairo_t> cr = adoptRef(cairo_create(m_data.m_surface.get()));
113     m_data.m_platformContext.setCr(cr.get());
114     m_context = adoptPtr(new GraphicsContext(&m_data.m_platformContext));
115     success = true;
116 }
117
118 ImageBuffer::~ImageBuffer()
119 {
120 }
121
122 GraphicsContext* ImageBuffer::context() const
123 {
124     return m_context.get();
125 }
126
127 PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior) const
128 {
129     if (copyBehavior == CopyBackingStore)
130         return BitmapImage::create(copyCairoImageSurface(m_data.m_surface.get()));
131
132     // BitmapImage will release the passed in surface on destruction
133     return BitmapImage::create(m_data.m_surface);
134 }
135
136 BackingStoreCopy ImageBuffer::fastCopyImageMode()
137 {
138     return DontCopyBackingStore;
139 }
140
141 void ImageBuffer::clip(GraphicsContext* context, const FloatRect& maskRect) const
142 {
143     context->platformContext()->pushImageMask(m_data.m_surface.get(), maskRect);
144 }
145
146 void ImageBuffer::draw(GraphicsContext* destinationContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
147     CompositeOperator op, BlendMode blendMode, bool useLowQualityScale)
148 {
149     BackingStoreCopy copyMode = destinationContext == context() ? CopyBackingStore : DontCopyBackingStore;
150     RefPtr<Image> image = copyImage(copyMode);
151     destinationContext->drawImage(image.get(), styleColorSpace, destRect, srcRect, op, blendMode, DoNotRespectImageOrientation, useLowQualityScale);
152 }
153
154 void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const AffineTransform& patternTransform,
155                               const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
156 {
157     RefPtr<Image> image = copyImage(DontCopyBackingStore);
158     image->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
159 }
160
161 void ImageBuffer::platformTransformColorSpace(const Vector<int>& lookUpTable)
162 {
163     // FIXME: Enable color space conversions on accelerated canvases.
164     if (cairo_surface_get_type(m_data.m_surface.get()) != CAIRO_SURFACE_TYPE_IMAGE)
165         return;
166
167     unsigned char* dataSrc = cairo_image_surface_get_data(m_data.m_surface.get());
168     int stride = cairo_image_surface_get_stride(m_data.m_surface.get());
169     for (int y = 0; y < m_size.height(); ++y) {
170         unsigned* row = reinterpret_cast<unsigned*>(dataSrc + stride * y);
171         for (int x = 0; x < m_size.width(); x++) {
172             unsigned* pixel = row + x;
173             Color pixelColor = colorFromPremultipliedARGB(*pixel);
174             pixelColor = Color(lookUpTable[pixelColor.red()],
175                                lookUpTable[pixelColor.green()],
176                                lookUpTable[pixelColor.blue()],
177                                pixelColor.alpha());
178             *pixel = premultipliedARGBFromColor(pixelColor);
179         }
180     }
181     cairo_surface_mark_dirty_rectangle(m_data.m_surface.get(), 0, 0, m_size.width(), m_size.height());
182 }
183
184 static cairo_surface_t* mapSurfaceToImage(cairo_surface_t* surface, const IntSize& size)
185 {
186     if (cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE)
187         return surface;
188
189     cairo_surface_t* imageSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.width(), size.height());
190     RefPtr<cairo_t> cr = adoptRef(cairo_create(imageSurface));
191     cairo_set_source_surface(cr.get(), surface, 0, 0);
192     cairo_paint(cr.get());
193     return imageSurface;
194 }
195
196 static void unmapSurfaceFromImage(cairo_surface_t* surface, cairo_surface_t* imageSurface, const IntRect& dirtyRectangle = IntRect())
197 {
198     if (surface == imageSurface && dirtyRectangle.isEmpty())
199         return;
200
201     if (dirtyRectangle.isEmpty()) {
202         cairo_surface_destroy(imageSurface);
203         return;
204     }
205
206     if (surface == imageSurface) {
207         cairo_surface_mark_dirty_rectangle(surface, dirtyRectangle.x(), dirtyRectangle.y(), dirtyRectangle.width(), dirtyRectangle.height());
208         return;
209     }
210
211     RefPtr<cairo_t> cr = adoptRef(cairo_create(surface));
212     cairo_set_source_surface(cr.get(), imageSurface, 0, 0);
213     cairo_rectangle(cr.get(), dirtyRectangle.x(), dirtyRectangle.y(), dirtyRectangle.width(), dirtyRectangle.height());
214     cairo_fill(cr.get());
215     cairo_surface_destroy(imageSurface);
216 }
217
218 template <Multiply multiplied>
219 PassRefPtr<Uint8ClampedArray> getImageData(const IntRect& rect, const ImageBufferData& data, const IntSize& size)
220 {
221     RefPtr<Uint8ClampedArray> result = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4);
222     cairo_surface_t* imageSurface = mapSurfaceToImage(data.m_surface.get(), size);
223     unsigned char* dataSrc = cairo_image_surface_get_data(imageSurface);
224     unsigned char* dataDst = result->data();
225
226     if (rect.x() < 0 || rect.y() < 0 || (rect.x() + rect.width()) > size.width() || (rect.y() + rect.height()) > size.height())
227         result->zeroFill();
228
229     int originx = rect.x();
230     int destx = 0;
231     if (originx < 0) {
232         destx = -originx;
233         originx = 0;
234     }
235     int endx = rect.maxX();
236     if (endx > size.width())
237         endx = size.width();
238     int numColumns = endx - originx;
239
240     int originy = rect.y();
241     int desty = 0;
242     if (originy < 0) {
243         desty = -originy;
244         originy = 0;
245     }
246     int endy = rect.maxY();
247     if (endy > size.height())
248         endy = size.height();
249     int numRows = endy - originy;
250
251     int stride = cairo_image_surface_get_stride(imageSurface);
252     unsigned destBytesPerRow = 4 * rect.width();
253
254     unsigned char* destRows = dataDst + desty * destBytesPerRow + destx * 4;
255     for (int y = 0; y < numRows; ++y) {
256         unsigned* row = reinterpret_cast<unsigned*>(dataSrc + stride * (y + originy));
257         for (int x = 0; x < numColumns; x++) {
258             int basex = x * 4;
259             unsigned* pixel = row + x + originx;
260
261             // Avoid calling Color::colorFromPremultipliedARGB() because one
262             // function call per pixel is too expensive.
263             unsigned alpha = (*pixel & 0xFF000000) >> 24;
264             unsigned red = (*pixel & 0x00FF0000) >> 16;
265             unsigned green = (*pixel & 0x0000FF00) >> 8;
266             unsigned blue = (*pixel & 0x000000FF);
267
268             if (multiplied == Unmultiplied) {
269                 if (alpha && alpha != 255) {
270                     red = red * 255 / alpha;
271                     green = green * 255 / alpha;
272                     blue = blue * 255 / alpha;
273                 }
274             }
275
276             destRows[basex]     = red;
277             destRows[basex + 1] = green;
278             destRows[basex + 2] = blue;
279             destRows[basex + 3] = alpha;
280         }
281         destRows += destBytesPerRow;
282     }
283
284     unmapSurfaceFromImage(data.m_surface.get(), imageSurface);
285     return result.release();
286 }
287
288 PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem) const
289 {
290     return getImageData<Unmultiplied>(rect, m_data, m_size);
291 }
292
293 PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem) const
294 {
295     return getImageData<Premultiplied>(rect, m_data, m_size);
296 }
297
298 void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem)
299 {
300     cairo_surface_t* imageSurface = mapSurfaceToImage(m_data.m_surface.get(), sourceSize);
301     unsigned char* dataDst = cairo_image_surface_get_data(imageSurface);
302
303     ASSERT(sourceRect.width() > 0);
304     ASSERT(sourceRect.height() > 0);
305
306     int originx = sourceRect.x();
307     int destx = destPoint.x() + sourceRect.x();
308     ASSERT(destx >= 0);
309     ASSERT(destx < m_size.width());
310     ASSERT(originx >= 0);
311     ASSERT(originx <= sourceRect.maxX());
312
313     int endx = destPoint.x() + sourceRect.maxX();
314     ASSERT(endx <= m_size.width());
315
316     int numColumns = endx - destx;
317
318     int originy = sourceRect.y();
319     int desty = destPoint.y() + sourceRect.y();
320     ASSERT(desty >= 0);
321     ASSERT(desty < m_size.height());
322     ASSERT(originy >= 0);
323     ASSERT(originy <= sourceRect.maxY());
324
325     int endy = destPoint.y() + sourceRect.maxY();
326     ASSERT(endy <= m_size.height());
327     int numRows = endy - desty;
328
329     unsigned srcBytesPerRow = 4 * sourceSize.width();
330     int stride = cairo_image_surface_get_stride(imageSurface);
331
332     unsigned char* srcRows = source->data() + originy * srcBytesPerRow + originx * 4;
333     for (int y = 0; y < numRows; ++y) {
334         unsigned* row = reinterpret_cast<unsigned*>(dataDst + stride * (y + desty));
335         for (int x = 0; x < numColumns; x++) {
336             int basex = x * 4;
337             unsigned* pixel = row + x + destx;
338
339             // Avoid calling Color::premultipliedARGBFromColor() because one
340             // function call per pixel is too expensive.
341             unsigned red = srcRows[basex];
342             unsigned green = srcRows[basex + 1];
343             unsigned blue = srcRows[basex + 2];
344             unsigned alpha = srcRows[basex + 3];
345
346             if (multiplied == Unmultiplied) {
347                 if (alpha && alpha != 255) {
348                     red = (red * alpha + 254) / 255;
349                     green = (green * alpha + 254) / 255;
350                     blue = (blue * alpha + 254) / 255;
351                 }
352             }
353
354             *pixel = (alpha << 24) | red  << 16 | green  << 8 | blue;
355         }
356         srcRows += srcBytesPerRow;
357     }
358
359     unmapSurfaceFromImage(m_data.m_surface.get(), imageSurface, IntRect(destx, desty, numColumns, numRows));
360 }
361
362 #if !PLATFORM(GTK)
363 static cairo_status_t writeFunction(void* output, const unsigned char* data, unsigned int length)
364 {
365     if (!reinterpret_cast<Vector<unsigned char>*>(output)->tryAppend(data, length))
366         return CAIRO_STATUS_WRITE_ERROR;
367     return CAIRO_STATUS_SUCCESS;
368 }
369
370 static bool encodeImage(cairo_surface_t* image, const String& mimeType, Vector<char>* output)
371 {
372     ASSERT_UNUSED(mimeType, mimeType == "image/png"); // Only PNG output is supported for now.
373
374     return cairo_surface_write_to_png_stream(image, writeFunction, output) == CAIRO_STATUS_SUCCESS;
375 }
376
377 String ImageBuffer::toDataURL(const String& mimeType, const double*, CoordinateSystem) const
378 {
379     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
380
381     cairo_surface_t* image = cairo_get_target(context()->platformContext()->cr());
382
383     Vector<char> encodedImage;
384     if (!image || !encodeImage(image, mimeType, &encodedImage))
385         return "data:,";
386
387     Vector<char> base64Data;
388     base64Encode(encodedImage, base64Data);
389
390     return "data:" + mimeType + ";base64," + base64Data;
391 }
392 #endif
393
394 #if ENABLE(ACCELERATED_2D_CANVAS)
395 void ImageBufferData::paintToTextureMapper(TextureMapper* textureMapper, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity)
396 {
397     if (textureMapper->accelerationMode() != TextureMapper::OpenGLMode) {
398         notImplemented();
399         return;
400     }
401
402     ASSERT(m_texture);
403
404     // Cairo may change the active context, so we make sure to change it back after flushing.
405     GLContext* previousActiveContext = GLContext::getCurrent();
406     cairo_surface_flush(m_surface.get());
407     previousActiveContext->makeContextCurrent();
408
409     static_cast<TextureMapperGL*>(textureMapper)->drawTexture(m_texture, TextureMapperGL::ShouldBlend, m_size, targetRect, matrix, opacity);
410 }
411 #endif
412
413 PlatformLayer* ImageBuffer::platformLayer() const
414 {
415 #if ENABLE(ACCELERATED_2D_CANVAS)
416     if (m_data.m_texture)
417         return const_cast<ImageBufferData*>(&m_data);
418 #endif
419     return 0;
420 }
421
422 } // namespace WebCore