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 "core/html/HTMLCanvasElement.h"
31 #include "HTMLNames.h"
32 #include "RuntimeEnabledFeatures.h"
33 #include "bindings/v8/ExceptionMessages.h"
34 #include "bindings/v8/ExceptionState.h"
35 #include "bindings/v8/ScriptController.h"
36 #include "core/dom/Document.h"
37 #include "core/dom/ExceptionCode.h"
38 #include "core/frame/LocalFrame.h"
39 #include "core/frame/Settings.h"
40 #include "core/html/ImageData.h"
41 #include "core/html/canvas/Canvas2DContextAttributes.h"
42 #include "core/html/canvas/CanvasRenderingContext2D.h"
43 #include "core/html/canvas/WebGLContextAttributes.h"
44 #include "core/html/canvas/WebGLContextEvent.h"
45 #include "core/html/canvas/WebGLRenderingContext.h"
46 #include "core/rendering/RenderHTMLCanvas.h"
47 #include "platform/MIMETypeRegistry.h"
48 #include "platform/graphics/Canvas2DImageBufferSurface.h"
49 #include "platform/graphics/GraphicsContextStateSaver.h"
50 #include "platform/graphics/ImageBuffer.h"
51 #include "platform/graphics/UnacceleratedImageBufferSurface.h"
52 #include "platform/graphics/gpu/WebGLImageBufferSurface.h"
53 #include "platform/transforms/AffineTransform.h"
54 #include "public/platform/Platform.h"
59 using namespace HTMLNames;
61 // These values come from the WhatWG spec.
62 static const int DefaultWidth = 300;
63 static const int DefaultHeight = 150;
65 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
66 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
67 // in exchange for a smaller maximum canvas size.
68 static const int MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
70 //In Skia, we will also limit width/height to 32767.
71 static const int MaxSkiaDim = 32767; // Maximum width/height in CSS pixels.
73 HTMLCanvasElement::HTMLCanvasElement(Document& document)
74 : HTMLElement(canvasTag, document)
75 , DocumentVisibilityObserver(document)
76 , m_size(DefaultWidth, DefaultHeight)
77 , m_ignoreReset(false)
78 , m_accelerationDisabled(false)
79 , m_externallyAllocatedMemory(0)
81 , m_didFailToCreateImageBuffer(false)
82 , m_didClearImageBuffer(false)
84 ScriptWrappable::init(this);
87 PassRefPtrWillBeRawPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
89 return adoptRefWillBeRefCountedGarbageCollected(new HTMLCanvasElement(document));
92 HTMLCanvasElement::~HTMLCanvasElement()
94 v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(-m_externallyAllocatedMemory);
95 HashSet<CanvasObserver*>::iterator end = m_observers.end();
96 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
97 (*it)->canvasDestroyed(this);
100 m_context.clear(); // Ensure this goes away before the ImageBuffer.
104 void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
106 if (name == widthAttr || name == heightAttr)
108 HTMLElement::parseAttribute(name, value);
111 RenderObject* HTMLCanvasElement::createRenderer(RenderStyle* style)
113 LocalFrame* frame = document().frame();
114 if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript))
115 return new RenderHTMLCanvas(this);
116 return HTMLElement::createRenderer(style);
119 Node::InsertionNotificationRequest HTMLCanvasElement::insertedInto(ContainerNode* node)
121 setIsInCanvasSubtree(true);
122 return HTMLElement::insertedInto(node);
125 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
127 m_observers.add(observer);
130 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
132 m_observers.remove(observer);
135 void HTMLCanvasElement::setHeight(int value)
137 setIntegralAttribute(heightAttr, value);
140 void HTMLCanvasElement::setWidth(int value)
142 setIntegralAttribute(widthAttr, value);
145 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
147 // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
148 // context is already 2D, just return that. If the existing context is WebGL, then destroy it
149 // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
150 // context with any other type string will destroy any existing context.
154 ContextExperimentalWebgl,
156 // Only add new items to the end and keep the order of existing items.
160 // FIXME - The code depends on the context not going away once created, to prevent JS from
161 // seeing a dangling pointer. So for now we will disallow the context from being changed
162 // once it is created.
164 if (m_context && !m_context->is2d())
167 blink::Platform::current()->histogramEnumeration("Canvas.ContextType", Context2d, ContextTypeCount);
168 m_context = CanvasRenderingContext2D::create(this, static_cast<Canvas2DContextAttributes*>(attrs), document().inQuirksMode());
169 setNeedsCompositingUpdate();
171 return m_context.get();
174 // Accept the the provisional "experimental-webgl" or official "webgl" context ID.
175 ContextType contextType;
176 bool is3dContext = true;
177 if (type == "experimental-webgl")
178 contextType = ContextExperimentalWebgl;
179 else if (type == "webgl")
180 contextType = ContextWebgl;
185 if (m_context && !m_context->is3d()) {
186 dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, "Canvas has an existing, non-WebGL context"));
190 blink::Platform::current()->histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount);
191 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
192 setNeedsCompositingUpdate();
193 updateExternallyAllocatedMemory();
195 return m_context.get();
200 void HTMLCanvasElement::didDraw(const FloatRect& rect)
204 if (RenderBox* ro = renderBox()) {
205 FloatRect destRect = ro->contentBoxRect();
206 FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
207 r.intersect(destRect);
208 if (r.isEmpty() || m_dirtyRect.contains(r))
211 m_dirtyRect.unite(r);
212 ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
215 notifyObserversCanvasChanged(rect);
218 void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
220 HashSet<CanvasObserver*>::iterator end = m_observers.end();
221 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
222 (*it)->canvasChanged(this, rect);
225 void HTMLCanvasElement::reset()
231 bool hadImageBuffer = hasImageBuffer();
233 int w = getAttribute(widthAttr).toInt(&ok);
237 int h = getAttribute(heightAttr).toInt(&ok);
241 if (m_contextStateSaver) {
242 // Reset to the initial graphics context state.
243 m_contextStateSaver->restore();
244 m_contextStateSaver->save();
247 if (m_context && m_context->is2d())
248 toCanvasRenderingContext2D(m_context.get())->reset();
250 IntSize oldSize = size();
251 IntSize newSize(w, h);
253 // If the size of an existing buffer matches, we can just clear it instead of reallocating.
254 // This optimization is only done for 2D canvases for now.
255 if (hadImageBuffer && oldSize == newSize && m_context && m_context->is2d()) {
256 if (!m_didClearImageBuffer)
261 setSurfaceSize(newSize);
263 if (m_context && m_context->is3d() && oldSize != size())
264 toWebGLRenderingContext(m_context.get())->reshape(width(), height());
266 if (RenderObject* renderer = this->renderer()) {
267 if (renderer->isCanvas()) {
268 if (oldSize != size()) {
269 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
270 if (renderBox() && renderBox()->hasAcceleratedCompositing())
271 renderBox()->contentChanged(CanvasChanged);
278 HashSet<CanvasObserver*>::iterator end = m_observers.end();
279 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
280 (*it)->canvasResized(this);
283 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
287 if (!m_context->isAccelerated())
290 if (renderBox() && renderBox()->hasAcceleratedCompositing())
297 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r)
299 // Clear the dirty rect
300 m_dirtyRect = FloatRect();
302 if (context->paintingDisabled())
306 if (!paintsIntoCanvasBuffer() && !document().printing())
308 m_context->paintRenderingResultsToCanvas();
311 if (hasImageBuffer()) {
312 ImageBuffer* imageBuffer = buffer();
314 CompositeOperator compositeOperator = !m_context || m_context->hasAlpha() ? CompositeSourceOver : CompositeCopy;
315 if (m_presentedImage)
316 context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation);
318 context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), 0, compositeOperator);
321 // When alpha is false, we should draw to opaque black.
322 if (m_context && !m_context->hasAlpha())
323 context->fillRect(FloatRect(0, 0, width(), height()), Color(0, 0, 0));
327 toWebGLRenderingContext(m_context.get())->markLayerComposited();
330 bool HTMLCanvasElement::is3D() const
332 return m_context && m_context->is3d();
335 void HTMLCanvasElement::makePresentationCopy()
337 if (!m_presentedImage) {
338 // The buffer contains the last presented data, so save a copy of it.
339 m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
340 updateExternallyAllocatedMemory();
344 void HTMLCanvasElement::clearPresentationCopy()
346 m_presentedImage.clear();
347 updateExternallyAllocatedMemory();
350 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
353 m_didFailToCreateImageBuffer = false;
354 discardImageBuffer();
356 if (m_context && m_context->is2d()) {
357 CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get());
358 if (context2d->isContextLost()) {
359 context2d->restoreContext();
364 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
366 String lowercaseMimeType = mimeType.lower();
368 // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
369 if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
370 lowercaseMimeType = "image/png";
372 return lowercaseMimeType;
375 const AtomicString HTMLCanvasElement::imageSourceURL() const
377 return AtomicString(toDataURLInternal("image/png", 0));
380 String HTMLCanvasElement::toDataURLInternal(const String& mimeType, const double* quality) const
382 if (m_size.isEmpty() || !buffer())
383 return String("data:,");
385 String encodingMimeType = toEncodingMimeType(mimeType);
387 // Try to get ImageData first, as that may avoid lossy conversions.
388 RefPtrWillBeRawPtr<ImageData> imageData = getImageData();
391 return ImageDataToDataURL(ImageDataBuffer(imageData->size(), imageData->data()), encodingMimeType, quality);
394 m_context->paintRenderingResultsToCanvas();
396 return buffer()->toDataURL(encodingMimeType, quality);
399 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionState& exceptionState) const
401 if (!m_originClean) {
402 exceptionState.throwSecurityError("Tainted canvases may not be exported.");
406 return toDataURLInternal(mimeType, quality);
409 PassRefPtrWillBeRawPtr<ImageData> HTMLCanvasElement::getImageData() const
411 if (!m_context || !m_context->is3d())
413 return toWebGLRenderingContext(m_context.get())->paintRenderingResultsToImageData();
416 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
418 return document().securityOrigin();
421 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
423 if (m_context && !m_context->is2d())
426 if (m_accelerationDisabled)
429 Settings* settings = document().settings();
430 if (!settings || !settings->accelerated2dCanvasEnabled())
433 // Do not use acceleration for small canvas.
434 if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
437 if (!blink::Platform::current()->canAccelerate2dCanvas())
443 PassOwnPtr<ImageBufferSurface> HTMLCanvasElement::createImageBufferSurface(const IntSize& deviceSize, int* msaaSampleCount)
445 OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
447 *msaaSampleCount = 0;
449 return adoptPtr(new WebGLImageBufferSurface(size(), opacityMode));
451 if (shouldAccelerate(deviceSize)) {
452 if (document().settings())
453 *msaaSampleCount = document().settings()->accelerated2dCanvasMSAASampleCount();
454 OwnPtr<ImageBufferSurface> surface = adoptPtr(new Canvas2DImageBufferSurface(size(), opacityMode, *msaaSampleCount));
455 if (surface->isValid())
456 return surface.release();
459 return adoptPtr(new UnacceleratedImageBufferSurface(size(), opacityMode));
462 void HTMLCanvasElement::createImageBuffer()
464 createImageBufferInternal();
465 if (m_didFailToCreateImageBuffer && m_context && m_context->is2d())
466 toCanvasRenderingContext2D(m_context.get())->loseContext();
469 void HTMLCanvasElement::createImageBufferInternal()
471 ASSERT(!m_imageBuffer);
472 ASSERT(!m_contextStateSaver);
474 m_didFailToCreateImageBuffer = true;
475 m_didClearImageBuffer = true;
477 IntSize deviceSize = size();
478 if (deviceSize.width() * deviceSize.height() > MaxCanvasArea)
481 if (deviceSize.width() > MaxSkiaDim || deviceSize.height() > MaxSkiaDim)
484 if (!deviceSize.width() || !deviceSize.height())
488 OwnPtr<ImageBufferSurface> surface = createImageBufferSurface(deviceSize, &msaaSampleCount);
489 if (!surface->isValid())
492 m_imageBuffer = ImageBuffer::create(surface.release());
493 m_imageBuffer->setClient(this);
495 m_didFailToCreateImageBuffer = false;
497 updateExternallyAllocatedMemory();
500 // Early out for WebGL canvases
504 m_imageBuffer->setClient(this);
505 m_imageBuffer->context()->setShouldClampToSourceRect(false);
506 m_imageBuffer->context()->disableAntialiasingOptimizationForHairlineImages();
507 m_imageBuffer->context()->setImageInterpolationQuality(CanvasDefaultInterpolationQuality);
508 // Enabling MSAA overrides a request to disable antialiasing. This is true regardless of whether the
509 // rendering mode is accelerated or not. For consistency, we don't want to apply AA in accelerated
510 // canvases but not in unaccelerated canvases.
511 if (!msaaSampleCount && document().settings() && !document().settings()->antialiased2dCanvasEnabled())
512 m_imageBuffer->context()->setShouldAntialias(false);
513 // GraphicsContext's defaults don't always agree with the 2d canvas spec.
514 // See CanvasRenderingContext2D::State::State() for more information.
515 m_imageBuffer->context()->setMiterLimit(10);
516 m_imageBuffer->context()->setStrokeThickness(1);
517 m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
520 setNeedsCompositingUpdate();
523 void HTMLCanvasElement::notifySurfaceInvalid()
525 if (m_context && m_context->is2d()) {
526 CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get());
527 context2d->loseContext();
531 void HTMLCanvasElement::trace(Visitor* visitor)
533 visitor->trace(m_context);
534 HTMLElement::trace(visitor);
537 void HTMLCanvasElement::updateExternallyAllocatedMemory() const
546 if (m_presentedImage)
549 Checked<intptr_t, RecordOverflow> checkedExternallyAllocatedMemory = 4 * bufferCount;
550 checkedExternallyAllocatedMemory *= width();
551 checkedExternallyAllocatedMemory *= height();
552 intptr_t externallyAllocatedMemory;
553 if (checkedExternallyAllocatedMemory.safeGet(externallyAllocatedMemory) == CheckedState::DidOverflow)
554 externallyAllocatedMemory = std::numeric_limits<intptr_t>::max();
556 // Subtracting two intptr_t that are known to be positive will never underflow.
557 v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(externallyAllocatedMemory - m_externallyAllocatedMemory);
558 m_externallyAllocatedMemory = externallyAllocatedMemory;
561 GraphicsContext* HTMLCanvasElement::drawingContext() const
563 return buffer() ? m_imageBuffer->context() : 0;
566 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
568 if (m_didFailToCreateImageBuffer) {
569 ASSERT(!hasImageBuffer());
573 return drawingContext();
576 ImageBuffer* HTMLCanvasElement::buffer() const
578 if (!hasImageBuffer() && !m_didFailToCreateImageBuffer)
579 const_cast<HTMLCanvasElement*>(this)->createImageBuffer();
580 return m_imageBuffer.get();
583 void HTMLCanvasElement::ensureUnacceleratedImageBuffer()
585 if ((hasImageBuffer() && !m_imageBuffer->isAccelerated()) || m_didFailToCreateImageBuffer)
587 discardImageBuffer();
588 OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
589 m_imageBuffer = ImageBuffer::create(size(), opacityMode);
590 m_didFailToCreateImageBuffer = !m_imageBuffer;
593 Image* HTMLCanvasElement::copiedImage() const
595 if (!m_copiedImage && buffer()) {
597 m_context->paintRenderingResultsToCanvas();
598 m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
599 updateExternallyAllocatedMemory();
601 return m_copiedImage.get();
604 void HTMLCanvasElement::clearImageBuffer()
606 ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer);
607 ASSERT(!m_didClearImageBuffer);
610 m_didClearImageBuffer = true;
612 if (m_context->is2d()) {
613 // No need to undo transforms/clip/etc. because we are called right
614 // after the context is reset.
615 toCanvasRenderingContext2D(m_context.get())->clearRect(0, 0, width(), height());
619 void HTMLCanvasElement::discardImageBuffer()
621 m_contextStateSaver.clear(); // uses context owned by m_imageBuffer
622 m_imageBuffer.clear();
623 updateExternallyAllocatedMemory();
626 bool HTMLCanvasElement::hasValidImageBuffer() const
628 return m_imageBuffer && m_imageBuffer->isSurfaceValid();
631 void HTMLCanvasElement::clearCopiedImage()
633 m_copiedImage.clear();
634 m_didClearImageBuffer = false;
635 updateExternallyAllocatedMemory();
638 AffineTransform HTMLCanvasElement::baseTransform() const
640 ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer);
641 return m_imageBuffer->baseTransform();
644 void HTMLCanvasElement::didChangeVisibilityState(PageVisibilityState visibility)
646 if (hasImageBuffer()) {
647 bool hidden = visibility != PageVisibilityStateVisible;
651 discardImageBuffer();
654 if (hasImageBuffer()) {
655 m_imageBuffer->setIsHidden(hidden);
660 void HTMLCanvasElement::didMoveToNewDocument(Document& oldDocument)
662 setObservedDocument(document());
663 HTMLElement::didMoveToNewDocument(oldDocument);
666 PassRefPtr<Image> HTMLCanvasElement::getSourceImageForCanvas(SourceImageMode mode, SourceImageStatus* status) const
668 if (!width() || !height()) {
669 *status = ZeroSizeCanvasSourceImageStatus;
674 *status = InvalidSourceImageStatus;
678 if (mode == CopySourceImageIfVolatile) {
679 *status = NormalSourceImageStatus;
680 return copiedImage();
683 if (m_context && m_context->is3d()) {
684 m_context->paintRenderingResultsToCanvas();
685 *status = ExternalSourceImageStatus;
687 *status = NormalSourceImageStatus;
689 return m_imageBuffer->copyImage(DontCopyBackingStore, Unscaled);
692 bool HTMLCanvasElement::wouldTaintOrigin(SecurityOrigin*) const
694 return !originClean();
697 FloatSize HTMLCanvasElement::sourceSize() const
699 return FloatSize(width(), height());