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  * 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 
     62 namespace WebCore {
     63 
     64 using namespace HTMLNames;
     65 
     66 // These values come from the WhatWG spec.
     67 static const int DefaultWidth = 300;
     68 static 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.
     73 static const float MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
     74 
     75 //In Skia, we will also limit width/height to 32767.
     76 static const float MaxSkiaDim = 32767.0F; // Maximum width/height in CSS pixels.
     77 
     78 HTMLCanvasElement::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 
    102 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document)
    103 {
    104     return adoptRef(new HTMLCanvasElement(canvasTag, document));
    105 }
    106 
    107 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document)
    108 {
    109     return adoptRef(new HTMLCanvasElement(tagName, document));
    110 }
    111 
    112 HTMLCanvasElement::~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 
    119 void 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 
    127 RenderObject* 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 
    139 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
    140 {
    141     m_observers.add(observer);
    142 }
    143 
    144 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
    145 {
    146     m_observers.remove(observer);
    147 }
    148 
    149 void HTMLCanvasElement::setHeight(int value)
    150 {
    151     setAttribute(heightAttr, String::number(value));
    152 }
    153 
    154 void HTMLCanvasElement::setWidth(int value)
    155 {
    156     setAttribute(widthAttr, String::number(value));
    157 }
    158 
    159 CanvasRenderingContext* 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 
    217 void 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 
    242 void 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 
    281 void 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)
    314 bool HTMLCanvasElement::is3D() const
    315 {
    316     return m_context && m_context->is3d();
    317 }
    318 #endif
    319 
    320 void HTMLCanvasElement::makeRenderingResultsAvailable()
    321 {
    322     if (m_context)
    323         m_context->paintRenderingResultsToCanvas();
    324 }
    325 
    326 void 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 
    334 void HTMLCanvasElement::clearPresentationCopy()
    335 {
    336     m_presentedImage.clear();
    337 }
    338 
    339 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
    340 {
    341     m_size = size;
    342     m_hasCreatedImageBuffer = false;
    343     m_imageBuffer.clear();
    344     m_copiedImage.clear();
    345 }
    346 
    347 String 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 
    377 PassRefPtr<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 
    391 IntRect 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 
    402 IntSize 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 
    410 IntSize 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 
    426 const SecurityOrigin& HTMLCanvasElement::securityOrigin() const
    427 {
    428     return *document()->securityOrigin();
    429 }
    430 
    431 CSSStyleSelector* HTMLCanvasElement::styleSelector()
    432 {
    433     return document()->styleSelector();
    434 }
    435 
    436 void 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 
    469 GraphicsContext* HTMLCanvasElement::drawingContext() const
    470 {
    471     return buffer() ? m_imageBuffer->context() : 0;
    472 }
    473 
    474 ImageBuffer* HTMLCanvasElement::buffer() const
    475 {
    476     if (!m_hasCreatedImageBuffer)
    477         createImageBuffer();
    478     return m_imageBuffer.get();
    479 }
    480 
    481 Image* 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 
    491 void HTMLCanvasElement::clearCopiedImage()
    492 {
    493     m_copiedImage.clear();
    494 }
    495 
    496 AffineTransform 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