blob: 3fe1f904c9b8fa742513e2314acca3aefd598297 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_canvas.hxx"
#include <canvas/debug.hxx>
#include <tools/diagnose_ex.h>
#include <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 |= ( sal_True == 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<sal_uInt16>(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;
rOutDev.SetLineColor();
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( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
(sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
(sal_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<sal_uInt16>(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;
rOutDev.SetLineColor();
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( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
(sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
(sal_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[(sal_uInt16)p] = ::Point(
basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
}
// close polygon explicitely
aTempPoly[(sal_uInt16)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<sal_uInt16>(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( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
(sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
(sal_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[(sal_uInt16)p] = ::Point(
basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
}
// close polygon explicitely
aTempPoly[(sal_uInt16)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< sal_uInt16 >(::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< sal_uInt8 >(
::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() ) );
sal_uInt8 nCol( static_cast< sal_uInt8 >(
::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);
}
}