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