From 6fb1323101ee6b2987a46be45e12524a4d69c9fc Mon Sep 17 00:00:00 2001 From: treeform Date: Sun, 23 May 2021 18:46:19 -0700 Subject: [PATCH] Add dashes. (#202) * Add dashes. * Miter limits as ratio everywhere. * Remove some fill and stroke overloads. --- src/pixie/context.nim | 6 +- src/pixie/fileformats/svg.nim | 2 +- src/pixie/paints.nim | 5 +- src/pixie/paths.nim | 189 ++++++++---------- tests/images/paths/dashes.png | Bin 0 -> 824 bytes .../images/paths/miterLimit_10deg_2.00num.png | Bin 0 -> 597 bytes .../paths/miterLimit_145deg_2.00num.png | Bin 0 -> 625 bytes .../paths/miterLimit_145deg_3.32num.png | Bin 0 -> 625 bytes .../paths/miterLimit_145deg_3.33num.png | Bin 0 -> 642 bytes .../paths/miterLimit_155deg_2.00num.png | Bin 0 -> 557 bytes .../paths/miterLimit_165deg_10.00num.png | Bin 0 -> 700 bytes .../paths/miterLimit_165deg_2.00num.png | Bin 0 -> 531 bytes tests/test_paths.nim | 68 ++++++- 13 files changed, 153 insertions(+), 117 deletions(-) create mode 100644 tests/images/paths/dashes.png create mode 100644 tests/images/paths/miterLimit_10deg_2.00num.png create mode 100644 tests/images/paths/miterLimit_145deg_2.00num.png create mode 100644 tests/images/paths/miterLimit_145deg_3.32num.png create mode 100644 tests/images/paths/miterLimit_145deg_3.33num.png create mode 100644 tests/images/paths/miterLimit_155deg_2.00num.png create mode 100644 tests/images/paths/miterLimit_165deg_10.00num.png create mode 100644 tests/images/paths/miterLimit_165deg_2.00num.png diff --git a/src/pixie/context.nim b/src/pixie/context.nim index bf6a3cc..a081e57 100644 --- a/src/pixie/context.nim +++ b/src/pixie/context.nim @@ -193,7 +193,11 @@ proc clearRect*(ctx: Context, rect: Rect) = ## Erases the pixels in a rectangular area. var path: Path path.rect(rect) - ctx.image.fillPath(path, rgbx(0, 0, 0, 0), ctx.mat, blendMode = bmOverwrite) + ctx.image.fillPath( + path, + Paint(kind: pkSolid, color:rgbx(0, 0, 0, 0), blendMode: bmOverwrite), + ctx.mat + ) proc clearRect*(ctx: Context, x, y, width, height: float32) {.inline.} = ## Erases the pixels in a rectangular area. diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index 666b6e8..c460ed5 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -1,6 +1,6 @@ ## Load SVG files. -import chroma, pixie/common, pixie/images, pixie/paths, strutils, vmath, +import chroma, pixie/common, pixie/images, pixie/paths, pixie/paints, strutils, vmath, xmlparser, xmltree const diff --git a/src/pixie/paints.nim b/src/pixie/paints.nim index f3bc2a5..93ed8a7 100644 --- a/src/pixie/paints.nim +++ b/src/pixie/paints.nim @@ -34,7 +34,10 @@ converter parseSomePaint*(paint: SomePaint): Paint {.inline.} = when type(paint) is string: Paint(kind: pkSolid, color: parseHtmlColor(paint).rgbx()) elif type(paint) is SomeColor: - Paint(kind: pkSolid, color: paint.rgbx()) + when type(paint) is ColorRGBX: + Paint(kind: pkSolid, color: paint) + else: + Paint(kind: pkSolid, color: paint.rgbx()) elif type(paint) is Paint: paint diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 49b09c0..af95fd6 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -41,12 +41,15 @@ const epsilon = 0.0001 * PI ## Tiny value used for some computations. when defined(release): {.push checks: off.} -proc maxScale(m: Mat3): float32 = - ## What is the largest scale factor of this matrix? - max( - vec2(m[0, 0], m[0, 1]).length, - vec2(m[1, 0], m[1, 1]).length - ) +proc pixelScale(transform: Vec2 | 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 + ) proc isRelative(kind: PathCommandKind): bool = kind in { @@ -1317,16 +1320,27 @@ proc fillShapes( mask.setValueUnsafe(x, y, blended) inc x +proc miterLimitToAngle*(limit: float32): float32 = + ## Converts milter-limit-ratio to miter-limit-angle. + arcsin(1 / limit) * 2 + +proc angleToMiterLimit*(angle: float32): float32 = + ## Converts miter-limit-angle to milter-limit-ratio. + 1 / sin(angle / 2) + proc strokeShapes( shapes: seq[seq[Vec2]], strokeWidth: float32, lineCap: LineCap, lineJoin: LineJoin, - miterAngleLimit = degToRad(28.96) + miterLimit: float32, + dashes: seq[float32] ): seq[seq[Vec2]] = if strokeWidth == 0: return + let miterAngleLimit = miterLimitToAngle(miterLimit) + let halfStroke = strokeWidth / 2 proc makeCircle(at: Vec2): seq[Vec2] = @@ -1413,7 +1427,25 @@ proc strokeShapes( pos = shape[i] prevPos = shape[i - 1] - shapeStroke.add(makeRect(prevPos, pos)) + if dashes.len > 0: + var dashes = dashes + if dashes.len mod 2 != 0: + dashes.add(dashes[^1]) + var distance = dist(prevPos, pos) + let dir = dir(pos, prevPos) + var currPos = prevPos + block dashLoop: + while true: + for i, d in dashes: + if i mod 2 == 0: + let d = min(distance, d) + shapeStroke.add(makeRect(currPos, currPos + dir * d)) + currPos += dir * d + distance -= d + if distance <= 0: + break dashLoop + else: + shapeStroke.add(makeRect(prevPos, pos)) # If we need a line join if i < shape.len - 1: @@ -1459,53 +1491,14 @@ proc transform(shapes: var seq[seq[Vec2]], transform: Vec2 | Mat3) = for segment in shape.mitems: segment = transform * segment -proc fillPath*( - image: Image, - path: SomePath, - color: SomeColor, - windingRule = wrNonZero, - blendMode = bmNormal -) {.inline.} = - ## Fills a path. - image.fillShapes(parseSomePath(path), color, windingRule, blendMode) - -proc fillPath*( - image: Image, - path: SomePath, - color: SomeColor, - transform: Vec2 | Mat3, - windingRule = wrNonZero, - blendMode = bmNormal -) = - ## Fills a path. - when type(transform) is Mat3: - let pixelScale = transform.maxScale() - else: - let pixelScale = 1.0 - var shapes = parseSomePath(path, pixelScale) - shapes.transform(transform) - image.fillShapes(shapes, color, windingRule, blendMode) - proc fillPath*( mask: Mask, path: SomePath, - windingRule = wrNonZero -) {.inline.} = - ## Fills a path. - mask.fillShapes(parseSomePath(path), windingRule) - -proc fillPath*( - mask: Mask, - path: SomePath, - transform: Vec2 | Mat3, + transform: Vec2 | Mat3 = vec2(), windingRule = wrNonZero ) = ## Fills a path. - when type(transform) is Mat3: - let pixelScale = transform.maxScale() - else: - let pixelScale = 1.0 - var shapes = parseSomePath(path, pixelScale) + var shapes = parseSomePath(path, transform.pixelScale()) shapes.transform(transform) mask.fillShapes(shapes, windingRule) @@ -1518,7 +1511,9 @@ proc fillPath*( ) = ## Fills a path. if paint.kind == pkSolid: - image.fillPath(path, paint.color, transform, windingRule) + var shapes = parseSomePath(path, transform.pixelScale()) + shapes.transform(transform) + image.fillShapes(shapes, paint.color, windingRule, paint.blendMode) return let @@ -1545,69 +1540,23 @@ proc fillPath*( image.draw(fill, blendMode = paint.blendMode) proc strokePath*( - image: Image, + mask: Mask, path: SomePath, - color: SomeColor, + transform: Vec2 | Mat3 = vec2(), strokeWidth = 1.0, lineCap = lcButt, lineJoin = ljMiter, - blendMode = bmNormal + miterLimit: float32 = 4, + dashes: seq[float32] = @[], ) = ## Strokes a path. - let strokeShapes = strokeShapes( - parseSomePath(path), strokeWidth, lineCap, lineJoin - ) - image.fillShapes(strokeShapes, color, wrNonZero, blendMode) - -proc strokePath*( - image: Image, - path: SomePath, - color: SomeColor, - transform: Vec2 | Mat3, - strokeWidth = 1.0, - lineCap = lcButt, - lineJoin = ljMiter, - blendMode = bmNormal -) = - ## Strokes a path. - when type(transform) is Mat3: - let pixelScale = transform.maxScale() - else: - let pixelScale = 1.0 var strokeShapes = strokeShapes( - parseSomePath(path, pixelScale), strokeWidth, lineCap, lineJoin - ) - strokeShapes.transform(transform) - image.fillShapes(strokeShapes, color, wrNonZero, blendMode) - -proc strokePath*( - mask: Mask, - path: SomePath, - strokeWidth = 1.0, - lineCap = lcButt, - lineJoin = ljMiter -) = - ## Strokes a path. - let strokeShapes = strokeShapes( - parseSomePath(path), strokeWidth, lineCap, lineJoin - ) - mask.fillShapes(strokeShapes, wrNonZero) - -proc strokePath*( - mask: Mask, - path: SomePath, - transform: Vec2 | Mat3, - strokeWidth = 1.0, - lineCap = lcButt, - lineJoin = ljMiter -) = - ## Strokes a path. - when type(transform) is Mat3: - let pixelScale = transform.maxScale() - else: - let pixelScale = 1.0 - var strokeShapes = strokeShapes( - parseSomePath(path, pixelScale), strokeWidth, lineCap, lineJoin + parseSomePath(path, transform.pixelScale()), + strokeWidth, + lineCap, + lineJoin, + miterLimit, + dashes ) strokeShapes.transform(transform) mask.fillShapes(strokeShapes, wrNonZero) @@ -1619,20 +1568,38 @@ proc strokePath*( transform: Vec2 | Mat3 = vec2(), strokeWidth = 1.0, lineCap = lcButt, - lineJoin = ljMiter + lineJoin = ljMiter, + miterLimit: float32 = 4, + dashes: seq[float32] = @[] ) = - ## Fills a path. + ## Strokes a path. if paint.kind == pkSolid: - image.strokePath( - path, paint.color, transform, strokeWidth, lineCap, lineJoin + var strokeShapes = strokeShapes( + parseSomePath(path, transform.pixelScale()), + strokeWidth, + lineCap, + lineJoin, + miterLimit, + dashes ) + strokeShapes.transform(transform) + image.fillShapes( + strokeShapes, paint.color, wrNonZero, blendMode = paint.blendMode) return let mask = newMask(image.width, image.height) fill = newImage(image.width, image.height) - mask.strokePath(parseSomePath(path), transform) + mask.strokePath( + parseSomePath(path), + transform, + strokeWidth, + lineCap, + lineJoin, + miterLimit, + dashes + ) case paint.kind: of pkSolid: diff --git a/tests/images/paths/dashes.png b/tests/images/paths/dashes.png new file mode 100644 index 0000000000000000000000000000000000000000..5389bd8b8b89e0882f36c7e880f3a7dfc0d51e84 GIT binary patch literal 824 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j#!3HGPnNH+jU|{<1>Eakt!T5H@+f~wrJgmhp zyVd>Y8R;@V3i11O-HAEr>(kVwm$~!m_TGR0-MC_J-1*~=3-_8XwyigOtqkN&+&TIA z=lK`qtPR#2n!f4(68Ycef2RMH-Z$m1wB29c%`-W#U5v9)+sy2rE;{d=md)JI3ahn; z?CtIA|1lrT<<9HZetY0w{>jGter;=mJL@v)>QhDA?p%0V2&8U{o_)W5&7ld>yKL8O z;VOQ3fJI)o#^;0K!Q+?=t6PogXqNnIJ33Y9bo6Q6uYd9~*wapU$?aJ>?>;Zknneqi zOscDYmMF?*uCjAw^sAqDH*g)+#5wGYBY5$@I>Qpz5@TyyAhWb5+HT>i^LZ`@z=X$NZ(FB`vYEX) z_TlK9E3;DiOZFC7DbGK5YJ2$!EuNe!y$`!BSDK!bds+1qWarfU_Y=Y%R!PKrpSk|L u^VZ~l=9~6+|DRs=;N+VF(1-*1KHoOgc=d^XV3P54aSW+od^^>CMPZ>#yZt|X zwYfYwI_0R#x!dn~s_q{w zeK))4{r7+Kte%#<`ciwYc+Pq+5!a1hx&sWu|0wM+D6L3*_2JN!hpj6Qa|gGJFXPkq zk&T;SwFfNm=f$DdU!^X;6cOuY^;#Mfwpw0s&+WGcv(FmH@Lhaa60kC4g4fc3_17=H z|1Q|;rpR&Vys+I|zX%;MiC(uCWxH2g%{uY4NMrix7hkI+`j5{z|NKVY_8l>L;f~vH z^Ugk7X8XTqxoF(luo*sTK?~D1cRDR>@KI|n+pRk_#npT^Z{P99>7TtL)`nH=jeGgs zHicVTgzKf}=H-7o7GKRuG?4K4fADEh=ij>e{A;r%c-UN&CeFV8-))=0%8+JHv`{T!tnf0b~ zyDk3sE_Ys7#L}Qz&mUjEipuq~KP-s2budSKv+W!oH9;P>)RRX0<{GSiQeYv#!7{-^ zB|t~)Vus0*sYg?0n%v&mq_E-V{xg?bO08t?-MjbT`)|SSqf7ezwnn+G3~{oNYd`<| z@bBN&K5CP{nAvdMJd`uRUG8Vizh72XbN%-0+2gSOy8WrwYMWYD9>5wIXt6ZcepmXj V0M+aw8DMf@@O1TaS?83{1OS2$8ioJ> literal 0 HcmV?d00001 diff --git a/tests/images/paths/miterLimit_145deg_2.00num.png b/tests/images/paths/miterLimit_145deg_2.00num.png new file mode 100644 index 0000000000000000000000000000000000000000..5938bc258d94e77578cf20424b00a20855d89bb5 GIT binary patch literal 625 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$>^XV6yOZaSW+od^_!6cV(cAyZsC) z?yxhryb==)MO4kMtZ})vW{s$~^*;_*&zuQ+Z*(}7Ez-$Zl5=C@VU_${>#yX`s=Hh} zt1`U&-OBAB-#wn^cklZv70*dU^&GN=N9CP3KKE&yv0h~GzT?myWiAM3gV3+PcFQhn z%J8*MIzFSQ{Ndla{_We%r<_h*b3MDoNzumM-gxT5hj|ZEj5=#l(*c@Wnp66vu96D1mY^YX8+>Q#D} zw6Vi!p~KeWK)dp`cb`mYN-)qkZO_!zl4~Zt?Y3^(X34Mj!dHu~x~jD%O#4ll_1EJ4 ztxAbTWs5Fq%<)sN+AG(0T>0w06T8Aze-(J_J@Er?kk&+xgpCm=SThwLufCb{=(AW(KW?sHd(<~J$@s$$ z1LWc-da$rHKP-EFeW94zBzxYKbI+zNy#BiW*XsY=#))ggwEMM-TtbD@4*C20pGYw( z*nKza*Ct`LDPF2SpDtHB!&SXEZpr0L)7aD*oXdkWf7IAj3TGYinK9?2uiE5U>x`}( u+8OiCU0-YyOIBi>03xPPq798x!?5ezUb~Eo9ZJBY#Ng@b=d#Wzp$Pyp3@i}< literal 0 HcmV?d00001 diff --git a/tests/images/paths/miterLimit_145deg_3.32num.png b/tests/images/paths/miterLimit_145deg_3.32num.png new file mode 100644 index 0000000000000000000000000000000000000000..5938bc258d94e77578cf20424b00a20855d89bb5 GIT binary patch literal 625 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$>^XV6yOZaSW+od^_!6cV(cAyZsC) z?yxhryb==)MO4kMtZ})vW{s$~^*;_*&zuQ+Z*(}7Ez-$Zl5=C@VU_${>#yX`s=Hh} zt1`U&-OBAB-#wn^cklZv70*dU^&GN=N9CP3KKE&yv0h~GzT?myWiAM3gV3+PcFQhn z%J8*MIzFSQ{Ndla{_We%r<_h*b3MDoNzumM-gxT5hj|ZEj5=#l(*c@Wnp66vu96D1mY^YX8+>Q#D} zw6Vi!p~KeWK)dp`cb`mYN-)qkZO_!zl4~Zt?Y3^(X34Mj!dHu~x~jD%O#4ll_1EJ4 ztxAbTWs5Fq%<)sN+AG(0T>0w06T8Aze-(J_J@Er?kk&+xgpCm=SThwLufCb{=(AW(KW?sHd(<~J$@s$$ z1LWc-da$rHKP-EFeW94zBzxYKbI+zNy#BiW*XsY=#))ggwEMM-TtbD@4*C20pGYw( z*nKza*Ct`LDPF2SpDtHB!&SXEZpr0L)7aD*oXdkWf7IAj3TGYinK9?2uiE5U>x`}( u+8OiCU0-YyOIBi>03xPPq798x!?5ezUb~Eo9ZJBY#Ng@b=d#Wzp$Pyp3@i}< literal 0 HcmV?d00001 diff --git a/tests/images/paths/miterLimit_145deg_3.33num.png b/tests/images/paths/miterLimit_145deg_3.33num.png new file mode 100644 index 0000000000000000000000000000000000000000..237608a839f2f68ab4b093f302c55aac15141017 GIT binary patch literal 642 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$>^XVDj{IaSW+od^_EC{m%e~WAgf) zCy$*{Ihc@Okf>nj`Q)4rTYNLWqG3g>fN`RrP>ZJrkC>tu8|Uol9si1Fne6;$J~6m( ztL67Ull0zZR@K$sva_{v6ll33@8llyvHiiJHHU@MWak;Y{?HQHu71YKCh^)s&eIET zzfCfldEjNqDNEnLIKLkS778+a%F|CLZjDkD;X0ULprPe_`1GMp7pB7x1NI!tF>~G; z)q4DKGL(Hrdqj{=4yBS(+UK<9JU9ZM*&UM&9-re#@^E$LmeknCi9RW=?^H z%$4VV#WuBgzI9m~xFbfd#7cIKpZcoGiG^RQVnx5IdPg*=i1r^hoPYi(OQz!2(A8HT zly?%NN=aXiU?xP7tGcUaUdZc_B+sb|GuCG@5>Z-fuV#bkos(*jiW^awUvPo#k zzF&X$MZ`BcIPA%s-xM{&W8dt*b^SGV`gXeWSLq*Z*&4OB;{S=ozOS-vrj_fHHvV}1b?FR)pV`0VSMIy^dg~0cl?Tfg&-h@x0GPHIJYD@<);T3K0RY_AFTVf) literal 0 HcmV?d00001 diff --git a/tests/images/paths/miterLimit_155deg_2.00num.png b/tests/images/paths/miterLimit_155deg_2.00num.png new file mode 100644 index 0000000000000000000000000000000000000000..76493bb672f1f369c3c68f3a3867ea0ce3264dda GIT binary patch literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$>^XVEp3g;uuoF_;#A%ipoTZ_Ieqf zB{~vQTqm74ma%2?jGi#=%`-%qkEVsHbstIDxWORehDx)M#F8nW-&>cyzP9$Y%>UxK z)$#9x(|^r5|GR=u-g8>&&QES0heY#PxP#lVq30_P&GcbA{7~Rvf`DBAbN^bmS*_P! zn{u@>O*^f6>#dnsH|w9e{Y8ISO}M9fsk(k^KAYB@x4rvh3e)!6y#X3uHh$5*sIuqw z+X?5N7w*5m|3t^uxb^LI`}x}sH)fb{ZNB;EX{p;{&mBQ46U=7UT7UjnaU{VYV3qfz zB^|eOw&lOCR9)5Tzg)Ozr%c}V*{gn@dv5HuSkP)NpWbxtq>U19%66}+t{1v`XloSj zZwtU-TlRqrAG}C3&XCzE`9o^$cdqXL7pvmjPH=_Px34^K=*mMv=qG#Zj%k|i+qho> PlL3RLtDnm{r-UW|1_2Ta literal 0 HcmV?d00001 diff --git a/tests/images/paths/miterLimit_165deg_10.00num.png b/tests/images/paths/miterLimit_165deg_10.00num.png new file mode 100644 index 0000000000000000000000000000000000000000..c65f63537861c8f1a3810c0def9b4899b5a20ab5 GIT binary patch literal 700 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$>^XU~2YsaSW+od^^qCJ1bD4Eq;$u zQ)fqo($d<#2 z6?@}sY-}EU{w&Pb&MeiNc5TwqWWB?$U%x()VszqZk%gVzv17;Bf>w&i$;&ScTm7)a zYP#M7lPit;_wC!TZCl&J0*jl@BCdDt-hKG(n;1Jg`;B||-0bEbfBjnfOq#Ke+T|zP zPcxN1a9Y@~ckkYr=WQi;E@YS#R91G*oh$oEXw_9MJ3G6CjS&m3zfNEGUp#=_&c;SX z?fJs%uNTJXJ%00sr_q6-)1~Rl{t(@upQ}SwRc@Qi`s#pOfBHk___(+evIQk2Eo;~6 zs!aB@v9)zve)(YDcKto}ydjPE@83V!s%m~VP1xMryu)qrjk|YUH%7Q@zpeT=-$d%E zU7v&Kj-Ztj+9wGw4eI<@VRJJ`faS{T(wLYS1raVqvF?e#9bVP1zUsBgfo}zq_tR(J ze?PUzKUDG{^WMFC0a{ZFs;auqp7o81jeYp<-@H94(@(RuIvMV5;aYV-sEGYv-t@NT z&(oJ&&OGwCkfqr%Wpm`GyQfZZ{bXP8bU$lc%f*bAu+^=vUuVC0|6WK%#Vo(&&TLC3PZl=% z=QVkO$$#$v_HRFH?7wZ^wae=n$M)O4@-swN@NQ<}KVx;r;B~>_HHUdO^O^XV7%q&;uuoF`1Z>F*NllW>>tX1 zPTb@v6q)mnvH9c)`-BY=s{a|Jg^dKoRGW3wCI@;Z2~YO)Ii=Pt|8k$Lxx~K1Ut`u; z*Ve?9mL9(Qyz<`neb%!VX?VSCzu^-0M}W^q)*KgFv-EDB^5l~b3M?M{tWlESIrOq5 z>+Yx064RKKAqMl$Z;a3}kl_nl8)i8BY=Mp3!vYHduGW2e59%*9#;+GI+9~ty`|s+u zMH)BWf1lyE{KeO*3=^pcz3CS7&wr@e`=P?-efIv=k_T6_TK$$U-ueFa+qS!T?I%;3 z(l)QOYk6DNef%-w{PXPFZ?CP~FS&&I`|sMVp0;|^xv#x8bz3a>>Z{eU#~XkB?cVOX z=U>_G*z*?79_)R`AIs}kSk1jse;~oYVf9s~g#kY*mc<1%uD`ze*I}a|3mG@N`Q}AS zL{=V{eU|NRUU=4mH5n#dC04rCI}?{Mu`@AFIh}e~OjM|oWzxwd7N%1}5)QsNurh>e zV}wtS+0@j=%{O(FCU&e$WIfF!-tGG1Z{0@r;7G9@VXF^5Et)A6Hi0ijYbuZJ8V+p_ j_GL}X!N++CpzrK27$zux)-}ukMmB?|tDnm{r-UW|q|NB$ literal 0 HcmV?d00001 diff --git a/tests/test_paths.nim b/tests/test_paths.nim index 1dfa970..93d2d8e 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -1,4 +1,4 @@ -import chroma, pixie, pixie/fileformats/png +import chroma, pixie, pixie/fileformats/png, strformat block: let pathStr = """ @@ -47,7 +47,7 @@ block: image = newImage(100, 100) pathStr = "M 10 10 L 90 90" color = rgba(255, 0, 0, 255) - image.strokePath(pathStr, color, 10) + image.strokePath(pathStr, color, strokeWidth=10) image.writeFile("tests/images/paths/pathStroke1.png") block: @@ -55,7 +55,7 @@ block: image = newImage(100, 100) pathStr = "M 10 10 L 50 60 90 90" color = rgba(255, 0, 0, 255) - image.strokePath(pathStr, color, 10) + image.strokePath(pathStr, color, strokeWidth=10) image.writeFile("tests/images/paths/pathStroke2.png") block: @@ -256,6 +256,68 @@ block: image.writeFile("tests/images/paths/lcSquare.png") +block: + let + image = newImage(60, 120) + path = parsePath("M 0 0 L 50 0") + image.fill(rgba(255, 255, 255, 255)) + + image.strokePath( + path, rgba(0, 0, 0, 255), vec2(5, 5), 10, lcButt, ljBevel, + ) + + image.strokePath( + path, rgba(0, 0, 0, 255), vec2(5, 25), 10, lcButt, ljBevel, + dashes = @[2.float32,2] + ) + + image.strokePath( + path, rgba(0, 0, 0, 255), vec2(5, 45), 10, lcButt, ljBevel, + dashes = @[4.float32,4] + ) + + image.strokePath( + path, rgba(0, 0, 0, 255), 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, + dashes = @[1.float32] + ) + + image.strokePath( + path, rgba(0, 0, 0, 255), vec2(5, 105), 10, lcButt, ljBevel, + dashes = @[1.float32, 2, 3, 4, 5, 6, 7, 8, 9] + ) + + image.writeFile("tests/images/paths/dashes.png") + +block: + proc miterTest(angle, limit: float32) = + let + image = newImage(60, 60) + image.fill(rgba(255, 255, 255, 255)) + var path: Path + path.moveTo(-20, 0) + path.lineTo(0, 0) + let th = angle.float32.degToRad() + PI/2 + path.lineTo(sin(th)*20, cos(th)*20) + + image.strokePath( + path, rgba(0, 0, 0, 255), vec2(30, 30), 8, lcButt, ljMiter, + miterLimit = limit + ) + image.writeFile(&"tests/images/paths/miterLimit_{angle.int}deg_{limit:0.2f}num.png") + + miterTest(10, 2) + miterTest(145, 2) + miterTest(155, 2) + miterTest(165, 2) + miterTest(165, 10) + miterTest(145, 3.32) + miterTest(145, 3.33) + # Potential error cases, ensure they do not crash block: