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 "CanvasGradient.h"
31 #include "CanvasPattern.h"
32 #include "CanvasRenderingContext2D.h"
33 #include "CanvasStyle.h"
34 #include "Chrome.h"
35 #include "Document.h"
36 #include "ExceptionCode.h"
37 #include "Frame.h"
38 #include "GraphicsContext.h"
39 #include "HTMLNames.h"
40 #include "ImageBuffer.h"
41 #include "MIMETypeRegistry.h"
42 #include "MappedAttribute.h"
43 #include "Page.h"
44 #include "RenderHTMLCanvas.h"
45 #include "Settings.h"
46 #include <math.h>
47 #include <stdio.h>
48
49 namespace WebCore {
50
51 using namespace HTMLNames;
52
53 // These values come from the WhatWG spec.
54 static const int defaultWidth = 300;
55 static const int defaultHeight = 150;
56
57 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
58 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
59 // in exchange for a smaller maximum canvas size.
60 const float HTMLCanvasElement::MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
61
HTMLCanvasElement(const QualifiedName & tagName,Document * doc)62 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* doc)
63 : HTMLElement(tagName, doc)
64 , m_size(defaultWidth, defaultHeight)
65 , m_observer(0)
66 , m_originClean(true)
67 , m_ignoreReset(false)
68 , m_createdImageBuffer(false)
69 {
70 ASSERT(hasTagName(canvasTag));
71 }
72
~HTMLCanvasElement()73 HTMLCanvasElement::~HTMLCanvasElement()
74 {
75 if (m_observer)
76 m_observer->canvasDestroyed(this);
77 }
78
79 #if ENABLE(DASHBOARD_SUPPORT)
80
endTagRequirement() const81 HTMLTagStatus HTMLCanvasElement::endTagRequirement() const
82 {
83 Settings* settings = document()->settings();
84 if (settings && settings->usesDashboardBackwardCompatibilityMode())
85 return TagStatusForbidden;
86
87 return HTMLElement::endTagRequirement();
88 }
89
tagPriority() const90 int HTMLCanvasElement::tagPriority() const
91 {
92 Settings* settings = document()->settings();
93 if (settings && settings->usesDashboardBackwardCompatibilityMode())
94 return 0;
95
96 return HTMLElement::tagPriority();
97 }
98
99 #endif
100
parseMappedAttribute(MappedAttribute * attr)101 void HTMLCanvasElement::parseMappedAttribute(MappedAttribute* attr)
102 {
103 const QualifiedName& attrName = attr->name();
104 if (attrName == widthAttr || attrName == heightAttr)
105 reset();
106 HTMLElement::parseMappedAttribute(attr);
107 }
108
createRenderer(RenderArena * arena,RenderStyle * style)109 RenderObject* HTMLCanvasElement::createRenderer(RenderArena* arena, RenderStyle* style)
110 {
111 Settings* settings = document()->settings();
112 if (settings && settings->isJavaScriptEnabled()) {
113 m_rendererIsCanvas = true;
114 return new (arena) RenderHTMLCanvas(this);
115 }
116
117 m_rendererIsCanvas = false;
118 return HTMLElement::createRenderer(arena, style);
119 }
120
setHeight(int value)121 void HTMLCanvasElement::setHeight(int value)
122 {
123 setAttribute(heightAttr, String::number(value));
124 }
125
setWidth(int value)126 void HTMLCanvasElement::setWidth(int value)
127 {
128 setAttribute(widthAttr, String::number(value));
129 }
130
toDataURL(const String & mimeType,ExceptionCode & ec)131 String HTMLCanvasElement::toDataURL(const String& mimeType, ExceptionCode& ec)
132 {
133 if (!m_originClean) {
134 ec = SECURITY_ERR;
135 return String();
136 }
137
138 if (m_size.isEmpty())
139 return String("data:,");
140
141 if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType))
142 return buffer()->toDataURL("image/png");
143
144 return buffer()->toDataURL(mimeType);
145 }
146
getContext(const String & type)147 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type)
148 {
149 if (type == "2d") {
150 if (!m_2DContext)
151 m_2DContext.set(new CanvasRenderingContext2D(this));
152 return m_2DContext.get();
153 }
154 return 0;
155 }
156
willDraw(const FloatRect & rect)157 void HTMLCanvasElement::willDraw(const FloatRect& rect)
158 {
159 m_imageBuffer->clearImage();
160
161 if (RenderBox* ro = renderBox()) {
162 FloatRect destRect = ro->contentBoxRect();
163 FloatRect r = mapRect(rect, FloatRect(0, 0, m_size.width(), m_size.height()), destRect);
164 r.intersect(destRect);
165 if (m_dirtyRect.contains(r))
166 return;
167
168 m_dirtyRect.unite(r);
169 ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
170 }
171
172 if (m_observer)
173 m_observer->canvasChanged(this, rect);
174 }
175
reset()176 void HTMLCanvasElement::reset()
177 {
178 if (m_ignoreReset)
179 return;
180
181 bool ok;
182 int w = getAttribute(widthAttr).toInt(&ok);
183 if (!ok)
184 w = defaultWidth;
185 int h = getAttribute(heightAttr).toInt(&ok);
186 if (!ok)
187 h = defaultHeight;
188
189 IntSize oldSize = m_size;
190 m_size = IntSize(w, h);
191
192 bool hadImageBuffer = m_createdImageBuffer;
193 m_createdImageBuffer = false;
194 m_imageBuffer.clear();
195 if (m_2DContext)
196 m_2DContext->reset();
197
198 if (RenderObject* renderer = this->renderer()) {
199 if (m_rendererIsCanvas) {
200 if (oldSize != m_size)
201 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
202 if (hadImageBuffer)
203 renderer->repaint();
204 }
205 }
206
207 if (m_observer)
208 m_observer->canvasResized(this);
209 }
210
paint(GraphicsContext * context,const IntRect & r)211 void HTMLCanvasElement::paint(GraphicsContext* context, const IntRect& r)
212 {
213 // Clear the dirty rect
214 m_dirtyRect = FloatRect();
215
216 if (context->paintingDisabled())
217 return;
218
219 if (m_imageBuffer) {
220 Image* image = m_imageBuffer->image();
221 if (image)
222 context->drawImage(image, r);
223 }
224 }
225
convertLogicalToDevice(const FloatRect & logicalRect) const226 IntRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
227 {
228 return IntRect(convertLogicalToDevice(logicalRect.location()), convertLogicalToDevice(logicalRect.size()));
229 }
230
convertLogicalToDevice(const FloatSize & logicalSize) const231 IntSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
232 {
233 float pageScaleFactor = document()->frame() ? document()->frame()->page()->chrome()->scaleFactor() : 1.0f;
234 float wf = ceilf(logicalSize.width() * pageScaleFactor);
235 float hf = ceilf(logicalSize.height() * pageScaleFactor);
236
237 if (!(wf >= 1 && hf >= 1 && wf * hf <= MaxCanvasArea))
238 return IntSize();
239
240 return IntSize(static_cast<unsigned>(wf), static_cast<unsigned>(hf));
241 }
242
convertLogicalToDevice(const FloatPoint & logicalPos) const243 IntPoint HTMLCanvasElement::convertLogicalToDevice(const FloatPoint& logicalPos) const
244 {
245 float pageScaleFactor = document()->frame() ? document()->frame()->page()->chrome()->scaleFactor() : 1.0f;
246 float xf = logicalPos.x() * pageScaleFactor;
247 float yf = logicalPos.y() * pageScaleFactor;
248
249 return IntPoint(static_cast<unsigned>(xf), static_cast<unsigned>(yf));
250 }
251
createImageBuffer() const252 void HTMLCanvasElement::createImageBuffer() const
253 {
254 ASSERT(!m_imageBuffer);
255
256 m_createdImageBuffer = true;
257
258 FloatSize unscaledSize(width(), height());
259 IntSize size = convertLogicalToDevice(unscaledSize);
260 if (!size.width() || !size.height())
261 return;
262
263 m_imageBuffer = ImageBuffer::create(size);
264 // The convertLogicalToDevice MaxCanvasArea check should prevent common cases
265 // where ImageBuffer::create() returns NULL, however we could still be low on memory.
266 if (!m_imageBuffer)
267 return;
268 m_imageBuffer->context()->scale(FloatSize(size.width() / unscaledSize.width(), size.height() / unscaledSize.height()));
269 m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
270 }
271
drawingContext() const272 GraphicsContext* HTMLCanvasElement::drawingContext() const
273 {
274 return buffer() ? m_imageBuffer->context() : 0;
275 }
276
buffer() const277 ImageBuffer* HTMLCanvasElement::buffer() const
278 {
279 if (!m_createdImageBuffer)
280 createImageBuffer();
281 return m_imageBuffer.get();
282 }
283
baseTransform() const284 TransformationMatrix HTMLCanvasElement::baseTransform() const
285 {
286 ASSERT(m_createdImageBuffer);
287 FloatSize unscaledSize(width(), height());
288 IntSize size = convertLogicalToDevice(unscaledSize);
289 TransformationMatrix transform;
290 if (size.width() && size.height())
291 transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
292 transform.multiply(m_imageBuffer->baseTransform());
293 return transform;
294 }
295
296 }
297