Home | History | Annotate | Download | only in html
      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