From a838de821ccc0931661416b8a3be470f1a4e79f0 Mon Sep 17 00:00:00 2001
From: Ryan Oldenburg <ryan@guzba.com>
Date: Fri, 18 Jun 2021 21:43:42 -0500
Subject: [PATCH] paths.nim masks take blendMode for masking, tests, fixes and
 etc

---
 pixie.nimble                                 |   2 +-
 src/pixie/blends.nim                         |  14 +-
 src/pixie/paths.nim                          | 200 ++++++++++++-------
 tests/images/paths/maskRectExcludeMask.png   | Bin 208 -> 232 bytes
 tests/images/paths/maskRectExcludeMaskAA.png | Bin 237 -> 269 bytes
 tests/images/paths/maskRectMask.png          | Bin 208 -> 188 bytes
 tests/images/paths/maskRectMaskAA.png        | Bin 237 -> 207 bytes
 tests/test_paths.nim                         |   8 +-
 8 files changed, 142 insertions(+), 82 deletions(-)

diff --git a/pixie.nimble b/pixie.nimble
index 973ada1..01de21a 100644
--- a/pixie.nimble
+++ b/pixie.nimble
@@ -1,4 +1,4 @@
-version     = "2.0.5"
+version     = "2.1.0"
 author      = "Andre von Houck and Ryan Oldenburg"
 description = "Full-featured 2d graphics library for Nim."
 license     = "MIT"
diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim
index 3fdd211..5e5b2c7 100644
--- a/src/pixie/blends.nim
+++ b/src/pixie/blends.nim
@@ -435,9 +435,6 @@ proc blendSubtractMask(backdrop, source: ColorRGBX): ColorRGBX =
   result.b = ((backdrop.b * a) div 255).uint8
   result.a = a.uint8
 
-proc blendIntersectMask(backdrop, source: ColorRGBX): ColorRGBX =
-  blendMask(backdrop, source)
-
 proc blendExcludeMask(backdrop, source: ColorRGBX): ColorRGBX =
   let a = max(backdrop.a, source.a).uint32 - min(backdrop.a, source.a)
   result.r = ((source.r * a) div 255).uint8
@@ -489,9 +486,6 @@ proc maskMask(backdrop, source: uint8): uint8 =
 proc maskSubtract(backdrop, source: uint8): uint8 =
   ((backdrop.uint32 * (255 - source)) div 255).uint8
 
-proc maskIntersect(backdrop, source: uint8): uint8 =
-  maskMask(backdrop, source)
-
 proc maskExclude(backdrop, source: uint8): uint8 =
   max(backdrop, source) - min(backdrop, source)
 
@@ -592,7 +586,7 @@ when defined(amd64) and not defined(pixieNoSimd):
       div255 = mm_set1_epi16(cast[int16](0x8081))
 
     var
-      sourceEven = mm_slli_epi16(mm_andnot_si128(oddMask, source), 8)
+      sourceEven = mm_slli_epi16(source, 8)
       sourceOdd = mm_and_si128(source, oddMask)
 
     let
@@ -600,7 +594,7 @@ when defined(amd64) and not defined(pixieNoSimd):
       oddK = mm_sub_epi16(v255high, sourceOdd)
 
     var
-      backdropEven = mm_slli_epi16(mm_andnot_si128(oddMask, backdrop), 8)
+      backdropEven = mm_slli_epi16(backdrop, 8)
       backdropOdd = mm_and_si128(backdrop, oddMask)
 
     # backdrop * k
@@ -625,11 +619,11 @@ when defined(amd64) and not defined(pixieNoSimd):
     let
       oddMask = mm_set1_epi16(cast[int16](0xff00))
       div255 = mm_set1_epi16(cast[int16](0x8081))
-      sourceEven = mm_slli_epi16(mm_andnot_si128(oddMask, source), 8)
+      sourceEven = mm_slli_epi16(source, 8)
       sourceOdd = mm_and_si128(source, oddMask)
 
     var
-      backdropEven = mm_slli_epi16(mm_andnot_si128(oddMask, backdrop), 8)
+      backdropEven = mm_slli_epi16(backdrop, 8)
       backdropOdd = mm_and_si128(backdrop, oddMask)
 
     # backdrop * source
diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim
index fbeccea..124dd5b 100644
--- a/src/pixie/paths.nim
+++ b/src/pixie/paths.nim
@@ -1137,10 +1137,12 @@ iterator walk(
       # between zero and nonzero (or the last hit)
       count += winding
       continue
-    if at > 0:
-      if shouldFill(windingRule, count):
-        yield (prevAt, at, count)
-      prevAt = at
+    if at <= 0:
+      count += winding
+      continue
+    if shouldFill(windingRule, count):
+      yield (prevAt, at, count)
+    prevAt = at
     count += winding
 
   when defined(pixieLeakCheck):
@@ -1226,13 +1228,15 @@ proc computeCoverages(
           for j in i ..< fillStart + fillLen:
             coverages[j] += sampleCoverage
 
-proc clearUnsafe(image: Image, startX, startY, toX, toY: int) =
-  ## From startXY to toXY, exclusive, toXY is not cleared.
-  image.data.fillUnsafe(
-    rgbx(0, 0, 0, 0),
-    image.dataIndex(startX, startY),
-    image.dataIndex(toX, toY) - image.dataIndex(startX, startY)
-  )
+proc clearUnsafe(target: Image | Mask, startX, startY, toX, toY: int) =
+  ## Clears data from [start, to).
+  let
+    start = target.dataIndex(startX, startY)
+    len = target.dataIndex(toX, toY) - start
+  when type(target) is Image:
+    target.data.fillUnsafe(rgbx(0, 0, 0, 0), start, len)
+  else: # target is Mask
+    target.data.fillUnsafe(0, start, len)
 
 proc fillCoverage(
   image: Image,
@@ -1258,7 +1262,7 @@ proc fillCoverage(
         let
           index = image.dataIndex(x, y)
           eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128())
-        if mm_movemask_epi8(eqZero) != 0xffff:
+        if mm_movemask_epi8(eqZero) != 0xffff: # or blendMode == bmExcludeMask:
           # If the coverages are not all zero
           if mm_movemask_epi8(mm_cmpeq_epi32(coverage, first32)) == 0xffff:
             # Coverages are all 255
@@ -1294,6 +1298,8 @@ proc fillCoverage(
               image.data[index].addr,
               blenderSimd(backdrop, source)
             )
+        elif blendMode == bmMask:
+          mm_storeu_si128(image.data[index].addr, mm_setzero_si128())
         x += 4
 
   let blender = blendMode.blender()
@@ -1319,31 +1325,45 @@ proc fillCoverage(
   if blendMode == bmMask:
     image.clearUnsafe(0, y, startX, y)
 
-proc fillCoverage(mask: Mask, startX, y: int, coverages: seq[uint8]) =
+proc fillCoverage(
+  mask: Mask,
+  startX, y: int,
+  coverages: seq[uint8],
+  blendMode: BlendMode
+) =
   var x = startX
   when defined(amd64) and not defined(pixieNoSimd):
-    # When supported, SIMD blend as much as possible
-    let maskerSimd = bmNormal.maskerSimd()
-    for _ in countup(x, coverages.len - 16, 16):
-      let
-        coverage = mm_loadu_si128(coverages[x].unsafeAddr)
-        eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128())
-      if mm_movemask_epi8(eqZero) != 0xffff:
-        # If the coverages are not all zero
-        let backdrop = mm_loadu_si128(mask.data[mask.dataIndex(x, y)].addr)
-        mm_storeu_si128(
-          mask.data[mask.dataIndex(x, y)].addr,
-          maskerSimd(backdrop, coverage)
-        )
-      x += 16
+    if blendMode.hasSimdMasker():
+      let maskerSimd = blendMode.maskerSimd()
+      for _ in countup(x, coverages.len - 16, 16):
+        let
+          index = mask.dataIndex(x, y)
+          coverage = mm_loadu_si128(coverages[x].unsafeAddr)
+          eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128())
+        if mm_movemask_epi8(eqZero) != 0xffff: # or blendMode == bmExcludeMask:
+          # If the coverages are not all zero
+          let backdrop = mm_loadu_si128(mask.data[index].addr)
+          mm_storeu_si128(
+            mask.data[index].addr,
+            maskerSimd(backdrop, coverage)
+          )
+        elif blendMode == bmMask:
+          mm_storeu_si128(mask.data[index].addr, mm_setzero_si128())
+        x += 16
 
+  let masker = blendMode.masker()
   while x < mask.width:
     let coverage = coverages[x]
-    if coverage != 0:
+    if coverage != 0 or blendMode == bmExcludeMask:
       let backdrop = mask.getValueUnsafe(x, y)
-      mask.setValueUnsafe(x, y, blendAlpha(backdrop, coverage))
+      mask.setValueUnsafe(x, y, masker(backdrop, coverage))
+    elif blendMode == bmMask:
+      mask.setValueUnsafe(x, y, 0)
     inc x
 
+  if blendMode == bmMask:
+    mask.clearUnsafe(0, y, startX, y)
+
 proc fillHits(
   image: Image,
   rgbx: ColorRGBX,
@@ -1354,53 +1374,89 @@ proc fillHits(
   blendMode: BlendMode
 ) =
   let blender = blendMode.blender()
-  var x = 0
+  var filledTo: int
   for (prevAt, at, count) in hits.walk(numHits, windingRule, y, image.wh):
     let
       fillStart = prevAt.int
       fillLen = at.int - fillStart
-    if fillLen > 0:
-      if blendMode == bmNormal and rgbx.a == 255:
-        fillUnsafe(image.data, rgbx, image.dataIndex(fillStart, y), fillLen)
-      else:
-        x = fillStart
-        when defined(amd64) and not defined(pixieNoSimd):
-          if blendMode.hasSimdBlender():
-            # When supported, SIMD blend as much as possible
-            let
-              blenderSimd = blendMode.blenderSimd()
-              vColor = mm_set1_epi32(cast[int32](rgbx))
-            for _ in countup(fillStart, fillLen - 16, 4):
-              let
-                index = image.dataIndex(x, y)
-                backdrop = mm_loadu_si128(image.data[index].addr)
-              mm_storeu_si128(
-                image.data[index].addr,
-                blenderSimd(backdrop, vColor)
-              )
-              x += 4
-        while x < fillStart + fillLen:
-          let backdrop = image.getRgbaUnsafe(x, y)
-          image.setRgbaUnsafe(x, y, blender(backdrop, rgbx))
-          inc x
+    if fillLen <= 0:
+      continue
+
+    filledTo = fillStart + fillLen
+
+    if blendMode == bmNormal and rgbx.a == 255:
+      fillUnsafe(image.data, rgbx, image.dataIndex(fillStart, y), fillLen)
+      continue
+
+    var x = fillStart
+    when defined(amd64) and not defined(pixieNoSimd):
+      if blendMode.hasSimdBlender():
+        # When supported, SIMD blend as much as possible
+        let
+          blenderSimd = blendMode.blenderSimd()
+          vColor = mm_set1_epi32(cast[int32](rgbx))
+        for _ in countup(fillStart, fillLen - 16, 4):
+          let
+            index = image.dataIndex(x, y)
+            backdrop = mm_loadu_si128(image.data[index].addr)
+          mm_storeu_si128(
+            image.data[index].addr,
+            blenderSimd(backdrop, vColor)
+          )
+          x += 4
+
+    for x in x ..< fillStart + fillLen:
+      let backdrop = image.getRgbaUnsafe(x, y)
+      image.setRgbaUnsafe(x, y, blender(backdrop, rgbx))
 
   if blendMode == bmMask:
     image.clearUnsafe(0, y, startX, y)
-    image.clearUnsafe(x, y, image.width, y)
+    image.clearUnsafe(filledTo, y, image.width, y)
 
 proc fillHits(
   mask: Mask,
   startX, y: int,
   hits: seq[(float32, int16)],
   numHits: int,
-  windingRule: WindingRule
+  windingRule: WindingRule,
+  blendMode: BlendMode
 ) =
+  let masker = blendMode.masker()
+  var filledTo: int
   for (prevAt, at, count) in hits.walk(numHits, windingRule, y, mask.wh):
     let
       fillStart = prevAt.int
       fillLen = at.int - fillStart
-    if fillLen > 0:
+    if fillLen <= 0:
+      continue
+
+    filledTo = fillStart + fillLen
+
+    if blendMode == bmNormal:
       fillUnsafe(mask.data, 255, mask.dataIndex(fillStart, y), fillLen)
+      continue
+
+    var x = fillStart
+    when defined(amd64) and not defined(pixieNoSimd):
+      if blendMode.hasSimdMasker():
+        let
+          maskerSimd = blendMode.maskerSimd()
+          vValue = mm_set1_epi8(cast[int8](255))
+        for _ in countup(fillStart, fillLen - 16, 16):
+          let backdrop = mm_loadu_si128(mask.data[mask.dataIndex(x, y)].addr)
+          mm_storeu_si128(
+            mask.data[mask.dataIndex(x, y)].addr,
+            maskerSimd(backdrop, vValue)
+          )
+          x += 16
+
+    for x in x ..< fillStart + fillLen:
+      let backdrop = mask.getValueUnsafe(x, y)
+      mask.setValueUnsafe(x, y, masker(backdrop, 255))
+
+  if blendMode == bmMask:
+    mask.clearUnsafe(0, y, startX, y)
+    mask.clearUnsafe(filledTo, y, mask.width, y)
 
 proc fillShapes(
   image: Image,
@@ -1460,7 +1516,12 @@ proc fillShapes(
     image.clearUnsafe(0, 0, 0, startY)
     image.clearUnsafe(0, pathHeight, 0, image.height)
 
-proc fillShapes(mask: Mask, shapes: seq[seq[Vec2]], windingRule: WindingRule) =
+proc fillShapes(
+  mask: Mask,
+  shapes: seq[seq[Vec2]],
+  windingRule: WindingRule,
+  blendMode: BlendMode
+) =
   # Figure out the total bounds of all the shapes,
   # rasterize only within the total bounds
   let
@@ -1469,8 +1530,7 @@ proc fillShapes(mask: Mask, shapes: seq[seq[Vec2]], windingRule: WindingRule) =
     bounds = computePixelBounds(segments)
     startX = max(0, bounds.x.int)
     startY = max(0, bounds.y.int)
-    stopY = min(mask.height, (bounds.y + bounds.h).int)
-    pathHeight = stopY - startY
+    pathHeight = min(mask.height, (bounds.y + bounds.h).int)
     partitioning = partitionSegments(segments, startY, pathHeight)
 
   var
@@ -1478,7 +1538,7 @@ proc fillShapes(mask: Mask, shapes: seq[seq[Vec2]], windingRule: WindingRule) =
     hits = newSeq[(float32, int16)](4)
     numHits: int
 
-  for y in startY ..< stopY:
+  for y in startY ..< pathHeight:
     computeCoverages(
       coverages,
       hits,
@@ -1490,9 +1550,13 @@ proc fillShapes(mask: Mask, shapes: seq[seq[Vec2]], windingRule: WindingRule) =
       windingRule
     )
     if aa:
-      mask.fillCoverage(startX, y, coverages)
+      mask.fillCoverage(startX, y, coverages, blendMode)
     else:
-      mask.fillHits(startX, y, hits, numHits, windingRule)
+      mask.fillHits(startX, y, hits, numHits, windingRule, blendMode)
+
+  if blendMode == bmMask:
+    mask.clearUnsafe(0, 0, 0, startY)
+    mask.clearUnsafe(0, pathHeight, 0, mask.height)
 
 proc miterLimitToAngle*(limit: float32): float32 =
   ## Converts miter-limit-ratio to miter-limit-angle.
@@ -1668,12 +1732,13 @@ proc fillPath*(
   mask: Mask,
   path: SomePath,
   transform: Vec2 | Mat3 = vec2(),
-  windingRule = wrNonZero
+  windingRule = wrNonZero,
+  blendMode = bmNormal
 ) =
   ## Fills a path.
   var shapes = parseSomePath(path, true, transform.pixelScale())
   shapes.transform(transform)
-  mask.fillShapes(shapes, windingRule)
+  mask.fillShapes(shapes, windingRule, blendMode)
 
 proc fillPath*(
   image: Image,
@@ -1721,7 +1786,8 @@ proc strokePath*(
   lineCap = lcButt,
   lineJoin = ljMiter,
   miterLimit = defaultMiterLimit,
-  dashes: seq[float32] = @[]
+  dashes: seq[float32] = @[],
+  blendMode = bmNormal
 ) =
   ## Strokes a path.
   var strokeShapes = strokeShapes(
@@ -1733,7 +1799,7 @@ proc strokePath*(
     dashes
   )
   strokeShapes.transform(transform)
-  mask.fillShapes(strokeShapes, wrNonZero)
+  mask.fillShapes(strokeShapes, wrNonZero, blendMode)
 
 proc strokePath*(
   image: Image,
diff --git a/tests/images/paths/maskRectExcludeMask.png b/tests/images/paths/maskRectExcludeMask.png
index d17c2b58aa780b1f92351364392363e16cc227db..145429c91cca41491fced6751571fa6b1ce81851 100644
GIT binary patch
delta 204
zcmcb>_=0hQO8t6I7srqa#<$nb^0^vFustk(bZlqQq^K|I`KLM}&Tf95tPxu$U%-*1
zcO=GRa(4X6x6f9jov8Xe;gn*>PJv>@XHI8YXK+_)Uaz0+xx2~a$0Paqd@C=lQ;>Z)
z;ZRKTmvte}q-Lysp1!nTE>DcQ?RC$GR+2mXs^ZsA-cs-X``fDhuJ?kgy3Wp3`Dhc&
z=Lj;&P#k2{iRPG>`|YQ%d+|vAKVQ27#1@d$6Nd8-PJAqA*L=(X1fH&bF6*2UngAQi
BV$A>m

delta 180
zcmaFCc!6<(O8s<C7srqa#<w?4b2TV1uwGmse_ellDqDDt$C0CRYGb6TE>`M1;7HOt
z65}!XwN}Vqi>p0T`rB<}9M81Q;5HNoNt~Dz^K!NV@Bgaf>U|2&oDdQnI|Ygrt)Hwq
zI7dGBY<TUX6SH^jV*Bp-=<3&JSFb+33N+xRu4#D1TmB`N<!r*96|g|e18Xx+aO&N$
T=y%0*1|aZs^>bP0l+XkK;=@tL

diff --git a/tests/images/paths/maskRectExcludeMaskAA.png b/tests/images/paths/maskRectExcludeMaskAA.png
index 64db8caee3aa76add4f09e80fd542cccb95b8c4b..c28291f98a2b75cb2381b29b0fa836b683bfaacd 100644
GIT binary patch
delta 241
zcmV<N01p4{0gVEXB!ARNL_t(|0qxl_3c^4Tg<-*3Jc4IbFQC0*YL8`U=Me-CAZ+8#
z=4Cfe7P0uhHiRLM8FrE^7e7D|3vpS9%aX<QxExH^*SuO;k?nfkEgxr!IEc$aTo&T8
z5SN9xES=)o?2Z-p(|9RR=YHdiAD*>G+poq~)zeWVA+7@56>2dj33F0lP9o+cVNMFn
zNyMBa|2ijw*xzr}uX$qMZY|tOpRajYcPSK!h-*^gol&8?BD!l5-DM#z%NKE}bH7gg
rOKVJg9~7|=mxZ`2#AP8a3vpTAZ*46CnAGD900000NkvXXu0mjfu(xZW

delta 209
zcmeBWddoOLrGAU2i(^Oy<J+4Dd7BI*91?}u3*5~O^cFBn*lw1!Hehh~`s4k3L8ir?
z_kRCAWMnX3_h#pFJkvUZ+q0<mzPO1`>E=m_{n2+-yZfCviWQ$Zfusz@FR5NPwh(st
z{coYIozUI6&YK{@NqR?OI(7<x)Sa8~`uo4~e6LRnbuA0rZ`GVF_6SQXb*)j$Ubjvs
wTpXnGL|@FsefxEPO%JPZ-1blauJugo8HVduc2?Eh=wJW>Pgg&ebxsLQ0P}5IUjP6A

diff --git a/tests/images/paths/maskRectMask.png b/tests/images/paths/maskRectMask.png
index d17c2b58aa780b1f92351364392363e16cc227db..2d7751c62afe51cee4d5e52f3f0bb803674b73c6 100644
GIT binary patch
delta 160
zcmcb>xQB6qN`14Zi(^Oy<J-%DTn!2WY!4T7zdp%bbG!8#+Y<KK?-{z9|5w%=@D?am
zeCBkfbq2Sgc#__cn2wzj?myWTwRC@v*~zP?lJ}?>hCg{Pb;t9OpHjKej>$*ny1Yv%
zP(NvB{c`D-H|~??u6d~1u@l1#kbtDqx09vJW;1Lvp1kkg%>V?Ru6{1-oD!M<&Vx$9

delta 180
zcmdnPc!6<(O8s<C7srqa#<w?4b2TV1uwGmse_ellDqDDt$C0CRYGb6TE>`M1;7HOt
z65}!XwN}Vqi>p0T`rB<}9M81Q;5HNoNt~Dz^K!NV@Bgaf>U|2&oDdQnI|Ygrt)Hwq
zI7dGBY<TUX6SH^jV*Bp-=<3&JSFb+33N+xRu4#D1TmB`N<!r*96|g|e18Xx+aO&N$
T=y%0*1|aZs^>bP0l+XkK!_!fl

diff --git a/tests/images/paths/maskRectMaskAA.png b/tests/images/paths/maskRectMaskAA.png
index 64db8caee3aa76add4f09e80fd542cccb95b8c4b..079cbc5413a1e6b325206eefb6cbd2d846a6c824 100644
GIT binary patch
delta 179
zcmaFMc%E^BO8qoX7srqa#<$lFa<&?Xum(O9|B$_vS=XT?xBOcIqf?oFw+gc&_uOCy
zCTG1T=DZJ7J9Y{bD?W2N(>jCOP&`TRNX&`!qS)U-1^05VYVCYh|8mAzZ>3__ed~_Q
zO}XRw#80SvQla`pYtK8L&upzNuT47kzbse1Hssii_UQ^hQ*_|QAek#U=`~}cNK9tB
SSJhVrAn<hcb6Mw<&;$Ujn@mms

delta 209
zcmX@l_?B^kO8pj37srqa#<w>Q@-`VrI3x<Q7r2`n=q+HDu-z<cZNT8}^~d}7f=r7&
z@BRLL$jD&6?#<5Uc&2p*w`WoBeQ^_?(#?|;`=jrwcK17T6e~V+0!bN)UsAnpY$5FO
z``<!aJE6OCoi{;*lk|?nbnFxWsXI5}_4j|}`CgwE>RJ}K->NxV>=Bk&>RO|gy>6XO
wxHw4ViN2VN`}XVpnjTi+xb2|;T<e+EGYr?S?5wK0(ZK)&p00i_>zopr04R=Ipa1{>

diff --git a/tests/test_paths.nim b/tests/test_paths.nim
index 4f7894b..e3ab5ec 100644
--- a/tests/test_paths.nim
+++ b/tests/test_paths.nim
@@ -415,23 +415,23 @@ block:
 block:
   let mask = newMask(100, 100)
   mask.fillPath("M 10 10 H 60 V 60 H 10 z")
-  mask.fillPath("M 30 30 H 80 V 80 H 30 z")#, blendMode = bmExcludeMask)
+  mask.fillPath("M 30 30 H 80 V 80 H 30 z", blendMode = bmExcludeMask)
   writeFile("tests/images/paths/maskRectExcludeMask.png", mask.encodePng())
 
 block:
   let mask = newMask(100, 100)
   mask.fillPath("M 10.1 10.1 H 60.1 V 60.1 H 10.1 z")
-  mask.fillPath("M 30.1 30.1 H 80.1 V 80.1 H 30.1 z")#, blendMode = bmExcludeMask)
+  mask.fillPath("M 30.1 30.1 H 80.1 V 80.1 H 30.1 z", blendMode = bmExcludeMask)
   writeFile("tests/images/paths/maskRectExcludeMaskAA.png", mask.encodePng())
 
 block:
   let mask = newMask(100, 100)
   mask.fillPath("M 10 10 H 60 V 60 H 10 z")
-  mask.fillPath("M 30 30 H 80 V 80 H 30 z")#, blendMode = bmMask)
+  mask.fillPath("M 30 30 H 80 V 80 H 30 z", blendMode = bmMask)
   writeFile("tests/images/paths/maskRectMask.png", mask.encodePng())
 
 block:
   let mask = newMask(100, 100)
   mask.fillPath("M 10.1 10.1 H 60.1 V 60.1 H 10.1 z")
-  mask.fillPath("M 30.1 30.1 H 80.1 V 80.1 H 30.1 z")#, blendMode = bmMask)
+  mask.fillPath("M 30.1 30.1 H 80.1 V 80.1 H 30.1 z", blendMode = bmMask)
   writeFile("tests/images/paths/maskRectMaskAA.png", mask.encodePng())