blob: 0494ce6ddaaed1aa258e31d3f048dd452ad65616 [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 <rtl/math.hxx>
#include <vcl/outdev.hxx>
#include <vcl/bitmap.hxx>
#include <vcl/alpha.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/canvastools.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/polygon/b2dpolygoncutandtouch.hxx>
#include <basegfx/polygon/b2dpolygontriangulator.hxx>
#include <basegfx/polygon/b2dpolygonclipper.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <canvas/canvastools.hxx>
#include "spritehelper.hxx"
using namespace ::com::sun::star;
namespace vclcanvas
{
SpriteHelper::SpriteHelper() :
mpBackBuffer(),
mpBackBufferMask(),
maContent(),
mbShowSpriteBounds(false)
{
}
void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize,
const ::canvas::SpriteSurface::Reference& rOwningSpriteCanvas,
const BackBufferSharedPtr& rBackBuffer,
const BackBufferSharedPtr& rBackBufferMask,
bool bShowSpriteBounds )
{
ENSURE_OR_THROW( rOwningSpriteCanvas.get() && rBackBuffer && rBackBufferMask,
"SpriteHelper::init(): Invalid sprite canvas or back buffer" );
mpBackBuffer = rBackBuffer;
mpBackBufferMask = rBackBufferMask;
mbShowSpriteBounds = bShowSpriteBounds;
init( rSpriteSize, rOwningSpriteCanvas );
}
void SpriteHelper::disposing()
{
mpBackBuffer.reset();
mpBackBufferMask.reset();
// forward to parent
CanvasCustomSpriteHelper::disposing();
}
void SpriteHelper::redraw( OutputDevice& rTargetSurface,
const ::basegfx::B2DPoint& rPos,
bool& io_bSurfacesDirty,
bool bBufferedUpdate ) const
{
(void)bBufferedUpdate; // not used on every platform
if( !mpBackBuffer ||
!mpBackBufferMask )
{
return; // we're disposed
}
// log output pos in device pixel
VERBOSE_TRACE( "SpriteHelper::redraw(): output pos is (%f, %f)",
rPos.getX(),
rPos.getY() );
const double fAlpha( getAlpha() );
if( isActive() &&
!::basegfx::fTools::equalZero( fAlpha ) )
{
const Point aEmptyPoint;
const ::basegfx::B2DVector& rOrigOutputSize( getSizePixel() );
// might get changed below (e.g. adapted for
// transformations). IMPORTANT: both position and size are
// rounded to integer values. From now on, only those
// rounded values are used, to keep clip and content in
// sync.
::Size aOutputSize( ::vcl::unotools::sizeFromB2DSize( rOrigOutputSize ) );
::Point aOutPos( ::vcl::unotools::pointFromB2DPoint( rPos ) );
// TODO(F3): Support for alpha-VDev
// Do we have to update our bitmaps (necessary if virdev
// was painted to, or transformation changed)?
const bool bNeedBitmapUpdate( io_bSurfacesDirty ||
hasTransformChanged() ||
maContent->IsEmpty() );
// updating content of sprite cache - surface is no
// longer dirty in relation to our cache
io_bSurfacesDirty = false;
transformUpdated();
if( bNeedBitmapUpdate )
{
Bitmap aBmp( mpBackBuffer->getOutDev().GetBitmap( aEmptyPoint,
aOutputSize ) );
if( isContentFullyOpaque() )
{
// optimized case: content canvas is fully
// opaque. Note: since we retrieved aBmp directly
// from an OutDev, it's already a 'display bitmap'
// on windows.
maContent = BitmapEx( aBmp );
}
else
{
// sprite content might contain alpha, create
// BmpEx, then.
Bitmap aMask( mpBackBufferMask->getOutDev().GetBitmap( aEmptyPoint,
aOutputSize ) );
// bitmasks are much faster than alphamasks on some platforms
// so convert to bitmask if useful
#if defined LINUX || defined FREEBSD || defined NETBSD || defined QUARTZ
// #122485# allow more than 1bit masks for Linux and Mac,
// but reduce to mono now
aMask.MakeMono(255);
#else
// #122485# assert when mask uses more than 1bit and reduce
// to mono
if( aMask.GetBitCount() != 1 )
{
OSL_ENSURE(false,
"CanvasCustomSprite::redraw(): Mask bitmap is not "
"monochrome (performance!)");
aMask.MakeMono(255);
}
#endif
// Note: since we retrieved aBmp and aMask
// directly from an OutDev, it's already a
// 'display bitmap' on windows.
maContent = BitmapEx( aBmp, aMask );
}
}
::basegfx::B2DHomMatrix aTransform( getTransformation() );
// check whether matrix is "easy" to handle - pure
// translations or scales are handled by OutputDevice
// alone
const bool bIdentityTransform( aTransform.isIdentity() );
// make transformation absolute (put sprite to final
// output position). Need to happen here, as we also have
// to translate the clip polygon
aTransform.translate( aOutPos.X(),
aOutPos.Y() );
if( !bIdentityTransform )
{
if( !::basegfx::fTools::equalZero( aTransform.get(0,1) ) ||
!::basegfx::fTools::equalZero( aTransform.get(1,0) ) )
{
// "complex" transformation, employ affine
// transformator
// modify output position, to account for the fact
// that transformBitmap() always normalizes its output
// bitmap into the smallest enclosing box.
::basegfx::B2DRectangle aDestRect;
::canvas::tools::calcTransformedRectBounds( aDestRect,
::basegfx::B2DRectangle(0,
0,
rOrigOutputSize.getX(),
rOrigOutputSize.getY()),
aTransform );
aOutPos.X() = ::basegfx::fround( aDestRect.getMinX() );
aOutPos.Y() = ::basegfx::fround( aDestRect.getMinY() );
// TODO(P3): Use optimized bitmap transformation here.
// actually re-create the bitmap ONLY if necessary
if( bNeedBitmapUpdate )
maContent = tools::transformBitmap( *maContent,
aTransform,
uno::Sequence<double>(),
tools::MODULATE_NONE );
aOutputSize = maContent->GetSizePixel();
}
else
{
// relatively 'simplistic' transformation -
// retrieve scale and translational offset
aOutputSize.setWidth (
::basegfx::fround( rOrigOutputSize.getX() * aTransform.get(0,0) ) );
aOutputSize.setHeight(
::basegfx::fround( rOrigOutputSize.getY() * aTransform.get(1,1) ) );
aOutPos.X() = ::basegfx::fround( aTransform.get(0,2) );
aOutPos.Y() = ::basegfx::fround( aTransform.get(1,2) );
}
}
// transformBitmap() might return empty bitmaps, for tiny
// scales.
if( !!(*maContent) )
{
// when true, fast path for slide transition has
// already redrawn the sprite.
bool bSpriteRedrawn( false );
rTargetSurface.Push( PUSH_CLIPREGION );
// apply clip (if any)
if( getClip().is() )
{
::basegfx::B2DPolyPolygon aClipPoly(
::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(
getClip() ));
if( aClipPoly.count() )
{
// aTransform already contains the
// translational component, moving the clip to
// the final sprite output position.
aClipPoly.transform( aTransform );
#if ! defined WNT && ! defined QUARTZ
// non-Windows only - bAtLeastOnePolygon is
// only used in non-WNT code below
// check whether maybe the clip consists
// solely out of rectangular polygons. If this
// is the case, enforce using the triangle
// clip region setup - non-optimized X11
// drivers tend to perform abyssmally on
// XPolygonRegion, which is used internally,
// when filling complex polypolygons.
bool bAtLeastOnePolygon( false );
const sal_Int32 nPolygons( aClipPoly.count() );
for( sal_Int32 i=0; i<nPolygons; ++i )
{
if( !::basegfx::tools::isRectangle(
aClipPoly.getB2DPolygon(i)) )
{
bAtLeastOnePolygon = true;
break;
}
}
#endif
if( mbShowSpriteBounds )
{
// Paint green sprite clip area
rTargetSurface.SetLineColor( Color( 0,255,0 ) );
rTargetSurface.SetFillColor();
rTargetSurface.DrawPolyPolygon(PolyPolygon(aClipPoly)); // #i76339#
}
#if ! defined WNT && ! defined QUARTZ
// as a matter of fact, this fast path only
// performs well for X11 - under Windows, the
// clip via SetTriangleClipRegion is faster.
if( bAtLeastOnePolygon &&
bBufferedUpdate &&
::rtl::math::approxEqual(fAlpha, 1.0) &&
!maContent->IsTransparent() )
{
// fast path for slide transitions
// (buffered, no alpha, no mask (because
// full slide is contained in the sprite))
// XOR bitmap onto backbuffer, clear area
// that should be _visible_ with black,
// XOR bitmap again on top of that -
// result: XOR cancels out where no black
// has been rendered, and yields the
// original bitmap, where black is
// underneath.
rTargetSurface.Push( PUSH_RASTEROP );
rTargetSurface.SetRasterOp( ROP_XOR );
rTargetSurface.DrawBitmap( aOutPos,
aOutputSize,
maContent->GetBitmap() );
rTargetSurface.SetLineColor();
rTargetSurface.SetFillColor( COL_BLACK );
rTargetSurface.SetRasterOp( ROP_0 );
rTargetSurface.DrawPolyPolygon(PolyPolygon(aClipPoly)); // #i76339#
rTargetSurface.SetRasterOp( ROP_XOR );
rTargetSurface.DrawBitmap( aOutPos,
aOutputSize,
maContent->GetBitmap() );
rTargetSurface.Pop();
bSpriteRedrawn = true;
}
else
#endif
{
Region aClipRegion( aClipPoly );
rTargetSurface.SetClipRegion( aClipRegion );
}
}
}
if( !bSpriteRedrawn )
{
if( ::rtl::math::approxEqual(fAlpha, 1.0) )
{
// no alpha modulation -> just copy to output
if( maContent->IsTransparent() )
rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize, *maContent );
else
rTargetSurface.DrawBitmap( aOutPos, aOutputSize, maContent->GetBitmap() );
}
else
{
// TODO(P3): Switch to OutputDevice::DrawTransparent()
// here
// draw semi-transparent
sal_uInt8 nColor( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) );
AlphaMask aAlpha( maContent->GetSizePixel(),
&nColor );
// mask out fully transparent areas
if( maContent->IsTransparent() )
aAlpha.Replace( maContent->GetMask(), 255 );
// alpha-blend to output
rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize,
BitmapEx( maContent->GetBitmap(),
aAlpha ) );
}
}
rTargetSurface.Pop();
if( mbShowSpriteBounds )
{
::PolyPolygon aMarkerPoly(
::canvas::tools::getBoundMarksPolyPolygon(
::basegfx::B2DRectangle(aOutPos.X(),
aOutPos.Y(),
aOutPos.X() + aOutputSize.Width()-1,
aOutPos.Y() + aOutputSize.Height()-1) ) );
// Paint little red sprite area markers
rTargetSurface.SetLineColor( COL_RED );
rTargetSurface.SetFillColor();
for( int i=0; i<aMarkerPoly.Count(); ++i )
{
rTargetSurface.DrawPolyLine( aMarkerPoly.GetObject((sal_uInt16)i) );
}
// paint sprite prio
Font aVCLFont;
aVCLFont.SetHeight( std::min(long(20),aOutputSize.Height()) );
aVCLFont.SetColor( COL_RED );
rTargetSurface.SetTextAlign(ALIGN_TOP);
rTargetSurface.SetTextColor( COL_RED );
rTargetSurface.SetFont( aVCLFont );
::rtl::OUString text( ::rtl::math::doubleToUString( getPriority(),
rtl_math_StringFormat_F,
2,'.',NULL,' ') );
rTargetSurface.DrawText( aOutPos+Point(2,2), text );
#if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0
OSL_TRACE( "SpriteHelper::redraw(): sprite %X has prio %f\n",
this, getPriority() );
#endif
}
}
}
}
::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const
{
return ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( xPoly );
}
}