123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- unit GR32.Lines.Thick;
- (* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1 or LGPL 2.1 with linking exception
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * Alternatively, the contents of this file may be used under the terms of the
- * Free Pascal modified version of the GNU Lesser General Public License
- * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
- * of this license are applicable instead of those above.
- * Please see the file LICENSE.txt for additional information concerning this
- * license.
- *
- * The Original Code is Thick Lines for Graphics32
- *
- * The Initial Developer of the Original Code is
- * Anders Melander
- *
- * Portions created by the Initial Developer are Copyright (C) 2023
- * the Initial Developer. All Rights Reserved.
- *
- * ***** END LICENSE BLOCK ***** *)
- interface
- uses
- Math,
- GR32;
- //------------------------------------------------------------------------------
- //
- // DrawThickLine
- //
- //------------------------------------------------------------------------------
- // Aliased, opaque thick line.
- // For anti-aliased & alpha blended lines use TCanvas32 instead.
- //------------------------------------------------------------------------------
- //
- // Draws a thick line using a modified Bresenham algorithm.
- //
- // We basicall draw a thick line by drawing a number of single-pixel width
- // lines, one pixel apart. We do this with a Bresenham loop inside a Bresenham
- // loop. The inner loop draws a line and the outer loop moves the line end-points.
- //
- // For a very similar algorithm see:
- //
- // - Line Thickening by Modification To Bresenham's Algorithm
- // Alan S. Murphy
- // IBM Technical Disclosure Bulletin, Vol. 20, No. 12, May 1978.
- // http://www.zoo.co.uk/murphy/thickline/
- // http://homepages.enterprise.net/murphy/thickline/index.html
- //
- // See the following for a good explanation of the above:
- // - http://kt8216.unixcab.org/murphy/index.html
- //
- // Murphy's algorithm above moves along the base line and draw lines
- // perpendicular to the base line.
- // This version however draw lines parallel to the base line.
- // This implementation was adapted from code by
- // - Armin Joachimsmeyer, Copyright (C) 2013-2022
- //
- // Missing features:
- // - Anti-aliasing.
- // - Blending
- // - Clipping
- //
- //------------------------------------------------------------------------------
- type
- TLineWidthMode = (
- LineWidthMiddle, // Start point is on the line at center of the thick line
- LineWidthDrawClockWise, // Start point is on the counter-clockwise border line
- LineWidthDrawCounterClockwise // Start point is on the clockwise border line
- );
- procedure DrawThickLine(Bitmap: TBitmap32; StartX, StartY, EndX, EndY: integer; Width: SmallInt; Color: TColor32;
- WidthMode: TLineWidthMode = LineWidthMiddle);
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- implementation
- type
- TLineOverlap = set of (
- // None: No line overlap, like in standard Bresenham
- LineOverlapMajor, // First go major then minor direction. Pixel is drawn as extension after actual line
- LineOverlapMinor // First go minor then major direction. Pixel is drawn as extension before next line
- );
- //------------------------------------------------------------------------------
- //
- // InnerDrawThickLine
- //
- //------------------------------------------------------------------------------
- procedure InnerDrawThickLine(Bitmap: TBitmap32; StartX, StartY, EndX, EndY: integer; Overlap: TLineOverlap; Color: TColor32);
- var
- DeltaX, DeltaY, TwoDeltaX, TwoDeltaY: SmallInt;
- Error: SmallInt;
- StepX, StepY: SmallInt;
- begin
- if (StartX = EndX) then
- begin
- if (StartY < EndY) then
- Bitmap.VertLineS(StartX, StartY, EndY, Color)
- else
- Bitmap.VertLineS(StartX, EndY, StartY, Color);
- end else
- if (StartY = EndY) then
- begin
- if (StartX < EndX) then
- Bitmap.HorzLineS(StartX, StartY, EndX, Color)
- else
- Bitmap.HorzLineS(StartX, EndX, StartY, Color);
- end else
- begin
- { calculate direction }
- DeltaX := EndX - StartX;
- DeltaY := EndY - StartY;
- if DeltaX < 0 then
- begin
- DeltaX := -DeltaX;
- StepX := -1;
- end else
- StepX := 1;
- if DeltaY < 0 then
- begin
- DeltaY := -DeltaY;
- StepY := -1;
- end else
- StepY := 1;
- TwoDeltaX := DeltaX * 2;
- TwoDeltaY := DeltaY * 2;
- { draw start pixel }
- Bitmap.PixelS[StartX, StartY] := Color;
- if DeltaX > DeltaY then
- begin
- { start value represents a half step in Y direction }
- Error := TwoDeltaY - DeltaX;
- while (StartX <> EndX) do
- begin
- { step in main direction }
- Inc(StartX, StepX);
- if (Error >= 0) then
- begin
- if (LineOverlapMajor in Overlap) then
- { draw pixel in major direction before changing }
- Bitmap.PixelS[StartX, StartY] := Color;
- { change Y }
- Inc(StartY, StepY);
- if (LineOverlapMinor in Overlap) then
- { draw pixel in minor direction before changing }
- Bitmap.PixelS[StartX - StepX, StartY] := Color;
- Dec(Error, TwoDeltaX);
- end;
- Inc(Error, TwoDeltaY);
- Bitmap.PixelS[StartX, StartY] := Color;
- end;
- end else
- begin
- { start value represents a half step in X direction }
- Error := TwoDeltaX - DeltaY;
- while (StartY <> EndY) do
- begin
- Inc(StartY, StepY);
- if (Error >= 0) then
- begin
- if (LineOverlapMajor in Overlap) then
- { draw pixel in major direction before changing }
- Bitmap.PixelS[StartX, StartY] := Color;
- { change X }
- Inc(StartX, StepX);
- if (LineOverlapMinor in Overlap) then
- { draw pixel in minor direction before changing }
- Bitmap.PixelS[StartX, StartY - StepY] := Color;
- Dec(Error, TwoDeltaY);
- end;
- Inc(Error, TwoDeltaX);
- Bitmap.PixelS[StartX, StartY] := Color;
- end;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- //
- // InnerDrawThickLine
- //
- //------------------------------------------------------------------------------
- procedure DrawThickLine(Bitmap: TBitmap32; StartX, StartY, EndX, EndY: integer; Width: SmallInt; Color: TColor32; WidthMode: TLineWidthMode = LineWidthMiddle);
- var
- i: integer;
- DeltaX, DeltaY, TwoDeltaX, TwoDeltaY: SmallInt;
- Error: SmallInt;
- StepX, StepY: SmallInt;
- MirrorQuadrant: Boolean;
- Overlap: TLineOverlap;
- DrawStartAdjustCount: integer;
- begin
- if (StartX = EndX) and (StartY = EndY) then
- exit;
- DeltaY := EndX - StartX;
- DeltaX := EndY - StartY;
- // Since we're not drawing anti-aliased we have to adjust the width for the
- // angle of the line. Otherwise diagonal lines would be wider (by Sqrt(2))
- // than straight lines.
- // Note:
- // - Even though were executing a costly Sqrt and float division,
- // the adjustment actually makes the routine faster on average since we're
- // potentially reducing the number of lines drawn.
- // - We're using Ceil instead of Trunc or Round to make the width better
- // match the width of a GDI thick line.
- Width := Ceil(Width * Hypot(DeltaX, DeltaY) / (Abs(DeltaX) + Abs(DeltaY)));
- if (Width <= 1) then
- begin
- InnerDrawThickLine(Bitmap, StartX, StartY, EndX, EndY, [], Color);
- exit;
- end;
- // Bresenham's algorithm only works in quadrant 1, so mirror 4 quadrants to one
- // and adjust deltas and stepping direction.
- // Make sure we are in quadrant 1 or 4
- MirrorQuadrant := True;
- if (DeltaX < 0) then
- begin
- DeltaX := -DeltaX;
- StepX := -1;
- MirrorQuadrant := not MirrorQuadrant;
- end else
- StepX := 1;
- // Make sure we are in quadrant 1
- if (DeltaY < 0) then
- begin
- DeltaY := -DeltaY;
- StepY := -1;
- MirrorQuadrant := not MirrorQuadrant;
- end else
- StepY := 1;
- // Now Delta* are positive and Step* define the direction.
- // MirrorQuadrant is False if we mirrored only once.
- TwoDeltaX := DeltaX * 2;
- TwoDeltaY := DeltaY * 2;
- // Adjust for right direction of thickness from line origin
- case WidthMode of
- LineWidthMiddle:
- DrawStartAdjustCount := Width div 2;
- LineWidthDrawCounterClockwise:
- DrawStartAdjustCount := Width - 1;
- LineWidthDrawClockWise:
- DrawStartAdjustCount := 0
- else
- DrawStartAdjustCount := 0; // Shut compiler up
- end;
- // Which octant are we now?
- if (DeltaX >= DeltaY) then
- begin
- // Octant 1, 3, 5, 7 (between 0 and 45, 90 and 135, etc. degree)
- if (MirrorQuadrant) then
- begin
- DrawStartAdjustCount := (Width - 1) - DrawStartAdjustCount;
- StepY := -StepY;
- end else
- StepX := -StepX;
- // Vector for draw direction of the starting points of lines is perpendicular
- // and counter-clockwise to main line direction.
- // Therefore no pixel will be missed if LineOverlapMajor is used on change
- // in minor perpendicular direction.
- // Adjust draw start point
- Error := TwoDeltaY - DeltaX;
- for i := 0 to DrawStartAdjustCount-1 do
- begin
- // Change X (main direction here)
- Dec(StartX, StepX);
- Dec(EndX, StepX);
- // Change Y
- if (Error >= 0) then
- begin
- Dec(StartY, StepY);
- Dec(EndY, StepY);
- Dec(Error, TwoDeltaX);
- end;
- Inc(Error, TwoDeltaY);
- end;
- // Draw start line.
- InnerDrawThickLine(Bitmap, StartX, StartY, EndX, EndY, [], Color);
- // Draw Width-1 number of lines (-1 because we have already drawn one)
- Error := TwoDeltaY - DeltaX;
- for i := 0 to Width-2 do
- begin
- // Change X (main direction here)
- Inc(StartX, StepX);
- Inc(EndX, StepX);
- Overlap := [];
- // Change Y
- if (Error >= 0) then
- begin
- Inc(StartY, StepY);
- Inc(EndY, StepY);
- Dec(Error, TwoDeltaX);
- Overlap := [LineOverlapMajor];
- end;
- Inc(Error, TwoDeltaY);
- InnerDrawThickLine(Bitmap, StartX, StartY, EndX, EndY, Overlap, Color);
- end;
- end else
- begin
- // Octant 2, 4, 6, 8 (between 45 and 90, 135 and 180, etc. degree)
- if (MirrorQuadrant) then
- StepX := -StepX
- else
- begin
- DrawStartAdjustCount := (Width - 1) - DrawStartAdjustCount;
- StepY := -StepY;
- end;
- // Adjust draw start point
- Error := TwoDeltaX - DeltaY;
- for i := 0 to DrawStartAdjustCount-1 do
- begin
- Dec(StartY, StepY);
- Dec(EndY, StepY);
- if (Error >= 0) then
- begin
- Dec(StartX, StepX);
- Dec(EndX, StepX);
- Dec(Error, TwoDeltaY);
- end;
- Inc(Error, TwoDeltaX);
- end;
- // Draw start line.
- InnerDrawThickLine(Bitmap, StartX, StartY, EndX, EndY, [], Color);
- // Draw Width-1 number of lines (-1 because we have already drawn one)
- Error := TwoDeltaX - DeltaY;
- for i := 0 to Width-2 do
- begin
- // Change Y (main direction here)
- Inc(StartY, StepY);
- Inc(EndY, StepY);
- Overlap := [];
- // Change X
- if (Error >= 0) then
- begin
- Inc(StartX, StepX);
- Inc(EndX, StepX);
- Dec(Error, TwoDeltaY);
- Overlap := [LineOverlapMajor];
- end;
- Inc(Error, TwoDeltaX);
- InnerDrawThickLine(Bitmap, StartX, StartY, EndX, EndY, Overlap, Color);
- end;
- end;
- end;
- end.
|