Skip to content

Commit

Permalink
bezier final, boxtop/bottom
Browse files Browse the repository at this point in the history
  • Loading branch information
cormullion committed Jun 7, 2018
1 parent bb5a564 commit 7efb94f
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 128 deletions.
41 changes: 30 additions & 11 deletions docs/src/polygons.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A polygon is an ordered collection of Points stored in an array.

A path is a sequence of one or more straight and curved (circular arc or Bézier curve) segments. Paths can consist of subpaths. Luxor maintains a 'current path', to which you can add lines and curves until you finish with a stroke or fill instruction.

Luxor also provides a Bézier-path type, which is an array of four-point tuples, each of which is a Bézier curve section.
Luxor also provides a BezierPath type, which is an array of four-point tuples, each of which is a Bézier cubic curve section.

```@setup polytable
using Luxor
Expand All @@ -20,13 +20,15 @@ polygon ngon() polysmooth() poly() isi
- ngonside() - prettypoly() polyperimeter() polysplit()
- star() - polysmooth() polyarea() polyportion()
- offsetpoly() - - polycentroid() polyremainder()
- polyfit() - - boundingbox() polysortbyangle()
- polyfit() - - boundingbox() polysortbyangle()
- hyptrochoid() - - - polysortbydistance()
- epitrochoid() - - - polyintersections()
path getpath() pathtopoly() - - -
- getpathflat() - - - -
bezierpath makebezierpath() pathtobezierpaths() drawbezierpath() - -
- pathtobezierpaths() bezierpathtopoly() - - -
- pathtobezierpaths() bezierpathtopoly() brush() - -
- BezierPath() - - - -
- BezierPathSegment() - - - -
"""))
# find the widths of the columns
Expand Down Expand Up @@ -600,10 +602,9 @@ getpathflat

Use the `makebezierpath()` and `drawbezierpath()` functions to make and draw Bézier paths, and `pathtobezierpaths()` to convert the current path to an array of Bézier paths.

A Bézier path is a sequence of Bézier curve segments; each curve segment is defined by four points: two end points and their control points.
A BezierPath type contains a sequence of `BezierPathSegment`s; each curve segment is defined by four points: two end points and their control points.

```
NTuple{4,Point}[
(Point(-129.904, 75.0), # start point
Point(-162.38, 18.75), # ^ control point
Point(-64.9519, -150.0), # v control point
Expand All @@ -618,7 +619,6 @@ NTuple{4,Point}[
Point(-129.904, 75.0)
),
...
]
```

Bézier paths are different from ordinary paths in that they don't usually contain straight line segments. However, by setting the two control points to be the same as their matching start/end points, you create straight line sections.
Expand Down Expand Up @@ -714,7 +714,7 @@ nothing # hide
```
![path to polygon](assets/figures/bezierpathtopoly.png)

You can convert the current path to an array of Bézier paths using the `pathtobezierpaths()` function.
You can convert the current path to an array of BezierPaths using the `pathtobezierpaths()` function.

In the next example, the letter "a" is placed at the current position (set by `move()`) and then converted to an array of Bézier paths. Each Bézier path is drawn first of all in gray, then the control points of segment are drawn (in orange) showing how they affect the curvature.

Expand Down Expand Up @@ -754,22 +754,41 @@ nothing # hide

![path to polygon](assets/figures/pathtobezierpaths.png)

### Brush strokes

```@example
using Luxor # hide
Drawing(600, 250, "assets/figures/brush.png") # hide
origin() # hide
background("white") # hide
srand(42) # hide
sethue("black") # hide
brush(Point(-250, 0), Point(250, 0), 20,
strokes=15,
tidystart=true,
lowhandle=0.0,
twist=-5,
lowhandle=-0.5,
highhandle=0.5)
finish() # hide
nothing # hide
```

![brush](assets/figures/brush.png)

```@docs
bezier
bezier′
bezier′′
beziercurvature
bezierfrompoints
bezierpathtopoly
bezierstroke
beziertopoly
brush
drawbezierpath
Luxor.findbeziercontrolpoints
makebezierpath
pathtobezierpaths
setbezierhandles
shiftbezierhandles
brush
```

## Polygon information
Expand Down
2 changes: 2 additions & 0 deletions docs/src/simplegraphics.md
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,8 @@ boxdiagonal
boxwidth
boxheight
intersectboundingboxes
boxtop
boxbottom
```

## Miscellaneous
Expand Down
3 changes: 1 addition & 2 deletions docs/src/transforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,12 @@ nothing # hide

To return home after many changes, you can use `setmatrix([1, 0, 0, 1, 0, 0])` to reset the matrix to the default. `origin()` resets the matrix then moves the origin to the center of the page.

`rescale()` is just a convenient utility function for linear interpolation, also called a "lerp".
`rescale()` is a convenient utility function for linear interpolation, also called a "lerp".

```@docs
scale
rotate
translate
rescale
```

# Matrices and transformations
Expand Down
16 changes: 15 additions & 1 deletion src/BoundingBox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,24 @@ boxdiagonal(bb::BoundingBox) = hypot(boxwidth(bb), boxheight(bb))
"""
boxaspectratio(bb::BoundingBox)
Return the asepct ratio (the height divided by the width) of bounding box `bb`.
Return the aspect ratio (the height divided by the width) of bounding box `bb`.
"""
boxaspectratio(bb::BoundingBox) = boxheight(bb)/boxwidth(bb)

"""
boxtop(bb::BoundingBox)
Return the top center point of bounding box `bb`.
"""
boxtop(bb::BoundingBox) = midpoint(bb.corner1, bb.corner2) - (0, boxheight(bb)/2)

"""
boxbottom(bb::BoundingBox)
Return the top center point of bounding box `bb`.
"""
boxbottom(bb::BoundingBox) = midpoint(bb.corner1, bb.corner2) + (0, boxheight(bb)/2)

"""
convert(Point, bbox::BoundingBox)
Expand Down
2 changes: 1 addition & 1 deletion src/Luxor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export Drawing, currentdrawing,

move, rmove, line, rule, rline, arrow,

BoundingBox, boundingbox, boxwidth, boxheight, boxdiagonal, boxaspectratio,
BoundingBox, boundingbox, boxwidth, boxheight, boxdiagonal, boxaspectratio, boxtop, boxbottom,

circle, circlepath, ellipse, hypotrochoid, epitrochoid, squircle, center3pts, curve,
arc, carc, arc2r, carc2r, spiral, sector, intersection2circles,
Expand Down
166 changes: 67 additions & 99 deletions src/bezierpath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Base.next(bps::BezierPathSegment, s::Int) = bps[s], s+1
Base.done(bps::BezierPathSegment, s::Int) = (s > length(bps))

function Base.show(io::IO, bps::BezierPathSegment)
println(io, "p1 $(bps.p1) cp1 $(bps.cp1)")
println(io, "p1 $(bps.p1) cp1 $(bps.cp1)")
println(io, "cp2 $(bps.cp2) p2 $(bps.p2)")
end

Expand Down Expand Up @@ -407,39 +407,6 @@ Given four points, return the Bezier curve that passes through all four points.
"""
bezierfrompoints(ptslist::Array{Point, 1}) = bezierfrompoints(ptslist...)

"""
bezierstroke(bps::BezierPathSegment, width=5.0)
Create a BezierPath that replaces the single BezierPathSegment in `bps`. The
new BezierPath contains two segments which together define a shape.
"""
function bezierstroke(bps::BezierPathSegment, width=5.0)
newbezpath = BezierPath()
p1, cp1, cp2, p2 = bps
# find two points on the curve
# choose third and two thirds
ip1 = bezier(0.33, p1, cp1, cp2, p2)
ip2 = bezier(0.66, p1, cp1, cp2, p2)
# find slope of curve at those points
pt1 = bezier′(0.33, p1, cp1, cp2, p2)
pt2 = bezier′(0.66, p1, cp1, cp2, p2)
# find normals normal is 1/slope
slopeip1 = -1/(pt1.y/pt1.x)
slopeip2 = -1/(pt2.y/pt2.x)
# find perpendiculars on both sides
ipt1 = ip1 + polar(width, atan(slopeip1))
ipt2 = ip2 + polar(width, atan(slopeip2))
ipt3 = ip1 - polar(width, atan(slopeip1))
ipt4 = ip2 - polar(width, atan(slopeip2))
# make two new beziers, one on each side
result1 = bezierfrompoints(p1, ipt3, ipt4, p2)
result2 = bezierfrompoints(p1, ipt1, ipt2, p2)
push!(newbezpath, BezierPathSegment(p1, result1[2], result1[3], p2))
push!(newbezpath, BezierPathSegment(p2, result2[3], result2[2], p1))

return newbezpath
end

"""
bezierstroke(point1, point2, width=0.0)
Expand All @@ -453,35 +420,51 @@ To draw it, use eg `drawbezierpath(..., :fill)`.
"""
function bezierstroke(p1::Point, p2::Point, width=0.0)
bezpath = BezierPath()
# simple stroke starting ending at point
if isapprox(width, 0.0)
# simple wavy stroke starting ending at point
push!(bezpath, BezierPathSegment(p1, p1, p2, p2))
push!(bezpath, BezierPathSegment(p2, p2, p1, p1))
# stroke with broad opening and closing
result = setbezierhandles.(bezpath, angles=[0.1, -0.1],handles=[0.3, 0.3])
else
# stroke with broad opening and closing
# make the paths, then give them some control power
cp1 = perpendicular(p1, p2, -width)
push!(bezpath, BezierPathSegment(p1, p1, cp1, cp1))
seg = BezierPathSegment(p1, p1, cp1, cp1)
adjustedseg = setbezierhandles(seg, angles=[0.1, -0.1], handles=[0.3, 0.3])
push!(bezpath, adjustedseg)

cp2 = perpendicular(p2, p1, width)
push!(bezpath, BezierPathSegment(cp1, cp1, cp2, cp2))
push!(bezpath, BezierPathSegment(cp2, cp2, p2, p2))
seg = BezierPathSegment(cp1, cp1, cp2, cp2)
adjustedseg = setbezierhandles(seg, angles=[0.1, -0.1], handles=[0.3, 0.3])
push!(bezpath, adjustedseg)

seg = BezierPathSegment(cp2, cp2, p2, p2)
adjustedseg = setbezierhandles(seg, angles=[0.1, -0.1], handles=[0.3, 0.3])
push!(bezpath, adjustedseg)

# TODO the segments should be collinear, perhaps?
cp3 = perpendicular(p2, p1, -width)
push!(bezpath, BezierPathSegment(p2, p2, cp3, cp3))
seg = BezierPathSegment(p2, p2, cp3, cp3)
adjustedseg = setbezierhandles(seg, angles=[0.1, -0.1], handles=[0.3, 0.3])
push!(bezpath, adjustedseg)

cp4 = perpendicular(p1, p2, width)
push!(bezpath, BezierPathSegment(cp3, cp3, cp4, cp4))

push!(bezpath, BezierPathSegment(cp4, cp4, p1, p1))
seg = BezierPathSegment(cp3, cp3, cp4, cp4)
adjustedseg = setbezierhandles(seg, angles=[0.1, -0.1], handles=[0.3, 0.3])
push!(bezpath, adjustedseg)

seg = BezierPathSegment(cp4, cp4, p1, p1)
adjustedseg = setbezierhandles(seg, angles=[0.1, -0.1], handles=[0.3, 0.3])
push!(bezpath, adjustedseg)
result = bezpath
end
return bezpath
return result
end

"""
setbezierhandles(bps::BezierPathSegment;
angles=[0.05, -0.1],
handles=[0.3, 0.3])
angles = [0.05, -0.1],
handles = [0.3, 0.3])
Return a new Bezier path segment with new locations for the Bezier control
points in the Bezier path segment `bps`.
Expand Down Expand Up @@ -533,8 +516,8 @@ the values in `handles` modifies the lengths: 1 preserves the length, 0.5 halves
the length of the handles, 2 doubles them.
"""
function shiftbezierhandles(bps::BezierPathSegment;
angles=[0.1, -0.1],
handles=[0.1, 0.1])
angles = [0.1, -0.1],
handles = [0.1, 0.1])
p1, cp1, cp2, p2 = bps
# find slope of curve at the end points
spt1 = bezier′(0.0, p1, cp1, cp2, p2)
Expand All @@ -558,74 +541,59 @@ end

"""
brush(pt1, pt2, width=10;
strokes=5,
minwidth=0.01,
maxwidth=0.03,
twist = -1, # -1 or 1
randomopacity = true
)
strokes=10,
minwidth=0.01,
maxwidth=0.03,
twist = -1,
lowhandle = 0.3,
highhandle = 0.7,
randomopacity = true,
tidystart = false,
action = :fill)
Draw a composite brush stroke made up of some randomized individual brush strokes.
Draw a composite brush stroke made up of some randomized individual filled
Bezier paths.
!!! note
!!!
There is a lot of randomness in this function. Results are unpredictable.
"""
function brush(pt1, pt2, width=10;
strokes=5,
strokes=10,
minwidth=0.01,
maxwidth=0.03,
twist = -1,
randomopacity = true
lowhandle = 0.3,
highhandle = 0.7,
randomopacity = true,
tidystart = false,
action = :fill
)
@layer begin
sl = slope(pt1, pt2)
n = norm(pt1, pt2)
translate(pt1)
rotate(sl - pi/2)
for j in linspace(-width/2, width/2, max(strokes, 2))
shp = [O + (j, 0), O + (j, n)]
shp .+= Point(rand(-5:5), rand(-5:5))
pbp = bezierstroke(shp[1], shp[2], rand(Bool) ? 0.0 : rand(minwidth:0.1:maxwidth))
widthsteps = (maxwidth - minwidth)/10
for j in 1:strokes
shiftedline = [O + (0, 0), O + (0, n)]
if tidystart
shiftedline .+= Point(rand(-width/2:width/2), 0)
else
shiftedline .+= Point(rand(-width/2:width/2), rand(-width/2:width/2))
end
pbp = bezierstroke(shiftedline[1],
shiftedline[2],
rand(Bool) ? 0.0 : rand(minwidth:widthsteps:maxwidth)
)
for bps in pbp
nbpb = setbezierhandles(bps,
angles = [rand(minwidth:0.001:maxwidth), twist * rand(minwidth:0.001:maxwidth)],
handles = [rand(0.3:0.1:0.4, 2)...]
randomopacity ? setopacity(rand(0.3:0.1:0.9)) : setopacity(1.0)
nbpb = shiftbezierhandles(bps,
angles = [twist * rand(minwidth:widthsteps:maxwidth), -twist * rand(minwidth:widthsteps:maxwidth)],
handles = [rand(lowhandle:0.01:highhandle, 2)...]
)
randomopacity ? setopacity(rand()) : setopacity(1.0)
drawbezierpath(nbpb, :stroke, close=false)
drawbezierpath(nbpb, action, close=false)
end
end
end
end

"""
brush(bpseg::BezierPathSegment, width=10;
strokes=5,
minwidth=0.01,
maxwidth=0.03,
twist = -1, # -1 or 1
randomopacity = true
)
"""
function brush(bpseg::BezierPathSegment, width=10;
strokes=5,
minwidth=0.01,
maxwidth=0.01,
twist = -1,
randomopacity = true
)
@layer begin
# random start positions
for j in linspace(-width/2, width/2, max(strokes, 2))
shiftedpts = [bpseg.p1 + (j, 0), bpseg.p2 + (j, 0)]
shiftedpts .+= Point(rand(-width/2:width/2), rand(-width/2:width/2))
pbp = bezierstroke(BezierPathSegment(shiftedpts[1], bpseg.cp1, bpseg.cp2, shiftedpts[2]),
rand(Bool) ? 0.0 : rand(minwidth:0.1:maxwidth))
npbp = shiftbezierhandles.(pbp,
angles = [rand(minwidth:0.001:maxwidth), twist * rand(minwidth:0.001:maxwidth)],
handles = [rand(0.8:0.1:1.2, 2)...])
randomopacity ? setopacity(rand()) : setopacity(1.0)
drawbezierpath.(npbp, :stroke, close=false)
end
end
end
Loading

0 comments on commit 7efb94f

Please sign in to comment.