/************************************************************************* * * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: canvashelper_texturefill.cxx,v $ * * $Revision: 1.4 $ * * last change: $Author: rt $ $Date: 2005-09-07 23:20:00 $ * * The Contents of this file are made available subject to * the terms of GNU Lesser General Public License Version 2.1. * * * GNU Lesser General Public License Version 2.1 * ============================================= * Copyright 2005 by Sun Microsystems, Inc. * 901 San Antonio Road, Palo Alto, CA 94303, USA * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * ************************************************************************/ #include #ifndef INCLUDED_RTL_MATH_HXX #include #endif #ifndef _COM_SUN_STAR_RENDERING_TEXTDIRECTION_HPP__ #include #endif #ifndef _COM_SUN_STAR_RENDERING_TEXTURINGMODE_HPP_ #include #endif #ifndef _COM_SUN_STAR_RENDERING_PATHCAPTYPE_HPP_ #include #endif #ifndef _COM_SUN_STAR_RENDERING_PATHJOINTYPE_HPP_ #include #endif #ifndef _TL_POLY_HXX #include #endif #ifndef _SV_WINDOW_HXX #include #endif #ifndef _SV_BITMAPEX_HXX #include #endif #ifndef _SV_BMPACC_HXX #include #endif #ifndef _VCL_CANVASTOOLS_HXX #include #endif #ifndef _BGFX_MATRIX_B2DHOMMATRIX_HXX #include #endif #ifndef _BGFX_RANGE_B2DRECTANGLE_HXX #include #endif #ifndef _BGFX_POINT_B2DPOINT_HXX #include #endif #ifndef _BGFX_VECTOR_B2DSIZE_HXX #include #endif #ifndef _BGFX_POLYGON_B2DPOLYGON_HXX #include #endif #ifndef _BGFX_POLYGON_B2DPOLYGONTOOLS_HXX #include #endif #ifndef _BGFX_POLYGON_B2DPOLYPOLYGONTOOLS_HXX #include #endif #ifndef _BGFX_POLYGON_B2DLINEGEOMETRY_HXX #include #endif #ifndef _BGFX_TOOLS_CANVASTOOLS_HXX #include #endif #ifndef _BGFX_NUMERIC_FTOOLS_HXX #include #endif #include #include #include #include "textlayout.hxx" #include "parametricpolypolygon.hxx" #include "canvashelper.hxx" #include "canvasbitmap.hxx" #include "impltools.hxx" #include "canvasfont.hxx" using namespace ::com::sun::star; namespace vclcanvas { namespace { bool textureFill( OutputDevice& rOutDev, GraphicObject& rGraphic, const ::Point& rPosPixel, const ::Size& rNextTileX, const ::Size& rNextTileY, sal_Int32 nTilesX, sal_Int32 nTilesY, const ::Size& rTileSize, const GraphicAttr& rAttr) { BOOL bRet( false ); Point aCurrPos; int nX, nY; for( nY=0; nY < nTilesY; ++nY ) { aCurrPos.X() = rPosPixel.X() + nY*rNextTileY.Width(); aCurrPos.Y() = rPosPixel.Y() + nY*rNextTileY.Height(); for( nX=0; nX < nTilesX; ++nX ) { // update return value. This method should return true, if // at least one of the looped Draws succeeded. bRet |= rGraphic.Draw( &rOutDev, aCurrPos, rTileSize, &rAttr ); aCurrPos.X() += rNextTileX.Width(); aCurrPos.Y() += rNextTileX.Height(); } } return bRet; } inline sal_Int32 roundDown( const double& rVal ) { return static_cast< sal_Int32 >( floor( rVal ) ); } inline sal_Int32 roundUp( const double& rVal ) { return static_cast< sal_Int32 >( ceil( rVal ) ); } } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas& rCanvas, const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, const rendering::ViewState& viewState, const rendering::RenderState& renderState, const uno::Sequence< rendering::Texture >& textures ) { CHECK_AND_THROW( xPolyPolygon.is(), "CanvasHelper::fillPolyPolygon(): polygon is NULL"); CHECK_AND_THROW( textures.getLength(), "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); if( mpOutDev.get() ) { tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev ); const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) ); PolyPolygon aPolyPoly( tools::mapPolyPolygon( tools::polyPolygonFromXPolyPolygon2D(xPolyPolygon), viewState, renderState ) ); // TODO(F1): Multi-texturing if( textures[0].Gradient.is() ) { uno::Reference< lang::XServiceInfo > xRef( textures[0].Gradient, uno::UNO_QUERY ); if( xRef.is() && xRef->getImplementationName().equals( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME))) ) { // TODO(Q1): Maybe use dynamic_cast here // TODO(E1): Return value // TODO(F1): FillRule static_cast(textures[0].Gradient.get())->fill( mpOutDev->getOutDev(), mp2ndOutDev.get() ? &mp2ndOutDev->getOutDev() : (OutputDevice*)NULL, aPolyPoly, viewState, renderState, textures[0], nTransparency ); } } else if( textures[0].Bitmap.is() ) { OSL_ENSURE( textures[0].RepeatModeX == rendering::TexturingMode::REPEAT && textures[0].RepeatModeY == rendering::TexturingMode::REPEAT, "CanvasHelper::fillTexturedPolyPolygon(): VCL canvas cannot currently clamp textures." ); const geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() ); CHECK_AND_THROW( aBmpSize.Width != 0 && aBmpSize.Height != 0, "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" ); // determine maximal bound rect of texture-filled // polygon const ::Rectangle aPolygonDeviceRect( aPolyPoly.GetBoundRect() ); // first of all, determine whether we have a // drawBitmap() in disguise // ========================================= const bool bRectangularPolygon( tools::isPolyPolygonEqualRectangle( aPolyPoly, aPolygonDeviceRect ) ); ::basegfx::B2DHomMatrix aTextureTransform; ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, textures[0].AffineTransform ); const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0); ::basegfx::B2DRectangle aTextureDeviceRect; ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect, aRect, aTextureTransform ); const ::Rectangle aIntegerTextureDeviceRect( ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) ); if( bRectangularPolygon && aIntegerTextureDeviceRect == aPolygonDeviceRect ) { // need alpha modulation? if( !::rtl::math::approxEqual( textures[0].Alpha, 1.0 ) ) { // setup alpha modulation values rendering::RenderState aLocalState( renderState ); ::canvas::tools::setDeviceColor( aLocalState, 0.0, 0.0, 0.0, textures[0].Alpha ); return drawBitmapModulated( rCanvas, textures[0].Bitmap, viewState, aLocalState ); } else { return drawBitmap( rCanvas, textures[0].Bitmap, viewState, renderState ); } } else { // No easy mapping to drawBitmap() - calculate // texturing parameters // =========================================== BitmapEx aBmpEx( tools::bitmapExFromXBitmap( textures[0].Bitmap ) ); // scale down bitmap to [0,1]x[0,1] rect, as required // from the XCanvas interface. ::basegfx::B2DHomMatrix aScaling; ::basegfx::B2DHomMatrix aTotalTransform; // with extra bitmap down-scaling ::basegfx::B2DHomMatrix aPureTotalTransform; // pure view*render*texture transform aScaling.scale( 1.0/aBmpSize.Width, 1.0/aBmpSize.Height ); aTotalTransform = aTextureTransform * aScaling; aPureTotalTransform = aTextureTransform; // combine with view and render transform ::basegfx::B2DHomMatrix aMatrix; ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState); // combine all three transformations into one // global texture-to-device-space transformation aTotalTransform *= aMatrix; aPureTotalTransform *= aMatrix; // analyze transformation, and setup an // appropriate GraphicObject ::basegfx::B2DVector aScale; ::basegfx::B2DPoint aOutputPos; double nRotate; double nShearX; aTotalTransform.decompose( aScale, aOutputPos, nRotate, nShearX ); GraphicAttr aGrfAttr; GraphicObjectSharedPtr pGrfObj; if( ::basegfx::fTools::equalZero( nShearX ) ) { // no shear, GraphicObject is enough (the // GraphicObject only supports scaling, rotation // and translation) // setup GraphicAttr aGrfAttr.SetMirrorFlags( ( aScale.getX() < 0.0 ? BMP_MIRROR_HORZ : 0 ) | ( aScale.getY() < 0.0 ? BMP_MIRROR_VERT : 0 ) ); aGrfAttr.SetRotation( static_cast< USHORT >(::basegfx::fround( nRotate*10.0 )) ); pGrfObj.reset( new GraphicObject( aBmpEx ) ); } else { // complex transformation, use generic affine bitmap // transformation aBmpEx = tools::transformBitmap( aBmpEx, aTotalTransform, uno::Sequence< double >(), tools::MODULATE_NONE); pGrfObj.reset( new GraphicObject( aBmpEx ) ); // clear scale values, generated bitmap already // contains scaling aScale.setX( 0.0 ); aScale.setY( 0.0 ); } // render texture tiled into polygon // ================================= // calc device space direction vectors. We employ // the followin approach for tiled output: the // texture bitmap is output in texture space // x-major order, i.e. tile neighbors in texture // space x direction are rendered back-to-back in // device coordinate space (after the full device // transformation). Thus, the aNextTile* vectors // denote the output position updates in device // space, to get from one tile to the next. ::basegfx::B2DVector aNextTileX( 1.0, 0.0 ); ::basegfx::B2DVector aNextTileY( 0.0, 1.0 ); aNextTileX *= aPureTotalTransform; aNextTileY *= aPureTotalTransform; ::basegfx::B2DHomMatrix aInverseTextureTransform( aPureTotalTransform ); CHECK_AND_THROW( aInverseTextureTransform.isInvertible(), "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" ); aInverseTextureTransform.invert(); // calc bound rect of extended texture area in // device coordinates. Therefore, we first calc // the area of the polygon bound rect in texture // space. To maintain texture phase, this bound // rect is then extended to integer coordinates // (extended, because shrinking might leave some // inner polygon areas unfilled). // Finally, the bound rect is transformed back to // device coordinate space, were we determine the // start point from it. ::basegfx::B2DRectangle aTextureSpacePolygonRect; ::canvas::tools::calcTransformedRectBounds( aTextureSpacePolygonRect, ::vcl::unotools::b2DRectangleFromRectangle( aPolygonDeviceRect ), aInverseTextureTransform ); // calc left, top of extended polygon rect in // texture space, create one-texture instance rect // from it (i.e. rect from start point extending // 1.0 units to the right and 1.0 units to the // bottom). Note that the rounding employed here // is a bit subtle, since we need to round up/down // as _soon_ as any fractional amount is // encountered. This is to ensure that the full // polygon area is filled with texture tiles. const sal_Int32 nX1( roundDown( aTextureSpacePolygonRect.getMinX() ) ); const sal_Int32 nY1( roundDown( aTextureSpacePolygonRect.getMinY() ) ); const sal_Int32 nX2( roundUp( aTextureSpacePolygonRect.getMaxX() ) ); const sal_Int32 nY2( roundUp( aTextureSpacePolygonRect.getMaxY() ) ); const ::basegfx::B2DRectangle aSingleTextureRect( nX1, nY1, nX1 + 1.0, nY1 + 1.0 ); // and convert back to device space ::basegfx::B2DRectangle aSingleDeviceTextureRect; ::canvas::tools::calcTransformedRectBounds( aSingleDeviceTextureRect, aSingleTextureRect, aPureTotalTransform ); const ::Point aPt( ::vcl::unotools::pointFromB2DPoint( aSingleDeviceTextureRect.getMinimum() ) ); const ::Size aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width ), ::basegfx::fround( aScale.getY() * aBmpSize.Height ) ); const ::Size aIntegerNextTileX( ::vcl::unotools::sizeFromB2DSize(aNextTileX) ); const ::Size aIntegerNextTileY( ::vcl::unotools::sizeFromB2DSize(aNextTileY) ); const sal_Int32 nTilesX( nX2 - nX1 ); const sal_Int32 nTilesY( nY2 - nY1 ); OutputDevice& rOutDev( mpOutDev->getOutDev() ); if( bRectangularPolygon ) { // use optimized output path // ------------------------- // this distinction really looks like a // micro-optimisation, but in fact greatly speeds up // especially complex fills. That's because when using // clipping, we can output polygons instead of // poly-polygons, and don't have to output the gradient // twice for XOR // setup alpha modulation if( !::rtl::math::approxEqual( textures[0].Alpha, 1.0 ) ) { // TODO(F1): Note that the GraphicManager has // a subtle difference in how it calculates // the resulting alpha value: it's using the // inverse alpha values (i.e. 'transparency'), // and calculates transOrig + transModulate, // instead of transOrig + transModulate - // transOrig*transModulate (which would be // equivalent to the origAlpha*modulateAlpha // the DX canvas performs) aGrfAttr.SetTransparency( static_cast< BYTE >( ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) ); } rOutDev.IntersectClipRegion( aPolygonDeviceRect ); textureFill( rOutDev, *pGrfObj, aPt, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); if( mp2ndOutDev ) { OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() ); r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect ); textureFill( r2ndOutDev, *pGrfObj, aPt, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); } } else { // output texture the hard way: XORing out the // polygon // =========================================== if( !::rtl::math::approxEqual( textures[0].Alpha, 1.0 ) ) { // uh-oh. alpha blending is required, // cannot do direct XOR, but have to // prepare the filled polygon within a // VDev VirtualDevice aVDev( rOutDev ); aVDev.SetOutputSizePixel( aPolygonDeviceRect.GetSize() ); // shift output to origin of VDev const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() ); aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(), -aPolygonDeviceRect.Top() ) ); aVDev.SetRasterOp( ROP_XOR ); textureFill( aVDev, *pGrfObj, aOutPos, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); aVDev.SetFillColor( COL_BLACK ); aVDev.SetRasterOp( ROP_0 ); aVDev.DrawPolyPolygon( aPolyPoly ); aVDev.SetRasterOp( ROP_XOR ); textureFill( aVDev, *pGrfObj, aOutPos, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); // output VDev content alpha-blended to // target position. const ::Point aEmptyPoint; Bitmap aContentBmp( aVDev.GetBitmap( aEmptyPoint, aVDev.GetOutputSizePixel() ) ); BYTE nCol( static_cast< BYTE >( ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) ); AlphaMask aAlpha( aVDev.GetOutputSizePixel(), &nCol ); BitmapEx aOutputBmpEx( aContentBmp, aAlpha ); rOutDev.DrawBitmapEx( aPolygonDeviceRect.TopLeft(), aOutputBmpEx ); if( mp2ndOutDev ) mp2ndOutDev->getOutDev().DrawBitmapEx( aPolygonDeviceRect.TopLeft(), aOutputBmpEx ); } else { // output via repeated XORing rOutDev.Push( PUSH_RASTEROP ); rOutDev.SetRasterOp( ROP_XOR ); textureFill( rOutDev, *pGrfObj, aPt, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); rOutDev.SetFillColor( COL_BLACK ); rOutDev.SetRasterOp( ROP_0 ); rOutDev.DrawPolyPolygon( aPolyPoly ); rOutDev.SetRasterOp( ROP_XOR ); textureFill( rOutDev, *pGrfObj, aPt, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); rOutDev.Pop(); if( mp2ndOutDev ) { OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() ); r2ndOutDev.Push( PUSH_RASTEROP ); r2ndOutDev.SetRasterOp( ROP_XOR ); textureFill( r2ndOutDev, *pGrfObj, aPt, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); r2ndOutDev.SetFillColor( COL_BLACK ); r2ndOutDev.SetRasterOp( ROP_0 ); r2ndOutDev.DrawPolyPolygon( aPolyPoly ); r2ndOutDev.SetRasterOp( ROP_XOR ); textureFill( r2ndOutDev, *pGrfObj, aPt, aIntegerNextTileX, aIntegerNextTileY, nTilesX, nTilesY, aSz, aGrfAttr ); r2ndOutDev.Pop(); } } } } } } // TODO(P1): Provide caching here. return uno::Reference< rendering::XCachedPrimitive >(NULL); } }