/* -*- 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 aClipBounds(
::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(aClipBounds);
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 aInputSize(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,
aInputSize.Width,
aInputSize.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 aBounds
= getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
maSize.getX(),
maSize.getY() ) );
if( mbActive )
{
mpSpriteCanvas->moveSprite( rSprite,
aBounds.getMinimum(),
aBounds.getMinimum() - maPosition + aPoint,
aBounds.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 aPrevBounds( getUpdateArea() );
maTransform = aMatrix;
if( !updateClipState( rSprite ) &&
mbActive )
{
mpSpriteCanvas->updateSprite( rSprite,
maPosition,
aPrevBounds );
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 aPrevBounds( getUpdateArea() );
mxClipPoly = xClip;
if( !updateClipState( rSprite ) &&
mbActive )
{
mpSpriteCanvas->updateSprite( rSprite,
maPosition,
aPrevBounds );
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: */
↑ V530 The return value of function 'calcTransformedRectBounds' is required to be utilized.
↑ V530 The return value of function 'mergeViewAndRenderTransform' is required to be utilized.
↑ V530 The return value of function 'homMatrixFromAffineMatrix' is required to be utilized.