| 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 | #if USE(CAIRO) |
| 33 | |
| 34 | #include "BitmapImage.h" |
| 35 | #include "CairoUtilities.h" |
| 36 | #include "Color.h" |
| 37 | #include "GraphicsContext.h" |
| 38 | #include "GraphicsContextImplCairo.h" |
| 39 | #include "MIMETypeRegistry.h" |
| 40 | #include "NotImplemented.h" |
| 41 | #include "Pattern.h" |
| 42 | #include "PlatformContextCairo.h" |
| 43 | #include "RefPtrCairo.h" |
| 44 | #include <JavaScriptCore/JSCInlines.h> |
| 45 | #include <JavaScriptCore/TypedArrayInlines.h> |
| 46 | #include <cairo.h> |
| 47 | #include <wtf/Vector.h> |
| 48 | #include <wtf/text/Base64.h> |
| 49 | #include <wtf/text/WTFString.h> |
| 50 | |
| 51 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 52 | #include "GLContext.h" |
| 53 | #include "TextureMapperGL.h" |
| 54 | |
| 55 | #if USE(EGL) |
| 56 | #if USE(LIBEPOXY) |
| 57 | #include "EpoxyEGL.h" |
| 58 | #else |
| 59 | #include <EGL/egl.h> |
| 60 | #endif |
| 61 | #endif |
| 62 | #include <cairo-gl.h> |
| 63 | |
| 64 | #if USE(LIBEPOXY) |
| 65 | #include <epoxy/gl.h> |
| 66 | #elif USE(OPENGL_ES) |
| 67 | #include <GLES2/gl2.h> |
| 68 | #else |
| 69 | #include "OpenGLShims.h" |
| 70 | #endif |
| 71 | |
| 72 | #if USE(COORDINATED_GRAPHICS) |
| 73 | #include "TextureMapperPlatformLayerBuffer.h" |
| 74 | #include "TextureMapperPlatformLayerProxy.h" |
| 75 | #endif |
| 76 | #endif |
| 77 | |
| 78 | |
| 79 | namespace WebCore { |
| 80 | |
| 81 | ImageBufferData::ImageBufferData(const IntSize& size, RenderingMode renderingMode) |
| 82 | : m_platformContext(0) |
| 83 | , m_size(size) |
| 84 | , m_renderingMode(renderingMode) |
| 85 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 86 | #if USE(COORDINATED_GRAPHICS) |
| 87 | , m_compositorTexture(0) |
| 88 | #endif |
| 89 | , m_texture(0) |
| 90 | #endif |
| 91 | { |
| 92 | #if ENABLE(ACCELERATED_2D_CANVAS) && USE(COORDINATED_GRAPHICS) |
| 93 | if (m_renderingMode == RenderingMode::Accelerated) { |
| 94 | #if USE(NICOSIA) |
| 95 | m_nicosiaLayer = Nicosia::ContentLayer::create(Nicosia::ContentLayerTextureMapperImpl::createFactory(*this)); |
| 96 | #else |
| 97 | m_platformLayerProxy = adoptRef(new TextureMapperPlatformLayerProxy); |
| 98 | #endif |
| 99 | } |
| 100 | #endif |
| 101 | } |
| 102 | |
| 103 | ImageBufferData::~ImageBufferData() |
| 104 | { |
| 105 | if (m_renderingMode != Accelerated) |
| 106 | return; |
| 107 | |
| 108 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 109 | #if USE(COORDINATED_GRAPHICS) && USE(NICOSIA) |
| 110 | downcast<Nicosia::ContentLayerTextureMapperImpl>(m_nicosiaLayer->impl()).invalidateClient(); |
| 111 | #endif |
| 112 | |
| 113 | GLContext* previousActiveContext = GLContext::current(); |
| 114 | PlatformDisplay::sharedDisplayForCompositing().sharingGLContext()->makeContextCurrent(); |
| 115 | |
| 116 | if (m_texture) |
| 117 | glDeleteTextures(1, &m_texture); |
| 118 | |
| 119 | #if USE(COORDINATED_GRAPHICS) |
| 120 | if (m_compositorTexture) |
| 121 | glDeleteTextures(1, &m_compositorTexture); |
| 122 | #endif |
| 123 | |
| 124 | if (previousActiveContext) |
| 125 | previousActiveContext->makeContextCurrent(); |
| 126 | #endif |
| 127 | } |
| 128 | |
| 129 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 130 | #if USE(COORDINATED_GRAPHICS) |
| 131 | void ImageBufferData::createCompositorBuffer() |
| 132 | { |
| 133 | auto* context = PlatformDisplay::sharedDisplayForCompositing().sharingGLContext(); |
| 134 | context->makeContextCurrent(); |
| 135 | |
| 136 | glGenTextures(1, &m_compositorTexture); |
| 137 | glBindTexture(GL_TEXTURE_2D, m_compositorTexture); |
| 138 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 139 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 140 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 141 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| 142 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| 143 | glTexImage2D(GL_TEXTURE_2D, 0 , GL_RGBA, m_size.width(), m_size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); |
| 144 | |
| 145 | m_compositorSurface = adoptRef(cairo_gl_surface_create_for_texture(context->cairoDevice(), CAIRO_CONTENT_COLOR_ALPHA, m_compositorTexture, m_size.width(), m_size.height())); |
| 146 | m_compositorCr = adoptRef(cairo_create(m_compositorSurface.get())); |
| 147 | cairo_set_antialias(m_compositorCr.get(), CAIRO_ANTIALIAS_NONE); |
| 148 | } |
| 149 | |
| 150 | #if !USE(NICOSIA) |
| 151 | RefPtr<TextureMapperPlatformLayerProxy> ImageBufferData::proxy() const |
| 152 | { |
| 153 | return m_platformLayerProxy.copyRef(); |
| 154 | } |
| 155 | #endif |
| 156 | |
| 157 | void ImageBufferData::swapBuffersIfNeeded() |
| 158 | { |
| 159 | GLContext* previousActiveContext = GLContext::current(); |
| 160 | |
| 161 | if (!m_compositorTexture) { |
| 162 | createCompositorBuffer(); |
| 163 | |
| 164 | auto proxyOperation = |
| 165 | [this](TextureMapperPlatformLayerProxy& proxy) |
| 166 | { |
| 167 | LockHolder holder(proxy.lock()); |
| 168 | proxy.pushNextBuffer(std::make_unique<TextureMapperPlatformLayerBuffer>(m_compositorTexture, m_size, TextureMapperGL::ShouldBlend, GL_RGBA)); |
| 169 | }; |
| 170 | #if USE(NICOSIA) |
| 171 | proxyOperation(downcast<Nicosia::ContentLayerTextureMapperImpl>(m_nicosiaLayer->impl()).proxy()); |
| 172 | #else |
| 173 | proxyOperation(*m_platformLayerProxy); |
| 174 | #endif |
| 175 | } |
| 176 | |
| 177 | // It would be great if we could just swap the buffers here as we do with webgl, but that breaks the cases |
| 178 | // where one frame uses the content already rendered in the previous frame. So we just copy the content |
| 179 | // into the compositor buffer. |
| 180 | cairo_set_source_surface(m_compositorCr.get(), m_surface.get(), 0, 0); |
| 181 | cairo_set_operator(m_compositorCr.get(), CAIRO_OPERATOR_SOURCE); |
| 182 | cairo_paint(m_compositorCr.get()); |
| 183 | |
| 184 | if (previousActiveContext) |
| 185 | previousActiveContext->makeContextCurrent(); |
| 186 | } |
| 187 | #endif |
| 188 | |
| 189 | void clearSurface(cairo_surface_t* surface) |
| 190 | { |
| 191 | if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) |
| 192 | return; |
| 193 | |
| 194 | RefPtr<cairo_t> cr = adoptRef(cairo_create(surface)); |
| 195 | cairo_set_operator(cr.get(), CAIRO_OPERATOR_CLEAR); |
| 196 | cairo_paint(cr.get()); |
| 197 | } |
| 198 | |
| 199 | void ImageBufferData::createCairoGLSurface() |
| 200 | { |
| 201 | auto* context = PlatformDisplay::sharedDisplayForCompositing().sharingGLContext(); |
| 202 | context->makeContextCurrent(); |
| 203 | |
| 204 | // We must generate the texture ourselves, because there is no Cairo API for extracting it |
| 205 | // from a pre-existing surface. |
| 206 | glGenTextures(1, &m_texture); |
| 207 | glBindTexture(GL_TEXTURE_2D, m_texture); |
| 208 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 209 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 210 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 211 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| 212 | |
| 213 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| 214 | |
| 215 | glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA, m_size.width(), m_size.height(), 0 /* border */, GL_RGBA, GL_UNSIGNED_BYTE, 0); |
| 216 | |
| 217 | cairo_device_t* device = context->cairoDevice(); |
| 218 | |
| 219 | // Thread-awareness is a huge performance hit on non-Intel drivers. |
| 220 | cairo_gl_device_set_thread_aware(device, FALSE); |
| 221 | |
| 222 | m_surface = adoptRef(cairo_gl_surface_create_for_texture(device, CAIRO_CONTENT_COLOR_ALPHA, m_texture, m_size.width(), m_size.height())); |
| 223 | clearSurface(m_surface.get()); |
| 224 | } |
| 225 | #endif |
| 226 | |
| 227 | static RefPtr<cairo_surface_t> |
| 228 | cairoSurfaceCoerceToImage(cairo_surface_t* surface) |
| 229 | { |
| 230 | if (cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE |
| 231 | && cairo_surface_get_content(surface) == CAIRO_CONTENT_COLOR_ALPHA) |
| 232 | return surface; |
| 233 | |
| 234 | auto copy = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, |
| 235 | cairo_image_surface_get_width(surface), |
| 236 | cairo_image_surface_get_height(surface))); |
| 237 | |
| 238 | auto cr = adoptRef(cairo_create(copy.get())); |
| 239 | cairo_set_operator(cr.get(), CAIRO_OPERATOR_SOURCE); |
| 240 | cairo_set_source_surface(cr.get(), surface, 0, 0); |
| 241 | cairo_paint(cr.get()); |
| 242 | |
| 243 | return copy; |
| 244 | } |
| 245 | |
| 246 | Vector<uint8_t> ImageBuffer::toBGRAData() const |
| 247 | { |
| 248 | auto surface = cairoSurfaceCoerceToImage(m_data.m_surface.get()); |
| 249 | cairo_surface_flush(surface.get()); |
| 250 | |
| 251 | Vector<uint8_t> imageData; |
| 252 | if (cairo_surface_status(surface.get())) |
| 253 | return imageData; |
| 254 | |
| 255 | auto pixels = cairo_image_surface_get_data(surface.get()); |
| 256 | imageData.append(pixels, cairo_image_surface_get_stride(surface.get()) * |
| 257 | cairo_image_surface_get_height(surface.get())); |
| 258 | |
| 259 | return imageData; |
| 260 | } |
| 261 | |
| 262 | ImageBuffer::ImageBuffer(const FloatSize& size, float resolutionScale, ColorSpace, RenderingMode renderingMode, const HostWindow*, bool& success) |
| 263 | : m_data(IntSize(size), renderingMode) |
| 264 | , m_logicalSize(size) |
| 265 | , m_resolutionScale(resolutionScale) |
| 266 | { |
| 267 | success = false; // Make early return mean error. |
| 268 | |
| 269 | float scaledWidth = ceilf(m_resolutionScale * size.width()); |
| 270 | float scaledHeight = ceilf(m_resolutionScale * size.height()); |
| 271 | |
| 272 | // FIXME: Should we automatically use a lower resolution? |
| 273 | if (!FloatSize(scaledWidth, scaledHeight).isExpressibleAsIntSize()) |
| 274 | return; |
| 275 | |
| 276 | m_size = IntSize(scaledWidth, scaledHeight); |
| 277 | m_data.m_size = m_size; |
| 278 | |
| 279 | if (m_size.isEmpty()) |
| 280 | return; |
| 281 | |
| 282 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 283 | if (m_data.m_renderingMode == Accelerated) { |
| 284 | m_data.createCairoGLSurface(); |
| 285 | if (!m_data.m_surface || cairo_surface_status(m_data.m_surface.get()) != CAIRO_STATUS_SUCCESS) |
| 286 | m_data.m_renderingMode = Unaccelerated; // If allocation fails, fall back to non-accelerated path. |
| 287 | } |
| 288 | if (m_data.m_renderingMode == Unaccelerated) |
| 289 | #else |
| 290 | ASSERT(m_data.m_renderingMode != Accelerated); |
| 291 | #endif |
| 292 | { |
| 293 | static cairo_user_data_key_t s_surfaceDataKey; |
| 294 | |
| 295 | int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, m_size.width()); |
| 296 | void* surfaceData; |
| 297 | if (!tryFastZeroedMalloc(m_size.height() * stride).getValue(surfaceData)) |
| 298 | return; |
| 299 | |
| 300 | m_data.m_surface = adoptRef(cairo_image_surface_create_for_data(static_cast<unsigned char*>(surfaceData), CAIRO_FORMAT_ARGB32, m_size.width(), m_size.height(), stride)); |
| 301 | cairo_surface_set_user_data(m_data.m_surface.get(), &s_surfaceDataKey, surfaceData, [](void* data) { fastFree(data); }); |
| 302 | } |
| 303 | |
| 304 | if (cairo_surface_status(m_data.m_surface.get()) != CAIRO_STATUS_SUCCESS) |
| 305 | return; // create will notice we didn't set m_initialized and fail. |
| 306 | |
| 307 | cairoSurfaceSetDeviceScale(m_data.m_surface.get(), m_resolutionScale, m_resolutionScale); |
| 308 | |
| 309 | RefPtr<cairo_t> cr = adoptRef(cairo_create(m_data.m_surface.get())); |
| 310 | m_data.m_platformContext.setCr(cr.get()); |
| 311 | m_data.m_context = std::make_unique<GraphicsContext>(GraphicsContextImplCairo::createFactory(m_data.m_platformContext)); |
| 312 | success = true; |
| 313 | } |
| 314 | |
| 315 | ImageBuffer::~ImageBuffer() = default; |
| 316 | |
| 317 | std::unique_ptr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, const GraphicsContext& context) |
| 318 | { |
| 319 | return createCompatibleBuffer(size, ColorSpaceSRGB, context); |
| 320 | } |
| 321 | |
| 322 | GraphicsContext& ImageBuffer::context() const |
| 323 | { |
| 324 | return *m_data.m_context; |
| 325 | } |
| 326 | |
| 327 | RefPtr<Image> ImageBuffer::sinkIntoImage(std::unique_ptr<ImageBuffer> imageBuffer, PreserveResolution preserveResolution) |
| 328 | { |
| 329 | return imageBuffer->copyImage(DontCopyBackingStore, preserveResolution); |
| 330 | } |
| 331 | |
| 332 | RefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, PreserveResolution) const |
| 333 | { |
| 334 | // copyCairoImageSurface inherits surface's device scale factor. |
| 335 | if (copyBehavior == CopyBackingStore) |
| 336 | return BitmapImage::create(copyCairoImageSurface(m_data.m_surface.get())); |
| 337 | |
| 338 | // BitmapImage will release the passed in surface on destruction |
| 339 | return BitmapImage::create(RefPtr<cairo_surface_t>(m_data.m_surface)); |
| 340 | } |
| 341 | |
| 342 | BackingStoreCopy ImageBuffer::fastCopyImageMode() |
| 343 | { |
| 344 | return DontCopyBackingStore; |
| 345 | } |
| 346 | |
| 347 | void ImageBuffer::drawConsuming(std::unique_ptr<ImageBuffer> imageBuffer, GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode) |
| 348 | { |
| 349 | imageBuffer->draw(destContext, destRect, srcRect, op, blendMode); |
| 350 | } |
| 351 | |
| 352 | void ImageBuffer::draw(GraphicsContext& destinationContext, const FloatRect& destRect, const FloatRect& srcRect, |
| 353 | CompositeOperator op, BlendMode blendMode) |
| 354 | { |
| 355 | BackingStoreCopy copyMode = &destinationContext == &context() ? CopyBackingStore : DontCopyBackingStore; |
| 356 | RefPtr<Image> image = copyImage(copyMode); |
| 357 | destinationContext.drawImage(*image, destRect, srcRect, ImagePaintingOptions(op, blendMode, DecodingMode::Synchronous, ImageOrientationDescription())); |
| 358 | } |
| 359 | |
| 360 | void ImageBuffer::drawPattern(GraphicsContext& context, const FloatRect& destRect, const FloatRect& srcRect, const AffineTransform& patternTransform, |
| 361 | const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, BlendMode) |
| 362 | { |
| 363 | if (RefPtr<Image> image = copyImage(DontCopyBackingStore)) |
| 364 | image->drawPattern(context, destRect, srcRect, patternTransform, phase, spacing, op); |
| 365 | } |
| 366 | |
| 367 | void ImageBuffer::platformTransformColorSpace(const std::array<uint8_t, 256>& lookUpTable) |
| 368 | { |
| 369 | // FIXME: Enable color space conversions on accelerated canvases. |
| 370 | if (cairo_surface_get_type(m_data.m_surface.get()) != CAIRO_SURFACE_TYPE_IMAGE) |
| 371 | return; |
| 372 | |
| 373 | unsigned char* dataSrc = cairo_image_surface_get_data(m_data.m_surface.get()); |
| 374 | int stride = cairo_image_surface_get_stride(m_data.m_surface.get()); |
| 375 | for (int y = 0; y < m_size.height(); ++y) { |
| 376 | unsigned* row = reinterpret_cast_ptr<unsigned*>(dataSrc + stride * y); |
| 377 | for (int x = 0; x < m_size.width(); x++) { |
| 378 | unsigned* pixel = row + x; |
| 379 | Color pixelColor = colorFromPremultipliedARGB(*pixel); |
| 380 | pixelColor = Color(lookUpTable[pixelColor.red()], |
| 381 | lookUpTable[pixelColor.green()], |
| 382 | lookUpTable[pixelColor.blue()], |
| 383 | pixelColor.alpha()); |
| 384 | *pixel = premultipliedARGBFromColor(pixelColor); |
| 385 | } |
| 386 | } |
| 387 | cairo_surface_mark_dirty_rectangle(m_data.m_surface.get(), 0, 0, m_logicalSize.width(), m_logicalSize.height()); |
| 388 | } |
| 389 | |
| 390 | RefPtr<cairo_surface_t> copySurfaceToImageAndAdjustRect(cairo_surface_t* surface, IntRect& rect) |
| 391 | { |
| 392 | cairo_surface_type_t surfaceType = cairo_surface_get_type(surface); |
| 393 | |
| 394 | // If we already have an image, we write directly to the underlying data; |
| 395 | // otherwise we create a temporary surface image |
| 396 | if (surfaceType == CAIRO_SURFACE_TYPE_IMAGE) |
| 397 | return surface; |
| 398 | |
| 399 | rect.setX(0); |
| 400 | rect.setY(0); |
| 401 | return adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, rect.width(), rect.height())); |
| 402 | } |
| 403 | |
| 404 | template <AlphaPremultiplication premultiplied> |
| 405 | RefPtr<Uint8ClampedArray> getImageData(const IntRect& rect, const IntRect& logicalRect, const ImageBufferData& data, const IntSize& size, const IntSize& logicalSize, float resolutionScale) |
| 406 | { |
| 407 | // The area can overflow if the rect is too big. |
| 408 | Checked<unsigned, RecordOverflow> area = 4; |
| 409 | area *= rect.width(); |
| 410 | area *= rect.height(); |
| 411 | if (area.hasOverflowed()) |
| 412 | return nullptr; |
| 413 | |
| 414 | auto result = Uint8ClampedArray::tryCreateUninitialized(area.unsafeGet()); |
| 415 | if (!result) |
| 416 | return nullptr; |
| 417 | |
| 418 | // Can overflow, as we are adding 2 ints. |
| 419 | int endx = 0; |
| 420 | if (!WTF::safeAdd(rect.x(), rect.width(), endx)) |
| 421 | return nullptr; |
| 422 | |
| 423 | // Can overflow, as we are adding 2 ints. |
| 424 | int endy = 0; |
| 425 | if (!WTF::safeAdd(rect.y(), rect.height(), endy)) |
| 426 | return nullptr; |
| 427 | |
| 428 | if (rect.x() < 0 || rect.y() < 0 || endx > size.width() || endy > size.height()) |
| 429 | result->zeroFill(); |
| 430 | |
| 431 | int originx = rect.x(); |
| 432 | int destx = 0; |
| 433 | if (originx < 0) { |
| 434 | destx = -originx; |
| 435 | originx = 0; |
| 436 | } |
| 437 | |
| 438 | if (endx > size.width()) |
| 439 | endx = size.width(); |
| 440 | int numColumns = endx - originx; |
| 441 | |
| 442 | int originy = rect.y(); |
| 443 | int desty = 0; |
| 444 | if (originy < 0) { |
| 445 | desty = -originy; |
| 446 | originy = 0; |
| 447 | } |
| 448 | |
| 449 | if (endy > size.height()) |
| 450 | endy = size.height(); |
| 451 | int numRows = endy - originy; |
| 452 | |
| 453 | // Nothing will be copied, so just return the result. |
| 454 | if (numColumns <= 0 || numRows <= 0) |
| 455 | return result; |
| 456 | |
| 457 | // The size of the derived surface is in BackingStoreCoordinateSystem. |
| 458 | // We need to set the device scale for the derived surface from this ImageBuffer. |
| 459 | IntRect imageRect(originx, originy, numColumns, numRows); |
| 460 | RefPtr<cairo_surface_t> imageSurface = copySurfaceToImageAndAdjustRect(data.m_surface.get(), imageRect); |
| 461 | cairoSurfaceSetDeviceScale(imageSurface.get(), resolutionScale, resolutionScale); |
| 462 | originx = imageRect.x(); |
| 463 | originy = imageRect.y(); |
| 464 | if (imageSurface != data.m_surface.get()) { |
| 465 | // This cairo surface operation is done in LogicalCoordinateSystem. |
| 466 | IntRect logicalArea = intersection(logicalRect, IntRect(0, 0, logicalSize.width(), logicalSize.height())); |
| 467 | copyRectFromOneSurfaceToAnother(data.m_surface.get(), imageSurface.get(), IntSize(-logicalArea.x(), -logicalArea.y()), IntRect(IntPoint(), logicalArea.size()), IntSize(), CAIRO_OPERATOR_SOURCE); |
| 468 | } |
| 469 | |
| 470 | unsigned char* dataSrc = cairo_image_surface_get_data(imageSurface.get()); |
| 471 | unsigned char* dataDst = result->data(); |
| 472 | int stride = cairo_image_surface_get_stride(imageSurface.get()); |
| 473 | unsigned destBytesPerRow = 4 * rect.width(); |
| 474 | |
| 475 | unsigned char* destRows = dataDst + desty * destBytesPerRow + destx * 4; |
| 476 | for (int y = 0; y < numRows; ++y) { |
| 477 | unsigned* row = reinterpret_cast_ptr<unsigned*>(dataSrc + stride * (y + originy)); |
| 478 | for (int x = 0; x < numColumns; x++) { |
| 479 | int basex = x * 4; |
| 480 | unsigned* pixel = row + x + originx; |
| 481 | |
| 482 | // Avoid calling Color::colorFromPremultipliedARGB() because one |
| 483 | // function call per pixel is too expensive. |
| 484 | unsigned alpha = (*pixel & 0xFF000000) >> 24; |
| 485 | unsigned red = (*pixel & 0x00FF0000) >> 16; |
| 486 | unsigned green = (*pixel & 0x0000FF00) >> 8; |
| 487 | unsigned blue = (*pixel & 0x000000FF); |
| 488 | |
| 489 | if (premultiplied == AlphaPremultiplication::Unpremultiplied) { |
| 490 | if (alpha && alpha != 255) { |
| 491 | red = red * 255 / alpha; |
| 492 | green = green * 255 / alpha; |
| 493 | blue = blue * 255 / alpha; |
| 494 | } |
| 495 | } |
| 496 | |
| 497 | destRows[basex] = red; |
| 498 | destRows[basex + 1] = green; |
| 499 | destRows[basex + 2] = blue; |
| 500 | destRows[basex + 3] = alpha; |
| 501 | } |
| 502 | destRows += destBytesPerRow; |
| 503 | } |
| 504 | |
| 505 | return result; |
| 506 | } |
| 507 | |
| 508 | template<typename Unit> |
| 509 | inline Unit logicalUnit(const Unit& value, ImageBuffer::CoordinateSystem coordinateSystemOfValue, float resolutionScale) |
| 510 | { |
| 511 | if (coordinateSystemOfValue == ImageBuffer::LogicalCoordinateSystem || resolutionScale == 1.0) |
| 512 | return value; |
| 513 | Unit result(value); |
| 514 | result.scale(1.0 / resolutionScale); |
| 515 | return result; |
| 516 | } |
| 517 | |
| 518 | template<typename Unit> |
| 519 | inline Unit backingStoreUnit(const Unit& value, ImageBuffer::CoordinateSystem coordinateSystemOfValue, float resolutionScale) |
| 520 | { |
| 521 | if (coordinateSystemOfValue == ImageBuffer::BackingStoreCoordinateSystem || resolutionScale == 1.0) |
| 522 | return value; |
| 523 | Unit result(value); |
| 524 | result.scale(resolutionScale); |
| 525 | return result; |
| 526 | } |
| 527 | |
| 528 | RefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, IntSize* pixelArrayDimensions, CoordinateSystem coordinateSystem) const |
| 529 | { |
| 530 | IntRect logicalRect = logicalUnit(rect, coordinateSystem, m_resolutionScale); |
| 531 | IntRect backingStoreRect = backingStoreUnit(rect, coordinateSystem, m_resolutionScale); |
| 532 | if (pixelArrayDimensions) |
| 533 | *pixelArrayDimensions = backingStoreRect.size(); |
| 534 | return getImageData<AlphaPremultiplication::Unpremultiplied>(backingStoreRect, logicalRect, m_data, m_size, m_logicalSize, m_resolutionScale); |
| 535 | } |
| 536 | |
| 537 | RefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, IntSize* pixelArrayDimensions, CoordinateSystem coordinateSystem) const |
| 538 | { |
| 539 | IntRect logicalRect = logicalUnit(rect, coordinateSystem, m_resolutionScale); |
| 540 | IntRect backingStoreRect = backingStoreUnit(rect, coordinateSystem, m_resolutionScale); |
| 541 | if (pixelArrayDimensions) |
| 542 | *pixelArrayDimensions = backingStoreRect.size(); |
| 543 | return getImageData<AlphaPremultiplication::Premultiplied>(backingStoreRect, logicalRect, m_data, m_size, m_logicalSize, m_resolutionScale); |
| 544 | } |
| 545 | |
| 546 | void ImageBuffer::putByteArray(const Uint8ClampedArray& source, AlphaPremultiplication sourceFormat, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem) |
| 547 | { |
| 548 | IntRect scaledSourceRect = backingStoreUnit(sourceRect, coordinateSystem, m_resolutionScale); |
| 549 | IntSize scaledSourceSize = backingStoreUnit(sourceSize, coordinateSystem, m_resolutionScale); |
| 550 | IntPoint scaledDestPoint = backingStoreUnit(destPoint, coordinateSystem, m_resolutionScale); |
| 551 | IntRect logicalSourceRect = logicalUnit(sourceRect, coordinateSystem, m_resolutionScale); |
| 552 | IntPoint logicalDestPoint = logicalUnit(destPoint, coordinateSystem, m_resolutionScale); |
| 553 | |
| 554 | ASSERT(scaledSourceRect.width() > 0); |
| 555 | ASSERT(scaledSourceRect.height() > 0); |
| 556 | |
| 557 | int originx = scaledSourceRect.x(); |
| 558 | int destx = scaledDestPoint.x() + scaledSourceRect.x(); |
| 559 | int logicalDestx = logicalDestPoint.x() + logicalSourceRect.x(); |
| 560 | ASSERT(destx >= 0); |
| 561 | ASSERT(destx < m_size.width()); |
| 562 | ASSERT(originx >= 0); |
| 563 | ASSERT(originx <= scaledSourceRect.maxX()); |
| 564 | |
| 565 | int endx = scaledDestPoint.x() + scaledSourceRect.maxX(); |
| 566 | int logicalEndx = logicalDestPoint.x() + logicalSourceRect.maxX(); |
| 567 | ASSERT(endx <= m_size.width()); |
| 568 | |
| 569 | int numColumns = endx - destx; |
| 570 | int logicalNumColumns = logicalEndx - logicalDestx; |
| 571 | |
| 572 | int originy = scaledSourceRect.y(); |
| 573 | int desty = scaledDestPoint.y() + scaledSourceRect.y(); |
| 574 | int logicalDesty = logicalDestPoint.y() + logicalSourceRect.y(); |
| 575 | ASSERT(desty >= 0); |
| 576 | ASSERT(desty < m_size.height()); |
| 577 | ASSERT(originy >= 0); |
| 578 | ASSERT(originy <= scaledSourceRect.maxY()); |
| 579 | |
| 580 | int endy = scaledDestPoint.y() + scaledSourceRect.maxY(); |
| 581 | int logicalEndy = logicalDestPoint.y() + logicalSourceRect.maxY(); |
| 582 | ASSERT(endy <= m_size.height()); |
| 583 | int numRows = endy - desty; |
| 584 | int logicalNumRows = logicalEndy - logicalDesty; |
| 585 | |
| 586 | // The size of the derived surface is in BackingStoreCoordinateSystem. |
| 587 | // We need to set the device scale for the derived surface from this ImageBuffer. |
| 588 | IntRect imageRect(destx, desty, numColumns, numRows); |
| 589 | RefPtr<cairo_surface_t> imageSurface = copySurfaceToImageAndAdjustRect(m_data.m_surface.get(), imageRect); |
| 590 | cairoSurfaceSetDeviceScale(imageSurface.get(), m_resolutionScale, m_resolutionScale); |
| 591 | destx = imageRect.x(); |
| 592 | desty = imageRect.y(); |
| 593 | |
| 594 | uint8_t* pixelData = cairo_image_surface_get_data(imageSurface.get()); |
| 595 | |
| 596 | unsigned srcBytesPerRow = 4 * scaledSourceSize.width(); |
| 597 | int stride = cairo_image_surface_get_stride(imageSurface.get()); |
| 598 | |
| 599 | const uint8_t* srcRows = source.data() + originy * srcBytesPerRow + originx * 4; |
| 600 | for (int y = 0; y < numRows; ++y) { |
| 601 | unsigned* row = reinterpret_cast_ptr<unsigned*>(pixelData + stride * (y + desty)); |
| 602 | for (int x = 0; x < numColumns; x++) { |
| 603 | int basex = x * 4; |
| 604 | unsigned* pixel = row + x + destx; |
| 605 | |
| 606 | // Avoid calling Color::premultipliedARGBFromColor() because one |
| 607 | // function call per pixel is too expensive. |
| 608 | unsigned red = srcRows[basex]; |
| 609 | unsigned green = srcRows[basex + 1]; |
| 610 | unsigned blue = srcRows[basex + 2]; |
| 611 | unsigned alpha = srcRows[basex + 3]; |
| 612 | |
| 613 | if (sourceFormat == AlphaPremultiplication::Unpremultiplied) { |
| 614 | if (alpha != 255) { |
| 615 | red = (red * alpha + 254) / 255; |
| 616 | green = (green * alpha + 254) / 255; |
| 617 | blue = (blue * alpha + 254) / 255; |
| 618 | } |
| 619 | } |
| 620 | |
| 621 | *pixel = (alpha << 24) | red << 16 | green << 8 | blue; |
| 622 | } |
| 623 | srcRows += srcBytesPerRow; |
| 624 | } |
| 625 | |
| 626 | // This cairo surface operation is done in LogicalCoordinateSystem. |
| 627 | cairo_surface_mark_dirty_rectangle(imageSurface.get(), logicalDestx, logicalDesty, logicalNumColumns, logicalNumRows); |
| 628 | |
| 629 | if (imageSurface != m_data.m_surface.get()) { |
| 630 | // This cairo surface operation is done in LogicalCoordinateSystem. |
| 631 | copyRectFromOneSurfaceToAnother(imageSurface.get(), m_data.m_surface.get(), IntSize(), IntRect(0, 0, logicalNumColumns, logicalNumRows), IntSize(logicalDestPoint.x() + logicalSourceRect.x(), logicalDestPoint.y() + logicalSourceRect.y()), CAIRO_OPERATOR_SOURCE); |
| 632 | } |
| 633 | } |
| 634 | |
| 635 | #if !PLATFORM(GTK) |
| 636 | static cairo_status_t writeFunction(void* output, const unsigned char* data, unsigned int length) |
| 637 | { |
| 638 | if (!reinterpret_cast<Vector<uint8_t>*>(output)->tryAppend(data, length)) |
| 639 | return CAIRO_STATUS_WRITE_ERROR; |
| 640 | return CAIRO_STATUS_SUCCESS; |
| 641 | } |
| 642 | |
| 643 | static bool encodeImage(cairo_surface_t* image, const String& mimeType, Vector<uint8_t>* output) |
| 644 | { |
| 645 | ASSERT_UNUSED(mimeType, mimeType == "image/png" ); // Only PNG output is supported for now. |
| 646 | |
| 647 | return cairo_surface_write_to_png_stream(image, writeFunction, output) == CAIRO_STATUS_SUCCESS; |
| 648 | } |
| 649 | |
| 650 | String ImageBuffer::toDataURL(const String& mimeType, Optional<double> quality, PreserveResolution) const |
| 651 | { |
| 652 | Vector<uint8_t> encodedImage = toData(mimeType, quality); |
| 653 | if (encodedImage.isEmpty()) |
| 654 | return "data:," ; |
| 655 | |
| 656 | Vector<char> base64Data; |
| 657 | base64Encode(encodedImage.data(), encodedImage.size(), base64Data); |
| 658 | |
| 659 | return "data:" + mimeType + ";base64," + base64Data; |
| 660 | } |
| 661 | |
| 662 | Vector<uint8_t> ImageBuffer::toData(const String& mimeType, Optional<double>) const |
| 663 | { |
| 664 | ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); |
| 665 | |
| 666 | cairo_surface_t* image = cairo_get_target(context().platformContext()->cr()); |
| 667 | |
| 668 | Vector<uint8_t> encodedImage; |
| 669 | if (!image || !encodeImage(image, mimeType, &encodedImage)) |
| 670 | return { }; |
| 671 | |
| 672 | return encodedImage; |
| 673 | } |
| 674 | |
| 675 | #endif |
| 676 | |
| 677 | #if ENABLE(ACCELERATED_2D_CANVAS) && !USE(COORDINATED_GRAPHICS) |
| 678 | void ImageBufferData::paintToTextureMapper(TextureMapper& textureMapper, const FloatRect& targetRect, const TransformationMatrix& matrix, float opacity) |
| 679 | { |
| 680 | ASSERT(m_texture); |
| 681 | |
| 682 | // Cairo may change the active context, so we make sure to change it back after flushing. |
| 683 | GLContext* previousActiveContext = GLContext::current(); |
| 684 | cairo_surface_flush(m_surface.get()); |
| 685 | previousActiveContext->makeContextCurrent(); |
| 686 | |
| 687 | static_cast<TextureMapperGL&>(textureMapper).drawTexture(m_texture, TextureMapperGL::ShouldBlend, m_size, targetRect, matrix, opacity); |
| 688 | } |
| 689 | #endif |
| 690 | |
| 691 | PlatformLayer* ImageBuffer::platformLayer() const |
| 692 | { |
| 693 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 694 | #if USE(NICOSIA) |
| 695 | if (m_data.m_renderingMode == RenderingMode::Accelerated) |
| 696 | return m_data.m_nicosiaLayer.get(); |
| 697 | #else |
| 698 | if (m_data.m_texture) |
| 699 | return const_cast<ImageBufferData*>(&m_data); |
| 700 | #endif |
| 701 | #endif |
| 702 | return 0; |
| 703 | } |
| 704 | |
| 705 | bool ImageBuffer::copyToPlatformTexture(GraphicsContext3D&, GC3Denum target, Platform3DObject destinationTexture, GC3Denum internalformat, bool premultiplyAlpha, bool flipY) |
| 706 | { |
| 707 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 708 | ASSERT_WITH_MESSAGE(m_resolutionScale == 1.0, "Since the HiDPI Canvas feature is removed, the resolution factor here is always 1." ); |
| 709 | if (premultiplyAlpha || flipY) |
| 710 | return false; |
| 711 | |
| 712 | if (!m_data.m_texture) |
| 713 | return false; |
| 714 | |
| 715 | GC3Denum bindTextureTarget; |
| 716 | switch (target) { |
| 717 | case GL_TEXTURE_2D: |
| 718 | bindTextureTarget = GL_TEXTURE_2D; |
| 719 | break; |
| 720 | case GL_TEXTURE_CUBE_MAP_POSITIVE_X: |
| 721 | case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: |
| 722 | case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: |
| 723 | case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: |
| 724 | case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: |
| 725 | case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: |
| 726 | bindTextureTarget = GL_TEXTURE_CUBE_MAP; |
| 727 | break; |
| 728 | default: |
| 729 | return false; |
| 730 | } |
| 731 | |
| 732 | cairo_surface_flush(m_data.m_surface.get()); |
| 733 | |
| 734 | std::unique_ptr<GLContext> context = GLContext::createOffscreenContext(&PlatformDisplay::sharedDisplayForCompositing()); |
| 735 | context->makeContextCurrent(); |
| 736 | uint32_t fbo; |
| 737 | glGenFramebuffers(1, &fbo); |
| 738 | glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| 739 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_data.m_texture, 0); |
| 740 | glBindTexture(bindTextureTarget, destinationTexture); |
| 741 | glCopyTexImage2D(target, 0, internalformat, 0, 0, m_size.width(), m_size.height(), 0); |
| 742 | glBindTexture(bindTextureTarget, 0); |
| 743 | glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| 744 | glFlush(); |
| 745 | glDeleteFramebuffers(1, &fbo); |
| 746 | return true; |
| 747 | #else |
| 748 | UNUSED_PARAM(target); |
| 749 | UNUSED_PARAM(destinationTexture); |
| 750 | UNUSED_PARAM(internalformat); |
| 751 | UNUSED_PARAM(premultiplyAlpha); |
| 752 | UNUSED_PARAM(flipY); |
| 753 | return false; |
| 754 | #endif |
| 755 | } |
| 756 | |
| 757 | } // namespace WebCore |
| 758 | |
| 759 | #endif // USE(CAIRO) |
| 760 | |