2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #include "HTMLCanvasElement.h"
31 #include "Attribute.h"
32 #include "CanvasContextAttributes.h"
33 #include "CanvasGradient.h"
34 #include "CanvasPattern.h"
35 #include "CanvasRenderingContext2D.h"
38 #include "ExceptionCode.h"
40 #include "GraphicsContext.h"
41 #include "HTMLNames.h"
42 #include "ImageBuffer.h"
43 #include "ImageData.h"
44 #include "MIMETypeRegistry.h"
46 #include "RenderHTMLCanvas.h"
47 #include "ScriptController.h"
52 #include <runtime/JSLock.h>
53 #include <runtime/Operations.h>
56 #include "WebGLContextAttributes.h"
57 #include "WebGLRenderingContext.h"
62 using namespace HTMLNames;
64 // These values come from the WhatWG spec.
65 static const int DefaultWidth = 300;
66 static const int DefaultHeight = 150;
68 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
69 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
70 // in exchange for a smaller maximum canvas size.
71 static const float MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
73 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* document)
74 : HTMLElement(tagName, document)
75 , m_size(DefaultWidth, DefaultHeight)
76 , m_rendererIsCanvas(false)
77 , m_ignoreReset(false)
78 , m_deviceScaleFactor(targetDeviceScaleFactor())
80 , m_hasCreatedImageBuffer(false)
81 , m_didClearImageBuffer(false)
83 ASSERT(hasTagName(canvasTag));
86 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document)
88 return adoptRef(new HTMLCanvasElement(canvasTag, document));
91 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document)
93 return adoptRef(new HTMLCanvasElement(tagName, document));
96 HTMLCanvasElement::~HTMLCanvasElement()
98 HashSet<CanvasObserver*>::iterator end = m_observers.end();
99 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
100 (*it)->canvasDestroyed(this);
102 m_context.clear(); // Ensure this goes away before the ImageBuffer.
105 void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
107 if (name == widthAttr || name == heightAttr)
109 HTMLElement::parseAttribute(name, value);
112 RenderObject* HTMLCanvasElement::createRenderer(RenderArena* arena, RenderStyle* style)
114 Frame* frame = document()->frame();
115 if (frame && frame->script()->canExecuteScripts(NotAboutToExecuteScript)) {
116 m_rendererIsCanvas = true;
117 return new (arena) RenderHTMLCanvas(this);
120 m_rendererIsCanvas = false;
121 return HTMLElement::createRenderer(arena, style);
124 void HTMLCanvasElement::attach(const AttachContext& context)
126 setIsInCanvasSubtree(true);
127 HTMLElement::attach(context);
130 bool HTMLCanvasElement::areAuthorShadowsAllowed() const
135 bool HTMLCanvasElement::canContainRangeEndPoint() const
140 bool HTMLCanvasElement::canStartSelection() const
145 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
147 m_observers.add(observer);
150 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
152 m_observers.remove(observer);
155 void HTMLCanvasElement::setHeight(int value)
157 setAttribute(heightAttr, String::number(value));
160 void HTMLCanvasElement::setWidth(int value)
162 setAttribute(widthAttr, String::number(value));
166 static bool requiresAcceleratedCompositingForWebGL()
168 #if PLATFORM(GTK) || PLATFORM(EFL) || PLATFORM(QT)
175 static bool shouldEnableWebGL(Settings* settings)
180 if (!settings->webGLEnabled())
183 if (!requiresAcceleratedCompositingForWebGL())
186 return settings->acceleratedCompositingEnabled();
190 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
192 // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
193 // context is already 2D, just return that. If the existing context is WebGL, then destroy it
194 // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
195 // context with any other type string will destroy any existing context.
197 // FIXME: The code depends on the context not going away once created, to prevent JS from
198 // seeing a dangling pointer. So for now we will disallow the context from being changed
199 // once it is created. https://bugs.webkit.org/show_bug.cgi?id=117095
200 if (is2dType(type)) {
201 if (m_context && !m_context->is2d())
204 bool usesDashbardCompatibilityMode = false;
205 #if ENABLE(DASHBOARD_SUPPORT)
206 if (Settings* settings = document()->settings())
207 usesDashbardCompatibilityMode = settings->usesDashboardBackwardCompatibilityMode();
209 m_context = CanvasRenderingContext2D::create(this, document()->inQuirksMode(), usesDashbardCompatibilityMode);
210 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
211 // Need to make sure a RenderLayer and compositing layer get created for the Canvas
212 setNeedsStyleRecalc(SyntheticStyleChange);
215 return m_context.get();
218 if (shouldEnableWebGL(document()->settings())) {
220 if (is3dType(type)) {
221 if (m_context && !m_context->is3d())
224 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
226 // Need to make sure a RenderLayer and compositing layer get created for the Canvas
227 setNeedsStyleRecalc(SyntheticStyleChange);
230 return m_context.get();
239 bool HTMLCanvasElement::supportsContext(const String& type, CanvasContextAttributes*)
241 // FIXME: Provide implementation that accounts for attributes. Bugzilla bug 117093
242 // https://bugs.webkit.org/show_bug.cgi?id=117093
244 // FIXME: The code depends on the context not going away once created (as getContext
245 // is implemented under this assumption) https://bugs.webkit.org/show_bug.cgi?id=117095
247 return !m_context || m_context->is2d();
250 if (shouldEnableWebGL(document()->settings())) {
252 return !m_context || m_context->is3d();
258 bool HTMLCanvasElement::is2dType(const String& type)
264 bool HTMLCanvasElement::is3dType(const String& type)
266 // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
267 return type == "webkit-3d" || type == "experimental-webgl";
271 void HTMLCanvasElement::didDraw(const FloatRect& rect)
275 if (RenderBox* ro = renderBox()) {
276 FloatRect destRect = ro->contentBoxRect();
277 FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
278 r.intersect(destRect);
279 if (r.isEmpty() || m_dirtyRect.contains(r))
282 m_dirtyRect.unite(r);
283 ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
286 notifyObserversCanvasChanged(rect);
289 void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
291 HashSet<CanvasObserver*>::iterator end = m_observers.end();
292 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
293 (*it)->canvasChanged(this, rect);
296 void HTMLCanvasElement::reset()
302 bool hadImageBuffer = hasCreatedImageBuffer();
304 int w = getAttribute(widthAttr).toInt(&ok);
308 int h = getAttribute(heightAttr).toInt(&ok);
312 if (m_contextStateSaver) {
313 // Reset to the initial graphics context state.
314 m_contextStateSaver->restore();
315 m_contextStateSaver->save();
318 if (m_context && m_context->is2d()) {
319 CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
323 IntSize oldSize = size();
324 IntSize newSize(w, h);
325 float newDeviceScaleFactor = targetDeviceScaleFactor();
327 // If the size of an existing buffer matches, we can just clear it instead of reallocating.
328 // This optimization is only done for 2D canvases for now.
329 if (m_hasCreatedImageBuffer && oldSize == newSize && m_deviceScaleFactor == newDeviceScaleFactor && m_context && m_context->is2d()) {
330 if (!m_didClearImageBuffer)
335 m_deviceScaleFactor = newDeviceScaleFactor;
337 setSurfaceSize(newSize);
340 if (m_context && m_context->is3d() && oldSize != size())
341 static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height());
344 if (RenderObject* renderer = this->renderer()) {
345 if (m_rendererIsCanvas) {
346 if (oldSize != size()) {
347 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
348 #if USE(ACCELERATED_COMPOSITING)
349 if (renderBox() && renderBox()->hasAcceleratedCompositing())
350 renderBox()->contentChanged(CanvasChanged);
358 HashSet<CanvasObserver*>::iterator end = m_observers.end();
359 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
360 (*it)->canvasResized(this);
363 float HTMLCanvasElement::targetDeviceScaleFactor() const
365 #if ENABLE(HIGH_DPI_CANVAS)
366 return document()->frame() ? document()->frame()->page()->deviceScaleFactor() : 1;
372 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
375 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
376 if (m_context->is2d())
380 #if USE(ACCELERATED_COMPOSITING)
381 if (!m_context->isAccelerated())
384 if (renderBox() && renderBox()->hasAcceleratedCompositing())
391 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
393 // Clear the dirty rect
394 m_dirtyRect = FloatRect();
396 if (context->paintingDisabled())
400 if (!paintsIntoCanvasBuffer() && !document()->printing())
402 m_context->paintRenderingResultsToCanvas();
405 if (hasCreatedImageBuffer()) {
406 ImageBuffer* imageBuffer = buffer();
408 if (m_presentedImage)
409 context->drawImage(m_presentedImage.get(), ColorSpaceDeviceRGB, pixelSnappedIntRect(r), CompositeSourceOver, DoNotRespectImageOrientation, useLowQualityScale);
411 context->drawImageBuffer(imageBuffer, ColorSpaceDeviceRGB, pixelSnappedIntRect(r), CompositeSourceOver, BlendModeNormal, useLowQualityScale);
417 static_cast<WebGLRenderingContext*>(m_context.get())->markLayerComposited();
422 bool HTMLCanvasElement::is3D() const
424 return m_context && m_context->is3d();
428 void HTMLCanvasElement::makeRenderingResultsAvailable()
431 m_context->paintRenderingResultsToCanvas();
434 void HTMLCanvasElement::makePresentationCopy()
436 if (!m_presentedImage) {
437 // The buffer contains the last presented data, so save a copy of it.
438 m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
442 void HTMLCanvasElement::clearPresentationCopy()
444 m_presentedImage.clear();
447 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
450 m_hasCreatedImageBuffer = false;
451 m_contextStateSaver.clear();
452 m_imageBuffer.clear();
456 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
458 String lowercaseMimeType = mimeType.lower();
460 // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
461 if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
462 lowercaseMimeType = "image/png";
464 return lowercaseMimeType;
467 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionCode& ec)
469 if (!m_originClean) {
474 if (m_size.isEmpty() || !buffer())
475 return String("data:,");
477 String encodingMimeType = toEncodingMimeType(mimeType);
480 // Try to get ImageData first, as that may avoid lossy conversions.
481 RefPtr<ImageData> imageData = getImageData();
484 return ImageDataToDataURL(*imageData, encodingMimeType, quality);
487 makeRenderingResultsAvailable();
489 return buffer()->toDataURL(encodingMimeType, quality);
492 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
494 if (!m_context || !m_context->is3d())
498 WebGLRenderingContext* ctx = static_cast<WebGLRenderingContext*>(m_context.get());
500 return ctx->paintRenderingResultsToImageData();
506 FloatRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
508 FloatRect deviceRect(logicalRect);
509 deviceRect.scale(m_deviceScaleFactor);
511 float x = floorf(deviceRect.x());
512 float y = floorf(deviceRect.y());
513 float w = ceilf(deviceRect.maxX() - x);
514 float h = ceilf(deviceRect.maxY() - y);
517 deviceRect.setWidth(w);
518 deviceRect.setHeight(h);
523 FloatSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
525 float width = ceilf(logicalSize.width() * m_deviceScaleFactor);
526 float height = ceilf(logicalSize.height() * m_deviceScaleFactor);
527 return FloatSize(width, height);
530 FloatSize HTMLCanvasElement::convertDeviceToLogical(const FloatSize& deviceSize) const
532 float width = ceilf(deviceSize.width() / m_deviceScaleFactor);
533 float height = ceilf(deviceSize.height() / m_deviceScaleFactor);
534 return FloatSize(width, height);
537 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
539 return document()->securityOrigin();
542 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
544 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
546 return document()->settings() && document()->settings()->canvasUsesAcceleratedDrawing();
547 #elif ENABLE(ACCELERATED_2D_CANVAS)
548 if (m_context && !m_context->is2d())
551 Settings* settings = document()->settings();
552 if (!settings || !settings->accelerated2dCanvasEnabled())
555 // Do not use acceleration for small canvas.
556 if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
566 void HTMLCanvasElement::createImageBuffer() const
568 ASSERT(!m_imageBuffer);
570 m_hasCreatedImageBuffer = true;
571 m_didClearImageBuffer = true;
573 FloatSize logicalSize = size();
574 FloatSize deviceSize = convertLogicalToDevice(logicalSize);
575 if (!deviceSize.isExpressibleAsIntSize())
578 if (deviceSize.width() * deviceSize.height() > MaxCanvasArea)
581 IntSize bufferSize(deviceSize.width(), deviceSize.height());
582 if (!bufferSize.width() || !bufferSize.height())
585 RenderingMode renderingMode = shouldAccelerate(bufferSize) ? Accelerated : Unaccelerated;
586 m_imageBuffer = ImageBuffer::create(size(), m_deviceScaleFactor, ColorSpaceDeviceRGB, renderingMode);
589 m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
590 m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
591 if (document()->settings() && !document()->settings()->antialiased2dCanvasEnabled())
592 m_imageBuffer->context()->setShouldAntialias(false);
593 m_imageBuffer->context()->setStrokeThickness(1);
594 m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
596 JSC::JSLockHolder lock(scriptExecutionContext()->vm());
597 size_t numBytes = 4 * m_imageBuffer->internalSize().width() * m_imageBuffer->internalSize().height();
598 scriptExecutionContext()->vm()->heap.reportExtraMemoryCost(numBytes);
600 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
601 if (m_context && m_context->is2d())
602 // Recalculate compositing requirements if acceleration state changed.
603 const_cast<HTMLCanvasElement*>(this)->setNeedsStyleRecalc(SyntheticStyleChange);
607 GraphicsContext* HTMLCanvasElement::drawingContext() const
609 return buffer() ? m_imageBuffer->context() : 0;
612 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
614 if (!m_hasCreatedImageBuffer)
617 return drawingContext();
620 ImageBuffer* HTMLCanvasElement::buffer() const
622 if (!m_hasCreatedImageBuffer)
624 return m_imageBuffer.get();
627 Image* HTMLCanvasElement::copiedImage() const
629 if (!m_copiedImage && buffer()) {
631 m_context->paintRenderingResultsToCanvas();
632 m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
634 return m_copiedImage.get();
637 void HTMLCanvasElement::clearImageBuffer() const
639 ASSERT(m_hasCreatedImageBuffer);
640 ASSERT(!m_didClearImageBuffer);
643 m_didClearImageBuffer = true;
645 if (m_context->is2d()) {
646 CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
647 // No need to undo transforms/clip/etc. because we are called right after the context is reset.
648 context2D->clearRect(0, 0, width(), height());
652 void HTMLCanvasElement::clearCopiedImage()
654 m_copiedImage.clear();
655 m_didClearImageBuffer = false;
658 AffineTransform HTMLCanvasElement::baseTransform() const
660 ASSERT(m_hasCreatedImageBuffer);
661 FloatSize unscaledSize = size();
662 FloatSize deviceSize = convertLogicalToDevice(unscaledSize);
663 IntSize size(deviceSize.width(), deviceSize.height());
664 AffineTransform transform;
665 if (size.width() && size.height())
666 transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
667 return m_imageBuffer->baseTransform() * transform;