Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified examples/heart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/masking.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/text.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/text_spans.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/tiger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions experiments/benchmark_svg_cairo.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import benchy, svg_cairo

let data = readFile("tests/images/svg/Ghostscript_Tiger.svg")
let data = readFile("tests/fileformats/svg/Ghostscript_Tiger.svg")

timeIt "svg decode":
keep decodeSvg(data)
discard decodeSvg(data)
3 changes: 3 additions & 0 deletions src/pixie/images.nim
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,9 @@ proc superImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].}
## Either cuts a sub image or returns a super image with padded transparency.
if x >= 0 and x + w <= image.width and y >= 0 and y + h <= image.height:
result = image.subImage(x, y, w, h)
elif abs(x) >= image.width or abs(y) >= image.height:
# Nothing to copy, just an empty new image
result = newImage(w, h)
else:
result = newImage(w, h)
result.draw(image, translate(vec2(-x.float32, -y.float32)), bmOverwrite)
Expand Down
132 changes: 73 additions & 59 deletions src/pixie/paths.nim
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type
startY, partitionHeight: uint32

const
epsilon: float32 = 0.0001 * PI ## Tiny value used for some computations.
epsilon: float64 = 0.0001 * PI ## Tiny value used for some computations. Must be float64 to prevent leaks.
defaultMiterLimit*: float32 = 4

