• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
3  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 #include "HTMLCanvasElement.h"
29 
30 #include "CanvasContextAttributes.h"
31 #include "CanvasGradient.h"
32 #include "CanvasPattern.h"
33 #include "CanvasRenderingContext2D.h"
34 #if ENABLE(3D_CANVAS)
35 #include "WebGLContextAttributes.h"
36 #include "WebGLRenderingContext.h"
37 #endif
38 #include "CanvasStyle.h"
39 #include "Chrome.h"
40 #include "Document.h"
41 #include "ExceptionCode.h"
42 #include "Frame.h"
43 #include "GraphicsContext.h"
44 #include "HTMLNames.h"
45 #include "ImageBuffer.h"
46 #include "MIMETypeRegistry.h"
47 #include "MappedAttribute.h"
48 #include "Page.h"
49 #include "RenderHTMLCanvas.h"
50 #include "Settings.h"
51 #include <math.h>
52 #include <stdio.h>
53 
54 namespace WebCore {
55 
56 using namespace HTMLNames;
57 
58 // These values come from the WhatWG spec.
59 static const int defaultWidth = 300;
60 static const int defaultHeight = 150;
61 
62 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
63 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
64 // in exchange for a smaller maximum canvas size.
65 const float HTMLCanvasElement::MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
66 
HTMLCanvasElement(const QualifiedName & tagName,Document * doc)67 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* doc)
68     : HTMLElement(tagName, doc)
69     , m_size(defaultWidth, defaultHeight)
70     , m_observer(0)
71     , m_originClean(true)
72     , m_ignoreReset(false)
73     , m_createdImageBuffer(false)
74 {
75     ASSERT(hasTagName(canvasTag));
76 }
77 
~HTMLCanvasElement()78 HTMLCanvasElement::~HTMLCanvasElement()
79 {
80     if (m_observer)
81         m_observer->canvasDestroyed(this);
82 }
83 
84 #if ENABLE(DASHBOARD_SUPPORT)
85 
endTagRequirement() const86 HTMLTagStatus HTMLCanvasElement::endTagRequirement() const
87 {
88     Settings* settings = document()->settings();
89     if (settings && settings->usesDashboardBackwardCompatibilityMode())
90         return TagStatusForbidden;
91 
92     return HTMLElement::endTagRequirement();
93 }
94 
tagPriority() const95 int HTMLCanvasElement::tagPriority() const
96 {
97     Settings* settings = document()->settings();
98     if (settings && settings->usesDashboardBackwardCompatibilityMode())
99         return 0;
100 
101     return HTMLElement::tagPriority();
102 }
103 
104 #endif
105 
parseMappedAttribute(MappedAttribute * attr)106 void HTMLCanvasElement::parseMappedAttribute(MappedAttribute* attr)
107 {
108     const QualifiedName& attrName = attr->name();
109     if (attrName == widthAttr || attrName == heightAttr)
110         reset();
111     HTMLElement::parseMappedAttribute(attr);
112 }
113 
createRenderer(RenderArena * arena,RenderStyle * style)114 RenderObject* HTMLCanvasElement::createRenderer(RenderArena* arena, RenderStyle* style)
115 {
116     Settings* settings = document()->settings();
117     if (settings && settings->isJavaScriptEnabled()) {
118         m_rendererIsCanvas = true;
119         return new (arena) RenderHTMLCanvas(this);
120     }
121 
122     m_rendererIsCanvas = false;
123     return HTMLElement::createRenderer(arena, style);
124 }
125 
setHeight(int value)126 void HTMLCanvasElement::setHeight(int value)
127 {
128     setAttribute(heightAttr, String::number(value));
129 }
130 
setWidth(int value)131 void HTMLCanvasElement::setWidth(int value)
132 {
133     setAttribute(widthAttr, String::number(value));
134 }
135 
toDataURL(const String & mimeType,ExceptionCode & ec)136 String HTMLCanvasElement::toDataURL(const String& mimeType, ExceptionCode& ec)
137 {
138     if (!m_originClean) {
139         ec = SECURITY_ERR;
140         return String();
141     }
142 
143     if (m_size.isEmpty() || !buffer())
144         return String("data:,");
145 
146     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType))
147         return buffer()->toDataURL("image/png");
148 
149     return buffer()->toDataURL(mimeType);
150 }
151 
getContext(const String & type,CanvasContextAttributes * attrs)152 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
153 {
154     // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
155     // context is already 2D, just return that. If the existing context is WebGL, then destroy it
156     // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
157     // context with any other type string will destroy any existing context.
158 
159     // FIXME - The code depends on the context not going away once created, to prevent JS from
160     // seeing a dangling pointer. So for now we will disallow the context from being changed
161     // once it is created.
162     if (type == "2d") {
163         if (m_context && !m_context->is2d())
164             return 0;
165         if (!m_context)
166             m_context = new CanvasRenderingContext2D(this);
167         return m_context.get();
168     }
169 #if ENABLE(3D_CANVAS)
170     Settings* settings = document()->settings();
171     if (settings && settings->webGLEnabled()) {
172         // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
173         // Once ratified, we will also accept "webgl" as the context name.
174         if ((type == "webkit-3d") ||
175             (type == "experimental-webgl")) {
176             if (m_context && !m_context->is3d())
177                 return 0;
178             if (!m_context) {
179                 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
180                 if (m_context) {
181                     // Need to make sure a RenderLayer and compositing layer get created for the Canvas
182                     setNeedsStyleRecalc(SyntheticStyleChange);
183                 }
184             }
185             return m_context.get();
186         }
187     }
188 #else
189     UNUSED_PARAM(attrs);
190 #endif
191     return 0;
192 }
193 
willDraw(const FloatRect & rect)194 void HTMLCanvasElement::willDraw(const FloatRect& rect)
195 {
196     if (m_imageBuffer)
197         m_imageBuffer->clearImage();
198 
199     if (RenderBox* ro = renderBox()) {
200         FloatRect destRect = ro->contentBoxRect();
201         FloatRect r = mapRect(rect, FloatRect(0, 0, m_size.width(), m_size.height()), destRect);
202         r.intersect(destRect);
203         if (m_dirtyRect.contains(r))
204             return;
205 
206         m_dirtyRect.unite(r);
207         ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
208     }
209 
210     if (m_observer)
211         m_observer->canvasChanged(this, rect);
212 }
213 
reset()214 void HTMLCanvasElement::reset()
215 {
216     if (m_ignoreReset)
217         return;
218 
219     bool ok;
220     int w = getAttribute(widthAttr).toInt(&ok);
221     if (!ok)
222         w = defaultWidth;
223     int h = getAttribute(heightAttr).toInt(&ok);
224     if (!ok)
225         h = defaultHeight;
226 
227     IntSize oldSize = m_size;
228     m_size = IntSize(w, h);
229 
230 #if ENABLE(3D_CANVAS)
231     if (m_context && m_context->is3d())
232         static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height());
233 #endif
234 
235     bool hadImageBuffer = m_createdImageBuffer;
236     m_createdImageBuffer = false;
237     m_imageBuffer.clear();
238     if (m_context && m_context->is2d())
239         static_cast<CanvasRenderingContext2D*>(m_context.get())->reset();
240 
241     if (RenderObject* renderer = this->renderer()) {
242         if (m_rendererIsCanvas) {
243             if (oldSize != m_size)
244                 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
245             if (hadImageBuffer)
246                 renderer->repaint();
247         }
248     }
249 
250     if (m_observer)
251         m_observer->canvasResized(this);
252 }
253 
paint(GraphicsContext * context,const IntRect & r)254 void HTMLCanvasElement::paint(GraphicsContext* context, const IntRect& r)
255 {
256     // Clear the dirty rect
257     m_dirtyRect = FloatRect();
258 
259     if (context->paintingDisabled())
260         return;
261 
262 #if ENABLE(3D_CANVAS)
263     WebGLRenderingContext* context3D = 0;
264     if (m_context && m_context->is3d()) {
265         context3D = static_cast<WebGLRenderingContext*>(m_context.get());
266         context3D->beginPaint();
267     }
268 #endif
269 
270     if (m_imageBuffer) {
271         Image* image = m_imageBuffer->image();
272         if (image)
273             context->drawImage(image, DeviceColorSpace, r);
274     }
275 
276 #if ENABLE(3D_CANVAS)
277     if (context3D)
278         context3D->endPaint();
279 #endif
280 }
281 
convertLogicalToDevice(const FloatRect & logicalRect) const282 IntRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
283 {
284     return IntRect(convertLogicalToDevice(logicalRect.location()), convertLogicalToDevice(logicalRect.size()));
285 }
286 
convertLogicalToDevice(const FloatSize & logicalSize) const287 IntSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
288 {
289 #if PLATFORM(ANDROID)
290     /*  In Android we capture the drawing into a displayList, and then
291         replay that list at various scale factors (sometimes zoomed out, other
292         times zoomed in for "normal" reading, yet other times at arbitrary
293         zoom values based on the user's choice). In all of these cases, we do
294         not re-record the displayList, hence it is usually harmful to perform
295         any pre-rounding, since we just don't know the actual drawing resolution
296         at record time.
297      */
298     float pageScaleFactor = 1.0f;
299 #else
300     float pageScaleFactor = document()->frame() ? document()->frame()->page()->chrome()->scaleFactor() : 1.0f;
301 #endif
302     float wf = ceilf(logicalSize.width() * pageScaleFactor);
303     float hf = ceilf(logicalSize.height() * pageScaleFactor);
304 
305     if (!(wf >= 1 && hf >= 1 && wf * hf <= MaxCanvasArea))
306         return IntSize();
307 
308     return IntSize(static_cast<unsigned>(wf), static_cast<unsigned>(hf));
309 }
310 
convertLogicalToDevice(const FloatPoint & logicalPos) const311 IntPoint HTMLCanvasElement::convertLogicalToDevice(const FloatPoint& logicalPos) const
312 {
313 #if PLATFORM(ANDROID)
314     /*  In Android we capture the drawing into a displayList, and then
315         replay that list at various scale factors (sometimes zoomed out, other
316         times zoomed in for "normal" reading, yet other times at arbitrary
317         zoom values based on the user's choice). In all of these cases, we do
318         not re-record the displayList, hence it is usually harmful to perform
319         any pre-rounding, since we just don't know the actual drawing resolution
320         at record time.
321      */
322     float pageScaleFactor = 1.0f;
323 #else
324     float pageScaleFactor = document()->frame() ? document()->frame()->page()->chrome()->scaleFactor() : 1.0f;
325 #endif
326     float xf = logicalPos.x() * pageScaleFactor;
327     float yf = logicalPos.y() * pageScaleFactor;
328 
329     return IntPoint(static_cast<unsigned>(xf), static_cast<unsigned>(yf));
330 }
331 
createImageBuffer() const332 void HTMLCanvasElement::createImageBuffer() const
333 {
334     ASSERT(!m_imageBuffer);
335 
336     m_createdImageBuffer = true;
337 
338     FloatSize unscaledSize(width(), height());
339     IntSize size = convertLogicalToDevice(unscaledSize);
340     if (!size.width() || !size.height())
341         return;
342 
343     m_imageBuffer = ImageBuffer::create(size);
344     // The convertLogicalToDevice MaxCanvasArea check should prevent common cases
345     // where ImageBuffer::create() returns NULL, however we could still be low on memory.
346     if (!m_imageBuffer)
347         return;
348     m_imageBuffer->context()->scale(FloatSize(size.width() / unscaledSize.width(), size.height() / unscaledSize.height()));
349     m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
350 }
351 
drawingContext() const352 GraphicsContext* HTMLCanvasElement::drawingContext() const
353 {
354     return buffer() ? m_imageBuffer->context() : 0;
355 }
356 
buffer() const357 ImageBuffer* HTMLCanvasElement::buffer() const
358 {
359     if (!m_createdImageBuffer)
360         createImageBuffer();
361     return m_imageBuffer.get();
362 }
363 
baseTransform() const364 AffineTransform HTMLCanvasElement::baseTransform() const
365 {
366     ASSERT(m_createdImageBuffer);
367     FloatSize unscaledSize(width(), height());
368     IntSize size = convertLogicalToDevice(unscaledSize);
369     AffineTransform transform;
370     if (size.width() && size.height())
371         transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
372     transform.multiply(m_imageBuffer->baseTransform());
373     return transform;
374 }
375 
376 #if ENABLE(3D_CANVAS)
is3D() const377 bool HTMLCanvasElement::is3D() const
378 {
379     return m_context && m_context->is3d();
380 }
381 #endif
382 
383 }
384