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