| /************************************************************** |
| * |
| * 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 ); |
| } |
| |
| } |