1/*
2 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2008 Holger Hans Peter Freyther
4 * Copyright (C) 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 "GraphicsContext.h"
33#include "ImageData.h"
34#include "MIMETypeRegistry.h"
35#include "StillImageQt.h"
36#include "TransparencyLayer.h"
37#include <wtf/text/CString.h>
38#include <wtf/text/WTFString.h>
39
40#include <QBuffer>
41#include <QColor>
42#include <QImage>
43#include <QImageWriter>
44#include <QPainter>
45#include <QPixmap>
46#include <math.h>
47
48namespace WebCore {
49
50ImageBufferData::ImageBufferData(const IntSize& size)
51    : m_pixmap(size)
52{
53    if (m_pixmap.isNull())
54        return;
55
56    m_pixmap.fill(QColor(Qt::transparent));
57
58    QPainter* painter = new QPainter;
59    m_painter = adoptPtr(painter);
60
61    if (!painter->begin(&m_pixmap))
62        return;
63
64    // Since ImageBuffer is used mainly for Canvas, explicitly initialize
65    // its painter's pen and brush with the corresponding canvas defaults
66    // NOTE: keep in sync with CanvasRenderingContext2D::State
67    QPen pen = painter->pen();
68    pen.setColor(Qt::black);
69    pen.setWidth(1);
70    pen.setCapStyle(Qt::FlatCap);
71    pen.setJoinStyle(Qt::SvgMiterJoin);
72    pen.setMiterLimit(10);
73    painter->setPen(pen);
74    QBrush brush = painter->brush();
75    brush.setColor(Qt::black);
76    painter->setBrush(brush);
77    painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
78
79    m_image = StillImage::createForRendering(&m_pixmap);
80}
81
82QImage ImageBufferData::toQImage() const
83{
84    QPaintEngine* paintEngine = m_pixmap.paintEngine();
85    if (!paintEngine || paintEngine->type() != QPaintEngine::Raster)
86        return m_pixmap.toImage();
87
88    // QRasterPixmapData::toImage() will deep-copy the backing QImage if there's an active QPainter on it.
89    // For performance reasons, we don't want that here, so we temporarily redirect the paint engine.
90    QPaintDevice* currentPaintDevice = paintEngine->paintDevice();
91    paintEngine->setPaintDevice(0);
92    QImage image = m_pixmap.toImage();
93    paintEngine->setPaintDevice(currentPaintDevice);
94    return image;
95}
96
97ImageBuffer::ImageBuffer(const IntSize& size, float /* resolutionScale */, ColorSpace, RenderingMode, bool& success)
98    : m_data(size)
99    , m_size(size)
100    , m_logicalSize(size)
101{
102    success = m_data.m_painter && m_data.m_painter->isActive();
103    if (!success)
104        return;
105
106    m_context = adoptPtr(new GraphicsContext(m_data.m_painter.get()));
107}
108
109ImageBuffer::~ImageBuffer()
110{
111}
112
113GraphicsContext* ImageBuffer::context() const
114{
115    ASSERT(m_data.m_painter->isActive());
116
117    return m_context.get();
118}
119
120PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior) const
121{
122    if (copyBehavior == CopyBackingStore)
123        return StillImage::create(m_data.m_pixmap);
124
125    return StillImage::createForRendering(&m_data.m_pixmap);
126}
127
128BackingStoreCopy ImageBuffer::fastCopyImageMode()
129{
130    return DontCopyBackingStore;
131}
132
133void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
134    CompositeOperator op, BlendMode, bool useLowQualityScale)
135{
136    if (destContext == context()) {
137        // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
138        RefPtr<Image> copy = copyImage(CopyBackingStore);
139        destContext->drawImage(copy.get(), ColorSpaceDeviceRGB, destRect, srcRect, op, DoNotRespectImageOrientation, useLowQualityScale);
140    } else
141        destContext->drawImage(m_data.m_image.get(), styleColorSpace, destRect, srcRect, op, DoNotRespectImageOrientation, useLowQualityScale);
142}
143
144void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform,
145                              const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
146{
147    if (destContext == context()) {
148        // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
149        RefPtr<Image> copy = copyImage(CopyBackingStore);
150        copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
151    } else
152        m_data.m_image->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
153}
154
155void ImageBuffer::clip(GraphicsContext* context, const FloatRect& floatRect) const
156{
157    QPixmap* nativeImage = m_data.m_image->nativeImageForCurrentFrame();
158    if (!nativeImage)
159        return;
160
161    IntRect rect = enclosingIntRect(floatRect);
162    QPixmap alphaMask = *nativeImage;
163
164    context->pushTransparencyLayerInternal(rect, 1.0, alphaMask);
165}
166
167void ImageBuffer::platformTransformColorSpace(const Vector<int>& lookUpTable)
168{
169    bool isPainting = m_data.m_painter->isActive();
170    if (isPainting)
171        m_data.m_painter->end();
172
173    QImage image = m_data.toQImage().convertToFormat(QImage::Format_ARGB32);
174    ASSERT(!image.isNull());
175
176    uchar* bits = image.bits();
177    const int bytesPerLine = image.bytesPerLine();
178
179    for (int y = 0; y < m_size.height(); ++y) {
180        quint32* scanLine = reinterpret_cast_ptr<quint32*>(bits + y * bytesPerLine);
181        for (int x = 0; x < m_size.width(); ++x) {
182            QRgb& pixel = scanLine[x];
183            pixel = qRgba(lookUpTable[qRed(pixel)],
184                          lookUpTable[qGreen(pixel)],
185                          lookUpTable[qBlue(pixel)],
186                          qAlpha(pixel));
187        }
188    }
189
190    m_data.m_pixmap = QPixmap::fromImage(image);
191
192    if (isPainting)
193        m_data.m_painter->begin(&m_data.m_pixmap);
194}
195
196static inline void copyColorToRGBA(Color& from, uchar* to)
197{
198    // Copy from endian dependent 32bit ARGB to endian independent RGBA8888.
199    to[0] = from.red();
200    to[1] = from.green();
201    to[2] = from.blue();
202    to[3] = from.alpha();
203}
204
205static inline void copyRGBAToColor(const uchar* from, Color& to)
206{
207    // Copy from endian independent RGBA8888 to endian dependent 32bit ARGB.
208    to = Color::createUnchecked(from[0], from[1], from[2], from[3]);
209}
210
211template <Multiply multiplied>
212PassRefPtr<Uint8ClampedArray> getImageData(const IntRect& rect, const ImageBufferData& imageData, const IntSize& size)
213{
214    float area = 4.0f * rect.width() * rect.height();
215    if (area > static_cast<float>(std::numeric_limits<int>::max()))
216        return 0;
217
218    RefPtr<Uint8ClampedArray> result = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4);
219    uchar* resultData = result->data();
220
221    if (rect.x() < 0 || rect.y() < 0 || rect.maxX() > size.width() || rect.maxY() > size.height())
222        result->zeroFill();
223
224    int originx = rect.x();
225    int destx = 0;
226    if (originx < 0) {
227        destx = -originx;
228        originx = 0;
229    }
230    int endx = rect.maxX();
231    if (endx > size.width())
232        endx = size.width();
233    int numColumns = endx - originx;
234
235    int originy = rect.y();
236    int desty = 0;
237    if (originy < 0) {
238        desty = -originy;
239        originy = 0;
240    }
241    int endy = rect.maxY();
242    if (endy > size.height())
243        endy = size.height();
244    int numRows = endy - originy;
245
246    const unsigned destBytesPerRow = 4 * rect.width();
247
248    // NOTE: For unmultiplied data, we undo the premultiplication below.
249    QImage image = imageData.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
250
251    ASSERT(!image.isNull());
252
253    // The Canvas 2D Context expects RGBA order, while Qt uses 32bit QRgb (ARGB/BGRA).
254    for (int y = 0; y < numRows; ++y) {
255        // This cast and the calls below relies on both QRgb and WebCore::RGBA32 being 32bit ARGB.
256        const unsigned* srcRow = reinterpret_cast<const unsigned*>(image.constScanLine(originy + y)) + originx;
257        uchar* destRow = resultData + (desty + y) * destBytesPerRow + destx * 4;
258        for (int x = 0; x < numColumns; x++, srcRow++, destRow += 4) {
259            Color pixelColor;
260            if (multiplied == Unmultiplied)
261                pixelColor = colorFromPremultipliedARGB(*srcRow);
262            else
263                pixelColor = Color(*srcRow);
264            copyColorToRGBA(pixelColor, destRow);
265        }
266    }
267
268    return result.release();
269}
270
271PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem) const
272{
273    return getImageData<Unmultiplied>(rect, m_data, m_size);
274}
275
276PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem) const
277{
278    return getImageData<Premultiplied>(rect, m_data, m_size);
279}
280
281void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem)
282{
283    ASSERT(sourceRect.width() > 0);
284    ASSERT(sourceRect.height() > 0);
285
286    int originx = sourceRect.x();
287    int destx = destPoint.x() + sourceRect.x();
288    ASSERT(destx >= 0);
289    ASSERT(destx < m_size.width());
290    ASSERT(originx >= 0);
291    ASSERT(originx <= sourceRect.maxX());
292
293    int endx = destPoint.x() + sourceRect.maxX();
294    ASSERT(endx <= m_size.width());
295
296    int numColumns = endx - destx;
297
298    int originy = sourceRect.y();
299    int desty = destPoint.y() + sourceRect.y();
300    ASSERT(desty >= 0);
301    ASSERT(desty < m_size.height());
302    ASSERT(originy >= 0);
303    ASSERT(originy <= sourceRect.maxY());
304
305    int endy = destPoint.y() + sourceRect.maxY();
306    ASSERT(endy <= m_size.height());
307    int numRows = endy - desty;
308
309    const unsigned srcBytesPerRow = 4 * sourceSize.width();
310
311    // NOTE: For unmultiplied input data, we do the premultiplication below.
312    QImage image(numColumns, numRows, QImage::Format_ARGB32_Premultiplied);
313
314    unsigned* destData = reinterpret_cast<unsigned*>(image.bits());
315    const uchar* srcData = source->data();
316
317    for (int y = 0; y < numRows; ++y) {
318        const uchar* srcRow = srcData + (originy + y) * srcBytesPerRow + originx * 4;
319        // This cast and the calls below relies on both QRgb and WebCore::RGBA32 being 32bit ARGB.
320        unsigned* destRow = destData + y * numColumns;
321        for (int x = 0; x < numColumns; x++, srcRow += 4, destRow++) {
322            Color pixelColor;
323            copyRGBAToColor(srcRow, pixelColor);
324            if (multiplied == Unmultiplied)
325                *destRow = premultipliedARGBFromColor(pixelColor);
326            else
327                *destRow = pixelColor.rgb();
328        }
329    }
330
331    bool isPainting = m_data.m_painter->isActive();
332    if (!isPainting)
333        m_data.m_painter->begin(&m_data.m_pixmap);
334    else {
335        m_data.m_painter->save();
336
337        // putImageData() should be unaffected by painter state
338        m_data.m_painter->resetTransform();
339        m_data.m_painter->setOpacity(1.0);
340        m_data.m_painter->setClipping(false);
341    }
342
343    m_data.m_painter->setCompositionMode(QPainter::CompositionMode_Source);
344    m_data.m_painter->drawImage(destx, desty, image);
345
346    if (!isPainting)
347        m_data.m_painter->end();
348    else
349        m_data.m_painter->restore();
350}
351
352static bool encodeImage(const QPixmap& pixmap, const String& format, const double* quality, QByteArray& data)
353{
354    int compressionQuality = 100;
355    if (quality && *quality >= 0.0 && *quality <= 1.0)
356        compressionQuality = static_cast<int>(*quality * 100 + 0.5);
357
358    QBuffer buffer(&data);
359    buffer.open(QBuffer::WriteOnly);
360    bool success = pixmap.save(&buffer, format.utf8().data(), compressionQuality);
361    buffer.close();
362
363    return success;
364}
365
366String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const
367{
368    ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
369
370    // QImageWriter does not support mimetypes. It does support Qt image formats (png,
371    // gif, jpeg..., xpm) so skip the image/ to get the Qt image format used to encode
372    // the m_pixmap image.
373
374    QByteArray data;
375    if (!encodeImage(m_data.m_pixmap, mimeType.substring(sizeof "image"), quality, data))
376        return "data:,";
377
378    return "data:" + mimeType + ";base64," + data.toBase64().data();
379}
380
381}
382