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