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 "core/html/HTMLCanvasElement.h"
     30 
     31 #include <math.h>
     32 #include "HTMLNames.h"
     33 #include "RuntimeEnabledFeatures.h"
     34 #include "bindings/v8/ExceptionState.h"
     35 #include "bindings/v8/ScriptController.h"
     36 #include "core/dom/Document.h"
     37 #include "core/dom/ExceptionCode.h"
     38 #include "core/html/ImageData.h"
     39 #include "core/html/canvas/Canvas2DContextAttributes.h"
     40 #include "core/html/canvas/CanvasRenderingContext2D.h"
     41 #include "core/html/canvas/WebGLContextAttributes.h"
     42 #include "core/html/canvas/WebGLRenderingContext.h"
     43 #include "core/page/Frame.h"
     44 #include "core/page/Settings.h"
     45 #include "core/platform/HistogramSupport.h"
     46 #include "core/platform/MIMETypeRegistry.h"
     47 #include "core/platform/graphics/GraphicsContextStateSaver.h"
     48 #include "core/platform/graphics/ImageBuffer.h"
     49 #include "core/rendering/RenderHTMLCanvas.h"
     50 #include "public/platform/Platform.h"
     51 
     52 namespace WebCore {
     53 
     54 using namespace HTMLNames;
     55 
     56 // These values come from the WhatWG spec.
     57 static const int DefaultWidth = 300;
     58 static const int DefaultHeight = 150;
     59 
     60 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
     61 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
     62 // in exchange for a smaller maximum canvas size.
     63 static const float MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
     64 
     65 //In Skia, we will also limit width/height to 32767.
     66 static const float MaxSkiaDim = 32767.0F; // Maximum width/height in CSS pixels.
     67 
     68 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* document)
     69     : HTMLElement(tagName, document)
     70     , m_size(DefaultWidth, DefaultHeight)
     71     , m_rendererIsCanvas(false)
     72     , m_ignoreReset(false)
     73     , m_deviceScaleFactor(1)
     74     , m_originClean(true)
     75     , m_hasCreatedImageBuffer(false)
     76     , m_didClearImageBuffer(false)
     77     , m_accelerationDisabled(false)
     78     , m_externallyAllocatedMemory(0)
     79 {
     80     ASSERT(hasTagName(canvasTag));
     81     ScriptWrappable::init(this);
     82 }
     83 
     84 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document)
     85 {
     86     return adoptRef(new HTMLCanvasElement(canvasTag, document));
     87 }
     88 
     89 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document)
     90 {
     91     return adoptRef(new HTMLCanvasElement(tagName, document));
     92 }
     93 
     94 HTMLCanvasElement::~HTMLCanvasElement()
     95 {
     96     setExternallyAllocatedMemory(0);
     97     HashSet<CanvasObserver*>::iterator end = m_observers.end();
     98     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
     99         (*it)->canvasDestroyed(this);
    100 
    101     m_context.clear(); // Ensure this goes away before the ImageBuffer.
    102 }
    103 
    104 void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    105 {
    106     if (name == widthAttr || name == heightAttr)
    107         reset();
    108     HTMLElement::parseAttribute(name, value);
    109 }
    110 
    111 RenderObject* HTMLCanvasElement::createRenderer(RenderStyle* style)
    112 {
    113     Frame* frame = document()->frame();
    114     if (frame && frame->script()->canExecuteScripts(NotAboutToExecuteScript)) {
    115         m_rendererIsCanvas = true;
    116         return new RenderHTMLCanvas(this);
    117     }
    118 
    119     m_rendererIsCanvas = false;
    120     return HTMLElement::createRenderer(style);
    121 }
    122 
    123 Node::InsertionNotificationRequest HTMLCanvasElement::insertedInto(ContainerNode* node)
    124 {
    125     setIsInCanvasSubtree(true);
    126     return HTMLElement::insertedInto(node);
    127 }
    128 
    129 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
    130 {
    131     m_observers.add(observer);
    132 }
    133 
    134 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
    135 {
    136     m_observers.remove(observer);
    137 }
    138 
    139 void HTMLCanvasElement::setHeight(int value)
    140 {
    141     setAttribute(heightAttr, String::number(value));
    142 }
    143 
    144 void HTMLCanvasElement::setWidth(int value)
    145 {
    146     setAttribute(widthAttr, String::number(value));
    147 }
    148 
    149 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
    150 {
    151     // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
    152     // context is already 2D, just return that. If the existing context is WebGL, then destroy it
    153     // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
    154     // context with any other type string will destroy any existing context.
    155     enum ContextType {
    156         Context2d,
    157         ContextWebkit3d,
    158         ContextExperimentalWebgl,
    159         ContextWebgl,
    160         // Only add new items to the end and keep the order of existing items.
    161         ContextTypeCount,
    162     };
    163 
    164     // FIXME - The code depends on the context not going away once created, to prevent JS from
    165     // seeing a dangling pointer. So for now we will disallow the context from being changed
    166     // once it is created.
    167     if (type == "2d") {
    168         if (m_context && !m_context->is2d())
    169             return 0;
    170         if (!m_context) {
    171             HistogramSupport::histogramEnumeration("Canvas.ContextType", Context2d, ContextTypeCount);
    172             m_context = CanvasRenderingContext2D::create(this, RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled() ? static_cast<Canvas2DContextAttributes*>(attrs) : 0, document()->inQuirksMode());
    173             if (m_context)
    174                 scheduleLayerUpdate();
    175         }
    176         return m_context.get();
    177     }
    178 
    179     Settings* settings = document()->settings();
    180     if (settings && settings->webGLEnabled()) {
    181         // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
    182         // Now that WebGL is ratified, we will also accept "webgl" as the context name in Chrome.
    183         ContextType contextType;
    184         bool is3dContext = true;
    185         if (type == "webkit-3d")
    186             contextType = ContextWebkit3d;
    187         else if (type == "experimental-webgl")
    188             contextType = ContextExperimentalWebgl;
    189         else if (type == "webgl")
    190             contextType = ContextWebgl;
    191         else
    192             is3dContext = false;
    193 
    194         if (is3dContext) {
    195             if (m_context && !m_context->is3d())
    196                 return 0;
    197             if (!m_context) {
    198                 HistogramSupport::histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount);
    199                 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
    200                 if (m_context)
    201                     scheduleLayerUpdate();
    202             }
    203             return m_context.get();
    204         }
    205     }
    206     return 0;
    207 }
    208 
    209 void HTMLCanvasElement::didDraw(const FloatRect& rect)
    210 {
    211     clearCopiedImage();
    212 
    213     if (RenderBox* ro = renderBox()) {
    214         FloatRect destRect = ro->contentBoxRect();
    215         FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
    216         r.intersect(destRect);
    217         if (r.isEmpty() || m_dirtyRect.contains(r))
    218             return;
    219 
    220         m_dirtyRect.unite(r);
    221         ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
    222     }
    223 
    224     notifyObserversCanvasChanged(rect);
    225 }
    226 
    227 void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
    228 {
    229     HashSet<CanvasObserver*>::iterator end = m_observers.end();
    230     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
    231         (*it)->canvasChanged(this, rect);
    232 }
    233 
    234 void HTMLCanvasElement::reset()
    235 {
    236     if (m_ignoreReset)
    237         return;
    238 
    239     bool ok;
    240     bool hadImageBuffer = hasCreatedImageBuffer();
    241 
    242     int w = getAttribute(widthAttr).toInt(&ok);
    243     if (!ok || w < 0)
    244         w = DefaultWidth;
    245 
    246     int h = getAttribute(heightAttr).toInt(&ok);
    247     if (!ok || h < 0)
    248         h = DefaultHeight;
    249 
    250     if (m_contextStateSaver) {
    251         // Reset to the initial graphics context state.
    252         m_contextStateSaver->restore();
    253         m_contextStateSaver->save();
    254     }
    255 
    256     if (m_context && m_context->is2d()) {
    257         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
    258         context2D->reset();
    259     }
    260 
    261     IntSize oldSize = size();
    262     IntSize newSize(w, h);
    263     float newDeviceScaleFactor = 1;
    264 
    265     // If the size of an existing buffer matches, we can just clear it instead of reallocating.
    266     // This optimization is only done for 2D canvases for now.
    267     if (m_hasCreatedImageBuffer && oldSize == newSize && m_deviceScaleFactor == newDeviceScaleFactor && m_context && m_context->is2d()) {
    268         if (!m_didClearImageBuffer)
    269             clearImageBuffer();
    270         return;
    271     }
    272 
    273     m_deviceScaleFactor = newDeviceScaleFactor;
    274 
    275     setSurfaceSize(newSize);
    276 
    277     if (m_context && m_context->is3d() && oldSize != size())
    278         static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height());
    279 
    280     if (RenderObject* renderer = this->renderer()) {
    281         if (m_rendererIsCanvas) {
    282             if (oldSize != size()) {
    283                 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
    284                 if (renderBox() && renderBox()->hasAcceleratedCompositing())
    285                     renderBox()->contentChanged(CanvasChanged);
    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 
    301     if (!m_context->isAccelerated())
    302         return true;
    303 
    304     if (renderBox() && renderBox()->hasAcceleratedCompositing())
    305         return false;
    306 
    307     return true;
    308 }
    309 
    310 
    311 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
    312 {
    313     // Clear the dirty rect
    314     m_dirtyRect = FloatRect();
    315 
    316     if (context->paintingDisabled())
    317         return;
    318 
    319     if (m_context) {
    320         if (!paintsIntoCanvasBuffer() && !document()->printing())
    321             return;
    322         m_context->paintRenderingResultsToCanvas();
    323     }
    324 
    325     if (hasCreatedImageBuffer()) {
    326         ImageBuffer* imageBuffer = buffer();
    327         if (imageBuffer) {
    328             CompositeOperator compositeOperator = !m_context || m_context->hasAlpha() ? CompositeSourceOver : CompositeCopy;
    329             if (m_presentedImage)
    330                 context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation, useLowQualityScale);
    331             else
    332                 context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), compositeOperator, BlendModeNormal, useLowQualityScale);
    333         }
    334     }
    335 
    336     if (is3D())
    337         static_cast<WebGLRenderingContext*>(m_context.get())->markLayerComposited();
    338 }
    339 
    340 bool HTMLCanvasElement::is3D() const
    341 {
    342     return m_context && m_context->is3d();
    343 }
    344 
    345 void HTMLCanvasElement::makeRenderingResultsAvailable()
    346 {
    347     if (m_context)
    348         m_context->paintRenderingResultsToCanvas();
    349 }
    350 
    351 void HTMLCanvasElement::makePresentationCopy()
    352 {
    353     if (!m_presentedImage) {
    354         // The buffer contains the last presented data, so save a copy of it.
    355         m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
    356     }
    357 }
    358 
    359 void HTMLCanvasElement::clearPresentationCopy()
    360 {
    361     m_presentedImage.clear();
    362 }
    363 
    364 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
    365 {
    366     m_size = size;
    367     m_hasCreatedImageBuffer = false;
    368     m_contextStateSaver.clear();
    369     m_imageBuffer.clear();
    370     setExternallyAllocatedMemory(0);
    371     clearCopiedImage();
    372 }
    373 
    374 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
    375 {
    376     String lowercaseMimeType = mimeType.lower();
    377 
    378     // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
    379     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
    380         lowercaseMimeType = "image/png";
    381 
    382     return lowercaseMimeType;
    383 }
    384 
    385 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionState& es)
    386 {
    387     if (!m_originClean) {
    388         es.throwDOMException(SecurityError);
    389         return String();
    390     }
    391 
    392     if (m_size.isEmpty() || !buffer())
    393         return String("data:,");
    394 
    395     String encodingMimeType = toEncodingMimeType(mimeType);
    396 
    397     // Try to get ImageData first, as that may avoid lossy conversions.
    398     RefPtr<ImageData> imageData = getImageData();
    399 
    400     if (imageData)
    401         return ImageDataToDataURL(*imageData, encodingMimeType, quality);
    402 
    403     makeRenderingResultsAvailable();
    404 
    405     return buffer()->toDataURL(encodingMimeType, quality);
    406 }
    407 
    408 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
    409 {
    410     if (!m_context || !m_context->is3d())
    411        return 0;
    412 
    413     WebGLRenderingContext* ctx = static_cast<WebGLRenderingContext*>(m_context.get());
    414 
    415     return ctx->paintRenderingResultsToImageData();
    416 }
    417 
    418 FloatRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
    419 {
    420     FloatRect deviceRect(logicalRect);
    421     deviceRect.scale(m_deviceScaleFactor);
    422 
    423     float x = floorf(deviceRect.x());
    424     float y = floorf(deviceRect.y());
    425     float w = ceilf(deviceRect.maxX() - x);
    426     float h = ceilf(deviceRect.maxY() - y);
    427     deviceRect.setX(x);
    428     deviceRect.setY(y);
    429     deviceRect.setWidth(w);
    430     deviceRect.setHeight(h);
    431 
    432     return deviceRect;
    433 }
    434 
    435 FloatSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
    436 {
    437     float width = ceilf(logicalSize.width() * m_deviceScaleFactor);
    438     float height = ceilf(logicalSize.height() * m_deviceScaleFactor);
    439     return FloatSize(width, height);
    440 }
    441 
    442 FloatSize HTMLCanvasElement::convertDeviceToLogical(const FloatSize& deviceSize) const
    443 {
    444     float width = ceilf(deviceSize.width() / m_deviceScaleFactor);
    445     float height = ceilf(deviceSize.height() / m_deviceScaleFactor);
    446     return FloatSize(width, height);
    447 }
    448 
    449 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
    450 {
    451     return document()->securityOrigin();
    452 }
    453 
    454 StyleResolver* HTMLCanvasElement::styleResolver()
    455 {
    456     return document()->styleResolver();
    457 }
    458 
    459 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
    460 {
    461     if (m_context && !m_context->is2d())
    462         return false;
    463 
    464     if (m_accelerationDisabled)
    465         return false;
    466 
    467     Settings* settings = document()->settings();
    468     if (!settings || !settings->accelerated2dCanvasEnabled())
    469         return false;
    470 
    471     // Do not use acceleration for small canvas.
    472     if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
    473         return false;
    474 
    475     if (!WebKit::Platform::current()->canAccelerate2dCanvas())
    476         return false;
    477 
    478     return true;
    479 }
    480 
    481 void HTMLCanvasElement::createImageBuffer()
    482 {
    483     ASSERT(!m_imageBuffer);
    484 
    485     m_hasCreatedImageBuffer = true;
    486     m_didClearImageBuffer = true;
    487 
    488     FloatSize logicalSize = size();
    489     FloatSize deviceSize = convertLogicalToDevice(logicalSize);
    490     if (!deviceSize.isExpressibleAsIntSize())
    491         return;
    492 
    493     if (deviceSize.width() * deviceSize.height() > MaxCanvasArea)
    494         return;
    495 
    496     if (deviceSize.width() > MaxSkiaDim || deviceSize.height() > MaxSkiaDim)
    497         return;
    498 
    499     IntSize bufferSize(deviceSize.width(), deviceSize.height());
    500     if (!bufferSize.width() || !bufferSize.height())
    501         return;
    502 
    503     RenderingMode renderingMode = shouldAccelerate(bufferSize) ? Accelerated : UnacceleratedNonPlatformBuffer;
    504     OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
    505     m_imageBuffer = ImageBuffer::create(size(), m_deviceScaleFactor, renderingMode, opacityMode);
    506     if (!m_imageBuffer)
    507         return;
    508     setExternallyAllocatedMemory(4 * width() * height());
    509     m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
    510     if (document()->settings() && !document()->settings()->antialiased2dCanvasEnabled())
    511         m_imageBuffer->context()->setShouldAntialias(false);
    512     // GraphicsContext's defaults don't always agree with the 2d canvas spec.
    513     // See CanvasRenderingContext2D::State::State() for more information.
    514     m_imageBuffer->context()->setMiterLimit(10);
    515     m_imageBuffer->context()->setStrokeThickness(1);
    516     m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
    517 
    518     // Recalculate compositing requirements if acceleration state changed.
    519     if (m_context && m_context->is2d())
    520         scheduleLayerUpdate();
    521 }
    522 
    523 void HTMLCanvasElement::setExternallyAllocatedMemory(intptr_t externallyAllocatedMemory)
    524 {
    525     v8::V8::AdjustAmountOfExternalAllocatedMemory(externallyAllocatedMemory - m_externallyAllocatedMemory);
    526     m_externallyAllocatedMemory = externallyAllocatedMemory;
    527 }
    528 
    529 GraphicsContext* HTMLCanvasElement::drawingContext() const
    530 {
    531     return buffer() ? m_imageBuffer->context() : 0;
    532 }
    533 
    534 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
    535 {
    536     if (!m_hasCreatedImageBuffer)
    537         return 0;
    538 
    539     return drawingContext();
    540 }
    541 
    542 ImageBuffer* HTMLCanvasElement::buffer() const
    543 {
    544     if (!m_hasCreatedImageBuffer)
    545         const_cast<HTMLCanvasElement*>(this)->createImageBuffer();
    546     return m_imageBuffer.get();
    547 }
    548 
    549 Image* HTMLCanvasElement::copiedImage() const
    550 {
    551     if (!m_copiedImage && buffer()) {
    552         if (m_context)
    553             m_context->paintRenderingResultsToCanvas();
    554         m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
    555     }
    556     return m_copiedImage.get();
    557 }
    558 
    559 void HTMLCanvasElement::clearImageBuffer()
    560 {
    561     ASSERT(m_hasCreatedImageBuffer);
    562     ASSERT(!m_didClearImageBuffer);
    563     ASSERT(m_context);
    564 
    565     m_didClearImageBuffer = true;
    566 
    567     if (m_context->is2d()) {
    568         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
    569         // No need to undo transforms/clip/etc. because we are called right after the context is reset.
    570         context2D->clearRect(0, 0, width(), height());
    571     }
    572 }
    573 
    574 void HTMLCanvasElement::clearCopiedImage()
    575 {
    576     m_copiedImage.clear();
    577     m_didClearImageBuffer = false;
    578 }
    579 
    580 AffineTransform HTMLCanvasElement::baseTransform() const
    581 {
    582     ASSERT(m_hasCreatedImageBuffer);
    583     FloatSize unscaledSize = size();
    584     FloatSize deviceSize = convertLogicalToDevice(unscaledSize);
    585     IntSize size(deviceSize.width(), deviceSize.height());
    586     AffineTransform transform;
    587     if (size.width() && size.height())
    588         transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
    589     return m_imageBuffer->baseTransform() * transform;
    590 }
    591 
    592 }
    593