/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "viewshape.hxx" #include #include using namespace ::com::sun::star; namespace slideshow::internal { // TODO(F2): Provide sensible setup for mtf-related attributes (fill mode, // char rotation etc.). Do that via mtf argument at this object bool ViewShape::prefetch( RendererCacheEntry& io_rCacheEntry, const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, const GDIMetaFileSharedPtr& rMtf, const ShapeAttributeLayerSharedPtr& rAttr ) { ENSURE_OR_RETURN_FALSE( rMtf, "ViewShape::prefetch(): no valid metafile!" ); if( rMtf != io_rCacheEntry.mpMtf || rDestinationCanvas != io_rCacheEntry.getDestinationCanvas() ) { // buffered renderer invalid, re-create ::cppcanvas::Renderer::Parameters aParms; // rendering attribute override parameter struct. For // every valid attribute, the corresponding struct // member is filled, which in the metafile renderer // forces rendering with the given attribute. if( rAttr ) { if( rAttr->isFillColorValid() ) { // convert RGBColor to RGBA32 integer. Note // that getIntegerColor() also truncates // out-of-range values appropriately aParms.maFillColor = rAttr->getFillColor().getIntegerColor(); } if( rAttr->isLineColorValid() ) { // convert RGBColor to RGBA32 integer. Note // that getIntegerColor() also truncates // out-of-range values appropriately aParms.maLineColor = rAttr->getLineColor().getIntegerColor(); } if( rAttr->isCharColorValid() ) { // convert RGBColor to RGBA32 integer. Note // that getIntegerColor() also truncates // out-of-range values appropriately aParms.maTextColor = rAttr->getCharColor().getIntegerColor(); } if( rAttr->isDimColorValid() ) { // convert RGBColor to RGBA32 integer. Note // that getIntegerColor() also truncates // out-of-range values appropriately // dim color overrides all other colors aParms.maFillColor = aParms.maLineColor = aParms.maTextColor = rAttr->getDimColor().getIntegerColor(); } if( rAttr->isFontFamilyValid() ) { aParms.maFontName = rAttr->getFontFamily(); } if( rAttr->isCharScaleValid() ) { ::basegfx::B2DHomMatrix aMatrix; // enlarge text by given scale factor. Do that // with the middle of the shape as the center // of scaling. aMatrix.translate( -0.5, -0.5 ); aMatrix.scale( rAttr->getCharScale(), rAttr->getCharScale() ); aMatrix.translate( 0.5, 0.5 ); aParms.maTextTransformation = aMatrix; } if( rAttr->isCharWeightValid() ) { aParms.maFontWeight = static_cast< sal_Int8 >( ::basegfx::fround( ::std::max( 0.0, ::std::min( 11.0, rAttr->getCharWeight() / 20.0 ) ) ) ); } if( rAttr->isCharPostureValid() ) { aParms.maFontLetterForm = rAttr->getCharPosture() == sal_Int16(awt::FontSlant_NONE) ? rendering::PanoseLetterForm::ANYTHING : rendering::PanoseLetterForm::OBLIQUE_CONTACT; } if( rAttr->isUnderlineModeValid() ) { aParms.maFontUnderline = rAttr->getUnderlineMode(); } } io_rCacheEntry.mpRenderer = ::cppcanvas::VCLFactory::createRenderer( rDestinationCanvas, *rMtf, aParms ); io_rCacheEntry.mpMtf = rMtf; io_rCacheEntry.mpDestinationCanvas = rDestinationCanvas; // also invalidate alpha compositing bitmap (created // new renderer, which possibly generates different // output). Do NOT invalidate, if we're incidentally // rendering INTO it. if( rDestinationCanvas != io_rCacheEntry.mpLastBitmapCanvas ) { io_rCacheEntry.mpLastBitmapCanvas.reset(); io_rCacheEntry.mpLastBitmap.reset(); } } return static_cast< bool >(io_rCacheEntry.mpRenderer); } bool ViewShape::draw( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, const GDIMetaFileSharedPtr& rMtf, const ShapeAttributeLayerSharedPtr& rAttr, const ::basegfx::B2DHomMatrix& rTransform, const ::basegfx::B2DPolyPolygon* pClip, const VectorOfDocTreeNodes& rSubsets ) const { ::cppcanvas::RendererSharedPtr pRenderer( getRenderer( rDestinationCanvas, rMtf, rAttr ) ); ENSURE_OR_RETURN_FALSE( pRenderer, "ViewShape::draw(): Invalid renderer" ); pRenderer->setTransformation( rTransform ); #if OSL_DEBUG_LEVEL >= 2 rendering::RenderState aRenderState; ::canvas::tools::initRenderState(aRenderState); ::canvas::tools::setRenderStateTransform(aRenderState, rTransform); aRenderState.DeviceColor.realloc(4); aRenderState.DeviceColor[0] = 1.0; aRenderState.DeviceColor[1] = 0.0; aRenderState.DeviceColor[2] = 0.0; aRenderState.DeviceColor[3] = 1.0; try { rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(0.0,0.0), geometry::RealPoint2D(1.0,1.0), rDestinationCanvas->getViewState(), aRenderState ); rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(1.0,0.0), geometry::RealPoint2D(0.0,1.0), rDestinationCanvas->getViewState(), aRenderState ); } catch( uno::Exception& ) { DBG_UNHANDLED_EXCEPTION("slideshow"); } #endif if( pClip ) pRenderer->setClip( *pClip ); else pRenderer->setClip(); if( rSubsets.empty() ) { return pRenderer->draw(); } else { // render subsets of whole metafile bool bRet(true); for( const auto& rSubset : rSubsets ) { if( !pRenderer->drawSubset( rSubset.getStartIndex(), rSubset.getEndIndex() ) ) bRet = false; } return bRet; } } namespace { /// Convert untransformed shape update area to device pixel. ::basegfx::B2DRectangle shapeArea2AreaPixel( const ::basegfx::B2DHomMatrix& rCanvasTransformation, const ::basegfx::B2DRectangle& rUntransformedArea ) { // convert area to pixel, and add anti-aliasing border // TODO(P1): Should the view transform some // day contain rotation/shear, transforming // the original bounds with the total // transformation might result in smaller // overall bounds. ::basegfx::B2DRectangle aBoundsPixel; ::canvas::tools::calcTransformedRectBounds( aBoundsPixel, rUntransformedArea, rCanvasTransformation ); // add antialiasing border around the shape (AA // touches pixel _outside_ the nominal bound rect) aBoundsPixel.grow( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE ); return aBoundsPixel; } /// Convert shape unit rect to device pixel. ::basegfx::B2DRectangle calcUpdateAreaPixel( const ::basegfx::B2DRectangle& rUnitBounds, const ::basegfx::B2DHomMatrix& rShapeTransformation, const ::basegfx::B2DHomMatrix& rCanvasTransformation, const ShapeAttributeLayerSharedPtr& pAttr ) { // calc update area for whole shape (including // character scaling) return shapeArea2AreaPixel( rCanvasTransformation, getShapeUpdateArea( rUnitBounds, rShapeTransformation, pAttr ) ); } } bool ViewShape::renderSprite( const ViewLayerSharedPtr& rViewLayer, const GDIMetaFileSharedPtr& rMtf, const ::basegfx::B2DRectangle& rOrigBounds, const ::basegfx::B2DRectangle& rBounds, const ::basegfx::B2DRectangle& rUnitBounds, UpdateFlags nUpdateFlags, const ShapeAttributeLayerSharedPtr& pAttr, const VectorOfDocTreeNodes& rSubsets, double nPrio, bool bIsVisible ) const { // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape, // in that all the common setup steps here are refactored to Shape (would then // have to be performed only _once_ per Shape paint). if( !bIsVisible || rUnitBounds.isEmpty() || rOrigBounds.isEmpty() || rBounds.isEmpty() ) { // shape is invisible or has zero size, no need to // update anything. if( mpSprite ) mpSprite->hide(); return true; } // calc sprite position, size and content transformation // ===================================================== // the shape transformation for a sprite is always a // simple scale-up to the nominal shape size. Everything // else is handled via the sprite transformation ::basegfx::B2DHomMatrix aNonTranslationalShapeTransformation; aNonTranslationalShapeTransformation.scale( rOrigBounds.getWidth(), rOrigBounds.getHeight() ); ::basegfx::B2DHomMatrix aShapeTransformation( aNonTranslationalShapeTransformation ); aShapeTransformation.translate( rOrigBounds.getMinX(), rOrigBounds.getMinY() ); const ::basegfx::B2DHomMatrix& rCanvasTransform( rViewLayer->getSpriteTransformation() ); // area actually needed for the sprite const ::basegfx::B2DRectangle& rSpriteBoundsPixel( calcUpdateAreaPixel( rUnitBounds, aShapeTransformation, rCanvasTransform, pAttr ) ); // actual area for the shape (without subsetting, but // including char scaling) const ::basegfx::B2DRectangle& rShapeBoundsPixel( calcUpdateAreaPixel( ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0), aShapeTransformation, rCanvasTransform, pAttr ) ); // nominal area for the shape (without subsetting, without // char scaling). NOTE: to cancel the shape translation, // contained in rSpriteBoundsPixel, this is _without_ any // translational component. ::basegfx::B2DRectangle aLogShapeBounds; const ::basegfx::B2DRectangle& rNominalShapeBoundsPixel( shapeArea2AreaPixel( rCanvasTransform, ::canvas::tools::calcTransformedRectBounds( aLogShapeBounds, ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0), aNonTranslationalShapeTransformation ) ) ); // create (or resize) sprite with sprite's pixel size, if // not done already auto aRange = rSpriteBoundsPixel.getRange(); basegfx::B2DSize rSpriteSizePixel(aRange.getX(), aRange.getY()); if( !mpSprite ) { mpSprite = std::make_shared( mpViewLayer, rSpriteSizePixel, nPrio ); } else { // TODO(F2): when the sprite _actually_ gets resized, // content needs a repaint! mpSprite->resize( rSpriteSizePixel ); } ENSURE_OR_RETURN_FALSE( mpSprite, "ViewShape::renderSprite(): No sprite" ); SAL_INFO("slideshow", "ViewShape::renderSprite(): Rendering sprite " << mpSprite.get() ); // always show the sprite (might have been hidden before) mpSprite->show(); // determine center of sprite output position in pixel // (assumption here: all shape transformations have the // shape center as the pivot point). From that, subtract // distance of rSpriteBoundsPixel's left, top edge from // rShapeBoundsPixel's center. This moves the sprite at // the appropriate output position within the virtual // rShapeBoundsPixel area. ::basegfx::B2DPoint aSpritePosPixel( rBounds.getCenter() ); aSpritePosPixel *= rCanvasTransform; aSpritePosPixel -= rShapeBoundsPixel.getCenter() - rSpriteBoundsPixel.getMinimum(); // the difference between rShapeBoundsPixel and // rSpriteBoundsPixel upper, left corner is: the offset we // have to move sprite output to the right, top (to make // the desired subset content visible at all) auto aDifference = rSpriteBoundsPixel.getMinimum() - rNominalShapeBoundsPixel.getMinimum(); const basegfx::B2DSize rSpriteCorrectionOffset(aDifference.getX(), aDifference.getY()); // offset added top, left for anti-aliasing (otherwise, // shapes fully filling the sprite will have anti-aliased // pixel cut off) const ::basegfx::B2DSize aAAOffset( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE, ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE ); // set pixel output offset to sprite: we always leave // ANTIALIASING_EXTRA_SIZE room atop and to the left, and, // what's more, for subsetted shapes, we _have_ to cancel // the effect of the shape renderer outputting the subset // at its absolute position inside the shape, instead of // at the origin. // NOTE: As for now, sprites are always positioned on // integer pixel positions on screen, have to round to // nearest integer here, too mpSprite->setPixelOffset( aAAOffset - ::basegfx::B2DSize( ::basegfx::fround( rSpriteCorrectionOffset.getWidth() ), ::basegfx::fround( rSpriteCorrectionOffset.getHeight() ) ) ); // always set sprite position and transformation, since // they do not relate directly to the update flags // (e.g. sprite position changes when sprite size changes) mpSprite->movePixel( aSpritePosPixel ); mpSprite->transform( getSpriteTransformation( basegfx::B2DVector(rSpriteSizePixel.getWidth(), rSpriteSizePixel.getHeight()), rOrigBounds.getRange(), pAttr ) ); // process flags // ============= bool bRedrawRequired( mbForceUpdate || (nUpdateFlags & UpdateFlags::Force) ); if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Alpha) ) { mpSprite->setAlpha( (pAttr && pAttr->isAlphaValid()) ? std::clamp(pAttr->getAlpha(), 0.0, 1.0) : 1.0 ); } if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Clip) ) { if( pAttr && pAttr->isClipValid() ) { ::basegfx::B2DPolyPolygon aClipPoly( pAttr->getClip() ); // extract linear part of canvas view transformation // (linear means: without translational components) ::basegfx::B2DHomMatrix aViewTransform( mpViewLayer->getTransformation() ); aViewTransform.set( 0, 2, 0.0 ); aViewTransform.set( 1, 2, 0.0 ); // make the clip 2*ANTIALIASING_EXTRA_SIZE larger // such that it's again centered over the sprite. aViewTransform.scale(rSpriteSizePixel.getWidth()/ (rSpriteSizePixel.getWidth()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE), rSpriteSizePixel.getHeight()/ (rSpriteSizePixel.getHeight()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE)); // transform clip polygon from view to device // coordinate space aClipPoly.transform( aViewTransform ); mpSprite->clip( aClipPoly ); } else mpSprite->clip(); } if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Content) ) { bRedrawRequired = true; // TODO(P1): maybe provide some appearance change methods at // the Renderer interface // force the renderer to be regenerated below, for the // different attributes to take effect invalidateRenderer(); } mbForceUpdate = false; if( !bRedrawRequired ) return true; // sprite needs repaint - output to sprite canvas // ============================================== ::cppcanvas::CanvasSharedPtr pContentCanvas( mpSprite->getContentCanvas() ); return draw( pContentCanvas, rMtf, pAttr, aShapeTransformation, nullptr, // clipping is done via Sprite::clip() rSubsets ); } bool ViewShape::render( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, const GDIMetaFileSharedPtr& rMtf, const ::basegfx::B2DRectangle& rBounds, const ::basegfx::B2DRectangle& rUpdateBounds, UpdateFlags nUpdateFlags, const ShapeAttributeLayerSharedPtr& pAttr, const VectorOfDocTreeNodes& rSubsets, bool bIsVisible ) const { // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape, // in that all the common setup steps here are refactored to Shape (would then // have to be performed only _once_ per Shape paint). if( !bIsVisible ) { SAL_INFO("slideshow", "ViewShape::render(): skipping shape " << this ); // shape is invisible, no need to update anything. return true; } // since we have no sprite here, _any_ update request // translates into a required redraw. bool bRedrawRequired( mbForceUpdate || nUpdateFlags != UpdateFlags::NONE ); if( nUpdateFlags & UpdateFlags::Content ) { // TODO(P1): maybe provide some appearance change methods at // the Renderer interface // force the renderer to be regenerated below, for the // different attributes to take effect invalidateRenderer(); } mbForceUpdate = false; if( !bRedrawRequired ) return true; SAL_INFO( "slideshow", "ViewShape::render(): rendering shape " << this << " at position (" << rBounds.getMinX() << "," << rBounds.getMinY() << ")" ); // shape needs repaint - setup all that's needed std::optional aClip; if( pAttr ) { // setup clip poly if( pAttr->isClipValid() ) aClip = pAttr->getClip(); // emulate global shape alpha by first rendering into // a temp bitmap, and then to screen (this would have // been much easier if we'd be currently a sprite - // see above) if( pAttr->isAlphaValid() ) { const double nAlpha( pAttr->getAlpha() ); if( !::basegfx::fTools::equalZero( nAlpha ) && !::rtl::math::approxEqual(nAlpha, 1.0) ) { // render with global alpha - have to prepare // a bitmap, and render that with modulated // alpha const ::basegfx::B2DHomMatrix aTransform( getShapeTransformation( rBounds, pAttr ) ); // TODO(P1): Should the view transform some // day contain rotation/shear, transforming // the original bounds with the total // transformation might result in smaller // overall bounds. // determine output rect of _shape update // area_ in device pixel const ::basegfx::B2DHomMatrix aCanvasTransform( rDestinationCanvas->getTransformation() ); ::basegfx::B2DRectangle aTmpRect; ::canvas::tools::calcTransformedRectBounds( aTmpRect, rUpdateBounds, aCanvasTransform ); // pixel size of cache bitmap: round up to // nearest int const ::basegfx::B2ISize aBmpSize( static_cast( aTmpRect.getWidth() )+1, static_cast( aTmpRect.getHeight() )+1 ); // try to fetch temporary surface for alpha // compositing (to achieve the global alpha // blend effect, have to first render shape as // a whole, then blit that surface with global // alpha to the destination) const RendererCacheVector::iterator aCompositingSurface( getCacheEntry( rDestinationCanvas ) ); if( !aCompositingSurface->mpLastBitmapCanvas || aCompositingSurface->mpLastBitmapCanvas->getSize() != aBmpSize ) { // create a bitmap of appropriate size ::cppcanvas::BitmapSharedPtr pBitmap( ::cppcanvas::BaseGfxFactory::createAlphaBitmap( rDestinationCanvas, aBmpSize ) ); ENSURE_OR_THROW(pBitmap, "ViewShape::render(): Could not create compositing surface"); aCompositingSurface->mpDestinationCanvas = rDestinationCanvas; aCompositingSurface->mpLastBitmap = pBitmap; aCompositingSurface->mpLastBitmapCanvas = pBitmap->getBitmapCanvas(); } // buffer aCompositingSurface iterator content // - said one might get invalidated during // draw() below. ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( aCompositingSurface->mpLastBitmapCanvas ); ::cppcanvas::BitmapSharedPtr pBitmap( aCompositingSurface->mpLastBitmap); // setup bitmap canvas transformation - // which happens to be the destination // canvas transformation without any // translational components. // But then, the render transformation as // calculated by getShapeTransformation() // above outputs the shape at its real // destination position. Thus, we have to // offset the output back to the origin, // for which we simply plug in the // negative position of the left, top edge // of the shape's bound rect in device // pixel into aLinearTransform below. ::basegfx::B2DHomMatrix aAdjustedCanvasTransform( aCanvasTransform ); aAdjustedCanvasTransform.translate( -aTmpRect.getMinX(), -aTmpRect.getMinY() ); pBitmapCanvas->setTransformation( aAdjustedCanvasTransform ); // TODO(P2): If no update flags, or only // alpha_update is set, we can save us the // rendering into the bitmap (uh, it's not // _that_ easy - for a forced redraw, // e.g. when ending an animation, we always // get UPDATE_FORCE here). // render into this bitmap if( !draw( pBitmapCanvas, rMtf, pAttr, aTransform, !aClip ? nullptr : &(*aClip), rSubsets ) ) { return false; } // render bitmap to screen, with given global // alpha. Since the bitmap already contains // pixel-equivalent output, we have to use the // inverse view transformation, adjusted with // the final shape output position (note: // cannot simply change the view // transformation here, as that would affect a // possibly set clip!) ::basegfx::B2DHomMatrix aBitmapTransform( aCanvasTransform ); OSL_ENSURE( aBitmapTransform.isInvertible(), "ViewShape::render(): View transformation is singular!" ); aBitmapTransform.invert(); const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createTranslateB2DHomMatrix( aTmpRect.getMinX(), aTmpRect.getMinY())); aBitmapTransform = aBitmapTransform * aTranslation; pBitmap->setTransformation( aBitmapTransform ); // finally, render bitmap alpha-modulated pBitmap->drawAlphaModulated( nAlpha ); return true; } } } // retrieve shape transformation, _with_ shape translation // to actual page position. const ::basegfx::B2DHomMatrix aTransform( getShapeTransformation( rBounds, pAttr ) ); return draw( rDestinationCanvas, rMtf, pAttr, aTransform, !aClip ? nullptr : &(*aClip), rSubsets ); } ViewShape::ViewShape( ViewLayerSharedPtr xViewLayer ) : mpViewLayer(std::move( xViewLayer )), maRenderers(), mpSprite(), mbAnimationMode( false ), mbForceUpdate( true ) { ENSURE_OR_THROW( mpViewLayer, "ViewShape::ViewShape(): Invalid View" ); } const ViewLayerSharedPtr& ViewShape::getViewLayer() const { return mpViewLayer; } ViewShape::RendererCacheVector::iterator ViewShape::getCacheEntry( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas ) const { // lookup destination canvas - is there already a renderer // created for that target? RendererCacheVector::iterator aIter; const RendererCacheVector::iterator aEnd( maRenderers.end() ); // already there? if( (aIter=::std::find_if( maRenderers.begin(), aEnd, [&rDestinationCanvas]( const RendererCacheEntry& rCacheEntry ) { return rDestinationCanvas == rCacheEntry.getDestinationCanvas(); } ) ) == aEnd ) { if( maRenderers.size() >= MAX_RENDER_CACHE_ENTRIES ) { // cache size exceeded - prune entries. For now, // simply remove the first one, which of course // breaks for more complex access schemes. But in // general, this leads to most recently used // entries to reside at the end of the vector. maRenderers.erase( maRenderers.begin() ); // ATTENTION: after this, both aIter and aEnd are // invalid! } // not yet in cache - add default-constructed cache // entry, to have something to return maRenderers.emplace_back( ); aIter = maRenderers.end()-1; } return aIter; } ::cppcanvas::RendererSharedPtr ViewShape::getRenderer( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, const GDIMetaFileSharedPtr& rMtf, const ShapeAttributeLayerSharedPtr& rAttr ) const { // lookup destination canvas - is there already a renderer // created for that target? const RendererCacheVector::iterator aIter( getCacheEntry( rDestinationCanvas ) ); // now we have a valid entry, either way. call prefetch() // on it, nevertheless - maybe the metafile changed, and // the renderer still needs an update (prefetch() will // detect that) if( prefetch( *aIter, rDestinationCanvas, rMtf, rAttr ) ) { return aIter->mpRenderer; } else { // prefetch failed - renderer is invalid return ::cppcanvas::RendererSharedPtr(); } } void ViewShape::invalidateRenderer() const { // simply clear the cache. Subsequent getRenderer() calls // will regenerate the Renderers. maRenderers.clear(); } ::basegfx::B2DSize ViewShape::getAntialiasingBorder() const { ENSURE_OR_THROW( mpViewLayer->getCanvas(), "ViewShape::getAntialiasingBorder(): Invalid ViewLayer canvas" ); const ::basegfx::B2DHomMatrix& rViewTransform( mpViewLayer->getTransformation() ); // TODO(F1): As a quick shortcut (did not want to invert // whole matrix here), taking only scale components of // view transformation matrix. This will be wrong when // e.g. shearing is involved. const double nXBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(0,0) ); const double nYBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(1,1) ); return ::basegfx::B2DSize( nXBorder, nYBorder ); } void ViewShape::enterAnimationMode() { mbForceUpdate = true; mbAnimationMode = true; } void ViewShape::leaveAnimationMode() { mpSprite.reset(); mbAnimationMode = false; mbForceUpdate = true; } bool ViewShape::update( const GDIMetaFileSharedPtr& rMtf, const RenderArgs& rArgs, UpdateFlags nUpdateFlags, bool bIsVisible ) const { ENSURE_OR_RETURN_FALSE( mpViewLayer->getCanvas(), "ViewShape::update(): Invalid layer canvas" ); // Shall we render to a sprite, or to a plain canvas? if( mbAnimationMode ) return renderSprite( mpViewLayer, rMtf, rArgs.maOrigBounds, rArgs.maBounds, rArgs.maUnitBounds, nUpdateFlags, rArgs.mrAttr, rArgs.mrSubsets, rArgs.mnShapePriority, bIsVisible ); else return render( mpViewLayer->getCanvas(), rMtf, rArgs.maBounds, rArgs.maUpdateBounds, nUpdateFlags, rArgs.mrAttr, rArgs.mrSubsets, bIsVisible ); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */