LibreOffice Module vcl (master) 1
outdev/hatch.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 <osl/diagnose.h>
21#include <tools/line.hxx>
22#include <tools/helpers.hxx>
24
25#include <vcl/hatch.hxx>
26#include <vcl/metaact.hxx>
27#include <vcl/settings.hxx>
28#include <vcl/virdev.hxx>
29
30#include <drawmode.hxx>
31#include <salgdi.hxx>
32
33#include <cassert>
34#include <cstdlib>
35#include <memory>
36
37#define HATCH_MAXPOINTS 1024
38
39extern "C" {
40
41static int HatchCmpFnc( const void* p1, const void* p2 )
42{
43 const tools::Long nX1 = static_cast<Point const *>(p1)->X();
44 const tools::Long nX2 = static_cast<Point const *>(p2)->X();
45 const tools::Long nY1 = static_cast<Point const *>(p1)->Y();
46 const tools::Long nY2 = static_cast<Point const *>(p2)->Y();
47
48 return ( nX1 > nX2 ? 1 : nX1 == nX2 ? nY1 > nY2 ? 1: nY1 == nY2 ? 0 : -1 : -1 );
49}
50
51}
52
53void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
54{
56
57 Hatch aHatch( rHatch );
58 aHatch.SetColor(vcl::drawmode::GetHatchColor(rHatch.GetColor(), GetDrawMode(), GetSettings().GetStyleSettings()));
59
60 if( mpMetaFile )
61 mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) );
62
64 return;
65
66 if( !mpGraphics && !AcquireGraphics() )
67 return;
68 assert(mpGraphics);
69
72
73 if( mbOutputClipped )
74 return;
75
76 if( rPolyPoly.Count() )
77 {
78 tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) );
79 GDIMetaFile* pOldMetaFile = mpMetaFile;
80 bool bOldMap = mbMap;
81
82 aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
84
85 mpMetaFile = nullptr;
86 EnableMapMode( false );
88 SetLineColor( aHatch.GetColor() );
90 DrawHatch( aPolyPoly, aHatch, false );
91 Pop();
92 EnableMapMode( bOldMap );
93 mpMetaFile = pOldMetaFile;
94 }
95
96 if( mpAlphaVDev )
97 mpAlphaVDev->DrawHatch( rPolyPoly, rHatch );
98}
99
100void OutputDevice::AddHatchActions( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch,
101 GDIMetaFile& rMtf )
102{
103
104 tools::PolyPolygon aPolyPoly( rPolyPoly );
105 aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME | PolyOptimizeFlags::CLOSE );
106
107 if( aPolyPoly.Count() )
108 {
109 GDIMetaFile* pOldMtf = mpMetaFile;
110
111 mpMetaFile = &rMtf;
113 mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) );
114 DrawHatch( aPolyPoly, rHatch, true );
116 mpMetaFile = pOldMtf;
117 }
118}
119
120static bool HasSaneNSteps(const Point& rPt1, const Point& rEndPt1, const Size& rInc)
121{
122 tools::Long nVertSteps = -1;
123 if (rInc.Height())
124 {
125 bool bFail = o3tl::checked_sub(rEndPt1.Y(), rPt1.Y(), nVertSteps);
126 if (bFail)
127 nVertSteps = std::numeric_limits<tools::Long>::max();
128 else
129 nVertSteps = nVertSteps / rInc.Height();
130 }
131 tools::Long nHorzSteps = -1;
132 if (rInc.Width())
133 {
134 bool bFail = o3tl::checked_sub(rEndPt1.X(), rPt1.X(), nHorzSteps);
135 if (bFail)
136 nHorzSteps = std::numeric_limits<tools::Long>::max();
137 else
138 nHorzSteps = nHorzSteps / rInc.Width();
139 }
140 auto nSteps = std::max(nVertSteps, nHorzSteps);
141 if (nSteps > 1024)
142 {
143 SAL_WARN("vcl.gdi", "skipping slow hatch with " << nSteps << " steps");
144 return false;
145 }
146 return true;
147}
148
149void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf )
150{
151 assert(!is_double_buffered_window());
152
153 if(!rPolyPoly.Count())
154 return;
155
156 // #i115630# DrawHatch does not work with beziers included in the polypolygon, take care of that
157 bool bIsCurve(false);
158
159 for(sal_uInt16 a(0); !bIsCurve && a < rPolyPoly.Count(); a++)
160 {
161 if(rPolyPoly[a].HasFlags())
162 {
163 bIsCurve = true;
164 }
165 }
166
167 if(bIsCurve)
168 {
169 OSL_ENSURE(false, "DrawHatch does *not* support curves, falling back to AdaptiveSubdivide()...");
170 tools::PolyPolygon aPolyPoly;
171
172 rPolyPoly.AdaptiveSubdivide(aPolyPoly);
173 DrawHatch(aPolyPoly, rHatch, bMtf);
174 }
175 else
176 {
177 tools::Rectangle aRect( rPolyPoly.GetBoundRect() );
178 const tools::Long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 );
180 std::unique_ptr<Point[]> pPtBuffer(new Point[ HATCH_MAXPOINTS ]);
181 Point aPt1, aPt2, aEndPt1;
182 Size aInc;
183
184 // Single hatch
185 aRect.AdjustLeft( -nLogPixelWidth ); aRect.AdjustTop( -nLogPixelWidth ); aRect.AdjustRight(nLogPixelWidth ); aRect.AdjustBottom(nLogPixelWidth );
186 CalcHatchValues( aRect, nWidth, rHatch.GetAngle(), aPt1, aPt2, aInc, aEndPt1 );
187 if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
188 return;
189
190 if (aInc.Width() <= 0 && aInc.Height() <= 0)
191 SAL_WARN("vcl.gdi", "invalid increment");
192 else
193 {
194 do
195 {
196 DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
197 aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
198 aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
199 }
200 while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
201 }
202
203 if( ( rHatch.GetStyle() == HatchStyle::Double ) || ( rHatch.GetStyle() == HatchStyle::Triple ) )
204 {
205 // Double hatch
206 CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 900_deg10, aPt1, aPt2, aInc, aEndPt1 );
207 if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
208 return;
209
210 do
211 {
212 DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
213 aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
214 aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
215 }
216 while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
217
218 if( rHatch.GetStyle() == HatchStyle::Triple )
219 {
220 // Triple hatch
221 CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 450_deg10, aPt1, aPt2, aInc, aEndPt1 );
222 if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
223 return;
224
225 do
226 {
227 DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
228 aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
229 aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
230 }
231 while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
232 }
233 }
234 }
235}
236
238 Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 )
239{
240 Point aRef;
241 Degree10 nAngle = nAngle10 % 1800_deg10;
242 tools::Long nOffset = 0;
243
244 if( nAngle > 900_deg10 )
245 nAngle -= 1800_deg10;
246
247 aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() );
248
249 if( 0_deg10 == nAngle )
250 {
251 rInc = Size( 0, nDist );
252 rPt1 = rRect.TopLeft();
253 rPt2 = rRect.TopRight();
254 rEndPt1 = rRect.BottomLeft();
255
256 if( aRef.Y() <= rRect.Top() )
257 nOffset = ( ( rRect.Top() - aRef.Y() ) % nDist );
258 else
259 nOffset = ( nDist - ( ( aRef.Y() - rRect.Top() ) % nDist ) );
260
261 rPt1.AdjustY( -nOffset );
262 rPt2.AdjustY( -nOffset );
263 }
264 else if( 900_deg10 == nAngle )
265 {
266 rInc = Size( nDist, 0 );
267 rPt1 = rRect.TopLeft();
268 rPt2 = rRect.BottomLeft();
269 rEndPt1 = rRect.TopRight();
270
271 if( aRef.X() <= rRect.Left() )
272 nOffset = ( rRect.Left() - aRef.X() ) % nDist;
273 else
274 nOffset = nDist - ( ( aRef.X() - rRect.Left() ) % nDist );
275
276 rPt1.AdjustX( -nOffset );
277 rPt2.AdjustX( -nOffset );
278 }
279 else if( nAngle >= Degree10(-450) && nAngle <= 450_deg10 )
280 {
281 const double fAngle = std::abs( toRadians(nAngle) );
282 const double fTan = tan( fAngle );
283 const tools::Long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan );
284 tools::Long nPY;
285
286 nDist = FRound( nDist / cos( fAngle ) );
287 rInc = Size( 0, nDist );
288
289 if( nAngle > 0_deg10 )
290 {
291 rPt1 = rRect.TopLeft();
292 rPt2 = Point( rRect.Right(), rRect.Top() - nYOff );
293 rEndPt1 = Point( rRect.Left(), rRect.Bottom() + nYOff );
294 nPY = FRound( aRef.Y() - ( ( rPt1.X() - aRef.X() ) * fTan ) );
295 }
296 else
297 {
298 rPt1 = rRect.TopRight();
299 rPt2 = Point( rRect.Left(), rRect.Top() - nYOff );
300 rEndPt1 = Point( rRect.Right(), rRect.Bottom() + nYOff );
301 nPY = FRound( aRef.Y() + ( ( rPt1.X() - aRef.X() ) * fTan ) );
302 }
303
304 if( nPY <= rPt1.Y() )
305 nOffset = ( rPt1.Y() - nPY ) % nDist;
306 else
307 nOffset = nDist - ( ( nPY - rPt1.Y() ) % nDist );
308
309 rPt1.AdjustY( -nOffset );
310 rPt2.AdjustY( -nOffset );
311 }
312 else
313 {
314 const double fAngle = std::abs( toRadians(nAngle) );
315 const double fTan = tan( fAngle );
316 const tools::Long nXOff = FRound( (static_cast<double>(rRect.Bottom()) - rRect.Top()) / fTan );
317 tools::Long nPX;
318
319 nDist = FRound( nDist / sin( fAngle ) );
320 rInc = Size( nDist, 0 );
321
322 if( nAngle > 0_deg10 )
323 {
324 rPt1 = rRect.TopLeft();
325 rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() );
326 rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() );
327 nPX = FRound( aRef.X() - ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) );
328 }
329 else
330 {
331 rPt1 = rRect.BottomLeft();
332 rPt2 = Point( rRect.Left() - nXOff, rRect.Top() );
333 rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() );
334 nPX = FRound( aRef.X() + ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) );
335 }
336
337 if( nPX <= rPt1.X() )
338 nOffset = ( rPt1.X() - nPX ) % nDist;
339 else
340 nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist );
341
342 rPt1.AdjustX( -nOffset );
343 rPt2.AdjustX( -nOffset );
344 }
345}
346
347void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly,
348 Point* pPtBuffer, bool bMtf )
349{
350 assert(!is_double_buffered_window());
351
352 double fX, fY;
353 tools::Long nAdd, nPCounter = 0;
354
355 for( tools::Long nPoly = 0, nPolyCount = rPolyPoly.Count(); nPoly < nPolyCount; nPoly++ )
356 {
357 const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(nPoly) ];
358
359 if( rPoly.GetSize() > 1 )
360 {
361 tools::Line aCurSegment( rPoly[ 0 ], Point() );
362
363 for( tools::Long i = 1, nCount = rPoly.GetSize(); i <= nCount; i++ )
364 {
365 aCurSegment.SetEnd( rPoly[ static_cast<sal_uInt16>( i % nCount ) ] );
366 nAdd = 0;
367
368 if( rLine.Intersection( aCurSegment, fX, fY ) )
369 {
370 if( ( fabs( fX - aCurSegment.GetStart().X() ) <= 0.0000001 ) &&
371 ( fabs( fY - aCurSegment.GetStart().Y() ) <= 0.0000001 ) )
372 {
373 const tools::Line aPrevSegment( rPoly[ static_cast<sal_uInt16>( ( i > 1 ) ? ( i - 2 ) : ( nCount - 1 ) ) ], aCurSegment.GetStart() );
374 const double fPrevDistance = rLine.GetDistance( aPrevSegment.GetStart() );
375 const double fCurDistance = rLine.GetDistance( aCurSegment.GetEnd() );
376
377 if( ( fPrevDistance <= 0.0 && fCurDistance > 0.0 ) ||
378 ( fPrevDistance > 0.0 && fCurDistance < 0.0 ) )
379 {
380 nAdd = 1;
381 }
382 }
383 else if( ( fabs( fX - aCurSegment.GetEnd().X() ) <= 0.0000001 ) &&
384 ( fabs( fY - aCurSegment.GetEnd().Y() ) <= 0.0000001 ) )
385 {
386 const tools::Line aNextSegment( aCurSegment.GetEnd(), rPoly[ static_cast<sal_uInt16>( ( i + 1 ) % nCount ) ] );
387
388 if( ( fabs( rLine.GetDistance( aNextSegment.GetEnd() ) ) <= 0.0000001 ) &&
389 ( rLine.GetDistance( aCurSegment.GetStart() ) > 0.0 ) )
390 {
391 nAdd = 1;
392 }
393 }
394 else
395 nAdd = 1;
396
397 if( nAdd )
398 {
399 if (nPCounter == HATCH_MAXPOINTS)
400 {
401 SAL_WARN("vcl.gdi", "too many hatch points");
402 return;
403 }
404 pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) );
405 }
406 }
407
408 aCurSegment.SetStart( aCurSegment.GetEnd() );
409 }
410 }
411 }
412
413 if( nPCounter <= 1 )
414 return;
415
416 qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc );
417
418 if( nPCounter & 1 )
419 nPCounter--;
420
421 if( bMtf )
422 {
423 for( tools::Long i = 0; i < nPCounter; i += 2 )
424 mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) );
425 }
426 else
427 {
428 for( tools::Long i = 0; i < nPCounter; i += 2 )
429 DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]);
430 }
431}
432
433void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
434{
435 Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)};
436 mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), *this);
437}
438
439/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void AddAction(const rtl::Reference< MetaAction > &pAction)
Definition: gdimtf.cxx:585
Definition: hatch.hxx:47
Degree10 GetAngle() const
Definition: hatch.hxx:68
void SetColor(const Color &rColor)
Definition: gdi/hatch.cxx:64
HatchStyle GetStyle() const
Definition: hatch.hxx:59
tools::Long GetDistance() const
Definition: hatch.hxx:65
const Color & GetColor() const
Definition: hatch.hxx:62
void SetDistance(tools::Long nDistance)
Definition: gdi/hatch.cxx:69
virtual void InitClipRegion()
SAL_DLLPRIVATE void DrawHatchLine(const tools::Line &rLine, const tools::PolyPolygon &rPolyPoly, Point *pPtBuffer, bool bMtf)
void EnableMapMode(bool bEnable=true)
Definition: map.cxx:589
SAL_DLLPRIVATE bool is_double_buffered_window() const
bool IsRefPoint() const
Definition: outdev.hxx:308
SAL_DLLPRIVATE void InitLineColor()
Definition: line.cxx:85
bool mbOutputClipped
Definition: outdev.hxx:245
virtual void DrawHatchLine_DrawLine(const Point &rStartPoint, const Point &rEndPoint)
SAL_DLLPRIVATE tools::Rectangle ImplLogicToDevicePixel(const tools::Rectangle &rLogicRect) const
Convert a logical rectangle to a rectangle in physical device pixel units.
Definition: map.cxx:334
virtual bool AcquireGraphics() const =0
Acquire a graphics device that the output device uses to draw on.
SAL_DLLPRIVATE bool ImplIsRecordLayout() const
Definition: outdev.cxx:708
void DrawHatch(const tools::PolyPolygon &rPolyPoly, const Hatch &rHatch)
GDIMetaFile * mpMetaFile
Definition: outdev.hxx:185
const Point & GetRefPoint() const
Definition: outdev.hxx:307
bool mbMap
Definition: outdev.hxx:240
void AddHatchActions(const tools::PolyPolygon &rPolyPoly, const Hatch &rHatch, GDIMetaFile &rMtf)
void SetLineColor()
Definition: line.cxx:37
SAL_DLLPRIVATE tools::Long ImplLogicWidthToDevicePixel(tools::Long nWidth) const
Convert a logical width to a width in units of device pixels.
Definition: map.cxx:280
SalGraphics * mpGraphics
Graphics context to draw on.
Definition: outdev.hxx:182
bool mbInitClipRegion
Definition: outdev.hxx:252
SAL_WARN_UNUSED_RESULT Point LogicToPixel(const Point &rLogicPt) const
Definition: map.cxx:879
SAL_DLLPRIVATE tools::Long ImplDevicePixelToLogicWidth(tools::Long nWidth) const
Convert device pixels to a width in logical units.
Definition: map.cxx:296
bool IsDeviceOutputNecessary() const
Definition: outdev.hxx:481
SAL_DLLPRIVATE void CalcHatchValues(const tools::Rectangle &rRect, tools::Long nDist, Degree10 nAngle10, Point &rPt1, Point &rPt2, Size &rInc, Point &rEndPt1)
VclPtr< VirtualDevice > mpAlphaVDev
Definition: outdev.hxx:196
void Push(vcl::PushFlags nFlags=vcl::PushFlags::ALL)
Definition: stack.cxx:32
void Pop()
Definition: stack.cxx:91
DrawModeFlags GetDrawMode() const
Definition: outdev.hxx:487
const AllSettings & GetSettings() const
Definition: outdev.hxx:288
constexpr tools::Long Y() const
tools::Long AdjustY(tools::Long nVertMove)
tools::Long AdjustX(tools::Long nHorzMove)
constexpr tools::Long X() const
void DrawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2, const OutputDevice &rOutDev)
constexpr tools::Long Height() const
constexpr tools::Long Width() const
const Point & GetEnd() const
void SetEnd(const Point &rEndPt)
const Point & GetStart() const
void SetStart(const Point &rStartPt)
bool Intersection(const tools::Line &rLine, double &rIntersectionX, double &rIntersectionY) const
double GetDistance(const double &rPtX, const double &rPtY) const
sal_uInt16 Count() const
void AdaptiveSubdivide(tools::PolyPolygon &rResult) const
tools::Rectangle GetBoundRect() const
void Optimize(PolyOptimizeFlags nOptimizeFlags)
sal_uInt16 GetSize() const
constexpr tools::Long Top() const
constexpr Point TopLeft() const
constexpr tools::Long Right() const
tools::Long AdjustTop(tools::Long nVertMoveDelta)
tools::Long AdjustRight(tools::Long nHorzMoveDelta)
constexpr Point TopRight() const
tools::Long AdjustBottom(tools::Long nVertMoveDelta)
tools::Long AdjustLeft(tools::Long nHorzMoveDelta)
constexpr tools::Long Left() const
constexpr tools::Long Bottom() const
constexpr Point BottomLeft() const
static bool IsFuzzing()
int nCount
double toRadians(D x)
tools::Long FRound(double fVal)
uno_Any a
#define SAL_WARN(area, stream)
int i
std::enable_if< std::is_signed< T >::value, bool >::type checked_sub(T a, T b, T &result)
long Long
Color GetHatchColor(Color const &rColor, DrawModeFlags nDrawMode, StyleSettings const &rStyleSettings)
Definition: drawmode.cxx:108
#define HATCH_MAXPOINTS
static int HatchCmpFnc(const void *p1, const void *p2)
static bool HasSaneNSteps(const Point &rPt1, const Point &rEndPt1, const Size &rInc)
#define Y