improve cairo svg
This commit is contained in:
parent
038a133b93
commit
fd4030eb8a
3 changed files with 200 additions and 85 deletions
|
@ -1,20 +1,28 @@
|
|||
## Load and Save SVG files.
|
||||
|
||||
import cairo, chroma, pixie/common, pixie/images, strutils, vmath, xmlparser, xmltree
|
||||
import cairo, chroma, pixie/common, pixie/images, pixie/paints, strutils, tables,
|
||||
vmath, xmlparser, xmltree
|
||||
|
||||
include pixie/paths
|
||||
|
||||
# type Path = paths.Path
|
||||
|
||||
proc processCommands(c: ptr Context, path: Path) =
|
||||
c.newPath()
|
||||
c.moveTo(0, 0)
|
||||
for i, command in path.commands:
|
||||
|
||||
var
|
||||
prevCommandKind = Move
|
||||
start, at, prevCtrl2: Vec2
|
||||
for command in path.commands:
|
||||
case command.kind
|
||||
of Move:
|
||||
c.moveTo(command.numbers[0], command.numbers[1])
|
||||
at.x = command.numbers[0]
|
||||
at.y = command.numbers[1]
|
||||
start = at
|
||||
of Line:
|
||||
c.lineTo(command.numbers[0], command.numbers[1])
|
||||
at.x = command.numbers[0]
|
||||
at.y = command.numbers[1]
|
||||
of HLine:
|
||||
echo "HLine not yet supported for Cairo"
|
||||
of VLine:
|
||||
|
@ -28,6 +36,9 @@ proc processCommands(c: ptr Context, path: Path) =
|
|||
command.numbers[4],
|
||||
command.numbers[5]
|
||||
)
|
||||
at.x = command.numbers[4]
|
||||
at.y = command.numbers[5]
|
||||
prevCtrl2 = vec2(command.numbers[2], command.numbers[3])
|
||||
of SCubic:
|
||||
echo "SCubic not yet supported for Cairo"
|
||||
of Quad:
|
||||
|
@ -38,12 +49,19 @@ proc processCommands(c: ptr Context, path: Path) =
|
|||
echo "Arc not yet supported for Cairo"
|
||||
of RMove:
|
||||
c.relMoveTo(command.numbers[0], command.numbers[1])
|
||||
at.x += command.numbers[0]
|
||||
at.y += command.numbers[1]
|
||||
start = at
|
||||
of RLine:
|
||||
c.relLineTo(command.numbers[0], command.numbers[1])
|
||||
at.x += command.numbers[0]
|
||||
at.y += command.numbers[1]
|
||||
of RHLine:
|
||||
c.relLineTo(command.numbers[0], 0)
|
||||
at.x += command.numbers[0]
|
||||
of RVLine:
|
||||
c.relLineTo(0, command.numbers[0])
|
||||
at.y += command.numbers[0]
|
||||
of RCubic:
|
||||
c.relCurveTo(
|
||||
command.numbers[0],
|
||||
|
@ -53,12 +71,28 @@ proc processCommands(c: ptr Context, path: Path) =
|
|||
command.numbers[4],
|
||||
command.numbers[5]
|
||||
)
|
||||
prevCtrl2 = vec2(at.x + command.numbers[2], at.y + command.numbers[3])
|
||||
at.x += command.numbers[4]
|
||||
at.y += command.numbers[5]
|
||||
of RSCubic:
|
||||
# This is not correct but good enough for now
|
||||
c.relLineTo(
|
||||
command.numbers[2],
|
||||
command.numbers[3]
|
||||
let
|
||||
ctrl1 =
|
||||
if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}:
|
||||
at * 2 - prevCtrl2
|
||||
else:
|
||||
at
|
||||
ctrl2 = vec2(at.x + command.numbers[0], at.y + command.numbers[1])
|
||||
to = vec2(at.x + command.numbers[2], at.y + command.numbers[3])
|
||||
c.curveTo(
|
||||
ctrl1.x,
|
||||
ctrl1.y,
|
||||
ctrl2.x,
|
||||
ctrl2.y,
|
||||
to.x,
|
||||
to.y
|
||||
)
|
||||
prevCtrl2 = ctrl2
|
||||
at = to
|
||||
of RQuad:
|
||||
echo "RQuad not supported by Cairo"
|
||||
of RTQuad:
|
||||
|
@ -67,18 +101,21 @@ proc processCommands(c: ptr Context, path: Path) =
|
|||
echo "RArc not yet supported for Cairo"
|
||||
of Close:
|
||||
c.closePath()
|
||||
at = start
|
||||
|
||||
prevCommandKind = command.kind
|
||||
|
||||
checkStatus(c.status())
|
||||
|
||||
proc prepare(
|
||||
c: ptr Context,
|
||||
path: Path,
|
||||
color: ColorRGBA,
|
||||
paint: Paint,
|
||||
mat: Mat3,
|
||||
windingRule = wrNonZero
|
||||
) =
|
||||
let
|
||||
color = color.color()
|
||||
color = paint.color
|
||||
matrix = Matrix(
|
||||
xx: mat[0, 0],
|
||||
yx: mat[0, 1],
|
||||
|
@ -96,16 +133,25 @@ proc prepare(
|
|||
c.setFillRule(FillRuleEvenOdd)
|
||||
c.processCommands(path)
|
||||
|
||||
type Ctx = object
|
||||
fillRule: WindingRule
|
||||
fill, stroke: ColorRGBA
|
||||
strokeWidth: float32
|
||||
strokeLineCap: LineCap
|
||||
strokeLineJoin: LineJoin
|
||||
strokeMiterLimit: float32
|
||||
strokeDashArray: seq[float32]
|
||||
transform: Mat3
|
||||
shouldStroke: bool
|
||||
type
|
||||
LinearGradient = object
|
||||
x1, y1, x2, y2: float32
|
||||
stops: seq[ColorStop]
|
||||
|
||||
Ctx = object
|
||||
display: bool
|
||||
fillRule: WindingRule
|
||||
fill: Paint
|
||||
stroke: ColorRGBX
|
||||
strokeWidth: float32
|
||||
strokeLineCap: LineCap
|
||||
strokeLineJoin: LineJoin
|
||||
strokeMiterLimit: float32
|
||||
strokeDashArray: seq[float32]
|
||||
transform: Mat3
|
||||
shouldStroke: bool
|
||||
opacity, strokeOpacity: float32
|
||||
linearGradients: TableRef[string, LinearGradient]
|
||||
|
||||
template failInvalid() =
|
||||
raise newException(PixieError, "Invalid SVG data")
|
||||
|
@ -116,13 +162,21 @@ proc attrOrDefault(node: XmlNode, name, default: string): string =
|
|||
result = default
|
||||
|
||||
proc initCtx(): Ctx =
|
||||
result.fill = parseHtmlColor("black").rgba
|
||||
result.stroke = parseHtmlColor("black").rgba
|
||||
result.display = true
|
||||
try:
|
||||
result.fill = parseHtmlColor("black").rgbx
|
||||
result.stroke = parseHtmlColor("black").rgbx
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
raise newException(PixieError, e.msg, e)
|
||||
result.strokeWidth = 1
|
||||
result.transform = mat3()
|
||||
result.strokeMiterLimit = defaultMiterLimit
|
||||
result.opacity = 1
|
||||
result.strokeOpacity = 1
|
||||
result.linearGradients = newTable[string, LinearGradient]()
|
||||
|
||||
proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||
proc decodeCtxInternal(inherited: Ctx, node: XmlNode): Ctx =
|
||||
result = inherited
|
||||
|
||||
proc splitArgs(s: string): seq[string] =
|
||||
|
@ -143,6 +197,10 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
strokeDashArray = node.attr("stroke-dasharray")
|
||||
transform = node.attr("transform")
|
||||
style = node.attr("style")
|
||||
display = node.attr("display")
|
||||
opacity = node.attr("opacity")
|
||||
fillOpacity = node.attr("fill-opacity")
|
||||
strokeOpacity = node.attr("stroke-opacity")
|
||||
|
||||
let pairs = style.split(';')
|
||||
for pair in pairs:
|
||||
|
@ -150,6 +208,9 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
if parts.len == 2:
|
||||
# Do not override element properties
|
||||
case parts[0].strip():
|
||||
of "fill-rule":
|
||||
if fillRule.len == 0:
|
||||
fillRule = parts[1].strip()
|
||||
of "fill":
|
||||
if fill.len == 0:
|
||||
fill = parts[1].strip()
|
||||
|
@ -171,6 +232,36 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
of "stroke-dasharray":
|
||||
if strokeDashArray.len == 0:
|
||||
strokeDashArray = parts[1].strip()
|
||||
of "display":
|
||||
if display.len == 0:
|
||||
display = parts[1].strip()
|
||||
of "opacity":
|
||||
if opacity.len == 0:
|
||||
opacity = parts[1].strip()
|
||||
of "fillOpacity":
|
||||
if fillOpacity.len == 0:
|
||||
fillOpacity = parts[1].strip()
|
||||
of "strokeOpacity":
|
||||
if strokeOpacity.len == 0:
|
||||
strokeOpacity = parts[1].strip()
|
||||
else:
|
||||
when defined(pixieDebugSvg):
|
||||
maybeLogPair(parts[0], parts[1])
|
||||
elif pair.len > 0:
|
||||
when defined(pixieDebugSvg):
|
||||
echo "Invalid style pair: ", pair
|
||||
|
||||
if display.len > 0:
|
||||
result.display = display.strip() != "none"
|
||||
|
||||
if opacity.len > 0:
|
||||
result.opacity = clamp(parseFloat(opacity), 0, 1)
|
||||
|
||||
if fillOpacity.len > 0:
|
||||
result.fill.opacity = clamp(parseFloat(fillOpacity), 0, 1)
|
||||
|
||||
if strokeOpacity.len > 0:
|
||||
result.strokeOpacity = clamp(parseFloat(strokeOpacity), 0, 1)
|
||||
|
||||
if fillRule == "":
|
||||
discard # Inherit
|
||||
|
@ -186,18 +277,30 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
if fill == "" or fill == "currentColor":
|
||||
discard # Inherit
|
||||
elif fill == "none":
|
||||
result.fill = ColorRGBA()
|
||||
result.fill = ColorRGBX()
|
||||
elif fill.startsWith("url("):
|
||||
let id = fill[5 .. ^2]
|
||||
if id in result.linearGradients:
|
||||
let linearGradient = result.linearGradients[id]
|
||||
result.fill = newPaint(pkGradientLinear)
|
||||
result.fill.gradientHandlePositions = @[
|
||||
result.transform * vec2(linearGradient.x1, linearGradient.y1),
|
||||
result.transform * vec2(linearGradient.x2, linearGradient.y2)
|
||||
]
|
||||
result.fill.gradientStops = linearGradient.stops
|
||||
else:
|
||||
raise newException(PixieError, "Missing SVG resource " & id)
|
||||
else:
|
||||
result.fill = parseHtmlColor(fill).rgba
|
||||
result.fill = parseHtmlColor(fill).rgbx
|
||||
|
||||
if stroke == "":
|
||||
discard # Inherit
|
||||
elif stroke == "currentColor":
|
||||
result.shouldStroke = true
|
||||
elif stroke == "none":
|
||||
result.stroke = ColorRGBA()
|
||||
result.stroke = ColorRGBX()
|
||||
else:
|
||||
result.stroke = parseHtmlColor(stroke).rgba
|
||||
result.stroke = parseHtmlColor(stroke).rgbx
|
||||
result.shouldStroke = true
|
||||
|
||||
if strokeWidth == "":
|
||||
|
@ -208,7 +311,7 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
result.strokeWidth = parseFloat(strokeWidth)
|
||||
result.shouldStroke = true
|
||||
|
||||
if result.stroke == ColorRGBA() or result.strokeWidth <= 0:
|
||||
if result.stroke == ColorRGBX() or result.strokeWidth <= 0:
|
||||
result.shouldStroke = false
|
||||
|
||||
if strokeLineCap == "":
|
||||
|
@ -262,7 +365,7 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
else:
|
||||
template failInvalidTransform(transform: string) =
|
||||
raise newException(
|
||||
PixieError, "Unsupported SVG transform: " & transform & "."
|
||||
PixieError, "Unsupported SVG transform: " & transform
|
||||
)
|
||||
|
||||
var remaining = transform
|
||||
|
@ -320,6 +423,15 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
else:
|
||||
failInvalidTransform(transform)
|
||||
|
||||
proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||
try:
|
||||
decodeCtxInternal(inherited, node)
|
||||
except PixieError as e:
|
||||
raise e
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc cairoLineCap(lineCap: LineCap): cairo.LineCap =
|
||||
case lineCap:
|
||||
of lcButt:
|
||||
|
@ -339,19 +451,24 @@ proc cairoLineJoin(lineJoin: LineJoin): cairo.LineJoin =
|
|||
LineJoinRound
|
||||
|
||||
proc fill(c: ptr Context, ctx: Ctx, path: Path) {.inline.} =
|
||||
# img.fillPath(path, ctx.fill, ctx.transform, ctx.fillRule)
|
||||
prepare(c, path, ctx.fill, ctx.transform, ctx.fillRule)
|
||||
c.fill()
|
||||
if ctx.display and ctx.opacity > 0:
|
||||
let paint = newPaint(ctx.fill)
|
||||
paint.opacity = paint.opacity * ctx.opacity
|
||||
prepare(c, path, paint, ctx.transform, ctx.fillRule)
|
||||
c.fill()
|
||||
|
||||
proc stroke(c: ptr Context, ctx: Ctx, path: Path) {.inline.} =
|
||||
prepare(c, path, ctx.stroke, ctx.transform)
|
||||
c.setLineWidth(ctx.strokeWidth)
|
||||
c.setLineCap(ctx.strokeLineCap.cairoLineCap())
|
||||
c.setLineJoin(ctx.strokeLineJoin.cairoLineJoin())
|
||||
c.setMiterLimit(ctx.strokeMiterLimit)
|
||||
c.stroke()
|
||||
if ctx.display and ctx.opacity > 0:
|
||||
let paint = newPaint(ctx.stroke)
|
||||
paint.color.a *= (ctx.opacity * ctx.strokeOpacity)
|
||||
prepare(c, path, paint, ctx.transform)
|
||||
c.setLineWidth(ctx.strokeWidth)
|
||||
c.setLineCap(ctx.strokeLineCap.cairoLineCap())
|
||||
c.setLineJoin(ctx.strokeLineJoin.cairoLineJoin())
|
||||
c.setMiterLimit(ctx.strokeMiterLimit)
|
||||
c.stroke()
|
||||
|
||||
proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||
proc drawInternal(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||
if node.kind != xnElement:
|
||||
# Skip <!-- comments -->
|
||||
return
|
||||
|
@ -364,7 +481,7 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
let ctx = decodeCtx(ctxStack[^1], node)
|
||||
ctxStack.add(ctx)
|
||||
for child in node:
|
||||
img.draw(child, ctxStack)
|
||||
img.drawInternal(child, ctxStack)
|
||||
discard ctxStack.pop()
|
||||
|
||||
of "path":
|
||||
|
@ -372,8 +489,8 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
d = node.attr("d")
|
||||
ctx = decodeCtx(ctxStack[^1], node)
|
||||
path = parsePath(d)
|
||||
if ctx.fill != ColorRGBA():
|
||||
img.fill(ctx, path)
|
||||
|
||||
img.fill(ctx, path)
|
||||
if ctx.shouldStroke:
|
||||
img.stroke(ctx, path)
|
||||
|
||||
|
@ -385,7 +502,7 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
x2 = parseFloat(node.attrOrDefault("x2", "0"))
|
||||
y2 = parseFloat(node.attrOrDefault("y2", "0"))
|
||||
|
||||
var path: Path
|
||||
let path = newPath()
|
||||
path.moveTo(x1, y1)
|
||||
path.lineTo(x2, y2)
|
||||
|
||||
|
@ -414,7 +531,7 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
if vecs.len == 0:
|
||||
failInvalid()
|
||||
|
||||
var path: Path
|
||||
let path = newPath()
|
||||
path.moveTo(vecs[0])
|
||||
for i in 1 ..< vecs.len:
|
||||
path.lineTo(vecs[i])
|
||||
|
@ -423,9 +540,7 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
# and fill or not
|
||||
if node.tag == "polygon":
|
||||
path.closePath()
|
||||
|
||||
if ctx.fill != ColorRGBA():
|
||||
img.fill(ctx, path)
|
||||
img.fill(ctx, path)
|
||||
|
||||
if ctx.shouldStroke:
|
||||
img.stroke(ctx, path)
|
||||
|
@ -445,7 +560,7 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
rx = max(parseFloat(node.attrOrDefault("rx", "0")), 0)
|
||||
ry = max(parseFloat(node.attrOrDefault("ry", "0")), 0)
|
||||
|
||||
var path: Path
|
||||
let path = newPath()
|
||||
if rx > 0 or ry > 0:
|
||||
if rx == 0:
|
||||
rx = ry
|
||||
|
@ -466,8 +581,7 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
else:
|
||||
path.rect(x, y, width, height)
|
||||
|
||||
if ctx.fill != ColorRGBA():
|
||||
img.fill(ctx, path)
|
||||
img.fill(ctx, path)
|
||||
if ctx.shouldStroke:
|
||||
img.stroke(ctx, path)
|
||||
|
||||
|
@ -485,17 +599,25 @@ proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
|||
rx = parseFloat(node.attrOrDefault("rx", "0"))
|
||||
ry = parseFloat(node.attrOrDefault("ry", "0"))
|
||||
|
||||
var path: Path
|
||||
let path = newPath()
|
||||
path.ellipse(cx, cy, rx, ry)
|
||||
|
||||
if ctx.fill != ColorRGBA():
|
||||
img.fill(ctx, path)
|
||||
img.fill(ctx, path)
|
||||
if ctx.shouldStroke:
|
||||
img.stroke(ctx, path)
|
||||
|
||||
else:
|
||||
raise newException(PixieError, "Unsupported SVG tag: " & node.tag & ".")
|
||||
|
||||
proc draw(img: ptr Context, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||
try:
|
||||
drawInternal(img, node, ctxStack)
|
||||
except PixieError as e:
|
||||
raise e
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
raise newException(PixieError, e.msg, e)
|
||||
|
||||
proc decodeSvg*(data: string, width = 0, height = 0): Image =
|
||||
## Render SVG file and return the image. Defaults to the SVG's view box size.
|
||||
try:
|
||||
|
@ -519,21 +641,21 @@ proc decodeSvg*(data: string, width = 0, height = 0): Image =
|
|||
vec2(-viewBoxMinX.float32, -viewBoxMinY.float32)
|
||||
)
|
||||
|
||||
var surface: ptr Surface
|
||||
var
|
||||
width = width
|
||||
height = height
|
||||
surface: ptr Surface
|
||||
if width == 0 and height == 0: # Default to the view box size
|
||||
result = newImage(viewBoxWidth, viewBoxHeight)
|
||||
surface = imageSurfaceCreate(
|
||||
FORMAT_ARGB32, viewBoxWidth.int32, viewBoxHeight.int32
|
||||
)
|
||||
width = viewBoxWidth.int32
|
||||
height = viewBoxHeight.int32
|
||||
else:
|
||||
result = newImage(width, height)
|
||||
surface = imageSurfaceCreate(FORMAT_ARGB32, width.int32, height.int32)
|
||||
|
||||
let
|
||||
scaleX = width.float32 / viewBoxWidth.float32
|
||||
scaleY = height.float32 / viewBoxHeight.float32
|
||||
rootCtx.transform = rootCtx.transform * scale(vec2(scaleX, scaleY))
|
||||
|
||||
surface = imageSurfaceCreate(FORMAT_ARGB32, width.int32, height.int32)
|
||||
|
||||
let c = surface.create()
|
||||
|
||||
var ctxStack = @[rootCtx]
|
||||
|
@ -542,6 +664,8 @@ proc decodeSvg*(data: string, width = 0, height = 0): Image =
|
|||
|
||||
surface.flush()
|
||||
|
||||
result = newImage(width, height)
|
||||
|
||||
let pixels = cast[ptr UncheckedArray[array[4, uint8]]](surface.getData())
|
||||
for y in 0 ..< result.height:
|
||||
for x in 0 ..< result.width:
|
||||
|
|
|
@ -10,9 +10,19 @@ const files = [
|
|||
"ellipse01",
|
||||
"triangle01",
|
||||
"quad01",
|
||||
"Ghostscript_Tiger"
|
||||
"Ghostscript_Tiger",
|
||||
"scale",
|
||||
"miterlimit",
|
||||
"dashes"
|
||||
]
|
||||
|
||||
proc doDiff(rendered: Image, name: string) =
|
||||
rendered.writeFile(&"tests/fileformats/svg/rendered/{name}.png")
|
||||
let
|
||||
master = readImage(&"tests/fileformats/svg/masters/{name}.png")
|
||||
(diffScore, diffImage) = diff(master, rendered)
|
||||
echo &"{name} score: {diffScore}"
|
||||
diffImage.writeFile(&"tests/fileformats/svg/diffs/{name}.png")
|
||||
|
||||
for file in files:
|
||||
let image = decodeSvg(readFile(&"tests/images/svg/{file}.svg"))
|
||||
image.writeFile(&"tests/images/svg/{file}.png")
|
||||
doDiff(decodeSvg(readFile(&"tests/fileformats/svg/{file}.svg")), file)
|
||||
|
|
|
@ -79,23 +79,6 @@ proc decodeCtxInternal(inherited: Ctx, node: XmlNode): Ctx =
|
|||
fillOpacity = node.attr("fill-opacity")
|
||||
strokeOpacity = node.attr("stroke-opacity")
|
||||
|
||||
when defined(pixieDebugSvg):
|
||||
proc maybeLogPair(k, v: string) =
|
||||
if k notin [
|
||||
"fill-rule", "fill", "stroke", "stroke-width", "stroke-linecap",
|
||||
"stroke-linejoin", "stroke-miterlimit", "stroke-dasharray",
|
||||
"transform", "style", "version", "viewBox", "width", "height",
|
||||
"xmlns", "x", "y", "x1", "x2", "y1", "y2", "id", "d", "cx", "cy",
|
||||
"r", "points", "rx", "ry", "enable-background", "xml:space",
|
||||
"xmlns:xlink", "data-name", "role", "class", "opacity",
|
||||
"fill-opacity", "stroke-opacity"
|
||||
]:
|
||||
echo k, ": ", v
|
||||
|
||||
if node.attrs() != nil:
|
||||
for k, v in node.attrs():
|
||||
maybeLogPair(k, v)
|
||||
|
||||
let pairs = style.split(';')
|
||||
for pair in pairs:
|
||||
let parts = pair.split(':')
|
||||
|
@ -329,15 +312,13 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
proc fill(img: Image, ctx: Ctx, path: Path) {.inline.} =
|
||||
if ctx.display and ctx.opacity > 0:
|
||||
let paint = newPaint(ctx.fill)
|
||||
if ctx.opacity != 1:
|
||||
paint.opacity = paint.opacity * ctx.opacity
|
||||
paint.opacity = paint.opacity * ctx.opacity
|
||||
img.fillPath(path, paint, ctx.transform, ctx.fillRule)
|
||||
|
||||
proc stroke(img: Image, ctx: Ctx, path: Path) {.inline.} =
|
||||
if ctx.display and ctx.opacity > 0:
|
||||
let paint = newPaint(ctx.stroke)
|
||||
if ctx.opacity != 1:
|
||||
paint.color.a *= (ctx.opacity * ctx.strokeOpacity)
|
||||
paint.color.a *= (ctx.opacity * ctx.strokeOpacity)
|
||||
img.strokePath(
|
||||
path,
|
||||
paint,
|
||||
|
|
Loading…
Reference in a new issue