| /************************************************************** |
| * |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_canvas.hxx" |
| |
| #include <canvas/debug.hxx> |
| #include <tools/diagnose_ex.h> |
| #include <canvas/verbosetrace.hxx> |
| #include <canvas/canvastools.hxx> |
| |
| #include <rtl/math.hxx> |
| |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| #include <basegfx/point/b2dpoint.hxx> |
| #include <basegfx/tools/canvastools.hxx> |
| #include <basegfx/polygon/b2dpolygon.hxx> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <basegfx/polygon/b2dpolypolygontools.hxx> |
| #include <basegfx/numeric/ftools.hxx> |
| |
| #include <canvas/base/canvascustomspritehelper.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::tools::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::tools::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 |
| typedef ::std::vector< ::basegfx::B2DRectangle > VectorOfRects; |
| VectorOfRects 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. |
| VectorOfRects::const_iterator aCurr( aClipDifferences.begin() ); |
| const VectorOfRects::const_iterator aEnd( aClipDifferences.end() ); |
| while( aCurr != aEnd ) |
| { |
| mpSpriteCanvas->updateSprite( |
| rSprite, |
| maPosition, |
| ::basegfx::B2DRectangle( |
| maPosition + aCurr->getMinimum(), |
| maPosition + aCurr->getMaximum() ) ); |
| ++aCurr; |
| } |
| |
| // update calls all done |
| return true; |
| } |
| } |
| } |
| |
| // caller needs to perform update calls |
| return false; |
| } |
| |
| CanvasCustomSpriteHelper::CanvasCustomSpriteHelper() : |
| mpSpriteCanvas(), |
| maCurrClipBounds(), |
| maPosition(), |
| maSize(), |
| maTransform(), |
| mxClipPoly(), |
| mfPriority(0.0), |
| mfAlpha(0.0), |
| mbActive(false), |
| mbIsCurrClipRectangle(true), |
| mbIsContentFullyOpaque( false ), |
| mbAlphaDirty( true ), |
| mbPositionDirty( true ), |
| mbTransformDirty( true ), |
| mbClipDirty( true ), |
| mbPrioDirty( true ), |
| mbVisibilityDirty( true ) |
| { |
| } |
| |
| void CanvasCustomSpriteHelper::init( const geometry::RealSize2D& rSpriteSize, |
| const SpriteSurface::Reference& rOwningSpriteCanvas ) |
| { |
| ENSURE_OR_THROW( rOwningSpriteCanvas.get(), |
| "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() ) |
| { |
| const geometry::IntegerSize2D& rInputSize( |
| xBitmap->getSize() ); |
| const ::basegfx::B2DSize& rOurSize( |
| rSprite->getSizePixel() ); |
| |
| ::basegfx::B2DHomMatrix aTransform; |
| if( tools::isInside( |
| ::basegfx::B2DRectangle( 0.0,0.0, |
| rOurSize.getX(), |
| rOurSize.getY() ), |
| ::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.get() ) |
| return; // we're disposed |
| |
| if( alpha != mfAlpha ) |
| { |
| mfAlpha = alpha; |
| |
| if( mbActive ) |
| { |
| mpSpriteCanvas->updateSprite( rSprite, |
| maPosition, |
| getUpdateArea() ); |
| } |
| |
| mbAlphaDirty = true; |
| } |
| } |
| |
| void CanvasCustomSpriteHelper::move( const Sprite::Reference& rSprite, |
| const geometry::RealPoint2D& aNewPos, |
| const rendering::ViewState& viewState, |
| const rendering::RenderState& renderState ) |
| { |
| if( !mpSpriteCanvas.get() ) |
| 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 ) |
| { |
| const ::basegfx::B2DRectangle& rBounds( getFullSpriteRect() ); |
| |
| if( mbActive ) |
| { |
| mpSpriteCanvas->moveSprite( rSprite, |
| rBounds.getMinimum(), |
| rBounds.getMinimum() - maPosition + aPoint, |
| rBounds.getRange() ); |
| } |
| |
| maPosition = aPoint; |
| mbPositionDirty = true; |
| } |
| } |
| |
| void CanvasCustomSpriteHelper::transform( const Sprite::Reference& rSprite, |
| const geometry::AffineMatrix2D& aTransformation ) |
| { |
| ::basegfx::B2DHomMatrix aMatrix; |
| ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix, |
| aTransformation); |
| |
| if( maTransform != aMatrix ) |
| { |
| // 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 explicitely 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() ); |
| } |
| |
| mbClipDirty = true; |
| } |
| |
| void CanvasCustomSpriteHelper::setPriority( const Sprite::Reference& rSprite, |
| double nPriority ) |
| { |
| if( !mpSpriteCanvas.get() ) |
| return; // we're disposed |
| |
| if( nPriority != mfPriority ) |
| { |
| mfPriority = nPriority; |
| |
| if( mbActive ) |
| { |
| mpSpriteCanvas->updateSprite( rSprite, |
| maPosition, |
| getUpdateArea() ); |
| } |
| |
| mbPrioDirty = true; |
| } |
| } |
| |
| void CanvasCustomSpriteHelper::show( const Sprite::Reference& rSprite ) |
| { |
| if( !mpSpriteCanvas.get() ) |
| return; // we're disposed |
| |
| if( !mbActive ) |
| { |
| 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() ); |
| } |
| |
| mbVisibilityDirty = true; |
| } |
| } |
| |
| void CanvasCustomSpriteHelper::hide( const Sprite::Reference& rSprite ) |
| { |
| if( !mpSpriteCanvas.get() ) |
| return; // we're disposed |
| |
| if( mbActive ) |
| { |
| 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() ); |
| } |
| |
| mbVisibilityDirty = true; |
| } |
| } |
| |
| // Sprite interface |
| 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::B2DPoint CanvasCustomSpriteHelper::getPosPixel() const |
| { |
| return maPosition; |
| } |
| |
| ::basegfx::B2DVector CanvasCustomSpriteHelper::getSizePixel() const |
| { |
| return maSize; |
| } |
| |
| ::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() ); |
| } |
| |
| double CanvasCustomSpriteHelper::getPriority() const |
| { |
| return mfPriority; |
| } |
| |
| ::basegfx::B2DRange CanvasCustomSpriteHelper::getFullSpriteRect() const |
| { |
| // Internal! Only call with locked object mutex! |
| return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0, |
| maSize.getX(), |
| maSize.getY() ) ); |
| } |
| } |