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 | |