afb613a6e47c76345f249d429aa996f63d10b66e
[WebKit-https.git] / Source / WebCore / html / HTMLCanvasElement.cpp
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
62 #if PLATFORM(CHROMIUM)
63 #include "PlatformSupport.h"
64 #endif
65
66 namespace WebCore {
67
68 using namespace HTMLNames;
69
70 // These values come from the WhatWG spec.
71 static const int DefaultWidth = 300;
72 static const int DefaultHeight = 150;
73
74 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
75 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
76 // in exchange for a smaller maximum canvas size.
77 static const float MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
78
79 //In Skia, we will also limit width/height to 32767.
80 static const float MaxSkiaDim = 32767.0F; // Maximum width/height in CSS pixels.
81
82 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* document)
83     : HTMLElement(tagName, document)
84     , m_size(DefaultWidth, DefaultHeight)
85     , m_rendererIsCanvas(false)
86     , m_ignoreReset(false)
87 #if ENABLE(HIGH_DPI_CANVAS)
88       // NOTE: High-DPI canvas requires the platform-specific ImageBuffer implementation to respect
89       // the resolutionScale parameter.
90     , m_deviceScaleFactor(document->frame() ? document->frame()->page()->deviceScaleFactor() : 1)
91 #else
92     , m_deviceScaleFactor(1)
93 #endif
94     , m_originClean(true)
95     , m_hasCreatedImageBuffer(false)
96     , m_didClearImageBuffer(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     m_context.clear(); // Ensure this goes away before the ImageBuffer.
118 }
119
120 void HTMLCanvasElement::parseAttribute(Attribute* attr)
121 {
122     const QualifiedName& attrName = attr->name();
123     if (attrName == widthAttr || attrName == heightAttr)
124         reset();
125     HTMLElement::parseAttribute(attr);
126 }
127
128 RenderObject* HTMLCanvasElement::createRenderer(RenderArena* arena, RenderStyle* style)
129 {
130     Frame* frame = document()->frame();
131     if (frame && frame->script()->canExecuteScripts(NotAboutToExecuteScript)) {
132         m_rendererIsCanvas = true;
133         return new (arena) RenderHTMLCanvas(this);
134     }
135
136     m_rendererIsCanvas = false;
137     return HTMLElement::createRenderer(arena, style);
138 }
139
140 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
141 {
142     m_observers.add(observer);
143 }
144
145 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
146 {
147     m_observers.remove(observer);
148 }
149
150 void HTMLCanvasElement::setHeight(int value)
151 {
152     setAttribute(heightAttr, String::number(value));
153 }
154
155 void HTMLCanvasElement::setWidth(int value)
156 {
157     setAttribute(widthAttr, String::number(value));
158 }
159
160 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
161 {
162     // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
163     // context is already 2D, just return that. If the existing context is WebGL, then destroy it
164     // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
165     // context with any other type string will destroy any existing context.
166     
167     // FIXME - The code depends on the context not going away once created, to prevent JS from
168     // seeing a dangling pointer. So for now we will disallow the context from being changed
169     // once it is created.
170     if (type == "2d") {
171         if (m_context && !m_context->is2d())
172             return 0;
173         if (!m_context) {
174             bool usesDashbardCompatibilityMode = false;
175 #if ENABLE(DASHBOARD_SUPPORT)
176             if (Settings* settings = document()->settings())
177                 usesDashbardCompatibilityMode = settings->usesDashboardBackwardCompatibilityMode();
178 #endif
179             m_context = CanvasRenderingContext2D::create(this, document()->inQuirksMode(), usesDashbardCompatibilityMode);
180 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
181             if (m_context) {
182                 // Need to make sure a RenderLayer and compositing layer get created for the Canvas
183                 setNeedsStyleRecalc(SyntheticStyleChange);
184             }
185 #endif
186         }
187         return m_context.get();
188     }
189 #if ENABLE(WEBGL)    
190     Settings* settings = document()->settings();
191     if (settings && settings->webGLEnabled()
192 #if !PLATFORM(CHROMIUM) && !PLATFORM(GTK) && !PLATFORM(EFL)
193         && settings->acceleratedCompositingEnabled()
194 #endif
195         ) {
196         // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
197         // Once ratified, we will also accept "webgl" as the context name.
198         if ((type == "webkit-3d") ||
199             (type == "experimental-webgl")) {
200             if (m_context && !m_context->is3d())
201                 return 0;
202             if (!m_context) {
203                 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
204                 if (m_context) {
205                     // Need to make sure a RenderLayer and compositing layer get created for the Canvas
206                     setNeedsStyleRecalc(SyntheticStyleChange);
207                 }
208             }
209             return m_context.get();
210         }
211     }
212 #else
213     UNUSED_PARAM(attrs);
214 #endif
215     return 0;
216 }
217
218 void HTMLCanvasElement::didDraw(const FloatRect& rect)
219 {
220     clearCopiedImage();
221
222     if (RenderBox* ro = renderBox()) {
223         FloatRect destRect = ro->contentBoxRect();
224         FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
225         r.intersect(destRect);
226         if (r.isEmpty() || m_dirtyRect.contains(r))
227             return;
228
229         m_dirtyRect.unite(r);
230         ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
231     }
232
233     HashSet<CanvasObserver*>::iterator end = m_observers.end();
234     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
235         (*it)->canvasChanged(this, rect);
236 }
237
238 void HTMLCanvasElement::reset()
239 {
240     if (m_ignoreReset)
241         return;
242
243     bool ok;
244     bool hadImageBuffer = hasCreatedImageBuffer();
245     int w = getAttribute(widthAttr).toInt(&ok);
246     if (!ok || w < 0)
247         w = DefaultWidth;
248     int h = getAttribute(heightAttr).toInt(&ok);
249     if (!ok || h < 0)
250         h = DefaultHeight;
251
252     if (m_contextStateSaver) {
253         // Reset to the initial graphics context state.
254         m_contextStateSaver->restore();
255         m_contextStateSaver->save();
256     }
257
258     if (m_context && m_context->is2d()) {
259         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
260         context2D->reset();
261     }
262
263     IntSize oldSize = size();
264     // If the size of an existing buffer matches, we can just clear it instead of reallocating.
265     // This optimization is only done for 2D canvases for now.
266     if (m_hasCreatedImageBuffer && oldSize == IntSize(w, h) && m_context && m_context->is2d()) {
267         if (!m_didClearImageBuffer)
268             clearImageBuffer();
269         return;
270     }
271     setSurfaceSize(IntSize(w, h));
272
273 #if ENABLE(WEBGL)
274     if (m_context && m_context->is3d() && oldSize != size())
275         static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height());
276 #endif
277
278     if (RenderObject* renderer = this->renderer()) {
279         if (m_rendererIsCanvas) {
280             if (oldSize != size()) {
281                 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
282 #if USE(ACCELERATED_COMPOSITING)
283                 if (renderBox() && renderBox()->hasLayer() && renderBox()->layer()->hasAcceleratedCompositing())
284                     renderBox()->layer()->contentChanged(RenderLayer::CanvasChanged);
285 #endif
286             }
287             if (hadImageBuffer)
288                 renderer->repaint();
289         }
290     }
291
292     HashSet<CanvasObserver*>::iterator end = m_observers.end();
293     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
294         (*it)->canvasResized(this);
295 }
296
297 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
298 {
299     ASSERT(m_context);
300 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
301     if (m_context->is2d())
302         return true;
303 #endif
304
305 #if USE(ACCELERATED_COMPOSITING)
306     if (!m_context->isAccelerated())
307         return true;
308
309     if (renderBox() && renderBox()->hasLayer() && renderBox()->layer()->hasAcceleratedCompositing())
310         return false;
311 #endif
312     return true;
313 }
314
315
316 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
317 {
318     // Clear the dirty rect
319     m_dirtyRect = FloatRect();
320
321     if (context->paintingDisabled())
322         return;
323     
324     if (m_context) {
325         if (!paintsIntoCanvasBuffer() && !document()->printing())
326             return;
327         m_context->paintRenderingResultsToCanvas();
328     }
329
330     if (hasCreatedImageBuffer()) {
331         ImageBuffer* imageBuffer = buffer();
332         if (imageBuffer) {
333             if (m_presentedImage)
334                 context->drawImage(m_presentedImage.get(), ColorSpaceDeviceRGB, pixelSnappedIntRect(r), CompositeSourceOver, useLowQualityScale);
335             else
336                 context->drawImageBuffer(imageBuffer, ColorSpaceDeviceRGB, pixelSnappedIntRect(r), CompositeSourceOver, useLowQualityScale);
337         }
338     }
339
340 #if ENABLE(WEBGL)    
341     if (is3D())
342         static_cast<WebGLRenderingContext*>(m_context.get())->markLayerComposited();
343 #endif
344 }
345
346 #if ENABLE(WEBGL)
347 bool HTMLCanvasElement::is3D() const
348 {
349     return m_context && m_context->is3d();
350 }
351 #endif
352
353 void HTMLCanvasElement::makeRenderingResultsAvailable()
354 {
355     if (m_context)
356         m_context->paintRenderingResultsToCanvas();
357 }
358
359 void HTMLCanvasElement::makePresentationCopy()
360 {
361     if (!m_presentedImage) {
362         // The buffer contains the last presented data, so save a copy of it.
363         m_presentedImage = buffer()->copyImage(CopyBackingStore);
364     }
365 }
366
367 void HTMLCanvasElement::clearPresentationCopy()
368 {
369     m_presentedImage.clear();
370 }
371
372 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
373 {
374     m_size = size;
375     m_hasCreatedImageBuffer = false;
376     m_contextStateSaver.clear();
377     m_imageBuffer.clear();
378     clearCopiedImage();
379 }
380
381 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
382 {
383     String lowercaseMimeType = mimeType.lower();
384
385     // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
386     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
387         lowercaseMimeType = "image/png";
388
389     return lowercaseMimeType;
390 }
391
392 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionCode& ec)
393 {
394     if (!m_originClean) {
395         ec = SECURITY_ERR;
396         return String();
397     }
398
399     if (m_size.isEmpty() || !buffer())
400         return String("data:,");
401
402     String encodingMimeType = toEncodingMimeType(mimeType);
403
404 #if USE(CG) || USE(SKIA)
405     // Try to get ImageData first, as that may avoid lossy conversions.
406     RefPtr<ImageData> imageData = getImageData();
407
408     if (imageData)
409         return ImageDataToDataURL(*imageData, encodingMimeType, quality);
410 #endif
411
412     makeRenderingResultsAvailable();
413
414     return buffer()->toDataURL(encodingMimeType, quality);
415 }
416
417 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
418 {
419     if (!m_context || !m_context->is3d())
420        return 0;
421
422 #if ENABLE(WEBGL)    
423     WebGLRenderingContext* ctx = static_cast<WebGLRenderingContext*>(m_context.get());
424
425     return ctx->paintRenderingResultsToImageData();
426 #else
427     return 0;
428 #endif
429 }
430
431 FloatRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
432 {
433     FloatRect deviceRect(logicalRect);
434     deviceRect.scale(m_deviceScaleFactor);
435
436     float x = floorf(deviceRect.x());
437     float y = floorf(deviceRect.y());
438     float w = ceilf(deviceRect.maxX() - x);
439     float h = ceilf(deviceRect.maxY() - y);
440     deviceRect.setX(x);
441     deviceRect.setY(y);
442     deviceRect.setWidth(w);
443     deviceRect.setHeight(h);
444
445     return deviceRect;
446 }
447
448 FloatSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
449 {
450     float width = ceilf(logicalSize.width() * m_deviceScaleFactor);
451     float height = ceilf(logicalSize.height() * m_deviceScaleFactor);
452     return FloatSize(width, height);
453 }
454
455 FloatSize HTMLCanvasElement::convertDeviceToLogical(const FloatSize& deviceSize) const
456 {
457     float width = ceilf(deviceSize.width() / m_deviceScaleFactor);
458     float height = ceilf(deviceSize.height() / m_deviceScaleFactor);
459     return FloatSize(width, height);
460 }
461
462 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
463 {
464     return document()->securityOrigin();
465 }
466
467 CSSStyleSelector* HTMLCanvasElement::styleSelector()
468 {
469     return document()->styleSelector();
470 }
471
472 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
473 {
474 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
475     UNUSED_PARAM(size);
476     return document()->settings() && document()->settings()->canvasUsesAcceleratedDrawing();
477 #elif ENABLE(ACCELERATED_2D_CANVAS)
478     if (m_context && !m_context->is2d())
479         return false;
480
481     Settings* settings = document()->settings();
482     if (!settings || !settings->accelerated2dCanvasEnabled())
483         return false;
484
485     // Do not use acceleration for small canvas.
486     if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
487         return false;
488
489 #if PLATFORM(CHROMIUM)
490     if (!PlatformSupport::canAccelerate2dCanvas())
491         return false;
492 #endif
493
494     return true;
495 #else
496     UNUSED_PARAM(size);
497     return false;
498 #endif
499 }
500
501 bool HTMLCanvasElement::shouldDefer() const
502 {
503 #if USE(SKIA)
504     if (m_context && !m_context->is2d())
505         return false;
506
507     Settings* settings = document()->settings();
508     if (!settings || !settings->deferred2dCanvasEnabled())
509         return false;
510
511     return true;
512 #else
513     return false;
514 #endif
515 }
516
517 void HTMLCanvasElement::createImageBuffer() const
518 {
519     ASSERT(!m_imageBuffer);
520
521     m_hasCreatedImageBuffer = true;
522     m_didClearImageBuffer = true;
523
524     FloatSize logicalSize = size();
525     FloatSize deviceSize = convertLogicalToDevice(logicalSize);
526     if (!deviceSize.isExpressibleAsIntSize())
527         return;
528
529     if (deviceSize.width() * deviceSize.height() > MaxCanvasArea)
530         return;
531 #if USE(SKIA)
532     if (deviceSize.width() > MaxSkiaDim || deviceSize.height() > MaxSkiaDim)
533         return;
534 #endif
535
536     IntSize bufferSize(deviceSize.width(), deviceSize.height());
537     if (!bufferSize.width() || !bufferSize.height())
538         return;
539
540     RenderingMode renderingMode = shouldAccelerate(bufferSize) ? Accelerated : 
541 #if USE(SKIA)
542         UnacceleratedNonPlatformBuffer;
543 #else
544         Unaccelerated;
545 #endif
546     DeferralMode deferralMode = shouldDefer() ? Deferred : NonDeferred;
547     m_imageBuffer = ImageBuffer::create(size(), m_deviceScaleFactor, ColorSpaceDeviceRGB, renderingMode, deferralMode);
548     if (!m_imageBuffer)
549         return;
550     m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
551     m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
552     m_imageBuffer->context()->setStrokeThickness(1);
553     m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
554
555 #if USE(JSC)
556     JSC::JSLock lock(JSC::SilenceAssertionsOnly);
557     size_t numBytes = 4 * m_imageBuffer->internalSize().width() * m_imageBuffer->internalSize().height();
558     scriptExecutionContext()->globalData()->heap.reportExtraMemoryCost(numBytes);
559 #endif
560
561 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
562     if (m_context && m_context->is2d())
563         // Recalculate compositing requirements if acceleration state changed.
564         const_cast<HTMLCanvasElement*>(this)->setNeedsStyleRecalc(SyntheticStyleChange);
565 #endif
566 }
567
568 GraphicsContext* HTMLCanvasElement::drawingContext() const
569 {
570     return buffer() ? m_imageBuffer->context() : 0;
571 }
572
573 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
574 {
575     if (!m_hasCreatedImageBuffer)
576         return 0;
577
578     return drawingContext();
579 }
580
581 ImageBuffer* HTMLCanvasElement::buffer() const
582 {
583     if (!m_hasCreatedImageBuffer)
584         createImageBuffer();
585     return m_imageBuffer.get();
586 }
587
588 Image* HTMLCanvasElement::copiedImage() const
589 {
590     if (!m_copiedImage && buffer()) {
591         if (m_context)
592             m_context->paintRenderingResultsToCanvas();
593         m_copiedImage = buffer()->copyImage(CopyBackingStore);
594     }
595     return m_copiedImage.get();
596 }
597
598 void HTMLCanvasElement::clearImageBuffer() const
599 {
600     ASSERT(m_hasCreatedImageBuffer);
601     ASSERT(!m_didClearImageBuffer);
602     ASSERT(m_context);
603
604     m_didClearImageBuffer = true;
605
606     if (m_context->is2d()) {
607         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
608         // No need to undo transforms/clip/etc. because we are called right after the context is reset.
609         context2D->clearRect(0, 0, width(), height());
610     }
611 }
612
613 void HTMLCanvasElement::clearCopiedImage()
614 {
615     m_copiedImage.clear();
616     m_didClearImageBuffer = false;
617 }
618
619 AffineTransform HTMLCanvasElement::baseTransform() const
620 {
621     ASSERT(m_hasCreatedImageBuffer);
622     FloatSize unscaledSize = size();
623     FloatSize deviceSize = convertLogicalToDevice(unscaledSize);
624     IntSize size(deviceSize.width(), deviceSize.height());
625     AffineTransform transform;
626     if (size.width() && size.height())
627         transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
628     return m_imageBuffer->baseTransform() * transform;
629 }
630
631 }