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