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
79namespace WebCore {
80
81ImageBufferData::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
103ImageBufferData::~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)
131void 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)
151RefPtr<TextureMapperPlatformLayerProxy> ImageBufferData::proxy() const
152{
153 return m_platformLayerProxy.copyRef();
154}
155#endif
156
157void 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
189void 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
199void 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
227static RefPtr<cairo_surface_t>
228cairoSurfaceCoerceToImage(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
246Vector<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
262ImageBuffer::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
315ImageBuffer::~ImageBuffer() = default;
316
317std::unique_ptr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, const GraphicsContext& context)
318{
319 return createCompatibleBuffer(size, ColorSpaceSRGB, context);
320}
321
322GraphicsContext& ImageBuffer::context() const
323{
324 return *m_data.m_context;
325}
326
327RefPtr<Image> ImageBuffer::sinkIntoImage(std::unique_ptr<ImageBuffer> imageBuffer, PreserveResolution preserveResolution)
328{
329 return imageBuffer->copyImage(DontCopyBackingStore, preserveResolution);
330}
331
332RefPtr<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
342BackingStoreCopy ImageBuffer::fastCopyImageMode()
343{
344 return DontCopyBackingStore;
345}
346
347void 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
352void 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
360void 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
367void 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
390RefPtr<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
404template <AlphaPremultiplication premultiplied>
405RefPtr<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
508template<typename Unit>
509inline 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
518template<typename Unit>
519inline 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
528RefPtr<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
537RefPtr<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
546void 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)
636static 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
643static 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
650String 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
662Vector<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)
678void 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
691PlatformLayer* 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
705bool 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