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 "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 <wtf/Vector.h> 43#include <wtf/text/Base64.h> 44#include <wtf/text/WTFString.h> 45 46#if ENABLE(ACCELERATED_2D_CANVAS) 47#include "GLContext.h" 48#include "OpenGLShims.h" 49#include "TextureMapperGL.h" 50#include <cairo-gl.h> 51#endif 52 53using namespace std; 54 55namespace WebCore { 56 57ImageBufferData::ImageBufferData(const IntSize& size) 58 : m_platformContext(0) 59 , m_size(size) 60#if ENABLE(ACCELERATED_2D_CANVAS) 61 , m_texture(0) 62#endif 63{ 64} 65 66#if ENABLE(ACCELERATED_2D_CANVAS) 67PassRefPtr<cairo_surface_t> createCairoGLSurface(const IntSize& size, uint32_t& texture) 68{ 69 GLContext::sharingContext()->makeContextCurrent(); 70 71 // We must generate the texture ourselves, because there is no Cairo API for extracting it 72 // from a pre-existing surface. 73 glGenTextures(1, &texture); 74 glBindTexture(GL_TEXTURE_2D, texture); 75 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); 76 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 77 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 78 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 79 80 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 81 82 glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA8, size.width(), size.height(), 0 /* border */, GL_RGBA, GL_UNSIGNED_BYTE, 0); 83 84 GLContext* context = GLContext::sharingContext(); 85 cairo_device_t* device = context->cairoDevice(); 86 87 // Thread-awareness is a huge performance hit on non-Intel drivers. 88 cairo_gl_device_set_thread_aware(device, FALSE); 89 90 return adoptRef(cairo_gl_surface_create_for_texture(device, CAIRO_CONTENT_COLOR_ALPHA, texture, size.width(), size.height())); 91} 92#endif 93 94ImageBuffer::ImageBuffer(const IntSize& size, float /* resolutionScale */, ColorSpace, RenderingMode renderingMode, bool& success) 95 : m_data(size) 96 , m_size(size) 97 , m_logicalSize(size) 98{ 99 success = false; // Make early return mean error. 100 101#if ENABLE(ACCELERATED_2D_CANVAS) 102 if (renderingMode == Accelerated) 103 m_data.m_surface = createCairoGLSurface(size, m_data.m_texture); 104 else 105#endif 106 m_data.m_surface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.width(), size.height())); 107 108 if (cairo_surface_status(m_data.m_surface.get()) != CAIRO_STATUS_SUCCESS) 109 return; // create will notice we didn't set m_initialized and fail. 110 111 RefPtr<cairo_t> cr = adoptRef(cairo_create(m_data.m_surface.get())); 112 m_data.m_platformContext.setCr(cr.get()); 113 m_context = adoptPtr(new GraphicsContext(&m_data.m_platformContext)); 114 success = true; 115} 116 117ImageBuffer::~ImageBuffer() 118{ 119} 120 121GraphicsContext* ImageBuffer::context() const 122{ 123 return m_context.get(); 124} 125 126PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior) const 127{ 128 if (copyBehavior == CopyBackingStore) 129 return BitmapImage::create(copyCairoImageSurface(m_data.m_surface.get())); 130 131 // BitmapImage will release the passed in surface on destruction 132 return BitmapImage::create(m_data.m_surface); 133} 134 135BackingStoreCopy ImageBuffer::fastCopyImageMode() 136{ 137 return DontCopyBackingStore; 138} 139 140void ImageBuffer::clip(GraphicsContext* context, const FloatRect& maskRect) const 141{ 142 context->platformContext()->pushImageMask(m_data.m_surface.get(), maskRect); 143} 144 145void ImageBuffer::draw(GraphicsContext* destinationContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, 146 CompositeOperator op, BlendMode blendMode, bool useLowQualityScale) 147{ 148 BackingStoreCopy copyMode = destinationContext == context() ? CopyBackingStore : DontCopyBackingStore; 149 RefPtr<Image> image = copyImage(copyMode); 150 destinationContext->drawImage(image.get(), styleColorSpace, destRect, srcRect, op, blendMode, DoNotRespectImageOrientation, useLowQualityScale); 151} 152 153void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const AffineTransform& patternTransform, 154 const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect) 155{ 156 RefPtr<Image> image = copyImage(DontCopyBackingStore); 157 image->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect); 158} 159 160void ImageBuffer::platformTransformColorSpace(const Vector<int>& lookUpTable) 161{ 162 // FIXME: Enable color space conversions on accelerated canvases. 163 if (cairo_surface_get_type(m_data.m_surface.get()) != CAIRO_SURFACE_TYPE_IMAGE) 164 return; 165 166 unsigned char* dataSrc = cairo_image_surface_get_data(m_data.m_surface.get()); 167 int stride = cairo_image_surface_get_stride(m_data.m_surface.get()); 168 for (int y = 0; y < m_size.height(); ++y) { 169 unsigned* row = reinterpret_cast<unsigned*>(dataSrc + stride * y); 170 for (int x = 0; x < m_size.width(); x++) { 171 unsigned* pixel = row + x; 172 Color pixelColor = colorFromPremultipliedARGB(*pixel); 173 pixelColor = Color(lookUpTable[pixelColor.red()], 174 lookUpTable[pixelColor.green()], 175 lookUpTable[pixelColor.blue()], 176 pixelColor.alpha()); 177 *pixel = premultipliedARGBFromColor(pixelColor); 178 } 179 } 180 cairo_surface_mark_dirty_rectangle(m_data.m_surface.get(), 0, 0, m_size.width(), m_size.height()); 181} 182 183static cairo_surface_t* mapSurfaceToImage(cairo_surface_t* surface, const IntSize& size) 184{ 185 if (cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE) 186 return surface; 187 188 cairo_surface_t* imageSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.width(), size.height()); 189 RefPtr<cairo_t> cr = adoptRef(cairo_create(imageSurface)); 190 cairo_set_source_surface(cr.get(), surface, 0, 0); 191 cairo_paint(cr.get()); 192 return imageSurface; 193} 194 195static void unmapSurfaceFromImage(cairo_surface_t* surface, cairo_surface_t* imageSurface, const IntRect& dirtyRectangle = IntRect()) 196{ 197 if (surface == imageSurface && dirtyRectangle.isEmpty()) 198 return; 199 200 if (dirtyRectangle.isEmpty()) { 201 cairo_surface_destroy(imageSurface); 202 return; 203 } 204 205 if (surface == imageSurface) { 206 cairo_surface_mark_dirty_rectangle(surface, dirtyRectangle.x(), dirtyRectangle.y(), dirtyRectangle.width(), dirtyRectangle.height()); 207 return; 208 } 209 210 RefPtr<cairo_t> cr = adoptRef(cairo_create(surface)); 211 cairo_set_source_surface(cr.get(), imageSurface, 0, 0); 212 cairo_rectangle(cr.get(), dirtyRectangle.x(), dirtyRectangle.y(), dirtyRectangle.width(), dirtyRectangle.height()); 213 cairo_fill(cr.get()); 214 cairo_surface_destroy(imageSurface); 215} 216 217template <Multiply multiplied> 218PassRefPtr<Uint8ClampedArray> getImageData(const IntRect& rect, const ImageBufferData& data, const IntSize& size) 219{ 220 RefPtr<Uint8ClampedArray> result = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4); 221 cairo_surface_t* imageSurface = mapSurfaceToImage(data.m_surface.get(), size); 222 unsigned char* dataSrc = cairo_image_surface_get_data(imageSurface); 223 unsigned char* dataDst = result->data(); 224 225 if (rect.x() < 0 || rect.y() < 0 || (rect.x() + rect.width()) > size.width() || (rect.y() + rect.height()) > size.height()) 226 result->zeroFill(); 227 228 int originx = rect.x(); 229 int destx = 0; 230 if (originx < 0) { 231 destx = -originx; 232 originx = 0; 233 } 234 int endx = rect.maxX(); 235 if (endx > size.width()) 236 endx = size.width(); 237 int numColumns = endx - originx; 238 239 int originy = rect.y(); 240 int desty = 0; 241 if (originy < 0) { 242 desty = -originy; 243 originy = 0; 244 } 245 int endy = rect.maxY(); 246 if (endy > size.height()) 247 endy = size.height(); 248 int numRows = endy - originy; 249 250 int stride = cairo_image_surface_get_stride(imageSurface); 251 unsigned destBytesPerRow = 4 * rect.width(); 252 253 unsigned char* destRows = dataDst + desty * destBytesPerRow + destx * 4; 254 for (int y = 0; y < numRows; ++y) { 255 unsigned* row = reinterpret_cast<unsigned*>(dataSrc + stride * (y + originy)); 256 for (int x = 0; x < numColumns; x++) { 257 int basex = x * 4; 258 unsigned* pixel = row + x + originx; 259 260 // Avoid calling Color::colorFromPremultipliedARGB() because one 261 // function call per pixel is too expensive. 262 unsigned alpha = (*pixel & 0xFF000000) >> 24; 263 unsigned red = (*pixel & 0x00FF0000) >> 16; 264 unsigned green = (*pixel & 0x0000FF00) >> 8; 265 unsigned blue = (*pixel & 0x000000FF); 266 267 if (multiplied == Unmultiplied) { 268 if (alpha && alpha != 255) { 269 red = red * 255 / alpha; 270 green = green * 255 / alpha; 271 blue = blue * 255 / alpha; 272 } 273 } 274 275 destRows[basex] = red; 276 destRows[basex + 1] = green; 277 destRows[basex + 2] = blue; 278 destRows[basex + 3] = alpha; 279 } 280 destRows += destBytesPerRow; 281 } 282 283 unmapSurfaceFromImage(data.m_surface.get(), imageSurface); 284 return result.release(); 285} 286 287PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem) const 288{ 289 return getImageData<Unmultiplied>(rect, m_data, m_size); 290} 291 292PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem) const 293{ 294 return getImageData<Premultiplied>(rect, m_data, m_size); 295} 296 297void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem) 298{ 299 cairo_surface_t* imageSurface = mapSurfaceToImage(m_data.m_surface.get(), sourceSize); 300 unsigned char* dataDst = cairo_image_surface_get_data(imageSurface); 301 302 ASSERT(sourceRect.width() > 0); 303 ASSERT(sourceRect.height() > 0); 304 305 int originx = sourceRect.x(); 306 int destx = destPoint.x() + sourceRect.x(); 307 ASSERT(destx >= 0); 308 ASSERT(destx < m_size.width()); 309 ASSERT(originx >= 0); 310 ASSERT(originx <= sourceRect.maxX()); 311 312 int endx = destPoint.x() + sourceRect.maxX(); 313 ASSERT(endx <= m_size.width()); 314 315 int numColumns = endx - destx; 316 317 int originy = sourceRect.y(); 318 int desty = destPoint.y() + sourceRect.y(); 319 ASSERT(desty >= 0); 320 ASSERT(desty < m_size.height()); 321 ASSERT(originy >= 0); 322 ASSERT(originy <= sourceRect.maxY()); 323 324 int endy = destPoint.y() + sourceRect.maxY(); 325 ASSERT(endy <= m_size.height()); 326 int numRows = endy - desty; 327 328 unsigned srcBytesPerRow = 4 * sourceSize.width(); 329 int stride = cairo_image_surface_get_stride(imageSurface); 330 331 unsigned char* srcRows = source->data() + originy * srcBytesPerRow + originx * 4; 332 for (int y = 0; y < numRows; ++y) { 333 unsigned* row = reinterpret_cast<unsigned*>(dataDst + stride * (y + desty)); 334 for (int x = 0; x < numColumns; x++) { 335 int basex = x * 4; 336 unsigned* pixel = row + x + destx; 337 338 // Avoid calling Color::premultipliedARGBFromColor() because one 339 // function call per pixel is too expensive. 340 unsigned red = srcRows[basex]; 341 unsigned green = srcRows[basex + 1]; 342 unsigned blue = srcRows[basex + 2]; 343 unsigned alpha = srcRows[basex + 3]; 344 345 if (multiplied == Unmultiplied) { 346 if (alpha && alpha != 255) { 347 red = (red * alpha + 254) / 255; 348 green = (green * alpha + 254) / 255; 349 blue = (blue * alpha + 254) / 255; 350 } 351 } 352 353 *pixel = (alpha << 24) | red << 16 | green << 8 | blue; 354 } 355 srcRows += srcBytesPerRow; 356 } 357 358 unmapSurfaceFromImage(m_data.m_surface.get(), imageSurface, IntRect(destx, desty, numColumns, numRows)); 359} 360 361#if !PLATFORM(GTK) 362static cairo_status_t writeFunction(void* output, const unsigned char* data, unsigned int length) 363{ 364 if (!reinterpret_cast<Vector<unsigned char>*>(output)->tryAppend(data, length)) 365 return CAIRO_STATUS_WRITE_ERROR; 366 return CAIRO_STATUS_SUCCESS; 367} 368 369static bool encodeImage(cairo_surface_t* image, const String& mimeType, Vector<char>* output) 370{ 371 ASSERT_UNUSED(mimeType, mimeType == "image/png"); // Only PNG output is supported for now. 372 373 return cairo_surface_write_to_png_stream(image, writeFunction, output) == CAIRO_STATUS_SUCCESS; 374} 375 376String ImageBuffer::toDataURL(const String& mimeType, const double*, CoordinateSystem) const 377{ 378 ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); 379 380 cairo_surface_t* image = cairo_get_target(context()->platformContext()->cr()); 381 382 Vector<char> encodedImage; 383 if (!image || !encodeImage(image, mimeType, &encodedImage)) 384 return "data:,"; 385 386 Vector<char> base64Data; 387 base64Encode(encodedImage, base64Data); 388 389 return "data:" + mimeType + ";base64," + base64Data; 390} 391#endif 392 393#if ENABLE(ACCELERATED_2D_CANVAS) 394void ImageBufferData::paintToTextureMapper(TextureMapper* textureMapper, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity) 395{ 396 if (textureMapper->accelerationMode() != TextureMapper::OpenGLMode) { 397 notImplemented(); 398 return; 399 } 400 401 ASSERT(m_texture); 402 403 // Cairo may change the active context, so we make sure to change it back after flushing. 404 GLContext* previousActiveContext = GLContext::getCurrent(); 405 cairo_surface_flush(m_surface.get()); 406 previousActiveContext->makeContextCurrent(); 407 408 static_cast<TextureMapperGL*>(textureMapper)->drawTexture(m_texture, TextureMapperGL::ShouldBlend, m_size, targetRect, matrix, opacity); 409} 410#endif 411 412#if USE(ACCELERATED_COMPOSITING) 413PlatformLayer* ImageBuffer::platformLayer() const 414{ 415#if ENABLE(ACCELERATED_2D_CANVAS) 416 if (m_data.m_texture) 417 return const_cast<ImageBufferData*>(&m_data); 418#endif 419 return 0; 420} 421#endif 422 423} // namespace WebCore 424