diff --git a/examples/tiger.png b/examples/tiger.png index 5521f5f..a241b71 100644 Binary files a/examples/tiger.png and b/examples/tiger.png differ diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index ab5df36..1f6db0d 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -1068,6 +1068,17 @@ proc computeBounds*( shapes.transform(transform) computeBounds(shapes.shapesToSegments()) +proc initPartitionEntry(segment: Segment, winding: int16): PartitionEntry = + result.atY = segment.at.y + result.toY = segment.to.y + result.winding = winding + let d = segment.at.x - segment.to.x + if d == 0: + result.b = segment.at.x # Leave m = 0, store the x we want in b + else: + result.m = (segment.at.y - segment.to.y) / d + result.b = segment.at.y - result.m * segment.at.x + proc partitionSegments( segments: seq[(Segment, int16)], top, height: int ): Partitioning = @@ -1081,17 +1092,7 @@ proc partitionSegments( result.partitionHeight = height.uint32 div numPartitions for (segment, winding) in segments: - var entry: PartitionEntry - entry.atY = segment.at.y - entry.toY = segment.to.y - entry.winding = winding - let d = segment.at.x - segment.to.x - if d == 0: - entry.b = segment.at.x # Leave m = 0, store the x we want in b - else: - entry.m = (segment.at.y - segment.to.y) / d - entry.b = segment.at.y - entry.m * segment.at.x - + let entry = initPartitionEntry(segment, winding) if result.partitionHeight == 0: result.partitions[0].add(entry) else: @@ -1106,7 +1107,7 @@ proc partitionSegments( result.partitions[i].add(entry) proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} = - if partitioning.partitionHeight == 0 or partitioning.partitions.len == 1: + if partitioning.partitions.len == 1: 0.uint32 else: min( @@ -1114,39 +1115,43 @@ proc getIndexForY(partitioning: Partitioning, y: int): uint32 {.inline.} = partitioning.partitions.high.uint32 ) +proc maxEntryCount(partitioning: Partitioning): int = + for i in 0 ..< partitioning.partitions.len: + result = max(result, partitioning.partitions[i].len) + proc insertionSort( - a: var seq[(float32, int16)], lo, hi: int + hits: var seq[(float32, int16)], lo, hi: int ) {.inline.} = for i in lo + 1 .. hi: var j = i - 1 k = i - while j >= 0 and a[j][0] > a[k][0]: - swap(a[j + 1], a[j]) + while j >= 0 and hits[j][0] > hits[k][0]: + swap(hits[j + 1], hits[j]) dec j dec k -proc sort(a: var seq[(float32, int16)], inl, inr: int) = +proc sort(hits: var seq[(float32, int16)], inl, inr: int) = ## Quicksort + insertion sort, in-place and faster than standard lib sort. let n = inr - inl + 1 if n < 32: - insertionSort(a, inl, inr) + insertionSort(hits, inl, inr) return var l = inl r = inr - let p = a[l + n div 2][0] + let p = hits[l + n div 2][0] while l <= r: - if a[l][0] < p: + if hits[l][0] < p: inc l - elif a[r][0] > p: + elif hits[r][0] > p: dec r else: - swap(a[l], a[r]) + swap(hits[l], hits[r]) inc l dec r - sort(a, inl, r) - sort(a, l, inr) + sort(hits, inl, r) + sort(hits, l, inr) proc shouldFill( windingRule: WindingRule, count: int @@ -1166,28 +1171,37 @@ iterator walk( width: float32 ): (float32, float32, int) = var + i, count: int prevAt: float32 - count: int - for i in 0 ..< numHits: + while i < numHits: let (at, winding) = hits[i] - if windingRule == wrNonZero and - (count != 0) == (count + winding != 0) and - i < numHits - 1: - # Shortcut: if nonzero rule, we only care about when the count changes - # between zero and nonzero (or the last hit) - count += winding - continue if at > 0: if shouldFill(windingRule, count): + if i < numHits - 1: + # Look ahead to see if the next hit is in the same spot as this hit. + # If it is, see if this hit and the next hit's windings cancel out. + # If they do, skip the hits. It will be yielded later in a + # larger chunk. + let (nextAt, nextWinding) = hits[i + 1] + if nextAt == at and winding + nextWinding == 0: + i += 2 + continue + # Shortcut: we only care about when we stop filling (or the last hit). + # If we continue filling, move to next hit. + if windingRule == wrNonZero and count + winding != 0: + count += winding + inc i + continue yield (prevAt, at, count) prevAt = at count += winding + inc i when defined(pixieLeakCheck): if prevAt != width and count != 0: echo "Leak detected: ", count, " @ (", prevAt, ", ", y, ")" -proc computeCoverages( +proc computeCoverage( coverages: var seq[uint8], hits: var seq[(float32, int16)], numHits: var int, @@ -1215,7 +1229,7 @@ proc computeCoverages( yLine += offset numHits = 0 for i in 0 ..< partitionEntryCount: # Perf - let entry = partitioning.partitions[partitionIndex][i] + let entry = partitioning.partitions[partitionIndex][i].unsafeAddr # Perf if entry.atY <= yLine and entry.toY >= yLine: let x = if entry.m == 0: @@ -1223,8 +1237,6 @@ proc computeCoverages( else: (yLine - entry.b) / entry.m - if numHits == hits.len: - hits.setLen(hits.len * 2) hits[numHits] = (min(x, width), entry.winding) inc numHits @@ -1259,9 +1271,9 @@ proc computeCoverages( when defined(amd64) and not defined(pixieNoSimd): let sampleCoverageVec = mm_set1_epi8(cast[int8](sampleCoverage)) for _ in 0 ..< fillLen div 16: - var coverage = mm_loadu_si128(coverages[i - startX].addr) - coverage = mm_add_epi8(coverage, sampleCoverageVec) - mm_storeu_si128(coverages[i - startX].addr, coverage) + var coverageVec = mm_loadu_si128(coverages[i - startX].addr) + coverageVec = mm_add_epi8(coverageVec, sampleCoverageVec) + mm_storeu_si128(coverages[i - startX].addr, coverageVec) i += 16 for j in i ..< fillStart + fillLen: coverages[j - startX] += sampleCoverage @@ -1299,11 +1311,11 @@ proc fillCoverage( for _ in 0 ..< coverages.len div 16: let index = image.dataIndex(x, y) - coverage = mm_loadu_si128(coverages[x - startX].unsafeAddr) + coverageVec = mm_loadu_si128(coverages[x - startX].unsafeAddr) - if mm_movemask_epi8(mm_cmpeq_epi16(coverage, zeroVec)) != 0xffff: + if mm_movemask_epi8(mm_cmpeq_epi16(coverageVec, zeroVec)) != 0xffff: # If the coverages are not all zero - if mm_movemask_epi8(mm_cmpeq_epi32(coverage, vec255)) == 0xffff: + if mm_movemask_epi8(mm_cmpeq_epi32(coverageVec, vec255)) == 0xffff: # If the coverages are all 255 if blendMode == bmNormal and rgbx.a == 255: for i in 0 ..< 4: @@ -1317,9 +1329,9 @@ proc fillCoverage( ) else: # Coverages are not all 255 - var coverage = coverage + var coverageVec = coverageVec for i in 0 ..< 4: - var unpacked = unpackAlphaValues(coverage) + var unpacked = unpackAlphaValues(coverageVec) # Shift the coverages from `a` to `g` and `a` for multiplying unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 16)) @@ -1342,7 +1354,7 @@ proc fillCoverage( blenderSimd(backdrop, source) ) - coverage = mm_srli_si128(coverage, 4) + coverageVec = mm_srli_si128(coverageVec, 4) elif blendMode == bmMask: for i in 0 ..< 4: @@ -1534,11 +1546,11 @@ proc fillShapes( var coverages = newSeq[uint8](bounds.w.int) - hits = newSeq[(float32, int16)](4) + hits = newSeq[(float32, int16)](partitioning.maxEntryCount) numHits: int for y in startY ..< pathHeight: - computeCoverages( + computeCoverage( coverages, hits, numHits, @@ -1591,11 +1603,11 @@ proc fillShapes( var coverages = newSeq[uint8](bounds.w.int) - hits = newSeq[(float32, int16)](4) + hits = newSeq[(float32, int16)](partitioning.maxEntryCount) numHits: int for y in startY ..< pathHeight: - computeCoverages( + computeCoverage( coverages, hits, numHits, @@ -1937,10 +1949,7 @@ proc overlaps( let scanline = line(vec2(0, test.y), vec2(1000, test.y)) segments = shapes.shapesToSegments() - for i in 0 ..< segments.len: # For gc:arc - let - segment = segments[i][0] - winding = segments[i][1] + for (segment, winding) in segments: if segment.at.y <= scanline.a.y and segment.to.y >= scanline.a.y: var at: Vec2 if scanline.intersects(segment, at): @@ -1950,8 +1959,7 @@ proc overlaps( sort(hits, 0, hits.high) var count: int - for i in 0 ..< hits.len: # For gc:arc - let (at, winding) = hits[i] + for (at, winding) in hits: if at > test.x: return shouldFill(windingRule, count) count += winding diff --git a/tests/benchmark_paths.nim b/tests/benchmark_paths.nim index 080d76e..cab240a 100644 --- a/tests/benchmark_paths.nim +++ b/tests/benchmark_paths.nim @@ -5,8 +5,16 @@ let pathStr = "m57.611-8.591c-1.487,1.851-4.899,4.42-1.982,6.348,0.194,0.129,0.5 timeIt "parsePath": keep parsePath(pathStr) -let image = newImage(500, 300) -image.fill(rgba(255, 255, 255, 255)) +block: + let path = parsePath(""" + M 10,30 + A 20,20 0,0,1 50,30 + A 20,20 0,0,1 90,30 + Q 90,60 50,90 + Q 10,60 10,30 z + """) + timeIt "fillOverlaps": + doAssert path.fillOverlaps(vec2(1, 1)) == false timeIt "roundedRect": const radius = 20 @@ -15,4 +23,5 @@ timeIt "roundedRect": path.roundedRect(0.5, 0.5, 499, 299, radius, radius, radius, radius) # path.roundedRect(0, 0, 500, 300, radius, radius, radius, radius) + let image = newImage(500, 300) image.fillPath(path, rgba(0, 0, 0, 255)) diff --git a/tests/contexts/bezierCurveTo_1.png b/tests/contexts/bezierCurveTo_1.png index a007d41..83bb3bf 100644 Binary files a/tests/contexts/bezierCurveTo_1.png and b/tests/contexts/bezierCurveTo_1.png differ diff --git a/tests/contexts/bezierCurveTo_2.png b/tests/contexts/bezierCurveTo_2.png index 3bfda4a..ef9ed76 100644 Binary files a/tests/contexts/bezierCurveTo_2.png and b/tests/contexts/bezierCurveTo_2.png differ diff --git a/tests/contexts/closePath_1.png b/tests/contexts/closePath_1.png index bd5fc25..8869e5f 100644 Binary files a/tests/contexts/closePath_1.png and b/tests/contexts/closePath_1.png differ diff --git a/tests/contexts/ellipse_1.png b/tests/contexts/ellipse_1.png index f28603e..786a7d5 100644 Binary files a/tests/contexts/ellipse_1.png and b/tests/contexts/ellipse_1.png differ diff --git a/tests/contexts/quadracticCurveTo_1.png b/tests/contexts/quadracticCurveTo_1.png index de9463d..6f6b63f 100644 Binary files a/tests/contexts/quadracticCurveTo_1.png and b/tests/contexts/quadracticCurveTo_1.png differ diff --git a/tests/contexts/strokeText_1.png b/tests/contexts/strokeText_1.png index 0f22eb8..3b028b6 100644 Binary files a/tests/contexts/strokeText_1.png and b/tests/contexts/strokeText_1.png differ diff --git a/tests/fileformats/svg/diffs/Ghostscript_Tiger.png b/tests/fileformats/svg/diffs/Ghostscript_Tiger.png index 3742296..b870b2f 100644 Binary files a/tests/fileformats/svg/diffs/Ghostscript_Tiger.png and b/tests/fileformats/svg/diffs/Ghostscript_Tiger.png differ diff --git a/tests/fileformats/svg/diffs/circle01.png b/tests/fileformats/svg/diffs/circle01.png index 31eb2ed..f8606a0 100644 Binary files a/tests/fileformats/svg/diffs/circle01.png and b/tests/fileformats/svg/diffs/circle01.png differ diff --git a/tests/fileformats/svg/diffs/ellipse01.png b/tests/fileformats/svg/diffs/ellipse01.png index 0de4684..b446986 100644 Binary files a/tests/fileformats/svg/diffs/ellipse01.png and b/tests/fileformats/svg/diffs/ellipse01.png differ diff --git a/tests/fileformats/svg/diffs/miterlimit.png b/tests/fileformats/svg/diffs/miterlimit.png index b8299c7..7803a67 100644 Binary files a/tests/fileformats/svg/diffs/miterlimit.png and b/tests/fileformats/svg/diffs/miterlimit.png differ diff --git a/tests/fileformats/svg/diffs/polygon01.png b/tests/fileformats/svg/diffs/polygon01.png index 09dec93..e66307f 100644 Binary files a/tests/fileformats/svg/diffs/polygon01.png and b/tests/fileformats/svg/diffs/polygon01.png differ diff --git a/tests/fileformats/svg/diffs/quad01.png b/tests/fileformats/svg/diffs/quad01.png index f0e4ac6..3edde6c 100644 Binary files a/tests/fileformats/svg/diffs/quad01.png and b/tests/fileformats/svg/diffs/quad01.png differ diff --git a/tests/fileformats/svg/diffs/rect02.png b/tests/fileformats/svg/diffs/rect02.png index c4afed4..d3cf8c8 100644 Binary files a/tests/fileformats/svg/diffs/rect02.png and b/tests/fileformats/svg/diffs/rect02.png differ diff --git a/tests/fileformats/svg/emojitwo.png b/tests/fileformats/svg/emojitwo.png index e9dc17c..35a3fab 100644 Binary files a/tests/fileformats/svg/emojitwo.png and b/tests/fileformats/svg/emojitwo.png differ diff --git a/tests/fileformats/svg/flat-color-icons.png b/tests/fileformats/svg/flat-color-icons.png index a993866..8de3840 100644 Binary files a/tests/fileformats/svg/flat-color-icons.png and b/tests/fileformats/svg/flat-color-icons.png differ diff --git a/tests/fileformats/svg/ionicons.png b/tests/fileformats/svg/ionicons.png index 6312731..1d2aab4 100644 Binary files a/tests/fileformats/svg/ionicons.png and b/tests/fileformats/svg/ionicons.png differ diff --git a/tests/fileformats/svg/noto-emoji.png b/tests/fileformats/svg/noto-emoji.png index 5567d57..dad7899 100644 Binary files a/tests/fileformats/svg/noto-emoji.png and b/tests/fileformats/svg/noto-emoji.png differ diff --git a/tests/fileformats/svg/openmoji.png b/tests/fileformats/svg/openmoji.png index f21ebcf..70e32e7 100644 Binary files a/tests/fileformats/svg/openmoji.png and b/tests/fileformats/svg/openmoji.png differ diff --git a/tests/fileformats/svg/rendered/Ghostscript_Tiger.png b/tests/fileformats/svg/rendered/Ghostscript_Tiger.png index 805beb3..710a1b9 100644 Binary files a/tests/fileformats/svg/rendered/Ghostscript_Tiger.png and b/tests/fileformats/svg/rendered/Ghostscript_Tiger.png differ diff --git a/tests/fileformats/svg/rendered/circle01.png b/tests/fileformats/svg/rendered/circle01.png index ec30745..56799c1 100644 Binary files a/tests/fileformats/svg/rendered/circle01.png and b/tests/fileformats/svg/rendered/circle01.png differ diff --git a/tests/fileformats/svg/rendered/ellipse01.png b/tests/fileformats/svg/rendered/ellipse01.png index f4df489..9bf42b7 100644 Binary files a/tests/fileformats/svg/rendered/ellipse01.png and b/tests/fileformats/svg/rendered/ellipse01.png differ diff --git a/tests/fileformats/svg/rendered/miterlimit.png b/tests/fileformats/svg/rendered/miterlimit.png index 7a38b52..c2babb8 100644 Binary files a/tests/fileformats/svg/rendered/miterlimit.png and b/tests/fileformats/svg/rendered/miterlimit.png differ diff --git a/tests/fileformats/svg/rendered/polygon01.png b/tests/fileformats/svg/rendered/polygon01.png index aee52b8..ec6a32f 100644 Binary files a/tests/fileformats/svg/rendered/polygon01.png and b/tests/fileformats/svg/rendered/polygon01.png differ diff --git a/tests/fileformats/svg/rendered/quad01.png b/tests/fileformats/svg/rendered/quad01.png index 48166f7..4b3e6c5 100644 Binary files a/tests/fileformats/svg/rendered/quad01.png and b/tests/fileformats/svg/rendered/quad01.png differ diff --git a/tests/fileformats/svg/rendered/rect02.png b/tests/fileformats/svg/rendered/rect02.png index bb77d88..2c00983 100644 Binary files a/tests/fileformats/svg/rendered/rect02.png and b/tests/fileformats/svg/rendered/rect02.png differ diff --git a/tests/fileformats/svg/simple-icons.png b/tests/fileformats/svg/simple-icons.png index 0769a88..9d6217c 100644 Binary files a/tests/fileformats/svg/simple-icons.png and b/tests/fileformats/svg/simple-icons.png differ diff --git a/tests/fileformats/svg/tabler-icons.png b/tests/fileformats/svg/tabler-icons.png index 91ecfca..b629c1c 100644 Binary files a/tests/fileformats/svg/tabler-icons.png and b/tests/fileformats/svg/tabler-icons.png differ diff --git a/tests/fileformats/svg/twbs-icons.png b/tests/fileformats/svg/twbs-icons.png index 53ef9d8..ac850f3 100644 Binary files a/tests/fileformats/svg/twbs-icons.png and b/tests/fileformats/svg/twbs-icons.png differ diff --git a/tests/fileformats/svg/twemoji.png b/tests/fileformats/svg/twemoji.png index f77ab2d..9e11f33 100644 Binary files a/tests/fileformats/svg/twemoji.png and b/tests/fileformats/svg/twemoji.png differ diff --git a/tests/fonts/diffs/image_stroke.png b/tests/fonts/diffs/image_stroke.png index 089c82d..69f41a8 100644 Binary files a/tests/fonts/diffs/image_stroke.png and b/tests/fonts/diffs/image_stroke.png differ diff --git a/tests/fonts/diffs/mask_stroke.png b/tests/fonts/diffs/mask_stroke.png index fa09664..22dbf26 100644 Binary files a/tests/fonts/diffs/mask_stroke.png and b/tests/fonts/diffs/mask_stroke.png differ diff --git a/tests/fonts/diffs/strikethrough3.png b/tests/fonts/diffs/strikethrough3.png index 59db90a..95fb128 100644 Binary files a/tests/fonts/diffs/strikethrough3.png and b/tests/fonts/diffs/strikethrough3.png differ diff --git a/tests/fonts/diffs/underline3.png b/tests/fonts/diffs/underline3.png index f8bfa52..43467ab 100644 Binary files a/tests/fonts/diffs/underline3.png and b/tests/fonts/diffs/underline3.png differ diff --git a/tests/fonts/rendered/image_stroke.png b/tests/fonts/rendered/image_stroke.png index 63f588d..da57d70 100644 Binary files a/tests/fonts/rendered/image_stroke.png and b/tests/fonts/rendered/image_stroke.png differ diff --git a/tests/fonts/rendered/mask_stroke.png b/tests/fonts/rendered/mask_stroke.png index cbefc50..7301630 100644 Binary files a/tests/fonts/rendered/mask_stroke.png and b/tests/fonts/rendered/mask_stroke.png differ diff --git a/tests/fonts/rendered/strikethrough3.png b/tests/fonts/rendered/strikethrough3.png index f8e8dd0..699ffc5 100644 Binary files a/tests/fonts/rendered/strikethrough3.png and b/tests/fonts/rendered/strikethrough3.png differ diff --git a/tests/fonts/rendered/underline3.png b/tests/fonts/rendered/underline3.png index 948a5d4..2c7f62a 100644 Binary files a/tests/fonts/rendered/underline3.png and b/tests/fonts/rendered/underline3.png differ diff --git a/tests/images/strokeEllipse.png b/tests/images/strokeEllipse.png index 5b8e68d..3ef7e07 100644 Binary files a/tests/images/strokeEllipse.png and b/tests/images/strokeEllipse.png differ diff --git a/tests/images/strokePolygon.png b/tests/images/strokePolygon.png index 899fe33..62b1816 100644 Binary files a/tests/images/strokePolygon.png and b/tests/images/strokePolygon.png differ diff --git a/tests/images/strokeRoundedRect.png b/tests/images/strokeRoundedRect.png index 75d2404..22dae20 100644 Binary files a/tests/images/strokeRoundedRect.png and b/tests/images/strokeRoundedRect.png differ diff --git a/tests/masks/strokeEllipse.png b/tests/masks/strokeEllipse.png index c02ca91..76c08c7 100644 Binary files a/tests/masks/strokeEllipse.png and b/tests/masks/strokeEllipse.png differ diff --git a/tests/masks/strokePolygon.png b/tests/masks/strokePolygon.png index 8f82ce8..3c8f6e1 100644 Binary files a/tests/masks/strokePolygon.png and b/tests/masks/strokePolygon.png differ diff --git a/tests/masks/strokeRoundedRect.png b/tests/masks/strokeRoundedRect.png index 8991178..ee37251 100644 Binary files a/tests/masks/strokeRoundedRect.png and b/tests/masks/strokeRoundedRect.png differ diff --git a/tests/paths/arc.png b/tests/paths/arc.png index acda868..8ac91b3 100644 Binary files a/tests/paths/arc.png and b/tests/paths/arc.png differ diff --git a/tests/paths/arcTo3.png b/tests/paths/arcTo3.png index 9db12d4..8bb1cd2 100644 Binary files a/tests/paths/arcTo3.png and b/tests/paths/arcTo3.png differ diff --git a/tests/paths/opacityStroke.png b/tests/paths/opacityStroke.png index 8046321..57f2b5d 100644 Binary files a/tests/paths/opacityStroke.png and b/tests/paths/opacityStroke.png differ diff --git a/tests/paths/pathStroke3.png b/tests/paths/pathStroke3.png index 01c0f55..428b9fa 100644 Binary files a/tests/paths/pathStroke3.png and b/tests/paths/pathStroke3.png differ diff --git a/tests/paths/pixelScale.png b/tests/paths/pixelScale.png index 266554c..eb4bf09 100644 Binary files a/tests/paths/pixelScale.png and b/tests/paths/pixelScale.png differ