diff options
Diffstat (limited to 'canvas/source/vcl/canvashelper_texturefill.cxx')
-rw-r--r-- | canvas/source/vcl/canvashelper_texturefill.cxx | 1182 |
1 files changed, 1182 insertions, 0 deletions
diff --git a/canvas/source/vcl/canvashelper_texturefill.cxx b/canvas/source/vcl/canvashelper_texturefill.cxx new file mode 100644 index 000000000000..c1e71ceb943f --- /dev/null +++ b/canvas/source/vcl/canvashelper_texturefill.cxx @@ -0,0 +1,1182 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org 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 version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 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 <rtl/math.hxx> + +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> + +#include <tools/poly.hxx> +#include <vcl/window.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/bmpacc.hxx> +#include <vcl/virdev.hxx> +#include <vcl/canvastools.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/tools/tools.hxx> +#include <basegfx/tools/lerp.hxx> +#include <basegfx/tools/keystoplerp.hxx> +#include <basegfx/tools/canvastools.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <comphelper/sequence.hxx> + +#include <canvas/canvastools.hxx> +#include <canvas/parametricpolypolygon.hxx> + +#include <boost/bind.hpp> +#include <boost/tuple/tuple.hpp> + +#include "spritecanvas.hxx" +#include "canvashelper.hxx" +#include "impltools.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; + } + + + /** Fill linear or axial gradient + + Since most of the code for linear and axial gradients are + the same, we've a unified method here + */ + void fillLinearGradient( OutputDevice& rOutDev, + const ::basegfx::B2DHomMatrix& rTextureTransform, + const ::Rectangle& rBounds, + unsigned int nStepCount, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors ) + { + // determine general position of gradient in relation to + // the bound rect + // ===================================================== + + ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 ); + ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 ); + ::basegfx::B2DPoint aRightTop( 1.0, 0.0 ); + ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 ); + + aLeftTop *= rTextureTransform; + aLeftBottom *= rTextureTransform; + aRightTop *= rTextureTransform; + aRightBottom*= rTextureTransform; + + // calc length of bound rect diagonal + const ::basegfx::B2DVector aBoundRectDiagonal( + ::vcl::unotools::b2DPointFromPoint( rBounds.TopLeft() ) - + ::vcl::unotools::b2DPointFromPoint( rBounds.BottomRight() ) ); + const double nDiagonalLength( aBoundRectDiagonal.getLength() ); + + // create direction of gradient: + // _______ + // | | | + // -> | | | ... + // | | | + // ------- + ::basegfx::B2DVector aDirection( aRightTop - aLeftTop ); + aDirection.normalize(); + + // now, we potentially have to enlarge our gradient area + // atop and below the transformed [0,1]x[0,1] unit rect, + // for the gradient to fill the complete bound rect. + ::basegfx::tools::infiniteLineFromParallelogram( aLeftTop, + aLeftBottom, + aRightTop, + aRightBottom, + ::vcl::unotools::b2DRectangleFromRectangle( rBounds ) ); + + + // render gradient + // =============== + + // for linear gradients, it's easy to render + // non-overlapping polygons: just split the gradient into + // nStepCount small strips. Prepare the strip now. + + // For performance reasons, we create a temporary VCL + // polygon here, keep it all the way and only change the + // vertex values in the loop below (as ::Polygon is a + // pimpl class, creating one every loop turn would really + // stress the mem allocator) + ::Polygon aTempPoly( static_cast<USHORT>(5) ); + + OSL_ENSURE( nStepCount >= 3, + "fillLinearGradient(): stepcount smaller than 3" ); + + + // fill initial strip (extending two times the bound rect's + // diagonal to the 'left' + // ------------------------------------------------------ + + // calculate left edge, by moving left edge of the + // gradient rect two times the bound rect's diagonal to + // the 'left'. Since we postpone actual rendering into the + // loop below, we set the _right_ edge here, which will be + // readily copied into the left edge in the loop below + const ::basegfx::B2DPoint& rPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection ); + aTempPoly[1] = ::Point( ::basegfx::fround( rPoint1.getX() ), + ::basegfx::fround( rPoint1.getY() ) ); + + const ::basegfx::B2DPoint& rPoint2( aLeftBottom - 2.0*nDiagonalLength*aDirection ); + aTempPoly[2] = ::Point( ::basegfx::fround( rPoint2.getX() ), + ::basegfx::fround( rPoint2.getY() ) ); + + + // iteratively render all other strips + // ----------------------------------- + + // ensure that nStepCount matches color stop parity, to + // have a well-defined middle color e.g. for axial + // gradients. + if( (rColors.size() % 2) != (nStepCount % 2) ) + ++nStepCount; + + basegfx::tools::KeyStopLerp aLerper(rValues.maStops); + + // only iterate nStepCount-1 steps, as the last strip is + // explicitely painted below + for( unsigned int i=0; i<nStepCount-1; ++i ) + { + std::ptrdiff_t nIndex; + double fAlpha; + boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(double(i)/nStepCount); + + rOutDev.SetFillColor( + Color( (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)), + (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)), + (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) )); + + // copy right egde of polygon to left edge (and also + // copy the closing point) + aTempPoly[0] = aTempPoly[4] = aTempPoly[1]; + aTempPoly[3] = aTempPoly[2]; + + // calculate new right edge, from interpolating + // between start and end line. Note that i is + // increased by one, to account for the fact that we + // calculate the right border here (whereas the fill + // color is governed by the left edge) + const ::basegfx::B2DPoint& rPoint3( + (nStepCount - i-1)/double(nStepCount)*aLeftTop + + (i+1)/double(nStepCount)*aRightTop ); + aTempPoly[1] = ::Point( ::basegfx::fround( rPoint3.getX() ), + ::basegfx::fround( rPoint3.getY() ) ); + + const ::basegfx::B2DPoint& rPoint4( + (nStepCount - i-1)/double(nStepCount)*aLeftBottom + + (i+1)/double(nStepCount)*aRightBottom ); + aTempPoly[2] = ::Point( ::basegfx::fround( rPoint4.getX() ), + ::basegfx::fround( rPoint4.getY() ) ); + + rOutDev.DrawPolygon( aTempPoly ); + } + + // fill final strip (extending two times the bound rect's + // diagonal to the 'right' + // ------------------------------------------------------ + + // copy right egde of polygon to left edge (and also + // copy the closing point) + aTempPoly[0] = aTempPoly[4] = aTempPoly[1]; + aTempPoly[3] = aTempPoly[2]; + + // calculate new right edge, by moving right edge of the + // gradient rect two times the bound rect's diagonal to + // the 'right'. + const ::basegfx::B2DPoint& rPoint3( aRightTop + 2.0*nDiagonalLength*aDirection ); + aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround( rPoint3.getX() ), + ::basegfx::fround( rPoint3.getY() ) ); + + const ::basegfx::B2DPoint& rPoint4( aRightBottom + 2.0*nDiagonalLength*aDirection ); + aTempPoly[3] = ::Point( ::basegfx::fround( rPoint4.getX() ), + ::basegfx::fround( rPoint4.getY() ) ); + + rOutDev.SetFillColor( rColors.back() ); + + rOutDev.DrawPolygon( aTempPoly ); + } + + void fillPolygonalGradient( OutputDevice& rOutDev, + const ::basegfx::B2DHomMatrix& rTextureTransform, + const ::Rectangle& rBounds, + unsigned int nStepCount, + bool bFillNonOverlapping, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors ) + { + const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly ); + + ENSURE_OR_THROW( rGradientPoly.count() > 2, + "fillPolygonalGradient(): polygon without area given" ); + + // For performance reasons, we create a temporary VCL polygon + // here, keep it all the way and only change the vertex values + // in the loop below (as ::Polygon is a pimpl class, creating + // one every loop turn would really stress the mem allocator) + ::basegfx::B2DPolygon aOuterPoly( rGradientPoly ); + ::basegfx::B2DPolygon aInnerPoly; + + // subdivide polygon _before_ rendering, would otherwise have + // to be performed on every loop turn. + if( aOuterPoly.areControlPointsUsed() ) + aOuterPoly = ::basegfx::tools::adaptiveSubdivideByAngle(aOuterPoly); + + aInnerPoly = aOuterPoly; + + // only transform outer polygon _after_ copying it into + // aInnerPoly, because inner polygon has to be scaled before + // the actual texture transformation takes place + aOuterPoly.transform( rTextureTransform ); + + // determine overall transformation for inner polygon (might + // have to be prefixed by anisotrophic scaling) + ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix; + + + // apply scaling (possibly anisotrophic) to inner polygon + // ------------------------------------------------------ + + // scale inner polygon according to aspect ratio: for + // wider-than-tall bounds (nAspectRatio > 1.0), the inner + // polygon, representing the gradient focus, must have + // non-zero width. Specifically, a bound rect twice as wide as + // tall has a focus polygon of half it's width. + const double nAspectRatio( rValues.mnAspectRatio ); + if( nAspectRatio > 1.0 ) + { + // width > height case + aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio, + 0.0 ); + } + else if( nAspectRatio < 1.0 ) + { + // width < height case + aInnerPolygonTransformMatrix.scale( 0.0, + 1.0 - nAspectRatio ); + } + else + { + // isotrophic case + aInnerPolygonTransformMatrix.scale( 0.0, 0.0 ); + } + + // and finally, add texture transform to it. + aInnerPolygonTransformMatrix *= rTextureTransform; + + // apply final matrix to polygon + aInnerPoly.transform( aInnerPolygonTransformMatrix ); + + + const sal_uInt32 nNumPoints( aOuterPoly.count() ); + ::Polygon aTempPoly( static_cast<USHORT>(nNumPoints+1) ); + + // increase number of steps by one: polygonal gradients have + // the outermost polygon rendered in rColor2, and the + // innermost in rColor1. The innermost polygon will never + // have zero area, thus, we must divide the interval into + // nStepCount+1 steps. For example, to create 3 steps: + // + // | | + // |-------|-------|-------| + // | | + // 3 2 1 0 + // + // This yields 4 tick marks, where 0 is never attained (since + // zero-area polygons typically don't display perceivable + // color). + ++nStepCount; + + basegfx::tools::KeyStopLerp aLerper(rValues.maStops); + + if( !bFillNonOverlapping ) + { + // fill background + rOutDev.SetFillColor( rColors.front() ); + rOutDev.DrawRect( rBounds ); + + // render polygon + // ============== + + for( unsigned int i=1,p; i<nStepCount; ++i ) + { + const double fT( i/double(nStepCount) ); + + std::ptrdiff_t nIndex; + double fAlpha; + boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(fT); + + // lerp color + rOutDev.SetFillColor( + Color( (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)), + (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)), + (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) )); + + // scale and render polygon, by interpolating between + // outer and inner polygon. + + for( p=0; p<nNumPoints; ++p ) + { + const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) ); + const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) ); + + aTempPoly[(USHORT)p] = ::Point( + basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ), + basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) ); + } + + // close polygon explicitely + aTempPoly[(USHORT)p] = aTempPoly[0]; + + // TODO(P1): compare with vcl/source/gdi/outdev4.cxx, + // OutputDevice::ImplDrawComplexGradient(), there's a note + // that on some VDev's, rendering disjunct poly-polygons + // is faster! + rOutDev.DrawPolygon( aTempPoly ); + } + } + else + { + // render polygon + // ============== + + // For performance reasons, we create a temporary VCL polygon + // here, keep it all the way and only change the vertex values + // in the loop below (as ::Polygon is a pimpl class, creating + // one every loop turn would really stress the mem allocator) + ::PolyPolygon aTempPolyPoly; + ::Polygon aTempPoly2( static_cast<USHORT>(nNumPoints+1) ); + + aTempPoly2[0] = rBounds.TopLeft(); + aTempPoly2[1] = rBounds.TopRight(); + aTempPoly2[2] = rBounds.BottomRight(); + aTempPoly2[3] = rBounds.BottomLeft(); + aTempPoly2[4] = rBounds.TopLeft(); + + aTempPolyPoly.Insert( aTempPoly ); + aTempPolyPoly.Insert( aTempPoly2 ); + + for( unsigned int i=0,p; i<nStepCount; ++i ) + { + const double fT( (i+1)/double(nStepCount) ); + + std::ptrdiff_t nIndex; + double fAlpha; + boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(fT); + + // lerp color + rOutDev.SetFillColor( + Color( (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)), + (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)), + (UINT8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) )); + +#if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0 + if( i && !(i % 10) ) + rOutDev.SetFillColor( COL_RED ); +#endif + + // scale and render polygon. Note that here, we + // calculate the inner polygon, which is actually the + // start of the _next_ color strip. Thus, i+1 + + for( p=0; p<nNumPoints; ++p ) + { + const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) ); + const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) ); + + aTempPoly[(USHORT)p] = ::Point( + basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ), + basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) ); + } + + // close polygon explicitely + aTempPoly[(USHORT)p] = aTempPoly[0]; + + // swap inner and outer polygon + aTempPolyPoly.Replace( aTempPolyPoly.GetObject( 1 ), 0 ); + + if( i+1<nStepCount ) + { + // assign new inner polygon. Note that with this + // formulation, the internal pimpl objects for both + // temp polygons and the polypolygon remain identical, + // minimizing heap accesses (only a Polygon wrapper + // object is freed and deleted twice during this swap). + aTempPolyPoly.Replace( aTempPoly, 1 ); + } + else + { + // last, i.e. inner strip. Now, the inner polygon + // has zero area anyway, and to not leave holes in + // the gradient, finally render a simple polygon: + aTempPolyPoly.Remove( 1 ); + } + + rOutDev.DrawPolyPolygon( aTempPolyPoly ); + } + } + } + + void doGradientFill( OutputDevice& rOutDev, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors, + const ::basegfx::B2DHomMatrix& rTextureTransform, + const ::Rectangle& rBounds, + unsigned int nStepCount, + bool bFillNonOverlapping ) + { + switch( rValues.meType ) + { + case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR: + fillLinearGradient( rOutDev, + rTextureTransform, + rBounds, + nStepCount, + rValues, + rColors ); + break; + + case ::canvas::ParametricPolyPolygon::GRADIENT_ELLIPTICAL: + // FALLTHROUGH intended + case ::canvas::ParametricPolyPolygon::GRADIENT_RECTANGULAR: + fillPolygonalGradient( rOutDev, + rTextureTransform, + rBounds, + nStepCount, + bFillNonOverlapping, + rValues, + rColors ); + break; + + default: + ENSURE_OR_THROW( false, + "CanvasHelper::doGradientFill(): Unexpected case" ); + } + } + + int numColorSteps( const ::Color& rColor1, const ::Color& rColor2 ) + { + return ::std::max( + labs( rColor1.GetRed() - rColor2.GetRed() ), + ::std::max( + labs( rColor1.GetGreen() - rColor2.GetGreen() ), + labs( rColor1.GetBlue() - rColor2.GetBlue() ) ) ); + } + + bool gradientFill( OutputDevice& rOutDev, + OutputDevice* p2ndOutDev, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors, + const PolyPolygon& rPoly, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::Texture& texture, + int nTransparency ) + { + (void)nTransparency; + + // TODO(T2): It is maybe necessary to lock here, should + // maGradientPoly someday cease to be const. But then, beware of + // deadlocks, canvashelper calls this method with locked own + // mutex. + + // calc step size + // -------------- + int nColorSteps = 0; + for( size_t i=0; i<rColors.size()-1; ++i ) + nColorSteps += numColorSteps(rColors[i],rColors[i+1]); + + ::basegfx::B2DHomMatrix aTotalTransform; + const int nStepCount= + ::canvas::tools::calcGradientStepCount(aTotalTransform, + viewState, + renderState, + texture, + nColorSteps); + + rOutDev.SetLineColor(); + + // determine maximal bound rect of texture-filled + // polygon + const ::Rectangle aPolygonDeviceRectOrig( + rPoly.GetBoundRect() ); + + if( tools::isRectangle( rPoly ) ) + { + // use optimized output path + // ------------------------- + + // this distinction really looks like a + // micro-optimisation, but in fact greatly speeds up + // especially complex gradients. 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 + + rOutDev.Push( PUSH_CLIPREGION ); + rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig ); + doGradientFill( rOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + false ); + rOutDev.Pop(); + + if( p2ndOutDev ) + { + p2ndOutDev->Push( PUSH_CLIPREGION ); + p2ndOutDev->IntersectClipRegion( aPolygonDeviceRectOrig ); + doGradientFill( *p2ndOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + false ); + p2ndOutDev->Pop(); + } + } + else +#if defined(QUARTZ) // TODO: other ports should avoid the XOR-trick too (implementation vs. interface!) + { + const Region aPolyClipRegion( rPoly ); + + rOutDev.Push( PUSH_CLIPREGION ); + rOutDev.SetClipRegion( aPolyClipRegion ); + + doGradientFill( rOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + false ); + rOutDev.Pop(); + + if( p2ndOutDev ) + { + p2ndOutDev->Push( PUSH_CLIPREGION ); + p2ndOutDev->SetClipRegion( aPolyClipRegion ); + doGradientFill( *p2ndOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + false ); + p2ndOutDev->Pop(); + } + } +#else // TODO: remove once doing the XOR-trick in the canvas-layer becomes redundant + { + // output gradient the hard way: XORing out the polygon + rOutDev.Push( PUSH_RASTEROP ); + rOutDev.SetRasterOp( ROP_XOR ); + doGradientFill( rOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + true ); + rOutDev.SetFillColor( COL_BLACK ); + rOutDev.SetRasterOp( ROP_0 ); + rOutDev.DrawPolyPolygon( rPoly ); + rOutDev.SetRasterOp( ROP_XOR ); + doGradientFill( rOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + true ); + rOutDev.Pop(); + + if( p2ndOutDev ) + { + p2ndOutDev->Push( PUSH_RASTEROP ); + p2ndOutDev->SetRasterOp( ROP_XOR ); + doGradientFill( *p2ndOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + true ); + p2ndOutDev->SetFillColor( COL_BLACK ); + p2ndOutDev->SetRasterOp( ROP_0 ); + p2ndOutDev->DrawPolyPolygon( rPoly ); + p2ndOutDev->SetRasterOp( ROP_XOR ); + doGradientFill( *p2ndOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount, + true ); + p2ndOutDev->Pop(); + } + } +#endif // complex-clipping vs. XOR-trick + +#if 0 //defined(VERBOSE) && OSL_DEBUG_LEVEL > 0 + { + ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0); + ::basegfx::B2DRectangle aTextureDeviceRect; + ::basegfx::B2DHomMatrix aTextureTransform; + ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect, + aRect, + aTextureTransform ); + rOutDev.SetLineColor( COL_RED ); + rOutDev.SetFillColor(); + rOutDev.DrawRect( ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) ); + + rOutDev.SetLineColor( COL_BLUE ); + ::Polygon aPoly1( + ::vcl::unotools::rectangleFromB2DRectangle( aRect )); + ::basegfx::B2DPolygon aPoly2( aPoly1.getB2DPolygon() ); + aPoly2.transform( aTextureTransform ); + ::Polygon aPoly3( aPoly2 ); + rOutDev.DrawPolygon( aPoly3 ); + } +#endif + + return true; + } + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Sequence< rendering::Texture >& textures ) + { + ENSURE_ARG_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillPolyPolygon(): polygon is NULL"); + ENSURE_ARG_OR_THROW( textures.getLength(), + "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); + + if( mpOutDev ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev ); + + const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) ); + PolyPolygon aPolyPoly( tools::mapPolyPolygon( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon), + viewState, renderState ) ); + + // TODO(F1): Multi-texturing + if( textures[0].Gradient.is() ) + { + // try to cast XParametricPolyPolygon2D reference to + // our implementation class. + ::canvas::ParametricPolyPolygon* pGradient = + dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() ); + + if( pGradient && pGradient->getValues().maColors.getLength() ) + { + // copy state from Gradient polypoly locally + // (given object might change!) + const ::canvas::ParametricPolyPolygon::Values& rValues( + pGradient->getValues() ); + + if( rValues.maColors.getLength() < 2 ) + { + rendering::RenderState aTempState=renderState; + aTempState.DeviceColor = rValues.maColors[0]; + fillPolyPolygon(pCanvas, xPolyPolygon, viewState, aTempState); + } + else + { + std::vector< ::Color > aColors(rValues.maColors.getLength()); + std::transform(&rValues.maColors[0], + &rValues.maColors[0]+rValues.maColors.getLength(), + aColors.begin(), + boost::bind( + &vcl::unotools::stdColorSpaceSequenceToColor, + _1)); + + // TODO(E1): Return value + // TODO(F1): FillRule + gradientFill( mpOutDev->getOutDev(), + mp2ndOutDev.get() ? &mp2ndOutDev->getOutDev() : (OutputDevice*)NULL, + rValues, + aColors, + aPolyPoly, + viewState, + renderState, + textures[0], + nTransparency ); + } + } + else + { + // TODO(F1): The generic case is missing here + ENSURE_OR_THROW( false, + "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" ); + } + } + else if( textures[0].Bitmap.is() ) + { + const geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() ); + + ENSURE_ARG_OR_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::isRectangle( aPolyPoly ) ); + + ::basegfx::B2DHomMatrix aTotalTransform; + ::canvas::tools::mergeViewAndRenderTransform(aTotalTransform, + viewState, + renderState); + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + textures[0].AffineTransform ); + + aTotalTransform *= aTextureTransform; + + const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0); + ::basegfx::B2DRectangle aTextureDeviceRect; + ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect, + aRect, + aTotalTransform ); + + const ::Rectangle aIntegerTextureDeviceRect( + ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) ); + + if( bRectangularPolygon && + aIntegerTextureDeviceRect == aPolygonDeviceRect ) + { + rendering::RenderState aLocalState( renderState ); + ::canvas::tools::appendToRenderState(aLocalState, + aTextureTransform); + ::basegfx::B2DHomMatrix aScaleCorrection; + aScaleCorrection.scale( 1.0/aBmpSize.Width, + 1.0/aBmpSize.Height ); + ::canvas::tools::appendToRenderState(aLocalState, + aScaleCorrection); + + // need alpha modulation? + if( !::rtl::math::approxEqual( textures[0].Alpha, + 1.0 ) ) + { + // setup alpha modulation values + aLocalState.DeviceColor.realloc(4); + double* pColor = aLocalState.DeviceColor.getArray(); + pColor[0] = + pColor[1] = + pColor[2] = 0.0; + pColor[3] = textures[0].Alpha; + + return drawBitmapModulated( pCanvas, + textures[0].Bitmap, + viewState, + aLocalState ); + } + else + { + return drawBitmap( pCanvas, + textures[0].Bitmap, + viewState, + aLocalState ); + } + } + 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 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 ); + + ENSURE_ARG_OR_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( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinX() ) ); + const sal_Int32 nY1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinY() ) ); + const sal_Int32 nX2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxX() ) ); + const sal_Int32 nY2( ::canvas::tools::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 aPtRepeat( ::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 ::Point aPt( textures[0].RepeatModeX == rendering::TexturingMode::NONE ? + ::basegfx::fround( aOutputPos.getX() ) : aPtRepeat.X(), + textures[0].RepeatModeY == rendering::TexturingMode::NONE ? + ::basegfx::fround( aOutputPos.getY() ) : aPtRepeat.Y() ); + const sal_Int32 nTilesX( textures[0].RepeatModeX == rendering::TexturingMode::NONE ? + 1 : nX2 - nX1 ); + const sal_Int32 nTilesY( textures[0].RepeatModeX == rendering::TexturingMode::NONE ? + 1 : 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() ) ); + + const Region aPolyClipRegion( aPolyPoly ); + + aVDev.SetClipRegion( aPolyClipRegion ); + 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 + { + const Region aPolyClipRegion( aPolyPoly ); + + rOutDev.Push( PUSH_CLIPREGION ); + rOutDev.SetClipRegion( aPolyClipRegion ); + + textureFill( rOutDev, + *pGrfObj, + aPt, + aIntegerNextTileX, + aIntegerNextTileY, + nTilesX, + nTilesY, + aSz, + aGrfAttr ); + rOutDev.Pop(); + + if( mp2ndOutDev ) + { + OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() ); + r2ndOutDev.Push( PUSH_CLIPREGION ); + + r2ndOutDev.SetClipRegion( aPolyClipRegion ); + textureFill( r2ndOutDev, + *pGrfObj, + aPt, + aIntegerNextTileX, + aIntegerNextTileY, + nTilesX, + nTilesY, + aSz, + aGrfAttr ); + r2ndOutDev.Pop(); + } + } + } + } + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(NULL); + } + +} |