| /************************************************************** |
| * |
| * 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); |
| } |
| |
| } |