diff --git a/README.md b/README.md index 4759975..ee8f9e3 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ font.size = 20 let text = "Typesetting is the arrangement and composition of text in graphic design and publishing in both digital and traditional medias." -image.fillText(font.typeset(text, bounds = vec2(180, 180)), vec2(10, 10)) +image.fillText(font.typeset(text, vec2(180, 180)), translate(vec2(10, 10))) ``` ![example output](examples/text.png) @@ -143,7 +143,7 @@ let spans = @[ newFont(typeface, 14, color(0.3125, 0.3125, 0.3125, 1))) ] -image.fillText(typeset(spans, bounds = vec2(180, 180)), vec2(10, 10)) +image.fillText(typeset(spans, vec2(180, 180)), translate(vec2(10, 10))) ``` ![example output](examples/text_spans.png) diff --git a/examples/text.nim b/examples/text.nim index 0048b79..05f16f3 100644 --- a/examples/text.nim +++ b/examples/text.nim @@ -8,5 +8,5 @@ font.size = 20 let text = "Typesetting is the arrangement and composition of text in graphic design and publishing in both digital and traditional medias." -image.fillText(font.typeset(text, bounds = vec2(180, 180)), vec2(10, 10)) +image.fillText(font.typeset(text, vec2(180, 180)), translate(vec2(10, 10))) image.writeFile("examples/text.png") diff --git a/examples/text_spans.nim b/examples/text_spans.nim index ebdb8f3..1649d97 100644 --- a/examples/text_spans.nim +++ b/examples/text_spans.nim @@ -19,5 +19,5 @@ let spans = @[ newFont(typeface, 14, color(0.3125, 0.3125, 0.3125, 1))) ] -image.fillText(typeset(spans, bounds = vec2(180, 180)), vec2(10, 10)) +image.fillText(typeset(spans, vec2(180, 180)), translate(vec2(10, 10))) image.writeFile("examples/text_spans.png") diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index 7fad185..560992a 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -505,9 +505,9 @@ proc circle*(ctx: Context, cx, cy, r: float32) {.inline.} = ## Adds a circle to the current path. ctx.path.circle(cx, cy, r) -proc circle*(ctx: Context, center: Vec2, r: float32) {.inline.} = +proc circle*(ctx: Context, circle: Circle) {.inline.} = ## Adds a circle to the current path. - ctx.path.circle(center, r) + ctx.path.circle(circle) proc polygon*(ctx: Context, x, y, size: float32, sides: int) {.inline.} = ## Adds an n-sided regular polygon at (x, y) of size to the current path. @@ -563,7 +563,7 @@ proc strokeEllipse*(ctx: Context, center: Vec2, rx, ry: float32) = proc fillCircle*(ctx: Context, circle: Circle) = ## Draws a circle that is filled according to the current fillStyle let path = newPath() - path.circle(circle.pos, circle.radius) + path.circle(circle) ctx.fill(path) proc fillCircle*(ctx: Context, center: Vec2, radius: float32) = @@ -576,7 +576,7 @@ proc strokeCircle*(ctx: Context, circle: Circle) = ## Draws a circle that is stroked (outlined) according to the current ## strokeStyle and other context settings. let path = newPath() - path.circle(circle.pos, circle.radius) + path.circle(circle) ctx.stroke(path) proc strokeCircle*(ctx: Context, center: Vec2, radius: float32) = diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index fcd7e61..b5cb207 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -437,7 +437,7 @@ proc parseSvgFont*(buf: string): Typeface = proc textUber( target: Image | Mask, arrangement: Arrangement, - transform: Vec2 | Mat3 = vec2(0, 0), + transform = mat3(), strokeWidth = 1.0, lineCap = lcButt, lineJoin = ljMiter, @@ -518,7 +518,7 @@ proc textUber( proc fillText*( target: Image | Mask, arrangement: Arrangement, - transform: Vec2 | Mat3 = vec2(0, 0) + transform = mat3() ) {.inline.} = ## Fills the text arrangement. textUber( @@ -531,7 +531,7 @@ proc fillText*( target: Image | Mask, font: Font, text: string, - transform: Vec2 | Mat3 = vec2(0, 0), + transform = mat3(), bounds = vec2(0, 0), hAlign = haLeft, vAlign = vaTop @@ -546,7 +546,7 @@ proc fillText*( proc strokeText*( target: Image | Mask, arrangement: Arrangement, - transform: Vec2 | Mat3 = vec2(0, 0), + transform = mat3(), strokeWidth = 1.0, lineCap = lcButt, lineJoin = ljMiter, @@ -570,7 +570,7 @@ proc strokeText*( target: Image | Mask, font: Font, text: string, - transform: Vec2 | Mat3 = vec2(0, 0), + transform = mat3(), strokeWidth = 1.0, bounds = vec2(0, 0), hAlign = haLeft, diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 8eec6d4..07cfdb5 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -126,6 +126,41 @@ proc fill*(image: Image, color: SomeColor) {.inline.} = ## Fills the image with the parameter color. fillUnsafe(image.data, color, 0, image.data.len) +proc isOneColor(image: Image, color: ColorRGBX): bool = + ## Checks if the entire image is color. + result = true + + let color = image.getRgbaUnsafe(0, 0) + + var i: int + when defined(amd64) and not defined(pixieNoSimd): + let colorVec = mm_set1_epi32(cast[int32](color)) + for j in countup(0, image.data.len - 16, 16): + let + values0 = mm_loadu_si128(image.data[j].addr) + values1 = mm_loadu_si128(image.data[j + 4].addr) + values2 = mm_loadu_si128(image.data[j + 8].addr) + values3 = mm_loadu_si128(image.data[j + 12].addr) + values01 = mm_or_si128(values0, values1) + values23 = mm_or_si128(values2, values3) + values = mm_or_si128(values01, values23) + mask = mm_movemask_epi8(mm_cmpeq_epi8(values, colorVec)) + if mask != uint16.high.int: + return false + i += 16 + + for j in i ..< image.data.len: + if image.data[j] != color: + return false + +proc isOneColor*(image: Image): bool = + ## Checks if the entire image is the same color. + image.isOneColor(image.getRgbaUnsafe(0, 0)) + +proc isTransparent*(image: Image): bool = + ## Checks if this image is fully transparent or not. + image.isOneColor(rgbx(0, 0, 0, 0)) + proc flipHorizontal*(image: Image) = ## Flips the image around the Y axis. let w = image.width div 2 @@ -170,30 +205,6 @@ proc subImage*(image: Image, x, y, w, h: int): Image = w * 4 ) -proc superImage*(image: Image, x, y, w, h: int): Image = - ## Either cuts a sub image or returns a super image with padded transparency. - if x >= 0 and x + w <= image.width and y >= 0 and y + h <= image.height: - result = image.subImage(x, y, w, h) - elif abs(x) >= image.width or abs(y) >= image.height: - # Nothing to copy, just an empty new image - result = newImage(w, h) - else: - let - readOffsetX = max(x, 0) - readOffsetY = max(y, 0) - writeOffsetX = max(0 - x, 0) - writeOffsetY = max(0 - y, 0) - copyWidth = max(min(image.width, w) - abs(x), 0) - copyHeight = max(min(image.height, h) - abs(y), 0) - - result = newImage(w, h) - for y2 in 0 ..< copyHeight: - copyMem( - result.data[result.dataIndex(writeOffsetX, writeOffsetY + y2)].addr, - image.data[image.dataIndex(readOffsetX, readOffsetY + y2)].addr, - copyWidth * 4 - ) - proc diff*(master, image: Image): (float32, Image) = ## Compares the parameters and returns a score and image of the difference. let @@ -815,7 +826,7 @@ proc drawUber(a, b: Image | Mask, mat = mat3(), blendMode = bmNormal) = zeroMem(a.data[a.dataIndex(xMax, y)].addr, 4 * (a.width - xMax)) proc draw*( - a, b: Image, transform: Mat3 = mat3(), blendMode = bmNormal + a, b: Image, transform = mat3(), blendMode = bmNormal ) {.inline.} = ## Draws one image onto another using matrix with color blending. when type(transform) is Vec2: @@ -824,7 +835,7 @@ proc draw*( a.drawUber(b, transform, blendMode) proc draw*( - a, b: Mask, transform: Mat3 = mat3(), blendMode = bmMask + a, b: Mask, transform = mat3(), blendMode = bmMask ) {.inline.} = ## Draws a mask onto a mask using a matrix with color blending. when type(transform) is Vec2: @@ -833,7 +844,7 @@ proc draw*( a.drawUber(b, transform, blendMode) proc draw*( - image: Image, mask: Mask, transform: Mat3 = mat3(), blendMode = bmMask + image: Image, mask: Mask, transform = mat3(), blendMode = bmMask ) {.inline.} = ## Draws a mask onto an image using a matrix with color blending. when type(transform) is Vec2: @@ -842,7 +853,7 @@ proc draw*( image.drawUber(mask, transform, blendMode) proc draw*( - mask: Mask, image: Image, transform: Mat3 = mat3(), blendMode = bmMask + mask: Mask, image: Image, transform = mat3(), blendMode = bmMask ) {.inline.} = ## Draws a image onto a mask using a matrix with color blending. when type(transform) is Vec2: @@ -882,5 +893,13 @@ proc shadow*( result.fill(color) result.draw(shifted, blendMode = bmMask) +proc superImage*(image: Image, x, y, w, h: int): Image = + ## Either cuts a sub image or returns a super image with padded transparency. + if x >= 0 and x + w <= image.width and y >= 0 and y + h <= image.height: + result = image.subImage(x, y, w, h) + else: + result = newImage(w, h) + result.draw(image, translate(vec2(-x.float32, -y.float32)), bmOverwrite) + when defined(release): {.pop.} diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index e424eee..1c85246 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -17,13 +17,13 @@ type ## Line join type for strokes. ljMiter, ljRound, ljBevel - PathCommandKind* = enum + PathCommandKind = enum ## Type of path commands Close, Move, Line, HLine, VLine, Cubic, SCubic, Quad, TQuad, Arc, RMove, RLine, RHLine, RVLine, RCubic, RSCubic, RQuad, RTQuad, RArc - PathCommand* = object + PathCommand = object ## Binary version of an SVG command. kind: PathCommandKind numbers: seq[float32] @@ -50,15 +50,12 @@ proc newPath*(): Path = ## Create a new Path. Path() -proc pixelScale(transform: Vec2 | Mat3): float32 = +proc pixelScale(transform: Mat3): float32 = ## What is the largest scale factor of this transform? - when type(transform) is Vec2: - return 1.0 - else: - max( - vec2(transform[0, 0], transform[0, 1]).length, - vec2(transform[1, 0], transform[1, 1]).length - ) + max( + vec2(transform[0, 0], transform[0, 1]).length, + vec2(transform[1, 0], transform[1, 1]).length + ) proc isRelative(kind: PathCommandKind): bool = kind in { @@ -507,12 +504,6 @@ proc rect*(path: Path, x, y, w, h: float32, clockwise = true) = path.lineTo(x + w, y) path.closePath() -proc rect*(path: Path, pos: Vec2, wh: Vec2, clockwise = true) {.inline.} = - ## Adds a rectangle. - ## Clockwise param can be used to subtract a rect from a path when using - ## even-odd winding rule. - path.rect(pos.x, pos.y, wh.x, wh.y, clockwise) - proc rect*(path: Path, rect: Rect, clockwise = true) {.inline.} = ## Adds a rectangle. ## Clockwise param can be used to subtract a rect from a path when using @@ -590,14 +581,6 @@ proc roundedRect*( path.closePath() -proc roundedRect*( - path: Path, pos, wh: Vec2, nw, ne, se, sw: float32, clockwise = true -) {.inline.} = - ## Adds a rounded rectangle. - ## Clockwise param can be used to subtract a rect from a path when using - ## even-odd winding rule. - path.roundedRect(pos.x, pos.y, wh.x, wh.y, nw, ne, se, sw, clockwise) - proc roundedRect*( path: Path, rect: Rect, nw, ne, se, sw: float32, clockwise = true ) {.inline.} = @@ -627,10 +610,6 @@ proc circle*(path: Path, cx, cy, r: float32) {.inline.} = ## Adds a circle. path.ellipse(cx, cy, r, r) -proc circle*(path: Path, center: Vec2, r: float32) {.inline.} = - ## Adds a circle. - path.ellipse(center.x, center.y, r, r) - proc circle*(path: Path, circle: Circle) {.inline.} = ## Adds a circle. path.ellipse(circle.pos.x, circle.pos.y, circle.radius, circle.radius) @@ -648,7 +627,7 @@ proc polygon*(path: Path, pos: Vec2, size: float32, sides: int) {.inline.} = ## Adds a n-sided regular polygon at (x, y) with the parameter size. path.polygon(pos.x, pos.y, size, sides) -proc commandsToShapes*( +proc commandsToShapes( path: Path, closeSubpaths = false, pixelScale: float32 = 1.0 ): seq[seq[Vec2]] = ## Converts SVG-like commands to sequences of vectors. @@ -1750,22 +1729,16 @@ proc parseSomePath( elif type(path) is Path: path.commandsToShapes(closeSubpaths, pixelScale) -proc transform(shapes: var seq[seq[Vec2]], transform: Vec2 | Mat3) = - when type(transform) is Vec2: - if transform != vec2(): - for shape in shapes.mitems: - for segment in shape.mitems: - segment += transform - else: - if transform != mat3(): - for shape in shapes.mitems: - for segment in shape.mitems: - segment = transform * segment +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*( mask: Mask, path: SomePath, - transform: Vec2 | Mat3 = vec2(), + transform = mat3(), windingRule = wrNonZero, blendMode = bmNormal ) = @@ -1778,7 +1751,7 @@ proc fillPath*( image: Image, path: SomePath, paint: Paint, - transform: Vec2 | Mat3 = vec2(), + transform = mat3(), windingRule = wrNonZero ) = ## Fills a path. @@ -1827,7 +1800,7 @@ proc fillPath*( proc strokePath*( mask: Mask, path: SomePath, - transform: Vec2 | Mat3 = vec2(), + transform = mat3(), strokeWidth = 1.0, lineCap = lcButt, lineJoin = ljMiter, @@ -1851,7 +1824,7 @@ proc strokePath*( image: Image, path: SomePath, paint: Paint, - transform: Vec2 | Mat3 = vec2(), + transform = mat3(), strokeWidth = 1.0, lineCap = lcButt, lineJoin = ljMiter, @@ -1948,7 +1921,7 @@ proc overlaps( proc fillOverlaps*( path: Path, test: Vec2, - transform: Vec2 | Mat3 = vec2(), ## Applied to the path, not the test point. + transform = mat3(), ## Applied to the path, not the test point. windingRule = wrNonZero ): bool = ## Returns whether or not the specified point is contained in the current path. @@ -1959,7 +1932,7 @@ proc fillOverlaps*( proc strokeOverlaps*( path: Path, test: Vec2, - transform: Vec2 | Mat3 = vec2(), ## Applied to the path, not the test point. + transform = mat3(), ## Applied to the path, not the test point. strokeWidth = 1.0, lineCap = lcButt, lineJoin = ljMiter, diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim index 6f761d7..428fbd0 100644 --- a/tests/benchmark_images.nim +++ b/tests/benchmark_images.nim @@ -17,6 +17,14 @@ timeIt "fill_rgba": image.fill(rgba(63, 127, 191, 191)) doAssert image[0, 0] == rgba(63, 127, 191, 191) +image.fill(rgba(100, 0, 100, 100)) +timeIt "isOneColor": + doAssert image.isOneColor() + +image.fill(rgba(0, 0, 0, 0)) +timeIt "isTransparent": + doAssert image.isTransparent() + reset() timeIt "subImage": diff --git a/tests/images/superimage5.png b/tests/images/superimage5.png index 4ed0c8e..0c4423a 100644 Binary files a/tests/images/superimage5.png and b/tests/images/superimage5.png differ diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim index 11241ca..3ef471a 100644 --- a/tests/test_fonts.nim +++ b/tests/test_fonts.nim @@ -154,7 +154,7 @@ block: let image = newImage(200, 100) image.fill(rgba(255, 255, 255, 255)) image.fillText(font, "First line") - image.fillText(font, "Second line", vec2(0, font.defaultLineHeight)) + image.fillText(font, "Second line", translate(vec2(0, font.defaultLineHeight))) doDiff(image, "basic7") @@ -826,7 +826,7 @@ block: let arrangement = typeset(spans, bounds = vec2(360, 360)) - image.fillText(arrangement, vec2(20, 20)) + image.fillText(arrangement, translate(vec2(20, 20))) doDiff(image, "spans5") @@ -965,7 +965,7 @@ block: let arrangement = typeset(spans, bounds = vec2(360, 360)) - image.fillText(arrangement, vec2(20, 20)) + image.fillText(arrangement, translate(vec2(20, 20))) doDiff(image, "spans6") diff --git a/tests/test_images.nim b/tests/test_images.nim index 13b3da0..1b052a0 100644 --- a/tests/test_images.nim +++ b/tests/test_images.nim @@ -164,3 +164,36 @@ block: image.fillPath(p, rgba(255, 0, 0, 255)) newImage(newMask(image)).writeFile("tests/images/mask2image.png") + +block: + let image = newImage(100, 100) + doAssert image.isOneColor() + +block: + let image = newImage(100, 100) + image.fill(rgba(255, 255, 255, 255)) + doAssert image.isOneColor() + +block: + let image = newImage(100, 100) + image.fill(rgba(1, 2, 3, 4)) + doAssert image.isOneColor() + +block: + let image = newImage(100, 100) + image[99, 99] = rgba(255, 255, 255, 255) + doAssert not image.isOneColor() + +block: + let image = newImage(100, 100) + doAssert image.isTransparent() + +block: + let image = newImage(100, 100) + image.fill(rgba(255, 255, 255, 0)) + doAssert image.isTransparent() + +block: + let image = newImage(100, 100) + image[99, 99] = rgba(255, 255, 255, 255) + doAssert not image.isTransparent() diff --git a/tests/test_paths.nim b/tests/test_paths.nim index 51c37f0..36269d6 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -213,7 +213,9 @@ block: image = newImage(60, 60) path = parsePath("M 3 3 L 20 3 L 20 20 L 3 20 Z") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcRound, ljRound) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcRound, ljRound + ) image.writeFile("tests/images/paths/boxRound.png") @@ -222,7 +224,9 @@ block: image = newImage(60, 60) path = parsePath("M 3 3 L 20 3 L 20 20 L 3 20 Z") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcRound, ljBevel) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcRound, ljBevel + ) image.writeFile("tests/images/paths/boxBevel.png") @@ -231,7 +235,9 @@ block: image = newImage(60, 60) path = parsePath("M 3 3 L 20 3 L 20 20 L 3 20 Z") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcRound, ljMiter) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcRound, ljMiter + ) image.writeFile("tests/images/paths/boxMiter.png") @@ -240,7 +246,9 @@ block: image = newImage(60, 60) path = parsePath("M 3 3 L 20 3 L 20 20 L 3 20") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcButt, ljBevel) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcButt, ljBevel + ) image.writeFile("tests/images/paths/lcButt.png") @@ -249,7 +257,9 @@ block: image = newImage(60, 60) path = parsePath("M 3 3 L 20 3 L 20 20 L 3 20") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcRound, ljBevel) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcRound, ljBevel + ) image.writeFile("tests/images/paths/lcRound.png") @@ -258,7 +268,9 @@ block: image = newImage(60, 60) path = parsePath("M 3 3 L 20 3 L 20 20 L 3 20") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcSquare, ljBevel) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcSquare, ljBevel + ) image.writeFile("tests/images/paths/lcSquare.png") @@ -269,31 +281,31 @@ block: image.fill(rgba(255, 255, 255, 255)) image.strokePath( - path, rgba(0, 0, 0, 255), vec2(5, 5), 10, lcButt, ljBevel, + path, rgba(0, 0, 0, 255), translate(vec2(5, 5)), 10, lcButt, ljBevel, ) image.strokePath( - path, rgba(0, 0, 0, 255), vec2(5, 25), 10, lcButt, ljBevel, + path, rgba(0, 0, 0, 255), translate(vec2(5, 25)), 10, lcButt, ljBevel, dashes = @[2.float32, 2] ) image.strokePath( - path, rgba(0, 0, 0, 255), vec2(5, 45), 10, lcButt, ljBevel, + path, rgba(0, 0, 0, 255), translate(vec2(5, 45)), 10, lcButt, ljBevel, dashes = @[4.float32, 4] ) image.strokePath( - path, rgba(0, 0, 0, 255), vec2(5, 65), 10, lcButt, ljBevel, + path, rgba(0, 0, 0, 255), translate(vec2(5, 65)), 10, lcButt, ljBevel, dashes = @[2.float32, 4, 6, 2] ) image.strokePath( - path, rgba(0, 0, 0, 255), vec2(5, 85), 10, lcButt, ljBevel, + path, rgba(0, 0, 0, 255), translate(vec2(5, 85)), 10, lcButt, ljBevel, dashes = @[1.float32] ) image.strokePath( - path, rgba(0, 0, 0, 255), vec2(5, 105), 10, lcButt, ljBevel, + path, rgba(0, 0, 0, 255), translate(vec2(5, 105)), 10, lcButt, ljBevel, dashes = @[1.float32, 2, 3, 4, 5, 6, 7, 8, 9] ) @@ -311,7 +323,7 @@ block: path.lineTo(sin(th)*20, cos(th)*20) image.strokePath( - path, rgba(0, 0, 0, 255), vec2(30, 30), 8, lcButt, ljMiter, + path, rgba(0, 0, 0, 255), translate(vec2(30, 30)), 8, lcButt, ljMiter, miterLimit = limit ) image.writeFile(&"tests/images/paths/miterLimit_{angle.int}deg_{limit:0.2f}num.png") @@ -340,28 +352,36 @@ block: image = newImage(60, 60) path = parsePath("M 3 3 L 3 3 L 3 3") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcSquare, ljMiter) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcSquare, ljMiter + ) block: let image = newImage(60, 60) path = parsePath("L 0 0 L 0 0") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcSquare, ljMiter) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcSquare, ljMiter + ) block: let image = newImage(60, 60) path = parsePath("L 1 1") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcSquare, ljMiter) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcSquare, ljMiter + ) block: let image = newImage(60, 60) path = parsePath("L 0 0") image.fill(rgba(255, 255, 255, 255)) - image.strokePath(path, rgba(0, 0, 0, 255), vec2(10, 10), 10, lcSquare, ljMiter) + image.strokePath( + path, rgba(0, 0, 0, 255), translate(vec2(10, 10)), 10, lcSquare, ljMiter + ) block: let image = newImage(100, 100)