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 <memory>
21 #include <cassert>
22
23 #include <tools/poly.hxx>
24 #include <vcl/gdimtf.hxx>
25 #include <vcl/gradient.hxx>
26 #include <vcl/metaact.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/outdev.hxx>
29 #include <vcl/virdev.hxx>
30 #include <vcl/window.hxx>
31
32 #include <salgdi.hxx>
33
34 #define GRADIENT_DEFAULT_STEPCOUNT 0
35
DrawGradient(const tools::Rectangle & rRect,const Gradient & rGradient)36 void OutputDevice::DrawGradient( const tools::Rectangle& rRect,
37 const Gradient& rGradient )
38 {
39 assert(!is_double_buffered_window());
40
41 // Convert rectangle to a tools::PolyPolygon by first converting to a Polygon
42 tools::Polygon aPolygon ( rRect );
43 tools::PolyPolygon aPolyPoly ( aPolygon );
44
45 DrawGradient ( aPolyPoly, rGradient );
46 }
47
DrawGradient(const tools::PolyPolygon & rPolyPoly,const Gradient & rGradient)48 void OutputDevice::DrawGradient( const tools::PolyPolygon& rPolyPoly,
49 const Gradient& rGradient )
50 {
51 assert(!is_double_buffered_window());
52
53 if (mbInitClipRegion)
54 InitClipRegion();
55 // don't return on mbOutputClipped here, as we may need to draw the clipped metafile, even if the output is clipped
56
57 if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() )
58 {
59 if ( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) )
60 {
61 Color aColor = GetSingleColorGradientFill();
62
63 Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
64 SetLineColor( aColor );
65 SetFillColor( aColor );
66 DrawPolyPolygon( rPolyPoly );
67 Pop();
68 return;
69 }
70
71 Gradient aGradient( rGradient );
72
73 if ( mnDrawMode & DrawModeFlags::GrayGradient )
74 {
75 SetGrayscaleColors( aGradient );
76 }
77
78 DrawGradientToMetafile( rPolyPoly, rGradient );
79
80 if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
81 return;
82
83 // Clip and then draw the gradient
84 if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() )
85 {
86 const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
87
88 // convert rectangle to pixels
89 tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) );
90 aRect.Justify();
91
92 // do nothing if the rectangle is empty
93 if ( !aRect.IsEmpty() )
94 {
95 tools::PolyPolygon aClixPolyPoly( ImplLogicToDevicePixel( rPolyPoly ) );
96 bool bDrawn = false;
97
98 if( !mpGraphics && !AcquireGraphics() )
99 return;
100
101 // secure clip region
102 Push( PushFlags::CLIPREGION );
103 IntersectClipRegion( aBoundRect );
104
105 if (mbInitClipRegion)
106 InitClipRegion();
107
108 // try to draw gradient natively
109 bDrawn = mpGraphics->DrawGradient( aClixPolyPoly, aGradient );
110
111 if (!bDrawn && !mbOutputClipped)
112 {
113 // draw gradients without border
114 if( mbLineColor || mbInitLineColor )
115 {
116 mpGraphics->SetLineColor();
117 mbInitLineColor = true;
118 }
119
120 mbInitFillColor = true;
121
122 // calculate step count if necessary
123 if ( !aGradient.GetSteps() )
124 aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
125
126 if ( rPolyPoly.IsRect() )
127 {
128 // because we draw with no border line, we have to expand gradient
129 // rect to avoid missing lines on the right and bottom edge
130 aRect.AdjustLeft( -1 );
131 aRect.AdjustTop( -1 );
132 aRect.AdjustRight( 1 );
133 aRect.AdjustBottom( 1 );
134 }
135
136 // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the
137 // polypolygon, so pass in a NULL for the clipping parameter
138 if( aGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
139 DrawLinearGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
140 else
141 DrawComplexGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
142 }
143
144 Pop();
145 }
146 }
147 }
148
149 if( mpAlphaVDev )
150 mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
151 }
152
ClipAndDrawGradientMetafile(const Gradient & rGradient,const tools::PolyPolygon & rPolyPoly)153 void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
154 {
155 const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
156 const bool bOldOutput = IsOutputEnabled();
157
158 EnableOutput( false );
159 Push( PushFlags::RASTEROP );
160 SetRasterOp( RasterOp::Xor );
161 DrawGradient( aBoundRect, rGradient );
162 SetFillColor( COL_BLACK );
163 SetRasterOp( RasterOp::N0 );
164 DrawPolyPolygon( rPolyPoly );
165 SetRasterOp( RasterOp::Xor );
166 DrawGradient( aBoundRect, rGradient );
167 Pop();
168 EnableOutput( bOldOutput );
169 }
170
DrawGradientToMetafile(const tools::PolyPolygon & rPolyPoly,const Gradient & rGradient)171 void OutputDevice::DrawGradientToMetafile ( const tools::PolyPolygon& rPolyPoly,
172 const Gradient& rGradient )
173 {
174 assert(!is_double_buffered_window());
175
176 if ( !mpMetaFile )
177 return;
178
179 if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() )
180 {
181 Gradient aGradient( rGradient );
182
183 if ( mnDrawMode & DrawModeFlags::GrayGradient )
184 {
185 SetGrayscaleColors( aGradient );
186 }
187
188 const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
189
190 if ( rPolyPoly.IsRect() )
191 {
192 mpMetaFile->AddAction( new MetaGradientAction( aBoundRect, aGradient ) );
193 }
194 else
195 {
196 mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN" ) );
197 mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) );
198
199 ClipAndDrawGradientMetafile ( rGradient, rPolyPoly );
200
201 mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END" ) );
202 }
203
204 if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
205 return;
206
207 // Clip and then draw the gradient
208 if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() )
209 {
210 // convert rectangle to pixels
211 tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) );
212 aRect.Justify();
213
214 // do nothing if the rectangle is empty
215 if ( !aRect.IsEmpty() )
216 {
217 if( !mbOutputClipped )
218 {
219 // calculate step count if necessary
220 if ( !aGradient.GetSteps() )
221 aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
222
223 if ( rPolyPoly.IsRect() )
224 {
225 // because we draw with no border line, we have to expand gradient
226 // rect to avoid missing lines on the right and bottom edge
227 aRect.AdjustLeft( -1 );
228 aRect.AdjustTop( -1 );
229 aRect.AdjustRight( 1 );
230 aRect.AdjustBottom( 1 );
231 }
232
233 // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the
234 // polypolygon, so pass in a NULL for the clipping parameter
235 if( aGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
236 DrawLinearGradientToMetafile( aRect, aGradient );
237 else
238 DrawComplexGradientToMetafile( aRect, aGradient );
239 }
240 }
241 }
242 }
243 }
244
245 namespace
246 {
GetGradientColorValue(long nValue)247 sal_uInt8 GetGradientColorValue( long nValue )
248 {
249 if ( nValue < 0 )
250 return 0;
251 else if ( nValue > 0xFF )
252 return 0xFF;
253 else
254 return static_cast<sal_uInt8>(nValue);
255 }
256 }
257
DrawLinearGradient(const tools::Rectangle & rRect,const Gradient & rGradient,const tools::PolyPolygon * pClixPolyPoly)258 void OutputDevice::DrawLinearGradient( const tools::Rectangle& rRect,
259 const Gradient& rGradient,
260 const tools::PolyPolygon* pClixPolyPoly )
261 {
262 assert(!is_double_buffered_window());
263
264 // get BoundRect of rotated rectangle
265 tools::Rectangle aRect;
266 Point aCenter;
267 sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
268
269 rGradient.GetBoundRect( rRect, aRect, aCenter );
270
271 bool bLinear = (rGradient.GetStyle() == GradientStyle::Linear);
272 double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0;
273 if ( !bLinear )
274 {
275 fBorder /= 2.0;
276 }
277 tools::Rectangle aMirrorRect = aRect; // used in style axial
278 aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
279 if ( !bLinear )
280 {
281 aRect.SetBottom( aMirrorRect.Top() );
282 }
283
284 // colour-intensities of start- and finish; change if needed
285 long nFactor;
286 Color aStartCol = rGradient.GetStartColor();
287 Color aEndCol = rGradient.GetEndColor();
288 long nStartRed = aStartCol.GetRed();
289 long nStartGreen = aStartCol.GetGreen();
290 long nStartBlue = aStartCol.GetBlue();
291 long nEndRed = aEndCol.GetRed();
292 long nEndGreen = aEndCol.GetGreen();
293 long nEndBlue = aEndCol.GetBlue();
294 nFactor = rGradient.GetStartIntensity();
295 nStartRed = (nStartRed * nFactor) / 100;
296 nStartGreen = (nStartGreen * nFactor) / 100;
297 nStartBlue = (nStartBlue * nFactor) / 100;
298 nFactor = rGradient.GetEndIntensity();
299 nEndRed = (nEndRed * nFactor) / 100;
300 nEndGreen = (nEndGreen * nFactor) / 100;
301 nEndBlue = (nEndBlue * nFactor) / 100;
302
303 // gradient style axial has exchanged start and end colors
304 if ( !bLinear)
305 {
306 long nTempColor = nStartRed;
307 nStartRed = nEndRed;
308 nEndRed = nTempColor;
309 nTempColor = nStartGreen;
310 nStartGreen = nEndGreen;
311 nEndGreen = nTempColor;
312 nTempColor = nStartBlue;
313 nStartBlue = nEndBlue;
314 nEndBlue = nTempColor;
315 }
316
317 sal_uInt8 nRed;
318 sal_uInt8 nGreen;
319 sal_uInt8 nBlue;
320
321 // Create border
322 tools::Rectangle aBorderRect = aRect;
323 tools::Polygon aPoly( 4 );
324 if (fBorder > 0.0)
325 {
326 nRed = static_cast<sal_uInt8>(nStartRed);
327 nGreen = static_cast<sal_uInt8>(nStartGreen);
328 nBlue = static_cast<sal_uInt8>(nStartBlue);
329
330 mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
331
332 aBorderRect.SetBottom( static_cast<long>( aBorderRect.Top() + fBorder ) );
333 aRect.SetTop( aBorderRect.Bottom() );
334 aPoly[0] = aBorderRect.TopLeft();
335 aPoly[1] = aBorderRect.TopRight();
336 aPoly[2] = aBorderRect.BottomRight();
337 aPoly[3] = aBorderRect.BottomLeft();
338 aPoly.Rotate( aCenter, nAngle );
339
340 ImplDrawPolygon( aPoly, pClixPolyPoly );
341
342 if ( !bLinear)
343 {
344 aBorderRect = aMirrorRect;
345 aBorderRect.SetTop( static_cast<long>( aBorderRect.Bottom() - fBorder ) );
346 aMirrorRect.SetBottom( aBorderRect.Top() );
347 aPoly[0] = aBorderRect.TopLeft();
348 aPoly[1] = aBorderRect.TopRight();
349 aPoly[2] = aBorderRect.BottomRight();
350 aPoly[3] = aBorderRect.BottomLeft();
351 aPoly.Rotate( aCenter, nAngle );
352
353 ImplDrawPolygon( aPoly, pClixPolyPoly );
354 }
355 }
356
357 // calculate step count
358 long nStepCount = GetGradientSteps( rGradient, aRect, false/*bMtf*/ );
359
360 // minimal three steps and maximal as max color steps
361 long nAbsRedSteps = std::abs( nEndRed - nStartRed );
362 long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
363 long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
364 long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
365 nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
366 long nSteps = std::min( nStepCount, nMaxColorSteps );
367 if ( nSteps < 3)
368 {
369 nSteps = 3;
370 }
371
372 double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps);
373 double fGradientLine = static_cast<double>(aRect.Top());
374 double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom());
375
376 const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0;
377 if ( !bLinear)
378 {
379 nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap
380 }
381 for ( long i = 0; i < nSteps; i++ )
382 {
383 // linear interpolation of color
384 const double fAlpha = static_cast<double>(i) / fStepsMinus1;
385 double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha;
386 nRed = GetGradientColorValue(static_cast<long>(fTempColor));
387 fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
388 nGreen = GetGradientColorValue(static_cast<long>(fTempColor));
389 fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
390 nBlue = GetGradientColorValue(static_cast<long>(fTempColor));
391
392 mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
393
394 // Polygon for this color step
395 aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
396 aRect.SetBottom( static_cast<long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) );
397 aPoly[0] = aRect.TopLeft();
398 aPoly[1] = aRect.TopRight();
399 aPoly[2] = aRect.BottomRight();
400 aPoly[3] = aRect.BottomLeft();
401 aPoly.Rotate( aCenter, nAngle );
402
403 ImplDrawPolygon( aPoly, pClixPolyPoly );
404
405 if ( !bLinear )
406 {
407 aMirrorRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
408 aMirrorRect.SetTop( static_cast<long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) );
409 aPoly[0] = aMirrorRect.TopLeft();
410 aPoly[1] = aMirrorRect.TopRight();
411 aPoly[2] = aMirrorRect.BottomRight();
412 aPoly[3] = aMirrorRect.BottomLeft();
413 aPoly.Rotate( aCenter, nAngle );
414
415 ImplDrawPolygon( aPoly, pClixPolyPoly );
416 }
417 }
418 if ( bLinear)
419 return;
420
421 // draw middle polygon with end color
422 nRed = GetGradientColorValue(nEndRed);
423 nGreen = GetGradientColorValue(nEndGreen);
424 nBlue = GetGradientColorValue(nEndBlue);
425
426 mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
427
428 aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
429 aRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) );
430 aPoly[0] = aRect.TopLeft();
431 aPoly[1] = aRect.TopRight();
432 aPoly[2] = aRect.BottomRight();
433 aPoly[3] = aRect.BottomLeft();
434 aPoly.Rotate( aCenter, nAngle );
435
436 ImplDrawPolygon( aPoly, pClixPolyPoly );
437
438 }
439
is_double_buffered_window() const440 bool OutputDevice::is_double_buffered_window() const
441 {
442 const vcl::Window *pWindow = dynamic_cast<const vcl::Window*>(this);
443 return pWindow && pWindow->SupportsDoubleBuffering();
444 }
445
DrawComplexGradient(const tools::Rectangle & rRect,const Gradient & rGradient,const tools::PolyPolygon * pClixPolyPoly)446 void OutputDevice::DrawComplexGradient( const tools::Rectangle& rRect,
447 const Gradient& rGradient,
448 const tools::PolyPolygon* pClixPolyPoly )
449 {
450 assert(!is_double_buffered_window());
451
452 // Determine if we output via Polygon or PolyPolygon
453 // For all rasteroperations other than Overpaint always use PolyPolygon,
454 // as we will get wrong results if we output multiple times on top of each other.
455 // Also for printers always use PolyPolygon, as not all printers
456 // can print polygons on top of each other.
457
458 std::unique_ptr<tools::PolyPolygon> xPolyPoly;
459 tools::Rectangle aRect;
460 Point aCenter;
461 Color aStartCol( rGradient.GetStartColor() );
462 Color aEndCol( rGradient.GetEndColor() );
463 long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
464 long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
465 long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
466 long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
467 long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
468 long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
469 long nRedSteps = nEndRed - nStartRed;
470 long nGreenSteps = nEndGreen - nStartGreen;
471 long nBlueSteps = nEndBlue - nStartBlue;
472 sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
473
474 rGradient.GetBoundRect( rRect, aRect, aCenter );
475
476 if ( UsePolyPolygonForComplexGradient() )
477 xPolyPoly.reset(new tools::PolyPolygon( 2 ));
478
479 long nStepCount = GetGradientSteps( rGradient, rRect, false/*bMtf*/, true/*bComplex*/ );
480
481 // at least three steps and at most the number of colour differences
482 long nSteps = std::max( nStepCount, 2L );
483 long nCalcSteps = std::abs( nRedSteps );
484 long nTempSteps = std::abs( nGreenSteps );
485 if ( nTempSteps > nCalcSteps )
486 nCalcSteps = nTempSteps;
487 nTempSteps = std::abs( nBlueSteps );
488 if ( nTempSteps > nCalcSteps )
489 nCalcSteps = nTempSteps;
490 if ( nCalcSteps < nSteps )
491 nSteps = nCalcSteps;
492 if ( !nSteps )
493 nSteps = 1;
494
495 // determine output limits and stepsizes for all directions
496 tools::Polygon aPoly;
497 double fScanLeft = aRect.Left();
498 double fScanTop = aRect.Top();
499 double fScanRight = aRect.Right();
500 double fScanBottom = aRect.Bottom();
501 double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
502 double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
503
504 // all gradients are rendered as nested rectangles which shrink
505 // equally in each dimension - except for 'square' gradients
506 // which shrink to a central vertex but are not per-se square.
507 if( rGradient.GetStyle() != GradientStyle::Square )
508 {
509 fScanIncY = std::min( fScanIncY, fScanIncX );
510 fScanIncX = fScanIncY;
511 }
512 sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
513 bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
514
515 mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
516
517 if( xPolyPoly )
518 {
519 aPoly = rRect;
520 xPolyPoly->Insert( aPoly );
521 xPolyPoly->Insert( aPoly );
522 }
523 else
524 {
525 // extend rect, to avoid missing bounding line
526 tools::Rectangle aExtRect( rRect );
527
528 aExtRect.AdjustLeft( -1 );
529 aExtRect.AdjustTop( -1 );
530 aExtRect.AdjustRight(1 );
531 aExtRect.AdjustBottom(1 );
532
533 aPoly = aExtRect;
534 ImplDrawPolygon( aPoly, pClixPolyPoly );
535 }
536
537 // loop to output Polygon/PolyPolygon sequentially
538 for( long i = 1; i < nSteps; i++ )
539 {
540 // calculate new Polygon
541 fScanLeft += fScanIncX;
542 aRect.SetLeft( static_cast<long>( fScanLeft ) );
543 fScanTop += fScanIncY;
544 aRect.SetTop( static_cast<long>( fScanTop ) );
545 fScanRight -= fScanIncX;
546 aRect.SetRight( static_cast<long>( fScanRight ) );
547 fScanBottom -= fScanIncY;
548 aRect.SetBottom( static_cast<long>( fScanBottom ) );
549
550 if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
551 break;
552
553 if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical )
554 aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
555 else
556 aPoly = tools::Polygon( aRect );
557
558 aPoly.Rotate( aCenter, nAngle );
559
560 // adapt colour accordingly
561 const long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) );
562 nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) );
563 nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) );
564 nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) );
565
566 // either slow tools::PolyPolygon output or fast Polygon-Painting
567 if( xPolyPoly )
568 {
569 bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output
570
571 xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 );
572 xPolyPoly->Replace( aPoly, 1 );
573
574 ImplDrawPolyPolygon( *xPolyPoly, pClixPolyPoly );
575
576 // #107349# Set fill color _after_ geometry painting:
577 // xPolyPoly's geometry is the band from last iteration's
578 // aPoly to current iteration's aPoly. The window outdev
579 // path (see else below), on the other hand, paints the
580 // full aPoly. Thus, here, we're painting the band before
581 // the one painted in the window outdev path below. To get
582 // matching colors, have to delay color setting here.
583 mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
584 }
585 else
586 {
587 // #107349# Set fill color _before_ geometry painting
588 mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
589
590 ImplDrawPolygon( aPoly, pClixPolyPoly );
591 }
592 }
593
594 // we should draw last inner Polygon if we output PolyPolygon
595 if( xPolyPoly )
596 {
597 const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
598
599 if( !rPoly.GetBoundRect().IsEmpty() )
600 {
601 // #107349# Paint last polygon with end color only if loop
602 // has generated output. Otherwise, the current
603 // (i.e. start) color is taken, to generate _any_ output.
604 if( bPaintLastPolygon )
605 {
606 nRed = GetGradientColorValue( nEndRed );
607 nGreen = GetGradientColorValue( nEndGreen );
608 nBlue = GetGradientColorValue( nEndBlue );
609 }
610
611 mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
612 ImplDrawPolygon( rPoly, pClixPolyPoly );
613 }
614 }
615 }
616
DrawLinearGradientToMetafile(const tools::Rectangle & rRect,const Gradient & rGradient)617 void OutputDevice::DrawLinearGradientToMetafile( const tools::Rectangle& rRect,
618 const Gradient& rGradient )
619 {
620 assert(!is_double_buffered_window());
621
622 // get BoundRect of rotated rectangle
623 tools::Rectangle aRect;
624 Point aCenter;
625 sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
626
627 rGradient.GetBoundRect( rRect, aRect, aCenter );
628
629 bool bLinear = (rGradient.GetStyle() == GradientStyle::Linear);
630 double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0;
631 if ( !bLinear )
632 {
633 fBorder /= 2.0;
634 }
635 tools::Rectangle aMirrorRect = aRect; // used in style axial
636 aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
637 if ( !bLinear )
638 {
639 aRect.SetBottom( aMirrorRect.Top() );
640 }
641
642 // colour-intensities of start- and finish; change if needed
643 long nFactor;
644 Color aStartCol = rGradient.GetStartColor();
645 Color aEndCol = rGradient.GetEndColor();
646 long nStartRed = aStartCol.GetRed();
647 long nStartGreen = aStartCol.GetGreen();
648 long nStartBlue = aStartCol.GetBlue();
649 long nEndRed = aEndCol.GetRed();
650 long nEndGreen = aEndCol.GetGreen();
651 long nEndBlue = aEndCol.GetBlue();
652 nFactor = rGradient.GetStartIntensity();
653 nStartRed = (nStartRed * nFactor) / 100;
654 nStartGreen = (nStartGreen * nFactor) / 100;
655 nStartBlue = (nStartBlue * nFactor) / 100;
656 nFactor = rGradient.GetEndIntensity();
657 nEndRed = (nEndRed * nFactor) / 100;
658 nEndGreen = (nEndGreen * nFactor) / 100;
659 nEndBlue = (nEndBlue * nFactor) / 100;
660
661 // gradient style axial has exchanged start and end colors
662 if ( !bLinear)
663 {
664 long nTempColor = nStartRed;
665 nStartRed = nEndRed;
666 nEndRed = nTempColor;
667 nTempColor = nStartGreen;
668 nStartGreen = nEndGreen;
669 nEndGreen = nTempColor;
670 nTempColor = nStartBlue;
671 nStartBlue = nEndBlue;
672 nEndBlue = nTempColor;
673 }
674
675 sal_uInt8 nRed;
676 sal_uInt8 nGreen;
677 sal_uInt8 nBlue;
678
679 // Create border
680 tools::Rectangle aBorderRect = aRect;
681 tools::Polygon aPoly( 4 );
682 if (fBorder > 0.0)
683 {
684 nRed = static_cast<sal_uInt8>(nStartRed);
685 nGreen = static_cast<sal_uInt8>(nStartGreen);
686 nBlue = static_cast<sal_uInt8>(nStartBlue);
687
688 mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
689
690 aBorderRect.SetBottom( static_cast<long>( aBorderRect.Top() + fBorder ) );
691 aRect.SetTop( aBorderRect.Bottom() );
692 aPoly[0] = aBorderRect.TopLeft();
693 aPoly[1] = aBorderRect.TopRight();
694 aPoly[2] = aBorderRect.BottomRight();
695 aPoly[3] = aBorderRect.BottomLeft();
696 aPoly.Rotate( aCenter, nAngle );
697
698 mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
699
700 if ( !bLinear)
701 {
702 aBorderRect = aMirrorRect;
703 aBorderRect.SetTop( static_cast<long>( aBorderRect.Bottom() - fBorder ) );
704 aMirrorRect.SetBottom( aBorderRect.Top() );
705 aPoly[0] = aBorderRect.TopLeft();
706 aPoly[1] = aBorderRect.TopRight();
707 aPoly[2] = aBorderRect.BottomRight();
708 aPoly[3] = aBorderRect.BottomLeft();
709 aPoly.Rotate( aCenter, nAngle );
710
711 mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
712 }
713 }
714
715 long nStepCount = GetGradientSteps( rGradient, aRect, true/*bMtf*/ );
716
717 // minimal three steps and maximal as max color steps
718 long nAbsRedSteps = std::abs( nEndRed - nStartRed );
719 long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
720 long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
721 long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
722 nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
723 long nSteps = std::min( nStepCount, nMaxColorSteps );
724 if ( nSteps < 3)
725 {
726 nSteps = 3;
727 }
728
729 double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps);
730 double fGradientLine = static_cast<double>(aRect.Top());
731 double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom());
732
733 const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0;
734 if ( !bLinear)
735 {
736 nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap
737 }
738 for ( long i = 0; i < nSteps; i++ )
739 {
740 // linear interpolation of color
741 double fAlpha = static_cast<double>(i) / fStepsMinus1;
742 double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha;
743 nRed = GetGradientColorValue(static_cast<long>(fTempColor));
744 fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
745 nGreen = GetGradientColorValue(static_cast<long>(fTempColor));
746 fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
747 nBlue = GetGradientColorValue(static_cast<long>(fTempColor));
748
749 mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
750
751 // Polygon for this color step
752 aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
753 aRect.SetBottom( static_cast<long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) );
754 aPoly[0] = aRect.TopLeft();
755 aPoly[1] = aRect.TopRight();
756 aPoly[2] = aRect.BottomRight();
757 aPoly[3] = aRect.BottomLeft();
758 aPoly.Rotate( aCenter, nAngle );
759
760 mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
761
762 if ( !bLinear )
763 {
764 aMirrorRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
765 aMirrorRect.SetTop( static_cast<long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) );
766 aPoly[0] = aMirrorRect.TopLeft();
767 aPoly[1] = aMirrorRect.TopRight();
768 aPoly[2] = aMirrorRect.BottomRight();
769 aPoly[3] = aMirrorRect.BottomLeft();
770 aPoly.Rotate( aCenter, nAngle );
771
772 mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
773 }
774 }
775 if ( bLinear)
776 return;
777
778 // draw middle polygon with end color
779 nRed = GetGradientColorValue(nEndRed);
780 nGreen = GetGradientColorValue(nEndGreen);
781 nBlue = GetGradientColorValue(nEndBlue);
782
783 mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
784
785 aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
786 aRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) );
787 aPoly[0] = aRect.TopLeft();
788 aPoly[1] = aRect.TopRight();
789 aPoly[2] = aRect.BottomRight();
790 aPoly[3] = aRect.BottomLeft();
791 aPoly.Rotate( aCenter, nAngle );
792
793 mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
794
795 }
796
DrawComplexGradientToMetafile(const tools::Rectangle & rRect,const Gradient & rGradient)797 void OutputDevice::DrawComplexGradientToMetafile( const tools::Rectangle& rRect,
798 const Gradient& rGradient )
799 {
800 assert(!is_double_buffered_window());
801
802 // Determine if we output via Polygon or PolyPolygon
803 // For all rasteroperations other than Overpaint always use PolyPolygon,
804 // as we will get wrong results if we output multiple times on top of each other.
805 // Also for printers always use PolyPolygon, as not all printers
806 // can print polygons on top of each other.
807
808 std::unique_ptr<tools::PolyPolygon> xPolyPoly;
809 tools::Rectangle aRect;
810 Point aCenter;
811 Color aStartCol( rGradient.GetStartColor() );
812 Color aEndCol( rGradient.GetEndColor() );
813 long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
814 long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
815 long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
816 long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
817 long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
818 long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
819 long nRedSteps = nEndRed - nStartRed;
820 long nGreenSteps = nEndGreen - nStartGreen;
821 long nBlueSteps = nEndBlue - nStartBlue;
822 sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
823
824 rGradient.GetBoundRect( rRect, aRect, aCenter );
825
826 xPolyPoly.reset(new tools::PolyPolygon( 2 ));
827
828 // last parameter - true if complex gradient, false if linear
829 long nStepCount = GetGradientSteps( rGradient, rRect, true, true );
830
831 // at least three steps and at most the number of colour differences
832 long nSteps = std::max( nStepCount, 2L );
833 long nCalcSteps = std::abs( nRedSteps );
834 long nTempSteps = std::abs( nGreenSteps );
835 if ( nTempSteps > nCalcSteps )
836 nCalcSteps = nTempSteps;
837 nTempSteps = std::abs( nBlueSteps );
838 if ( nTempSteps > nCalcSteps )
839 nCalcSteps = nTempSteps;
840 if ( nCalcSteps < nSteps )
841 nSteps = nCalcSteps;
842 if ( !nSteps )
843 nSteps = 1;
844
845 // determine output limits and stepsizes for all directions
846 tools::Polygon aPoly;
847 double fScanLeft = aRect.Left();
848 double fScanTop = aRect.Top();
849 double fScanRight = aRect.Right();
850 double fScanBottom = aRect.Bottom();
851 double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
852 double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
853
854 // all gradients are rendered as nested rectangles which shrink
855 // equally in each dimension - except for 'square' gradients
856 // which shrink to a central vertex but are not per-se square.
857 if( rGradient.GetStyle() != GradientStyle::Square )
858 {
859 fScanIncY = std::min( fScanIncY, fScanIncX );
860 fScanIncX = fScanIncY;
861 }
862 sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
863 bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
864
865 mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
866
867 aPoly = rRect;
868 xPolyPoly->Insert( aPoly );
869 xPolyPoly->Insert( aPoly );
870
871 // loop to output Polygon/PolyPolygon sequentially
872 for( long i = 1; i < nSteps; i++ )
873 {
874 // calculate new Polygon
875 fScanLeft += fScanIncX;
876 aRect.SetLeft( static_cast<long>( fScanLeft ) );
877 fScanTop += fScanIncY;
878 aRect.SetTop( static_cast<long>( fScanTop ) );
879 fScanRight -= fScanIncX;
880 aRect.SetRight( static_cast<long>( fScanRight ) );
881 fScanBottom -= fScanIncY;
882 aRect.SetBottom( static_cast<long>( fScanBottom ) );
883
884 if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
885 break;
886
887 if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical )
888 aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
889 else
890 aPoly = tools::Polygon( aRect );
891
892 aPoly.Rotate( aCenter, nAngle );
893
894 // adapt colour accordingly
895 const long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) );
896 nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) );
897 nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) );
898 nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) );
899
900 bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output
901
902 xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 );
903 xPolyPoly->Replace( aPoly, 1 );
904
905 mpMetaFile->AddAction( new MetaPolyPolygonAction( *xPolyPoly ) );
906
907 // #107349# Set fill color _after_ geometry painting:
908 // xPolyPoly's geometry is the band from last iteration's
909 // aPoly to current iteration's aPoly. The window outdev
910 // path (see else below), on the other hand, paints the
911 // full aPoly. Thus, here, we're painting the band before
912 // the one painted in the window outdev path below. To get
913 // matching colors, have to delay color setting here.
914 mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
915 }
916
917 const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
918
919 if( !rPoly.GetBoundRect().IsEmpty() )
920 {
921 // #107349# Paint last polygon with end color only if loop
922 // has generated output. Otherwise, the current
923 // (i.e. start) color is taken, to generate _any_ output.
924 if( bPaintLastPolygon )
925 {
926 nRed = GetGradientColorValue( nEndRed );
927 nGreen = GetGradientColorValue( nEndGreen );
928 nBlue = GetGradientColorValue( nEndBlue );
929 }
930
931 mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
932 mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) );
933 }
934 }
935
GetGradientStepCount(long nMinRect)936 long OutputDevice::GetGradientStepCount( long nMinRect )
937 {
938 long nInc = (nMinRect < 50) ? 2 : 4;
939
940 return nInc;
941 }
942
GetGradientSteps(const Gradient & rGradient,const tools::Rectangle & rRect,bool bMtf,bool bComplex)943 long OutputDevice::GetGradientSteps( const Gradient& rGradient, const tools::Rectangle& rRect, bool bMtf, bool bComplex )
944 {
945 // calculate step count
946 long nStepCount = rGradient.GetSteps();
947 long nMinRect;
948
949 // generate nStepCount, if not passed
950 if (bComplex)
951 nMinRect = std::min( rRect.GetWidth(), rRect.GetHeight() );
952 else
953 nMinRect = rRect.GetHeight();
954
955 if ( !nStepCount )
956 {
957 long nInc;
958
959 nInc = GetGradientStepCount (nMinRect);
960 if ( !nInc || bMtf )
961 nInc = 1;
962 nStepCount = nMinRect / nInc;
963 }
964
965 return nStepCount;
966 }
967
GetSingleColorGradientFill()968 Color OutputDevice::GetSingleColorGradientFill()
969 {
970 Color aColor;
971
972 // we should never call on this function if any of these aren't set!
973 assert( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) );
974
975 if ( mnDrawMode & DrawModeFlags::BlackGradient )
976 aColor = COL_BLACK;
977 else if ( mnDrawMode & DrawModeFlags::WhiteGradient )
978 aColor = COL_WHITE;
979 else if ( mnDrawMode & DrawModeFlags::SettingsGradient )
980 aColor = GetSettings().GetStyleSettings().GetWindowColor();
981
982 return aColor;
983 }
984
SetGrayscaleColors(Gradient & rGradient)985 void OutputDevice::SetGrayscaleColors( Gradient &rGradient )
986 {
987 // this should only be called with the drawing mode is for grayscale gradients
988 assert ( mnDrawMode & DrawModeFlags::GrayGradient );
989
990 Color aStartCol( rGradient.GetStartColor() );
991 Color aEndCol( rGradient.GetEndColor() );
992
993 if ( mnDrawMode & DrawModeFlags::GrayGradient )
994 {
995 sal_uInt8 cStartLum = aStartCol.GetLuminance(), cEndLum = aEndCol.GetLuminance();
996 aStartCol = Color( cStartLum, cStartLum, cStartLum );
997 aEndCol = Color( cEndLum, cEndLum, cEndLum );
998 }
999
1000 rGradient.SetStartColor( aStartCol );
1001 rGradient.SetEndColor( aEndCol );
1002 }
1003
AddGradientActions(const tools::Rectangle & rRect,const Gradient & rGradient,GDIMetaFile & rMtf)1004 void OutputDevice::AddGradientActions( const tools::Rectangle& rRect, const Gradient& rGradient,
1005 GDIMetaFile& rMtf )
1006 {
1007
1008 tools::Rectangle aRect( rRect );
1009
1010 aRect.Justify();
1011
1012 // do nothing if the rectangle is empty
1013 if ( aRect.IsEmpty() )
1014 return;
1015
1016 Gradient aGradient( rGradient );
1017 GDIMetaFile* pOldMtf = mpMetaFile;
1018
1019 mpMetaFile = &rMtf;
1020 mpMetaFile->AddAction( new MetaPushAction( PushFlags::ALL ) );
1021 mpMetaFile->AddAction( new MetaISectRectClipRegionAction( aRect ) );
1022 mpMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
1023
1024 // because we draw with no border line, we have to expand gradient
1025 // rect to avoid missing lines on the right and bottom edge
1026 aRect.AdjustLeft( -1 );
1027 aRect.AdjustTop( -1 );
1028 aRect.AdjustRight( 1 );
1029 aRect.AdjustBottom( 1 );
1030
1031 // calculate step count if necessary
1032 if ( !aGradient.GetSteps() )
1033 aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
1034
1035 if( aGradient.GetStyle() == GradientStyle::Linear || aGradient.GetStyle() == GradientStyle::Axial )
1036 DrawLinearGradientToMetafile( aRect, aGradient );
1037 else
1038 DrawComplexGradientToMetafile( aRect, aGradient );
1039
1040 mpMetaFile->AddAction( new MetaPopAction() );
1041 mpMetaFile = pOldMtf;
1042
1043 }
1044
1045 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1046