From 0d65dffcccc5f2260a9d95675644c11f5beca23c Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Wed, 19 May 2021 00:04:39 -0500 Subject: [PATCH 1/2] add context api --- src/pixie.nim | 9 +- src/pixie/context.nim | 231 ++++++++++++++ src/pixie/demo.nim | 1 + src/pixie/fileformats/svg.nim | 2 +- src/pixie/paints.nim | 11 + src/pixie/paths.nim | 104 ++++--- tests/images/context/beginPath_1.png | Bin 0 -> 1119 bytes tests/images/context/bezierCurveTo_1.png | Bin 0 -> 2420 bytes tests/images/context/bezierCurveTo_2.png | Bin 0 -> 2732 bytes tests/images/context/clearRect_1.png | Bin 0 -> 1892 bytes tests/images/context/closePath_1.png | Bin 0 -> 2673 bytes tests/images/context/ellipse_1.png | Bin 0 -> 3709 bytes tests/images/context/fillText_1.png | Bin 0 -> 5555 bytes tests/images/context/fill_1.png | Bin 0 -> 3585 bytes tests/images/context/moveTo_1.png | Bin 0 -> 1152 bytes tests/images/context/quadracticCurveTo_1.png | Bin 0 -> 2303 bytes tests/images/context/quadracticCurveTo_2.png | Bin 0 -> 2730 bytes tests/images/context/resetTransform_1.png | Bin 0 -> 1179 bytes tests/images/context/resetTransform_2.png | Bin 0 -> 1976 bytes tests/images/context/rotate_1.png | Bin 0 -> 1241 bytes tests/images/context/save_1.png | Bin 0 -> 1043 bytes tests/images/context/scale_1.png | Bin 0 -> 956 bytes tests/images/context/setTransform_1.png | Bin 0 -> 1832 bytes tests/images/context/strokeRect_1.png | Bin 0 -> 1008 bytes tests/images/context/strokeRect_2.png | Bin 0 -> 1308 bytes tests/images/context/strokeText_1.png | Bin 0 -> 7055 bytes tests/images/context/stroke_1.png | Bin 0 -> 987 bytes tests/images/context/stroke_2.png | Bin 0 -> 968 bytes tests/images/context/stroke_3.png | Bin 0 -> 1968 bytes tests/images/context/translate_1.png | Bin 0 -> 1104 bytes tests/test_context.nim | 312 +++++++++++++++++++ tests/test_masks.nim | 36 +-- tests/test_paths.nim | 64 ++-- 33 files changed, 664 insertions(+), 106 deletions(-) create mode 100644 src/pixie/context.nim create mode 100644 tests/images/context/beginPath_1.png create mode 100644 tests/images/context/bezierCurveTo_1.png create mode 100644 tests/images/context/bezierCurveTo_2.png create mode 100644 tests/images/context/clearRect_1.png create mode 100644 tests/images/context/closePath_1.png create mode 100644 tests/images/context/ellipse_1.png create mode 100644 tests/images/context/fillText_1.png create mode 100644 tests/images/context/fill_1.png create mode 100644 tests/images/context/moveTo_1.png create mode 100644 tests/images/context/quadracticCurveTo_1.png create mode 100644 tests/images/context/quadracticCurveTo_2.png create mode 100644 tests/images/context/resetTransform_1.png create mode 100644 tests/images/context/resetTransform_2.png create mode 100644 tests/images/context/rotate_1.png create mode 100644 tests/images/context/save_1.png create mode 100644 tests/images/context/scale_1.png create mode 100644 tests/images/context/setTransform_1.png create mode 100644 tests/images/context/strokeRect_1.png create mode 100644 tests/images/context/strokeRect_2.png create mode 100644 tests/images/context/strokeText_1.png create mode 100644 tests/images/context/stroke_1.png create mode 100644 tests/images/context/stroke_2.png create mode 100644 tests/images/context/stroke_3.png create mode 100644 tests/images/context/translate_1.png create mode 100644 tests/test_context.nim diff --git a/src/pixie.nim b/src/pixie.nim index baca69f..f1eb8c2 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -1,9 +1,10 @@ import bumpy, chroma, flatty/binny, os, pixie/blends, pixie/common, - pixie/fileformats/bmp, pixie/fileformats/gif, pixie/fileformats/jpg, - pixie/fileformats/png, pixie/fileformats/svg, pixie/fonts, pixie/images, - pixie/masks, pixie/paints, pixie/paths, strutils, vmath + pixie/context, pixie/fileformats/bmp, pixie/fileformats/gif, + pixie/fileformats/jpg, pixie/fileformats/png, pixie/fileformats/svg, + pixie/fonts, pixie/images, pixie/masks, pixie/paints, pixie/paths, strutils, vmath -export blends, bumpy, chroma, common, fonts, images, masks, paints, paths, vmath +export blends, bumpy, chroma, common, context, fonts, images, masks, paints, + paths, vmath type FileFormat* = enum diff --git a/src/pixie/context.nim b/src/pixie/context.nim new file mode 100644 index 0000000..5e7d5ad --- /dev/null +++ b/src/pixie/context.nim @@ -0,0 +1,231 @@ +import bumpy, chroma, pixie/blends, pixie/common, pixie/fonts, pixie/images, + pixie/paints, pixie/paths, vmath + +# https://developer.mozilla.org/en-US/docs/Web/API/ContextRenderingContext2D + +type + Context* = ref object + image*: Image + fillStyle*, strokeStyle*: Paint + lineWidth*: float32 + lineCap*: LineCap + lineJoin*: LineJoin + font*: Font + textAlign*: HAlignMode + path: Path + mat: Mat3 + stateStack: seq[ContextState] + + ContextState = object + mat: Mat3 + fillStyle, strokeStyle: Paint + lineWidth: float32 + lineCap: LineCap + lineJoin: LineJoin + font: Font + textAlign: HAlignMode + +proc newContext*(image: Image): Context = + result = Context() + result.image = image + result.mat = mat3() + result.lineWidth = 1 + result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) + result.strokeStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) + +proc beginPath*(ctx: Context) {.inline.} = + ctx.path = Path() + +proc moveTo*(ctx: Context, v: Vec2) {.inline.} = + ctx.path.moveTo(v) + +proc moveTo*(ctx: Context, x, y: float32) {.inline.} = + ctx.moveTo(vec2(x, y)) + +proc lineTo*(ctx: Context, v: Vec2) {.inline.} = + ctx.path.lineTo(v) + +proc lineTo*(ctx: Context, x, y: float32) {.inline.} = + ctx.lineTo(vec2(x, y)) + +proc bezierCurveTo*(ctx: Context, cp1, cp2, to: Vec2) {.inline.} = + ctx.path.bezierCurveTo(cp1, cp2, to) + +proc bezierCurveTo*( + ctx: Context, cp1x, cp1y, cp2x, cp2y, x, y: float32 +) {.inline.} = + ctx.bezierCurveTo(vec2(cp1x, cp1y), vec2(cp2x, cp2y), vec2(x, y)) + +proc quadraticCurveTo*(ctx: Context, cpx, cpy, x, y: float32) {.inline.} = + ctx.path.quadraticCurveTo(cpx, cpy, x, y) + +proc quadraticCurveTo*(ctx: Context, ctrl, to: Vec2) {.inline.} = + ctx.path.quadraticCurveTo(ctrl, to) + +proc closePath*(ctx: Context) {.inline.} = + ctx.path.closePath() + +proc rect*(ctx: Context, rect: Rect) {.inline.} = + ctx.path.rect(rect) + +proc rect*(ctx: Context, x, y, width, height: float32) {.inline.} = + ctx.path.rect(x, y, width, height) + +proc ellipse*(ctx: Context, center: Vec2, rx, ry: float32) {.inline.} = + ctx.path.ellipse(center, rx, ry) + +proc ellipse*(ctx: Context, x, y, rx, ry: float32) {.inline.} = + ctx.path.ellipse(x, y, rx, ry) + +proc fill*(ctx: Context, path: Path, windingRule = wrNonZero) {.inline.} = + ctx.image.fillPath( + path, + ctx.fillStyle, + ctx.mat, + windingRule = windingRule + ) + +proc fill*(ctx: Context, windingRule = wrNonZero) {.inline.} = + ctx.fill(ctx.path, windingRule) + +proc stroke*(ctx: Context, path: Path) {.inline.} = + ctx.image.strokePath( + path, + ctx.strokeStyle, + ctx.mat, + strokeWidth = ctx.lineWidth, + lineCap = ctx.lineCap, + lineJoin = ctx.lineJoin + ) + +proc stroke*(ctx: Context) {.inline.} = + ctx.stroke(ctx.path) + +proc clearRect*(ctx: Context, rect: Rect) = + var path: Path + path.rect(rect) + ctx.image.fillPath(path, rgbx(0, 0, 0, 0), ctx.mat, blendMode = bmOverwrite) + +proc clearRect*(ctx: Context, x, y, width, height: float32) {.inline.} = + ctx.clearRect(rect(x, y, width, height)) + +proc fillRect*(ctx: Context, rect: Rect) = + var path: Path + path.rect(rect) + ctx.image.fillPath(path, ctx.fillStyle, ctx.mat) + +proc fillRect*(ctx: Context, x, y, width, height: float32) {.inline.} = + ctx.fillRect(rect(x, y, width, height)) + +proc strokeRect*(ctx: Context, rect: Rect) = + var path: Path + path.rect(rect) + ctx.image.strokePath( + path, + ctx.strokeStyle, + ctx.mat, + ctx.lineWidth, + ctx.lineCap, + ctx.lineJoin + ) + +proc strokeRect*(ctx: Context, x, y, width, height: float32) {.inline.} = + ctx.strokeRect(rect(x, y, width, height)) + +proc fillText*(ctx: Context, text: string, at: Vec2) = + if ctx.font.typeface == nil: + raise newException(PixieError, "No font has been set on this Context") + + # Canvas positions text relative to the alphabetic baseline by default + var at = at + at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) + + ctx.font.paint = ctx.fillStyle + ctx.image.fillText( + ctx.font, + text, + at, + hAlign = ctx.textAlign + ) + +proc fillText*(ctx: Context, text: string, x, y: float32) {.inline.} = + ctx.fillText(text, vec2(x, y)) + +proc strokeText*(ctx: Context, text: string, at: Vec2) = + if ctx.font.typeface == nil: + raise newException(PixieError, "No font has been set on this Context") + + # Canvas positions text relative to the alphabetic baseline by default + var at = at + at.y -= round(ctx.font.typeface.ascent * ctx.font.scale) + + ctx.font.paint = ctx.strokeStyle + ctx.image.strokeText( + ctx.font, + text, + at, + ctx.lineWidth, + hAlign = ctx.textAlign, + lineCap = ctx.lineCap, + lineJoin = ctx.lineJoin + ) + +proc strokeText*(ctx: Context, text: string, x, y: float32) {.inline.} = + ctx.strokeText(text, vec2(x, y)) + +proc getTransform*(ctx: Context): Mat3 {.inline.} = + ctx.mat + +proc setTransform*(ctx: Context, transform: Mat3) {.inline.} = + ctx.mat = transform + +proc setTransform*(ctx: Context, a, b, c, d, e, f: float32) {.inline.} = + ctx.mat = mat3(a, b, 0, c, d, 0, e, f, 1) + +proc transform*(ctx: Context, transform: Mat3) {.inline.} = + ctx.mat = ctx.mat * transform + +proc transform*(ctx: Context, a, b, c, d, e, f: float32) {.inline.} = + ctx.transform(mat3(a, b, 0, c, d, 0, e, f, 1)) + +proc translate*(ctx: Context, v: Vec2) {.inline.} = + ctx.mat = ctx.mat * translate(v) + +proc translate*(ctx: Context, x, y: float32) {.inline.} = + ctx.mat = ctx.mat * translate(vec2(x, y)) + +proc scale*(ctx: Context, v: Vec2) {.inline.} = + ctx.mat = ctx.mat * scale(v) + +proc scale*(ctx: Context, x, y: float32) {.inline.} = + ctx.mat = ctx.mat * scale(vec2(x, y)) + +proc rotate*(ctx: Context, angle: float32) {.inline.} = + ctx.mat = ctx.mat * rotate(-angle) + +proc resetTransform*(ctx: Context) {.inline.} = + ctx.mat = mat3() + +proc save*(ctx: Context) = + var state: ContextState + state.mat = ctx.mat + state.fillStyle = ctx.fillStyle + state.strokeStyle = ctx.strokeStyle + state.lineWidth = ctx.lineWidth + state.lineCap = ctx.lineCap + state.lineJoin = ctx.lineJoin + state.font = ctx.font + state.textAlign = ctx.textAlign + ctx.stateStack.add(state) + +proc restore*(ctx: Context) = + if ctx.stateStack.len > 0: + let state = ctx.stateStack.pop() + ctx.mat = state.mat + ctx.fillStyle = state.fillStyle + ctx.strokeStyle = state.strokeStyle + ctx.lineWidth = state.lineWidth + ctx.lineCap = state.lineCap + ctx.lineJoin = state.lineJoin + ctx.font = state.font + ctx.textAlign = state.textAlign diff --git a/src/pixie/demo.nim b/src/pixie/demo.nim index db157b6..0ed888b 100644 --- a/src/pixie/demo.nim +++ b/src/pixie/demo.nim @@ -1,4 +1,5 @@ import opengl, pixie, staticglfw + export pixie, staticglfw type Image = pixie.Image diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index a01d02e..666b6e8 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -364,7 +364,7 @@ proc decodeSvg*(data: string, width = 0, height = 0): Image = viewBox = root.attr("viewBox") box = viewBox.split(" ") viewBoxMinX = parseInt(box[0]) - viewBoxMinY= parseInt(box[1]) + viewBoxMinY = parseInt(box[1]) viewBoxWidth = parseInt(box[2]) viewBoxHeight = parseInt(box[3]) diff --git a/src/pixie/paints.nim b/src/pixie/paints.nim index 2449b5b..59bf7c4 100644 --- a/src/pixie/paints.nim +++ b/src/pixie/paints.nim @@ -27,6 +27,17 @@ type color*: ColorRGBX ## Color of the stop position*: float32 ## Gradient Stop position 0..1. + SomePaint* = string | Paint | SomeColor + +converter parseSomePaint*(paint: SomePaint): Paint {.inline.} = + ## Given SomePaint, parse it in different ways. + when type(paint) is string: + Paint(kind: pkSolid, color: parseHtmlColor(paint).rgba()) + elif type(paint) is SomeColor: + Paint(kind: pkSolid, color: paint.rgba()) + elif type(paint) is Paint: + paint + proc toLineSpace(at, to, point: Vec2): float32 {.inline.} = ## Convert position on to where it would fall on a line between at and to. let diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 618a2c0..8ee9ac6 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -360,64 +360,66 @@ proc quadraticCurveTo*(path: var Path, ctrl, to: Vec2) {.inline.} = ## path.quadraticCurveTo(ctrl.x, ctrl.y, to.x, to.y) -proc arcTo*(path: var Path, ctrl1, ctrl2: Vec2, radius: float32) {.inline.} = - ## Adds a circular arc to the current sub-path, using the given control - ## points and radius. +# proc arcTo*(path: var Path, ctrl1, ctrl2: Vec2, radius: float32) {.inline.} = +# ## Adds a circular arc to the current sub-path, using the given control +# ## points and radius. - const epsilon = 1e-6.float32 +# const epsilon = 1e-6.float32 - var radius = radius - if radius < 0: - radius = -radius +# var radius = radius +# if radius < 0: +# radius = -radius - if path.commands.len == 0: - path.moveTo(ctrl1) +# if path.commands.len == 0: +# path.moveTo(ctrl1) - let - a = path.at - ctrl1 - b = ctrl2 - ctrl1 +# let +# a = path.at - ctrl1 +# b = ctrl2 - ctrl1 - if a.lengthSq() < epsilon: - # If the control point is coincident with at, do nothing - discard - elif abs(a.y * b.x - a.x * b.y) < epsilon or radius == 0: - # If ctrl1, a and b are colinear or coincident or radius is zero - path.lineTo(ctrl1) - else: - let - c = ctrl2 - path.at - als = a.lengthSq() - bls = b.lengthSq() - cls = c.lengthSq() - al = a.length() - bl = b.length() - l = radius * tan((PI - arccos((als + bls - cls) / 2 * al * bl)) / 2) - ta = l / al - tb = l / bl +# if a.lengthSq() < epsilon: +# # If the control point is coincident with at, do nothing +# discard +# elif abs(a.y * b.x - a.x * b.y) < epsilon or radius == 0: +# # If ctrl1, a and b are colinear or coincident or radius is zero +# path.lineTo(ctrl1) +# else: +# let +# c = ctrl2 - path.at +# als = a.lengthSq() +# bls = b.lengthSq() +# cls = c.lengthSq() +# al = a.length() +# bl = b.length() +# l = radius * tan((PI - arccos((als + bls - cls) / 2 * al * bl)) / 2) +# ta = l / al +# tb = l / bl - if abs(ta - 1) > epsilon: - # If the start tangent is not coincident with path.at - path.lineTo(ctrl1 + a * ta) +# if abs(ta - 1) > epsilon: +# # If the start tangent is not coincident with path.at +# path.lineTo(ctrl1 + a * ta) - let to = ctrl1 + b * tb - path.commands.add(PathCommand( - kind: Arc, - numbers: @[ - radius, - radius, - 0, - 0, - if a.y * c.x > a.x * c.y: 1 else: 0, - to.x, - to.y - ] - )) - path.at = to +# echo "INSIDE ", (als + bls - cls) / 2 * al * bl, " ", arccos((als + bls - cls) / 2 * al * bl) -proc arcTo*(path: var Path, x1, y1, x2, y2, radius: float32) {.inline.} = - ## Adds a circular arc to the current sub-path, using the given control - ## points and radius. - path.arcTo(vec2(x1, y1), vec2(x2, y2), radius) +# let to = ctrl1 + b * tb +# path.commands.add(PathCommand( +# kind: Arc, +# numbers: @[ +# radius, +# radius, +# 0, +# 0, +# if a.y * c.x > a.x * c.y: 1 else: 0, +# to.x, +# to.y +# ] +# )) +# path.at = to + +# proc arcTo*(path: var Path, x1, y1, x2, y2, radius: float32) {.inline.} = +# ## Adds a circular arc to the current sub-path, using the given control +# ## points and radius. +# path.arcTo(vec2(x1, y1), vec2(x2, y2), radius) proc ellipticalArcTo*( path: var Path, @@ -1499,7 +1501,7 @@ proc fillPath*( ) = ## Fills a path. if paint.kind == pkSolid: - image.fillPath(path, paint.color, transform) + image.fillPath(path, paint.color, transform, windingRule) return let diff --git a/tests/images/context/beginPath_1.png b/tests/images/context/beginPath_1.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2e81c97e4b4da18433cdaca08a7a096264dfb4 GIT binary patch literal 1119 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 K21xy&lYaqIKUWX{ literal 0 HcmV?d00001 diff --git a/tests/images/context/bezierCurveTo_2.png b/tests/images/context/bezierCurveTo_2.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4f518075ebfb04d67e75ecf693ee7c83098bcc GIT binary patch literal 2732 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 literal 0 HcmV?d00001 diff --git a/tests/images/context/fillText_1.png b/tests/images/context/fillText_1.png new file mode 100644 index 0000000000000000000000000000000000000000..2801423966b3e69699d3c4d8d2d63185fde0f08e GIT binary patch literal 5555 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 literal 0 HcmV?d00001 diff --git a/tests/images/context/quadracticCurveTo_1.png b/tests/images/context/quadracticCurveTo_1.png new file mode 100644 index 0000000000000000000000000000000000000000..a45e568a1c0ebb6c17837bf0a83efdf880eec44c GIT binary patch literal 2303 zcmb_edo)yw8sD?`*yAz7>E%@!S0(ZqoIIwCLKreK%(RJVGD)cH!;zH73_U1PWKhni zi)zMe4&_ystr%x{b{(AZ$gXqH5v3fD6{u66$See3r<*6;i0TkH3w9bmap zRCQGW0H844UDyCXFkR(hWq36nJ@l{_0F)I>m%V;x2-72!GI7YC*awS(=c6AF4-UZw zA>h)`RP)}l?P84pBcE?FZ6OtTAz9a?`0yU{xz(h-1AfEf7t_iXLdO(s(u{wdFb?4UR z>l@Y5A;u<=N#=;H-r?6a05USsQ{d;!QRP|>fZi=_QTSdylUPQKOmwMeITiieDM*u$ z>4v`~Pf_A&uTSfuYiNc~xID2sX+BvYh$Tq~Ha5}1tOjl^e>!zy3)*3T#=7DaX?0!c z{~mHW9$)b|eTwv0%?`AK^EOzxbrg=Ok)eyg7g8BG@ct(eGe#gk+Z}DGYEo2l&LI5Y zzP|1YiZ|0QwZkZ|I@k!rYvpN2hlPC4bsBKw9TVGG4-81Kxq-(9fbmum^G$`C!|yORwKU!Zy*IL##6U^PA&^ z&OX^ZKuowIG!cbi0&GZViJjmR<)j(E7B3kwC1(`ohonF8z|2#91_+oR-+=ZFqID#@ z*9z(P1?QE~U9@$J8|1bq9W|71oH3F&oV`~evqt(CwxYJEF-k&dG&Q^3;|=W{9bWxS zWEq&kvG9h}1ix{{pRiyC%HWHZiq)5S5&38KGV)%o^0lac_snlEgdZ%FfXj*5W_uO zDW#;`IND!Lc*L?cMv(rvSZsw1imPrm4!`r&ye-}*efhEIYe_Py7%ad39kr;>3ij+W zDjnYrb%>g5W|i2HG$h=$ek9b4_K(vU*8-mKrT$}@lB$1oJ!D~}9J2T(hucP7yUY2; zt6Kfs$>iUg}$Bd$ojfXXUy9nmi@I=bI(*@)TH*~vW?_( z%>rL)t7Cf7J1!KZ;wD4qZubqXPK-fVE9Ud#ibuF{N=e0((lDG3>rGDXO@LMCkCg{6 z*8*3B#+Y}_$GHCpeyd!!4gorcKW4K{bWCGxh;?dpn-*CU-tN;FKdd?yydwG?Td?_3 z)W;%=FK2?bU}(!5k8n@ea}?-Sas_M8+4psmxe3#%ol5GkI>hZ;U>m8WaJ)&1staS7 z5q{w;4-OH?CrhX^W_U2(fnC5Zo(%#+j>#tl-Z+eJewKH%sfn?AF*IZjU5ip^8oEh| zQoVcSGE1btMgw(_JI+Xv2K|x{xh!l?CnpH*gjukpd|WDHk-2l^%z9bXiC6CU)yW_J zIoZzVxaIpbt+z9CJs1jEa3}uQTAM3P4iZKx%y!m-MWPapon@HkRe98|9frLf0WyK` zmT0ctn>l&2x&3#2JODp{-#~x{`h?WI%NnVlQp<>4Y2VH9FX08xAr-#YP8T${-6MXj4*2E4FGoDekH`m~ZVW#6x>S_pw8d}CHDvTtHr)~_pEEpu z!Y9zx&l~zRSnGgoUv|wC{zC=!+$}+U#ul&3s|@JB`Sf}e4M~VFqHS1Y%%J=4<{n1E z@ArYlKVN^B8hzH52Iaxapf)tDT-FswXGdt4z1#`b5f|yG15F(vJ|2GL$dwtU^w093 zbU2Y1zbOw&CnZvrA5A0`_)+1~i77aK4FXK5?7w=w|GkjnuplU2vO;!6Dx>N%&itL| z4%%jdoxL3H+ewQK#N+D`aSXB`_H|X((s)d!{0?|J#nADU(u{fE+BMXQqW~lpF35s;L+*q!Gj~oF)qWxzyVec^N%!aKVL^KO(-;E z?%SNqIhAei;OhG{ILxWO_Pr)vAGK!d*nxij9LZH7HlKi!z{d4OrI$o=_7Xkj4;(Wj zGP7P9b*1wF-u~S96&KI%-fnJOC?l4ZF)Vy`bgWIJ+Ug%Bn#SBCO6jt-WmP?XI(u`b zgEZ;3+c>_QWX`hEb7VzddQ-OEl(`{PzP04Yf5CNq#GUQz_G?qt4dLJ5)BoG+W!sC> n=~j9ViqBdhS-rIX1B)69)MF9(mAgt-9~*$VkL6O~#83VUV75Mu literal 0 HcmV?d00001 diff --git a/tests/images/context/quadracticCurveTo_2.png b/tests/images/context/quadracticCurveTo_2.png new file mode 100644 index 0000000000000000000000000000000000000000..32daddcf64f4e96b7ab950ca9472b118272c0fc1 GIT binary patch literal 2730 zcmbtW`Bzit7QIPG!jJ?gCCsSUh+rgyAx4>z!59RL43RMq0SQrpGAN2BR;$dFCqqS` z@)(0aphOT*E)-B1M1f$W2zgZsWu8H4@!fX4f1vCA@~v;5v+mt{?X&N?C)v~86|1PN z2mk<pfp4VZJMse_>#t;*fsR zne93Aok~0QT?z9w-1uw3{wV)r^!>y5&c~j~ex8wn9zRgUv0a3}V_6)QT)e-6sNT+3 z-)|Mz_hkc}FxPh}-bg)dM>qB;eXUzyr{j z^j&L*M$yW}^ub(U1C!os?U3+RH`D^UrmsrcAHt%2lIl`P;+mir<4j3@N*&yh` za^_3@*=m!Th~-fz?K6hq$b{op0uR;(FONQlaM%?VMO6ONjp%R|ZM`mhbb*S$)9uP@ zP=p@MWOgil<7H)50fWG8GPnTBvm$L$yzEdp!`Fi0uYh>^c0-j0go4N~^sA(sFnw#pwEXiWT?8ZzxsM1l%_ z(TJMk-_9;4Abm0Ozrz6Fg}ZM&A!kLswvm2nSs9*Fx+w98ff2;x9Pk#10*}eWBSrPr z5Orb{@Du+apM=SaWDQ~j{S2_CY?XUK$+Msq0q5Q68X2+a-cef2L9jDw>I~}$Pm|XU7P>?` z2gK5%`zfF`ON|vRd)BzY!`>4R)`PF=FLkbw^CLkfi}qz#O*^E1=dvB|ph)}^e;!Iq zlE0Fs@&k0ATn4OgetQo`9YLQ7B*N2uyt&AWyLt4*1c|4qsN_&;=12f;4yJq8Hk4f@ zL}7@(W9wQRiKp(s)_0YQ64%o6CFfB&PU>FerFstRPICKGlR0zdP4WGEH)i%! zY@BZEIk7R*3o;Bx<%5)kXu(O(d6DeC@V>cdnD!V^W^ZY$e$e<^V{=r_k@jnmSV>cZ zMTiAYTs6GPW>tETZR0Gwdew@<)RpX%(F;HjQ9M{`F7nEfI*}*?+xk4pl7%UXsCeHf($BRPqM+#R}skkj8)32I# zQIUD~;8C=QfRtB~IWWckKDrMIZ4J5*pwQamta%g>Y3ZcQ1{?*Iz|?|mwkK&EshbBk za^zf;*~*+`E#OsI@Kb9aBqyF#%^@#E$Z)dbGFLE`6~Z}}q_Smfiliz?l{Im#8kby~ z2d?`hG1O<@gZhnvXxyE6ZYk=LSZ09Lym?}+U|RT5ew64}WrpF^NCIrq}3W;_*}hmi~{C4g<{IG?zi zy6`H}0Q}3b>Uw{E6r*>x&*G_G2!8YQ!G5o6w%A|0seWns^VqA{tuy!Kw(J!f(tk{R z@Nk3~Fpvopxd(36mJ61f7CsEFr`46a4(Yhv^cllfms<&L^QUy|#KSFPh$>PY&MbDdI}oKHhf=#pJCF{WrbK4V6et9pU#uTGM#3$R0S z?WdB|_w6OWQ)S<>f)~-$_K;6$d`KoQ=~b7W)pM@0M7<0?(W?|*o-^MWn_f$Q`{$x( z+?!H|1BXRBMe3DRpLm>y?{VH0U5=g87hEd3hCg{nezxxRZa!+-VI1^Gl^fI9qO{cp@4TjEpK5|a|F%?hi<9C0!qEFt^WglLC(0`;EQ9gCKn4mS8Qr|hpjm>Aj z4`@g+xKliL+riRZ^Pwe1_``Ucqa;1m2FY%>ZS@FB00oah9{;wjbY*1|6fv)eA4Pl;m9mI2k^ z=sDHqH{Ka5UH{=X*0Wf&9%yGU(m~~va5b4(_0SLUpA`xej9dc`0 XHQrS`{Jjmdg90>Xcc&Uh){XxHKI+;x literal 0 HcmV?d00001 diff --git a/tests/images/context/resetTransform_1.png b/tests/images/context/resetTransform_1.png new file mode 100644 index 0000000000000000000000000000000000000000..bc1a96f0a37d4d2f16db5405d006a5fcfd60cb49 GIT binary patch literal 1179 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 Oox#)9&t;ucLK6V|h>36j literal 0 HcmV?d00001 diff --git a/tests/images/context/scale_1.png b/tests/images/context/scale_1.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1d6d850fabeb7ba092c0b3d4130ad1b0d0ed71 GIT binary patch literal 956 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!r*W_W$&~1=mlznBn>}3|Ln;{GUJL9}aujgA z$YTEN(y6&SH_N?w|Iyds^2I$bi#xu!YieEo^yn?a6ak$DZ3&E~EZi#;BOG`SGG%dy z1vqbLkZNMR0+c#t5plh=_Rjn7ukTjb&F`NT%RKei!KzlRw*ea(-m78JfX6Jb)N5&( z2hn$yh3~!pKK$34&p&IHd}rL}1+?#R!rp(5OG7t5mj6_E?s)0B)W3gumKTa#NU?S z=V9f?m#+IPb+2;Hbq6&vqvX>s$EfFTC3R|frwHRsgapzQS5qm2-CetQMlO`3OF$Qn y1dl~m-E!vMyk2wOKYoxrfkg={yFjHrviC?-eGXoBnGu*P89ZJ6T-G@yGywpIlV{HW literal 0 HcmV?d00001 diff --git a/tests/images/context/setTransform_1.png b/tests/images/context/setTransform_1.png new file mode 100644 index 0000000000000000000000000000000000000000..69bc03918190170f263adf2d39b50e8b962d017d GIT binary patch literal 1832 zcmbVNYgAHc6y+g^NE#KU=A=dTQWPRh$F#{QiFC4M282a7%Sz4E%*jG$c&O1RHQ8h@ z!&+gDib5IbG-g1U6`9jRDa*XrN>`zQQ>H!C!Rz(M{G9o5?>g)I&e`Xjz4u-B(AqVO zNqBcW0)d#sT(u$wfk67&;$j@u_TCn!euO|^EzA|mW3!Nb9hZ|fzHvL)Z!c%%3p8iF ze%OOS9Zye4GrdG!it_VH%y8r*pGWdBy1ezh7pdiYk#UjW%Os2`^r+D%?#lM=`p)}< zGRr4n0lm?5U$}p7_vXSu!O!SX)QFu2qEs+rPC1>Z7)6aDMzCK=L-VZrK>vuSnCRJ@ zT1^Nz$=ds_tBYxF+4pc!e9`sET6{mrinh8uJu-bJpr6zCqzB0J0m;^HomkuSb5fcu zVv?V*N0aWc>yCt()MW&f4$I%)u=4{Bh>qu9kphj~e>-KNm(F$~b8}9_UKL2VJfME=6JZyD;5C{*IJpZ{9D=oL>wJouCHs`SQ4-Zp7;U+tuAYIR% zT;t>)NeU2GY9R6oY7KBP_v$8l_&R=IvM&V4Rm_A)3DzYaJ>UQXHojQ^cXlbOohUAx zDSkZ;_;9OAM9%hOWKqJET0GQwJ+EaV;#wvmRvr*MHVk_M3GPuTUkF_$Nv*3??B2N> zQ7yt(@y3qIx+6wv2fLi#Dk8l^`wK1v{k6-cBAE2?l>GKWtl1^Yz8)zHXjn#mYR^ON z&@|&xIhpGTEXltmpbjtp^?GaoXI6tQ+m6>H++Lu3IG5#Fd(>Wwu(id?^}hk1Fci2V>E^u)iJ$}r$Hbs&n2=J^`t^u$jr)LCDMDC&EQMTJ}HSBl4 zyzf9+1WPnEUmrW~@XW%LAeY>_!4F7KjrfOgJO#DX9B^?9G7VLt`(VF(^%hkFE8i&ux4V6BUg+ZShV+KAx&rT%i42 z49QvSU!+6-|OxLqadhMY5l&Z$g?3Z$-zR>&L1c0${M z@og#CO$8m?q8`)E?KvhPi6c?B^9TP-xKu~fR#IiD4)^*q#cO>2*%F^EvyaH&bs zhgJ&cC#$2;G!B>TOe|>bvkSuPHs+u!48Ve0ezUuA%e=&8tZPIXV@T`jo3gGz60CF9 z1Bu4&i2KB!ys(j>+$Hgctzs&tqN`joj2t3cOBbfDGSfK~y=IA9WgeYVbdUPKpRM5) pJC$84Bi)Z_i(>Qt!*hES;hVV#|8UM_iR}-FV6I%VqB*?-qqs44WxCJazjiCHbf%X7Usbn{?|@Jf z>l(#~15#M2=WMT^|E!z$Y`NvLn-VrUe+~*c^mpH)1ICGnOZmZc8vi@{=tm(@ABs*r0#XyaO}_r zf&YIKZ|yfYxX5|KCzfu$gQtXbtXQ=DH@py7V?&nU8gm1iV}{57w&#B?+CQn-_G_mO zPk+jP>KIO;+jPoi_!pch{C_1k?p=7%y5)8gTOUq*6F)EEqGCj4lWTjzQ%*72rm5-? zB^+S`AXvLB@_E%OS^45S``?@Cf7jll{(JgBxdkI6@&(t(Kbw*Ne8%(Qw0X~gDM`32 z;aO%{-Td2ocs#EM&wrKqZ{F!Yrr<=#X#L?fFhOpwv@Ko#^S*_7#DBe0xpori9}dV0 ztlm)E%=)2?g*(D|LxU+5seL}o(y`|EOg^Lv=_K$TWV$9mnEJuKQ7Sy_+&?xaU`}T6 MboFyt=akR{0D9Z0j{pDw literal 0 HcmV?d00001 diff --git a/tests/images/context/strokeRect_2.png b/tests/images/context/strokeRect_2.png new file mode 100644 index 0000000000000000000000000000000000000000..5df7000a452e2c2c8d9c23245c1f377272a94908 GIT binary patch literal 1308 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!r*W_W$&~1=mlzmW9(%ethEy=Vy=9*rb6oED zL&=qV7EkXT+QIqq?Uzj#wzd7}Wyz~N+iM^nZKqf3eqg)0~5bX$ySczD(L#r5CkT#w6l@ z*VX2z<1VxA{kFZh>UqLJA#FG1sIM|A5lKf^xU8PGm;JTgu~mUy@BhZlzIFL%(bqp| zo9lwJK9`?)Y4`s7!_C{j-@X#}ZSuc_`S;oFre<8-usxwu^lHboAK%2*96kDKcHO1< zef2CK4*t&gTe>+izW#^C;v4B3&Q-mCzW9zt&6bt1oY!WsaBt(hwgDH}#&gZY*WNey zcipU8+@~eq7%{Kiy7K$Kr+({g4oY2{a4;+Q_s8k^{&f|5BliQnr)RujM?~$$&&j4u zrfUM5pIS=ToqY62cpkIJnnX^qi7eWa69hds7*T_1y5)9c%o>niw(owmcV+C|oNL$L z-%nOp{`*#UyZdV7`@c2I*V}JhJ^Sph zs^!@WO+Sl!ZRmI+o;C=a2@NVOfi-N~x9y0S`}N-W{Mv^(S>fihzkdCrmTY`U;Zh5s_lK#YVK=3TUI!&$-0rfA#Kso7e`k0?Pbqa zi|X-OlV#}v63RHTYF;xdNC>FPDq}<1f0yi8xAHGviHkV*K*IP!_3!=v*{&6TNI3f} zWAD8+@sZ~qL>L>Ke_+#nrD#e5;~S?94Mr^78qR3cyJc*mbHnbK6qs`oU9$Y)Z8&(N V=zrv|y}(kB!PC{xWt~$(698ZNEr0+3 literal 0 HcmV?d00001 diff --git a/tests/images/context/strokeText_1.png b/tests/images/context/strokeText_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8be48e3961528f16711e46a29705afe2a5a02c86 GIT binary patch literal 7055 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 literal 0 HcmV?d00001 diff --git a/tests/images/context/stroke_1.png b/tests/images/context/stroke_1.png new file mode 100644 index 0000000000000000000000000000000000000000..06b8b27c275bc6c5bc7655b0e67a3164e81e1cda GIT binary patch literal 987 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!r*W_W$&~1=mlznB7kRokhEy=Vy=u5BBvYjI zBD1NOY{Lxu;>u&T6Xv=1pFA~xXURIg1AAsq;!dkFzfxRWqHA$yL0#9Q=XciYF>h!| zV9e$a+t7xMI(9Q*@7$UD)1O`3(QN(xK)vzj_p{8~R&74`D?2Uj+?n4qf9BjdIJfM< z)ic`@Vpr!gX3ychcKA%qteN#`&rSlhygktU*jG@;&$FpCdE<`dx8)!9?tLV3aFOSR z4h}KjCRdjY8A3WrO|Fv@1O;_wv2=GNTvUm0Vd+*osH7BO!YL+5Q0Z$)o^)Fo_Vvew z>t1|1?7x3=gbjN&70e~U3HX#={+YmgCdM!B(BZ;&RW=Xh-iQ+R5@xP% zdt>&Lq#GYhYDw5QCBe{XLxk4`2}9e6zcsmkzi$N@&8UA@5-9NRp@wsf*9nG$NxBD< z8WJ{6OE7fU5aGQ+!oW6S`~KBim#=c4_bnuK+uH^EL-lXG*z)nW8PHF;|I*~89#FW^P@K!0JK;_^q*9{LmnznW) z9Gu9KJ^kmwUH_eYwliD*_{4T2wQd8$zD{z22g#?PbjZT8n&F?t-=ACp4v^$XY%rlF zVw}3Z&fpT7D3z$P0JYD@<);T3K0RYTvcPan? literal 0 HcmV?d00001 diff --git a/tests/images/context/stroke_3.png b/tests/images/context/stroke_3.png new file mode 100644 index 0000000000000000000000000000000000000000..3f809d83e08ef78d8aa371ff73adec32c0706955 GIT binary patch literal 1968 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!r*W_W$&~1=mlzn>%ROBjLn;{G-oBW1_Zbsw zqUin|-zRU+IkQts{SwdJ-Y`@7{dtpmTc>_(YWP~Ja`(zK1n8zlfUheq(`^P`t z{kkUogH_*k_0{WEr+@vj|L=m>hX<8b+Ah9R%r87 zZ=h)NSD!7b*|eu$*sy}_^~{WL-Y~s|iGjS=j4!T}S`*`Rc#+ha^hL3z5j$JjTumd+ zyX?-|P{hgGowZ?JXW7++r$SOh@2)`}Ozy*2=5tf$|o|;LmnUlGpNSZh7oJqvaUN-G?sJ4E& zHTHe;%N_6SKYQov|M{(*M}Yc{ZSmN>>+NdG_Y+T93U@jlUQ}h8K0k9J&~?4W6D2@l zb}Kz<2{0fY`>3#i{QqsHSY|lyH4Cs}sWr9BT!BGqdd?*Y6q4D4U8^7a>mR$X-~X?% z|Kj!?-(OeEf4y&A{g>n4KibU?@3}t1gGWj%b=Rti)rPv4fa#!6O{f_Z{l9&>fr%x1 zPUn#YpfqB4SqYe;q|%iPK?!N=d0-G`Z8$a)r1@&XTjNo!6bIA$fsGA z?FDn=i7w!XD;LXU)_mvpxc-y-)BkJ6Mj7FI|I7ha2vaJ5TK=YaH2|$&(ydlp2bMUW zYxnriYb*QeDj$$&E6m%yYBjLR0aaZKURT^P{_&+h*w=Rb%J2Iv-*WMXP5Pt98#YOj c7+lY|xHB+BB%V(iSQ9gNy85}Sb4q9e054{uPyhe` literal 0 HcmV?d00001 diff --git a/tests/images/context/translate_1.png b/tests/images/context/translate_1.png new file mode 100644 index 0000000000000000000000000000000000000000..efeecdeb557ae3da489e6ea3995735338139647d GIT binary patch literal 1104 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!r*W_W$&~1=mlzmW#64XcLn;{G-Zkt~@)U8s zIOT?fck|qho23ljZaX@=Z`SS7zPcSBIH!bH+>z@)e*JG*N>Nf zh>hQEA9R-^?DE>3W=&fK*Sy}fPW$<^+cWBI*M0x6^7&V}EEa9A4Q8OQ4g|@o9qbZX z(+i4=DG9kCk1t}$RtCm}sA_~XD4H}5-r@j8lPl*nH()dk0nkiY4Cpr4!*-@ U*XGn+29^{Ip00i_>zopr07xIb*#H0l literal 0 HcmV?d00001 diff --git a/tests/test_context.nim b/tests/test_context.nim new file mode 100644 index 0000000..6777b97 --- /dev/null +++ b/tests/test_context.nim @@ -0,0 +1,312 @@ +import chroma, pixie + +block: + let ctx = newContext(newImage(300, 160)) + + ctx.beginPath() + ctx.fillStyle = "#ff6" + ctx.fillRect(0, 0, ctx.image.width.float32, ctx.image.height.float32) + + ctx.beginPath() + ctx.fillStyle = "blue" + ctx.moveTo(20, 20) + ctx.lineTo(180, 20) + ctx.lineTo(130, 130) + ctx.closePath() + ctx.fill() + + ctx.clearRect(10, 10, 120, 100) + + ctx.image.writeFile("tests/images/context/clearRect_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.beginPath() + ctx.strokeStyle = "blue" + ctx.moveTo(20, 20) + ctx.lineTo(200, 20) + ctx.stroke() + + ctx.beginPath() + ctx.strokeStyle = "green" + ctx.moveTo(20, 20) + ctx.lineTo(120, 120) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/beginPath_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.beginPath() + ctx.moveTo(50, 50) + ctx.lineTo(200, 50) + ctx.moveTo(50, 90) + ctx.lineTo(280, 120) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/moveTo_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + var region: Path + region.moveTo(30, 90) + region.lineTo(110, 20) + region.lineTo(240, 130) + region.lineTo(60, 130) + region.lineTo(190, 20) + region.lineTo(270, 90) + region.closePath() + + ctx.fillStyle = "green" + ctx.fill(region, wrEvenOdd) + + ctx.image.writeFile("tests/images/context/fill_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.rect(10, 10, 150, 100) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/stroke_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.lineWidth = 26 + ctx.strokeStyle = "orange" + ctx.moveTo(20, 20) + ctx.lineTo(160, 20) + ctx.stroke() + + ctx.lineWidth = 14 + ctx.strokeStyle = "green" + ctx.moveTo(20, 80) + ctx.lineTo(220, 80) + ctx.stroke() + + ctx.lineWidth = 4 + ctx.strokeStyle = "pink" + ctx.moveTo(20, 140) + ctx.lineTo(280, 140) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/stroke_2.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.lineWidth = 26 + ctx.strokeStyle = "red" + + ctx.beginPath() + ctx.rect(25, 25, 100, 100) + ctx.fill() + ctx.stroke() + + ctx.beginPath() + ctx.rect(175, 25, 100, 100) + ctx.stroke() + ctx.fill() + + ctx.image.writeFile("tests/images/context/stroke_3.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.beginPath() + ctx.moveTo(20, 140) + ctx.lineTo(120, 10) + ctx.lineTo(220, 140) + ctx.closePath() + ctx.stroke() + + ctx.image.writeFile("tests/images/context/closePath_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + let + start = vec2(50, 20) + cp1 = vec2(230, 30) + cp2 = vec2(150, 80) + to = vec2(250, 100) + + ctx.beginPath() + ctx.moveTo(start) + ctx.bezierCurveTo(cp1, cp2, to) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/bezierCurveTo_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.beginPath() + ctx.moveTo(30, 30) + ctx.bezierCurveTo(120, 160, 180, 10, 220, 140) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/bezierCurveTo_2.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.moveTo(50, 20) + ctx.quadraticCurveTo(230, 30, 50, 100) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/quadracticCurveTo_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.beginPath() + ctx.moveTo(20, 110) + ctx.quadraticCurveTo(230, 150, 250, 20) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/quadracticCurveTo_2.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.beginPath() + ctx.ellipse(100, 75, 75, 50) + ctx.stroke() + + ctx.image.writeFile("tests/images/context/ellipse_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.strokeStyle = "green" + ctx.strokeRect(20, 10, 160, 100) + + ctx.image.writeFile("tests/images/context/strokeRect_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.lineJoin = ljBevel + ctx.lineWidth = 15 + ctx.strokeStyle = "#38f" + ctx.strokeRect(30, 30, 160, 90) + + ctx.image.writeFile("tests/images/context/strokeRect_2.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.setTransform(1, 0.2, 0.8, 1, 0, 0) + ctx.fillRect(0, 0, 100, 100) + + ctx.image.writeFile("tests/images/context/setTransform_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.setTransform(1, 0.2, 0.8, 1, 0, 0) + ctx.fillRect(0, 0, 100, 100) + + ctx.image.writeFile("tests/images/context/resetTransform_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.rotate(45 * PI / 180) + ctx.fillRect(60, 0, 100, 30) + + ctx.image.writeFile("tests/images/context/resetTransform_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.transform(1, 0, 1.7, 1, 0, 0) + ctx.fillStyle = "gray" + ctx.fillRect(40, 40, 50, 20) + ctx.fillRect(40, 90, 50, 20) + + ctx.resetTransform() + ctx.fillStyle = "red" + ctx.fillRect(40, 40, 50, 20) + ctx.fillRect(40, 90, 50, 20) + + ctx.image.writeFile("tests/images/context/resetTransform_2.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.translate(110, 30) + ctx.fillStyle = "red" + ctx.fillRect(0, 0, 80, 80) + + ctx.setTransform(1, 0, 0, 1, 0, 0) + + ctx.fillStyle = "gray" + ctx.fillRect(0, 0, 80, 80) + + ctx.image.writeFile("tests/images/context/translate_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.scale(9, 3) + ctx.fillStyle = "red" + ctx.fillRect(10, 10, 8, 20) + + ctx.setTransform(1, 0, 0, 1, 0, 0) + + ctx.fillStyle = "gray" + ctx.fillRect(10, 10, 8, 20) + + ctx.image.writeFile("tests/images/context/scale_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.fillStyle = "gray" + ctx.fillRect(100, 0, 80, 20) + + ctx.rotate(45 * PI / 180) + ctx.fillStyle = "red" + ctx.fillRect(100, 0, 80, 20) + + ctx.image.writeFile("tests/images/context/rotate_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") + ctx.font.size = 50 + + ctx.fillText("Hello world", 50, 90) + + ctx.image.writeFile("tests/images/context/fillText_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.font = readFont("tests/fonts/Roboto-Regular_1.ttf") + ctx.font.size = 50 + + ctx.strokeText("Hello world", 50, 90) + + ctx.image.writeFile("tests/images/context/strokeText_1.png") + +block: + let ctx = newContext(newImage(300, 150)) + + ctx.save() + + ctx.fillStyle = "green" + ctx.fillRect(10, 10, 100, 100) + + ctx.restore() + + ctx.fillRect(150, 40, 100, 100) + + ctx.image.writeFile("tests/images/context/save_1.png") diff --git a/tests/test_masks.nim b/tests/test_masks.nim index ca0817e..e936e13 100644 --- a/tests/test_masks.nim +++ b/tests/test_masks.nim @@ -13,27 +13,27 @@ block: mask.invert() doAssert mask[0, 0] == 55 -block: - let - mask = newMask(100, 100) - r = 10.0 - x = 10.0 - y = 10.0 - h = 80.0 - w = 80.0 - var path: Path - path.moveTo(x + r, y) - path.arcTo(x + w, y, x + w, y + h, r) - path.arcTo(x + w, y + h, x, y + h, r) - path.arcTo(x, y + h, x, y, r) - path.arcTo(x, y, x + w, y, r) - mask.fillPath(path) +# block: +# let +# mask = newMask(100, 100) +# r = 10.0 +# x = 10.0 +# y = 10.0 +# h = 80.0 +# w = 80.0 +# var path: Path +# path.moveTo(x + r, y) +# path.arcTo(x + w, y, x + w, y + h, r) +# path.arcTo(x + w, y + h, x, y + h, r) +# path.arcTo(x, y + h, x, y, r) +# path.arcTo(x, y, x + w, y, r) +# mask.fillPath(path) - let minified = mask.minifyBy2() +# let minified = mask.minifyBy2() - doAssert minified.width == 50 and minified.height == 50 +# doAssert minified.width == 50 and minified.height == 50 - writeFile("tests/images/masks/maskMinified.png", minified.encodePng()) +# writeFile("tests/images/masks/maskMinified.png", minified.encodePng()) block: let image = newImage(100, 100) diff --git a/tests/test_paths.nim b/tests/test_paths.nim index 63ee51e..1dfa970 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -149,22 +149,22 @@ block: ) image.writeFile("tests/images/paths/pathCornerArc.png") -block: - let - image = newImage(100, 100) - r = 10.0 - x = 10.0 - y = 10.0 - h = 80.0 - w = 80.0 - var path: Path - path.moveTo(x + r, y) - path.arcTo(x + w, y, x + w, y + h, r) - path.arcTo(x + w, y + h, x, y + h, r) - path.arcTo(x, y + h, x, y, r) - path.arcTo(x, y, x + w, y, r) - image.fillPath(path, rgba(255, 0, 0, 255)) - image.writeFile("tests/images/paths/pathRoundRect.png") +# block: +# let +# image = newImage(100, 100) +# r = 10.0 +# x = 10.0 +# y = 10.0 +# h = 80.0 +# w = 80.0 +# var path: Path +# path.moveTo(x + r, y) +# path.arcTo(x + w, y, x + w, y + h, r) +# path.arcTo(x + w, y + h, x, y + h, r) +# path.arcTo(x, y + h, x, y, r) +# path.arcTo(x, y, x + w, y, r) +# image.fillPath(path, rgba(255, 0, 0, 255)) +# image.writeFile("tests/images/paths/pathRoundRect.png") block: let @@ -173,22 +173,22 @@ block: mask.fillPath(pathStr) writeFile("tests/images/paths/pathRectangleMask.png", mask.encodePng()) -block: - let - mask = newMask(100, 100) - r = 10.0 - x = 10.0 - y = 10.0 - h = 80.0 - w = 80.0 - var path: Path - path.moveTo(x + r, y) - path.arcTo(x + w, y, x + w, y + h, r) - path.arcTo(x + w, y + h, x, y + h, r) - path.arcTo(x, y + h, x, y, r) - path.arcTo(x, y, x + w, y, r) - mask.fillPath(path) - writeFile("tests/images/paths/pathRoundRectMask.png", mask.encodePng()) +# block: +# let +# mask = newMask(100, 100) +# r = 10.0 +# x = 10.0 +# y = 10.0 +# h = 80.0 +# w = 80.0 +# var path: Path +# path.moveTo(x + r, y) +# path.arcTo(x + w, y, x + w, y + h, r) +# path.arcTo(x + w, y + h, x, y + h, r) +# path.arcTo(x, y + h, x, y, r) +# path.arcTo(x, y, x + w, y, r) +# mask.fillPath(path) +# writeFile("tests/images/paths/pathRoundRectMask.png", mask.encodePng()) block: let image = newImage(200, 200) From bb0d44c23ecfed0e0a11962030f76ad395b98bc0 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Wed, 19 May 2021 00:38:12 -0500 Subject: [PATCH 2/2] feedback --- src/pixie/context.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pixie/context.nim b/src/pixie/context.nim index 5e7d5ad..a7a5d4b 100644 --- a/src/pixie/context.nim +++ b/src/pixie/context.nim @@ -33,6 +33,9 @@ proc newContext*(image: Image): Context = result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) result.strokeStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) +proc newContext*(width, height: int): Context {.inline.} = + newContext(newImage(width, height)) + proc beginPath*(ctx: Context) {.inline.} = ctx.path = Path() @@ -144,7 +147,7 @@ proc fillText*(ctx: Context, text: string, at: Vec2) = ctx.image.fillText( ctx.font, text, - at, + ctx.mat * translate(at), hAlign = ctx.textAlign ) @@ -163,7 +166,7 @@ proc strokeText*(ctx: Context, text: string, at: Vec2) = ctx.image.strokeText( ctx.font, text, - at, + ctx.mat * translate(at), ctx.lineWidth, hAlign = ctx.textAlign, lineCap = ctx.lineCap,