Experiment with sweeps.
749
experiments/sweeps.nim
Normal file
|
@ -0,0 +1,749 @@
|
|||
|
||||
import algorithm, bumpy, chroma, pixie/images, print,
|
||||
sequtils, vmath, benchy
|
||||
|
||||
import pixie, pixie/paths {.all.}
|
||||
|
||||
|
||||
printColors = false
|
||||
|
||||
proc intersects*(a, b: Segment, at: var Vec2): bool {.inline.} =
|
||||
## Checks if the a segment intersects b segment.
|
||||
## If it returns true, at will have point of intersection
|
||||
let
|
||||
s1 = a.to - a.at
|
||||
s2 = b.to - b.at
|
||||
denominator = (-s2.x * s1.y + s1.x * s2.y)
|
||||
s = (-s1.y * (a.at.x - b.at.x) + s1.x * (a.at.y - b.at.y)) / denominator
|
||||
t = (s2.x * (a.at.y - b.at.y) - s2.y * (a.at.x - b.at.x)) / denominator
|
||||
|
||||
if s > 0 and s < 1 and t > 0 and t < 1:
|
||||
at = a.at + (t * s1)
|
||||
return true
|
||||
|
||||
proc pixelCover(a0, b0: Vec2): float32 =
|
||||
## Returns the amount of area a given segment sweeps to the right
|
||||
## in a [0,0 to 1,1] box.
|
||||
var
|
||||
a = a0
|
||||
b = b0
|
||||
aI: Vec2
|
||||
bI: Vec2
|
||||
area: float32 = 0.0
|
||||
|
||||
# # Sort A on top.
|
||||
# if a.y > b.y:
|
||||
# let tmp = a
|
||||
# a = b
|
||||
# b = tmp
|
||||
|
||||
# if (b.y < 0 or a.y > 1) or # Above or bellow, no effect.
|
||||
# (a.x >= 1 and b.x >= 1) or # To the right, no effect.
|
||||
# (a.y == b.y): # Horizontal line, no effect.
|
||||
# return 0
|
||||
|
||||
if (a.x < 0 and b.x < 0) or # Both to the left.
|
||||
(a.x == b.x): # Vertical line
|
||||
# Area of the rectangle:
|
||||
return (1 - clamp(a.x, 0, 1)) * (min(b.y, 1) - max(a.y, 0))
|
||||
|
||||
else:
|
||||
# y = mm*x + bb
|
||||
let
|
||||
mm: float32 = (b.y - a.y) / (b.x - a.x)
|
||||
bb: float32 = a.y - mm * a.x
|
||||
|
||||
if a.x >= 0 and a.x <= 1 and a.y >= 0 and a.y <= 1:
|
||||
# A is in pixel bounds.
|
||||
aI = a
|
||||
else:
|
||||
aI = vec2((0 - bb) / mm, 0)
|
||||
if aI.x < 0:
|
||||
let y = mm * 0 + bb
|
||||
# Area of the extra rectangle.
|
||||
area += (min(bb, 1) - max(a.y, 0)).clamp(0, 1)
|
||||
aI = vec2(0, y.clamp(0, 1))
|
||||
elif aI.x > 1:
|
||||
let y = mm * 1 + bb
|
||||
aI = vec2(1, y.clamp(0, 1))
|
||||
|
||||
if b.x >= 0 and b.x <= 1 and b.y >= 0 and b.y <= 1:
|
||||
# B is in pixel bounds.
|
||||
bI = b
|
||||
else:
|
||||
bI = vec2((1 - bb) / mm, 1)
|
||||
if bI.x < 0:
|
||||
let y = mm * 0 + bb
|
||||
# Area of the extra rectangle.
|
||||
area += (min(b.y, 1) - max(bb, 0)).clamp(0, 1)
|
||||
bI = vec2(0, y.clamp(0, 1))
|
||||
elif bI.x > 1:
|
||||
let y = mm * 1 + bb
|
||||
bI = vec2(1, y.clamp(0, 1))
|
||||
|
||||
area += ((1 - aI.x) + (1 - bI.x)) / 2 * (bI.y - aI.y)
|
||||
return area
|
||||
|
||||
proc intersectsInner*(a, b: Segment, at: var Vec2): bool {.inline.} =
|
||||
## Checks if the a segment intersects b segment.
|
||||
## If it returns true, at will have point of intersection
|
||||
let
|
||||
s1 = a.to - a.at
|
||||
s2 = b.to - b.at
|
||||
denominator = (-s2.x * s1.y + s1.x * s2.y)
|
||||
s = (-s1.y * (a.at.x - b.at.x) + s1.x * (a.at.y - b.at.y)) / denominator
|
||||
t = (s2.x * (a.at.y - b.at.y) - s2.y * (a.at.x - b.at.x)) / denominator
|
||||
|
||||
if s > 0 and s < 1 and t >= 0 and t <= 1:
|
||||
at = a.at + (t * s1)
|
||||
return true
|
||||
|
||||
type
|
||||
|
||||
Trapezoid = object
|
||||
nw, ne, se, sw: Vec2
|
||||
|
||||
Line = object
|
||||
#m, x, b: float32
|
||||
atx, tox: float32
|
||||
|
||||
proc toLine(s: Segment): Line =
|
||||
var line = Line()
|
||||
# y = mx + b
|
||||
line.atx = s.at.x
|
||||
line.tox = s.to.x
|
||||
# line.m = (s.at.y - s.to.y) / (s.at.x - s.to.x)
|
||||
# line.b = s.at.y - line.m * s.at.x
|
||||
return line
|
||||
|
||||
proc roundBy*(v: Vec2, n: float32): Vec2 {.inline.} =
|
||||
result.x = sign(v.x) * round(abs(v.x) / n) * n
|
||||
result.y = sign(v.y) * round(abs(v.y) / n) * n
|
||||
|
||||
proc fillPath2(mask: Mask, p: Path) =
|
||||
|
||||
var polygons = p.commandsToShapes()
|
||||
|
||||
const q = 1/256.0
|
||||
|
||||
# Creates segment q, quantize and remove horizontal lines.
|
||||
var segments1: seq[Segment]
|
||||
for shape in polygons:
|
||||
for s in shape.segments:
|
||||
var s = s
|
||||
s.at = s.at.roundBy(q)
|
||||
s.to = s.to.roundBy(q)
|
||||
if s.at.y != s.to.y:
|
||||
if s.at.y > s.to.y:
|
||||
# make sure segments always are at.y higher
|
||||
swap(s.at, s.to)
|
||||
segments1.add(s)
|
||||
segments1.sort(proc(a, b: Segment): int = cmp(a.at.y, b.at.y))
|
||||
|
||||
# Compute cutLines
|
||||
var
|
||||
cutLines: seq[float32]
|
||||
last = segments1[0].at.y
|
||||
bottom = segments1[0].to.y
|
||||
cutLines.add(last)
|
||||
for s in segments1:
|
||||
if s.at.y != last:
|
||||
last = s.at.y
|
||||
cutLines.add(last)
|
||||
if bottom < s.to.y:
|
||||
bottom = s.to.y
|
||||
cutLines.add(bottom)
|
||||
#print cutLines
|
||||
|
||||
var
|
||||
sweeps = newSeq[seq[Line]](cutLines.len - 1) # dont add bottom cutLine
|
||||
lastSeg = 0
|
||||
for i, sweep in sweeps.mpairs:
|
||||
#print "sweep", i, cutLines[i]
|
||||
while segments1[lastSeg].at.y == cutLines[i]:
|
||||
let s = segments1[lastSeg]
|
||||
#print s
|
||||
if s.to.y != cutLines[i + 1]:
|
||||
#print "needs cut?"
|
||||
quit()
|
||||
sweep.add(toLine(segments1[lastSeg]))
|
||||
inc lastSeg
|
||||
if lastSeg >= segments1.len:
|
||||
# Sort the last sweep by X
|
||||
break
|
||||
# Sort the sweep by X
|
||||
sweep.sort proc(a, b: Line): int =
|
||||
result = cmp(a.atx, b.atx)
|
||||
if result == 0:
|
||||
result = cmp(a.tox, b.tox)
|
||||
|
||||
#print sweeps
|
||||
|
||||
proc fillCoverage(y: int, currCutLine: int, sweep: seq[Line]) =
|
||||
|
||||
let
|
||||
sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
yFracTop = (y.float32 - cutLines[currCutLine]) / sweepHeight
|
||||
yFracBottom = (y.float32 + 1 - cutLines[currCutLine]) / sweepHeight
|
||||
var i = 0
|
||||
while i < sweep.len:
|
||||
let
|
||||
nwX = mix(sweep[i+0].atx, sweep[i+0].tox, yFracTop)
|
||||
neX = mix(sweep[i+1].atx, sweep[i+1].tox, yFracTop)
|
||||
|
||||
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
|
||||
|
||||
minEi = min(neX, seX).int
|
||||
maxEi = max(neX, seX).ceil.int
|
||||
|
||||
template write(x, y: int, alpha: float32) =
|
||||
let backdrop = mask.getValueUnsafe(x, y)
|
||||
mask.setValueUnsafe(x, y, backdrop + (alpha * 255).uint8)
|
||||
|
||||
let
|
||||
nw = vec2(sweep[i+0].atx, cutLines[currCutLine])
|
||||
sw = vec2(sweep[i+0].tox, cutLines[currCutLine + 1])
|
||||
for x in minWi ..< maxWi:
|
||||
var area = pixelCover(nw - vec2(x.float32, y.float32), sw - vec2(x.float32, y.float32))
|
||||
write(x, y, area)
|
||||
|
||||
let x = maxWi
|
||||
var midArea = pixelCover(nw - vec2(x.float32, y.float32), sw - vec2(x.float32, y.float32))
|
||||
for x in maxWi ..< minEi:
|
||||
write(x, y, midArea)
|
||||
|
||||
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))
|
||||
write(x, y, area)
|
||||
|
||||
i += 2
|
||||
|
||||
|
||||
# ## x10 slower
|
||||
# for x in 0 ..< mask.width:
|
||||
# for i, line in sweep:
|
||||
# let
|
||||
# segat = vec2(line.atx, cutLines[currCutLine])
|
||||
# segto = vec2(line.tox, cutLines[currCutLine + 1])
|
||||
# var area = pixelCover(segat - vec2(x.float32, y.float32), segto - vec2(x.float32, y.float32))
|
||||
# if i mod 2 == 1:
|
||||
# area = -area
|
||||
# let backdrop = mask.getValueUnsafe(x, y)
|
||||
# mask.setValueUnsafe(x, y, backdrop + (area * 255).uint8)
|
||||
|
||||
|
||||
# var i = 0
|
||||
# let
|
||||
# sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
# yFracTop = (y.float32 - cutLines[currCutLine]) / sweepHeight
|
||||
# yFracBottom = (y.float32 + 1 - cutLines[currCutLine]) / sweepHeight
|
||||
# #print "cover", y, sweepHeight, yFrac
|
||||
# while i < sweep.len:
|
||||
# #print "fill", sweep[i].at.x, "..", sweep[i+1].at.x
|
||||
|
||||
|
||||
# var ay = 1.0.float32
|
||||
# if y.float32 < cutLines[currCutLine]:
|
||||
# let a2 = cutLines[currCutLine] - y.float32
|
||||
# #print "cut from top", y.float, cutLines[currCutLine], a2
|
||||
# ay *= a2
|
||||
# if y.float32 + 1 > cutLines[currCutLine + 1]:
|
||||
# let a2 = (y.float32 + 1) - cutLines[currCutLine + 1]
|
||||
# #print "cut from bottom", y.float, cutLines[currCutLine], a2
|
||||
# ay *= a2
|
||||
|
||||
# #if y == 20:
|
||||
# # print "--", y
|
||||
|
||||
# template write(x, y: int, alpha: float32) =
|
||||
# let backdrop = mask.getValueUnsafe(x, y)
|
||||
# mask.setValueUnsafe(x, y, backdrop + (ay * alpha * 255).uint8)
|
||||
|
||||
# let
|
||||
# nwX = mix(sweep[i+0].atx, sweep[i+0].tox, yFracTop)
|
||||
# neX = mix(sweep[i+1].atx, sweep[i+1].tox, yFracTop)
|
||||
|
||||
# swX = mix(sweep[i+0].atx, sweep[i+0].tox, yFracBottom)
|
||||
# seX = mix(sweep[i+1].atx, sweep[i+1].tox, yFracBottom)
|
||||
|
||||
# # minW = min(nwX, swX)
|
||||
# # maxW = max(nwX, swX)
|
||||
# # minE = min(neX, seX)
|
||||
# # maxE = max(neX, seX)
|
||||
|
||||
|
||||
|
||||
# var
|
||||
# endWAt: float32 = 0
|
||||
# endWTo: float32 = 0
|
||||
# endWArea: float32 = 0
|
||||
|
||||
# slopeWUp = swX < nwX
|
||||
# slopeWAt: float32 = 0
|
||||
# slopeWTo: float32 = 0
|
||||
# slopeWArea: float32 = 0
|
||||
# slopeWRate: float32 = 0
|
||||
|
||||
# transitionWAt: float32 = 0
|
||||
# transitionWTo: float32 = 0
|
||||
# transitionWArea: float32 = 0
|
||||
|
||||
# fillAt: float32 = 0
|
||||
# fillTo: float32 = 0
|
||||
|
||||
# transitionEAt: float32 = 0
|
||||
# transitionETo: float32 = 0
|
||||
# transitionEArea: float32 = 0
|
||||
|
||||
# slopeEUp = seX < neX
|
||||
# slopeEAt: float32 = 0
|
||||
# slopeETo: float32 = 0
|
||||
# slopeEArea: float32 = 0
|
||||
# slopeERate: float32 = 0
|
||||
|
||||
# endEAt: float32 = 0
|
||||
# endETo: float32 = 0
|
||||
# endEArea: float32 = 0
|
||||
|
||||
# if slopeWUp:
|
||||
# endWAt = swX.floor
|
||||
# endWTo = swX.ceil
|
||||
|
||||
# slopeWAt = endWTo
|
||||
# slopeWTo = nwX.floor
|
||||
|
||||
# transitionWAt = slopeWAt
|
||||
# transitionWTo = nwX.ceil
|
||||
|
||||
# fillAt = transitionWTo
|
||||
|
||||
# else:
|
||||
# endWAt = nwX.floor
|
||||
# endWTo = nwX.ceil
|
||||
|
||||
# slopeWAt = endWTo
|
||||
# slopeWTo = swX.floor
|
||||
|
||||
# transitionWAt = slopeWAt
|
||||
# transitionWTo = swX.ceil
|
||||
|
||||
# fillAt = transitionWTo
|
||||
|
||||
|
||||
# if slopeEUp:
|
||||
|
||||
# fillTo = seX.floor
|
||||
|
||||
# transitionEAt = fillTo
|
||||
# transitionETo = seX.ceil
|
||||
|
||||
# slopeEAt = transitionETo
|
||||
# slopeETo = neX.floor
|
||||
|
||||
# endEAt = slopeETo
|
||||
# endETo = neX.ceil
|
||||
|
||||
# else:
|
||||
|
||||
# fillTo = neX.floor
|
||||
|
||||
# transitionEAt = fillTo
|
||||
# transitionETo = neX.ceil
|
||||
|
||||
# slopeEAt = transitionETo
|
||||
# slopeETo = seX.floor
|
||||
|
||||
# endEAt = slopeETo
|
||||
# endETo = seX.ceil
|
||||
|
||||
|
||||
# let
|
||||
# segat = vec2(line.atx, cutLines[currCutLine])
|
||||
# segto = vec2(line.tox, cutLines[currCutLine + 1])
|
||||
|
||||
|
||||
# var area = pixelCover(segat - vec2(x.float32, y.float32), segto - vec2(x.float32, y.float32))
|
||||
# if i mod 2 == 1:
|
||||
# area = -area
|
||||
|
||||
|
||||
# #print endWAt, slopeWAt, transitionWAt, fillAt, fillTo, transitionEAt, slopeEAt, endEAt
|
||||
|
||||
# # if nwX < swX:
|
||||
# # print nwX.int, swX.int
|
||||
# # for x in nwX.int ..< swX.int:
|
||||
# # var a = 0.5 # ((x.float32 - nwX) / (swX - nwX))
|
||||
# # #print a
|
||||
# # write(x, y, a.clamp(0, a))
|
||||
# # else:
|
||||
# # for x in swX.int ..< nwX.int:
|
||||
# # var a = 0.5 #((x.float32 - swX) / (nwX - swX))
|
||||
# # #print a
|
||||
# # write(x, y, a.clamp(0, a))
|
||||
|
||||
# #write(wX.int, y, 1 - (wX - wX.floor))
|
||||
|
||||
# # for x in endWAt.int ..< endWTo.int:
|
||||
# # write(x, y, 0.1)
|
||||
|
||||
# # for x in slopeWAt.int ..< slopeWTo.int:
|
||||
# # write(x, y, 0.25)
|
||||
|
||||
# # for x in transitionWAt.int ..< transitionWTo.int:
|
||||
# # write(x, y, 0.50)
|
||||
|
||||
# for x in fillAt.int ..< fillTo.int:
|
||||
# write(x, y, 1)
|
||||
|
||||
# # for x in transitionEAt.int ..< transitionETo.int:
|
||||
# # write(x, y, 0.50)
|
||||
|
||||
# # for x in slopeEAt.int ..< slopeETo.int:
|
||||
# # write(x, y, 0.25)
|
||||
|
||||
# # for x in endEAt.int ..< endETo.int:
|
||||
# # write(x, y, 0.1)
|
||||
|
||||
# # print 1 - (eX - eX.floor)
|
||||
# # write(eX.int - 1, y, 1 - (eX - eX.floor))
|
||||
|
||||
# # if neX < seX:
|
||||
# # for x in neX.int ..< seX.int:
|
||||
# # var a = 0.5 # ((neX - x.float32) / (seX - neX))
|
||||
# # #print a
|
||||
# # write(x, y, a.clamp(0, a))
|
||||
# # else:
|
||||
# # for x in seX.int ..< neX.int:
|
||||
# # var a = 0.5 # ((seX - x.float32) / (neX - seX))
|
||||
# # #print a
|
||||
# # write(x, y, a.clamp(0, a))
|
||||
|
||||
# i += 2
|
||||
|
||||
# let quality = 5
|
||||
# for m in 0 ..< quality:
|
||||
# let
|
||||
# sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
# yFrac = (y.float32 + (m.float32 / quality.float32) - cutLines[currCutLine]) / sweepHeight
|
||||
# if yFrac < 0.0 or yFrac >= 1.0:
|
||||
# continue
|
||||
# var i = 0
|
||||
# while i < sweep.len:
|
||||
# let
|
||||
# minXf1 = mix(sweep[i+0].at.x, sweep[i+0].to.x, yFrac)
|
||||
# maxXf1 = mix(sweep[i+1].at.x, sweep[i+1].to.x, yFrac)
|
||||
# minXi1 = minXf1.int
|
||||
# maxXi1 = maxXf1.int
|
||||
# for x in minXi1 ..< maxXi1:
|
||||
# let backdrop = mask.getValueUnsafe(x, y)
|
||||
# mask.setValueUnsafe(x, y, backdrop + (255 div quality).uint8)
|
||||
# # if x == 100 and y == 165:
|
||||
# # print backdrop, 255 div quality
|
||||
# # print mask.getValueUnsafe(x, y)
|
||||
# i += 2
|
||||
|
||||
# let
|
||||
# sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
# yFrac = (y.float32 - cutLines[currCutLine]) / sweepHeight
|
||||
# var i = 0
|
||||
# while i < sweep.len:
|
||||
# let
|
||||
# minXf1 = mix(sweep[i+0].atx, sweep[i+0].tox, yFrac)
|
||||
# maxXf1 = mix(sweep[i+1].atx, sweep[i+1].tox, yFrac)
|
||||
# minXi1 = minXf1.floor.int
|
||||
# maxXi1 = maxXf1.floor.int
|
||||
# for x in minXi1 .. maxXi1:
|
||||
# mask.setValueUnsafe(x, y, 255)
|
||||
# i += 2
|
||||
|
||||
var
|
||||
currCutLine = 0
|
||||
for scanLine in cutLines[0].int ..< cutLines[^1].ceil.int:
|
||||
#print scanLine, "..<", scanLine + 1
|
||||
#print " ", currCutLine, cutLines[currCutLine], "..<", cutLines[currCutLine + 1]
|
||||
fillCoverage(scanLine, currCutLine, sweeps[currCutLine])
|
||||
|
||||
while cutLines[currCutLine + 1] < scanLine.float + 1.0:
|
||||
inc currCutLine
|
||||
if currCutLine == sweeps.len:
|
||||
break
|
||||
#print " ", currCutLine, cutLines[currCutLine], "..<", cutLines[currCutLine + 1]
|
||||
fillCoverage(scanLine, currCutLine, sweeps[currCutLine])
|
||||
|
||||
# print sweeps[^1]
|
||||
# print cutLines
|
||||
|
||||
|
||||
|
||||
|
||||
# var segments: seq[Segment]
|
||||
# while segments1.len > 0:
|
||||
# #print segments1.len, segments.len
|
||||
# var s = segments1.pop()
|
||||
# var collision = false
|
||||
# for y in cutLines:
|
||||
# let scanLine = line(vec2(0, y), vec2(1, y))
|
||||
# var at: Vec2
|
||||
# if intersects(scanLine, s, at):
|
||||
# at = at.roundBy(q)
|
||||
# at.y = y
|
||||
# if s.at.y != at.y and s.to.y != at.y:
|
||||
# #print "seg2yline intersects!", a, y, at
|
||||
# collision = true
|
||||
# var s1 = segment(s.at, at)
|
||||
# var s2 = segment(at, s.to)
|
||||
# #print s.length, "->", s1.length, s2.length
|
||||
# segments1.add(s1)
|
||||
# segments1.add(s2)
|
||||
# break
|
||||
|
||||
# if not collision:
|
||||
# # means its touching, not intersecting
|
||||
# segments.add(s)
|
||||
|
||||
# # sort at/to in segments
|
||||
# # for s in segments.mitems:
|
||||
# # if s.at.y > s.to.y:
|
||||
# # swap(s.at, s.to)
|
||||
|
||||
|
||||
# #let blender = blendMode.blender()
|
||||
|
||||
# for yScanLine in cutLines[0..^2]:
|
||||
|
||||
# var scanSegments: seq[Segment]
|
||||
# for s in segments:
|
||||
# if s.at.y == yScanLine:
|
||||
# scanSegments.add(s)
|
||||
# scanSegments.sort(proc(a, b: Segment): int =
|
||||
# cmp(a.at.x, b.at.x))
|
||||
|
||||
# # if scanSegments.len mod 2 != 0:
|
||||
# # print "error???"
|
||||
# # print yScanLine
|
||||
# # print scanSegments
|
||||
# # quit()
|
||||
|
||||
# # TODO: winding rules will go here
|
||||
|
||||
# var trapezoids: seq[Trapezoid]
|
||||
# for i in 0 ..< scanSegments.len div 2:
|
||||
# let
|
||||
# a = scanSegments[i*2+0]
|
||||
# b = scanSegments[i*2+1]
|
||||
|
||||
# assert a.at.y == b.at.y
|
||||
# assert a.to.y == b.to.y
|
||||
# #assert a.at.x < b.at.x
|
||||
# #assert a.to.x < b.to.x
|
||||
|
||||
# trapezoids.add(Trapezoid(
|
||||
# nw: a.at,
|
||||
# ne: b.at,
|
||||
# se: b.to, # + vec2(0,0.7),
|
||||
# sw: a.to # + vec2(0,0.7)
|
||||
# ))
|
||||
|
||||
# var i = 0
|
||||
# while i < trapezoids.len:
|
||||
|
||||
# let t = trapezoids[i]
|
||||
# # print t
|
||||
# let
|
||||
# nw = t.nw
|
||||
# ne = t.ne
|
||||
# se = t.se
|
||||
# sw = t.sw
|
||||
|
||||
# let
|
||||
# height = sw.y - nw.y
|
||||
# minYf = nw.y
|
||||
# maxYf = sw.y
|
||||
# minYi = minYf.floor.int
|
||||
# maxYi = maxYf.floor.int
|
||||
|
||||
# # print t
|
||||
|
||||
# for y in minYi .. maxYi:
|
||||
# let
|
||||
# yFrac = (y.float - nw.y) / height
|
||||
# minXf = mix(nw.x, sw.x, yFrac)
|
||||
# maxXf = mix(ne.x, se.x, yFrac)
|
||||
# minXi = minXf.floor.int
|
||||
# maxXi = maxXf.floor.int
|
||||
# #print yFrac
|
||||
# # if not(minY.int == 58 or maxY.int == 58) or minX > 100:
|
||||
# # continue
|
||||
|
||||
# var ay: float32
|
||||
# if y == minYi and y == maxYi:
|
||||
# ay = maxYf - minYf
|
||||
# # print "middle", maxYf, minYf, a
|
||||
# #print "double", y, a, minY, maxY, round(a * 255)
|
||||
# elif y == minYi:
|
||||
# ay = (1 - (minYf - float32(minYi)))
|
||||
# # print "min y", minYf, minYi, a
|
||||
# #print "s", y, a, minY, round(a * 255)
|
||||
# elif y == maxYi:
|
||||
# ay = (maxYf - float32(maxYi))
|
||||
# #print "max y", maxYf, maxYi, a
|
||||
# # print "e", y, a, maxY, round(a * 255)
|
||||
# else:
|
||||
# ay = 1.0
|
||||
|
||||
# for x in minXi .. maxXi:
|
||||
# var ax: float32
|
||||
# # if x == minXi:
|
||||
# # a2 = (1 - (minXf - float32(minXi)))
|
||||
# # #a2 = 1.0
|
||||
# # elif x == maxXi:
|
||||
# # a2 = (maxXf - float32(maxXi))
|
||||
# # #a2 = 1.0
|
||||
# # else:
|
||||
# # a2 = 1.0
|
||||
|
||||
# if x.float32 < max(nw.x, sw.x):
|
||||
# ax = 0.5
|
||||
# elif x.float32 > min(ne.x, se.x):
|
||||
# ax = 0.25
|
||||
# else:
|
||||
# ax = 1.0
|
||||
|
||||
# let backdrop = mask.getValueUnsafe(x, y)
|
||||
# mask.setValueUnsafe(x, y, backdrop + floor(255 * ay * ax).uint8)
|
||||
# # if x == 100 and y == 172:
|
||||
# # print backdrop, round(255 * a * a2).uint8
|
||||
# # print mask.getValueUnsafe(x, y)
|
||||
|
||||
# inc i
|
||||
|
||||
block:
|
||||
# Rect
|
||||
print "rect"
|
||||
#var image = newImage(200, 200)
|
||||
|
||||
# rect
|
||||
# var p = Path()
|
||||
# p.moveTo(50.5, 50.5)
|
||||
# p.lineTo(50.5, 150.5)
|
||||
# p.lineTo(150.5, 150.5)
|
||||
# p.lineTo(150.5, 50.5)
|
||||
# p.closePath()
|
||||
|
||||
## rhobus
|
||||
var p = Path()
|
||||
p.moveTo(100, 50)
|
||||
p.lineTo(150, 100)
|
||||
p.lineTo(100, 150)
|
||||
p.lineTo(50, 100)
|
||||
p.closePath()
|
||||
|
||||
# ## heart
|
||||
# var p = parsePath("""
|
||||
# M 20 60
|
||||
# A 40 40 90 0 1 100 60
|
||||
# A 40 40 90 0 1 180 60
|
||||
# Q 180 120 100 180
|
||||
# Q 20 120 20 60
|
||||
# z
|
||||
# """)
|
||||
|
||||
## cricle
|
||||
# var p = Path()
|
||||
# p.arc(100, 100, 50, 0, PI * 2, true)
|
||||
# p.closePath()
|
||||
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
#image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
var mask = newMask(200, 200)
|
||||
timeIt "rect sweeps", 100:
|
||||
for i in 0 ..< 100:
|
||||
mask.fill(0)
|
||||
mask.fillPath2(p)
|
||||
#image.fillPath2(p, color(0, 0, 0, 1))
|
||||
mask.writeFile("experiments/trapezoids/output_sweep.png")
|
||||
|
||||
var mask2 = newMask(200, 200)
|
||||
timeIt "rect scanline", 10:
|
||||
for i in 0 ..< 100:
|
||||
mask2.fill(0)
|
||||
mask2.fillPath(p)
|
||||
mask2.writeFile("experiments/trapezoids/output_scanline.png")
|
||||
|
||||
let (score, image) = diff(mask.newImage, mask2.newImage)
|
||||
print score
|
||||
image.writeFile("experiments/trapezoids/output_diff.png")
|
||||
|
||||
|
||||
|
||||
# block:
|
||||
# # Rhombus
|
||||
# print "rhombus"
|
||||
# var image = newImage(200, 200)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = Path()
|
||||
# p.moveTo(100, 50)
|
||||
# p.lineTo(150, 100)
|
||||
# p.lineTo(100, 150)
|
||||
# p.lineTo(50, 100)
|
||||
# p.closePath()
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/rhombus.png")
|
||||
|
||||
# block:
|
||||
# # heart
|
||||
# print "heart"
|
||||
# var image = newImage(400, 400)
|
||||
# image.fill(rgba(0, 0, 0, 0))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 40 120 A 80 80 90 0 1 200 120 A 80 80 90 0 1 360 120
|
||||
# Q 360 240 200 360 Q 40 240 40 120 z
|
||||
# """)
|
||||
|
||||
# var mask = newMask(image)
|
||||
# mask.fillPath2(p)
|
||||
|
||||
# image.draw(mask, blendMode = bmOverwrite)
|
||||
|
||||
# image.writeFile("experiments/trapezoids/heart.png")
|
||||
|
||||
# block:
|
||||
# # l
|
||||
# print "l"
|
||||
# var image = newImage(500, 800)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 236 20 Q 150 22 114 57 T 78 166 V 790 L 171 806 V 181 Q 171 158 175 143 T 188 119 T 212 105.5 T 249 98 Z
|
||||
# """)
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/l.png")
|
||||
|
||||
# block:
|
||||
# # g
|
||||
# print "g"
|
||||
# var image = newImage(500, 800)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 406 538 Q 394 546 359.5 558.5 T 279 571 Q 232 571 190.5 556 T 118 509.5 T 69 431 T 51 319 Q 51 262 68 214.5 T 117.5 132.5 T 197 78.5 T 303 59 Q 368 59 416.5 68.5 T 498 86 V 550 Q 498 670 436 724 T 248 778 Q 199 778 155.5 770 T 80 751 L 97 670 Q 125 681 165.5 689.5 T 250 698 Q 333 698 369.5 665 T 406 560 V 538 Z M 405 152 Q 391 148 367.5 144.5 T 304 141 Q 229 141 188.5 190 T 148 320 Q 148 365 159.5 397 T 190.5 450 T 235.5 481 T 288 491 Q 325 491 356 480.5 T 405 456 V 152 Z
|
||||
# """)
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/g.png")
|
481
experiments/sweeps2.nim
Normal file
|
@ -0,0 +1,481 @@
|
|||
|
||||
import algorithm, bumpy, chroma, pixie/images, print,
|
||||
sequtils, vmath, benchy, fidget2/perf
|
||||
|
||||
import pixie, pixie/paths {.all.}
|
||||
|
||||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc pixelCover(a0, b0: Vec2): float32 =
|
||||
## Returns the amount of area a given segment sweeps to the right
|
||||
## in a [0,0 to 1,1] box.
|
||||
var
|
||||
a = a0
|
||||
b = b0
|
||||
aI: Vec2
|
||||
bI: Vec2
|
||||
area: float32 = 0.0
|
||||
|
||||
# # Sort A on top.
|
||||
# if a.y > b.y:
|
||||
# let tmp = a
|
||||
# a = b
|
||||
# b = tmp
|
||||
|
||||
# if (b.y < 0 or a.y > 1) or # Above or bellow, no effect.
|
||||
# (a.x >= 1 and b.x >= 1) or # To the right, no effect.
|
||||
# (a.y == b.y): # Horizontal line, no effect.
|
||||
# return 0
|
||||
|
||||
if (a.x < 0 and b.x < 0) or # Both to the left.
|
||||
(a.x == b.x): # Vertical line
|
||||
# Area of the rectangle:
|
||||
return (1 - clamp(a.x, 0, 1)) * (min(b.y, 1) - max(a.y, 0))
|
||||
|
||||
else:
|
||||
# y = mm*x + bb
|
||||
let
|
||||
mm: float32 = (b.y - a.y) / (b.x - a.x)
|
||||
bb: float32 = a.y - mm * a.x
|
||||
|
||||
if a.x >= 0 and a.x <= 1 and a.y >= 0 and a.y <= 1:
|
||||
# A is in pixel bounds.
|
||||
aI = a
|
||||
else:
|
||||
aI = vec2((0 - bb) / mm, 0)
|
||||
if aI.x < 0:
|
||||
let y = mm * 0 + bb
|
||||
# Area of the extra rectangle.
|
||||
area += (min(bb, 1) - max(a.y, 0)).clamp(0, 1)
|
||||
aI = vec2(0, y.clamp(0, 1))
|
||||
elif aI.x > 1:
|
||||
let y = mm * 1 + bb
|
||||
aI = vec2(1, y.clamp(0, 1))
|
||||
|
||||
if b.x >= 0 and b.x <= 1 and b.y >= 0 and b.y <= 1:
|
||||
# B is in pixel bounds.
|
||||
bI = b
|
||||
else:
|
||||
bI = vec2((1 - bb) / mm, 1)
|
||||
if bI.x < 0:
|
||||
let y = mm * 0 + bb
|
||||
# Area of the extra rectangle.
|
||||
area += (min(b.y, 1) - max(bb, 0)).clamp(0, 1)
|
||||
bI = vec2(0, y.clamp(0, 1))
|
||||
elif bI.x > 1:
|
||||
let y = mm * 1 + bb
|
||||
bI = vec2(1, y.clamp(0, 1))
|
||||
|
||||
area += ((1 - aI.x) + (1 - bI.x)) / 2 * (bI.y - aI.y)
|
||||
return area
|
||||
|
||||
proc intersectsInner*(a, b: Segment, at: var Vec2): bool {.inline.} =
|
||||
## Checks if the a segment intersects b segment.
|
||||
## If it returns true, at will have point of intersection
|
||||
let
|
||||
s1 = a.to - a.at
|
||||
s2 = b.to - b.at
|
||||
denominator = (-s2.x * s1.y + s1.x * s2.y)
|
||||
s = (-s1.y * (a.at.x - b.at.x) + s1.x * (a.at.y - b.at.y)) / denominator
|
||||
t = (s2.x * (a.at.y - b.at.y) - s2.y * (a.at.x - b.at.x)) / denominator
|
||||
|
||||
if s > 0 and s < 1 and t > 0 and t < 1:
|
||||
#print s, t
|
||||
at = a.at + (t * s1)
|
||||
return true
|
||||
|
||||
type
|
||||
|
||||
Trapezoid = object
|
||||
nw, ne, se, sw: Vec2
|
||||
|
||||
Line = object
|
||||
#m, x, b: float32
|
||||
atx, tox: float32
|
||||
winding: int16
|
||||
|
||||
proc toLine(s: (Segment, int16)): Line =
|
||||
var line = Line()
|
||||
# y = mx + b
|
||||
line.atx = s[0].at.x
|
||||
line.tox = s[0].to.x
|
||||
line.winding = s[1]
|
||||
# line.m = (s.at.y - s.to.y) / (s.at.x - s.to.x)
|
||||
# line.b = s.at.y - line.m * s.at.x
|
||||
return line
|
||||
|
||||
proc roundBy*(v: Vec2, n: float32): Vec2 {.inline.} =
|
||||
result.x = sign(v.x) * round(abs(v.x) / n) * n
|
||||
result.y = sign(v.y) * round(abs(v.y) / n) * n
|
||||
|
||||
|
||||
proc computeBounds(polygons: seq[seq[Vec2]]): Rect =
|
||||
## Compute the bounds of the segments.
|
||||
var
|
||||
xMin = float32.high
|
||||
xMax = float32.low
|
||||
yMin = float32.high
|
||||
yMax = float32.low
|
||||
for segments in polygons:
|
||||
for v in segments:
|
||||
xMin = min(xMin, v.x)
|
||||
xMax = max(xMax, v.x)
|
||||
yMin = min(yMin, v.y)
|
||||
yMax = max(yMax, v.y)
|
||||
|
||||
if xMin.isNaN() or xMax.isNaN() or yMin.isNaN() or yMax.isNaN():
|
||||
discard
|
||||
else:
|
||||
result.x = xMin
|
||||
result.y = yMin
|
||||
result.w = xMax - xMin
|
||||
result.h = yMax - yMin
|
||||
|
||||
proc binaryInsert(arr: var seq[float32], v: float32) =
|
||||
if arr.len == 0:
|
||||
arr.add(v)
|
||||
return
|
||||
var
|
||||
L = 0
|
||||
R = arr.len - 1
|
||||
while L < R:
|
||||
let m = (L + R) div 2
|
||||
if arr[m] ~= v:
|
||||
return
|
||||
elif arr[m] < v:
|
||||
L = m + 1
|
||||
else: # arr[m] > v:
|
||||
R = m - 1
|
||||
if arr[L] ~= v:
|
||||
return
|
||||
elif arr[L] > v:
|
||||
#print "insert", v, arr, L, R
|
||||
arr.insert(v, L)
|
||||
else:
|
||||
#print "insert", v, arr, L, R
|
||||
arr.insert(v, L + 1)
|
||||
|
||||
|
||||
proc fillPath2(image: Image, p: Path, color: Color, windingRule = wrNonZero, blendMode = bmNormal) =
|
||||
const q = 1/256.0
|
||||
let rgbx = color.rgbx
|
||||
var segments = p.commandsToShapes().shapesToSegments()
|
||||
let
|
||||
bounds = computeBounds(segments).snapToPixels()
|
||||
startX = max(0, bounds.x.int)
|
||||
|
||||
# Create sorted segments and quantize.
|
||||
segments.sort(proc(a, b: (Segment, int16)): int = cmp(a[0].at.y, b[0].at.y))
|
||||
|
||||
# Compute cut lines
|
||||
var cutLines: seq[float32]
|
||||
for s in segments:
|
||||
cutLines.binaryInsert(s[0].at.y)
|
||||
cutLines.binaryInsert(s[0].to.y)
|
||||
|
||||
var
|
||||
sweeps = newSeq[seq[Line]](cutLines.len - 1) # dont add bottom cutLine
|
||||
lastSeg = 0
|
||||
i = 0
|
||||
while i < sweeps.len:
|
||||
|
||||
#for i, sweep in sweeps.mpairs:
|
||||
#print "sweep", i, cutLines[i]
|
||||
|
||||
if lastSeg < segments.len:
|
||||
|
||||
while segments[lastSeg][0].at.y == cutLines[i]:
|
||||
let s = segments[lastSeg]
|
||||
|
||||
if s[0].at.y != s[0].to.y:
|
||||
|
||||
#print s
|
||||
if s[0].to.y != cutLines[i + 1]:
|
||||
#print "needs cut?", s
|
||||
|
||||
#quit("need to cut lines")
|
||||
var at: Vec2
|
||||
var seg = s[0]
|
||||
for j in i ..< sweeps.len:
|
||||
let y = cutLines[j + 1]
|
||||
if intersects(line(vec2(0, y), vec2(1, y)), seg, at):
|
||||
#print "cutting", j, seg
|
||||
#print "add cut", j, segment(seg.at, at)
|
||||
sweeps[j].add(toLine((segment(seg.at, at), s[1])))
|
||||
seg = segment(at, seg.to)
|
||||
else:
|
||||
if seg.at.y != seg.to.y:
|
||||
#print "add rest", j, segment(seg.at, seg.to)
|
||||
sweeps[j].add(toLine(s))
|
||||
# else:
|
||||
# print "micro?"
|
||||
break
|
||||
else:
|
||||
#print "add", s
|
||||
sweeps[i].add(toLine(s))
|
||||
|
||||
inc lastSeg
|
||||
|
||||
if lastSeg >= segments.len:
|
||||
break
|
||||
inc i
|
||||
|
||||
i = 0
|
||||
while i < sweeps.len:
|
||||
for t in 0 ..< 10:
|
||||
# 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
|
||||
if needsCut:
|
||||
var
|
||||
thisSweep = sweeps[i]
|
||||
sweeps[i].setLen(0)
|
||||
sweeps.insert(newSeq[Line](), 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
|
||||
sweeps[i].sort proc(a, b: Line): int =
|
||||
result = cmp(a.atx, b.atx)
|
||||
if result == 0:
|
||||
result = cmp(a.tox, b.tox)
|
||||
|
||||
# Do winding order
|
||||
var
|
||||
pen = 0
|
||||
prevFill = false
|
||||
j = 0
|
||||
# print "sweep", i, "--------------"
|
||||
while j < sweeps[i].len:
|
||||
let a = sweeps[i][j]
|
||||
# print a.winding
|
||||
if a.winding == 1:
|
||||
inc pen
|
||||
if a.winding == -1:
|
||||
dec pen
|
||||
# print j, pen, prevFill, shouldFill(windingRule, pen)
|
||||
let thisFill = shouldFill(windingRule, pen)
|
||||
if prevFill == thisFill:
|
||||
# remove this line
|
||||
# print "remove", j
|
||||
sweeps[i].delete(j)
|
||||
continue
|
||||
prevFill = thisFill
|
||||
inc j
|
||||
|
||||
# print sweeps[i]
|
||||
|
||||
inc i
|
||||
|
||||
#print sweeps
|
||||
# for s in 0 ..< sweeps.len:
|
||||
# let
|
||||
# y1 = cutLines[s]
|
||||
# echo "M -100 ", y1
|
||||
# echo "L 300 ", y1
|
||||
# for line in sweeps[s]:
|
||||
# let
|
||||
# nw = vec2(line.atx, cutLines[s])
|
||||
# sw = vec2(line.tox, cutLines[s + 1])
|
||||
# echo "M ", nw.x, " ", nw.y
|
||||
# echo "L ", sw.x, " ", sw.y
|
||||
|
||||
proc computeCoverage(
|
||||
coverages: var seq[uint8],
|
||||
y: int,
|
||||
startX: int,
|
||||
cutLines: seq[float32],
|
||||
currCutLine: int,
|
||||
sweep: seq[Line]
|
||||
) =
|
||||
|
||||
# if sweep.len mod 2 != 0:
|
||||
# return
|
||||
|
||||
let
|
||||
sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
yFracTop = ((y.float32 - cutLines[currCutLine]) / sweepHeight).clamp(0, 1)
|
||||
yFracBottom = ((y.float32 + 1 - cutLines[currCutLine]) / sweepHeight).clamp(0, 1)
|
||||
var i = 0
|
||||
while i < sweep.len:
|
||||
let
|
||||
nwX = mix(sweep[i+0].atx, sweep[i+0].tox, yFracTop)
|
||||
neX = mix(sweep[i+1].atx, sweep[i+1].tox, yFracTop)
|
||||
|
||||
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
|
||||
|
||||
minEi = min(neX, seX).int
|
||||
maxEi = max(neX, seX).ceil.int
|
||||
|
||||
let
|
||||
nw = vec2(sweep[i+0].atx, cutLines[currCutLine])
|
||||
sw = vec2(sweep[i+0].tox, cutLines[currCutLine + 1])
|
||||
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
|
||||
|
||||
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:
|
||||
coverages[x - startX] += midArea8
|
||||
|
||||
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
|
||||
|
||||
i += 2
|
||||
|
||||
var
|
||||
currCutLine = 0
|
||||
coverages = 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])
|
||||
while cutLines[currCutLine + 1] < scanLine.float + 1.0:
|
||||
inc currCutLine
|
||||
if currCutLine == sweeps.len:
|
||||
break
|
||||
coverages.computeCoverage(scanLine, startX, cutLines, currCutLine, sweeps[currCutLine])
|
||||
|
||||
image.fillCoverage(
|
||||
rgbx,
|
||||
startX = startX,
|
||||
y = scanLine,
|
||||
coverages,
|
||||
blendMode
|
||||
)
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
||||
|
||||
template test(name: string, p: Path, a: static int = 1, wr = wrNonZero) =
|
||||
echo name
|
||||
var image = newImage(200, 200)
|
||||
timeIt " sweeps", a:
|
||||
for i in 0 ..< a:
|
||||
image.fill(color(0, 0, 0, 0))
|
||||
image.fillPath2(p, color(1, 0, 0, 1), windingRule = wr)
|
||||
image.writeFile("experiments/trapezoids/output_sweep.png")
|
||||
|
||||
var image2 = newImage(200, 200)
|
||||
timeIt " scanline", a:
|
||||
for i in 0 ..< a:
|
||||
image2.fill(color(0, 0, 0, 0))
|
||||
image2.fillPath(p, color(1, 0, 0, 1), windingRule = wr)
|
||||
image2.writeFile("experiments/trapezoids/output_scanline.png")
|
||||
|
||||
let (score, diff) = diff(image, image2)
|
||||
if score > 0.05:
|
||||
echo "does not appear ot match"
|
||||
diff.writeFile("experiments/trapezoids/output_diff.png")
|
||||
|
||||
|
||||
var rect = Path()
|
||||
rect.moveTo(50.5, 50.5)
|
||||
rect.lineTo(50.5, 150.5)
|
||||
rect.lineTo(150.5, 150.5)
|
||||
rect.lineTo(150.5, 50.5)
|
||||
rect.closePath()
|
||||
|
||||
var rhombus = Path()
|
||||
rhombus.moveTo(100, 50)
|
||||
rhombus.lineTo(150, 100)
|
||||
rhombus.lineTo(100, 150)
|
||||
rhombus.lineTo(50, 100)
|
||||
rhombus.closePath()
|
||||
|
||||
var heart = parsePath("""
|
||||
M 20 60
|
||||
A 40 40 90 0 1 100 60
|
||||
A 40 40 90 0 1 180 60
|
||||
Q 180 120 100 180
|
||||
Q 20 120 20 60
|
||||
z
|
||||
""")
|
||||
|
||||
var cricle = Path()
|
||||
cricle.arc(100, 100, 50, 0, PI * 2, true)
|
||||
cricle.closePath()
|
||||
|
||||
|
||||
# Half arc (test cut lines)
|
||||
var halfAarc = parsePath("""
|
||||
M 25 25 C 85 25 85 125 25 125 z
|
||||
""")
|
||||
|
||||
# Hour glass (test cross lines)
|
||||
var hourGlass = parsePath("""
|
||||
M 20 20 L 180 20 L 20 180 L 180 180 z
|
||||
""")
|
||||
|
||||
# Hole
|
||||
var hole = parsePath("""
|
||||
M 40 40 L 40 160 L 160 160 L 160 40 z
|
||||
M 120 80 L 120 120 L 80 120 L 80 80 z
|
||||
""")
|
||||
|
||||
var holeEvenOdd = parsePath("""
|
||||
M 40 40 L 40 160 L 160 160 L 160 40 z
|
||||
M 80 80 L 80 120 L 120 120 L 120 80 z
|
||||
""")
|
||||
|
||||
## g
|
||||
var letterG = parsePath("""
|
||||
M 406 538 Q 394 546 359.5 558.5 T 279 571 Q 232 571 190.5 556 T 118 509.5 T 69 431 T 51 319 Q 51 262 68 214.5 T 117.5 132.5 T 197 78.5 T 303 59 Q 368 59 416.5 68.5 T 498 86 V 550 Q 498 670 436 724 T 248 778 Q 199 778 155.5 770 T 80 751 L 97 670 Q 125 681 165.5 689.5 T 250 698 Q 333 698 369.5 665 T 406 560 V 538 Z M 405 152 Q 391 148 367.5 144.5 T 304 141 Q 229 141 188.5 190 T 148 320 Q 148 365 159.5 397 T 190.5 450 T 235.5 481 T 288 491 Q 325 491 356 480.5 T 405 456 V 152 Z
|
||||
""")
|
||||
letterG.transform(scale(vec2(0.2, 0.2)))
|
||||
|
||||
when defined(bench):
|
||||
test("rect", rect, 100)
|
||||
test("rhombus", rhombus, 100)
|
||||
test("heart", heart, 100)
|
||||
test("cricle", cricle, 100)
|
||||
test("halfAarc", halfAarc, 100)
|
||||
test("hourGlass", hourGlass, 100)
|
||||
test("hole", hole, 100)
|
||||
test("holeEvenOdd", holeEvenOdd, 100, wr=wrNonZero)
|
||||
test("holeEvenOdd", holeEvenOdd, 100, wr=wrEvenOdd)
|
||||
test("letterG", letterG, 100)
|
||||
else:
|
||||
# test("rect", rect)
|
||||
# test("rhombus", rhombus)
|
||||
# test("heart", heart)
|
||||
# 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("letterG", letterG)
|
|
@ -1,6 +1,9 @@
|
|||
|
||||
import algorithm, bumpy, chroma, pixie, pixie/images, pixie/paths, print,
|
||||
sequtils, vmath
|
||||
import algorithm, bumpy, chroma, pixie/images, print,
|
||||
sequtils, vmath, benchy
|
||||
|
||||
import pixie, pixie/paths {.all.}
|
||||
|
||||
|
||||
printColors = false
|
||||
|
||||
|
@ -25,7 +28,7 @@ proc roundBy*(v: Vec2, n: float32): Vec2 {.inline.} =
|
|||
result.x = sign(v.x) * round(abs(v.x) / n) * n
|
||||
result.y = sign(v.y) * round(abs(v.y) / n) * n
|
||||
|
||||
proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
||||
proc fillPath2(mask: Mask, p: Path) =
|
||||
|
||||
var polygons = p.commandsToShapes()
|
||||
|
||||
|
@ -39,22 +42,10 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
|||
s.at = s.at.roundBy(q)
|
||||
s.to = s.to.roundBy(q)
|
||||
if s.at.y != s.to.y:
|
||||
if s.at.y > s.to.y:
|
||||
# make sure segments always are at.y higher
|
||||
swap(s.at, s.to)
|
||||
segments1.add(s)
|
||||
#print segments1
|
||||
|
||||
# Handle segments overlapping each other:
|
||||
# var segments1: seq[Segment]
|
||||
# while segments0.len > 0:
|
||||
# var a = segments0.pop()
|
||||
# var collision = false
|
||||
# for b in segments0:
|
||||
# if a != b:
|
||||
# var at: Vec2
|
||||
# if a.intersectsInner(b, at):
|
||||
# print "seg2seg intersects!", a, b, at
|
||||
# quit()
|
||||
# if not collision:
|
||||
# segments1.add(a)
|
||||
|
||||
# There is probably a clever way to insert-sort them.
|
||||
var yScanLines: seq[float32]
|
||||
|
@ -64,6 +55,7 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
|||
if s.to.y notin yScanLines:
|
||||
yScanLines.add s.to.y
|
||||
yScanLines.sort()
|
||||
print yScanLines
|
||||
|
||||
var segments: seq[Segment]
|
||||
while segments1.len > 0:
|
||||
|
@ -71,8 +63,9 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
|||
var s = segments1.pop()
|
||||
var collision = false
|
||||
for y in yScanLines:
|
||||
let scanLine = line(vec2(0, y), vec2(1, y))
|
||||
var at: Vec2
|
||||
if intersects(line(vec2(0, y), vec2(1, y)), s, at):
|
||||
if intersects(scanLine, s, at):
|
||||
at = at.roundBy(q)
|
||||
at.y = y
|
||||
if s.at.y != at.y and s.to.y != at.y:
|
||||
|
@ -86,17 +79,16 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
|||
break
|
||||
|
||||
if not collision:
|
||||
# means its touching, not intersecting
|
||||
segments.add(s)
|
||||
|
||||
#print segments
|
||||
|
||||
# sort at/to in segments
|
||||
for s in segments.mitems:
|
||||
if s.at.y > s.to.y:
|
||||
swap(s.at, s.to)
|
||||
# for s in segments.mitems:
|
||||
# if s.at.y > s.to.y:
|
||||
# swap(s.at, s.to)
|
||||
|
||||
#print segments
|
||||
#print yScanLines
|
||||
|
||||
#let blender = blendMode.blender()
|
||||
|
||||
for yScanLine in yScanLines[0..^2]:
|
||||
|
||||
|
@ -107,13 +99,7 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
|||
scanSegments.sort(proc(a, b: Segment): int =
|
||||
cmp(a.at.x, b.at.x))
|
||||
|
||||
if scanSegments.len mod 2 != 0:
|
||||
print "error???"
|
||||
print yScanLine
|
||||
print scanSegments
|
||||
quit()
|
||||
|
||||
# if scanSegments.len == 0:
|
||||
# if scanSegments.len mod 2 != 0:
|
||||
# print "error???"
|
||||
# print yScanLine
|
||||
# print scanSegments
|
||||
|
@ -121,6 +107,7 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
|||
|
||||
# TODO: winding rules will go here
|
||||
|
||||
var trapezoids: seq[Trapezoid]
|
||||
for i in 0 ..< scanSegments.len div 2:
|
||||
let
|
||||
a = scanSegments[i*2+0]
|
||||
|
@ -131,130 +118,190 @@ proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
|||
#assert a.at.x < b.at.x
|
||||
#assert a.to.x < b.to.x
|
||||
|
||||
result.add(
|
||||
Trapezoid(
|
||||
nw: a.at,
|
||||
ne: b.at,
|
||||
se: b.to, # + vec2(0,0.7),
|
||||
trapezoids.add(Trapezoid(
|
||||
nw: a.at,
|
||||
ne: b.at,
|
||||
se: b.to, # + vec2(0,0.7),
|
||||
sw: a.to # + vec2(0,0.7)
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
proc trapFill(image: Image, t: Trapezoid, color: ColorRGBA) =
|
||||
# assert t.nw.y == t.ne.y
|
||||
# assert t.sw.y == t.se.y
|
||||
var i = 0
|
||||
while i < trapezoids.len:
|
||||
|
||||
let
|
||||
height = t.sw.y - t.nw.y
|
||||
minY = clamp(t.nw.y, 0, image.height.float)
|
||||
maxY = clamp(t.sw.y, 0, image.height.float)
|
||||
for y in minY.int ..< maxY.int:
|
||||
var yRate, minX, maxX: float32
|
||||
let t = trapezoids[i]
|
||||
# print t
|
||||
let
|
||||
nw = t.nw
|
||||
ne = t.ne
|
||||
se = t.se
|
||||
sw = t.sw
|
||||
|
||||
yRate = clamp((y.float - t.nw.y) / height, 0, 1)
|
||||
minX = clamp(lerp(t.nw.x, t.sw.x, yRate).round, 0, image.width.float)
|
||||
maxX = clamp(lerp(t.ne.x, t.se.x, yRate).round, 0, image.width.float)
|
||||
let
|
||||
height = sw.y - nw.y
|
||||
minYf = nw.y
|
||||
maxYf = sw.y
|
||||
minYi = minYf.floor.int
|
||||
maxYi = maxYf.floor.int
|
||||
|
||||
for x in minX.int ..< maxX.int:
|
||||
image.setRgbaUnsafe(x, y, color)
|
||||
# print t
|
||||
|
||||
proc drawTrapezoids(image: Image, trapezoids: seq[Trapezoid]) =
|
||||
for y in minYi .. maxYi:
|
||||
let
|
||||
yFrac = (y.float - nw.y) / height
|
||||
minXf = mix(nw.x, sw.x, yFrac)
|
||||
maxXf = mix(ne.x, se.x, yFrac)
|
||||
minXi = minXf.floor.int
|
||||
maxXi = maxXf.floor.int
|
||||
#print yFrac
|
||||
# if not(minY.int == 58 or maxY.int == 58) or minX > 100:
|
||||
# continue
|
||||
|
||||
for trapezoid in trapezoids:
|
||||
image.trapFill(trapezoid, rgba(0, 0, 0, 255))
|
||||
var ay: float32
|
||||
if y == minYi and y == maxYi:
|
||||
ay = maxYf - minYf
|
||||
# print "middle", maxYf, minYf, a
|
||||
#print "double", y, a, minY, maxY, round(a * 255)
|
||||
elif y == minYi:
|
||||
ay = (1 - (minYf - float32(minYi)))
|
||||
# print "min y", minYf, minYi, a
|
||||
#print "s", y, a, minY, round(a * 255)
|
||||
elif y == maxYi:
|
||||
ay = (maxYf - float32(maxYi))
|
||||
#print "max y", maxYf, maxYi, a
|
||||
# print "e", y, a, maxY, round(a * 255)
|
||||
else:
|
||||
ay = 1.0
|
||||
|
||||
# for trapezoid in trapezoids:
|
||||
# var p = newPath()
|
||||
# p.moveTo(trapezoid.nw)
|
||||
# p.lineTo(trapezoid.ne)
|
||||
# p.lineTo(trapezoid.se)
|
||||
# p.lineTo(trapezoid.sw)
|
||||
# p.closePath()
|
||||
# image.fillPath(p, rgba(0, 0, 0, 255))
|
||||
# image.strokePath(p, rgba(255, 0, 0, 255))
|
||||
for x in minXi .. maxXi:
|
||||
var ax: float32
|
||||
# if x == minXi:
|
||||
# a2 = (1 - (minXf - float32(minXi)))
|
||||
# #a2 = 1.0
|
||||
# elif x == maxXi:
|
||||
# a2 = (maxXf - float32(maxXi))
|
||||
# #a2 = 1.0
|
||||
# else:
|
||||
# a2 = 1.0
|
||||
|
||||
if x.float32 < max(nw.x, sw.x):
|
||||
ax = 0.5
|
||||
elif x.float32 > min(ne.x, se.x):
|
||||
ax = 0.25
|
||||
else:
|
||||
ax = 1.0
|
||||
|
||||
let backdrop = mask.getValueUnsafe(x, y)
|
||||
mask.setValueUnsafe(x, y, backdrop + floor(255 * ay * ax).uint8)
|
||||
# if x == 100 and y == 172:
|
||||
# print backdrop, round(255 * a * a2).uint8
|
||||
# print mask.getValueUnsafe(x, y)
|
||||
|
||||
inc i
|
||||
|
||||
block:
|
||||
# Rect
|
||||
print "rect"
|
||||
var image = newImage(200, 200)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
#var image = newImage(200, 200)
|
||||
|
||||
var p: Path
|
||||
p.moveTo(50, 50)
|
||||
p.lineTo(50, 150)
|
||||
p.lineTo(150, 150)
|
||||
p.lineTo(150, 50)
|
||||
p.closePath()
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/rect.png")
|
||||
|
||||
block:
|
||||
# Rhombus
|
||||
print "rhombus"
|
||||
var image = newImage(200, 200)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
var p: Path
|
||||
p.moveTo(100, 50)
|
||||
p.lineTo(150, 100)
|
||||
p.lineTo(100, 150)
|
||||
p.lineTo(50, 100)
|
||||
p.closePath()
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/rhombus.png")
|
||||
|
||||
block:
|
||||
# heart
|
||||
print "heart"
|
||||
var image = newImage(400, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
# var p = Path()
|
||||
# p.moveTo(50.25, 50.25)
|
||||
# p.lineTo(50.25, 150.25)
|
||||
# p.lineTo(150.25, 150.25)
|
||||
# p.lineTo(150.25, 50.25)
|
||||
# p.closePath()
|
||||
|
||||
var p = parsePath("""
|
||||
M 40 120 A 80 80 90 0 1 200 120 A 80 80 90 0 1 360 120
|
||||
Q 360 240 200 360 Q 40 240 40 120 z
|
||||
M 20 60
|
||||
A 40 40 90 0 1 100 60
|
||||
A 40 40 90 0 1 180 60
|
||||
Q 180 120 100 180
|
||||
Q 20 120 20 60
|
||||
z
|
||||
""")
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
#image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
image.writeFile("experiments/trapezoids/heart.png")
|
||||
var mask = newMask(200, 200)
|
||||
timeIt "rect trapezoids", 1:
|
||||
#for i in 0 ..< 100:
|
||||
mask.fill(0)
|
||||
mask.fillPath2(p)
|
||||
#image.fillPath2(p, color(0, 0, 0, 1))
|
||||
mask.writeFile("experiments/trapezoids/rect_trapesoid.png")
|
||||
|
||||
block:
|
||||
# l
|
||||
print "l"
|
||||
var image = newImage(500, 800)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
var mask2 = newMask(200, 200)
|
||||
timeIt "rect normal", 1:
|
||||
#for i in 0 ..< 100:
|
||||
mask2.fill(0)
|
||||
mask2.fillPath(p)
|
||||
mask2.writeFile("experiments/trapezoids/rect_scanline.png")
|
||||
|
||||
var p = parsePath("""
|
||||
M 236 20 Q 150 22 114 57 T 78 166 V 790 L 171 806 V 181 Q 171 158 175 143 T 188 119 T 212 105.5 T 249 98 Z
|
||||
""")
|
||||
let (score, image) = diff(mask.newImage, mask2.newImage)
|
||||
print score
|
||||
image.writeFile("experiments/trapezoids/rect_diff.png")
|
||||
|
||||
#image.strokePath(p, rgba(0, 0, 0, 255))
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/l.png")
|
||||
# block:
|
||||
# # Rhombus
|
||||
# print "rhombus"
|
||||
# var image = newImage(200, 200)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
block:
|
||||
# g
|
||||
print "g"
|
||||
var image = newImage(500, 800)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
# var p = Path()
|
||||
# p.moveTo(100, 50)
|
||||
# p.lineTo(150, 100)
|
||||
# p.lineTo(100, 150)
|
||||
# p.lineTo(50, 100)
|
||||
# p.closePath()
|
||||
|
||||
var p = parsePath("""
|
||||
M 406 538 Q 394 546 359.5 558.5 T 279 571 Q 232 571 190.5 556 T 118 509.5 T 69 431 T 51 319 Q 51 262 68 214.5 T 117.5 132.5 T 197 78.5 T 303 59 Q 368 59 416.5 68.5 T 498 86 V 550 Q 498 670 436 724 T 248 778 Q 199 778 155.5 770 T 80 751 L 97 670 Q 125 681 165.5 689.5 T 250 698 Q 333 698 369.5 665 T 406 560 V 538 Z M 405 152 Q 391 148 367.5 144.5 T 304 141 Q 229 141 188.5 190 T 148 320 Q 148 365 159.5 397 T 190.5 450 T 235.5 481 T 288 491 Q 325 491 356 480.5 T 405 456 V 152 Z
|
||||
""")
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
#image.strokePath(p, rgba(0, 0, 0, 255))
|
||||
# image.writeFile("experiments/trapezoids/rhombus.png")
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
# block:
|
||||
# # heart
|
||||
# print "heart"
|
||||
# var image = newImage(400, 400)
|
||||
# image.fill(rgba(0, 0, 0, 0))
|
||||
|
||||
image.writeFile("experiments/trapezoids/g.png")
|
||||
# var p = parsePath("""
|
||||
# M 40 120 A 80 80 90 0 1 200 120 A 80 80 90 0 1 360 120
|
||||
# Q 360 240 200 360 Q 40 240 40 120 z
|
||||
# """)
|
||||
|
||||
# var mask = newMask(image)
|
||||
# mask.fillPath2(p)
|
||||
|
||||
# image.draw(mask, blendMode = bmOverwrite)
|
||||
|
||||
# image.writeFile("experiments/trapezoids/heart.png")
|
||||
|
||||
# block:
|
||||
# # l
|
||||
# print "l"
|
||||
# var image = newImage(500, 800)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 236 20 Q 150 22 114 57 T 78 166 V 790 L 171 806 V 181 Q 171 158 175 143 T 188 119 T 212 105.5 T 249 98 Z
|
||||
# """)
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/l.png")
|
||||
|
||||
# block:
|
||||
# # g
|
||||
# print "g"
|
||||
# var image = newImage(500, 800)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 406 538 Q 394 546 359.5 558.5 T 279 571 Q 232 571 190.5 556 T 118 509.5 T 69 431 T 51 319 Q 51 262 68 214.5 T 117.5 132.5 T 197 78.5 T 303 59 Q 368 59 416.5 68.5 T 498 86 V 550 Q 498 670 436 724 T 248 778 Q 199 778 155.5 770 T 80 751 L 97 670 Q 125 681 165.5 689.5 T 250 698 Q 333 698 369.5 665 T 406 560 V 538 Z M 405 152 Q 391 148 367.5 144.5 T 304 141 Q 229 141 188.5 190 T 148 320 Q 148 365 159.5 397 T 190.5 450 T 235.5 481 T 288 491 Q 325 491 356 480.5 T 405 456 V 152 Z
|
||||
# """)
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/g.png")
|
||||
|
|
260
experiments/trapezoid0.nim
Normal file
|
@ -0,0 +1,260 @@
|
|||
|
||||
import algorithm, bumpy, chroma, pixie, pixie/images, pixie/paths, print,
|
||||
sequtils, vmath
|
||||
|
||||
printColors = false
|
||||
|
||||
proc intersectsInner*(a, b: Segment, at: var Vec2): bool {.inline.} =
|
||||
## Checks if the a segment intersects b segment.
|
||||
## If it returns true, at will have point of intersection
|
||||
let
|
||||
s1 = a.to - a.at
|
||||
s2 = b.to - b.at
|
||||
denominator = (-s2.x * s1.y + s1.x * s2.y)
|
||||
s = (-s1.y * (a.at.x - b.at.x) + s1.x * (a.at.y - b.at.y)) / denominator
|
||||
t = (s2.x * (a.at.y - b.at.y) - s2.y * (a.at.x - b.at.x)) / denominator
|
||||
|
||||
if s > 0 and s < 1 and t >= 0 and t <= 1:
|
||||
at = a.at + (t * s1)
|
||||
return true
|
||||
|
||||
type Trapezoid = object
|
||||
nw, ne, se, sw: Vec2
|
||||
|
||||
proc roundBy*(v: Vec2, n: float32): Vec2 {.inline.} =
|
||||
result.x = sign(v.x) * round(abs(v.x) / n) * n
|
||||
result.y = sign(v.y) * round(abs(v.y) / n) * n
|
||||
|
||||
proc pathToTrapezoids(p: Path): seq[Trapezoid] =
|
||||
|
||||
var polygons = p.commandsToShapes()
|
||||
|
||||
const q = 1/256.0
|
||||
|
||||
# Creates segment q, quantize and remove verticals.
|
||||
var segments1: seq[Segment]
|
||||
for shape in polygons:
|
||||
for s in shape.segments:
|
||||
var s = s
|
||||
s.at = s.at.roundBy(q)
|
||||
s.to = s.to.roundBy(q)
|
||||
if s.at.y != s.to.y:
|
||||
segments1.add(s)
|
||||
#print segments1
|
||||
|
||||
# Handle segments overlapping each other:
|
||||
# var segments1: seq[Segment]
|
||||
# while segments0.len > 0:
|
||||
# var a = segments0.pop()
|
||||
# var collision = false
|
||||
# for b in segments0:
|
||||
# if a != b:
|
||||
# var at: Vec2
|
||||
# if a.intersectsInner(b, at):
|
||||
# print "seg2seg intersects!", a, b, at
|
||||
# quit()
|
||||
# if not collision:
|
||||
# segments1.add(a)
|
||||
|
||||
# There is probably a clever way to insert-sort them.
|
||||
var yScanLines: seq[float32]
|
||||
for s in segments1:
|
||||
if s.at.y notin yScanLines:
|
||||
yScanLines.add s.at.y
|
||||
if s.to.y notin yScanLines:
|
||||
yScanLines.add s.to.y
|
||||
yScanLines.sort()
|
||||
|
||||
var segments: seq[Segment]
|
||||
while segments1.len > 0:
|
||||
#print segments1.len, segments.len
|
||||
var s = segments1.pop()
|
||||
var collision = false
|
||||
for y in yScanLines:
|
||||
var at: Vec2
|
||||
if intersects(line(vec2(0, y), vec2(1, y)), s, at):
|
||||
at = at.roundBy(q)
|
||||
at.y = y
|
||||
if s.at.y != at.y and s.to.y != at.y:
|
||||
#print "seg2yline intersects!", a, y, at
|
||||
collision = true
|
||||
var s1 = segment(s.at, at)
|
||||
var s2 = segment(at, s.to)
|
||||
#print s.length, "->", s1.length, s2.length
|
||||
segments1.add(s1)
|
||||
segments1.add(s2)
|
||||
break
|
||||
|
||||
if not collision:
|
||||
segments.add(s)
|
||||
|
||||
#print segments
|
||||
|
||||
# sort at/to in segments
|
||||
for s in segments.mitems:
|
||||
if s.at.y > s.to.y:
|
||||
swap(s.at, s.to)
|
||||
|
||||
#print segments
|
||||
#print yScanLines
|
||||
|
||||
for yScanLine in yScanLines[0..^2]:
|
||||
|
||||
var scanSegments: seq[Segment]
|
||||
for s in segments:
|
||||
if s.at.y == yScanLine:
|
||||
scanSegments.add(s)
|
||||
scanSegments.sort(proc(a, b: Segment): int =
|
||||
cmp(a.at.x, b.at.x))
|
||||
|
||||
if scanSegments.len mod 2 != 0:
|
||||
print "error???"
|
||||
print yScanLine
|
||||
print scanSegments
|
||||
quit()
|
||||
|
||||
# if scanSegments.len == 0:
|
||||
# print "error???"
|
||||
# print yScanLine
|
||||
# print scanSegments
|
||||
# quit()
|
||||
|
||||
# TODO: winding rules will go here
|
||||
|
||||
for i in 0 ..< scanSegments.len div 2:
|
||||
let
|
||||
a = scanSegments[i*2+0]
|
||||
b = scanSegments[i*2+1]
|
||||
|
||||
assert a.at.y == b.at.y
|
||||
assert a.to.y == b.to.y
|
||||
#assert a.at.x < b.at.x
|
||||
#assert a.to.x < b.to.x
|
||||
|
||||
result.add(
|
||||
Trapezoid(
|
||||
nw: a.at,
|
||||
ne: b.at,
|
||||
se: b.to, # + vec2(0,0.7),
|
||||
sw: a.to # + vec2(0,0.7)
|
||||
)
|
||||
)
|
||||
|
||||
proc trapFill(image: Image, t: Trapezoid, color: ColorRGBA) =
|
||||
# assert t.nw.y == t.ne.y
|
||||
# assert t.sw.y == t.se.y
|
||||
|
||||
let
|
||||
height = t.sw.y - t.nw.y
|
||||
minY = clamp(t.nw.y, 0, image.height.float)
|
||||
maxY = clamp(t.sw.y, 0, image.height.float)
|
||||
for y in minY.int ..< maxY.int:
|
||||
var yRate, minX, maxX: float32
|
||||
|
||||
yRate = clamp((y.float - t.nw.y) / height, 0, 1)
|
||||
minX = clamp(lerp(t.nw.x, t.sw.x, yRate).round, 0, image.width.float)
|
||||
maxX = clamp(lerp(t.ne.x, t.se.x, yRate).round, 0, image.width.float)
|
||||
|
||||
for x in minX.int ..< maxX.int:
|
||||
image.setRgbaUnsafe(x, y, color)
|
||||
|
||||
proc drawTrapezoids(image: Image, trapezoids: seq[Trapezoid]) =
|
||||
|
||||
for trapezoid in trapezoids:
|
||||
image.trapFill(trapezoid, rgba(0, 0, 0, 255))
|
||||
|
||||
# for trapezoid in trapezoids:
|
||||
# var p = newPath()
|
||||
# p.moveTo(trapezoid.nw)
|
||||
# p.lineTo(trapezoid.ne)
|
||||
# p.lineTo(trapezoid.se)
|
||||
# p.lineTo(trapezoid.sw)
|
||||
# p.closePath()
|
||||
# image.fillPath(p, rgba(0, 0, 0, 255))
|
||||
# image.strokePath(p, rgba(255, 0, 0, 255))
|
||||
|
||||
block:
|
||||
# Rect
|
||||
print "rect"
|
||||
var image = newImage(200, 200)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
var p: Path
|
||||
p.moveTo(50, 50)
|
||||
p.lineTo(50, 150)
|
||||
p.lineTo(150, 150)
|
||||
p.lineTo(150, 50)
|
||||
p.closePath()
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/rect.png")
|
||||
|
||||
block:
|
||||
# Rhombus
|
||||
print "rhombus"
|
||||
var image = newImage(200, 200)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
var p: Path
|
||||
p.moveTo(100, 50)
|
||||
p.lineTo(150, 100)
|
||||
p.lineTo(100, 150)
|
||||
p.lineTo(50, 100)
|
||||
p.closePath()
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/rhombus.png")
|
||||
|
||||
block:
|
||||
# heart
|
||||
print "heart"
|
||||
var image = newImage(400, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
var p = parsePath("""
|
||||
M 40 120 A 80 80 90 0 1 200 120 A 80 80 90 0 1 360 120
|
||||
Q 360 240 200 360 Q 40 240 40 120 z
|
||||
""")
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/heart.png")
|
||||
|
||||
block:
|
||||
# l
|
||||
print "l"
|
||||
var image = newImage(500, 800)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
var p = parsePath("""
|
||||
M 236 20 Q 150 22 114 57 T 78 166 V 790 L 171 806 V 181 Q 171 158 175 143 T 188 119 T 212 105.5 T 249 98 Z
|
||||
""")
|
||||
|
||||
#image.strokePath(p, rgba(0, 0, 0, 255))
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/l.png")
|
||||
|
||||
block:
|
||||
# g
|
||||
print "g"
|
||||
var image = newImage(500, 800)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
var p = parsePath("""
|
||||
M 406 538 Q 394 546 359.5 558.5 T 279 571 Q 232 571 190.5 556 T 118 509.5 T 69 431 T 51 319 Q 51 262 68 214.5 T 117.5 132.5 T 197 78.5 T 303 59 Q 368 59 416.5 68.5 T 498 86 V 550 Q 498 670 436 724 T 248 778 Q 199 778 155.5 770 T 80 751 L 97 670 Q 125 681 165.5 689.5 T 250 698 Q 333 698 369.5 665 T 406 560 V 538 Z M 405 152 Q 391 148 367.5 144.5 T 304 141 Q 229 141 188.5 190 T 148 320 Q 148 365 159.5 397 T 190.5 450 T 235.5 481 T 288 491 Q 325 491 356 480.5 T 405 456 V 152 Z
|
||||
""")
|
||||
|
||||
#image.strokePath(p, rgba(0, 0, 0, 255))
|
||||
|
||||
var trapezoids = p.pathToTrapezoids()
|
||||
image.drawTrapezoids(trapezoids)
|
||||
|
||||
image.writeFile("experiments/trapezoids/g.png")
|
505
experiments/trapezoid2.nim
Normal file
|
@ -0,0 +1,505 @@
|
|||
|
||||
import algorithm, bumpy, chroma, pixie/images, print,
|
||||
sequtils, vmath, benchy
|
||||
|
||||
import pixie, pixie/paths {.all.}
|
||||
|
||||
|
||||
printColors = false
|
||||
|
||||
proc pixelCover(a0, b0: Vec2): float32 =
|
||||
## Returns the amount of area a given segment sweeps to the right
|
||||
## in a [0,0 to 1,1] box.
|
||||
var
|
||||
a = a0
|
||||
b = b0
|
||||
aI: Vec2
|
||||
bI: Vec2
|
||||
area: float32 = 0.0
|
||||
|
||||
# # Sort A on top.
|
||||
# if a.y > b.y:
|
||||
# let tmp = a
|
||||
# a = b
|
||||
# b = tmp
|
||||
|
||||
# if (b.y < 0 or a.y > 1) or # Above or bellow, no effect.
|
||||
# (a.x >= 1 and b.x >= 1) or # To the right, no effect.
|
||||
# (a.y == b.y): # Horizontal line, no effect.
|
||||
# return 0
|
||||
|
||||
if (a.x < 0 and b.x < 0) or # Both to the left.
|
||||
(a.x == b.x): # Vertical line
|
||||
# Area of the rectangle:
|
||||
return (1 - clamp(a.x, 0, 1)) * (min(b.y, 1) - max(a.y, 0))
|
||||
|
||||
else:
|
||||
# y = mm*x + bb
|
||||
let
|
||||
mm: float32 = (b.y - a.y) / (b.x - a.x)
|
||||
bb: float32 = a.y - mm * a.x
|
||||
|
||||
if a.x >= 0 and a.x <= 1 and a.y >= 0 and a.y <= 1:
|
||||
# A is in pixel bounds.
|
||||
aI = a
|
||||
else:
|
||||
aI = vec2((0 - bb) / mm, 0)
|
||||
if aI.x < 0:
|
||||
let y = mm * 0 + bb
|
||||
# Area of the extra rectangle.
|
||||
area += (min(bb, 1) - max(a.y, 0)).clamp(0, 1)
|
||||
aI = vec2(0, y.clamp(0, 1))
|
||||
elif aI.x > 1:
|
||||
let y = mm * 1 + bb
|
||||
aI = vec2(1, y.clamp(0, 1))
|
||||
|
||||
if b.x >= 0 and b.x <= 1 and b.y >= 0 and b.y <= 1:
|
||||
# B is in pixel bounds.
|
||||
bI = b
|
||||
else:
|
||||
bI = vec2((1 - bb) / mm, 1)
|
||||
if bI.x < 0:
|
||||
let y = mm * 0 + bb
|
||||
# Area of the extra rectangle.
|
||||
area += (min(b.y, 1) - max(bb, 0)).clamp(0, 1)
|
||||
bI = vec2(0, y.clamp(0, 1))
|
||||
elif bI.x > 1:
|
||||
let y = mm * 1 + bb
|
||||
bI = vec2(1, y.clamp(0, 1))
|
||||
|
||||
area += ((1 - aI.x) + (1 - bI.x)) / 2 * (bI.y - aI.y)
|
||||
return area
|
||||
|
||||
proc intersectsInner*(a, b: Segment, at: var Vec2): bool {.inline.} =
|
||||
## Checks if the a segment intersects b segment.
|
||||
## If it returns true, at will have point of intersection
|
||||
let
|
||||
s1 = a.to - a.at
|
||||
s2 = b.to - b.at
|
||||
denominator = (-s2.x * s1.y + s1.x * s2.y)
|
||||
s = (-s1.y * (a.at.x - b.at.x) + s1.x * (a.at.y - b.at.y)) / denominator
|
||||
t = (s2.x * (a.at.y - b.at.y) - s2.y * (a.at.x - b.at.x)) / denominator
|
||||
|
||||
if s > 0 and s < 1 and t >= 0 and t <= 1:
|
||||
at = a.at + (t * s1)
|
||||
return true
|
||||
|
||||
type
|
||||
|
||||
Trapezoid = object
|
||||
nw, ne, se, sw: Vec2
|
||||
|
||||
proc roundBy*(v: Vec2, n: float32): Vec2 {.inline.} =
|
||||
result.x = sign(v.x) * round(abs(v.x) / n) * n
|
||||
result.y = sign(v.y) * round(abs(v.y) / n) * n
|
||||
|
||||
proc fillPath2(mask: Mask, p: Path) =
|
||||
|
||||
var polygons = p.commandsToShapes()
|
||||
|
||||
const q = 1/256.0
|
||||
|
||||
# Creates segment q, quantize and remove horizontal lines.
|
||||
var segments1: seq[Segment]
|
||||
for shape in polygons:
|
||||
for s in shape.segments:
|
||||
var s = s
|
||||
s.at = s.at.roundBy(q)
|
||||
s.to = s.to.roundBy(q)
|
||||
if s.at.y != s.to.y:
|
||||
if s.at.y > s.to.y:
|
||||
# make sure segments always are at.y higher
|
||||
swap(s.at, s.to)
|
||||
segments1.add(s)
|
||||
segments1.sort(proc(a, b: Segment): int = cmp(a.at.y, b.at.y))
|
||||
|
||||
# Dumb way to compute cutLines
|
||||
# var cutLines: seq[float32]
|
||||
# for s in segments1:
|
||||
# if s.at.y notin cutLines:
|
||||
# cutLines.add s.at.y
|
||||
# if s.to.y notin cutLines:
|
||||
# cutLines.add s.to.y
|
||||
# cutLines.sort()
|
||||
|
||||
# Compute cutLines
|
||||
var
|
||||
cutLines: seq[float32]
|
||||
last = segments1[0].at.y
|
||||
bottom = segments1[0].to.y
|
||||
cutLines.add(last)
|
||||
for s in segments1:
|
||||
if s.at.y != last:
|
||||
last = s.at.y
|
||||
cutLines.add(last)
|
||||
if bottom < s.to.y:
|
||||
bottom = s.to.y
|
||||
cutLines.add(bottom)
|
||||
#print cutLines
|
||||
|
||||
var
|
||||
sweeps = newSeq[seq[Segment]](cutLines.len - 1) # dont add bottom cutLine
|
||||
lastSeg = 0
|
||||
|
||||
for i, sweep in sweeps.mpairs:
|
||||
#print "sweep", i, cutLines[i]
|
||||
while segments1[lastSeg].at.y == cutLines[i]:
|
||||
let s = segments1[lastSeg]
|
||||
#print s
|
||||
if s.to.y != cutLines[i + 1]:
|
||||
#print "needs cut?"
|
||||
quit()
|
||||
sweep.add(segments1[lastSeg])
|
||||
inc lastSeg
|
||||
if lastSeg >= segments1.len:
|
||||
# Sort the last sweep by X
|
||||
break
|
||||
# Sort the sweep by X
|
||||
sweep.sort(proc(a, b: Segment): int = cmp(a.at.x, b.at.x))
|
||||
|
||||
proc fillCoverage(y: int, currCutLine: int, sweep: seq[Segment]) =
|
||||
|
||||
|
||||
# var i = 0
|
||||
# let
|
||||
# sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
# yFracTop = (y.float - cutLines[currCutLine]) / sweepHeight
|
||||
# yFracBottom = (y.float + 1 - cutLines[currCutLine]) / sweepHeight
|
||||
# #print "cover", y, sweepHeight, yFrac
|
||||
# while i < sweep.len:
|
||||
# #print "fill", sweep[i].at.x, "..", sweep[i+1].at.x
|
||||
# let
|
||||
# minXf1 = mix(sweep[i+0].at.x, sweep[i+0].to.x, yFracTop)
|
||||
# maxXf1 = mix(sweep[i+1].at.x, sweep[i+1].to.x, yFracTop)
|
||||
|
||||
# minXf2 = mix(sweep[i+0].at.x, sweep[i+0].to.x, yFracBottom)
|
||||
# maxXf2 = mix(sweep[i+1].at.x, sweep[i+1].to.x, yFracBottom)
|
||||
|
||||
# minXi1 = minXf1.floor.int
|
||||
# maxXi1 = maxXf1.floor.int
|
||||
|
||||
# minXi2 = minXf2.floor.int
|
||||
# maxXi2 = maxXf2.floor.int
|
||||
|
||||
# for x in min(minXi1, minXi2) .. max(maxXi1, maxXi2):
|
||||
# var a = 1.0f
|
||||
# # if x < max(minXi1, minXi2):
|
||||
# # a = 0.1
|
||||
# # elif x > min(maxXi1, maxXi2):
|
||||
# # a = 0.1
|
||||
# # else:
|
||||
# # a = 0.5
|
||||
# let backdrop = mask.getValueUnsafe(x, y)
|
||||
# mask.setValueUnsafe(x, y, backdrop + (a * 255).uint8)
|
||||
# i += 2
|
||||
|
||||
# x10 slower
|
||||
# for x in 0 ..< mask.width:
|
||||
# for i, seg in sweep:
|
||||
# var area = pixelCover(seg.at - vec2(x.float32, y.float32), seg.to - vec2(x.float32, y.float32))
|
||||
# if i mod 2 == 1:
|
||||
# area = -area
|
||||
# let backdrop = mask.getValueUnsafe(x, y)
|
||||
# mask.setValueUnsafe(x, y, backdrop + (area * 255).uint8)
|
||||
|
||||
let quality = 5
|
||||
for m in 0 ..< quality:
|
||||
let
|
||||
sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
yFrac = (y.float32 + (m.float32 / quality.float32) - cutLines[currCutLine]) / sweepHeight
|
||||
if yFrac < 0.0 or yFrac >= 1.0:
|
||||
continue
|
||||
var i = 0
|
||||
while i < sweep.len:
|
||||
let
|
||||
minXf1 = mix(sweep[i+0].at.x, sweep[i+0].to.x, yFrac)
|
||||
maxXf1 = mix(sweep[i+1].at.x, sweep[i+1].to.x, yFrac)
|
||||
minXi1 = minXf1.int
|
||||
maxXi1 = maxXf1.int
|
||||
for x in minXi1 ..< maxXi1:
|
||||
let backdrop = mask.getValueUnsafe(x, y)
|
||||
mask.setValueUnsafe(x, y, backdrop + (255 div quality).uint8)
|
||||
# if x == 100 and y == 165:
|
||||
# print backdrop, 255 div quality
|
||||
# print mask.getValueUnsafe(x, y)
|
||||
i += 2
|
||||
|
||||
# let
|
||||
# sweepHeight = cutLines[currCutLine + 1] - cutLines[currCutLine]
|
||||
# yFrac = (y.float32 - cutLines[currCutLine]) / sweepHeight
|
||||
# var i = 0
|
||||
# while i < sweep.len:
|
||||
# let
|
||||
# minXf1 = mix(sweep[i+0].at.x, sweep[i+0].to.x, yFrac)
|
||||
# maxXf1 = mix(sweep[i+1].at.x, sweep[i+1].to.x, yFrac)
|
||||
# minXi1 = minXf1.floor.int
|
||||
# maxXi1 = maxXf1.floor.int
|
||||
# for x in minXi1 .. maxXi1:
|
||||
# mask.setValueUnsafe(x, y, 255)
|
||||
# i += 2
|
||||
|
||||
var
|
||||
currCutLine = 0
|
||||
for scanLine in cutLines[0].int ..< cutLines[^1].ceil.int:
|
||||
print scanLine, "..<", scanLine + 1
|
||||
print " ", currCutLine, cutLines[currCutLine], "..<", cutLines[currCutLine + 1]
|
||||
fillCoverage(scanLine, currCutLine, sweeps[currCutLine])
|
||||
while cutLines[currCutLine + 1] < scanLine.float + 1.0:
|
||||
inc currCutLine
|
||||
print " ", currCutLine, cutLines[currCutLine], "..<", cutLines[currCutLine + 1]
|
||||
fillCoverage(scanLine, currCutLine, sweeps[currCutLine])
|
||||
|
||||
# print sweeps[^1]
|
||||
# print cutLines
|
||||
|
||||
|
||||
|
||||
|
||||
# var segments: seq[Segment]
|
||||
# while segments1.len > 0:
|
||||
# #print segments1.len, segments.len
|
||||
# var s = segments1.pop()
|
||||
# var collision = false
|
||||
# for y in cutLines:
|
||||
# let scanLine = line(vec2(0, y), vec2(1, y))
|
||||
# var at: Vec2
|
||||
# if intersects(scanLine, s, at):
|
||||
# at = at.roundBy(q)
|
||||
# at.y = y
|
||||
# if s.at.y != at.y and s.to.y != at.y:
|
||||
# #print "seg2yline intersects!", a, y, at
|
||||
# collision = true
|
||||
# var s1 = segment(s.at, at)
|
||||
# var s2 = segment(at, s.to)
|
||||
# #print s.length, "->", s1.length, s2.length
|
||||
# segments1.add(s1)
|
||||
# segments1.add(s2)
|
||||
# break
|
||||
|
||||
# if not collision:
|
||||
# # means its touching, not intersecting
|
||||
# segments.add(s)
|
||||
|
||||
# # sort at/to in segments
|
||||
# # for s in segments.mitems:
|
||||
# # if s.at.y > s.to.y:
|
||||
# # swap(s.at, s.to)
|
||||
|
||||
|
||||
# #let blender = blendMode.blender()
|
||||
|
||||
# for yScanLine in cutLines[0..^2]:
|
||||
|
||||
# var scanSegments: seq[Segment]
|
||||
# for s in segments:
|
||||
# if s.at.y == yScanLine:
|
||||
# scanSegments.add(s)
|
||||
# scanSegments.sort(proc(a, b: Segment): int =
|
||||
# cmp(a.at.x, b.at.x))
|
||||
|
||||
# # if scanSegments.len mod 2 != 0:
|
||||
# # print "error???"
|
||||
# # print yScanLine
|
||||
# # print scanSegments
|
||||
# # quit()
|
||||
|
||||
# # TODO: winding rules will go here
|
||||
|
||||
# var trapezoids: seq[Trapezoid]
|
||||
# for i in 0 ..< scanSegments.len div 2:
|
||||
# let
|
||||
# a = scanSegments[i*2+0]
|
||||
# b = scanSegments[i*2+1]
|
||||
|
||||
# assert a.at.y == b.at.y
|
||||
# assert a.to.y == b.to.y
|
||||
# #assert a.at.x < b.at.x
|
||||
# #assert a.to.x < b.to.x
|
||||
|
||||
# trapezoids.add(Trapezoid(
|
||||
# nw: a.at,
|
||||
# ne: b.at,
|
||||
# se: b.to, # + vec2(0,0.7),
|
||||
# sw: a.to # + vec2(0,0.7)
|
||||
# ))
|
||||
|
||||
# var i = 0
|
||||
# while i < trapezoids.len:
|
||||
|
||||
# let t = trapezoids[i]
|
||||
# # print t
|
||||
# let
|
||||
# nw = t.nw
|
||||
# ne = t.ne
|
||||
# se = t.se
|
||||
# sw = t.sw
|
||||
|
||||
# let
|
||||
# height = sw.y - nw.y
|
||||
# minYf = nw.y
|
||||
# maxYf = sw.y
|
||||
# minYi = minYf.floor.int
|
||||
# maxYi = maxYf.floor.int
|
||||
|
||||
# # print t
|
||||
|
||||
# for y in minYi .. maxYi:
|
||||
# let
|
||||
# yFrac = (y.float - nw.y) / height
|
||||
# minXf = mix(nw.x, sw.x, yFrac)
|
||||
# maxXf = mix(ne.x, se.x, yFrac)
|
||||
# minXi = minXf.floor.int
|
||||
# maxXi = maxXf.floor.int
|
||||
# #print yFrac
|
||||
# # if not(minY.int == 58 or maxY.int == 58) or minX > 100:
|
||||
# # continue
|
||||
|
||||
# var ay: float32
|
||||
# if y == minYi and y == maxYi:
|
||||
# ay = maxYf - minYf
|
||||
# # print "middle", maxYf, minYf, a
|
||||
# #print "double", y, a, minY, maxY, round(a * 255)
|
||||
# elif y == minYi:
|
||||
# ay = (1 - (minYf - float32(minYi)))
|
||||
# # print "min y", minYf, minYi, a
|
||||
# #print "s", y, a, minY, round(a * 255)
|
||||
# elif y == maxYi:
|
||||
# ay = (maxYf - float32(maxYi))
|
||||
# #print "max y", maxYf, maxYi, a
|
||||
# # print "e", y, a, maxY, round(a * 255)
|
||||
# else:
|
||||
# ay = 1.0
|
||||
|
||||
# for x in minXi .. maxXi:
|
||||
# var ax: float32
|
||||
# # if x == minXi:
|
||||
# # a2 = (1 - (minXf - float32(minXi)))
|
||||
# # #a2 = 1.0
|
||||
# # elif x == maxXi:
|
||||
# # a2 = (maxXf - float32(maxXi))
|
||||
# # #a2 = 1.0
|
||||
# # else:
|
||||
# # a2 = 1.0
|
||||
|
||||
# if x.float32 < max(nw.x, sw.x):
|
||||
# ax = 0.5
|
||||
# elif x.float32 > min(ne.x, se.x):
|
||||
# ax = 0.25
|
||||
# else:
|
||||
# ax = 1.0
|
||||
|
||||
# let backdrop = mask.getValueUnsafe(x, y)
|
||||
# mask.setValueUnsafe(x, y, backdrop + floor(255 * ay * ax).uint8)
|
||||
# # if x == 100 and y == 172:
|
||||
# # print backdrop, round(255 * a * a2).uint8
|
||||
# # print mask.getValueUnsafe(x, y)
|
||||
|
||||
# inc i
|
||||
|
||||
block:
|
||||
# Rect
|
||||
print "rect"
|
||||
#var image = newImage(200, 200)
|
||||
|
||||
# var p = Path()
|
||||
# p.moveTo(50.25, 50.25)
|
||||
# p.lineTo(50.25, 150.25)
|
||||
# p.lineTo(150.25, 150.25)
|
||||
# p.lineTo(150.25, 50.25)
|
||||
# p.closePath()
|
||||
|
||||
var p = parsePath("""
|
||||
M 20 60
|
||||
A 40 40 90 0 1 100 60
|
||||
A 40 40 90 0 1 180 60
|
||||
Q 180 120 100 180
|
||||
Q 20 120 20 60
|
||||
z
|
||||
""")
|
||||
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
#image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
var mask = newMask(200, 200)
|
||||
timeIt "rect trapezoids", 1:
|
||||
#for i in 0 ..< 100:
|
||||
mask.fill(0)
|
||||
mask.fillPath2(p)
|
||||
#image.fillPath2(p, color(0, 0, 0, 1))
|
||||
mask.writeFile("experiments/trapezoids/rect_trapesoid.png")
|
||||
|
||||
var mask2 = newMask(200, 200)
|
||||
timeIt "rect normal", 1:
|
||||
#for i in 0 ..< 100:
|
||||
mask2.fill(0)
|
||||
mask2.fillPath(p)
|
||||
mask2.writeFile("experiments/trapezoids/rect_scanline.png")
|
||||
|
||||
let (score, image) = diff(mask.newImage, mask2.newImage)
|
||||
print score
|
||||
image.writeFile("experiments/trapezoids/rect_diff.png")
|
||||
|
||||
|
||||
|
||||
# block:
|
||||
# # Rhombus
|
||||
# print "rhombus"
|
||||
# var image = newImage(200, 200)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = Path()
|
||||
# p.moveTo(100, 50)
|
||||
# p.lineTo(150, 100)
|
||||
# p.lineTo(100, 150)
|
||||
# p.lineTo(50, 100)
|
||||
# p.closePath()
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/rhombus.png")
|
||||
|
||||
# block:
|
||||
# # heart
|
||||
# print "heart"
|
||||
# var image = newImage(400, 400)
|
||||
# image.fill(rgba(0, 0, 0, 0))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 40 120 A 80 80 90 0 1 200 120 A 80 80 90 0 1 360 120
|
||||
# Q 360 240 200 360 Q 40 240 40 120 z
|
||||
# """)
|
||||
|
||||
# var mask = newMask(image)
|
||||
# mask.fillPath2(p)
|
||||
|
||||
# image.draw(mask, blendMode = bmOverwrite)
|
||||
|
||||
# image.writeFile("experiments/trapezoids/heart.png")
|
||||
|
||||
# block:
|
||||
# # l
|
||||
# print "l"
|
||||
# var image = newImage(500, 800)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 236 20 Q 150 22 114 57 T 78 166 V 790 L 171 806 V 181 Q 171 158 175 143 T 188 119 T 212 105.5 T 249 98 Z
|
||||
# """)
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/l.png")
|
||||
|
||||
# block:
|
||||
# # g
|
||||
# print "g"
|
||||
# var image = newImage(500, 800)
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
|
||||
# var p = parsePath("""
|
||||
# M 406 538 Q 394 546 359.5 558.5 T 279 571 Q 232 571 190.5 556 T 118 509.5 T 69 431 T 51 319 Q 51 262 68 214.5 T 117.5 132.5 T 197 78.5 T 303 59 Q 368 59 416.5 68.5 T 498 86 V 550 Q 498 670 436 724 T 248 778 Q 199 778 155.5 770 T 80 751 L 97 670 Q 125 681 165.5 689.5 T 250 698 Q 333 698 369.5 665 T 406 560 V 538 Z M 405 152 Q 391 148 367.5 144.5 T 304 141 Q 229 141 188.5 190 T 148 320 Q 148 365 159.5 397 T 190.5 450 T 235.5 481 T 288 491 Q 325 491 356 480.5 T 405 456 V 152 Z
|
||||
# """)
|
||||
|
||||
# image.fillPath2(p, color(0, 0, 0, 1))
|
||||
|
||||
# image.writeFile("experiments/trapezoids/g.png")
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 11 KiB |
BIN
experiments/trapezoids/output_diff.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
experiments/trapezoids/output_scanline.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
experiments/trapezoids/output_sweep.png
Normal file
After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 921 B After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |