/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 .
*/
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);
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 ::tools::Rectangle& rBounds, unsignedint nStepCount, const ::canvas::ParametricPolyPolygon::Values& rValues, const std::vector< ::Color >& rColors )
{ // determine general position of gradient in relation to // the bound rect // =====================================================
// 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::utils::infiniteLineFromParallelogram( aLeftTop,
aLeftBottom,
aRightTop,
aRightBottom,
vcl::unotools::b2DRectangleFromRectangle(rBounds) );
// render gradient // ===============
// First try to use directly VCL's DrawGradient(), as that one is generally // a better choice than here decomposing to polygons. The VCL API allows // only 2 colors, but that should generally do. // Do not use nStepCount, it limits optimized implementations, and it's computed // by vclcanvas based on number of colors, so it's practically irrelevant.
// 2 colors and 2 stops (at 0 and 1) is a linear gradient: if( rColors.size() == 2 && rValues.maStops.size() == 2 && rValues.maStops[0] == 0 && rValues.maStops[1] == 1)
{ // tdf#144073 and tdf#147645: use bounds and angle for gradient // Passing an expanded, rotated polygon noticeably modifies the // drawing of the gradient in a slideshow due to moving of the // starting and ending colors far off the edges of the drawing // surface. So try another way and set the angle of the // gradient and draw only the unadjusted bounds.
Gradient vclGradient( css::awt::GradientStyle_LINEAR, rColors[ 0 ], rColors[ 1 ] ); double fRotate = atan2( aDirection.getY(), aDirection.getX() ); constdouble nAngleInTenthOfDegrees = 3600.0 - basegfx::rad2deg<10>( fRotate ) + 900.0;
vclGradient.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees ) ) );
rOutDev.DrawGradient( rBounds, vclGradient ); return;
} // 3 colors with first and last being equal and 3 stops (at 0, 0.5 and 1) is an axial gradient: if( rColors.size() == 3 && rColors[ 0 ] == rColors[ 2 ]
&& rValues.maStops.size() == 3 && rValues.maStops[0] == 0
&& rValues.maStops[1] == 0.5 && rValues.maStops[2] == 1)
{ // tdf#144073 and tdf#147645: use bounds and angle for gradient // Passing an expanded, rotated polygon noticeably modifies the // drawing of the gradient in a slideshow due to moving of the // starting and ending colors far off the edges of the drawing // surface. So try another way and set the angle of the // gradient and draw only the unadjusted bounds.
Gradient vclGradient( css::awt::GradientStyle_AXIAL, rColors[ 1 ], rColors[ 0 ] ); double fRotate = atan2( aDirection.getY(), aDirection.getX() ); constdouble nAngleInTenthOfDegrees = 3600.0 - basegfx::rad2deg<10>( fRotate ) + 900.0;
vclGradient.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees ) ) );
rOutDev.DrawGradient( rBounds, vclGradient ); return;
}
// 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)
::tools::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 aPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection );
aTempPoly[1] = ::Point( ::basegfx::fround<::tools::Long>( aPoint1.getX() ),
::basegfx::fround<::tools::Long>( aPoint1.getY() ) );
// 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;
// copy right edge 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 aPoint3(
(nStepCount - i-1)/double(nStepCount)*aLeftTop +
(i+1)/double(nStepCount)*aRightTop );
aTempPoly[1] = ::Point( ::basegfx::fround<::tools::Long>( aPoint3.getX() ),
::basegfx::fround<::tools::Long>( aPoint3.getY() ) );
// fill final strip (extending two times the bound rect's // diagonal to the 'right'
// copy right edge 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 aPoint3( aRightTop + 2.0*nDiagonalLength*aDirection );
aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround<::tools::Long>( aPoint3.getX() ),
::basegfx::fround<::tools::Long>( aPoint3.getY() ) );
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::utils::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 its width. constdouble nAspectRatio( rValues.mnAspectRatio ); if( nAspectRatio > 1.0 )
{ // width > height case
aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio,
0.0 );
} elseif( 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 );
// 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:
// close polygon explicitly
aTempPoly[static_cast<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 );
}
}
// this distinction really looks like a // micro-optimization, 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
if( p2ndOutDev && nTransparency < 253 )
{ // HACK. Normally, CanvasHelper does not care about // actually what mp2ndOutDev is... well, here we do & // assume a 1bpp target - everything beyond 97% // transparency is fully transparent
p2ndOutDev->SetFillColor( COL_BLACK );
p2ndOutDev->DrawPolyPolygon( rPoly );
}
}
// 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 );
// 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;
if( ::basegfx::fTools::equalZero( nShearX ) )
{ // no shear, GraphicObject is enough (the // GraphicObject only supports scaling, rotation // and translation)
// #i75339# don't apply mirror flags, having // negative size values is enough to make // GraphicObject flip the bitmap
// The angle has to be mapped from radian to tenths of // degrees with the orientation reversed: [0,2Pi) -> // (3600,0]. Note that the original angle may have // values outside the [0,2Pi) interval. constdouble nAngleInTenthOfDegrees (3600.0 - basegfx::rad2deg<10>(nRotate));
aGrfAttr.SetRotation( Degree10(::basegfx::fround(nAngleInTenthOfDegrees)) );
pGrfObj = std::make_shared<GraphicObject>( aBmpEx );
} else
{ // modify output position, to account for the fact // that transformBitmap() always normalizes its output // bitmap into the smallest enclosing box.
::basegfx::B2DRectangle aDestRect = ::canvas::tools::calcTransformedRectBounds(
::basegfx::B2DRectangle(0,
0,
aBmpSize.Width,
aBmpSize.Height),
aMatrix );
// render texture tiled into polygon // =================================
// calc device space direction vectors. We employ // the following 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;
// 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(
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(
aSingleTextureRect,
aPureTotalTransform );
if( bRectangularPolygon )
{ // use optimized output path
// this distinction really looks like a // micro-optimization, 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.SetAlpha( static_cast< sal_uInt8 >(
::basegfx::fround( 255.0 * textures[0].Alpha ) ) );
}
if( mp2ndOutDevProvider )
{
OutputDevice& r2ndOutDev( mp2ndOutDevProvider->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
ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
pVDev->SetOutputSizePixel( aPolygonDeviceRect.GetSize() );
// shift output to origin of VDev const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() );
aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(),
-aPolygonDeviceRect.Top() ) );
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.