From b9402e592d4d793d590d0a425674d2d75ec4d170 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 29 Nov 2021 07:51:43 -0800 Subject: [PATCH 1/6] Sweeps: Fix jugged outline and streaking. --- src/pixie/paths.nim | 60 ++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 4aa032a..bb63a2a 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -2256,9 +2256,15 @@ when defined(pixieSweeps): var cutterLine: float32 = 0 block doubleFor: for a in sweeps[i]: - let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) + let aSeg = segment( + vec2(a.atx, cutLines[i]), + vec2(a.tox, cutLines[i+1]) + ) for b in sweeps[i]: - let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1])) + let bSeg = segment( + vec2(b.atx, cutLines[i]), + vec2(b.tox, cutLines[i+1]) + ) var at: Vec2 if intersectsInner(aSeg, bSeg, at): needsCut = true @@ -2320,7 +2326,7 @@ when defined(pixieSweeps): # echo "L ", sw.x, " ", sw.y proc computeCoverage( - coverages: var seq[uint8], + coverages: var seq[uint16], y: int, startX: int, cutLines: seq[float32], @@ -2347,53 +2353,61 @@ when defined(pixieSweeps): minEi = min(neX, seX).int maxEi = max(neX, seX).ceil.int - # TODO: Add case when trapezoids both starts and stops on same pixle. - let nw = vec2(sweep[i+0].atx, cutLines[currCutLine]) sw = vec2(sweep[i+0].tox, cutLines[currCutLine + 1]) + f16 = (256 * 256 - 1).float32 for x in minWi ..< maxWi: - var area = pixelCover(nw - vec2(x.float32, y.float32), sw - vec2( - x.float32, y.float32)) - coverages[x - startX] += (area * 255).uint8 + var area = pixelCover( + nw - vec2(x.float32, y.float32), + sw - vec2(x.float32, y.float32) + ) + coverages[x - startX] += (area * f16).uint16 let x = maxWi - var midArea = pixelCover(nw - vec2(x.float32, y.float32), sw - vec2( - x.float32, y.float32)) - var midArea8 = (midArea * 255).uint8 - for x in maxWi ..< minEi: - # TODO: Maybe try coverages of uint16 to prevent streeks in solid white fill? - coverages[x - startX] += midArea8 + var midArea = pixelCover( + nw - vec2(x.float32, y.float32), + sw - vec2(x.float32, y.float32) + ) + for x in maxWi ..< maxEi: + coverages[x - startX] += (midArea * f16).uint16 let ne = vec2(sweep[i+1].atx, cutLines[currCutLine]) se = vec2(sweep[i+1].tox, cutLines[currCutLine + 1]) for x in minEi ..< maxEi: - var area = midArea - pixelCover(ne - vec2(x.float32, y.float32), se - - vec2(x.float32, y.float32)) - coverages[x - startX] += (area * 255).uint8 + var area = pixelCover( + ne - vec2(x.float32, y.float32), + se - vec2(x.float32, y.float32) + ) + coverages[x - startX] -= (area * f16).uint16 i += 2 var currCutLine = 0 - coverages = newSeq[uint8](bounds.w.int) + coverages16 = newSeq[uint16](bounds.w.int) + coverages8 = newSeq[uint8](bounds.w.int) for scanLine in cutLines[0].int ..< cutLines[^1].ceil.int: - zeroMem(coverages[0].addr, coverages.len) - coverages.computeCoverage(scanLine, startX, cutLines, currCutLine, sweeps[currCutLine]) + zeroMem(coverages16[0].addr, coverages16.len * 2) + + coverages16.computeCoverage( + scanLine, startX, cutLines, currCutLine, sweeps[currCutLine]) while cutLines[currCutLine + 1] < scanLine.float + 1.0: inc currCutLine if currCutLine == sweeps.len: break - coverages.computeCoverage(scanLine, startX, cutLines, currCutLine, - sweeps[currCutLine]) + coverages16.computeCoverage( + scanLine, startX, cutLines, currCutLine, sweeps[currCutLine]) + for i in 0 ..< coverages16.len: + coverages8[i] = (coverages16[i] shr 8).uint8 image.fillCoverage( rgbx, startX = startX, y = scanLine, - coverages, + coverages8, blendMode ) From 367a86b56663839c7082dd8bc635d9c128fea5ab Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 29 Nov 2021 09:24:33 -0800 Subject: [PATCH 2/6] Speed up with horizintal line intersect. --- src/pixie/paths.nim | 67 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index bb63a2a..8515f8b 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -2103,6 +2103,17 @@ when defined(pixieSweeps): line.winding = s[1] return line + proc intersectsYLine(y: float32, s: Segment, atx: var float32): bool {.inline.} = + let + s2y = s.to.y - s.at.y + denominator = -s2y + numerator = s.at.y - y + u = numerator / denominator + if u >= 0 and u <= 1: + let at = s.at + (u * vec2(s.to.x - s.at.x, s2y)) + atx = at.x + return true + proc binaryInsert(arr: var seq[float32], v: float32) = if arr.len == 0: arr.add(v) @@ -2202,7 +2213,7 @@ when defined(pixieSweeps): bounds = computeBounds(segments).snapToPixels() startX = max(0, bounds.x.int) - if segments.len == 0: + if segments.len == 0 or bounds.w.int == 0 or bounds.h.int == 0: return # Create sorted segments. @@ -2227,14 +2238,13 @@ when defined(pixieSweeps): let s = segments[lastSeg] if s[0].to.y != cutLines[i + 1]: - var at: Vec2 + var atx: float32 var seg = s[0] for j in i ..< sweeps.len: let y = cutLines[j + 1] - #TODO: speed up with horizintal line intersect - if intersects(line(vec2(0, y), vec2(1, y)), seg, at): - sweeps[j].add(toLine((segment(seg.at, at), s[1]))) - seg = segment(at, seg.to) + if intersectsYLine(y, seg, atx): + sweeps[j].add(toLine((segment(seg.at, vec2(atx, y)), s[1]))) + seg = segment(vec2(atx, y), seg.to) else: if seg.at.y != seg.to.y: sweeps[j].add(toLine(s)) @@ -2288,6 +2298,41 @@ when defined(pixieSweeps): break inc i + # i = 0 + # while i < sweeps.len: + # # TODO: Maybe finds all cuts first, add them to array, cut all lines at once. + # for t in 0 ..< 10: # TODO: maybe while true: + # # keep cutting sweep + # var needsCut = false + # var cutterLine: float32 = 0 + # block doubleFor: + # for a in sweeps[i]: + # let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) + # for b in sweeps[i]: + # let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1])) + # var at: Vec2 + # if intersectsInner(aSeg, bSeg, at): + # needsCut = true + # cutterLine = at.y + # break doubleFor + # # TODO enable? + # if false and needsCut: + # # Doing a cut. + # var + # thisSweep = sweeps[i] + # sweeps[i].setLen(0) + # sweeps.insert(newSeq[SweepLine](), i + 1) + # for a in thisSweep: + # let seg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) + # var at: Vec2 + # if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at): + # sweeps[i+0].add(toLine((segment(seg.at, at), a.winding))) + # sweeps[i+1].add(toLine((segment(at, seg.to), a.winding))) + # cutLines.binaryInsert(cutterLine) + # else: + # break + # inc i + i = 0 while i < sweeps.len: # Sort the sweep by X @@ -2347,11 +2392,11 @@ when defined(pixieSweeps): swX = mix(sweep[i+0].atx, sweep[i+0].tox, yFracBottom) seX = mix(sweep[i+1].atx, sweep[i+1].tox, yFracBottom) - minWi = min(nwX, swX).int - maxWi = max(nwX, swX).ceil.int + minWi = min(nwX, swX).int#.clamp(startX, coverages.len + startX) + maxWi = max(nwX, swX).ceil.int#.clamp(startX, coverages.len + startX) - minEi = min(neX, seX).int - maxEi = max(neX, seX).ceil.int + minEi = min(neX, seX).int#.clamp(startX, coverages.len + startX) + maxEi = max(neX, seX).ceil.int#.clamp(startX, coverages.len + startX) let nw = vec2(sweep[i+0].atx, cutLines[currCutLine]) @@ -2388,7 +2433,7 @@ when defined(pixieSweeps): currCutLine = 0 coverages16 = newSeq[uint16](bounds.w.int) coverages8 = newSeq[uint8](bounds.w.int) - for scanLine in cutLines[0].int ..< cutLines[^1].ceil.int: + for scanLine in max(cutLines[0].int, 0) ..< min(cutLines[^1].ceil.int, image.height): zeroMem(coverages16[0].addr, coverages16.len * 2) From f522dbb9ff275bf18d5aa07dc840120bb0c1a263 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 29 Nov 2021 17:30:18 -0800 Subject: [PATCH 3/6] f --- src/pixie/paths.nim | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 8515f8b..e219689 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -2206,7 +2206,7 @@ when defined(pixieSweeps): windingRule: WindingRule, blendMode: BlendMode ) = - const q = 1/256.0 + let rgbx = color.rgbx var segments = shapes.shapesToSegments() let @@ -2216,6 +2216,13 @@ when defined(pixieSweeps): if segments.len == 0 or bounds.w.int == 0 or bounds.h.int == 0: return + # const q = 1/10 + # for i in 0 ..< segments.len: + # segments[i][0].at.x = quantize(segments[i][0].at.x, q) + # segments[i][0].at.y = quantize(segments[i][0].at.y, q) + # segments[i][0].to.x = quantize(segments[i][0].to.x, q) + # segments[i][0].to.y = quantize(segments[i][0].to.y, q) + # Create sorted segments. segments.sortSegments(0, segments.high) @@ -2378,6 +2385,11 @@ when defined(pixieSweeps): currCutLine: int, sweep: seq[SweepLine] ) = + + if cutLines[currCutLine + 1] - cutLines[currCutLine] < 1/256: + # TODO some thing about micro sweeps + return + let sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine] yFracTop = ((y.float32 - cutLines[currCutLine]) / sweepHeight).clamp(0, 1) From 5ec38c068ddf2ed1ef99a597c01b190c611c8a9d Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 3 Dec 2021 16:50:02 -0800 Subject: [PATCH 4/6] f --- experiments/sweeps3.nim | 13 ++- experiments/trapezoids/output_sweep.png | Bin 5147 -> 4542 bytes src/pixie/fileformats/svg.nim | 1 + src/pixie/paths.nim | 110 +++++++++--------------- tests/benchmark_svg.nim | 5 +- 5 files changed, 54 insertions(+), 75 deletions(-) diff --git a/experiments/sweeps3.nim b/experiments/sweeps3.nim index 655783e..6d81fbd 100644 --- a/experiments/sweeps3.nim +++ b/experiments/sweeps3.nim @@ -64,6 +64,10 @@ var hourGlass = parsePath(""" M 20 20 L 180 20 L 20 180 L 180 180 z """) +var hourGlass2 = parsePath(""" + M 20 20 L 180 20 L 20 180 L 180 180 z M 62 24 L 132 24 L 50 173 L 156 173 z +""") + # Hole var hole = parsePath(""" M 40 40 L 40 160 L 160 160 L 160 40 z @@ -88,6 +92,8 @@ when defined(bench): test("cricle", cricle, 100) test("halfAarc", halfAarc, 100) test("hourGlass", hourGlass, 100) + test("hourGlass2", hourGlass2, wr=wrNonZero) + test("hourGlass2", hourGlass2, wr=wrEvenOdd) test("hole", hole, 100) test("holeEvenOdd", holeEvenOdd, 100, wr=wrNonZero) test("holeEvenOdd", holeEvenOdd, 100, wr=wrEvenOdd) @@ -99,7 +105,8 @@ else: # test("cricle", cricle) # test("halfAarc", halfAarc) # test("hourGlass", hourGlass) - #test("hole", hole, wr=wrEvenOdd) - test("holeEvenOdd", holeEvenOdd, wr=wrNonZero) - test("holeEvenOdd", holeEvenOdd, wr=wrEvenOdd) + test("hourGlass2", hourGlass2, wr=wrEvenOdd) + # test("hole", hole, wr=wrEvenOdd) + # test("holeEvenOdd", holeEvenOdd, wr=wrNonZero) + # test("holeEvenOdd", holeEvenOdd, wr=wrEvenOdd) # test("letterG", letterG) diff --git a/experiments/trapezoids/output_sweep.png b/experiments/trapezoids/output_sweep.png index 4835f2dcf6cfd844916f87c1d10c3d864dc9b9c9..516a83a91976f11ee96e8bd849a581fd2dfb4020 100644 GIT binary patch literal 4542 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 La;M(d#Yq1j2M7Zx literal 5147 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 diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index f786db8..293d3a2 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -2,6 +2,7 @@ import chroma, pixie/common, pixie/images, pixie/internal, pixie/paints, pixie/paths, strutils, tables, vmath, xmlparser, xmltree + when defined(pixieDebugSvg): import strtabs diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index e219689..fe51b71 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -2267,78 +2267,46 @@ when defined(pixieSweeps): i = 0 while i < sweeps.len: # TODO: Maybe finds all cuts first, add them to array, cut all lines at once. - for t in 0 ..< 10: # TODO: maybe while true: - # keep cutting sweep - var needsCut = false - var cutterLine: float32 = 0 - block doubleFor: - for a in sweeps[i]: - let aSeg = segment( - vec2(a.atx, cutLines[i]), - vec2(a.tox, cutLines[i+1]) - ) - for b in sweeps[i]: - let bSeg = segment( - vec2(b.atx, cutLines[i]), - vec2(b.tox, cutLines[i+1]) - ) - var at: Vec2 - if intersectsInner(aSeg, bSeg, at): - needsCut = true - cutterLine = at.y - break doubleFor - # TODO enable? - if false and needsCut: - # Doing a cut. - var - thisSweep = sweeps[i] - sweeps[i].setLen(0) - sweeps.insert(newSeq[SweepLine](), i + 1) - for a in thisSweep: - let seg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) - var at: Vec2 - if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at): - sweeps[i+0].add(toLine((segment(seg.at, at), a.winding))) - sweeps[i+1].add(toLine((segment(at, seg.to), a.winding))) - cutLines.binaryInsert(cutterLine) - else: - break - inc i + var crossCuts: seq[float32] - # i = 0 - # while i < sweeps.len: - # # TODO: Maybe finds all cuts first, add them to array, cut all lines at once. - # for t in 0 ..< 10: # TODO: maybe while true: - # # keep cutting sweep - # var needsCut = false - # var cutterLine: float32 = 0 - # block doubleFor: - # for a in sweeps[i]: - # let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) - # for b in sweeps[i]: - # let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1])) - # var at: Vec2 - # if intersectsInner(aSeg, bSeg, at): - # needsCut = true - # cutterLine = at.y - # break doubleFor - # # TODO enable? - # if false and needsCut: - # # Doing a cut. - # var - # thisSweep = sweeps[i] - # sweeps[i].setLen(0) - # sweeps.insert(newSeq[SweepLine](), i + 1) - # for a in thisSweep: - # let seg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) - # var at: Vec2 - # if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at): - # sweeps[i+0].add(toLine((segment(seg.at, at), a.winding))) - # sweeps[i+1].add(toLine((segment(at, seg.to), a.winding))) - # cutLines.binaryInsert(cutterLine) - # else: - # break - # inc i + # echo i, " cut?" + + for aIndex in 0 ..< sweeps[i].len: + let a = sweeps[i][aIndex] + # echo i, ":", sweeps.len, ":", cutLines.len + let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) + for bIndex in aIndex + 1 ..< sweeps[i].len: + let b = sweeps[i][bIndex] + let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1])) + var at: Vec2 + if intersectsInner(aSeg, bSeg, at): + crossCuts.binaryInsert(at.y) + + if crossCuts.len > 0: + var + thisSweep = sweeps[i] + yTop = cutLines[i] + yBottom = cutLines[i + 1] + sweeps[i].setLen(0) + + for k in crossCuts: + let prevLen = cutLines.len + cutLines.binaryInsert(k) + if prevLen != cutLines.len: + sweeps.insert(newSeq[SweepLine](), i + 1) + + for a in thisSweep: + var seg = segment(vec2(a.atx, yTop), vec2(a.tox, yBottom)) + var at: Vec2 + for j, cutterLine in crossCuts: + if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at): + sweeps[i+j].add(toLine((segment(seg.at, at), a.winding))) + seg = segment(at, seg.to) + sweeps[i+crossCuts.len].add(toLine((seg, a.winding))) + + i += crossCuts.len + + inc i i = 0 while i < sweeps.len: diff --git a/tests/benchmark_svg.nim b/tests/benchmark_svg.nim index c158d6d..b44ffc3 100644 --- a/tests/benchmark_svg.nim +++ b/tests/benchmark_svg.nim @@ -1,6 +1,9 @@ -import benchy, pixie/fileformats/svg +import benchy, pixie/fileformats/svg, pixie/fileformats/png let data = readFile("tests/fileformats/svg/Ghostscript_Tiger.svg") +#let data = readFile("tests/fileformats/svg/rotatedRect.svg") + +writeFile("tiger.png", decodeSvg(data).encodePng()) timeIt "svg decode": discard decodeSvg(data) From f4c0619fa8f3ef515f810f26db4191f3fc20e555 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 4 Dec 2021 17:39:00 -0800 Subject: [PATCH 5/6] Fixes after rebase. --- src/pixie/paths.nim | 72 ++++++++++++++++++++--------------------- tests/benchmark_svg.nim | 5 +-- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index fe51b71..acca6bb 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -2264,49 +2264,49 @@ when defined(pixieSweeps): break inc i - i = 0 - while i < sweeps.len: - # TODO: Maybe finds all cuts first, add them to array, cut all lines at once. - var crossCuts: seq[float32] + # i = 0 + # while i < sweeps.len: + # # TODO: Maybe finds all cuts first, add them to array, cut all lines at once. + # var crossCuts: seq[float32] - # echo i, " cut?" + # # echo i, " cut?" - for aIndex in 0 ..< sweeps[i].len: - let a = sweeps[i][aIndex] - # echo i, ":", sweeps.len, ":", cutLines.len - let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) - for bIndex in aIndex + 1 ..< sweeps[i].len: - let b = sweeps[i][bIndex] - let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1])) - var at: Vec2 - if intersectsInner(aSeg, bSeg, at): - crossCuts.binaryInsert(at.y) + # for aIndex in 0 ..< sweeps[i].len: + # let a = sweeps[i][aIndex] + # # echo i, ":", sweeps.len, ":", cutLines.len + # let aSeg = segment(vec2(a.atx, cutLines[i]), vec2(a.tox, cutLines[i+1])) + # for bIndex in aIndex + 1 ..< sweeps[i].len: + # let b = sweeps[i][bIndex] + # let bSeg = segment(vec2(b.atx, cutLines[i]), vec2(b.tox, cutLines[i+1])) + # var at: Vec2 + # if intersectsInner(aSeg, bSeg, at): + # crossCuts.binaryInsert(at.y) - if crossCuts.len > 0: - var - thisSweep = sweeps[i] - yTop = cutLines[i] - yBottom = cutLines[i + 1] - sweeps[i].setLen(0) + # if crossCuts.len > 0: + # var + # thisSweep = sweeps[i] + # yTop = cutLines[i] + # yBottom = cutLines[i + 1] + # sweeps[i].setLen(0) - for k in crossCuts: - let prevLen = cutLines.len - cutLines.binaryInsert(k) - if prevLen != cutLines.len: - sweeps.insert(newSeq[SweepLine](), i + 1) + # for k in crossCuts: + # let prevLen = cutLines.len + # cutLines.binaryInsert(k) + # if prevLen != cutLines.len: + # sweeps.insert(newSeq[SweepLine](), i + 1) - for a in thisSweep: - var seg = segment(vec2(a.atx, yTop), vec2(a.tox, yBottom)) - var at: Vec2 - for j, cutterLine in crossCuts: - if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at): - sweeps[i+j].add(toLine((segment(seg.at, at), a.winding))) - seg = segment(at, seg.to) - sweeps[i+crossCuts.len].add(toLine((seg, a.winding))) + # for a in thisSweep: + # var seg = segment(vec2(a.atx, yTop), vec2(a.tox, yBottom)) + # var at: Vec2 + # for j, cutterLine in crossCuts: + # if intersects(line(vec2(0, cutterLine), vec2(1, cutterLine)), seg, at): + # sweeps[i+j].add(toLine((segment(seg.at, at), a.winding))) + # seg = segment(at, seg.to) + # sweeps[i+crossCuts.len].add(toLine((seg, a.winding))) - i += crossCuts.len + # i += crossCuts.len - inc i + # inc i i = 0 while i < sweeps.len: diff --git a/tests/benchmark_svg.nim b/tests/benchmark_svg.nim index b44ffc3..c158d6d 100644 --- a/tests/benchmark_svg.nim +++ b/tests/benchmark_svg.nim @@ -1,9 +1,6 @@ -import benchy, pixie/fileformats/svg, pixie/fileformats/png +import benchy, pixie/fileformats/svg let data = readFile("tests/fileformats/svg/Ghostscript_Tiger.svg") -#let data = readFile("tests/fileformats/svg/rotatedRect.svg") - -writeFile("tiger.png", decodeSvg(data).encodePng()) timeIt "svg decode": discard decodeSvg(data) From 2f9ae6157aec526376f6bd0ef52ac8bfa04c8270 Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 4 Dec 2021 17:42:47 -0800 Subject: [PATCH 6/6] Added rounded rect. --- experiments/rounded_rect.nim | 123 +++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 experiments/rounded_rect.nim diff --git a/experiments/rounded_rect.nim b/experiments/rounded_rect.nim new file mode 100644 index 0000000..8313e3a --- /dev/null +++ b/experiments/rounded_rect.nim @@ -0,0 +1,123 @@ +import benchy, chroma, pixie, pixie/internal, strformat +import benchy, chroma, pixie + +proc newRoundedRectImage1(w, h, r: int, color: Color): Image = + result = newImage(w, h) + let ctx = newContext(result) + ctx.fillStyle = color(0, 1, 0, 1) + let + pos = vec2(0, 0) + wh = vec2(w.float32, h.float32) + r = r.float32 + ctx.fillRoundedRect(rect(pos, wh), r) + +proc newRoundedRectImage15(w, h, r: int, color: Color): Image = + let path = newPath() + let + pos = vec2(0, 0) + wh = vec2(w.float32, h.float32) + r = r.float32 + path.roundedRect(rect(pos, wh), r, r, r, r) + result = path.fillImage(w, h, color(0, 1, 0, 1)) + +proc newRoundedRectImage2(w, h, r: int, color: Color): Image = + result = newImage(w, h) + result.fill(color) + + let + w1 = w - 1 + h1 = h - 1 + for y in 0 ..< r: + for x in 0 ..< r: + var a: float32 = 0 + for s in 0 ..< 5: + let + yc = y.float32 + s.float32 / 5 + (1 / 5 / 2) + xc = r.float32 - sqrt(r.float32*r.float32 - (yc - r.float32) ^ 2) + let mid = (x.float32 - xc + 1).clamp(0, 1) + a += 1/5 * mid + + if a < 1: + var c = color + c.a = a + let cx = c.rgbx + result.setRgbaUnsafe(x, y, cx) + result.setRgbaUnsafe(w1 - x, y, cx) + result.setRgbaUnsafe(w1 - x, h1 - y, cx) + result.setRgbaUnsafe(x, h1 - y, cx) + +proc newRoundedRectImage3(w, h, r: int, color: Color): Image = + result = newImage(w, h) + result.fill(color) + + if r == 0: + return + + const + q = 5 + qf = q.float32 + qoffset: float32 = (1 / qf / 2) + + let + r = r.clamp(0, min(w, h) div 2) + rf = r.float32 + w1 = w - 1 + h1 = h - 1 + rgbx = color.rgbx + channels = [rgbx.r.uint32, rgbx.g.uint32, rgbx.b.uint32, rgbx.a.uint32] + + var coverage = newSeq[uint8](r) + + for y in 0 ..< r: + zeroMem(coverage[0].addr, coverage.len) + var yf: float32 = y.float32 + qoffset + for m in 0 ..< q: + let hit = sqrt(rf^2 - yf^2) + coverage[hit.int] += max((1 - (hit - hit.trunc)) * 255 / qf, 0).uint8 + for x in hit.int + 1 ..< r: + coverage[x] += (255 div q).uint8 + yf += 1 / qf + + for x in 0 ..< r: + let coverage = 255 - coverage[x] + if coverage != 255: + var cx: ColorRGBX + cx.r = ((channels[0] * coverage) div 255).uint8 + cx.g = ((channels[1] * coverage) div 255).uint8 + cx.b = ((channels[2] * coverage) div 255).uint8 + cx.a = ((channels[3] * coverage) div 255).uint8 + + let + xn = r - x - 1 + yn = r - y - 1 + result.setRgbaUnsafe(xn, yn, cx) + result.setRgbaUnsafe(w1 - xn, yn, cx) + result.setRgbaUnsafe(w1 - xn, h1 - yn, cx) + result.setRgbaUnsafe(xn, h1 - yn, cx) + +const r = 16 + +let img1 = newRoundedRectImage1(200, 200, r, color(0, 1, 0, 1)) +img1.writeFile("rrect_current.png") +let img2 = newRoundedRectImage3(200, 200, r, color(0, 1, 0, 1)) +img2.writeFile("rrect_new.png") + +let (diffScore, diffImage) = diff(img1, img2) +echo &"score: {diffScore}" +diffImage.writeFile("rrect_diff.png") + +timeIt "fill rounded rect via path 1": + for i in 0 ..< 10: + discard newRoundedRectImage1(200, 200, r, color(0, 1, 0, 1)) + +timeIt "fill rounded rect via path 1.5": + for i in 0 ..< 10: + discard newRoundedRectImage15(200, 200, r, color(0, 1, 0, 1)) + +timeIt "fill rounded rect via math 2": + for i in 0 ..< 10: + discard newRoundedRectImage2(200, 200, 50, color(0, 1, 0, 1)) + +timeIt "fill rounded rect via math 3": + for i in 0 ..< 10: + discard newRoundedRectImage3(200, 200, r, color(0, 1, 0, 1))