summaryrefslogtreecommitdiffstats
path: root/canvas/source/tools/canvascustomspritehelper.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /canvas/source/tools/canvascustomspritehelper.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'canvas/source/tools/canvascustomspritehelper.cxx')
-rw-r--r--canvas/source/tools/canvascustomspritehelper.cxx439
1 files changed, 439 insertions, 0 deletions
diff --git a/canvas/source/tools/canvascustomspritehelper.cxx b/canvas/source/tools/canvascustomspritehelper.cxx
new file mode 100644
index 0000000000..975d62325d
--- /dev/null
+++ b/canvas/source/tools/canvascustomspritehelper.cxx
@@ -0,0 +1,439 @@
+/* -*- 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 <sal/config.h>
+
+#include <com/sun/star/rendering/XPolyPolygon2D.hpp>
+#include <com/sun/star/geometry/RealSize2D.hpp>
+#include <com/sun/star/rendering/XBitmap.hpp>
+#include <com/sun/star/geometry/IntegerSize2D.hpp>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <rtl/math.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <base/canvascustomspritehelper.hxx>
+#include <canvas/canvastools.hxx>
+
+using namespace ::com::sun::star;
+
+
+namespace canvas
+{
+ bool CanvasCustomSpriteHelper::updateClipState( const Sprite::Reference& rSprite )
+ {
+ if( !mxClipPoly.is() )
+ {
+ // empty clip polygon -> everything is visible now
+ maCurrClipBounds.reset();
+ mbIsCurrClipRectangle = true;
+ }
+ else
+ {
+ const sal_Int32 nNumClipPolygons( mxClipPoly->getNumberOfPolygons() );
+
+ // clip is not empty - determine actual update area
+ ::basegfx::B2DPolyPolygon aClipPath(
+ polyPolygonFromXPolyPolygon2D( mxClipPoly ) );
+
+ // apply sprite transformation also to clip!
+ aClipPath.transform( maTransform );
+
+ // clip which is about to be set, expressed as a
+ // b2drectangle
+ const ::basegfx::B2DRectangle& rClipBounds(
+ ::basegfx::utils::getRange( aClipPath ) );
+
+ const ::basegfx::B2DRectangle aBounds( 0.0, 0.0,
+ maSize.getX(),
+ maSize.getY() );
+
+ // rectangular area which is actually covered by the sprite.
+ // coordinates are relative to the sprite origin.
+ ::basegfx::B2DRectangle aSpriteRectPixel;
+ ::canvas::tools::calcTransformedRectBounds( aSpriteRectPixel,
+ aBounds,
+ maTransform );
+
+ // aClipBoundsA = new clip bound rect, intersected
+ // with sprite area
+ ::basegfx::B2DRectangle aClipBoundsA(rClipBounds);
+ aClipBoundsA.intersect( aSpriteRectPixel );
+
+ if( nNumClipPolygons != 1 )
+ {
+ // clip cannot be a single rectangle -> cannot
+ // optimize update
+ mbIsCurrClipRectangle = false;
+ maCurrClipBounds = aClipBoundsA;
+ }
+ else
+ {
+ // new clip could be a single rectangle - check
+ // that now:
+ const bool bNewClipIsRect(
+ ::basegfx::utils::isRectangle( aClipPath.getB2DPolygon(0) ) );
+
+ // both new and old clip are truly rectangles
+ // - can now take the optimized path
+ const bool bUseOptimizedUpdate( bNewClipIsRect &&
+ mbIsCurrClipRectangle );
+
+ const ::basegfx::B2DRectangle aOldBounds( maCurrClipBounds );
+
+ // store new current clip type
+ maCurrClipBounds = aClipBoundsA;
+ mbIsCurrClipRectangle = bNewClipIsRect;
+
+ if( mbActive &&
+ bUseOptimizedUpdate )
+ {
+ // aClipBoundsB = maCurrClipBounds, i.e. last
+ // clip, intersected with sprite area
+ std::vector< ::basegfx::B2DRectangle > aClipDifferences;
+
+ // get all rectangles covered by exactly one
+ // of the polygons (aka XOR)
+ ::basegfx::computeSetDifference(aClipDifferences,
+ aClipBoundsA,
+ aOldBounds);
+
+ // aClipDifferences now contains the final
+ // update areas, coordinates are still relative
+ // to the sprite origin. before submitting
+ // this area to 'updateSprite()' we need to
+ // translate this area to the final position,
+ // coordinates need to be relative to the
+ // spritecanvas.
+ for( const auto& rClipDiff : aClipDifferences )
+ {
+ mpSpriteCanvas->updateSprite(
+ rSprite,
+ maPosition,
+ ::basegfx::B2DRectangle(
+ maPosition + rClipDiff.getMinimum(),
+ maPosition + rClipDiff.getMaximum() ) );
+ }
+
+ // update calls all done
+ return true;
+ }
+ }
+ }
+
+ // caller needs to perform update calls
+ return false;
+ }
+
+ CanvasCustomSpriteHelper::CanvasCustomSpriteHelper() :
+ mfPriority(0.0),
+ mfAlpha(0.0),
+ mbActive(false),
+ mbIsCurrClipRectangle(true),
+ mbIsContentFullyOpaque( false ),
+ mbTransformDirty( true )
+ {
+ }
+
+ void CanvasCustomSpriteHelper::init( const geometry::RealSize2D& rSpriteSize,
+ const SpriteSurface::Reference& rOwningSpriteCanvas )
+ {
+ ENSURE_OR_THROW( rOwningSpriteCanvas,
+ "CanvasCustomSpriteHelper::init(): Invalid owning sprite canvas" );
+
+ mpSpriteCanvas = rOwningSpriteCanvas;
+ maSize.setX( std::max( 1.0,
+ ceil( rSpriteSize.Width ) ) ); // round up to nearest int,
+ // enforce sprite to have at
+ // least (1,1) pixel size
+ maSize.setY( std::max( 1.0,
+ ceil( rSpriteSize.Height ) ) );
+ }
+
+ void CanvasCustomSpriteHelper::disposing()
+ {
+ mpSpriteCanvas.clear();
+ }
+
+ void CanvasCustomSpriteHelper::clearingContent( const Sprite::Reference& /*rSprite*/ )
+ {
+ // about to clear content to fully transparent
+ mbIsContentFullyOpaque = false;
+ }
+
+ void CanvasCustomSpriteHelper::checkDrawBitmap( const Sprite::Reference& rSprite,
+ const uno::Reference< rendering::XBitmap >& xBitmap,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ // check whether bitmap is non-alpha, and whether its
+ // transformed size covers the whole sprite.
+ if( xBitmap->hasAlpha() )
+ return;
+
+ const geometry::IntegerSize2D& rInputSize(xBitmap->getSize());
+ basegfx::B2DSize rOurSize(rSprite->getSizePixel().getX(), rSprite->getSizePixel().getY());
+
+ ::basegfx::B2DHomMatrix aTransform;
+ if( tools::isInside(
+ ::basegfx::B2DRectangle( 0.0,0.0,
+ rOurSize.getWidth(),
+ rOurSize.getHeight() ),
+ ::basegfx::B2DRectangle( 0.0,0.0,
+ rInputSize.Width,
+ rInputSize.Height ),
+ ::canvas::tools::mergeViewAndRenderTransform(aTransform,
+ viewState,
+ renderState) ) )
+ {
+ // bitmap is opaque and will fully cover the sprite,
+ // set flag appropriately
+ mbIsContentFullyOpaque = true;
+ }
+ }
+
+ void CanvasCustomSpriteHelper::setAlpha( const Sprite::Reference& rSprite,
+ double alpha )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( alpha != mfAlpha )
+ {
+ mfAlpha = alpha;
+
+ if( mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+ }
+
+ void CanvasCustomSpriteHelper::move( const Sprite::Reference& rSprite,
+ const geometry::RealPoint2D& aNewPos,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ ::basegfx::B2DHomMatrix aTransform;
+ ::canvas::tools::mergeViewAndRenderTransform(aTransform,
+ viewState,
+ renderState);
+
+ // convert position to device pixel
+ ::basegfx::B2DPoint aPoint(
+ ::basegfx::unotools::b2DPointFromRealPoint2D(aNewPos) );
+ aPoint *= aTransform;
+
+ if( aPoint == maPosition )
+ return;
+
+ const ::basegfx::B2DRectangle& rBounds
+ = getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
+ maSize.getX(),
+ maSize.getY() ) );
+
+ if( mbActive )
+ {
+ mpSpriteCanvas->moveSprite( rSprite,
+ rBounds.getMinimum(),
+ rBounds.getMinimum() - maPosition + aPoint,
+ rBounds.getRange() );
+ }
+
+ maPosition = aPoint;
+ }
+
+ void CanvasCustomSpriteHelper::transform( const Sprite::Reference& rSprite,
+ const geometry::AffineMatrix2D& aTransformation )
+ {
+ ::basegfx::B2DHomMatrix aMatrix;
+ ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix,
+ aTransformation);
+
+ if( maTransform == aMatrix )
+ return;
+
+ // retrieve bounds before and after transformation change.
+ const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() );
+
+ maTransform = aMatrix;
+
+ if( !updateClipState( rSprite ) &&
+ mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ rPrevBounds );
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+
+ mbTransformDirty = true;
+ }
+
+ void CanvasCustomSpriteHelper::clip( const Sprite::Reference& rSprite,
+ const uno::Reference< rendering::XPolyPolygon2D >& xClip )
+ {
+ // NULL xClip explicitly allowed here (to clear clipping)
+
+ // retrieve bounds before and after clip change.
+ const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() );
+
+ mxClipPoly = xClip;
+
+ if( !updateClipState( rSprite ) &&
+ mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ rPrevBounds );
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+
+ void CanvasCustomSpriteHelper::setPriority( const Sprite::Reference& rSprite,
+ double nPriority )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( nPriority != mfPriority )
+ {
+ mfPriority = nPriority;
+
+ if( mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+ }
+
+ void CanvasCustomSpriteHelper::show( const Sprite::Reference& rSprite )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( mbActive )
+ return;
+
+ mpSpriteCanvas->showSprite( rSprite );
+ mbActive = true;
+
+ // TODO(P1): if clip is the NULL clip (nothing visible),
+ // also save us the update call.
+
+ if( mfAlpha != 0.0 )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+
+ void CanvasCustomSpriteHelper::hide( const Sprite::Reference& rSprite )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( !mbActive )
+ return;
+
+ mpSpriteCanvas->hideSprite( rSprite );
+ mbActive = false;
+
+ // TODO(P1): if clip is the NULL clip (nothing visible),
+ // also save us the update call.
+
+ if( mfAlpha != 0.0 )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+
+ bool CanvasCustomSpriteHelper::isAreaUpdateOpaque( const ::basegfx::B2DRange& rUpdateArea ) const
+ {
+ if( !mbIsCurrClipRectangle ||
+ !mbIsContentFullyOpaque ||
+ !::rtl::math::approxEqual(mfAlpha, 1.0) )
+ {
+ // sprite either transparent, or clip rect does not
+ // represent exact bounds -> update might not be fully
+ // opaque
+ return false;
+ }
+ else
+ {
+ // make sure sprite rect fully covers update area -
+ // although the update area originates from the sprite,
+ // it's by no means guaranteed that it's limited to this
+ // sprite's update area - after all, other sprites might
+ // have been merged, or this sprite is moving.
+ return getUpdateArea().isInside( rUpdateArea );
+ }
+ }
+
+ ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea( const ::basegfx::B2DRange& rBounds ) const
+ {
+ // Internal! Only call with locked object mutex!
+ ::basegfx::B2DHomMatrix aTransform( maTransform );
+ aTransform.translate( maPosition.getX(),
+ maPosition.getY() );
+
+ // transform bounds at origin, as the sprite transformation is
+ // formulated that way
+ ::basegfx::B2DRectangle aTransformedBounds;
+ return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds,
+ rBounds,
+ aTransform );
+ }
+
+ ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea() const
+ {
+ // Internal! Only call with locked object mutex!
+
+ // return effective sprite rect, i.e. take active clip into
+ // account
+ if( maCurrClipBounds.isEmpty() )
+ return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
+ maSize.getX(),
+ maSize.getY() ) );
+ else
+ return ::basegfx::B2DRectangle(
+ maPosition + maCurrClipBounds.getMinimum(),
+ maPosition + maCurrClipBounds.getMaximum() );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */