computeBounds, bindings changes

This commit is contained in:
Ryan Oldenburg 2021-08-17 20:11:57 -05:00
parent 400f66c3f4
commit fe6fb8c90e
4 changed files with 154 additions and 153 deletions

View file

@ -1,4 +1,4 @@
import chroma, vmath import bumpy, chroma, vmath
type type
PixieError* = object of ValueError ## Raised if an operation fails. PixieError* = object of ValueError ## Raised if an operation fails.
@ -16,9 +16,20 @@ proc lerp*(a, b: ColorRGBX, t: float32): ColorRGBX {.inline.} =
result.b = ((a.b.uint32 * (255 - x) + b.b.uint32 * x) div 255).uint8 result.b = ((a.b.uint32 * (255 - x) + b.b.uint32 * x) div 255).uint8
result.a = ((a.a.uint32 * (255 - x) + b.a.uint32 * x) div 255).uint8 result.a = ((a.a.uint32 * (255 - x) + b.a.uint32 * x) div 255).uint8
func lerp*(a, b: Color, v: float32): Color {.inline.} = proc lerp*(a, b: Color, v: float32): Color {.inline.} =
## Linearly interpolate between a and b using t. ## Linearly interpolate between a and b using t.
result.r = lerp(a.r, b.r, v) result.r = lerp(a.r, b.r, v)
result.g = lerp(a.g, b.g, v) result.g = lerp(a.g, b.g, v)
result.b = lerp(a.b, b.b, v) result.b = lerp(a.b, b.b, v)
result.a = lerp(a.a, b.a, v) result.a = lerp(a.a, b.a, v)
proc snapToPixels*(rect: Rect): Rect =
let
xMin = rect.x
xMax = rect.x + rect.w
yMin = rect.y
yMax = rect.y + rect.h
result.x = floor(xMin)
result.w = ceil(xMax) - result.x
result.y = floor(yMin)
result.h = ceil(yMax) - result.y

View file

@ -17,9 +17,9 @@ type
lineJoin*: LineJoin lineJoin*: LineJoin
font*: string ## File path to a .ttf or .otf file. font*: string ## File path to a .ttf or .otf file.
fontSize*: float32 fontSize*: float32
textAlign*: HAlignMode textAlign*: HorizontalAlignment
lineDash*: seq[float32]
path: Path path: Path
lineDash: seq[float32]
mat: Mat3 mat: Mat3
mask: Mask mask: Mask
layer: Image layer: Image
@ -35,7 +35,7 @@ type
lineJoin: LineJoin lineJoin: LineJoin
font: string font: string
fontSize*: float32 fontSize*: float32
textAlign: HAlignMode textAlign: HorizontalAlignment
lineDash: seq[float32] lineDash: seq[float32]
mat: Mat3 mat: Mat3
mask: Mask mask: Mask
@ -289,6 +289,22 @@ proc quadraticCurveTo*(ctx: Context, ctrl, to: Vec2) {.inline.} =
## Bézier curve. ## Bézier curve.
ctx.path.quadraticCurveTo(ctrl, to) ctx.path.quadraticCurveTo(ctrl, to)
proc arc*(ctx: Context, x, y, r, a0, a1: float32, ccw: bool = false) =
## Draws a circular arc.
ctx.path.arc(x, y, r, a0, a1, ccw)
proc arc*(ctx: Context, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) =
## Adds a circular arc to the current sub-path.
ctx.path.arc(pos, r, a, ccw)
proc arcTo*(ctx: Context, x1, y1, x2, y2, radius: float32) =
## Draws a circular arc using the given control points and radius.
ctx.path.arcTo(x1, y1, x2, y2, radius)
proc arcTo*(ctx: Context, a, b: Vec2, r: float32) =
## Adds a circular arc using the given control points and radius.
ctx.path.arcTo(a, b, r)
proc closePath*(ctx: Context) {.inline.} = proc closePath*(ctx: Context) {.inline.} =
## Attempts to add a straight line from the current point to the start of ## Attempts to add a straight line from the current point to the start of
## the current sub-path. If the shape has already been closed or has only ## the current sub-path. If the shape has already been closed or has only
@ -326,6 +342,8 @@ proc fill*(ctx: Context, windingRule = wrNonZero) {.inline.} =
## Fills the current path with the current fillStyle. ## Fills the current path with the current fillStyle.
ctx.fill(ctx.path, windingRule) ctx.fill(ctx.path, windingRule)
proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.}
proc clip*(ctx: Context, path: Path, windingRule = wrNonZero) = proc clip*(ctx: Context, path: Path, windingRule = wrNonZero) =
## Turns the path into the current clipping region. The previous clipping ## Turns the path into the current clipping region. The previous clipping
## region, if any, is intersected with the current or given path to create ## region, if any, is intersected with the current or given path to create
@ -342,6 +360,8 @@ proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.} =
## to create the new clipping region. ## to create the new clipping region.
ctx.clip(ctx.path, windingRule) ctx.clip(ctx.path, windingRule)
proc stroke*(ctx: Context) {.inline.}
proc stroke*(ctx: Context, path: Path) = proc stroke*(ctx: Context, path: Path) =
## Strokes (outlines) the current or given path with the current strokeStyle. ## Strokes (outlines) the current or given path with the current strokeStyle.
if ctx.mask != nil and ctx.layer == nil: if ctx.mask != nil and ctx.layer == nil:
@ -491,7 +511,110 @@ proc resetTransform*(ctx: Context) {.inline.} =
## Resets the current transform to the identity matrix. ## Resets the current transform to the identity matrix.
ctx.mat = mat3() ctx.mat = mat3()
proc drawImage*(ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32) =
## Draws a source image onto the destination image.
let
imageMat = ctx.mat * translate(vec2(dx, dy)) * scale(vec2(
dWidth / image.width.float32,
dHeight / image.height.float32
))
savedFillStyle = ctx.fillStyle
ctx.fillStyle = newPaint(pkImage)
ctx.fillStyle.image = image
ctx.fillStyle.imageMat = imageMat
let path = newPath()
path.rect(rect(dx, dy, dWidth, dHeight))
ctx.fill(path)
ctx.fillStyle = savedFillStyle
proc drawImage*(ctx: Context, image: Image, dx, dy: float32) =
## Draws a source image onto the destination image.
ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32)
proc drawImage*(ctx: Context, image: Image, pos: Vec2) =
## Draws a source image onto the destination image.
ctx.drawImage(image, pos.x, pos.y)
proc drawImage*(ctx: Context, image: Image, rect: Rect) =
## Draws a source image onto the destination image.
ctx.drawImage(image, rect.x, rect.y, rect.w, rect.h)
proc drawImage*(
ctx: Context,
image: Image,
sx, sy, sWidth, sHeight,
dx, dy, dWidth, dHeight: float32
) =
## Draws a source image onto the destination image.
let image = image.subImage(sx.int, sy.int, sWidth.int, sHeight.int)
ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32)
proc drawImage*(ctx: Context, image: Image, src, dest: Rect) =
## Draws a source image onto the destination image.
ctx.drawImage(
image,
src.x, src.y, src.w, src.h,
dest.x, dest.y, dest.w, dest.h
)
proc isPointInPath*(
ctx: Context, path: Path, pos: Vec2, windingRule = wrNonZero
): bool =
## Returns whether or not the specified point is contained in the current path.
path.fillOverlaps(pos, ctx.mat, windingRule)
proc isPointInPath*(
ctx: Context, path: Path, x, y: float32, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(path, vec2(x, y), windingRule)
proc isPointInPath*(
ctx: Context, pos: Vec2, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(ctx.path, pos, windingRule)
proc isPointInPath*(
ctx: Context, x, y: float32, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(ctx.path, vec2(x, y), windingRule)
proc isPointInStroke*(ctx: Context, path: Path, pos: Vec2): bool =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
path.strokeOverlaps(
pos,
ctx.mat,
ctx.lineWidth,
ctx.lineCap,
ctx.lineJoin,
ctx.miterLimit,
ctx.lineDash
)
proc isPointInStroke*(ctx: Context, path: Path, x, y: float32): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(path, vec2(x, y))
proc isPointInStroke*(ctx: Context, pos: Vec2): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(ctx.path, pos)
proc isPointInStroke*(ctx: Context, x, y: float32): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(ctx.path, vec2(x, y))
#
# Additional procs that are not part of the JS API # Additional procs that are not part of the JS API
#
proc roundedRect*(ctx: Context, x, y, w, h, nw, ne, se, sw: float32) {.inline.} = proc roundedRect*(ctx: Context, x, y, w, h, nw, ne, se, sw: float32) {.inline.} =
## Adds a rounded rectangle to the current path. ## Adds a rounded rectangle to the current path.
@ -566,12 +689,6 @@ proc fillCircle*(ctx: Context, circle: Circle) =
path.circle(circle) path.circle(circle)
ctx.fill(path) ctx.fill(path)
proc fillCircle*(ctx: Context, center: Vec2, radius: float32) =
## Draws a circle that is filled according to the current fillStyle.
let path = newPath()
path.ellipse(center, radius, radius)
ctx.fill(path)
proc strokeCircle*(ctx: Context, circle: Circle) = proc strokeCircle*(ctx: Context, circle: Circle) =
## Draws a circle that is stroked (outlined) according to the current ## Draws a circle that is stroked (outlined) according to the current
## strokeStyle and other context settings. ## strokeStyle and other context settings.
@ -579,13 +696,6 @@ proc strokeCircle*(ctx: Context, circle: Circle) =
path.circle(circle) path.circle(circle)
ctx.stroke(path) ctx.stroke(path)
proc strokeCircle*(ctx: Context, center: Vec2, radius: float32) =
## Draws a circle that is stroked (outlined) according to the current
## strokeStyle and other context settings.
let path = newPath()
path.ellipse(center, radius, radius)
ctx.stroke(path)
proc fillPolygon*(ctx: Context, pos: Vec2, size: float32, sides: int) = proc fillPolygon*(ctx: Context, pos: Vec2, size: float32, sides: int) =
## Draws an n-sided regular polygon at (x, y) of size that is filled according ## Draws an n-sided regular polygon at (x, y) of size that is filled according
## to the current fillStyle. ## to the current fillStyle.
@ -599,120 +709,3 @@ proc strokePolygon*(ctx: Context, pos: Vec2, size: float32, sides: int) =
let path = newPath() let path = newPath()
path.polygon(pos, size, sides) path.polygon(pos, size, sides)
ctx.stroke(path) ctx.stroke(path)
proc drawImage*(ctx: Context, image: Image, dx, dy, dWidth, dHeight: float32) =
## Draws a source image onto the destination image.
let
imageMat = ctx.mat * translate(vec2(dx, dy)) * scale(vec2(
dWidth / image.width.float32,
dHeight / image.height.float32
))
savedFillStyle = ctx.fillStyle
ctx.fillStyle = newPaint(pkImage)
ctx.fillStyle.image = image
ctx.fillStyle.imageMat = imageMat
let path = newPath()
path.rect(rect(dx, dy, dWidth, dHeight))
ctx.fill(path)
ctx.fillStyle = savedFillStyle
proc drawImage*(ctx: Context, image: Image, dx, dy: float32) =
## Draws a source image onto the destination image.
ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32)
proc drawImage*(ctx: Context, image: Image, pos: Vec2) =
## Draws a source image onto the destination image.
ctx.drawImage(image, pos.x, pos.y)
proc drawImage*(ctx: Context, image: Image, rect: Rect) =
## Draws a source image onto the destination image.
ctx.drawImage(image, rect.x, rect.y, rect.w, rect.h)
proc drawImage*(
ctx: Context,
image: Image,
sx, sy, sWidth, sHeight,
dx, dy, dWidth, dHeight: float32
) =
## Draws a source image onto the destination image.
let image = image.subImage(sx.int, sy.int, sWidth.int, sHeight.int)
ctx.drawImage(image, dx, dx, image.width.float32, image.height.float32)
proc drawImage*(ctx: Context, image: Image, src, dest: Rect) =
## Draws a source image onto the destination image.
ctx.drawImage(
image,
src.x, src.y, src.w, src.h,
dest.x, dest.y, dest.w, dest.h
)
proc arc*(ctx: Context, x, y, r, a0, a1: float32, ccw: bool = false) =
## Draws a circular arc.
ctx.path.arc(x, y, r, a0, a1, ccw)
proc arc*(ctx: Context, pos: Vec2, r: float32, a: Vec2, ccw: bool = false) =
## Adds a circular arc to the current sub-path.
ctx.path.arc(pos, r, a, ccw)
proc arcTo*(ctx: Context, x1, y1, x2, y2, radius: float32) =
## Draws a circular arc using the given control points and radius.
ctx.path.arcTo(x1, y1, x2, y2, radius)
proc arcTo*(ctx: Context, a, b: Vec2, r: float32) =
## Adds a circular arc using the given control points and radius.
ctx.path.arcTo(a, b, r)
proc isPointInPath*(
ctx: Context, path: Path, pos: Vec2, windingRule = wrNonZero
): bool =
## Returns whether or not the specified point is contained in the current path.
path.fillOverlaps(pos, ctx.mat, windingRule)
proc isPointInPath*(
ctx: Context, path: Path, x, y: float32, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(path, vec2(x, y), windingRule)
proc isPointInPath*(
ctx: Context, pos: Vec2, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(ctx.path, pos, windingRule)
proc isPointInPath*(
ctx: Context, x, y: float32, windingRule = wrNonZero
): bool {.inline.} =
## Returns whether or not the specified point is contained in the current path.
ctx.isPointInPath(ctx.path, vec2(x, y), windingRule)
proc isPointInStroke*(ctx: Context, path: Path, pos: Vec2): bool =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
path.strokeOverlaps(
pos,
ctx.mat,
ctx.lineWidth,
ctx.lineCap,
ctx.lineJoin,
ctx.miterLimit,
ctx.lineDash
)
proc isPointInStroke*(ctx: Context, path: Path, x, y: float32): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(path, vec2(x, y))
proc isPointInStroke*(ctx: Context, pos: Vec2): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(ctx.path, pos)
proc isPointInStroke*(ctx: Context, x, y: float32): bool {.inline.} =
## Returns whether or not the specified point is inside the area contained
## by the stroking of a path.
ctx.isPointInStroke(ctx.path, vec2(x, y))

View file

@ -35,12 +35,12 @@ type
positions*: seq[Vec2] ## The positions of the glyphs for each rune. positions*: seq[Vec2] ## The positions of the glyphs for each rune.
selectionRects*: seq[Rect] ## The selection rects for each glyph. selectionRects*: seq[Rect] ## The selection rects for each glyph.
HAlignMode* = enum HorizontalAlignment* = enum
haLeft haLeft
haCenter haCenter
haRight haRight
VAlignMode* = enum VerticalAlignment* = enum
vaTop vaTop
vaMiddle vaMiddle
vaBottom vaBottom

View file

@ -1021,7 +1021,13 @@ proc requiresAntiAliasing(segments: seq[(Segment, int16)]): bool =
# AA is required if all segments are not vertical or have fractional > 0 # AA is required if all segments are not vertical or have fractional > 0
return true return true
proc computePixelBounds(segments: seq[(Segment, int16)]): Rect = proc transform(shapes: var seq[seq[Vec2]], transform: Mat3) =
if transform != mat3():
for shape in shapes.mitems:
for vec in shape.mitems:
vec = transform * vec
proc computeBounds(segments: seq[(Segment, int16)]): Rect =
## Compute the bounds of the segments. ## Compute the bounds of the segments.
var var
xMin = float32.high xMin = float32.high
@ -1035,11 +1041,6 @@ proc computePixelBounds(segments: seq[(Segment, int16)]): Rect =
yMin = min(yMin, segment.at.y) yMin = min(yMin, segment.at.y)
yMax = max(yMax, segment.to.y) yMax = max(yMax, segment.to.y)
xMin = floor(xMin)
xMax = ceil(xMax)
yMin = floor(yMin)
yMax = ceil(yMax)
if xMin.isNaN() or xMax.isNaN() or yMin.isNaN() or yMax.isNaN(): if xMin.isNaN() or xMax.isNaN() or yMin.isNaN() or yMax.isNaN():
discard discard
else: else:
@ -1048,9 +1049,11 @@ proc computePixelBounds(segments: seq[(Segment, int16)]): Rect =
result.w = xMax - xMin result.w = xMax - xMin
result.h = yMax - yMin result.h = yMax - yMin
proc computePixelBounds*(path: Path): Rect = proc computeBounds*(path: Path, transform = mat3()): Rect =
## Compute the bounds of the path. ## Compute the bounds of the path.
path.commandsToShapes().shapesToSegments().computePixelBounds() var shapes = path.commandsToShapes()
shapes.transform(transform)
computeBounds(shapes.shapesToSegments())
proc partitionSegments( proc partitionSegments(
segments: seq[(Segment, int16)], top, height: int segments: seq[(Segment, int16)], top, height: int
@ -1482,7 +1485,7 @@ proc fillShapes(
rgbx = color.asRgbx() rgbx = color.asRgbx()
segments = shapes.shapesToSegments() segments = shapes.shapesToSegments()
aa = segments.requiresAntiAliasing() aa = segments.requiresAntiAliasing()
bounds = computePixelBounds(segments) bounds = computeBounds(segments).snapToPixels()
startX = max(0, bounds.x.int) startX = max(0, bounds.x.int)
startY = max(0, bounds.y.int) startY = max(0, bounds.y.int)
pathHeight = min(image.height, (bounds.y + bounds.h).int) pathHeight = min(image.height, (bounds.y + bounds.h).int)
@ -1539,7 +1542,7 @@ proc fillShapes(
let let
segments = shapes.shapesToSegments() segments = shapes.shapesToSegments()
aa = segments.requiresAntiAliasing() aa = segments.requiresAntiAliasing()
bounds = computePixelBounds(segments) bounds = computeBounds(segments).snapToPixels()
startX = max(0, bounds.x.int) startX = max(0, bounds.x.int)
startY = max(0, bounds.y.int) startY = max(0, bounds.y.int)
pathHeight = min(mask.height, (bounds.y + bounds.h).int) pathHeight = min(mask.height, (bounds.y + bounds.h).int)
@ -1729,12 +1732,6 @@ proc parseSomePath(
elif type(path) is Path: elif type(path) is Path:
path.commandsToShapes(closeSubpaths, pixelScale) path.commandsToShapes(closeSubpaths, pixelScale)
proc transform(shapes: var seq[seq[Vec2]], transform: Mat3) =
if transform != mat3():
for shape in shapes.mitems:
for segment in shape.mitems:
segment = transform * segment
proc fillPath*( proc fillPath*(
mask: Mask, mask: Mask,
path: SomePath, path: SomePath,