1 /* 2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Alp Toker <alp (at) atoker.com> 4 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 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. 14 * 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. 26 */ 27 28 #include "config.h" 29 #include "HTMLCanvasElement.h" 30 31 #include "Attribute.h" 32 #include "CanvasContextAttributes.h" 33 #include "CanvasGradient.h" 34 #include "CanvasPattern.h" 35 #include "CanvasRenderingContext2D.h" 36 #include "CanvasStyle.h" 37 #include "Chrome.h" 38 #include "Document.h" 39 #include "ExceptionCode.h" 40 #include "Frame.h" 41 #include "GraphicsContext.h" 42 #include "HTMLNames.h" 43 #include "ImageBuffer.h" 44 #include "ImageData.h" 45 #include "MIMETypeRegistry.h" 46 #include "Page.h" 47 #include "RenderHTMLCanvas.h" 48 #include "Settings.h" 49 #include <math.h> 50 #include <stdio.h> 51 52 #if USE(JSC) 53 #include <runtime/JSLock.h> 54 #endif 55 56 #if ENABLE(WEBGL) 57 #include "WebGLContextAttributes.h" 58 #include "WebGLRenderingContext.h" 59 #endif 60 61 namespace WebCore { 62 63 using namespace HTMLNames; 64 65 // These values come from the WhatWG spec. 66 static const int DefaultWidth = 300; 67 static const int DefaultHeight = 150; 68 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 73 74 //In Skia, we will also limit width/height to 32767. 75 static const float MaxSkiaDim = 32767.0F; // Maximum width/height in CSS pixels. 76 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 #ifdef ANDROID 83 /* In Android we capture the drawing into a displayList, and then 84 replay that list at various scale factors (sometimes zoomed out, other 85 times zoomed in for "normal" reading, yet other times at arbitrary 86 zoom values based on the user's choice). In all of these cases, we do 87 not re-record the displayList, hence it is usually harmful to perform 88 any pre-rounding, since we just don't know the actual drawing resolution 89 at record time. 90 */ 91 , m_pageScaleFactor(1) 92 #else 93 , m_pageScaleFactor(document->frame() ? document->frame()->page()->chrome()->scaleFactor() : 1) 94 #endif 95 , m_originClean(true) 96 , m_hasCreatedImageBuffer(false) 97 { 98 ASSERT(hasTagName(canvasTag)); 99 } 100 101 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document) 102 { 103 return adoptRef(new HTMLCanvasElement(canvasTag, document)); 104 } 105 106 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document) 107 { 108 return adoptRef(new HTMLCanvasElement(tagName, document)); 109 } 110 111 HTMLCanvasElement::~HTMLCanvasElement() 112 { 113 HashSet<CanvasObserver*>::iterator end = m_observers.end(); 114 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it) 115 (*it)->canvasDestroyed(this); 116 } 117 118 void HTMLCanvasElement::parseMappedAttribute(Attribute* attr) 119 { 120 const QualifiedName& attrName = attr->name(); 121 if (attrName == widthAttr || attrName == heightAttr) 122 reset(); 123 HTMLElement::parseMappedAttribute(attr); 124 } 125 126 RenderObject* HTMLCanvasElement::createRenderer(RenderArena* arena, RenderStyle* style) 127 { 128 Frame* frame = document()->frame(); 129 if (frame && frame->script()->canExecuteScripts(NotAboutToExecuteScript)) { 130 m_rendererIsCanvas = true; 131 return new (arena) RenderHTMLCanvas(this); 132 } 133 134 m_rendererIsCanvas = false; 135 return HTMLElement::createRenderer(arena, style); 136 } 137 138 void HTMLCanvasElement::addObserver(CanvasObserver* observer) 139 { 140 m_observers.add(observer); 141 } 142 143 void HTMLCanvasElement::removeObserver(CanvasObserver* observer) 144 { 145 m_observers.remove(observer); 146 } 147 148 void HTMLCanvasElement::setHeight(int value) 149 { 150 setAttribute(heightAttr, String::number(value)); 151 } 152 153 void HTMLCanvasElement::setWidth(int value) 154 { 155 setAttribute(widthAttr, String::number(value)); 156 } 157 158 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs) 159 { 160 // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing 161 // context is already 2D, just return that. If the existing context is WebGL, then destroy it 162 // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a 163 // context with any other type string will destroy any existing context. 164 165 // FIXME - The code depends on the context not going away once created, to prevent JS from 166 // seeing a dangling pointer. So for now we will disallow the context from being changed 167 // once it is created. 168 if (type == "2d") { 169 if (m_context && !m_context->is2d()) 170 return 0; 171 if (!m_context) { 172 bool usesDashbardCompatibilityMode = false; 173 #if ENABLE(DASHBOARD_SUPPORT) 174 if (Settings* settings = document()->settings()) 175 usesDashbardCompatibilityMode = settings->usesDashboardBackwardCompatibilityMode(); 176 #endif 177 m_context = adoptPtr(new CanvasRenderingContext2D(this, document()->inQuirksMode(), usesDashbardCompatibilityMode)); 178 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING)) 179 if (m_context) { 180 // Need to make sure a RenderLayer and compositing layer get created for the Canvas 181 setNeedsStyleRecalc(SyntheticStyleChange); 182 } 183 #endif 184 } 185 return m_context.get(); 186 } 187 #if ENABLE(WEBGL) 188 Settings* settings = document()->settings(); 189 if (settings && settings->webGLEnabled() 190 #if !PLATFORM(CHROMIUM) && !PLATFORM(GTK) 191 && settings->acceleratedCompositingEnabled() 192 #endif 193 ) { 194 // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name. 195 // Once ratified, we will also accept "webgl" as the context name. 196 if ((type == "webkit-3d") || 197 (type == "experimental-webgl")) { 198 if (m_context && !m_context->is3d()) 199 return 0; 200 if (!m_context) { 201 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs)); 202 if (m_context) { 203 // Need to make sure a RenderLayer and compositing layer get created for the Canvas 204 setNeedsStyleRecalc(SyntheticStyleChange); 205 } 206 } 207 return m_context.get(); 208 } 209 } 210 #else 211 UNUSED_PARAM(attrs); 212 #endif 213 return 0; 214 } 215 216 void HTMLCanvasElement::didDraw(const FloatRect& rect) 217 { 218 m_copiedImage.clear(); // Clear our image snapshot if we have one. 219 220 if (RenderBox* ro = renderBox()) { 221 FloatRect destRect = ro->contentBoxRect(); 222 FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect); 223 r.intersect(destRect); 224 if (r.isEmpty() || m_dirtyRect.contains(r)) 225 return; 226 227 m_dirtyRect.unite(r); 228 ro->repaintRectangle(enclosingIntRect(m_dirtyRect)); 229 } 230 231 HashSet<CanvasObserver*>::iterator end = m_observers.end(); 232 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it) 233 (*it)->canvasChanged(this, rect); 234 } 235 236 void HTMLCanvasElement::reset() 237 { 238 if (m_ignoreReset) 239 return; 240 241 bool ok; 242 bool hadImageBuffer = hasCreatedImageBuffer(); 243 int w = getAttribute(widthAttr).toInt(&ok); 244 if (!ok || w < 0) 245 w = DefaultWidth; 246 int h = getAttribute(heightAttr).toInt(&ok); 247 if (!ok || h < 0) 248 h = DefaultHeight; 249 250 IntSize oldSize = size(); 251 setSurfaceSize(IntSize(w, h)); // The image buffer gets cleared here. 252 253 #if ENABLE(WEBGL) 254 if (m_context && m_context->is3d() && oldSize != size()) 255 static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height()); 256 #endif 257 258 if (m_context && m_context->is2d()) 259 static_cast<CanvasRenderingContext2D*>(m_context.get())->reset(); 260 261 if (RenderObject* renderer = this->renderer()) { 262 if (m_rendererIsCanvas) { 263 if (oldSize != size()) 264 toRenderHTMLCanvas(renderer)->canvasSizeChanged(); 265 if (hadImageBuffer) 266 renderer->repaint(); 267 } 268 } 269 270 HashSet<CanvasObserver*>::iterator end = m_observers.end(); 271 for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it) 272 (*it)->canvasResized(this); 273 } 274 275 void HTMLCanvasElement::paint(GraphicsContext* context, const IntRect& r) 276 { 277 // Clear the dirty rect 278 m_dirtyRect = FloatRect(); 279 280 if (context->paintingDisabled()) 281 return; 282 283 if (m_context) { 284 if (!m_context->paintsIntoCanvasBuffer()) 285 return; 286 m_context->paintRenderingResultsToCanvas(); 287 } 288 289 if (hasCreatedImageBuffer()) { 290 ImageBuffer* imageBuffer = buffer(); 291 if (imageBuffer) { 292 if (m_presentedImage) 293 context->drawImage(m_presentedImage.get(), ColorSpaceDeviceRGB, r); 294 else if (imageBuffer->drawsUsingCopy()) 295 context->drawImage(copiedImage(), ColorSpaceDeviceRGB, r); 296 else 297 context->drawImageBuffer(imageBuffer, ColorSpaceDeviceRGB, r); 298 } 299 } 300 301 #if ENABLE(WEBGL) 302 if (is3D()) 303 static_cast<WebGLRenderingContext*>(m_context.get())->markLayerComposited(); 304 #endif 305 } 306 307 #if ENABLE(WEBGL) 308 bool HTMLCanvasElement::is3D() const 309 { 310 return m_context && m_context->is3d(); 311 } 312 #endif 313 314 void HTMLCanvasElement::makeRenderingResultsAvailable() 315 { 316 if (m_context) 317 m_context->paintRenderingResultsToCanvas(); 318 } 319 320 void HTMLCanvasElement::makePresentationCopy() 321 { 322 if (!m_presentedImage) { 323 // The buffer contains the last presented data, so save a copy of it. 324 m_presentedImage = buffer()->copyImage(); 325 } 326 } 327 328 void HTMLCanvasElement::clearPresentationCopy() 329 { 330 m_presentedImage.clear(); 331 } 332 333 void HTMLCanvasElement::setSurfaceSize(const IntSize& size) 334 { 335 m_size = size; 336 m_hasCreatedImageBuffer = false; 337 m_imageBuffer.clear(); 338 m_copiedImage.clear(); 339 } 340 341 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionCode& ec) 342 { 343 if (!m_originClean) { 344 ec = SECURITY_ERR; 345 return String(); 346 } 347 348 if (m_size.isEmpty() || !buffer()) 349 return String("data:,"); 350 351 String lowercaseMimeType = mimeType.lower(); 352 353 // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread). 354 if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType)) 355 lowercaseMimeType = "image/png"; 356 357 #if USE(CG) || (USE(SKIA) && !PLATFORM(ANDROID)) 358 // FIXME: Consider using this code path on Android. http://b/4572024 359 // Try to get ImageData first, as that may avoid lossy conversions. 360 RefPtr<ImageData> imageData = getImageData(); 361 362 if (imageData) 363 return ImageDataToDataURL(*imageData, lowercaseMimeType, quality); 364 #endif 365 366 makeRenderingResultsAvailable(); 367 368 return buffer()->toDataURL(lowercaseMimeType, quality); 369 } 370 371 PassRefPtr<ImageData> HTMLCanvasElement::getImageData() 372 { 373 if (!m_context || !m_context->is3d()) 374 return 0; 375 376 #if ENABLE(WEBGL) 377 WebGLRenderingContext* ctx = static_cast<WebGLRenderingContext*>(m_context.get()); 378 379 return ctx->paintRenderingResultsToImageData(); 380 #else 381 return 0; 382 #endif 383 } 384 385 IntRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const 386 { 387 // Prevent under/overflow by ensuring the rect's bounds stay within integer-expressible range 388 int left = clampToInteger(floorf(logicalRect.x() * m_pageScaleFactor)); 389 int top = clampToInteger(floorf(logicalRect.y() * m_pageScaleFactor)); 390 int right = clampToInteger(ceilf(logicalRect.maxX() * m_pageScaleFactor)); 391 int bottom = clampToInteger(ceilf(logicalRect.maxY() * m_pageScaleFactor)); 392 393 return IntRect(IntPoint(left, top), convertToValidDeviceSize(right - left, bottom - top)); 394 } 395 396 IntSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const 397 { 398 // Prevent overflow by ensuring the rect's bounds stay within integer-expressible range 399 float width = clampToInteger(ceilf(logicalSize.width() * m_pageScaleFactor)); 400 float height = clampToInteger(ceilf(logicalSize.height() * m_pageScaleFactor)); 401 return convertToValidDeviceSize(width, height); 402 } 403 404 IntSize HTMLCanvasElement::convertToValidDeviceSize(float width, float height) const 405 { 406 width = ceilf(width); 407 height = ceilf(height); 408 409 if (width < 1 || height < 1 || width * height > MaxCanvasArea) 410 return IntSize(); 411 412 #if USE(SKIA) 413 if (width > MaxSkiaDim || height > MaxSkiaDim) 414 return IntSize(); 415 #endif 416 417 return IntSize(width, height); 418 } 419 420 const SecurityOrigin& HTMLCanvasElement::securityOrigin() const 421 { 422 return *document()->securityOrigin(); 423 } 424 425 CSSStyleSelector* HTMLCanvasElement::styleSelector() 426 { 427 return document()->styleSelector(); 428 } 429 430 void HTMLCanvasElement::createImageBuffer() const 431 { 432 ASSERT(!m_imageBuffer); 433 434 m_hasCreatedImageBuffer = true; 435 436 FloatSize unscaledSize(width(), height()); 437 IntSize size = convertLogicalToDevice(unscaledSize); 438 if (!size.width() || !size.height()) 439 return; 440 441 #if USE(IOSURFACE_CANVAS_BACKING_STORE) 442 if (document()->settings()->canvasUsesAcceleratedDrawing()) 443 m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Accelerated); 444 else 445 m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Unaccelerated); 446 #else 447 m_imageBuffer = ImageBuffer::create(size); 448 #endif 449 // The convertLogicalToDevice MaxCanvasArea check should prevent common cases 450 // where ImageBuffer::create() returns 0, however we could still be low on memory. 451 if (!m_imageBuffer) 452 return; 453 m_imageBuffer->context()->scale(FloatSize(size.width() / unscaledSize.width(), size.height() / unscaledSize.height())); 454 m_imageBuffer->context()->setShadowsIgnoreTransforms(true); 455 m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality); 456 457 #if USE(JSC) 458 JSC::JSLock lock(JSC::SilenceAssertionsOnly); 459 scriptExecutionContext()->globalData()->heap.reportExtraMemoryCost(m_imageBuffer->dataSize()); 460 #endif 461 } 462 463 GraphicsContext* HTMLCanvasElement::drawingContext() const 464 { 465 return buffer() ? m_imageBuffer->context() : 0; 466 } 467 468 ImageBuffer* HTMLCanvasElement::buffer() const 469 { 470 if (!m_hasCreatedImageBuffer) 471 createImageBuffer(); 472 return m_imageBuffer.get(); 473 } 474 475 Image* HTMLCanvasElement::copiedImage() const 476 { 477 if (!m_copiedImage && buffer()) { 478 if (m_context) 479 m_context->paintRenderingResultsToCanvas(); 480 m_copiedImage = buffer()->copyImage(); 481 } 482 return m_copiedImage.get(); 483 } 484 485 void HTMLCanvasElement::clearCopiedImage() 486 { 487 m_copiedImage.clear(); 488 } 489 490 AffineTransform HTMLCanvasElement::baseTransform() const 491 { 492 ASSERT(m_hasCreatedImageBuffer); 493 FloatSize unscaledSize(width(), height()); 494 IntSize size = convertLogicalToDevice(unscaledSize); 495 AffineTransform transform; 496 if (size.width() && size.height()) 497 transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height()); 498 return m_imageBuffer->baseTransform() * transform; 499 } 500 501 } 502