Extremely large canvas crashes on pre-El Capitan machines
[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 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 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 "CanvasGradient.h"
32 #include "CanvasPattern.h"
33 #include "CanvasRenderingContext2D.h"
34 #include "Chrome.h"
35 #include "ChromeClient.h"
36 #include "Document.h"
37 #include "ExceptionCode.h"
38 #include "Frame.h"
39 #include "FrameLoaderClient.h"
40 #include "GeometryUtilities.h"
41 #include "GraphicsContext.h"
42 #include "HTMLNames.h"
43 #include "ImageData.h"
44 #include "MIMETypeRegistry.h"
45 #include "Page.h"
46 #include "RenderHTMLCanvas.h"
47 #include "ScriptController.h"
48 #include "Settings.h"
49 #include <math.h>
50
51 #include <runtime/JSCInlines.h>
52 #include <runtime/JSLock.h>
53
54 #if ENABLE(WEBGL)    
55 #include "WebGLContextAttributes.h"
56 #include "WebGLRenderingContextBase.h"
57 #endif
58
59 namespace WebCore {
60
61 using namespace HTMLNames;
62
63 // These values come from the WhatWG/W3C HTML spec.
64 static const int DefaultWidth = 300;
65 static const int DefaultHeight = 150;
66
67 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
68 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
69 // in exchange for a smaller maximum canvas size. The maximum canvas size is in device pixels.
70 #if PLATFORM(IOS)
71 static const unsigned MaxCanvasArea = 4096 * 4096;
72 #elif PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101100
73 static const unsigned MaxCanvasArea = 8192 * 8192;
74 #else
75 static const unsigned MaxCanvasArea = 16384 * 16384;
76 #endif
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     , m_originClean(true)
84     , m_hasCreatedImageBuffer(false)
85     , m_didClearImageBuffer(false)
86 {
87     ASSERT(hasTagName(canvasTag));
88 }
89
90 Ref<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
91 {
92     return adoptRef(*new HTMLCanvasElement(canvasTag, document));
93 }
94
95 Ref<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document& document)
96 {
97     return adoptRef(*new HTMLCanvasElement(tagName, document));
98 }
99
100 HTMLCanvasElement::~HTMLCanvasElement()
101 {
102     for (auto it = m_observers.begin(), end = m_observers.end(); it != end; ++it)
103         (*it)->canvasDestroyed(*this);
104
105     m_context = nullptr; // Ensure this goes away before the ImageBuffer.
106 }
107
108 void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
109 {
110     if (name == widthAttr || name == heightAttr)
111         reset();
112     HTMLElement::parseAttribute(name, value);
113 }
114
115 RenderPtr<RenderElement> HTMLCanvasElement::createElementRenderer(Ref<RenderStyle>&& style, const RenderTreePosition& insertionPosition)
116 {
117     Frame* frame = document().frame();
118     if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript)) {
119         m_rendererIsCanvas = true;
120         return createRenderer<RenderHTMLCanvas>(*this, WTF::move(style));
121     }
122
123     m_rendererIsCanvas = false;
124     return HTMLElement::createElementRenderer(WTF::move(style), insertionPosition);
125 }
126
127 bool HTMLCanvasElement::canContainRangeEndPoint() const
128 {
129     return false;
130 }
131
132 bool HTMLCanvasElement::canStartSelection() const
133 {
134     return false;
135 }
136
137 void HTMLCanvasElement::addObserver(CanvasObserver& observer)
138 {
139     m_observers.add(&observer);
140 }
141
142 void HTMLCanvasElement::removeObserver(CanvasObserver& observer)
143 {
144     m_observers.remove(&observer);
145 }
146
147 void HTMLCanvasElement::setHeight(int value)
148 {
149     setIntegralAttribute(heightAttr, value);
150 }
151
152 void HTMLCanvasElement::setWidth(int value)
153 {
154     setIntegralAttribute(widthAttr, value);
155 }
156
157 #if ENABLE(WEBGL)
158 static bool requiresAcceleratedCompositingForWebGL()
159 {
160 #if PLATFORM(GTK) || PLATFORM(EFL)
161     return false;
162 #else
163     return true;
164 #endif
165
166 }
167 static bool shouldEnableWebGL(Settings* settings)
168 {
169     if (!settings)
170         return false;
171
172     if (!settings->webGLEnabled())
173         return false;
174
175     if (!requiresAcceleratedCompositingForWebGL())
176         return true;
177
178     return settings->acceleratedCompositingEnabled();
179 }
180 #endif
181
182 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
183 {
184     // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
185     // context is already 2D, just return that. If the existing context is WebGL, then destroy it
186     // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
187     // context with any other type string will destroy any existing context.
188     
189     // FIXME: The code depends on the context not going away once created, to prevent JS from
190     // seeing a dangling pointer. So for now we will disallow the context from being changed
191     // once it is created. https://bugs.webkit.org/show_bug.cgi?id=117095
192     if (is2dType(type)) {
193         if (m_context && !m_context->is2d())
194             return nullptr;
195         if (!m_context) {
196             bool usesDashbardCompatibilityMode = false;
197 #if ENABLE(DASHBOARD_SUPPORT)
198             if (Settings* settings = document().settings())
199                 usesDashbardCompatibilityMode = settings->usesDashboardBackwardCompatibilityMode();
200 #endif
201             m_context = std::make_unique<CanvasRenderingContext2D>(this, document().inQuirksMode(), usesDashbardCompatibilityMode);
202 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
203             // Need to make sure a RenderLayer and compositing layer get created for the Canvas
204             setNeedsStyleRecalc(SyntheticStyleChange);
205 #endif
206         }
207         return m_context.get();
208     }
209 #if ENABLE(WEBGL)
210     if (shouldEnableWebGL(document().settings())) {
211
212         if (is3dType(type)) {
213             if (m_context && !m_context->is3d())
214                 return nullptr;
215             if (!m_context) {
216                 m_context = WebGLRenderingContextBase::create(this, static_cast<WebGLContextAttributes*>(attrs), type);
217                 if (m_context) {
218                     // Need to make sure a RenderLayer and compositing layer get created for the Canvas
219                     setNeedsStyleRecalc(SyntheticStyleChange);
220                 }
221             }
222             return m_context.get();
223         }
224     }
225 #else
226     UNUSED_PARAM(attrs);
227 #endif
228     return nullptr;
229 }
230     
231 bool HTMLCanvasElement::probablySupportsContext(const String& type, CanvasContextAttributes*)
232 {
233     // FIXME: Provide implementation that accounts for attributes. Bugzilla bug 117093
234     // https://bugs.webkit.org/show_bug.cgi?id=117093
235
236     // FIXME: The code depends on the context not going away once created (as getContext
237     // is implemented under this assumption) https://bugs.webkit.org/show_bug.cgi?id=117095
238     if (is2dType(type))
239         return !m_context || m_context->is2d();
240
241 #if ENABLE(WEBGL)
242     if (shouldEnableWebGL(document().settings())) {
243         if (is3dType(type))
244             return !m_context || m_context->is3d();
245     }
246 #endif
247     return false;
248 }
249
250 bool HTMLCanvasElement::is2dType(const String& type)
251 {
252     return type == "2d";
253 }
254
255 #if ENABLE(WEBGL)
256 bool HTMLCanvasElement::is3dType(const String& type)
257 {
258     // Retain support for the legacy "webkit-3d" name.
259     return type == "webgl" || type == "experimental-webgl" || type == "webkit-3d" || type == "experimental-webgl2";
260 }
261 #endif
262
263 void HTMLCanvasElement::didDraw(const FloatRect& rect)
264 {
265     clearCopiedImage();
266
267     if (RenderBox* ro = renderBox()) {
268         FloatRect destRect = ro->contentBoxRect();
269         FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
270         r.intersect(destRect);
271         if (r.isEmpty() || m_dirtyRect.contains(r))
272             return;
273
274         m_dirtyRect.unite(r);
275         ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
276     }
277
278     notifyObserversCanvasChanged(rect);
279 }
280
281 void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
282 {
283     for (auto it = m_observers.begin(), end = m_observers.end(); it != end; ++it)
284         (*it)->canvasChanged(*this, rect);
285 }
286
287 void HTMLCanvasElement::reset()
288 {
289     if (m_ignoreReset)
290         return;
291
292     bool ok;
293     bool hadImageBuffer = hasCreatedImageBuffer();
294
295     int w = getAttribute(widthAttr).toInt(&ok);
296     if (!ok || w < 0)
297         w = DefaultWidth;
298
299     int h = getAttribute(heightAttr).toInt(&ok);
300     if (!ok || h < 0)
301         h = DefaultHeight;
302
303     if (m_contextStateSaver) {
304         // Reset to the initial graphics context state.
305         m_contextStateSaver->restore();
306         m_contextStateSaver->save();
307     }
308
309     if (m_context && m_context->is2d()) {
310         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
311         context2D->reset();
312     }
313
314     IntSize oldSize = size();
315     IntSize newSize(w, h);
316     // If the size of an existing buffer matches, we can just clear it instead of reallocating.
317     // This optimization is only done for 2D canvases for now.
318     if (m_hasCreatedImageBuffer && oldSize == newSize && m_context && m_context->is2d()) {
319         if (!m_didClearImageBuffer)
320             clearImageBuffer();
321         return;
322     }
323
324     setSurfaceSize(newSize);
325
326 #if ENABLE(WEBGL)
327     if (is3D() && oldSize != size())
328         static_cast<WebGLRenderingContextBase*>(m_context.get())->reshape(width(), height());
329 #endif
330
331     if (auto renderer = this->renderer()) {
332         if (m_rendererIsCanvas) {
333             if (oldSize != size()) {
334                 downcast<RenderHTMLCanvas>(*renderer).canvasSizeChanged();
335                 if (renderBox() && renderBox()->hasAcceleratedCompositing())
336                     renderBox()->contentChanged(CanvasChanged);
337             }
338             if (hadImageBuffer)
339                 renderer->repaint();
340         }
341     }
342
343     for (auto it = m_observers.begin(), end = m_observers.end(); it != end; ++it)
344         (*it)->canvasResized(*this);
345 }
346
347 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
348 {
349     ASSERT(m_context);
350 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
351     if (m_context->is2d())
352         return true;
353 #endif
354
355     if (!m_context->isAccelerated())
356         return true;
357
358     if (renderBox() && renderBox()->hasAcceleratedCompositing())
359         return false;
360
361     return true;
362 }
363
364
365 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
366 {
367     // Clear the dirty rect
368     m_dirtyRect = FloatRect();
369
370     if (context->paintingDisabled())
371         return;
372     
373     if (m_context) {
374         if (!paintsIntoCanvasBuffer() && !document().printing())
375             return;
376         m_context->paintRenderingResultsToCanvas();
377     }
378
379     if (hasCreatedImageBuffer()) {
380         ImageBuffer* imageBuffer = buffer();
381         if (imageBuffer) {
382             if (m_presentedImage) {
383                 ImageOrientationDescription orientationDescription;
384 #if ENABLE(CSS_IMAGE_ORIENTATION)
385                 orientationDescription.setImageOrientationEnum(renderer()->style().imageOrientation());
386 #endif 
387                 context->drawImage(m_presentedImage.get(), ColorSpaceDeviceRGB, snappedIntRect(r), ImagePaintingOptions(orientationDescription, useLowQualityScale));
388             } else
389                 context->drawImageBuffer(imageBuffer, ColorSpaceDeviceRGB, snappedIntRect(r), useLowQualityScale);
390         }
391     }
392
393 #if ENABLE(WEBGL)    
394     if (is3D())
395         static_cast<WebGLRenderingContextBase*>(m_context.get())->markLayerComposited();
396 #endif
397 }
398
399 #if ENABLE(WEBGL)
400 bool HTMLCanvasElement::is3D() const
401 {
402     return m_context && m_context->is3d();
403 }
404 #endif
405
406 void HTMLCanvasElement::makeRenderingResultsAvailable()
407 {
408     if (m_context)
409         m_context->paintRenderingResultsToCanvas();
410 }
411
412 void HTMLCanvasElement::makePresentationCopy()
413 {
414     if (!m_presentedImage) {
415         // The buffer contains the last presented data, so save a copy of it.
416         m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
417     }
418 }
419
420 void HTMLCanvasElement::clearPresentationCopy()
421 {
422     m_presentedImage.clear();
423 }
424
425 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
426 {
427     m_size = size;
428     m_hasCreatedImageBuffer = false;
429     m_contextStateSaver = nullptr;
430     m_imageBuffer.reset();
431     clearCopiedImage();
432 }
433
434 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
435 {
436     String lowercaseMimeType = mimeType.lower();
437
438     // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
439     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
440         lowercaseMimeType = "image/png";
441
442     return lowercaseMimeType;
443 }
444
445 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionCode& ec)
446 {
447     if (!m_originClean) {
448         ec = SECURITY_ERR;
449         return String();
450     }
451
452     if (m_size.isEmpty() || !buffer())
453         return String("data:,");
454
455     String encodingMimeType = toEncodingMimeType(mimeType);
456
457 #if USE(CG)
458     // Try to get ImageData first, as that may avoid lossy conversions.
459     RefPtr<ImageData> imageData = getImageData();
460
461     if (imageData)
462         return ImageDataToDataURL(*imageData, encodingMimeType, quality);
463 #endif
464
465     makeRenderingResultsAvailable();
466
467     return buffer()->toDataURL(encodingMimeType, quality);
468 }
469
470 RefPtr<ImageData> HTMLCanvasElement::getImageData()
471 {
472 #if ENABLE(WEBGL)
473     if (!is3D())
474         return nullptr;
475
476     WebGLRenderingContextBase* ctx = static_cast<WebGLRenderingContextBase*>(m_context.get());
477
478     return ctx->paintRenderingResultsToImageData();
479 #else
480     return nullptr;
481 #endif
482 }
483
484 FloatRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
485 {
486     FloatRect deviceRect(logicalRect);
487
488     float x = floorf(deviceRect.x());
489     float y = floorf(deviceRect.y());
490     float w = ceilf(deviceRect.maxX() - x);
491     float h = ceilf(deviceRect.maxY() - y);
492     deviceRect.setX(x);
493     deviceRect.setY(y);
494     deviceRect.setWidth(w);
495     deviceRect.setHeight(h);
496
497     return deviceRect;
498 }
499
500 FloatSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
501 {
502     float width = ceilf(logicalSize.width());
503     float height = ceilf(logicalSize.height());
504     return FloatSize(width, height);
505 }
506
507 FloatSize HTMLCanvasElement::convertDeviceToLogical(const FloatSize& deviceSize) const
508 {
509     float width = ceilf(deviceSize.width());
510     float height = ceilf(deviceSize.height());
511     return FloatSize(width, height);
512 }
513
514 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
515 {
516     return document().securityOrigin();
517 }
518
519 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
520 {
521 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
522     UNUSED_PARAM(size);
523     return document().settings() && document().settings()->canvasUsesAcceleratedDrawing();
524 #elif ENABLE(ACCELERATED_2D_CANVAS)
525     if (m_context && !m_context->is2d())
526         return false;
527
528     Settings* settings = document().settings();
529     if (!settings || !settings->accelerated2dCanvasEnabled())
530         return false;
531
532     // Do not use acceleration for small canvas.
533     if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
534         return false;
535
536     return true;
537 #else
538     UNUSED_PARAM(size);
539     return false;
540 #endif
541 }
542
543 size_t HTMLCanvasElement::memoryCost() const
544 {
545     if (!m_imageBuffer)
546         return 0;
547     return 4 * m_imageBuffer->internalSize().width() * m_imageBuffer->internalSize().height();
548 }
549
550 void HTMLCanvasElement::createImageBuffer() const
551 {
552     ASSERT(!m_imageBuffer);
553
554     m_hasCreatedImageBuffer = true;
555     m_didClearImageBuffer = true;
556
557     FloatSize logicalSize = size();
558     FloatSize deviceSize = convertLogicalToDevice(logicalSize);
559     if (!deviceSize.isExpressibleAsIntSize())
560         return;
561
562     if (deviceSize.width() * deviceSize.height() > MaxCanvasArea) {
563         StringBuilder stringBuilder;
564         stringBuilder.appendLiteral("Canvas area exceeds the maximum limit (width * height > ");
565         stringBuilder.appendNumber(MaxCanvasArea);
566         stringBuilder.appendLiteral(").");
567         document().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, stringBuilder.toString());
568         return;
569     }
570
571     IntSize bufferSize(deviceSize.width(), deviceSize.height());
572     if (!bufferSize.width() || !bufferSize.height())
573         return;
574
575     RenderingMode renderingMode = shouldAccelerate(bufferSize) ? Accelerated : Unaccelerated;
576     m_imageBuffer = ImageBuffer::create(size(), 1, ColorSpaceDeviceRGB, renderingMode);
577     if (!m_imageBuffer)
578         return;
579     m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
580     m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
581     if (document().settings() && !document().settings()->antialiased2dCanvasEnabled())
582         m_imageBuffer->context()->setShouldAntialias(false);
583     m_imageBuffer->context()->setStrokeThickness(1);
584     m_contextStateSaver = std::make_unique<GraphicsContextStateSaver>(*m_imageBuffer->context());
585
586     JSC::JSLockHolder lock(scriptExecutionContext()->vm());
587     scriptExecutionContext()->vm().heap.reportExtraMemoryAllocated(memoryCost());
588
589 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
590     if (m_context && m_context->is2d())
591         // Recalculate compositing requirements if acceleration state changed.
592         const_cast<HTMLCanvasElement*>(this)->setNeedsStyleRecalc(SyntheticStyleChange);
593 #endif
594 }
595
596 GraphicsContext* HTMLCanvasElement::drawingContext() const
597 {
598     return buffer() ? m_imageBuffer->context() : nullptr;
599 }
600
601 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
602 {
603     if (!m_hasCreatedImageBuffer)
604         return nullptr;
605
606     return drawingContext();
607 }
608
609 ImageBuffer* HTMLCanvasElement::buffer() const
610 {
611     if (!m_hasCreatedImageBuffer)
612         createImageBuffer();
613     return m_imageBuffer.get();
614 }
615
616 Image* HTMLCanvasElement::copiedImage() const
617 {
618     if (!m_copiedImage && buffer()) {
619         if (m_context)
620             m_context->paintRenderingResultsToCanvas();
621         m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
622     }
623     return m_copiedImage.get();
624 }
625
626 void HTMLCanvasElement::clearImageBuffer() const
627 {
628     ASSERT(m_hasCreatedImageBuffer);
629     ASSERT(!m_didClearImageBuffer);
630     ASSERT(m_context);
631
632     m_didClearImageBuffer = true;
633
634     if (m_context->is2d()) {
635         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
636         // No need to undo transforms/clip/etc. because we are called right after the context is reset.
637         context2D->clearRect(0, 0, width(), height());
638     }
639 }
640
641 void HTMLCanvasElement::clearCopiedImage()
642 {
643     m_copiedImage.clear();
644     m_didClearImageBuffer = false;
645 }
646
647 AffineTransform HTMLCanvasElement::baseTransform() const
648 {
649     ASSERT(m_hasCreatedImageBuffer);
650     FloatSize unscaledSize = size();
651     FloatSize deviceSize = convertLogicalToDevice(unscaledSize);
652     IntSize size(deviceSize.width(), deviceSize.height());
653     AffineTransform transform;
654     if (size.width() && size.height())
655         transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
656     return m_imageBuffer->baseTransform() * transform;
657 }
658
659 }