c747486335
Change-Id: Id8b3c2bcf0bf4be5aba2812b0edda479bc20c6a9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139683 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
439 lines
16 KiB
C++
439 lines
16 KiB
C++
/* -*- 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: */
|