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 "Base64.h"
33#include "BitmapImage.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 "PlatformString.h"
42#include "RefPtrCairo.h"
43#include <cairo.h>
44#include <wtf/Vector.h>
45
46using namespace std;
47
48// Cairo doesn't provide a way to copy a cairo_surface_t.
49// See http://lists.cairographics.org/archives/cairo/2007-June/010877.html
50// Once cairo provides the way, use the function instead of this.
51static inline cairo_surface_t* copySurface(cairo_surface_t* surface)
52{
53    cairo_format_t format = cairo_image_surface_get_format(surface);
54    int width = cairo_image_surface_get_width(surface);
55    int height = cairo_image_surface_get_height(surface);
56    cairo_surface_t* newsurface = cairo_image_surface_create(format, width, height);
57
58    RefPtr<cairo_t> cr = adoptRef(cairo_create(newsurface));
59    cairo_set_source_surface(cr.get(), surface, 0, 0);
60    cairo_set_operator(cr.get(), CAIRO_OPERATOR_SOURCE);
61    cairo_paint(cr.get());
62
63    return newsurface;
64}
65
66namespace WebCore {
67
68ImageBufferData::ImageBufferData(const IntSize& size)
69    : m_surface(0)
70    , m_platformContext(0)
71{
72}
73
74ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace, RenderingMode, bool& success)
75    : m_data(size)
76    , m_size(size)
77{
78    success = false;  // Make early return mean error.
79    m_data.m_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
80                                                  size.width(),
81                                                  size.height());
82    if (cairo_surface_status(m_data.m_surface) != CAIRO_STATUS_SUCCESS)
83        return;  // create will notice we didn't set m_initialized and fail.
84
85    RefPtr<cairo_t> cr = adoptRef(cairo_create(m_data.m_surface));
86    m_data.m_platformContext.setCr(cr.get());
87    m_context.set(new GraphicsContext(&m_data.m_platformContext));
88    success = true;
89}
90
91ImageBuffer::~ImageBuffer()
92{
93    cairo_surface_destroy(m_data.m_surface);
94}
95
96size_t ImageBuffer::dataSize() const
97{
98    return m_size.width() * m_size.height() * 4;
99}
100
101GraphicsContext* ImageBuffer::context() const
102{
103    return m_context.get();
104}
105
106bool ImageBuffer::drawsUsingCopy() const
107{
108    return false;
109}
110
111PassRefPtr<Image> ImageBuffer::copyImage() const
112{
113    // BitmapImage will release the passed in surface on destruction
114    return BitmapImage::create(copySurface(m_data.m_surface));
115}
116
117void ImageBuffer::clip(GraphicsContext* context, const FloatRect& maskRect) const
118{
119    context->platformContext()->pushImageMask(m_data.m_surface, maskRect);
120}
121
122void ImageBuffer::draw(GraphicsContext* context, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
123                       CompositeOperator op , bool useLowQualityScale)
124{
125    // BitmapImage will release the passed in surface on destruction
126    RefPtr<Image> image = BitmapImage::create(cairo_surface_reference(m_data.m_surface));
127    context->drawImage(image.get(), styleColorSpace, destRect, srcRect, op, useLowQualityScale);
128}
129
130void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const AffineTransform& patternTransform,
131                              const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
132{
133    // BitmapImage will release the passed in surface on destruction
134    RefPtr<Image> image = BitmapImage::create(cairo_surface_reference(m_data.m_surface));
135    image->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
136}
137
138void ImageBuffer::platformTransformColorSpace(const Vector<int>& lookUpTable)
139{
140    ASSERT(cairo_surface_get_type(m_data.m_surface) == CAIRO_SURFACE_TYPE_IMAGE);
141
142    unsigned char* dataSrc = cairo_image_surface_get_data(m_data.m_surface);
143    int stride = cairo_image_surface_get_stride(m_data.m_surface);
144    for (int y = 0; y < m_size.height(); ++y) {
145        unsigned* row = reinterpret_cast<unsigned*>(dataSrc + stride * y);
146        for (int x = 0; x < m_size.width(); x++) {
147            unsigned* pixel = row + x;
148            Color pixelColor = colorFromPremultipliedARGB(*pixel);
149            pixelColor = Color(lookUpTable[pixelColor.red()],
150                               lookUpTable[pixelColor.green()],
151                               lookUpTable[pixelColor.blue()],
152                               pixelColor.alpha());
153            *pixel = premultipliedARGBFromColor(pixelColor);
154        }
155    }
156    cairo_surface_mark_dirty_rectangle (m_data.m_surface, 0, 0, m_size.width(), m_size.height());
157}
158
159template <Multiply multiplied>
160PassRefPtr<ByteArray> getImageData(const IntRect& rect, const ImageBufferData& data, const IntSize& size)
161{
162    ASSERT(cairo_surface_get_type(data.m_surface) == CAIRO_SURFACE_TYPE_IMAGE);
163
164    RefPtr<ByteArray> result = ByteArray::create(rect.width() * rect.height() * 4);
165    unsigned char* dataSrc = cairo_image_surface_get_data(data.m_surface);
166    unsigned char* dataDst = result->data();
167
168    if (rect.x() < 0 || rect.y() < 0 || (rect.x() + rect.width()) > size.width() || (rect.y() + rect.height()) > size.height())
169        memset(dataDst, 0, result->length());
170
171    int originx = rect.x();
172    int destx = 0;
173    if (originx < 0) {
174        destx = -originx;
175        originx = 0;
176    }
177    int endx = rect.maxX();
178    if (endx > size.width())
179        endx = size.width();
180    int numColumns = endx - originx;
181
182    int originy = rect.y();
183    int desty = 0;
184    if (originy < 0) {
185        desty = -originy;
186        originy = 0;
187    }
188    int endy = rect.maxY();
189    if (endy > size.height())
190        endy = size.height();
191    int numRows = endy - originy;
192
193    int stride = cairo_image_surface_get_stride(data.m_surface);
194    unsigned destBytesPerRow = 4 * rect.width();
195
196    unsigned char* destRows = dataDst + desty * destBytesPerRow + destx * 4;
197    for (int y = 0; y < numRows; ++y) {
198        unsigned* row = reinterpret_cast<unsigned*>(dataSrc + stride * (y + originy));
199        for (int x = 0; x < numColumns; x++) {
200            int basex = x * 4;
201            unsigned* pixel = row + x + originx;
202            Color pixelColor;
203            if (multiplied == Unmultiplied)
204                pixelColor = colorFromPremultipliedARGB(*pixel);
205            else
206                pixelColor = Color(*pixel);
207            destRows[basex]     = pixelColor.red();
208            destRows[basex + 1] = pixelColor.green();
209            destRows[basex + 2] = pixelColor.blue();
210            destRows[basex + 3] = pixelColor.alpha();
211        }
212        destRows += destBytesPerRow;
213    }
214
215    return result.release();
216}
217
218PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
219{
220    return getImageData<Unmultiplied>(rect, m_data, m_size);
221}
222
223PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
224{
225    return getImageData<Premultiplied>(rect, m_data, m_size);
226}
227
228template <Multiply multiplied>
229void putImageData(ByteArray*& source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, ImageBufferData& data, const IntSize& size)
230{
231    ASSERT(cairo_surface_get_type(data.m_surface) == CAIRO_SURFACE_TYPE_IMAGE);
232
233    unsigned char* dataDst = cairo_image_surface_get_data(data.m_surface);
234
235    ASSERT(sourceRect.width() > 0);
236    ASSERT(sourceRect.height() > 0);
237
238    int originx = sourceRect.x();
239    int destx = destPoint.x() + sourceRect.x();
240    ASSERT(destx >= 0);
241    ASSERT(destx < size.width());
242    ASSERT(originx >= 0);
243    ASSERT(originx <= sourceRect.maxX());
244
245    int endx = destPoint.x() + sourceRect.maxX();
246    ASSERT(endx <= size.width());
247
248    int numColumns = endx - destx;
249
250    int originy = sourceRect.y();
251    int desty = destPoint.y() + sourceRect.y();
252    ASSERT(desty >= 0);
253    ASSERT(desty < size.height());
254    ASSERT(originy >= 0);
255    ASSERT(originy <= sourceRect.maxY());
256
257    int endy = destPoint.y() + sourceRect.maxY();
258    ASSERT(endy <= size.height());
259    int numRows = endy - desty;
260
261    unsigned srcBytesPerRow = 4 * sourceSize.width();
262    int stride = cairo_image_surface_get_stride(data.m_surface);
263
264    unsigned char* srcRows = source->data() + originy * srcBytesPerRow + originx * 4;
265    for (int y = 0; y < numRows; ++y) {
266        unsigned* row = reinterpret_cast<unsigned*>(dataDst + stride * (y + desty));
267        for (int x = 0; x < numColumns; x++) {
268            int basex = x * 4;
269            unsigned* pixel = row + x + destx;
270            Color pixelColor(srcRows[basex],
271                    srcRows[basex + 1],
272                    srcRows[basex + 2],
273                    srcRows[basex + 3]);
274            if (multiplied == Unmultiplied)
275                *pixel = premultipliedARGBFromColor(pixelColor);
276            else
277                *pixel = pixelColor.rgb();
278        }
279        srcRows += srcBytesPerRow;
280    }
281    cairo_surface_mark_dirty_rectangle (data.m_surface,
282                                        destx, desty,
283                                        numColumns, numRows);
284}
285
286void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
287{
288    putImageData<Unmultiplied>(source, sourceSize, sourceRect, destPoint, m_data, m_size);
289}
290
291void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
292{
293    putImageData<Premultiplied>(source, sourceSize, sourceRect, destPoint, m_data, m_size);
294}
295
296#if !PLATFORM(GTK)
297static cairo_status_t writeFunction(void* closure, const unsigned char* data, unsigned int length)
298{
299    Vector<char>* in = reinterpret_cast<Vector<char>*>(closure);
300    in->append(data, length);
301    return CAIRO_STATUS_SUCCESS;
302}
303
304String ImageBuffer::toDataURL(const String& mimeType, const double*) const
305{
306    cairo_surface_t* image = cairo_get_target(context()->platformContext()->cr());
307    if (!image)
308        return "data:,";
309
310    String actualMimeType("image/png");
311    if (MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType))
312        actualMimeType = mimeType;
313
314    Vector<char> in;
315    // Only PNG output is supported for now.
316    cairo_surface_write_to_png_stream(image, writeFunction, &in);
317
318    Vector<char> out;
319    base64Encode(in, out);
320
321    return "data:" + actualMimeType + ";base64," + String(out.data(), out.size());
322}
323#endif
324
325} // namespace WebCore
326