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