summaryrefslogtreecommitdiff
path: root/canvas/source/vcl/canvashelper_texturefill.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'canvas/source/vcl/canvashelper_texturefill.cxx')
-rw-r--r--canvas/source/vcl/canvashelper_texturefill.cxx1182
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);
+ }
+
+}