1 /* 2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Alp Toker <alp (at) 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 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 78 HTMLCanvasElement::~HTMLCanvasElement() 79 { 80 if (m_observer) 81 m_observer->canvasDestroyed(this); 82 } 83 84 #if ENABLE(DASHBOARD_SUPPORT) 85 86 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 95 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 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 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 126 void HTMLCanvasElement::setHeight(int value) 127 { 128 setAttribute(heightAttr, String::number(value)); 129 } 130 131 void HTMLCanvasElement::setWidth(int value) 132 { 133 setAttribute(widthAttr, String::number(value)); 134 } 135 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 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 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 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 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 282 IntRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const 283 { 284 return IntRect(convertLogicalToDevice(logicalRect.location()), convertLogicalToDevice(logicalRect.size())); 285 } 286 287 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 311 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 332 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 352 GraphicsContext* HTMLCanvasElement::drawingContext() const 353 { 354 return buffer() ? m_imageBuffer->context() : 0; 355 } 356 357 ImageBuffer* HTMLCanvasElement::buffer() const 358 { 359 if (!m_createdImageBuffer) 360 createImageBuffer(); 361 return m_imageBuffer.get(); 362 } 363 364 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) 377 bool HTMLCanvasElement::is3D() const 378 { 379 return m_context && m_context->is3d(); 380 } 381 #endif 382 383 } 384