Merge pull request #200 from guzba/master

context clip support
This commit is contained in:
treeform 2021-05-22 21:53:41 -07:00 committed by GitHub
commit 277c14fc05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 189 additions and 33 deletions

View file

@ -1,5 +1,5 @@
import bumpy, chroma, pixie/blends, pixie/common, pixie/fonts, pixie/images, import bumpy, chroma, pixie/blends, pixie/common, pixie/fonts, pixie/images,
pixie/paints, pixie/paths, vmath pixie/masks, pixie/paints, pixie/paths, vmath
## This file provides a Nim version of the Canvas 2D API commonly used on the ## This file provides a Nim version of the Canvas 2D API commonly used on the
## web. The goal is to make picking up Pixie easy for developers familiar with ## web. The goal is to make picking up Pixie easy for developers familiar with
@ -17,16 +17,18 @@ type
textAlign*: HAlignMode textAlign*: HAlignMode
path: Path path: Path
mat: Mat3 mat: Mat3
mask: Mask
stateStack: seq[ContextState] stateStack: seq[ContextState]
ContextState = object ContextState = object
mat: Mat3
fillStyle, strokeStyle: Paint fillStyle, strokeStyle: Paint
lineWidth: float32 lineWidth: float32
lineCap: LineCap lineCap: LineCap
lineJoin: LineJoin lineJoin: LineJoin
font: Font font: Font
textAlign: HAlignMode textAlign: HAlignMode
mat: Mat3
mask: Mask
proc newContext*(image: Image): Context = proc newContext*(image: Image): Context =
## Create a new Context that will draw to the parameter image. ## Create a new Context that will draw to the parameter image.
@ -119,27 +121,69 @@ proc ellipse*(ctx: Context, x, y, rx, ry: float32) {.inline.} =
proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) {.inline.} = proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) {.inline.} =
## Fills the path with the current fillStyle. ## Fills the path with the current fillStyle.
ctx.image.fillPath( if ctx.mask != nil:
path, let tmp = newImage(ctx.image.width, ctx.image.height)
ctx.fillStyle, tmp.fillPath(
ctx.mat, path,
windingRule = windingRule ctx.fillStyle,
) ctx.mat,
windingRule
)
tmp.draw(ctx.mask)
ctx.image.draw(tmp)
else:
ctx.image.fillPath(
path,
ctx.fillStyle,
ctx.mat,
windingRule
)
proc fill*(ctx: Context, windingRule = wrNonZero) {.inline.} = 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, path: Path, windingRule = wrNonZero) {.inline.} =
## Turns the path into the current clipping region. The previous clipping
## region, if any, is intersected with the current or given path to create
## the new clipping region.
let mask = newMask(ctx.image.width, ctx.image.height)
mask.fillPath(path, ctx.mat, windingRule)
if ctx.mask == nil:
ctx.mask = mask
else:
ctx.mask.draw(mask, blendMode = bmMask)
proc clip*(ctx: Context, windingRule = wrNonZero) {.inline.} =
## Turns the current path into the current clipping region. The previous
## clipping region, if any, is intersected with the current or given path
## to create the new clipping region.
ctx.clip(ctx.path, windingRule)
proc stroke*(ctx: Context, path: Path) {.inline.} = proc stroke*(ctx: Context, path: Path) {.inline.} =
## Strokes (outlines) the current or given path with the current strokeStyle. ## Strokes (outlines) the current or given path with the current strokeStyle.
ctx.image.strokePath( if ctx.mask != nil:
path, let tmp = newImage(ctx.image.width, ctx.image.height)
ctx.strokeStyle, tmp.strokePath(
ctx.mat, path,
ctx.lineWidth, ctx.strokeStyle,
ctx.lineCap, ctx.mat,
ctx.lineJoin ctx.lineWidth,
) ctx.lineCap,
ctx.lineJoin
)
tmp.draw(ctx.mask)
ctx.image.draw(tmp)
else:
ctx.image.strokePath(
path,
ctx.strokeStyle,
ctx.mat,
ctx.lineWidth,
ctx.lineCap,
ctx.lineJoin
)
proc stroke*(ctx: Context) {.inline.} = proc stroke*(ctx: Context) {.inline.} =
## Strokes (outlines) the current or given path with the current strokeStyle. ## Strokes (outlines) the current or given path with the current strokeStyle.
@ -189,12 +233,24 @@ proc fillText*(ctx: Context, text: string, at: Vec2) =
at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) at.y -= round(ctx.font.typeface.ascent * ctx.font.scale)
ctx.font.paint = ctx.fillStyle ctx.font.paint = ctx.fillStyle
ctx.image.fillText(
ctx.font, if ctx.mask != nil:
text, let tmp = newImage(ctx.image.width, ctx.image.height)
ctx.mat * translate(at), tmp.fillText(
hAlign = ctx.textAlign ctx.font,
) text,
ctx.mat * translate(at),
hAlign = ctx.textAlign
)
tmp.draw(ctx.mask)
ctx.image.draw(tmp)
else:
ctx.image.fillText(
ctx.font,
text,
ctx.mat * translate(at),
hAlign = ctx.textAlign
)
proc fillText*(ctx: Context, text: string, x, y: float32) {.inline.} = proc fillText*(ctx: Context, text: string, x, y: float32) {.inline.} =
## Draws the outlines of the characters of a text string at the specified ## Draws the outlines of the characters of a text string at the specified
@ -213,15 +269,30 @@ proc strokeText*(ctx: Context, text: string, at: Vec2) =
at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) at.y -= round(ctx.font.typeface.ascent * ctx.font.scale)
ctx.font.paint = ctx.strokeStyle ctx.font.paint = ctx.strokeStyle
ctx.image.strokeText(
ctx.font, if ctx.mask != nil:
text, let tmp = newImage(ctx.image.width, ctx.image.height)
ctx.mat * translate(at), tmp.strokeText(
ctx.lineWidth, ctx.font,
hAlign = ctx.textAlign, text,
lineCap = ctx.lineCap, ctx.mat * translate(at),
lineJoin = ctx.lineJoin ctx.lineWidth,
) hAlign = ctx.textAlign,
lineCap = ctx.lineCap,
lineJoin = ctx.lineJoin
)
tmp.draw(ctx.mask)
ctx.image.draw(tmp)
else:
ctx.image.strokeText(
ctx.font,
text,
ctx.mat * translate(at),
ctx.lineWidth,
hAlign = ctx.textAlign,
lineCap = ctx.lineCap,
lineJoin = ctx.lineJoin
)
proc strokeText*(ctx: Context, text: string, x, y: float32) {.inline.} = proc strokeText*(ctx: Context, text: string, x, y: float32) {.inline.} =
## Draws the outlines of the characters of a text string at the specified ## Draws the outlines of the characters of a text string at the specified
@ -280,7 +351,6 @@ proc save*(ctx: Context) =
## Saves the entire state of the canvas by pushing the current state onto ## Saves the entire state of the canvas by pushing the current state onto
## a stack. ## a stack.
var state: ContextState var state: ContextState
state.mat = ctx.mat
state.fillStyle = ctx.fillStyle state.fillStyle = ctx.fillStyle
state.strokeStyle = ctx.strokeStyle state.strokeStyle = ctx.strokeStyle
state.lineWidth = ctx.lineWidth state.lineWidth = ctx.lineWidth
@ -288,6 +358,8 @@ proc save*(ctx: Context) =
state.lineJoin = ctx.lineJoin state.lineJoin = ctx.lineJoin
state.font = ctx.font state.font = ctx.font
state.textAlign = ctx.textAlign state.textAlign = ctx.textAlign
state.mat = ctx.mat
state.mask = if ctx.mask != nil: ctx.mask.copy() else: nil
ctx.stateStack.add(state) ctx.stateStack.add(state)
proc restore*(ctx: Context) = proc restore*(ctx: Context) =
@ -296,7 +368,6 @@ proc restore*(ctx: Context) =
## nothing. ## nothing.
if ctx.stateStack.len > 0: if ctx.stateStack.len > 0:
let state = ctx.stateStack.pop() let state = ctx.stateStack.pop()
ctx.mat = state.mat
ctx.fillStyle = state.fillStyle ctx.fillStyle = state.fillStyle
ctx.strokeStyle = state.strokeStyle ctx.strokeStyle = state.strokeStyle
ctx.lineWidth = state.lineWidth ctx.lineWidth = state.lineWidth
@ -304,6 +375,8 @@ proc restore*(ctx: Context) =
ctx.lineJoin = state.lineJoin ctx.lineJoin = state.lineJoin
ctx.font = state.font ctx.font = state.font
ctx.textAlign = state.textAlign ctx.textAlign = state.textAlign
ctx.mat = state.mat
ctx.mask = state.mask
# Additional procs that are not part of the JS API # Additional procs that are not part of the JS API

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,005 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -310,3 +310,86 @@ block:
ctx.fillRect(150, 40, 100, 100) ctx.fillRect(150, 40, 100, 100)
ctx.image.writeFile("tests/images/context/save_1.png") ctx.image.writeFile("tests/images/context/save_1.png")
block:
let ctx = newContext(newImage(300, 150))
ctx.beginPath()
ctx.circle(100, 75, 50)
ctx.clip()
ctx.fillStyle = "blue"
ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32)
ctx.fillStyle = "orange"
ctx.fillRect(0, 0, 100, 100)
ctx.image.writeFile("tests/images/context/clip_1.png")
block:
let ctx = newContext(newImage(300, 150))
ctx.fillStyle = "blue"
ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32)
ctx.beginPath()
ctx.circle(100, 75, 50)
ctx.clip()
ctx.fillStyle = "red"
ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32)
ctx.fillStyle = "orange"
ctx.fillRect(0, 0, 100, 100)
ctx.image.writeFile("tests/images/context/clip_1b.png")
block:
let ctx = newContext(newImage(300, 150))
ctx.save()
ctx.beginPath()
ctx.circle(100, 75, 50)
ctx.clip()
ctx.fillStyle = "red"
ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32)
ctx.fillStyle = "orange"
ctx.fillRect(0, 0, 100, 100)
ctx.restore()
ctx.fillStyle = "blue"
ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32)
ctx.image.writeFile("tests/images/context/clip_1c.png")
block:
let ctx = newContext(newImage(300, 150))
var region: Path
region.rect(80, 10, 20, 130)
region.rect(40, 50, 100, 50)
ctx.clip(region, wrEvenOdd)
ctx.fillStyle = "blue"
ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32)
ctx.image.writeFile("tests/images/context/clip_2.png")
block:
let image = newImage(300, 150)
let ctx = newContext(image)
var circlePath: Path
circlePath.circle(150, 75, 75)
var squarePath: Path
squarePath.rect(85, 10, 130, 130)
ctx.clip(circlePath)
ctx.clip(squarePath)
ctx.fillStyle = "blue"
ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32)
image.writeFile("tests/images/context/clip_3.png")