blob: f7aba297deb1c3f9a01f4b8a5dd5f2fb986261ef [file] [log] [blame]
/**************************************************************
*
* 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() ) );
}
}