2 // System.Drawing.Drawing2D.ExtendedGeneralPath.cs
5 // Bors Kirzner <boris@mainsoft.com>
7 // Copyright (C) 2005 Mainsoft Corporation, (http://www.mainsoft.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using java.awt.geom;
\r
35 namespace System.Drawing.Drawing2D
\r
37 internal class ExtendedGeneralPath : Shape, ICloneable
\r
41 public const int WIND_EVEN_ODD = 0; //PathIterator__Finals.WIND_EVEN_ODD;
\r
42 public const int WIND_NON_ZERO = 1; //PathIterator__Finals.WIND_NON_ZERO;
\r
44 public const sbyte SEG_MOVETO = 0; //(byte) PathIterator__Finals.SEG_MOVETO;
\r
45 public const sbyte SEG_LINETO = 1; //(byte) PathIterator__Finals.SEG_LINETO;
\r
46 public const sbyte SEG_QUADTO = 2; //(byte) PathIterator__Finals.SEG_QUADTO;
\r
47 public const sbyte SEG_CUBICTO = 3; //(byte) PathIterator__Finals.SEG_CUBICTO;
\r
48 public const sbyte SEG_CLOSE = 4; //(byte) PathIterator__Finals.SEG_CLOSE;
\r
50 public const sbyte SEG_START = 16; // segment start
\r
52 public const sbyte SEG_MASK = SEG_MOVETO | SEG_LINETO | SEG_QUADTO | SEG_CUBICTO | SEG_CLOSE; // mask to eliminate SEG_CLOSE and SEG_MARKER
\r
54 private const sbyte SEG_MARKER = 32; // path marker
\r
57 private sbyte [] _types;
\r
58 private float [] _coords;
\r
59 private int _typesCount;
\r
60 private int _coordsCount;
\r
61 private int _windingRule;
\r
63 private PathData _pathData;
\r
64 private GeneralPath _generalPath;
\r
66 const int INIT_SIZE = 20;
\r
67 const int EXPAND_MAX = 500;
\r
69 #endregion // Fileds
\r
71 #region Constructors
\r
73 public ExtendedGeneralPath() : this (WIND_NON_ZERO, INIT_SIZE, INIT_SIZE)
\r
77 public ExtendedGeneralPath(int rule) : this (rule, INIT_SIZE, INIT_SIZE)
\r
81 public ExtendedGeneralPath(int rule, int initialCapacity) : this (rule, initialCapacity, initialCapacity)
\r
85 public ExtendedGeneralPath(Shape s) : this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE)
\r
87 PathIterator pi = s.getPathIterator (null);
\r
88 setWindingRule (pi.getWindingRule ());
\r
92 private ExtendedGeneralPath(int rule, int initialTypes, int initialCoords)
\r
94 setWindingRule(rule);
\r
95 Reset (initialTypes, initialCoords);
\r
98 #endregion // Constructors
\r
102 private GeneralPath GeneralPath
\r
105 if (_generalPath == null) {
\r
106 _generalPath = GetGeneralPath ();
\r
108 return _generalPath;
\r
112 public sbyte [] Types
\r
114 get { return _types; }
\r
117 public float [] Coords
\r
119 get { return _coords; }
\r
122 public int TypesCount
\r
124 get { return _typesCount; }
\r
127 public int CoordsCount
\r
129 get { return _coordsCount; }
\r
132 public bool LastFigureClosed
\r
135 return ((TypesCount == 0) ||
\r
136 ((Types [TypesCount - 1] & ExtendedGeneralPath.SEG_CLOSE) != 0) ||
\r
137 ((Types [TypesCount - 1] & ExtendedGeneralPath.SEG_START) != 0));
\r
141 public int PointCount
\r
144 return CoordsCount / 2;
\r
148 public PathData PathData
\r
152 if (_pathData == null)
\r
153 _pathData = GetPathData ();
\r
159 #endregion // Properties
\r
165 private void ClearCache ()
\r
168 _generalPath = null;
\r
171 private GeneralPath GetGeneralPath ()
\r
173 PathIterator iter = getPathIterator (null);
\r
174 GeneralPath path = new GeneralPath ();
\r
175 path.append (iter, false);
\r
179 private PathData GetPathData ()
\r
181 PathData pathData = new PathData();
\r
182 int nPts = PointCount;
\r
183 for (int i = 0; i < TypesCount; i++)
\r
184 if ((Types [i] & SEG_MASK) == SEG_QUADTO)
\r
187 pathData.Types = new byte [nPts];
\r
188 pathData.Points = new PointF [nPts];
\r
194 for (int i = 0; i < TypesCount; i++) {
\r
195 sbyte segmentType = (sbyte)(Types [i] & SEG_MASK);
\r
197 // set the masks and the markers
\r
198 marker = ((Types [i] & SEG_MARKER) != 0) ? (byte)PathPointType.PathMarker : (byte)0;
\r
199 start = ((Types [i] & SEG_START) != 0);
\r
201 switch (segmentType) {
\r
203 pathData.Types [tpos - 1] = (byte) (pathData.Types [tpos - 1] | (byte) PathPointType.CloseSubpath | marker);
\r
206 pathData.Types [tpos++] = (byte)((byte) PathPointType.Start | marker);
\r
207 pathData.Points [ppos++] = new PointF (Coords [cpos++], Coords [cpos++]);
\r
210 pathData.Types [tpos++] = (byte) ((byte) PathPointType.Line | marker);
\r
211 pathData.Points [ppos++] = new PointF (Coords [cpos++], Coords [cpos++]);
\r
215 .net does not support Quadratic curves, so convert to Cubic according to http://pfaedit.sourceforge.net/bezier.html
\r
217 The end points of the cubic will be the same as the quadratic's.
\r
221 The two control points for the cubic are:
\r
223 CP1 = QP0 + 2/3 *(QP1-QP0)
\r
224 CP2 = CP1 + 1/3 *(QP2-QP0)
\r
227 float x0 = Coords[cpos-2]; //QP0
\r
228 float y0 = Coords[cpos-1]; //QP0
\r
230 float x1 = x0 + (2/3 * (Coords [cpos++]-x0));
\r
231 float y1 = y0 + (2/3 * (Coords [cpos++]-y0));
\r
233 float x3 = Coords [cpos++]; //QP2
\r
234 float y3 = Coords [cpos++]; //QP2
\r
236 float x2 = x1 + (1/3 * (x3-x0));
\r
237 float y2 = y1 + (1/3 * (y3-y0));
\r
239 pathData.Types [tpos++] = (byte)(byte) PathPointType.Bezier;
\r
240 pathData.Points [ppos++] = new PointF (x1, y1);
\r
241 pathData.Types [tpos++] = (byte)(byte) PathPointType.Bezier;
\r
242 pathData.Points [ppos++] = new PointF (x2, y2);
\r
243 pathData.Types [tpos++] = (byte) ((byte)PathPointType.Bezier | marker);
\r
244 pathData.Points [ppos++] = new PointF (x3, y3);
\r
247 pathData.Types [tpos++] = (byte)(byte) PathPointType.Bezier3;
\r
248 pathData.Points [ppos++] = new PointF (Coords [cpos++], Coords [cpos++]);
\r
249 pathData.Types [tpos++] = (byte) PathPointType.Bezier3;
\r
250 pathData.Points [ppos++] = new PointF (Coords [cpos++], Coords [cpos++]);
\r
251 pathData.Types [tpos++] = (byte) ((byte)PathPointType.Bezier3 | marker);
\r
252 pathData.Points [ppos++] = new PointF (Coords [cpos++], Coords [cpos++]);
\r
259 #endregion // CachedData
\r
261 public void append(Shape s)
\r
263 append (s, !LastFigureClosed);
\r
266 #region GeneralPath
\r
268 public void append(PathIterator pi, bool connect)
\r
271 float [] coords = new float [6];
\r
272 while (!pi.isDone ()) {
\r
273 switch (pi.currentSegment (coords)) {
\r
275 if (!connect || _typesCount < 1 || _coordsCount < 2) {
\r
276 moveTo (coords [0], coords [1]);
\r
279 if (_types [_typesCount - 1] != SEG_CLOSE &&
\r
280 _coords [_coordsCount - 2] == coords [0] &&
\r
281 _coords [_coordsCount - 1] == coords [1])
\r
283 goto case SEG_LINETO;
\r
285 lineTo (coords [0], coords [1]);
\r
288 quadTo (coords [0], coords [1], coords [2], coords [3]);
\r
291 curveTo (coords [0], coords [1], coords [2], coords [3], coords [4], coords [5]);
\r
302 public void append(Shape s, bool connect)
\r
304 PathIterator pi = s.getPathIterator (null);
\r
305 append (pi,connect);
\r
308 public object Clone()
\r
310 ExtendedGeneralPath copy = (ExtendedGeneralPath)MemberwiseClone ();
\r
311 copy._types = (sbyte []) _types.Clone ();
\r
312 copy._coords = (float []) _coords.Clone ();
\r
316 public void closePath()
\r
319 if (_typesCount == 0 || _types[_typesCount - 1] != SEG_CLOSE) {
\r
320 needRoom (1, 0, true);
\r
321 _types [_typesCount++] = SEG_CLOSE;
\r
325 public bool contains(double x, double y)
\r
327 return GeneralPath.contains (x, y);
\r
330 public bool contains(double x, double y, double w, double h)
\r
332 return GeneralPath.contains (x, y, w, h);
\r
335 public bool contains(Point2D p)
\r
337 return contains (p.getX (), p.getY ());
\r
340 public bool contains(Rectangle2D r)
\r
342 return contains (r.getX (), r.getY (), r.getWidth (), r.getHeight ());
\r
345 public Shape createTransformedShape(AffineTransform at)
\r
347 ExtendedGeneralPath gp = (ExtendedGeneralPath) Clone ();
\r
354 public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3)
\r
357 needRoom (1, 6, true);
\r
358 _types [_typesCount++] = SEG_CUBICTO;
\r
359 _coords [_coordsCount++] = x1;
\r
360 _coords [_coordsCount++] = y1;
\r
361 _coords [_coordsCount++] = x2;
\r
362 _coords [_coordsCount++] = y2;
\r
363 _coords [_coordsCount++] = x3;
\r
364 _coords [_coordsCount++] = y3;
\r
367 public java.awt.Rectangle getBounds()
\r
369 return getBounds2D ().getBounds ();
\r
372 public Rectangle2D getBounds2D()
\r
374 float x1, y1, x2, y2;
\r
375 int i = _coordsCount;
\r
377 y1 = y2 = _coords [--i];
\r
378 x1 = x2 = _coords [--i];
\r
380 float y = _coords [--i];
\r
381 float x = _coords [--i];
\r
382 if (x < x1) x1 = x;
\r
383 if (y < y1) y1 = y;
\r
384 if (x > x2) x2 = x;
\r
385 if (y > y2) y2 = y;
\r
389 x1 = y1 = x2 = y2 = 0f;
\r
391 return new Rectangle2D.Float (x1, y1, x2 - x1, y2 - y1);
\r
394 public Point2D getCurrentPoint()
\r
396 if (_typesCount < 1 || _coordsCount < 2)
\r
399 int index = _coordsCount;
\r
400 if (_types [_typesCount - 1] == SEG_CLOSE)
\r
401 for (int i = _typesCount - 2; i > 0; i--) {
\r
402 switch (_types [i]) {
\r
421 return new Point2D.Float (_coords [index - 2], _coords [index - 1]);
\r
424 public PathIterator getPathIterator(AffineTransform at) {
\r
425 return new GeneralPathIterator (this, at);
\r
428 public PathIterator getPathIterator(AffineTransform at, double flatness) {
\r
429 return new FlatteningPathIterator (getPathIterator (at), flatness);
\r
432 public int getWindingRule()
\r
434 return _windingRule;
\r
437 public bool intersects(double x, double y, double w, double h)
\r
439 return GeneralPath.intersects (x, y, w, h);
\r
442 public bool intersects(Rectangle2D r)
\r
444 return intersects (r.getX (), r.getY (), r.getWidth (), r.getHeight ());
\r
447 public void lineTo(float x, float y)
\r
450 needRoom (1, 2, true);
\r
451 _types [_typesCount++] = SEG_LINETO;
\r
452 _coords [_coordsCount++] = x;
\r
453 _coords [_coordsCount++] = y;
\r
456 public void moveTo(float x, float y)
\r
459 if (_typesCount > 0 && _types [_typesCount - 1] == SEG_MOVETO) {
\r
460 _coords [_coordsCount - 2] = x;
\r
461 _coords [_coordsCount - 1] = y;
\r
464 needRoom (1, 2, false);
\r
465 _types [_typesCount++] = SEG_MOVETO;
\r
466 _coords [_coordsCount++] = x;
\r
467 _coords [_coordsCount++] = y;
\r
471 public void quadTo(float x1, float y1, float x2, float y2)
\r
473 // restore quadTo as cubic affects quality
\r
475 needRoom (1, 4, true);
\r
476 _types [_typesCount++] = SEG_QUADTO;
\r
477 _coords [_coordsCount++] = x1;
\r
478 _coords [_coordsCount++] = y1;
\r
479 _coords [_coordsCount++] = x2;
\r
480 _coords [_coordsCount++] = y2;
\r
483 public void reset()
\r
490 public void setWindingRule(int rule)
\r
492 if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
\r
493 throw new IllegalArgumentException ("winding rule must be WIND_EVEN_ODD or WIND_NON_ZERO");
\r
495 _windingRule = rule;
\r
498 public void transform(AffineTransform at)
\r
500 transform(at, 0, CoordsCount);
\r
503 public void transform(AffineTransform at, int startCoord, int numCoords) {
\r
505 at.transform (_coords, startCoord, _coords, startCoord, numCoords/2);
\r
508 private void needRoom(int newTypes, int newCoords, bool needMove)
\r
510 if (needMove && _typesCount == 0)
\r
511 throw new IllegalPathStateException ("missing initial moveto in path definition");
\r
513 int size = _coords.Length;
\r
514 if (_coordsCount + newCoords > size) {
\r
516 if (grow > EXPAND_MAX * 2)
\r
517 grow = EXPAND_MAX * 2;
\r
519 if (grow < newCoords)
\r
522 float [] arr = new float [size + grow];
\r
523 Array.Copy (_coords, 0, arr, 0, _coordsCount);
\r
526 size = _types.Length;
\r
527 if (_typesCount + newTypes > size) {
\r
529 if (grow > EXPAND_MAX)
\r
532 if (grow < newTypes)
\r
535 sbyte [] arr = new sbyte [size + grow];
\r
536 Array.Copy (_types, 0, arr, 0, _typesCount);
\r
541 #endregion // GeneralPath
\r
543 public void SetMarkers()
\r
546 if (TypesCount > 0)
\r
547 Types [ TypesCount - 1] |= SEG_MARKER;
\r
550 public void ClearMarkers()
\r
553 for (int i = 0; i < TypesCount; i++)
\r
554 Types [i] &= ~SEG_MARKER;
\r
557 public void StartFigure ()
\r
560 if (TypesCount > 0)
\r
561 Types [TypesCount - 1] |= ExtendedGeneralPath.SEG_START;
\r
564 private void Reset (int initialTypes, int initialCoords)
\r
567 _types = new sbyte [initialTypes];
\r
568 _coords = new float [initialCoords * 2];
\r
573 internal void Clear ()
\r
575 Reset (INIT_SIZE, INIT_SIZE);
\r
578 internal void Reverse ()
\r
581 // revert coordinates
\r
582 for (int i=0, max = CoordsCount / 2; i < max;) {
\r
585 int rix = CoordsCount - i;
\r
587 float tmpx = Coords [ix];
\r
588 float tmpy = Coords [iy];
\r
589 Coords [ix] = Coords [rix];
\r
590 Coords [iy] = Coords [riy];
\r
591 Coords [rix] = tmpx;
\r
592 Coords [riy] = tmpy;
\r
596 sbyte [] newTypes = new sbyte [TypesCount];
\r
598 int newIdx = TypesCount - 1;
\r
603 sbyte closeMask = 0;
\r
604 bool closedFigure = false;
\r
606 while (oldIdx < TypesCount) {
\r
607 // start copying after moveto
\r
608 copyStart = ++oldIdx;
\r
609 // continue to the next figure start
\r
610 while ((Types [oldIdx] != SEG_MOVETO) && (oldIdx < TypesCount))
\r
613 copyEnd = oldIdx - 1;
\r
614 // check whenever current figure is closed
\r
615 if ((Types [oldIdx - 1] & SEG_CLOSE) != 0) {
\r
616 closedFigure = true;
\r
618 newTypes [newIdx--] = (sbyte)(SEG_CLOSE | mask1);
\r
621 // end copy one cell earlier
\r
623 closeMask = (sbyte)(Types [oldIdx - 1] & (sbyte)SEG_MARKER);
\r
630 // copy reverted "inner" types
\r
631 for(int i = copyStart; i <= copyEnd; i++) {
\r
632 newTypes [newIdx--] = (sbyte)((Types [i] & SEG_MASK) | mask2);
\r
634 mask1 = (sbyte)(Types [i] & (sbyte)SEG_MARKER);
\r
638 newTypes [newIdx--] = SEG_MOVETO;
\r
640 // pass close mask to the nex figure
\r
641 if (closedFigure) {
\r
643 closedFigure = false;
\r
650 public PointF GetLastPoint ()
\r
652 if (CoordsCount == 0)
\r
653 throw new System.ArgumentException ("Invalid parameter used.");
\r
655 return new PointF (Coords [CoordsCount - 2], Coords [CoordsCount - 1]);
\r
658 #endregion //Methods
\r
660 #region Private helpers
\r
663 private void Print()
\r
665 Console.WriteLine ("\n\n");
\r
666 float [] fpoints = _coords;
\r
668 for (int i=0; i < _typesCount; i++) {
\r
669 sbyte type = _types [i];
\r
670 string marker = String.Empty;
\r
671 if ((type & SEG_MARKER) != 0)
\r
672 marker = " | MARKER";
\r
674 switch (type & SEG_MASK) {
\r
676 Console.WriteLine ("CLOSE {0}",marker);
\r
679 Console.WriteLine("{0}{3} ({1},{2})","MOVETO", fpoints[cpos++], fpoints[cpos++], marker);
\r
682 Console.WriteLine("{0}{3} ({1},{2})","LINETO", fpoints[cpos++], fpoints[cpos++], marker);
\r
685 Console.WriteLine("{0}{3} ({1},{2})","QUADTO", fpoints[cpos++], fpoints[cpos++], marker);
\r
686 Console.WriteLine(" ({1},{2})","QUADTO", fpoints[cpos++], fpoints[cpos++]);
\r
689 Console.WriteLine("{0}{3} ({1},{2})","CUBICTO", fpoints[cpos++], fpoints[cpos++], marker);
\r
690 Console.WriteLine(" ({1},{2})","CUBICTO", fpoints[cpos++], fpoints[cpos++]);
\r
691 Console.WriteLine(" ({1},{2})","CUBICTO", fpoints[cpos++], fpoints[cpos++]);
\r
697 #endregion // Private helpers
\r