LibreOffice Module canvas (master) 1
canvashelper_texturefill.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <sal/config.h>
21
22#include <cstdlib>
23#include <tuple>
24
35#include <com/sun/star/rendering/TexturingMode.hpp>
36#include <rtl/math.hxx>
38#include <tools/poly.hxx>
39#include <vcl/bitmapex.hxx>
40#include <vcl/canvastools.hxx>
41#include <vcl/virdev.hxx>
42#include <vcl/gradient.hxx>
43
45#include <parametricpolypolygon.hxx>
46
47#include "canvashelper.hxx"
48#include "impltools.hxx"
49
50
51using namespace ::com::sun::star;
52
53namespace vclcanvas
54{
55 namespace
56 {
57 bool textureFill( OutputDevice& rOutDev,
58 const GraphicObject& rGraphic,
59 const ::Point& rPosPixel,
60 const ::Size& rNextTileX,
61 const ::Size& rNextTileY,
62 sal_Int32 nTilesX,
63 sal_Int32 nTilesY,
64 const ::Size& rTileSize,
65 const GraphicAttr& rAttr)
66 {
67 bool bRet( false );
68 Point aCurrPos;
69 int nX, nY;
70
71 for( nY=0; nY < nTilesY; ++nY )
72 {
73 aCurrPos.setX( rPosPixel.X() + nY*rNextTileY.Width() );
74 aCurrPos.setY( rPosPixel.Y() + nY*rNextTileY.Height() );
75
76 for( nX=0; nX < nTilesX; ++nX )
77 {
78 // update return value. This method should return true, if
79 // at least one of the looped Draws succeeded.
80 bRet |= rGraphic.Draw(rOutDev,
81 aCurrPos,
82 rTileSize,
83 &rAttr);
84
85 aCurrPos.AdjustX(rNextTileX.Width() );
86 aCurrPos.AdjustY(rNextTileX.Height() );
87 }
88 }
89
90 return bRet;
91 }
92
93
99 void fillLinearGradient( OutputDevice& rOutDev,
100 const ::basegfx::B2DHomMatrix& rTextureTransform,
101 const ::tools::Rectangle& rBounds,
102 unsigned int nStepCount,
103 const ::canvas::ParametricPolyPolygon::Values& rValues,
104 const std::vector< ::Color >& rColors )
105 {
106 // determine general position of gradient in relation to
107 // the bound rect
108 // =====================================================
109
110 ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
111 ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
112 ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
113 ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
114
115 aLeftTop *= rTextureTransform;
116 aLeftBottom *= rTextureTransform;
117 aRightTop *= rTextureTransform;
118 aRightBottom*= rTextureTransform;
119
120 // calc length of bound rect diagonal
121 const ::basegfx::B2DVector aBoundRectDiagonal(
122 vcl::unotools::b2DPointFromPoint( rBounds.TopLeft() ) -
123 vcl::unotools::b2DPointFromPoint( rBounds.BottomRight() ) );
124 const double nDiagonalLength( aBoundRectDiagonal.getLength() );
125
126 // create direction of gradient:
127 // _______
128 // | | |
129 // -> | | | ...
130 // | | |
131 // -------
132 ::basegfx::B2DVector aDirection( aRightTop - aLeftTop );
133 aDirection.normalize();
134
135 // now, we potentially have to enlarge our gradient area
136 // atop and below the transformed [0,1]x[0,1] unit rect,
137 // for the gradient to fill the complete bound rect.
138 ::basegfx::utils::infiniteLineFromParallelogram( aLeftTop,
139 aLeftBottom,
140 aRightTop,
141 aRightBottom,
143
144
145 // render gradient
146 // ===============
147
148 // First try to use directly VCL's DrawGradient(), as that one is generally
149 // a better choice than here decomposing to polygons. The VCL API allows
150 // only 2 colors, but that should generally do.
151 // Do not use nStepCount, it limits optimized implementations, and it's computed
152 // by vclcanvas based on number of colors, so it's practically irrelevant.
153
154 // 2 colors and 2 stops (at 0 and 1) is a linear gradient:
155 if( rColors.size() == 2 && rValues.maStops.size() == 2 && rValues.maStops[0] == 0 && rValues.maStops[1] == 1)
156 {
157 // tdf#144073: Note that the code below adjusts the gradient area this way.
158 // No, I have no idea why.
159 aLeftTop -= 2.0*nDiagonalLength*aDirection;
160 aLeftBottom -= 2.0*nDiagonalLength*aDirection;
161 aRightTop += 2.0*nDiagonalLength*aDirection;
162 aRightBottom += 2.0*nDiagonalLength*aDirection;
163 Gradient vclGradient( css::awt::GradientStyle_LINEAR, rColors[ 0 ], rColors[ 1 ] );
164 ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(5) );
165 aTempPoly[0] = ::Point( ::basegfx::fround( aLeftTop.getX() ),
166 ::basegfx::fround( aLeftTop.getY() ) );
167 aTempPoly[1] = ::Point( ::basegfx::fround( aRightTop.getX() ),
168 ::basegfx::fround( aRightTop.getY() ) );
169 aTempPoly[2] = ::Point( ::basegfx::fround( aRightBottom.getX() ),
170 ::basegfx::fround( aRightBottom.getY() ) );
171 aTempPoly[3] = ::Point( ::basegfx::fround( aLeftBottom.getX() ),
172 ::basegfx::fround( aLeftBottom.getY() ) );
173 aTempPoly[4] = aTempPoly[0];
174 rOutDev.DrawGradient( ::tools::PolyPolygon(aTempPoly), vclGradient );
175 return;
176 }
177 // 3 colors with first and last being equal and 3 stops (at 0, 0.5 and 1) is an axial gradient:
178 if( rColors.size() == 3 && rColors[ 0 ] == rColors[ 2 ]
179 && rValues.maStops.size() == 3 && rValues.maStops[0] == 0
180 && rValues.maStops[1] == 0.5 && rValues.maStops[2] == 1)
181 {
182 // tdf#144073: Note that the code below adjusts the gradient area this way.
183 // No, I have no idea why.
184 aLeftTop -= 2.0*nDiagonalLength*aDirection;
185 aLeftBottom -= 2.0*nDiagonalLength*aDirection;
186 aRightTop += 2.0*nDiagonalLength*aDirection;
187 aRightBottom += 2.0*nDiagonalLength*aDirection;
188 Gradient vclGradient( css::awt::GradientStyle_AXIAL, rColors[ 1 ], rColors[ 0 ] );
189 ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(5) );
190 aTempPoly[0] = ::Point( ::basegfx::fround( aLeftTop.getX() ),
191 ::basegfx::fround( aLeftTop.getY() ) );
192 aTempPoly[1] = ::Point( ::basegfx::fround( aRightTop.getX() ),
193 ::basegfx::fround( aRightTop.getY() ) );
194 aTempPoly[2] = ::Point( ::basegfx::fround( aRightBottom.getX() ),
195 ::basegfx::fround( aRightBottom.getY() ) );
196 aTempPoly[3] = ::Point( ::basegfx::fround( aLeftBottom.getX() ),
197 ::basegfx::fround( aLeftBottom.getY() ) );
198 aTempPoly[4] = aTempPoly[0];
199 rOutDev.DrawGradient( ::tools::PolyPolygon(aTempPoly), vclGradient );
200 return;
201 }
202
203 // for linear gradients, it's easy to render
204 // non-overlapping polygons: just split the gradient into
205 // nStepCount small strips. Prepare the strip now.
206
207 // For performance reasons, we create a temporary VCL
208 // polygon here, keep it all the way and only change the
209 // vertex values in the loop below (as ::Polygon is a
210 // pimpl class, creating one every loop turn would really
211 // stress the mem allocator)
212 ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(5) );
213
214 OSL_ENSURE( nStepCount >= 3,
215 "fillLinearGradient(): stepcount smaller than 3" );
216
217
218 // fill initial strip (extending two times the bound rect's
219 // diagonal to the 'left'
220
221
222 // calculate left edge, by moving left edge of the
223 // gradient rect two times the bound rect's diagonal to
224 // the 'left'. Since we postpone actual rendering into the
225 // loop below, we set the _right_ edge here, which will be
226 // readily copied into the left edge in the loop below
227 const ::basegfx::B2DPoint& rPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection );
228 aTempPoly[1] = ::Point( ::basegfx::fround( rPoint1.getX() ),
229 ::basegfx::fround( rPoint1.getY() ) );
230
231 const ::basegfx::B2DPoint& rPoint2( aLeftBottom - 2.0*nDiagonalLength*aDirection );
232 aTempPoly[2] = ::Point( ::basegfx::fround( rPoint2.getX() ),
233 ::basegfx::fround( rPoint2.getY() ) );
234
235
236 // iteratively render all other strips
237
238
239 // ensure that nStepCount matches color stop parity, to
240 // have a well-defined middle color e.g. for axial
241 // gradients.
242 if( (rColors.size() % 2) != (nStepCount % 2) )
243 ++nStepCount;
244
245 rOutDev.SetLineColor();
246
247 basegfx::utils::KeyStopLerp aLerper(rValues.maStops);
248
249 // only iterate nStepCount-1 steps, as the last strip is
250 // explicitly painted below
251 for( unsigned int i=0; i<nStepCount-1; ++i )
252 {
253 std::ptrdiff_t nIndex;
254 double fAlpha;
255 std::tie(nIndex,fAlpha)=aLerper.lerp(double(i)/nStepCount);
256
257 rOutDev.SetFillColor(
258 Color( static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
259 static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
260 static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
261
262 // copy right edge of polygon to left edge (and also
263 // copy the closing point)
264 aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
265 aTempPoly[3] = aTempPoly[2];
266
267 // calculate new right edge, from interpolating
268 // between start and end line. Note that i is
269 // increased by one, to account for the fact that we
270 // calculate the right border here (whereas the fill
271 // color is governed by the left edge)
272 const ::basegfx::B2DPoint& rPoint3(
273 (nStepCount - i-1)/double(nStepCount)*aLeftTop +
274 (i+1)/double(nStepCount)*aRightTop );
275 aTempPoly[1] = ::Point( ::basegfx::fround( rPoint3.getX() ),
276 ::basegfx::fround( rPoint3.getY() ) );
277
278 const ::basegfx::B2DPoint& rPoint4(
279 (nStepCount - i-1)/double(nStepCount)*aLeftBottom +
280 (i+1)/double(nStepCount)*aRightBottom );
281 aTempPoly[2] = ::Point( ::basegfx::fround( rPoint4.getX() ),
282 ::basegfx::fround( rPoint4.getY() ) );
283
284 rOutDev.DrawPolygon( aTempPoly );
285 }
286
287 // fill final strip (extending two times the bound rect's
288 // diagonal to the 'right'
289
290
291 // copy right edge of polygon to left edge (and also
292 // copy the closing point)
293 aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
294 aTempPoly[3] = aTempPoly[2];
295
296 // calculate new right edge, by moving right edge of the
297 // gradient rect two times the bound rect's diagonal to
298 // the 'right'.
299 const ::basegfx::B2DPoint& rPoint3( aRightTop + 2.0*nDiagonalLength*aDirection );
300 aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround( rPoint3.getX() ),
301 ::basegfx::fround( rPoint3.getY() ) );
302
303 const ::basegfx::B2DPoint& rPoint4( aRightBottom + 2.0*nDiagonalLength*aDirection );
304 aTempPoly[3] = ::Point( ::basegfx::fround( rPoint4.getX() ),
305 ::basegfx::fround( rPoint4.getY() ) );
306
307 rOutDev.SetFillColor( rColors.back() );
308
309 rOutDev.DrawPolygon( aTempPoly );
310 }
311
312 void fillPolygonalGradient( OutputDevice& rOutDev,
313 const ::basegfx::B2DHomMatrix& rTextureTransform,
314 const ::tools::Rectangle& rBounds,
315 unsigned int nStepCount,
316 const ::canvas::ParametricPolyPolygon::Values& rValues,
317 const std::vector< ::Color >& rColors )
318 {
319 const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly );
320
321 ENSURE_OR_THROW( rGradientPoly.count() > 2,
322 "fillPolygonalGradient(): polygon without area given" );
323
324 // For performance reasons, we create a temporary VCL polygon
325 // here, keep it all the way and only change the vertex values
326 // in the loop below (as ::Polygon is a pimpl class, creating
327 // one every loop turn would really stress the mem allocator)
328 ::basegfx::B2DPolygon aOuterPoly( rGradientPoly );
329 ::basegfx::B2DPolygon aInnerPoly;
330
331 // subdivide polygon _before_ rendering, would otherwise have
332 // to be performed on every loop turn.
333 if( aOuterPoly.areControlPointsUsed() )
334 aOuterPoly = ::basegfx::utils::adaptiveSubdivideByAngle(aOuterPoly);
335
336 aInnerPoly = aOuterPoly;
337
338 // only transform outer polygon _after_ copying it into
339 // aInnerPoly, because inner polygon has to be scaled before
340 // the actual texture transformation takes place
341 aOuterPoly.transform( rTextureTransform );
342
343 // determine overall transformation for inner polygon (might
344 // have to be prefixed by anisotrophic scaling)
345 ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix;
346
347
348 // apply scaling (possibly anisotrophic) to inner polygon
349
350
351 // scale inner polygon according to aspect ratio: for
352 // wider-than-tall bounds (nAspectRatio > 1.0), the inner
353 // polygon, representing the gradient focus, must have
354 // non-zero width. Specifically, a bound rect twice as wide as
355 // tall has a focus polygon of half its width.
356 const double nAspectRatio( rValues.mnAspectRatio );
357 if( nAspectRatio > 1.0 )
358 {
359 // width > height case
360 aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio,
361 0.0 );
362 }
363 else if( nAspectRatio < 1.0 )
364 {
365 // width < height case
366 aInnerPolygonTransformMatrix.scale( 0.0,
367 1.0 - nAspectRatio );
368 }
369 else
370 {
371 // isotrophic case
372 aInnerPolygonTransformMatrix.scale( 0.0, 0.0 );
373 }
374
375 // and finally, add texture transform to it.
376 aInnerPolygonTransformMatrix *= rTextureTransform;
377
378 // apply final matrix to polygon
379 aInnerPoly.transform( aInnerPolygonTransformMatrix );
380
381
382 const sal_uInt32 nNumPoints( aOuterPoly.count() );
383 ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(nNumPoints+1) );
384
385 // increase number of steps by one: polygonal gradients have
386 // the outermost polygon rendered in rColor2, and the
387 // innermost in rColor1. The innermost polygon will never
388 // have zero area, thus, we must divide the interval into
389 // nStepCount+1 steps. For example, to create 3 steps:
390
391 // | |
392 // |-------|-------|-------|
393 // | |
394 // 3 2 1 0
395
396 // This yields 4 tick marks, where 0 is never attained (since
397 // zero-area polygons typically don't display perceivable
398 // color).
399 ++nStepCount;
400
401 rOutDev.SetLineColor();
402
403 basegfx::utils::KeyStopLerp aLerper(rValues.maStops);
404
405 // fill background
406 rOutDev.SetFillColor( rColors.front() );
407 rOutDev.DrawRect( rBounds );
408
409 // render polygon
410 // ==============
411
412 for( unsigned int i=1,p; i<nStepCount; ++i )
413 {
414 const double fT( i/double(nStepCount) );
415
416 std::ptrdiff_t nIndex;
417 double fAlpha;
418 std::tie(nIndex,fAlpha)=aLerper.lerp(fT);
419
420 // lerp color
421 rOutDev.SetFillColor(
422 Color( static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
423 static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
424 static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
425
426 // scale and render polygon, by interpolating between
427 // outer and inner polygon.
428
429 for( p=0; p<nNumPoints; ++p )
430 {
431 const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
432 const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
433
434 aTempPoly[static_cast<sal_uInt16>(p)] = ::Point(
435 basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
436 basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
437 }
438
439 // close polygon explicitly
440 aTempPoly[static_cast<sal_uInt16>(p)] = aTempPoly[0];
441
442 // TODO(P1): compare with vcl/source/gdi/outdev4.cxx,
443 // OutputDevice::ImplDrawComplexGradient(), there's a note
444 // that on some VDev's, rendering disjunct poly-polygons
445 // is faster!
446 rOutDev.DrawPolygon( aTempPoly );
447 }
448 }
449
450 void doGradientFill( OutputDevice& rOutDev,
451 const ::canvas::ParametricPolyPolygon::Values& rValues,
452 const std::vector< ::Color >& rColors,
453 const ::basegfx::B2DHomMatrix& rTextureTransform,
454 const ::tools::Rectangle& rBounds,
455 unsigned int nStepCount )
456 {
457 switch( rValues.meType )
458 {
459 case ::canvas::ParametricPolyPolygon::GradientType::Linear:
460 fillLinearGradient( rOutDev,
461 rTextureTransform,
462 rBounds,
463 nStepCount,
464 rValues,
465 rColors );
466 break;
467
468 case ::canvas::ParametricPolyPolygon::GradientType::Elliptical:
469 case ::canvas::ParametricPolyPolygon::GradientType::Rectangular:
470 fillPolygonalGradient( rOutDev,
471 rTextureTransform,
472 rBounds,
473 nStepCount,
474 rValues,
475 rColors );
476 break;
477
478 default:
479 ENSURE_OR_THROW( false,
480 "CanvasHelper::doGradientFill(): Unexpected case" );
481 }
482 }
483
484 int numColorSteps( const ::Color& rColor1, const ::Color& rColor2 )
485 {
486 return std::max(
487 std::abs( rColor1.GetRed() - rColor2.GetRed() ),
488 std::max(
489 std::abs( rColor1.GetGreen() - rColor2.GetGreen() ),
490 std::abs( rColor1.GetBlue() - rColor2.GetBlue() ) ) );
491 }
492
493 bool gradientFill( OutputDevice& rOutDev,
494 OutputDevice* p2ndOutDev,
495 const ::canvas::ParametricPolyPolygon::Values& rValues,
496 const std::vector< ::Color >& rColors,
497 const ::tools::PolyPolygon& rPoly,
498 const rendering::ViewState& viewState,
499 const rendering::RenderState& renderState,
500 const rendering::Texture& texture,
501 int nTransparency )
502 {
503 // TODO(T2): It is maybe necessary to lock here, should
504 // maGradientPoly someday cease to be const. But then, beware of
505 // deadlocks, canvashelper calls this method with locked own
506 // mutex.
507
508 // calc step size
509
510 int nColorSteps = 0;
511 for( size_t i=0; i<rColors.size()-1; ++i )
512 nColorSteps += numColorSteps(rColors[i],rColors[i+1]);
513
514 ::basegfx::B2DHomMatrix aTotalTransform;
515 const int nStepCount=
517 viewState,
518 renderState,
519 texture,
520 nColorSteps);
521
522 rOutDev.SetLineColor();
523
524 // determine maximal bound rect of texture-filled
525 // polygon
526 const ::tools::Rectangle aPolygonDeviceRectOrig(
527 rPoly.GetBoundRect() );
528
529 if( tools::isRectangle( rPoly ) )
530 {
531 // use optimized output path
532
533
534 // this distinction really looks like a
535 // micro-optimization, but in fact greatly speeds up
536 // especially complex gradients. That's because when using
537 // clipping, we can output polygons instead of
538 // poly-polygons, and don't have to output the gradient
539 // twice for XOR
540
542 rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig );
543 doGradientFill( rOutDev,
544 rValues,
545 rColors,
546 aTotalTransform,
547 aPolygonDeviceRectOrig,
548 nStepCount );
549 rOutDev.Pop();
550
551 if( p2ndOutDev && nTransparency < 253 )
552 {
553 // HACK. Normally, CanvasHelper does not care about
554 // actually what mp2ndOutDev is... well, here we do &
555 // assume a 1bpp target - everything beyond 97%
556 // transparency is fully transparent
557 p2ndOutDev->SetFillColor( COL_BLACK );
558 p2ndOutDev->DrawRect( aPolygonDeviceRectOrig );
559 }
560 }
561 else
562 {
563 const vcl::Region aPolyClipRegion( rPoly );
564
566 rOutDev.IntersectClipRegion( aPolyClipRegion );
567
568 doGradientFill( rOutDev,
569 rValues,
570 rColors,
571 aTotalTransform,
572 aPolygonDeviceRectOrig,
573 nStepCount );
574 rOutDev.Pop();
575
576 if( p2ndOutDev && nTransparency < 253 )
577 {
578 // HACK. Normally, CanvasHelper does not care about
579 // actually what mp2ndOutDev is... well, here we do &
580 // assume a 1bpp target - everything beyond 97%
581 // transparency is fully transparent
582 p2ndOutDev->SetFillColor( COL_BLACK );
583 p2ndOutDev->DrawPolyPolygon( rPoly );
584 }
585 }
586
587#ifdef DEBUG_CANVAS_CANVASHELPER_TEXTUREFILL
588 // extra-verbosity
589 {
590 ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
591 ::basegfx::B2DRectangle aTextureDeviceRect;
592 ::basegfx::B2DHomMatrix aTextureTransform;
594 aRect,
595 aTextureTransform );
596 rOutDev.SetLineColor( COL_RED );
597 rOutDev.SetFillColor();
598 rOutDev.DrawRect( vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
599
600 rOutDev.SetLineColor( COL_BLUE );
601 ::tools::Polygon aPoly1(
603 ::basegfx::B2DPolygon aPoly2( aPoly1.getB2DPolygon() );
604 aPoly2.transform( aTextureTransform );
605 ::tools::Polygon aPoly3( aPoly2 );
606 rOutDev.DrawPolygon( aPoly3 );
607 }
608#endif
609
610 return true;
611 }
612 }
613
614 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* pCanvas,
615 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
616 const rendering::ViewState& viewState,
617 const rendering::RenderState& renderState,
618 const uno::Sequence< rendering::Texture >& textures )
619 {
620 ENSURE_ARG_OR_THROW( xPolyPolygon.is(),
621 "CanvasHelper::fillPolyPolygon(): polygon is NULL");
622 ENSURE_ARG_OR_THROW( textures.hasElements(),
623 "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
624
625 if( mpOutDevProvider )
626 {
627 tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider );
628
629 const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) );
631 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon),
632 viewState, renderState ) );
633
634 // TODO(F1): Multi-texturing
635 if( textures[0].Gradient.is() )
636 {
637 // try to cast XParametricPolyPolygon2D reference to
638 // our implementation class.
640 dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );
641
642 if( pGradient && pGradient->getValues().maColors.hasElements() )
643 {
644 // copy state from Gradient polypoly locally
645 // (given object might change!)
646 const ::canvas::ParametricPolyPolygon::Values& rValues(
647 pGradient->getValues() );
648
649 if( rValues.maColors.getLength() < 2 )
650 {
651 rendering::RenderState aTempState=renderState;
652 aTempState.DeviceColor = rValues.maColors[0];
653 fillPolyPolygon(pCanvas, xPolyPolygon, viewState, aTempState);
654 }
655 else
656 {
657 std::vector< ::Color > aColors(rValues.maColors.getLength());
658 std::transform(&rValues.maColors[0],
659 &rValues.maColors[0]+rValues.maColors.getLength(),
660 aColors.begin(),
661 [](const uno::Sequence< double >& aColor) {
662 return vcl::unotools::stdColorSpaceSequenceToColor( aColor );
663 } );
664
665 // TODO(E1): Return value
666 // TODO(F1): FillRule
667 gradientFill( mpOutDevProvider->getOutDev(),
668 mp2ndOutDevProvider ? &mp2ndOutDevProvider->getOutDev() : nullptr,
669 rValues,
670 aColors,
671 aPolyPoly,
672 viewState,
673 renderState,
674 textures[0],
675 nTransparency );
676 }
677 }
678 else
679 {
680 // TODO(F1): The generic case is missing here
681 ENSURE_OR_THROW( false,
682 "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
683 }
684 }
685 else if( textures[0].Bitmap.is() )
686 {
687 geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() );
688
689 ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 &&
690 aBmpSize.Height != 0,
691 "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" );
692
693 // determine maximal bound rect of texture-filled
694 // polygon
695 const ::tools::Rectangle aPolygonDeviceRect(
696 aPolyPoly.GetBoundRect() );
697
698
699 // first of all, determine whether we have a
700 // drawBitmap() in disguise
701 // =========================================
702
703 const bool bRectangularPolygon( tools::isRectangle( aPolyPoly ) );
704
705 ::basegfx::B2DHomMatrix aTotalTransform;
707 viewState,
708 renderState);
709 ::basegfx::B2DHomMatrix aTextureTransform;
710 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
711 textures[0].AffineTransform );
712
713 aTotalTransform *= aTextureTransform;
714
715 const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
716 ::basegfx::B2DRectangle aTextureDeviceRect;
718 aRect,
719 aTotalTransform );
720
721 const ::tools::Rectangle aIntegerTextureDeviceRect(
722 vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
723
724 if( bRectangularPolygon &&
725 aIntegerTextureDeviceRect == aPolygonDeviceRect )
726 {
727 rendering::RenderState aLocalState( renderState );
729 aTextureTransform);
730 ::basegfx::B2DHomMatrix aScaleCorrection;
731 aScaleCorrection.scale( 1.0/aBmpSize.Width,
732 1.0/aBmpSize.Height );
734 aScaleCorrection);
735
736 // need alpha modulation?
737 if( !::rtl::math::approxEqual( textures[0].Alpha,
738 1.0 ) )
739 {
740 // setup alpha modulation values
741 aLocalState.DeviceColor.realloc(4);
742 double* pColor = aLocalState.DeviceColor.getArray();
743 pColor[0] =
744 pColor[1] =
745 pColor[2] = 0.0;
746 pColor[3] = textures[0].Alpha;
747
748 return drawBitmapModulated( pCanvas,
749 textures[0].Bitmap,
750 viewState,
751 aLocalState );
752 }
753 else
754 {
755 return drawBitmap( pCanvas,
756 textures[0].Bitmap,
757 viewState,
758 aLocalState );
759 }
760 }
761 else
762 {
763 // No easy mapping to drawBitmap() - calculate
764 // texturing parameters
765 // ===========================================
766
767 BitmapEx aBmpEx( tools::bitmapExFromXBitmap( textures[0].Bitmap ) );
768
769 // scale down bitmap to [0,1]x[0,1] rect, as required
770 // from the XCanvas interface.
772 ::basegfx::B2DHomMatrix aPureTotalTransform; // pure view*render*texture transform
773 aScaling.scale( 1.0/aBmpSize.Width,
774 1.0/aBmpSize.Height );
775
776 aTotalTransform = aTextureTransform * aScaling;
777 aPureTotalTransform = aTextureTransform;
778
779 // combine with view and render transform
781 ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState);
782
783 // combine all three transformations into one
784 // global texture-to-device-space transformation
785 aTotalTransform *= aMatrix;
786 aPureTotalTransform *= aMatrix;
787
788 // analyze transformation, and setup an
789 // appropriate GraphicObject
791 ::basegfx::B2DPoint aOutputPos;
792 double nRotate;
793 double nShearX;
794 aTotalTransform.decompose( aScale, aOutputPos, nRotate, nShearX );
795
796 GraphicAttr aGrfAttr;
798
799 if( ::basegfx::fTools::equalZero( nShearX ) )
800 {
801 // no shear, GraphicObject is enough (the
802 // GraphicObject only supports scaling, rotation
803 // and translation)
804
805 // #i75339# don't apply mirror flags, having
806 // negative size values is enough to make
807 // GraphicObject flip the bitmap
808
809 // The angle has to be mapped from radian to tenths of
810 // degrees with the orientation reversed: [0,2Pi) ->
811 // (3600,0]. Note that the original angle may have
812 // values outside the [0,2Pi) interval.
813 const double nAngleInTenthOfDegrees (3600.0 - basegfx::rad2deg<10>(nRotate));
814 aGrfAttr.SetRotation( Degree10(::basegfx::fround(nAngleInTenthOfDegrees)) );
815
816 pGrfObj = std::make_shared<GraphicObject>( aBmpEx );
817 }
818 else
819 {
820 // modify output position, to account for the fact
821 // that transformBitmap() always normalizes its output
822 // bitmap into the smallest enclosing box.
823 ::basegfx::B2DRectangle aDestRect;
826 0,
827 aBmpSize.Width,
828 aBmpSize.Height),
829 aMatrix );
830
831 aOutputPos.setX( aDestRect.getMinX() );
832 aOutputPos.setY( aDestRect.getMinY() );
833
834 // complex transformation, use generic affine bitmap
835 // transformation
836 aBmpEx = tools::transformBitmap( aBmpEx,
837 aTotalTransform);
838
839 pGrfObj = std::make_shared<GraphicObject>( aBmpEx );
840
841 // clear scale values, generated bitmap already
842 // contains scaling
843 aScale.setX( 1.0 ); aScale.setY( 1.0 );
844
845 // update bitmap size, bitmap has changed above.
846 aBmpSize = vcl::unotools::integerSize2DFromSize(aBmpEx.GetSizePixel());
847 }
848
849
850 // render texture tiled into polygon
851 // =================================
852
853 // calc device space direction vectors. We employ
854 // the following approach for tiled output: the
855 // texture bitmap is output in texture space
856 // x-major order, i.e. tile neighbors in texture
857 // space x direction are rendered back-to-back in
858 // device coordinate space (after the full device
859 // transformation). Thus, the aNextTile* vectors
860 // denote the output position updates in device
861 // space, to get from one tile to the next.
862 ::basegfx::B2DVector aNextTileX( 1.0, 0.0 );
863 ::basegfx::B2DVector aNextTileY( 0.0, 1.0 );
864 aNextTileX *= aPureTotalTransform;
865 aNextTileY *= aPureTotalTransform;
866
867 ::basegfx::B2DHomMatrix aInverseTextureTransform( aPureTotalTransform );
868
869 ENSURE_ARG_OR_THROW( aInverseTextureTransform.isInvertible(),
870 "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" );
871
872 aInverseTextureTransform.invert();
873
874 // calc bound rect of extended texture area in
875 // device coordinates. Therefore, we first calc
876 // the area of the polygon bound rect in texture
877 // space. To maintain texture phase, this bound
878 // rect is then extended to integer coordinates
879 // (extended, because shrinking might leave some
880 // inner polygon areas unfilled).
881 // Finally, the bound rect is transformed back to
882 // device coordinate space, were we determine the
883 // start point from it.
884 ::basegfx::B2DRectangle aTextureSpacePolygonRect;
885 ::canvas::tools::calcTransformedRectBounds( aTextureSpacePolygonRect,
887 aInverseTextureTransform );
888
889 // calc left, top of extended polygon rect in
890 // texture space, create one-texture instance rect
891 // from it (i.e. rect from start point extending
892 // 1.0 units to the right and 1.0 units to the
893 // bottom). Note that the rounding employed here
894 // is a bit subtle, since we need to round up/down
895 // as _soon_ as any fractional amount is
896 // encountered. This is to ensure that the full
897 // polygon area is filled with texture tiles.
898 const sal_Int32 nX1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinX() ) );
899 const sal_Int32 nY1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinY() ) );
900 const sal_Int32 nX2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxX() ) );
901 const sal_Int32 nY2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxY() ) );
902 const ::basegfx::B2DRectangle aSingleTextureRect(
903 nX1, nY1,
904 nX1 + 1.0,
905 nY1 + 1.0 );
906
907 // and convert back to device space
908 ::basegfx::B2DRectangle aSingleDeviceTextureRect;
909 ::canvas::tools::calcTransformedRectBounds( aSingleDeviceTextureRect,
910 aSingleTextureRect,
911 aPureTotalTransform );
912
913 const ::Point aPtRepeat( vcl::unotools::pointFromB2DPoint(
914 aSingleDeviceTextureRect.getMinimum() ) );
915 const ::Size aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width ),
916 ::basegfx::fround( aScale.getY() * aBmpSize.Height ) );
917 const ::Size aIntegerNextTileX( vcl::unotools::sizeFromB2DSize(aNextTileX) );
918 const ::Size aIntegerNextTileY( vcl::unotools::sizeFromB2DSize(aNextTileY) );
919
920 const ::Point aPt( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
921 ::basegfx::fround( aOutputPos.getX() ) : aPtRepeat.X(),
922 textures[0].RepeatModeY == rendering::TexturingMode::NONE ?
923 ::basegfx::fround( aOutputPos.getY() ) : aPtRepeat.Y() );
924 const sal_Int32 nTilesX( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
925 1 : nX2 - nX1 );
926 const sal_Int32 nTilesY( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
927 1 : nY2 - nY1 );
928
929 OutputDevice& rOutDev( mpOutDevProvider->getOutDev() );
930
931 if( bRectangularPolygon )
932 {
933 // use optimized output path
934
935
936 // this distinction really looks like a
937 // micro-optimization, but in fact greatly speeds up
938 // especially complex fills. That's because when using
939 // clipping, we can output polygons instead of
940 // poly-polygons, and don't have to output the gradient
941 // twice for XOR
942
943 // setup alpha modulation
944 if( !::rtl::math::approxEqual( textures[0].Alpha,
945 1.0 ) )
946 {
947 // TODO(F1): Note that the GraphicManager has
948 // a subtle difference in how it calculates
949 // the resulting alpha value: it's using the
950 // inverse alpha values (i.e. 'transparency'),
951 // and calculates transOrig + transModulate,
952 // instead of transOrig + transModulate -
953 // transOrig*transModulate (which would be
954 // equivalent to the origAlpha*modulateAlpha
955 // the DX canvas performs)
956 aGrfAttr.SetAlpha(
957 static_cast< sal_uInt8 >(
958 ::basegfx::fround( 255.0 * textures[0].Alpha ) ) );
959 }
960
961 rOutDev.IntersectClipRegion( aPolygonDeviceRect );
962 textureFill( rOutDev,
963 *pGrfObj,
964 aPt,
965 aIntegerNextTileX,
966 aIntegerNextTileY,
967 nTilesX,
968 nTilesY,
969 aSz,
970 aGrfAttr );
971
973 {
974 OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() );
975 r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect );
976 textureFill( r2ndOutDev,
977 *pGrfObj,
978 aPt,
979 aIntegerNextTileX,
980 aIntegerNextTileY,
981 nTilesX,
982 nTilesY,
983 aSz,
984 aGrfAttr );
985 }
986 }
987 else
988 {
989 // output texture the hard way: XORing out the
990 // polygon
991 // ===========================================
992
993 if( !::rtl::math::approxEqual( textures[0].Alpha,
994 1.0 ) )
995 {
996 // uh-oh. alpha blending is required,
997 // cannot do direct XOR, but have to
998 // prepare the filled polygon within a
999 // VDev
1001 pVDev->SetOutputSizePixel( aPolygonDeviceRect.GetSize() );
1002
1003 // shift output to origin of VDev
1004 const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() );
1005 aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(),
1006 -aPolygonDeviceRect.Top() ) );
1007
1008 const vcl::Region aPolyClipRegion( aPolyPoly );
1009
1010 pVDev->SetClipRegion( aPolyClipRegion );
1011 textureFill( *pVDev,
1012 *pGrfObj,
1013 aOutPos,
1014 aIntegerNextTileX,
1015 aIntegerNextTileY,
1016 nTilesX,
1017 nTilesY,
1018 aSz,
1019 aGrfAttr );
1020
1021 // output VDev content alpha-blended to
1022 // target position.
1023 const ::Point aEmptyPoint;
1024 BitmapEx aContentBmp(
1025 pVDev->GetBitmapEx( aEmptyPoint,
1026 pVDev->GetOutputSizePixel() ) );
1027
1028 sal_uInt8 nCol( static_cast< sal_uInt8 >(
1029 ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
1030 AlphaMask aAlpha( pVDev->GetOutputSizePixel(),
1031 &nCol );
1032
1033 BitmapEx aOutputBmpEx( aContentBmp.GetBitmap(), aAlpha );
1034 rOutDev.DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1035 aOutputBmpEx );
1036
1038 mp2ndOutDevProvider->getOutDev().DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1039 aOutputBmpEx );
1040 }
1041 else
1042 {
1043 const vcl::Region aPolyClipRegion( aPolyPoly );
1044
1046 rOutDev.IntersectClipRegion( aPolyClipRegion );
1047
1048 textureFill( rOutDev,
1049 *pGrfObj,
1050 aPt,
1051 aIntegerNextTileX,
1052 aIntegerNextTileY,
1053 nTilesX,
1054 nTilesY,
1055 aSz,
1056 aGrfAttr );
1057 rOutDev.Pop();
1058
1060 {
1061 OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() );
1062 r2ndOutDev.Push( vcl::PushFlags::CLIPREGION );
1063
1064 r2ndOutDev.IntersectClipRegion( aPolyClipRegion );
1065 textureFill( r2ndOutDev,
1066 *pGrfObj,
1067 aPt,
1068 aIntegerNextTileX,
1069 aIntegerNextTileY,
1070 nTilesX,
1071 nTilesY,
1072 aSz,
1073 aGrfAttr );
1074 r2ndOutDev.Pop();
1075 }
1076 }
1077 }
1078 }
1079 }
1080 }
1081
1082 // TODO(P1): Provide caching here.
1083 return uno::Reference< rendering::XCachedPrimitive >(nullptr);
1084 }
1085
1086}
1087
1088/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void SetRotation(Degree10 nRotate10)
void SetAlpha(sal_uInt8 cAlpha)
bool Draw(OutputDevice &rOut, const Point &rPt, const Size &rSz, const GraphicAttr *pAttr=nullptr) const
void DrawBitmapEx(const Point &rDestPt, const BitmapEx &rBitmapEx)
void DrawRect(const tools::Rectangle &rRect)
void SetLineColor()
void DrawPolygon(const tools::Polygon &rPoly)
void SetFillColor()
void Push(vcl::PushFlags nFlags=vcl::PushFlags::ALL)
void DrawGradient(const tools::Rectangle &rRect, const Gradient &rGradient)
void DrawPolyPolygon(const tools::PolyPolygon &rPolyPoly)
void IntersectClipRegion(const tools::Rectangle &rRect)
void setX(tools::Long nX)
void setY(tools::Long nY)
tools::Long AdjustY(tools::Long nVertMove)
tools::Long AdjustX(tools::Long nHorzMove)
bool decompose(B2DTuple &rScale, B2DTuple &rTranslate, double &rRotate, double &rShearX) const
void scale(double fX, double fY)
basegfx::B2DPoint const & getB2DPoint(sal_uInt32 nIndex) const
void transform(const basegfx::B2DHomMatrix &rMatrix)
B2DPoint getMinimum() const
TYPE getMaxX() const
TYPE getMinX() const
TYPE getMinY() const
TYPE getMaxY() const
TYPE getX() const
void setY(TYPE fY)
TYPE getY() const
void setX(TYPE fX)
Values getValues() const
Query all defining values of this object atomically.
OutDevProviderSharedPtr mpProtectedOutDevProvider
Rendering to this outdev preserves its state.
int setupOutDevState(const css::rendering::ViewState &viewState, const css::rendering::RenderState &renderState, ColorType eColorType) const
css::uno::Reference< css::rendering::XCachedPrimitive > fillTexturedPolyPolygon(const css::rendering::XCanvas *rCanvas, const css::uno::Reference< css::rendering::XPolyPolygon2D > &xPolyPolygon, const css::rendering::ViewState &viewState, const css::rendering::RenderState &renderState, const css::uno::Sequence< css::rendering::Texture > &textures)
css::uno::Reference< css::rendering::XCachedPrimitive > drawBitmap(const css::rendering::XCanvas *rCanvas, const css::uno::Reference< css::rendering::XBitmap > &xBitmap, const css::rendering::ViewState &viewState, const css::rendering::RenderState &renderState)
css::uno::Reference< css::rendering::XCachedPrimitive > drawBitmapModulated(const css::rendering::XCanvas *rCanvas, const css::uno::Reference< css::rendering::XBitmap > &xBitmap, const css::rendering::ViewState &viewState, const css::rendering::RenderState &renderState)
OutDevProviderSharedPtr mpOutDevProvider
Rendering to this outdev does not preserve its state.
OutDevProviderSharedPtr mp2ndOutDevProvider
Rendering to this outdev does not preserve its state.
css::uno::Reference< css::rendering::XCachedPrimitive > fillPolyPolygon(const css::rendering::XCanvas *rCanvas, const css::uno::Reference< css::rendering::XPolyPolygon2D > &xPolyPolygon, const css::rendering::ViewState &viewState, const css::rendering::RenderState &renderState)
#define ENSURE_OR_THROW(c, m)
#define ENSURE_ARG_OR_THROW(c, m)
#define max(a, b)
Definition: dx_winstuff.hxx:43
sal_Int32 nIndex
void * p
ValueType lerp(const ValueType &rFrom, const ValueType &rTo, double t)
B2IRange fround(const B2DRange &rRange)
::basegfx::B2DHomMatrix & mergeViewAndRenderTransform(::basegfx::B2DHomMatrix &combinedTransform, const rendering::ViewState &viewState, const rendering::RenderState &renderState)
rendering::RenderState & appendToRenderState(rendering::RenderState &renderState, const ::basegfx::B2DHomMatrix &rTransform)
::basegfx::B2DRange & calcTransformedRectBounds(::basegfx::B2DRange &outRect, const ::basegfx::B2DRange &inRect, const ::basegfx::B2DHomMatrix &transformation)
Calc the bounding rectangle of a transformed rectangle.
int calcGradientStepCount(::basegfx::B2DHomMatrix &rTotalTransform, const rendering::ViewState &viewState, const rendering::RenderState &renderState, const rendering::Texture &texture, int nColorSteps)
int i
basegfx::B2DRange b2DRectangleFromRectangle(const ::tools::Rectangle &rRect)
basegfx::B2DPoint b2DPointFromPoint(const ::Point &rPoint)
geometry::IntegerSize2D integerSize2DFromSize(const Size &rSize)
::Point pointFromB2DPoint(const basegfx::B2DPoint &rPoint)
::tools::Rectangle rectangleFromB2DRectangle(const basegfx::B2DRange &rRect)
::Size sizeFromB2DSize(const basegfx::B2DVector &rVec)
::tools::PolyPolygon mapPolyPolygon(const ::basegfx::B2DPolyPolygon &rPoly, const rendering::ViewState &rViewState, const rendering::RenderState &rRenderState)
Definition: impltools.cxx:199
::BitmapEx transformBitmap(const BitmapEx &rBitmap, const ::basegfx::B2DHomMatrix &rTransform)
Definition: impltools.cxx:215
bool isRectangle(const ::tools::PolyPolygon &rPolyPoly)
Predicate, to determine whether polygon is actually an axis-aligned rectangle.
Definition: impltools.cxx:165
::BitmapEx bitmapExFromXBitmap(const uno::Reference< rendering::XBitmap > &xBitmap)
Definition: impltools.cxx:49
std::shared_ptr< GraphicObject > GraphicObjectSharedPtr
const css::uno::Sequence< css::uno::Sequence< double > > maColors
Gradient colors.
css::drawing::Direction3D aDirection
unsigned char sal_uInt8