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