/* | |
* Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> | |
* Copyright (C) 2008 Holger Hans Peter Freyther | |
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org> | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* 1. Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* 2. Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* | |
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | |
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | |
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#include "config.h" | |
#include "ImageBuffer.h" | |
#include "CString.h" | |
#include "GraphicsContext.h" | |
#include "ImageData.h" | |
#include "MIMETypeRegistry.h" | |
#include "StillImageQt.h" | |
#include <QBuffer> | |
#include <QColor> | |
#include <QImage> | |
#include <QImageWriter> | |
#include <QPainter> | |
#include <QPixmap> | |
#include <math.h> | |
namespace WebCore { | |
ImageBufferData::ImageBufferData(const IntSize& size) | |
: m_pixmap(size) | |
, m_painter(0) | |
{ | |
if (m_pixmap.isNull()) | |
return; | |
m_pixmap.fill(QColor(Qt::transparent)); | |
QPainter* painter = new QPainter; | |
m_painter.set(painter); | |
if (!painter->begin(&m_pixmap)) | |
return; | |
// Since ImageBuffer is used mainly for Canvas, explicitly initialize | |
// its painter's pen and brush with the corresponding canvas defaults | |
// NOTE: keep in sync with CanvasRenderingContext2D::State | |
QPen pen = painter->pen(); | |
pen.setColor(Qt::black); | |
pen.setWidth(1); | |
pen.setCapStyle(Qt::FlatCap); | |
pen.setJoinStyle(Qt::MiterJoin); | |
pen.setMiterLimit(10); | |
painter->setPen(pen); | |
QBrush brush = painter->brush(); | |
brush.setColor(Qt::black); | |
painter->setBrush(brush); | |
painter->setCompositionMode(QPainter::CompositionMode_SourceOver); | |
} | |
ImageBuffer::ImageBuffer(const IntSize& size, ImageColorSpace, bool& success) | |
: m_data(size) | |
, m_size(size) | |
{ | |
success = m_data.m_painter && m_data.m_painter->isActive(); | |
if (!success) | |
return; | |
m_context.set(new GraphicsContext(m_data.m_painter.get())); | |
} | |
ImageBuffer::~ImageBuffer() | |
{ | |
} | |
GraphicsContext* ImageBuffer::context() const | |
{ | |
ASSERT(m_data.m_painter->isActive()); | |
return m_context.get(); | |
} | |
Image* ImageBuffer::image() const | |
{ | |
if (!m_image) { | |
// It's assumed that if image() is called, the actual rendering to the | |
// GraphicsContext must be done. | |
ASSERT(context()); | |
m_image = StillImage::create(m_data.m_pixmap); | |
} | |
return m_image.get(); | |
} | |
void ImageBuffer::platformTransformColorSpace(const Vector<int>& lookUpTable) | |
{ | |
bool isPainting = m_data.m_painter->isActive(); | |
if (isPainting) | |
m_data.m_painter->end(); | |
QImage image = m_data.m_pixmap.toImage().convertToFormat(QImage::Format_ARGB32); | |
ASSERT(!image.isNull()); | |
for (int y = 0; y < m_size.height(); ++y) { | |
for (int x = 0; x < m_size.width(); x++) { | |
QRgb value = image.pixel(x, y); | |
value = qRgba(lookUpTable[qRed(value)], | |
lookUpTable[qGreen(value)], | |
lookUpTable[qBlue(value)], | |
qAlpha(value)); | |
image.setPixel(x, y, value); | |
} | |
} | |
m_data.m_pixmap = QPixmap::fromImage(image); | |
if (isPainting) | |
m_data.m_painter->begin(&m_data.m_pixmap); | |
} | |
template <Multiply multiplied> | |
PassRefPtr<ImageData> getImageData(const IntRect& rect, const ImageBufferData& imageData, const IntSize& size) | |
{ | |
PassRefPtr<ImageData> result = ImageData::create(rect.width(), rect.height()); | |
unsigned char* data = result->data()->data()->data(); | |
if (rect.x() < 0 || rect.y() < 0 || (rect.x() + rect.width()) > size.width() || (rect.y() + rect.height()) > size.height()) | |
memset(data, 0, result->data()->length()); | |
int originx = rect.x(); | |
int destx = 0; | |
if (originx < 0) { | |
destx = -originx; | |
originx = 0; | |
} | |
int endx = rect.x() + rect.width(); | |
if (endx > size.width()) | |
endx = size.width(); | |
int numColumns = endx - originx; | |
int originy = rect.y(); | |
int desty = 0; | |
if (originy < 0) { | |
desty = -originy; | |
originy = 0; | |
} | |
int endy = rect.y() + rect.height(); | |
if (endy > size.height()) | |
endy = size.height(); | |
int numRows = endy - originy; | |
QImage image = imageData.m_pixmap.toImage(); | |
if (multiplied == Unmultiplied) | |
image = image.convertToFormat(QImage::Format_ARGB32); | |
else | |
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); | |
ASSERT(!image.isNull()); | |
unsigned destBytesPerRow = 4 * rect.width(); | |
unsigned char* destRows = data + desty * destBytesPerRow + destx * 4; | |
for (int y = 0; y < numRows; ++y) { | |
for (int x = 0; x < numColumns; x++) { | |
QRgb value = image.pixel(x + originx, y + originy); | |
int basex = x * 4; | |
destRows[basex] = qRed(value); | |
destRows[basex + 1] = qGreen(value); | |
destRows[basex + 2] = qBlue(value); | |
destRows[basex + 3] = qAlpha(value); | |
} | |
destRows += destBytesPerRow; | |
} | |
return result; | |
} | |
PassRefPtr<ImageData> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const | |
{ | |
return getImageData<Unmultiplied>(rect, m_data, m_size); | |
} | |
PassRefPtr<ImageData> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const | |
{ | |
return getImageData<Premultiplied>(rect, m_data, m_size); | |
} | |
template <Multiply multiplied> | |
void putImageData(ImageData*& source, const IntRect& sourceRect, const IntPoint& destPoint, ImageBufferData& data, const IntSize& size) | |
{ | |
ASSERT(sourceRect.width() > 0); | |
ASSERT(sourceRect.height() > 0); | |
int originx = sourceRect.x(); | |
int destx = destPoint.x() + sourceRect.x(); | |
ASSERT(destx >= 0); | |
ASSERT(destx < size.width()); | |
ASSERT(originx >= 0); | |
ASSERT(originx <= sourceRect.right()); | |
int endx = destPoint.x() + sourceRect.right(); | |
ASSERT(endx <= size.width()); | |
int numColumns = endx - destx; | |
int originy = sourceRect.y(); | |
int desty = destPoint.y() + sourceRect.y(); | |
ASSERT(desty >= 0); | |
ASSERT(desty < size.height()); | |
ASSERT(originy >= 0); | |
ASSERT(originy <= sourceRect.bottom()); | |
int endy = destPoint.y() + sourceRect.bottom(); | |
ASSERT(endy <= size.height()); | |
int numRows = endy - desty; | |
unsigned srcBytesPerRow = 4 * source->width(); | |
bool isPainting = data.m_painter->isActive(); | |
if (isPainting) | |
data.m_painter->end(); | |
QImage image = data.m_pixmap.toImage(); | |
if (multiplied == Unmultiplied) | |
image = image.convertToFormat(QImage::Format_ARGB32); | |
else | |
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); | |
unsigned char* srcRows = source->data()->data()->data() + originy * srcBytesPerRow + originx * 4; | |
for (int y = 0; y < numRows; ++y) { | |
quint32* scanLine = reinterpret_cast<quint32*>(image.scanLine(y + desty)); | |
for (int x = 0; x < numColumns; x++) { | |
// ImageData stores the pixels in RGBA while QImage is ARGB | |
quint32 pixel = reinterpret_cast<quint32*>(srcRows + 4 * x)[0]; | |
pixel = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff) | (pixel & 0xff00ff00); | |
scanLine[x + destx] = pixel; | |
} | |
srcRows += srcBytesPerRow; | |
} | |
data.m_pixmap = QPixmap::fromImage(image); | |
if (isPainting) | |
data.m_painter->begin(&data.m_pixmap); | |
} | |
void ImageBuffer::putUnmultipliedImageData(ImageData* source, const IntRect& sourceRect, const IntPoint& destPoint) | |
{ | |
putImageData<Unmultiplied>(source, sourceRect, destPoint, m_data, m_size); | |
} | |
void ImageBuffer::putPremultipliedImageData(ImageData* source, const IntRect& sourceRect, const IntPoint& destPoint) | |
{ | |
putImageData<Premultiplied>(source, sourceRect, destPoint, m_data, m_size); | |
} | |
// We get a mimeType here but QImageWriter does not support mimetypes but | |
// only formats (png, gif, jpeg..., xpm). So assume we get image/ as image | |
// mimetypes and then remove the image/ to get the Qt format. | |
String ImageBuffer::toDataURL(const String& mimeType) const | |
{ | |
ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); | |
if (!mimeType.startsWith("image/")) | |
return "data:,"; | |
// prepare our target | |
QByteArray data; | |
QBuffer buffer(&data); | |
buffer.open(QBuffer::WriteOnly); | |
if (!m_data.m_pixmap.save(&buffer, mimeType.substring(sizeof "image").utf8().data())) | |
return "data:,"; | |
buffer.close(); | |
return String::format("data:%s;base64,%s", mimeType.utf8().data(), data.toBase64().data()); | |
} | |
} |