when defined(release):
Expand Down Expand Up @@ -651,7 +651,7 @@ proc commandsToShapes(
prevCommandKind = Move
prevCtrl, prevCtrl2: Vec2

let errorMargin = 0.2 / pixelScale
let errorMarginSq = pow(0.2.float32 / pixelScale, 2)

proc addSegment(shape: var seq[Vec2], at, to: Vec2) =
# Don't add any 0 length lines
Expand All @@ -669,27 +669,29 @@ proc commandsToShapes(
(1 - t) * 3 * pow(t, 2) * ctrl2 +
pow(t, 3) * to

var prev = at

proc discretize(shape: var seq[Vec2], i, steps: int) =
# Closure captures at, ctrl1, ctrl2, to and prev
var
t: float32 # Where we are at on the curve from [0, 1]
step = 1.float32 # How far we want to try to move along the curve
prev = at
next = compute(at, ctrl1, ctrl2, to, t + step)
halfway = compute(at, ctrl1, ctrl2, to, t + step / 2)
while true:
let
tPrev = (i - 1).float32 / steps.float32
t = i.float32 / steps.float32
next = compute(at, ctrl1, ctrl2, to, t)
halfway = compute(at, ctrl1, ctrl2, to, tPrev + (t - tPrev) / 2)
midpoint = (prev + next) / 2
error = (midpoint - halfway).length

if error >= errorMargin:
# Error too large, double precision for this step
shape.discretize(i * 2 - 1, steps * 2)
shape.discretize(i * 2, steps * 2)
error = (midpoint - halfway).lengthSq
if error > errorMarginSq:
next = halfway
halfway = compute(at, ctrl1, ctrl2, to, t + step / 4)
step /= 2
else:
shape.addSegment(prev, next)
t += step
if t == 1:
break
prev = next

shape.discretize(1, 1)
step = min(step * 2, 1 - t) # Optimistically attempt larger steps
next = compute(at, ctrl1, ctrl2, to, t + step)
halfway = compute(at, ctrl1, ctrl2, to, t + step / 2)

proc addQuadratic(shape: var seq[Vec2], at, ctrl, to: Vec2) =
## Adds quadratic segments to shape.
Expand All @@ -698,27 +700,34 @@ proc commandsToShapes(
2 * (1 - t) * t * ctrl +
pow(t, 2) * to

var prev = at

proc discretize(shape: var seq[Vec2], i, steps: int) =
# Closure captures at, ctrl, to and prev
var
t: float32 # Where we are at on the curve from [0, 1]
step = 1.float32 # How far we want to try to move along the curve
prev = at
next = compute(at, ctrl, to, t + step)
halfway = compute(at, ctrl, to, t + step / 2)
halfStepping = false
while true:
let
tPrev = (i - 1).float32 / steps.float32
t = i.float32 / steps.float32
next = compute(at, ctrl, to, t)
halfway = compute(at, ctrl, to, tPrev + (t - tPrev) / 2)
midpoint = (prev + next) / 2
error = (midpoint - halfway).length

if error >= errorMargin:
# Error too large, double precision for this step
shape.discretize(i * 2 - 1, steps * 2)
shape.discretize(i * 2, steps * 2)
error = (midpoint - halfway).lengthSq
if error > errorMarginSq:
next = halfway
halfway = compute(at, ctrl, to, t + step / 4)
step /= 2
halfStepping = true
else:
shape.addSegment(prev, next)
t += step
if t == 1:
break
prev = next

shape.discretize(1, 1)
if halfStepping:
step = min(step, 1 - t)
else:
step = min(step * 2, 1 - t) # Optimistically attempt larger steps
next = compute(at, ctrl, to, t + step)
halfway = compute(at, ctrl, to, t + step / 2)

proc addArc(
shape: var seq[Vec2],
Expand Down Expand Up @@ -808,28 +817,37 @@ proc commandsToShapes(
result = vec2(cos(a) * arc.radii.x, sin(a) * arc.radii.y)
result = arc.rotMat * result + arc.center

var prev = at
let arc = endpointToCenterArcParams(at, radii, rotation, large, sweep, to)

proc discretize(shape: var seq[Vec2], arc: ArcParams, i, steps: int) =
var
t: float32 # Where we are at on the curve from [0, 1]
step = 1.float32 # How far we want to try to move along the curve
prev = at
while t != 1:
let
step = arc.delta / steps.float32
aPrev = arc.theta + step * (i - 1).float32
a = arc.theta + step * i.float32
aPrev = arc.theta + arc.delta * t
a = arc.theta + arc.delta * (t + step)
next = arc.compute(a)
halfway = arc.compute(aPrev + (a - aPrev) / 2)
midpoint = (prev + next) / 2
error = (midpoint - halfway).length

if error >= errorMargin:
# Error too large, try again with doubled precision
shape.discretize(arc, i * 2 - 1, steps * 2)
shape.discretize(arc, i * 2, steps * 2)
error = (midpoint - halfway).lengthSq
if error > errorMarginSq:
let
quarterway = arc.compute(aPrev + (a - aPrev) / 4)
midpoint = (prev + halfway) / 2
halfwayError = (midpoint - quarterway).lengthSq
if halfwayError < errorMarginSq:
shape.addSegment(prev, halfway)
prev = halfway
t += step / 2
step = min(step / 2, 1 - t) # Assume next steps hould be the same size
else:
step = step / 4 # We know a half-step is too big
else:
shape.addSegment(prev, next)
prev = next

let arc = endpointToCenterArcParams(at, radii, rotation, large, sweep, to)
shape.discretize(arc, 1, 1)
t += step
step = min(step * 2, 1 - t) # Optimistically attempt larger steps

for command in path.commands:
if command.numbers.len != command.kind.parameterCount():
Expand Down Expand Up @@ -1002,9 +1020,7 @@ proc commandsToShapes(
shape.addSegment(at, start)
result.add(shape)

proc shapesToSegments(
shapes: seq[seq[Vec2]]
): seq[(Segment, int16)] =
proc shapesToSegments(shapes: seq[seq[Vec2]]): seq[(Segment, int16)] =
## Converts the shapes into a set of filtered segments with winding value.
for shape in shapes:
for segment in shape.segments:
Expand Down Expand Up @@ -1084,8 +1100,8 @@ proc partitionSegments(
): Partitioning =
## Puts segments into the height partitions they intersect with.
let
maxPartitions = max(1, height div 10).uint32
numPartitions = min(maxPartitions, max(1, segments.len div 10).uint32)
maxPartitions = max(1, height div 4).uint32
numPartitions = min(maxPartitions, max(1, segments.len div 4).uint32)

result.partitions.setLen(numPartitions)
result.startY = top.uint32
Expand Down Expand Up @@ -1214,7 +1230,7 @@ proc computeCoverage(
let
quality = if aa: 5 else: 1 # Must divide 255 cleanly (1, 3, 5, 15, 17, 51, 85)
sampleCoverage = (255 div quality).uint8
offset = 1 / quality.float32
offset = 1 / quality.float64
initialOffset = offset / 2 + epsilon

if aa: # Coverage is only used for anti-aliasing
Expand All @@ -1224,7 +1240,7 @@ proc computeCoverage(
let
partitionIndex = partitioning.getIndexForY(y)
partitionEntryCount = partitioning.partitions[partitionIndex].len
var yLine = y.float32 + initialOffset - offset
var yLine = y.float64 + initialOffset - offset
for m in 0 ..< quality:
yLine += offset
numHits = 0
Expand Down Expand Up @@ -1813,8 +1829,7 @@ proc fillPath*(
var shapes = parseSomePath(path, true, transform.pixelScale())
shapes.transform(transform)
var color = paint.color
if paint.opacity != 1:
color.a *= paint.opacity
color.a *= paint.opacity
image.fillShapes(shapes, color, windingRule, paint.blendMode)
return

Expand Down Expand Up @@ -1897,8 +1912,7 @@ proc strokePath*(
)
strokeShapes.transform(transform)
var color = paint.color
if paint.opacity != 1:
color.a *= paint.opacity
color.a *= paint.opacity
image.fillShapes(strokeShapes, color, wrNonZero, paint.blendMode)
return

Expand Down
2 changes: 1 addition & 1 deletion tests/benchmark_svg.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import benchy, pixie/fileformats/svg
let data = readFile("tests/fileformats/svg/Ghostscript_Tiger.svg")

timeIt "svg decode":
keep decodeSvg(data)
discard decodeSvg(data)
Binary file modified tests/contexts/bezierCurveTo_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/contexts/bezierCurveTo_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/contexts/clip_text.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/contexts/ellipse_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/contexts/fillText_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/contexts/quadracticCurveTo_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/contexts/quadracticCurveTo_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/contexts/strokeText_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/Ghostscript_Tiger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/circle01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/ellipse01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/miterlimit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/polygon01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/quad01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/rect02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/diffs/scale.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/emojitwo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/flat-color-icons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/ionicons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/noto-emoji.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/fileformats/svg/openmoji.png
Binary file modified tests/fileformats/svg/rendered/Ghostscript_Tiger.png
Binary file modified tests/fileformats/svg/rendered/circle01.png
Binary file modified tests/fileformats/svg/rendered/ellipse01.png
Binary file modified tests/fileformats/svg/rendered/miterlimit.png
Binary file modified tests/fileformats/svg/rendered/polygon01.png
Binary file modified tests/fileformats/svg/rendered/quad01.png
Binary file modified tests/fileformats/svg/rendered/rect02.png
Binary file modified tests/fileformats/svg/rendered/scale.png
Binary file modified tests/fileformats/svg/simple-icons.png
Binary file modified tests/fileformats/svg/tabler-icons.png
Binary file modified tests/fileformats/svg/twbs-icons.png
Binary file modified tests/fileformats/svg/twemoji.png
Binary file modified tests/fonts/diffs/alignments.png
Binary file modified tests/fonts/diffs/basic4.png
Binary file modified tests/fonts/diffs/basic8b.png
Binary file modified tests/fonts/diffs/cff.png
Binary file modified tests/fonts/diffs/cff_jp.png
Binary file modified tests/fonts/diffs/cff_strikethrough.png
Binary file modified tests/fonts/diffs/cff_underline.png
Binary file modified tests/fonts/diffs/fallback.png
Binary file modified tests/fonts/diffs/fallback2.png
Binary file modified tests/fonts/diffs/huge1.png
Binary file modified tests/fonts/diffs/huge1_nokern.png
Binary file modified tests/fonts/diffs/huge2.png
Binary file modified tests/fonts/diffs/huge2_nokern.png
Binary file modified tests/fonts/diffs/huge3_nokern.png
Binary file modified tests/fonts/diffs/image_stroke.png
Binary file modified tests/fonts/diffs/lines1.png
Binary file modified tests/fonts/diffs/lines2.png
Binary file modified tests/fonts/diffs/mask_stroke.png
Binary file modified tests/fonts/diffs/pairs1.png
Binary file modified tests/fonts/diffs/pairs2.png
Binary file modified tests/fonts/diffs/pairs3.png
Binary file modified tests/fonts/diffs/paragraph1.png
Binary file modified tests/fonts/diffs/paragraph1_2.png
Binary file modified tests/fonts/diffs/paragraph1_3.png
Binary file modified tests/fonts/diffs/paragraph1_nokern.png
Binary file modified tests/fonts/diffs/paragraph1_nokern_2.png
Binary file modified tests/fonts/diffs/paragraph1_nokern_3.png
Binary file modified tests/fonts/diffs/paragraph2.png
Binary file modified tests/fonts/diffs/paragraph2_2.png
Binary file modified tests/fonts/diffs/paragraph2_3.png
Binary file modified tests/fonts/diffs/paragraph2_nokern.png
Binary file modified tests/fonts/diffs/paragraph2_nokern_2.png
Binary file modified tests/fonts/diffs/paragraph2_nokern_3.png
Binary file modified tests/fonts/diffs/paragraph3.png
Binary file modified tests/fonts/diffs/paragraph3_2.png
Binary file modified tests/fonts/diffs/paragraph3_3.png
Binary file modified tests/fonts/diffs/paragraph3_nokern_2.png
Binary file modified tests/fonts/diffs/paragraph3_nokern_3.png
Binary file modified tests/fonts/diffs/paragraph4.png
Binary file modified tests/fonts/diffs/paragraph4_2.png
Binary file modified tests/fonts/diffs/paragraph4_3.png
Binary file modified tests/fonts/diffs/paragraph4_nokern.png
Binary file modified tests/fonts/diffs/paragraph4_nokern_2.png
Binary file modified tests/fonts/diffs/paragraph4_nokern_3.png
Binary file modified tests/fonts/diffs/paragraph5.png
Binary file modified tests/fonts/diffs/paragraph5_2.png
Binary file modified tests/fonts/diffs/paragraph5_3.png
Binary file modified tests/fonts/diffs/paragraph5_nokern.png
Binary file modified tests/fonts/diffs/paragraph5_nokern_2.png
Binary file modified tests/fonts/diffs/paragraph5_nokern_3.png
Binary file modified tests/fonts/diffs/selection_rects1.png
Binary file modified tests/fonts/diffs/selection_rects2.png
Binary file modified tests/fonts/diffs/spans1.png
Binary file modified tests/fonts/diffs/spans4.png
Binary file modified tests/fonts/diffs/spans5.png
Binary file modified tests/fonts/diffs/spans6.png
Binary file modified tests/fonts/diffs/strikethrough3.png
Binary file modified tests/fonts/diffs/tofu_advance.png
Binary file modified tests/fonts/diffs/underline3.png
Binary file modified tests/fonts/rendered/alignments.png
Binary file modified tests/fonts/rendered/basic4.png
Binary file modified tests/fonts/rendered/basic8b.png
Binary file modified tests/fonts/rendered/cff.png
Binary file modified tests/fonts/rendered/cff_jp.png
Binary file modified tests/fonts/rendered/cff_strikethrough.png
Binary file modified tests/fonts/rendered/cff_underline.png
Binary file modified tests/fonts/rendered/fallback.png
Binary file modified tests/fonts/rendered/fallback2.png
Binary file modified tests/fonts/rendered/huge1.png
Binary file modified tests/fonts/rendered/huge1_nokern.png
Binary file modified tests/fonts/rendered/huge2.png
Binary file modified tests/fonts/rendered/huge2_nokern.png
Binary file modified tests/fonts/rendered/huge3.png
Binary file modified tests/fonts/rendered/huge3_nokern.png
Binary file modified tests/fonts/rendered/image_stroke.png
Binary file modified tests/fonts/rendered/lines1.png
Binary file modified tests/fonts/rendered/lines2.png
Binary file modified tests/fonts/rendered/mask_stroke.png
Binary file modified tests/fonts/rendered/pairs1.png
Binary file modified tests/fonts/rendered/pairs2.png
Binary file modified tests/fonts/rendered/pairs3.png
Binary file modified tests/fonts/rendered/paragraph1.png
Binary file modified tests/fonts/rendered/paragraph1_2.png
Binary file modified tests/fonts/rendered/paragraph1_3.png
Binary file modified tests/fonts/rendered/paragraph1_nokern.png
Binary file modified tests/fonts/rendered/paragraph1_nokern_2.png
Binary file modified tests/fonts/rendered/paragraph1_nokern_3.png
Binary file modified tests/fonts/rendered/paragraph2.png
Binary file modified tests/fonts/rendered/paragraph2_2.png
Binary file modified tests/fonts/rendered/paragraph2_3.png
Binary file modified tests/fonts/rendered/paragraph2_nokern.png
Binary file modified tests/fonts/rendered/paragraph2_nokern_2.png
Binary file modified tests/fonts/rendered/paragraph2_nokern_3.png
Binary file modified tests/fonts/rendered/paragraph3.png
Binary file modified tests/fonts/rendered/paragraph3_2.png
Binary file modified tests/fonts/rendered/paragraph3_3.png
Binary file modified tests/fonts/rendered/paragraph3_nokern.png
Binary file modified tests/fonts/rendered/paragraph3_nokern_2.png
Binary file modified tests/fonts/rendered/paragraph3_nokern_3.png
Binary file modified tests/fonts/rendered/paragraph4.png
Binary file modified tests/fonts/rendered/paragraph4_2.png
Binary file modified tests/fonts/rendered/paragraph4_3.png
Binary file modified tests/fonts/rendered/paragraph4_nokern.png
Binary file modified tests/fonts/rendered/paragraph4_nokern_2.png
Binary file modified tests/fonts/rendered/paragraph4_nokern_3.png
Binary file modified tests/fonts/rendered/paragraph5.png
Binary file modified tests/fonts/rendered/paragraph5_2.png
Binary file modified tests/fonts/rendered/paragraph5_3.png
Binary file modified tests/fonts/rendered/paragraph5_nokern.png
Binary file modified tests/fonts/rendered/paragraph5_nokern_2.png
Binary file modified tests/fonts/rendered/paragraph5_nokern_3.png
Binary file modified tests/fonts/rendered/selection_rects1.png
Binary file modified tests/fonts/rendered/selection_rects2.png
Binary file modified tests/fonts/rendered/spans1.png
Binary file modified tests/fonts/rendered/spans2.png
Binary file modified tests/fonts/rendered/spans4.png
Binary file modified tests/fonts/rendered/spans5.png
Binary file modified tests/fonts/rendered/spans6.png
Binary file modified tests/fonts/rendered/strikethrough3.png
Binary file modified tests/fonts/rendered/tofu_advance.png
Binary file modified tests/fonts/rendered/underline3.png
Binary file modified tests/images/drawRoundedRect.png
Binary file modified tests/images/mask2image.png
Binary file modified tests/images/strokeEllipse.png
Binary file modified tests/images/strokePolygon.png
Binary file modified tests/images/strokeRoundedRect.png
Binary file modified tests/masks/drawEllipse.png
Binary file modified tests/masks/drawRoundedRect.png
Binary file modified tests/masks/maskMagnified.png
Binary file modified tests/masks/maskMinified.png
Binary file modified tests/masks/strokeEllipse.png
Binary file modified tests/masks/strokePolygon.png
Binary file modified tests/masks/strokeRoundedRect.png
Binary file modified tests/paths/arc.png
Binary file modified tests/paths/arcTo1.png
Binary file modified tests/paths/arcTo2.png
Binary file modified tests/paths/arcTo3.png
Binary file modified tests/paths/opacityStroke.png
Binary file modified tests/paths/pathRotatedArc.png
Binary file modified tests/paths/pixelScale.png