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