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"
36 #include "CanvasStyle.h"
39 #include "ExceptionCode.h"
41 #include "GraphicsContext.h"
42 #include "HTMLNames.h"
43 #include "ImageBuffer.h"
44 #include "ImageData.h"
45 #include "MIMETypeRegistry.h"
47 #include "RenderHTMLCanvas.h"
53 #include <runtime/JSLock.h>
57 #include "WebGLContextAttributes.h"
58 #include "WebGLRenderingContext.h"
63 using namespace HTMLNames;
65 // These values come from the WhatWG spec.
66 static const int DefaultWidth = 300;
67 static const int DefaultHeight = 150;
69 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
70 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
71 // in exchange for a smaller maximum canvas size.
72 static const float MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
74 //In Skia, we will also limit width/height to 32767.
75 static const float MaxSkiaDim = 32767.0F; // Maximum width/height in CSS pixels.
77 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* document)
78 : HTMLElement(tagName, document)
79 , m_size(DefaultWidth, DefaultHeight)
80 , m_rendererIsCanvas(false)
81 , m_ignoreReset(false)
82 , m_pageScaleFactor(document->frame() ? document->frame()->page()->chrome()->scaleFactor() : 1)
84 , m_hasCreatedImageBuffer(false)
86 ASSERT(hasTagName(canvasTag));
89 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document)
91 return adoptRef(new HTMLCanvasElement(canvasTag, document));
94 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document)
96 return adoptRef(new HTMLCanvasElement(tagName, document));
99 HTMLCanvasElement::~HTMLCanvasElement()
101 HashSet<CanvasObserver*>::iterator end = m_observers.end();
102 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
103 (*it)->canvasDestroyed(this);
106 void HTMLCanvasElement::parseMappedAttribute(Attribute* attr)
108 const QualifiedName& attrName = attr->name();
109 if (attrName == widthAttr || attrName == heightAttr)
111 HTMLElement::parseMappedAttribute(attr);
114 RenderObject* HTMLCanvasElement::createRenderer(RenderArena* arena, RenderStyle* style)
116 Frame* frame = document()->frame();
117 if (frame && frame->script()->canExecuteScripts(NotAboutToExecuteScript)) {
118 m_rendererIsCanvas = true;
119 return new (arena) RenderHTMLCanvas(this);
122 m_rendererIsCanvas = false;
123 return HTMLElement::createRenderer(arena, style);
126 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
128 m_observers.add(observer);
131 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
133 m_observers.remove(observer);
136 void HTMLCanvasElement::setHeight(int value)
138 setAttribute(heightAttr, String::number(value));
141 void HTMLCanvasElement::setWidth(int value)
143 setAttribute(widthAttr, String::number(value));
146 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
148 // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
149 // context is already 2D, just return that. If the existing context is WebGL, then destroy it
150 // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
151 // context with any other type string will destroy any existing context.
153 // FIXME - The code depends on the context not going away once created, to prevent JS from
154 // seeing a dangling pointer. So for now we will disallow the context from being changed
155 // once it is created.
157 if (m_context && !m_context->is2d())
160 bool usesDashbardCompatibilityMode = false;
161 #if ENABLE(DASHBOARD_SUPPORT)
162 if (Settings* settings = document()->settings())
163 usesDashbardCompatibilityMode = settings->usesDashboardBackwardCompatibilityMode();
165 m_context = adoptPtr(new CanvasRenderingContext2D(this, document()->inQuirksMode(), usesDashbardCompatibilityMode));
166 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
168 // Need to make sure a RenderLayer and compositing layer get created for the Canvas
169 setNeedsStyleRecalc(SyntheticStyleChange);
173 return m_context.get();
176 Settings* settings = document()->settings();
177 if (settings && settings->webGLEnabled()
178 #if !PLATFORM(CHROMIUM) && !PLATFORM(GTK)
179 && settings->acceleratedCompositingEnabled()
182 // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
183 // Once ratified, we will also accept "webgl" as the context name.
184 if ((type == "webkit-3d") ||
185 (type == "experimental-webgl")) {
186 if (m_context && !m_context->is3d())
189 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
191 // Need to make sure a RenderLayer and compositing layer get created for the Canvas
192 setNeedsStyleRecalc(SyntheticStyleChange);
195 return m_context.get();
204 void HTMLCanvasElement::didDraw(const FloatRect& rect)
206 m_copiedImage.clear(); // Clear our image snapshot if we have one.
208 if (RenderBox* ro = renderBox()) {
209 FloatRect destRect = ro->contentBoxRect();
210 FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
211 r.intersect(destRect);
212 if (r.isEmpty() || m_dirtyRect.contains(r))
215 m_dirtyRect.unite(r);
216 ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
219 HashSet<CanvasObserver*>::iterator end = m_observers.end();
220 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
221 (*it)->canvasChanged(this, rect);
224 void HTMLCanvasElement::reset()
230 bool hadImageBuffer = hasCreatedImageBuffer();
231 int w = getAttribute(widthAttr).toInt(&ok);
234 int h = getAttribute(heightAttr).toInt(&ok);
238 IntSize oldSize = size();
239 setSurfaceSize(IntSize(w, h)); // The image buffer gets cleared here.
242 if (m_context && m_context->is3d() && oldSize != size())
243 static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height());
246 if (m_context && m_context->is2d())
247 static_cast<CanvasRenderingContext2D*>(m_context.get())->reset();
249 if (RenderObject* renderer = this->renderer()) {
250 if (m_rendererIsCanvas) {
251 if (oldSize != size())
252 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
258 HashSet<CanvasObserver*>::iterator end = m_observers.end();
259 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
260 (*it)->canvasResized(this);
263 void HTMLCanvasElement::paint(GraphicsContext* context, const IntRect& r)
265 // Clear the dirty rect
266 m_dirtyRect = FloatRect();
268 if (context->paintingDisabled())
272 if (!m_context->paintsIntoCanvasBuffer())
274 m_context->paintRenderingResultsToCanvas();
277 if (hasCreatedImageBuffer()) {
278 ImageBuffer* imageBuffer = buffer();
280 if (m_presentedImage)
281 context->drawImage(m_presentedImage.get(), ColorSpaceDeviceRGB, r);
282 else if (imageBuffer->drawsUsingCopy())
283 context->drawImage(copiedImage(), ColorSpaceDeviceRGB, r);
285 context->drawImageBuffer(imageBuffer, ColorSpaceDeviceRGB, r);
291 static_cast<WebGLRenderingContext*>(m_context.get())->markLayerComposited();
296 bool HTMLCanvasElement::is3D() const
298 return m_context && m_context->is3d();
302 void HTMLCanvasElement::makeRenderingResultsAvailable()
305 m_context->paintRenderingResultsToCanvas();
308 void HTMLCanvasElement::makePresentationCopy()
310 if (!m_presentedImage) {
311 // The buffer contains the last presented data, so save a copy of it.
312 m_presentedImage = buffer()->copyImage();
316 void HTMLCanvasElement::clearPresentationCopy()
318 m_presentedImage.clear();
321 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
324 m_hasCreatedImageBuffer = false;
325 m_imageBuffer.clear();
326 m_copiedImage.clear();
329 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionCode& ec)
331 if (!m_originClean) {
336 if (m_size.isEmpty() || !buffer())
337 return String("data:,");
339 String lowercaseMimeType = mimeType.lower();
341 // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
342 if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
343 lowercaseMimeType = "image/png";
346 #if PLATFORM(CG) || (USE(SKIA) && !PLATFORM(ANDROID))
347 // FIXME: Consider using this code path on Android. http://b/4572024
349 #if USE(CG) || USE(SKIA)
350 >>>>>>> WebKit.org at r84325
351 // Try to get ImageData first, as that may avoid lossy conversions.
352 RefPtr<ImageData> imageData = getImageData();
355 return ImageDataToDataURL(*imageData, lowercaseMimeType, quality);
358 makeRenderingResultsAvailable();
360 return buffer()->toDataURL(lowercaseMimeType, quality);
363 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
365 if (!m_context || !m_context->is3d())
369 WebGLRenderingContext* ctx = static_cast<WebGLRenderingContext*>(m_context.get());
371 return ctx->paintRenderingResultsToImageData();
377 IntRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
379 float left = floorf(logicalRect.x() * m_pageScaleFactor);
380 float top = floorf(logicalRect.y() * m_pageScaleFactor);
381 float right = ceilf(logicalRect.maxX() * m_pageScaleFactor);
382 float bottom = ceilf(logicalRect.maxY() * m_pageScaleFactor);
384 return IntRect(IntPoint(left, top), convertToValidDeviceSize(right - left, bottom - top));
387 IntSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
389 return convertToValidDeviceSize(logicalSize.width() * m_pageScaleFactor, logicalSize.height() * m_pageScaleFactor);
392 IntSize HTMLCanvasElement::convertToValidDeviceSize(float width, float height) const
394 width = ceilf(width);
395 height = ceilf(height);
397 if (width < 1 || height < 1 || width * height > MaxCanvasArea)
401 if (width > MaxSkiaDim || height > MaxSkiaDim)
405 return IntSize(width, height);
408 const SecurityOrigin& HTMLCanvasElement::securityOrigin() const
410 return *document()->securityOrigin();
413 CSSStyleSelector* HTMLCanvasElement::styleSelector()
415 return document()->styleSelector();
418 void HTMLCanvasElement::createImageBuffer() const
420 ASSERT(!m_imageBuffer);
422 m_hasCreatedImageBuffer = true;
424 FloatSize unscaledSize(width(), height());
425 IntSize size = convertLogicalToDevice(unscaledSize);
426 if (!size.width() || !size.height())
429 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
430 if (document()->settings()->canvasUsesAcceleratedDrawing())
431 m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Accelerated);
433 m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Unaccelerated);
435 m_imageBuffer = ImageBuffer::create(size);
437 // The convertLogicalToDevice MaxCanvasArea check should prevent common cases
438 // where ImageBuffer::create() returns 0, however we could still be low on memory.
441 m_imageBuffer->context()->scale(FloatSize(size.width() / unscaledSize.width(), size.height() / unscaledSize.height()));
442 m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
443 m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
446 JSC::JSLock lock(JSC::SilenceAssertionsOnly);
447 scriptExecutionContext()->globalData()->heap.reportExtraMemoryCost(m_imageBuffer->dataSize());
451 GraphicsContext* HTMLCanvasElement::drawingContext() const
453 return buffer() ? m_imageBuffer->context() : 0;
456 ImageBuffer* HTMLCanvasElement::buffer() const
458 if (!m_hasCreatedImageBuffer)
460 return m_imageBuffer.get();
463 Image* HTMLCanvasElement::copiedImage() const
465 if (!m_copiedImage && buffer()) {
467 m_context->paintRenderingResultsToCanvas();
468 m_copiedImage = buffer()->copyImage();
470 return m_copiedImage.get();
473 void HTMLCanvasElement::clearCopiedImage()
475 m_copiedImage.clear();
478 AffineTransform HTMLCanvasElement::baseTransform() const
480 ASSERT(m_hasCreatedImageBuffer);
481 FloatSize unscaledSize(width(), height());
482 IntSize size = convertLogicalToDevice(unscaledSize);
483 AffineTransform transform;
484 if (size.width() && size.height())
485 transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
486 return m_imageBuffer->baseTransform() * transform;