[Cairo] Canvas putImageData is not working as expected
[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 "MIMETypeRegistry.h"
37 #include "NotImplemented.h"
38 #include "Pattern.h"
39 #include "PlatformContextCairo.h"
40 #include "RefPtrCairo.h"
41 #include <cairo.h>
42 #include <wtf/Vector.h>
43 #include <wtf/text/Base64.h>
44 #include <wtf/text/WTFString.h>
45
46 #if ENABLE(ACCELERATED_2D_CANVAS)
47 #include "GLContext.h"
48 #include "OpenGLShims.h"
49 #include "TextureMapperGL.h"
50 #include <cairo-gl.h>
51 #endif
52
53 using namespace std;
54
55 namespace WebCore {
56
57 ImageBufferData::ImageBufferData(const IntSize& size)
58     : m_platformContext(0)
59     , m_size(size)
60 #if ENABLE(ACCELERATED_2D_CANVAS)
61     , m_texture(0)
62 #endif
63 {
64 }
65
66 #if ENABLE(ACCELERATED_2D_CANVAS)
67 PassRefPtr<cairo_surface_t> createCairoGLSurface(const IntSize& size, uint32_t& texture)
68 {
69     GLContext::sharingContext()->makeContextCurrent();
70
71     // We must generate the texture ourselves, because there is no Cairo API for extracting it
72     // from a pre-existing surface.
73     glGenTextures(1, &texture);
74     glBindTexture(GL_TEXTURE_2D, texture);
75     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
76     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
77     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
78     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
79
80     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
81
82     glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA8, size.width(), size.height(), 0 /* border */, GL_RGBA, GL_UNSIGNED_BYTE, 0);
83
84     GLContext* context = GLContext::sharingContext();
85     cairo_device_t* device = context->cairoDevice();
86
87     // Thread-awareness is a huge performance hit on non-Intel drivers.
88     cairo_gl_device_set_thread_aware(device, FALSE);
89
90     return adoptRef(cairo_gl_surface_create_for_texture(device, CAIRO_CONTENT_COLOR_ALPHA, texture, size.width(), size.height()));
91 }
92 #endif
93
94 ImageBuffer::ImageBuffer(const IntSize& size, float /* resolutionScale */, ColorSpace, RenderingMode renderingMode, bool& success)
95     : m_data(size)
96     , m_size(size)
97     , m_logicalSize(size)
98 {
99     success = false;  // Make early return mean error.
100
101 #if ENABLE(ACCELERATED_2D_CANVAS)
102     if (renderingMode == Accelerated)
103         m_data.m_surface = createCairoGLSurface(size, m_data.m_texture);
104     else
105 #endif
106         m_data.m_surface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.width(), size.height()));
107
108     if (cairo_surface_status(m_data.m_surface.get()) != CAIRO_STATUS_SUCCESS)
109         return;  // create will notice we didn't set m_initialized and fail.
110
111     RefPtr<cairo_t> cr = adoptRef(cairo_create(m_data.m_surface.get()));
112     m_data.m_platformContext.setCr(cr.get());
113     m_context = adoptPtr(new GraphicsContext(&m_data.m_platformContext));
114     success = true;
115 }
116
117 ImageBuffer::~ImageBuffer()
118 {
119 }
120
121 GraphicsContext* ImageBuffer::context() const
122 {
123     return m_context.get();
124 }
125
126 PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior) const
127 {
128     if (copyBehavior == CopyBackingStore)
129         return BitmapImage::create(copyCairoImageSurface(m_data.m_surface.get()));
130
131     // BitmapImage will release the passed in surface on destruction
132     return BitmapImage::create(m_data.m_surface);
133 }
134
135 BackingStoreCopy ImageBuffer::fastCopyImageMode()
136 {
137     return DontCopyBackingStore;
138 }
139
140 void ImageBuffer::clip(GraphicsContext* context, const FloatRect& maskRect) const
141 {
142     context->platformContext()->pushImageMask(m_data.m_surface.get(), maskRect);
143 }
144
145 void ImageBuffer::draw(GraphicsContext* destinationContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
146     CompositeOperator op, BlendMode blendMode, bool useLowQualityScale)
147 {
148     BackingStoreCopy copyMode = destinationContext == context() ? CopyBackingStore : DontCopyBackingStore;
149     RefPtr<Image> image = copyImage(copyMode);
150     destinationContext->drawImage(image.get(), styleColorSpace, destRect, srcRect, op, blendMode, ImageOrientationDescription(), useLowQualityScale);
151 }
152
153 void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const AffineTransform& patternTransform,
154                               const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
155 {
156     RefPtr<Image> image = copyImage(DontCopyBackingStore);
157     image->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
158 }
159
160 void ImageBuffer::platformTransformColorSpace(const Vector<int>& lookUpTable)
161 {
162     // FIXME: Enable color space conversions on accelerated canvases.
163     if (cairo_surface_get_type(m_data.m_surface.get()) != CAIRO_SURFACE_TYPE_IMAGE)
164         return;
165
166     unsigned char* dataSrc = cairo_image_surface_get_data(m_data.m_surface.get());
167     int stride = cairo_image_surface_get_stride(m_data.m_surface.get());
168     for (int y = 0; y < m_size.height(); ++y) {
169         unsigned* row = reinterpret_cast_ptr<unsigned*>(dataSrc + stride * y);
170         for (int x = 0; x < m_size.width(); x++) {
171             unsigned* pixel = row + x;
172             Color pixelColor = colorFromPremultipliedARGB(*pixel);
173             pixelColor = Color(lookUpTable[pixelColor.red()],
174                                lookUpTable[pixelColor.green()],
175                                lookUpTable[pixelColor.blue()],
176                                pixelColor.alpha());
177             *pixel = premultipliedARGBFromColor(pixelColor);
178         }
179     }
180     cairo_surface_mark_dirty_rectangle(m_data.m_surface.get(), 0, 0, m_size.width(), m_size.height());
181 }
182
183 PassRefPtr<cairo_surface_t> copySurfaceToImageAndAdjustRect(cairo_surface_t* surface, IntRect& rect)
184 {
185     cairo_surface_type_t surfaceType = cairo_surface_get_type(surface);
186
187     // If we already have an image, we write directly to the underlying data;
188     // otherwise we create a temporary surface image
189     if (surfaceType == CAIRO_SURFACE_TYPE_IMAGE)
190         return surface;
191     
192     rect.setX(0);
193     rect.setY(0);
194     return adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, rect.width(), rect.height()));
195 }
196
197 template <Multiply multiplied>
198 PassRefPtr<Uint8ClampedArray> getImageData(const IntRect& rect, const ImageBufferData& data, const IntSize& size)
199 {
200     RefPtr<Uint8ClampedArray> result = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4);
201
202     if (rect.x() < 0 || rect.y() < 0 || (rect.x() + rect.width()) > size.width() || (rect.y() + rect.height()) > size.height())
203         result->zeroFill();
204
205     int originx = rect.x();
206     int destx = 0;
207     if (originx < 0) {
208         destx = -originx;
209         originx = 0;
210     }
211     int endx = rect.maxX();
212     if (endx > size.width())
213         endx = size.width();
214     int numColumns = endx - originx;
215
216     int originy = rect.y();
217     int desty = 0;
218     if (originy < 0) {
219         desty = -originy;
220         originy = 0;
221     }
222     int endy = rect.maxY();
223     if (endy > size.height())
224         endy = size.height();
225     int numRows = endy - originy;
226
227     IntRect imageRect(originx, originy, numColumns, numRows);
228     RefPtr<cairo_surface_t> imageSurface = copySurfaceToImageAndAdjustRect(data.m_surface.get(), imageRect);
229     originx = imageRect.x();
230     originy = imageRect.y();
231     if (imageSurface != data.m_surface.get()) {
232         IntRect area = intersection(rect, IntRect(0, 0, size.width(), size.height()));
233         copyRectFromOneSurfaceToAnother(data.m_surface.get(), imageSurface.get(), IntSize(-area.x(), -area.y()), IntRect(IntPoint(), area.size()), IntSize(), CAIRO_OPERATOR_SOURCE);
234     }
235
236     unsigned char* dataSrc = cairo_image_surface_get_data(imageSurface.get());
237     unsigned char* dataDst = result->data();
238     int stride = cairo_image_surface_get_stride(imageSurface.get());
239     unsigned destBytesPerRow = 4 * rect.width();
240
241     unsigned char* destRows = dataDst + desty * destBytesPerRow + destx * 4;
242     for (int y = 0; y < numRows; ++y) {
243         unsigned* row = reinterpret_cast_ptr<unsigned*>(dataSrc + stride * (y + originy));
244         for (int x = 0; x < numColumns; x++) {
245             int basex = x * 4;
246             unsigned* pixel = row + x + originx;
247
248             // Avoid calling Color::colorFromPremultipliedARGB() because one
249             // function call per pixel is too expensive.
250             unsigned alpha = (*pixel & 0xFF000000) >> 24;
251             unsigned red = (*pixel & 0x00FF0000) >> 16;
252             unsigned green = (*pixel & 0x0000FF00) >> 8;
253             unsigned blue = (*pixel & 0x000000FF);
254
255             if (multiplied == Unmultiplied) {
256                 if (alpha && alpha != 255) {
257                     red = red * 255 / alpha;
258                     green = green * 255 / alpha;
259                     blue = blue * 255 / alpha;
260                 }
261             }
262
263             destRows[basex]     = red;
264             destRows[basex + 1] = green;
265             destRows[basex + 2] = blue;
266             destRows[basex + 3] = alpha;
267         }
268         destRows += destBytesPerRow;
269     }
270
271     return result.release();
272 }
273
274 PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem) const
275 {
276     return getImageData<Unmultiplied>(rect, m_data, m_size);
277 }
278
279 PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem) const
280 {
281     return getImageData<Premultiplied>(rect, m_data, m_size);
282 }
283
284 void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem)
285 {
286
287     ASSERT(sourceRect.width() > 0);
288     ASSERT(sourceRect.height() > 0);
289
290     int originx = sourceRect.x();
291     int destx = destPoint.x() + sourceRect.x();
292     ASSERT(destx >= 0);
293     ASSERT(destx < m_size.width());
294     ASSERT(originx >= 0);
295     ASSERT(originx <= sourceRect.maxX());
296
297     int endx = destPoint.x() + sourceRect.maxX();
298     ASSERT(endx <= m_size.width());
299
300     int numColumns = endx - destx;
301
302     int originy = sourceRect.y();
303     int desty = destPoint.y() + sourceRect.y();
304     ASSERT(desty >= 0);
305     ASSERT(desty < m_size.height());
306     ASSERT(originy >= 0);
307     ASSERT(originy <= sourceRect.maxY());
308
309     int endy = destPoint.y() + sourceRect.maxY();
310     ASSERT(endy <= m_size.height());
311     int numRows = endy - desty;
312
313     IntRect imageRect(destx, desty, numColumns, numRows);
314     RefPtr<cairo_surface_t> imageSurface = copySurfaceToImageAndAdjustRect(m_data.m_surface.get(), imageRect);
315     destx = imageRect.x();
316     desty = imageRect.y();
317
318     unsigned char* pixelData = cairo_image_surface_get_data(imageSurface.get());
319
320     unsigned srcBytesPerRow = 4 * sourceSize.width();
321     int stride = cairo_image_surface_get_stride(imageSurface.get());
322
323     unsigned char* srcRows = source->data() + originy * srcBytesPerRow + originx * 4;
324     for (int y = 0; y < numRows; ++y) {
325         unsigned* row = reinterpret_cast_ptr<unsigned*>(pixelData + stride * (y + desty));
326         for (int x = 0; x < numColumns; x++) {
327             int basex = x * 4;
328             unsigned* pixel = row + x + destx;
329
330             // Avoid calling Color::premultipliedARGBFromColor() because one
331             // function call per pixel is too expensive.
332             unsigned red = srcRows[basex];
333             unsigned green = srcRows[basex + 1];
334             unsigned blue = srcRows[basex + 2];
335             unsigned alpha = srcRows[basex + 3];
336
337             if (multiplied == Unmultiplied) {
338                 if (alpha != 255) {
339                     red = (red * alpha + 254) / 255;
340                     green = (green * alpha + 254) / 255;
341                     blue = (blue * alpha + 254) / 255;
342                 }
343             }
344
345             *pixel = (alpha << 24) | red  << 16 | green  << 8 | blue;
346         }
347         srcRows += srcBytesPerRow;
348     }
349
350     cairo_surface_mark_dirty_rectangle(imageSurface.get(), destx, desty, numColumns, numRows);
351
352     if (imageSurface != m_data.m_surface.get())
353         copyRectFromOneSurfaceToAnother(imageSurface.get(), m_data.m_surface.get(), IntSize(), IntRect(0, 0, numColumns, numRows), IntSize(destPoint.x() + sourceRect.x(), destPoint.y() + sourceRect.y()), CAIRO_OPERATOR_SOURCE);
354 }
355
356 #if !PLATFORM(GTK)
357 static cairo_status_t writeFunction(void* output, const unsigned char* data, unsigned int length)
358 {
359     if (!reinterpret_cast<Vector<unsigned char>*>(output)->tryAppend(data, length))
360         return CAIRO_STATUS_WRITE_ERROR;
361     return CAIRO_STATUS_SUCCESS;
362 }
363
364 static bool encodeImage(cairo_surface_t* image, const String& mimeType, Vector<char>* output)
365 {
366     ASSERT_UNUSED(mimeType, mimeType == "image/png"); // Only PNG output is supported for now.
367
368     return cairo_surface_write_to_png_stream(image, writeFunction, output) == CAIRO_STATUS_SUCCESS;
369 }
370
371 String ImageBuffer::toDataURL(const String& mimeType, const double*, CoordinateSystem) const
372 {
373     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
374
375     cairo_surface_t* image = cairo_get_target(context()->platformContext()->cr());
376
377     Vector<char> encodedImage;
378     if (!image || !encodeImage(image, mimeType, &encodedImage))
379         return "data:,";
380
381     Vector<char> base64Data;
382     base64Encode(encodedImage, base64Data);
383
384     return "data:" + mimeType + ";base64," + base64Data;
385 }
386 #endif
387
388 #if ENABLE(ACCELERATED_2D_CANVAS)
389 void ImageBufferData::paintToTextureMapper(TextureMapper* textureMapper, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity)
390 {
391     if (textureMapper->accelerationMode() != TextureMapper::OpenGLMode) {
392         notImplemented();
393         return;
394     }
395
396     ASSERT(m_texture);
397
398     // Cairo may change the active context, so we make sure to change it back after flushing.
399     GLContext* previousActiveContext = GLContext::getCurrent();
400     cairo_surface_flush(m_surface.get());
401     previousActiveContext->makeContextCurrent();
402
403     static_cast<TextureMapperGL*>(textureMapper)->drawTexture(m_texture, TextureMapperGL::ShouldBlend, m_size, targetRect, matrix, opacity);
404 }
405 #endif
406
407 #if USE(ACCELERATED_COMPOSITING)
408 PlatformLayer* ImageBuffer::platformLayer() const
409 {
410 #if ENABLE(ACCELERATED_2D_CANVAS)
411     if (m_data.m_texture)
412         return const_cast<ImageBufferData*>(&m_data);
413 #endif
414     return 0;
415 }
416 #endif
417
418 } // namespace WebCore