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