#i105937# Much improved gradient support for canvas/basegfx/drawinglayer.

See http://blog.thebehrens.net/2009/07/28/hackweek-iv-canvas-convwatch/ for more background information
This commit is contained in:
thb
2009-10-16 00:43:16 +02:00
parent 9b9d44ee50
commit 1837a267a2
24 changed files with 1024 additions and 679 deletions

View File

@@ -49,7 +49,6 @@
#include <com/sun/star/rendering/XGraphicDevice.hpp>
#include <com/sun/star/rendering/TexturingMode.hpp>
#include <com/sun/star/rendering/XParametricPolyPolygon2DFactory.hpp>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/geometry/RealPoint2D.hpp>
#include <com/sun/star/rendering/ViewState.hpp>
@@ -61,6 +60,7 @@
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <basegfx/tools/canvastools.hxx>
#include <basegfx/tools/gradienttools.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
@@ -590,13 +590,12 @@ namespace cppcanvas
// discernible difference should be visible.
nSteps > 64 )
{
uno::Reference< rendering::XParametricPolyPolygon2DFactory > xFactory(
uno::Reference< lang::XMultiServiceFactory> xFactory(
rParms.mrCanvas->getUNOCanvas()->getDevice()->getParametricPolyPolygonFactory() );
if( xFactory.is() )
{
::basegfx::B2DHomMatrix aTextureTransformation;
rendering::Texture aTexture;
rendering::Texture aTexture;
aTexture.RepeatModeX = rendering::TexturingMode::CLAMP;
aTexture.RepeatModeY = rendering::TexturingMode::CLAMP;
@@ -631,242 +630,118 @@ namespace cppcanvas
uno::Sequence< uno::Sequence < double > > aColors(2);
uno::Sequence< double > aStops(2);
aStops[0] = 0.0;
aStops[1] = 1.0;
if( rGradient.GetStyle() == GRADIENT_AXIAL )
{
aStops.realloc(3);
aColors.realloc(3);
aColors[0] = aStartColor;
aColors[1] = aEndColor;
aStops[0] = 0.0;
aStops[1] = 0.5;
aStops[2] = 1.0;
aColors[0] = aEndColor;
aColors[1] = aStartColor;
aColors[2] = aEndColor;
}
else
{
aStops[0] = 0.0;
aStops[1] = 1.0;
// Setup texture transformation
// ----------------------------
aColors[0] = aStartColor;
aColors[1] = aEndColor;
}
const ::basegfx::B2DRectangle aBounds(
::basegfx::tools::getRange(aDevicePoly) );
const ::basegfx::B2DVector aOffset(
rGradient.GetOfsX() / 100.0,
rGradient.GetOfsY() / 100.0);
double fRotation( rGradient.GetAngle() * M_PI / 1800.0 );
const double fBorder( rGradient.GetBorder() / 100.0 );
// setup rotation angle. VCL rotates
// counter-clockwise, while canvas transformation
// rotates clockwise
double nRotation( -rGradient.GetAngle() * M_PI / 1800.0 );
basegfx::B2DHomMatrix aRot90;
aRot90.rotate(M_PI_2);
basegfx::ODFGradientInfo aGradInfo;
rtl::OUString aGradientService;
switch( rGradient.GetStyle() )
{
case GRADIENT_LINEAR:
// FALLTHROUGH intended
basegfx::tools::createLinearODFGradientInfo(aGradInfo,
aBounds,
nSteps,
fBorder,
fRotation);
// map odf to svg gradient orientation - x
// instead of y direction
aGradInfo.maTextureTransform = aGradInfo.maTextureTransform * aRot90;
aGradientService = rtl::OUString::createFromAscii("LinearGradient");
break;
case GRADIENT_AXIAL:
{
// standard orientation for VCL linear
// gradient is vertical, thus, rotate 90
// degrees
nRotation += M_PI/2.0;
basegfx::tools::createLinearODFGradientInfo(aGradInfo,
aBounds,
nSteps,
fBorder,
fRotation);
// map odf to svg gradient orientation - x
// instead of y direction
aGradInfo.maTextureTransform = aGradInfo.maTextureTransform * aRot90;
const double nBorder(
::basegfx::pruneScaleValue(
(1.0 - rGradient.GetBorder() / 100.0) ) );
// map odf axial gradient to 3-stop linear
// gradient - shift left by 0.5
basegfx::B2DHomMatrix aShift;
aShift.translate(-0.5,0);
aGradInfo.maTextureTransform = aGradInfo.maTextureTransform * aShift;
// shrink texture, to account for border
// (only in x direction, linear gradient
// is constant in y direction, anyway)
aTextureTransformation.scale( nBorder,
1.0 );
// linear gradients don't respect offsets
// (they are implicitely assumed to be
// 50%). linear gradients don't have
// border on both sides, only on the
// startColor side, axial gradients have
// border on both sides. As both gradients
// are invariant in y direction: leave y
// offset alone.
double nOffsetX( rGradient.GetBorder() / 200.0 );
// determine type of gradient (and necessary
// transformation matrix, should it be emulated by a
// generic gradient)
switch( rGradient.GetStyle() )
{
case GRADIENT_LINEAR:
nOffsetX = rGradient.GetBorder() / 100.0;
aTexture.Gradient = xFactory->createLinearHorizontalGradient( aColors,
aStops );
break;
case GRADIENT_AXIAL:
// vcl considers center color as start color
::std::swap(aColors[0],aColors[1]);
aTexture.Gradient = xFactory->createAxialHorizontalGradient( aColors,
aStops );
break;
default: // other cases can't happen
break;
}
// apply border offset values
aTextureTransformation.translate( nOffsetX,
0.0 );
// rotate texture according to gradient rotation
aTextureTransformation.translate( -0.5, -0.5 );
aTextureTransformation.rotate( nRotation );
// to let the first strip of a rotated
// gradient start at the _edge_ of the
// bound rect (and not, due to rotation,
// slightly inside), slightly enlarge the
// gradient:
//
// y/2 sin(alpha) + x/2 cos(alpha)
//
// (values to change are not actual
// gradient scales, but original bound
// rect dimensions. Since we still want
// the border setting to apply after that,
// we multiply with that as above for
// nScaleX)
const double nScale(
::basegfx::pruneScaleValue(
fabs( aBounds.getHeight()*sin(nRotation) ) +
fabs( aBounds.getWidth()*cos(nRotation) )));
aTextureTransformation.scale( nScale, nScale );
// translate back origin to center of
// primitive
aTextureTransformation.translate( 0.5*aBounds.getWidth(),
0.5*aBounds.getHeight() );
aGradientService = rtl::OUString::createFromAscii("LinearGradient");
break;
}
break;
case GRADIENT_RADIAL:
// FALLTHROUGH intended
basegfx::tools::createRadialODFGradientInfo(aGradInfo,
aBounds,
aOffset,
nSteps,
fBorder);
aGradientService = rtl::OUString::createFromAscii("EllipticalGradient");
break;
case GRADIENT_ELLIPTICAL:
// FALLTHROUGH intended
basegfx::tools::createEllipticalODFGradientInfo(aGradInfo,
aBounds,
aOffset,
nSteps,
fBorder,
fRotation);
aGradientService = rtl::OUString::createFromAscii("EllipticalGradient");
break;
case GRADIENT_SQUARE:
// FALLTHROUGH intended
basegfx::tools::createSquareODFGradientInfo(aGradInfo,
aBounds,
aOffset,
nSteps,
fBorder,
fRotation);
aGradientService = rtl::OUString::createFromAscii("RectangularGradient");
break;
case GRADIENT_RECT:
{
// determine scale factors for the gradient (must
// be scaled up from [0,1]x[0,1] rect to object
// bounds). Will potentially changed in switch
// statement below.
// Respect border value, while doing so, the VCL
// gradient's border will effectively shrink the
// resulting gradient.
double nScaleX( aBounds.getWidth() * (1.0 - rGradient.GetBorder() / 100.0) );
double nScaleY( aBounds.getHeight()* (1.0 - rGradient.GetBorder() / 100.0) );
// determine offset values. Since the border is
// divided half-by-half to both sides of the
// gradient, divide translation offset by an
// additional 2. Also respect offset here, but
// since VCL gradients have their center at [0,0]
// for zero offset, but canvas gradients have
// their top, left edge aligned with the
// primitive, and offset of 50% effectively must
// yield zero shift. Both values will potentially
// be adapted in switch statement below.
double nOffsetX( aBounds.getWidth() *
(2.0 * rGradient.GetOfsX() - 100.0 + rGradient.GetBorder()) / 200.0 );
double nOffsetY( aBounds.getHeight() *
(2.0 * rGradient.GetOfsY() - 100.0 + rGradient.GetBorder()) / 200.0 );
// determine type of gradient (and necessary
// transformation matrix, should it be emulated by a
// generic gradient)
switch( rGradient.GetStyle() )
{
case GRADIENT_RADIAL:
{
// create isotrophic scaling
if( nScaleX > nScaleY )
{
nOffsetY -= (nScaleX - nScaleY) * 0.5;
nScaleY = nScaleX;
}
else
{
nOffsetX -= (nScaleY - nScaleX) * 0.5;
nScaleX = nScaleY;
}
// enlarge gradient to match bound rect diagonal
aTextureTransformation.translate( -0.5, -0.5 );
const double nScale( hypot(aBounds.getWidth(), aBounds.getHeight()) / nScaleX );
aTextureTransformation.scale( nScale, nScale );
aTextureTransformation.translate( 0.5, 0.5 );
aTexture.Gradient = xFactory->createEllipticalGradient( aColors,
aStops,
geometry::RealRectangle2D(0.0,0.0,
1.0,1.0) );
}
break;
case GRADIENT_ELLIPTICAL:
{
// enlarge gradient slightly
aTextureTransformation.translate( -0.5, -0.5 );
const double nSqrt2( sqrt(2.0) );
aTextureTransformation.scale( nSqrt2,nSqrt2 );
aTextureTransformation.translate( 0.5, 0.5 );
aTexture.Gradient = xFactory->createEllipticalGradient(
aColors,
aStops,
::basegfx::unotools::rectangle2DFromB2DRectangle(
aBounds ));
}
break;
case GRADIENT_SQUARE:
// create isotrophic scaling
if( nScaleX > nScaleY )
{
nOffsetY -= (nScaleX - nScaleY) * 0.5;
nScaleY = nScaleX;
}
else
{
nOffsetX -= (nScaleY - nScaleX) * 0.5;
nScaleX = nScaleY;
}
aTexture.Gradient = xFactory->createRectangularGradient( aColors,
aStops,
geometry::RealRectangle2D(0.0,0.0,
1.0,1.0) );
break;
case GRADIENT_RECT:
aTexture.Gradient = xFactory->createRectangularGradient(
aColors,
aStops,
::basegfx::unotools::rectangle2DFromB2DRectangle(
aBounds ) );
break;
default: // other cases can't happen
break;
}
nScaleX = ::basegfx::pruneScaleValue( nScaleX );
nScaleY = ::basegfx::pruneScaleValue( nScaleY );
aTextureTransformation.scale( nScaleX, nScaleY );
// rotate texture according to gradient rotation
aTextureTransformation.translate( -0.5*nScaleX, -0.5*nScaleY );
aTextureTransformation.rotate( nRotation );
aTextureTransformation.translate( 0.5*nScaleX, 0.5*nScaleY );
aTextureTransformation.translate( nOffsetX, nOffsetY );
}
break;
basegfx::tools::createRectangularODFGradientInfo(aGradInfo,
aBounds,
aOffset,
nSteps,
fBorder,
fRotation);
aGradientService = rtl::OUString::createFromAscii("RectangularGradient");
break;
default:
ENSURE_OR_THROW( false,
"ImplRenderer::createGradientAction(): Unexpected gradient type" );
"ImplRenderer::createGradientAction(): Unexpected gradient type" );
break;
}
@@ -877,31 +752,49 @@ namespace cppcanvas
// gradient will always display at the origin, and
// not within the polygon bound (which might be
// miles away from the origin).
aTextureTransformation.translate( aBounds.getMinX(),
aBounds.getMinY() );
aGradInfo.maTextureTransform.translate( aBounds.getMinX(),
aBounds.getMinY() );
::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform,
aTextureTransformation );
aGradInfo.maTextureTransform );
ActionSharedPtr pPolyAction(
internal::PolyPolyActionFactory::createPolyPolyAction(
aDevicePoly,
rParms.mrCanvas,
getState( rParms.mrStates ),
aTexture ) );
uno::Sequence<uno::Any> args(3);
beans::PropertyValue aProp;
aProp.Name = rtl::OUString::createFromAscii("Colors");
aProp.Value <<= aColors;
args[0] <<= aProp;
aProp.Name = rtl::OUString::createFromAscii("Stops");
aProp.Value <<= aStops;
args[1] <<= aProp;
aProp.Name = rtl::OUString::createFromAscii("AspectRatio");
aProp.Value <<= aGradInfo.mfAspectRatio;
args[2] <<= aProp;
if( pPolyAction )
aTexture.Gradient.set(
xFactory->createInstanceWithArguments(aGradientService,
args),
uno::UNO_QUERY);
if( aTexture.Gradient.is() )
{
maActions.push_back(
MtfAction(
pPolyAction,
rParms.mrCurrActionIndex ) );
ActionSharedPtr pPolyAction(
internal::PolyPolyActionFactory::createPolyPolyAction(
aDevicePoly,
rParms.mrCanvas,
getState( rParms.mrStates ),
aTexture ) );
rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
if( pPolyAction )
{
maActions.push_back(
MtfAction(
pPolyAction,
rParms.mrCurrActionIndex ) );
rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
}
// done, using native gradients
return;
}
// done, using native gradients
return;
}
}