Merge pull request #77 from guzba/master
paths.nim expects and returns premultiplied alpha
|
@ -331,20 +331,10 @@ proc hardLight(backdrop, source: uint32): uint8 {.inline.} =
|
|||
else:
|
||||
screen(backdrop, 2 * source - 255)
|
||||
|
||||
proc blendNormal*(backdrop, source: ColorRGBA): ColorRGBA =
|
||||
proc blendNormal(backdrop, source: ColorRGBA): ColorRGBA =
|
||||
result = source
|
||||
result = alphaFix(backdrop, source, result)
|
||||
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
proc blendNormalSimd*(backdrop, source: M128i): M128i =
|
||||
let
|
||||
backdrops = cast[array[4, ColorRGBA]](backdrop)
|
||||
sources = cast[array[4, ColorRGBA]](source)
|
||||
var blended: array[4, ColorRGBA]
|
||||
for i in 0 ..< 4:
|
||||
blended[i] = blendNormal(backdrops[i], sources[i])
|
||||
cast[M128i](blended)
|
||||
|
||||
proc blendDarken(backdrop, source: ColorRGBA): ColorRGBA =
|
||||
result.r = min(backdrop.r, source.r)
|
||||
result.g = min(backdrop.g, source.g)
|
||||
|
@ -514,3 +504,55 @@ proc mixer*(blendMode: BlendMode): Mixer =
|
|||
of bmSubtractMask: blendSubtractMask
|
||||
of bmIntersectMask: blendIntersectMask
|
||||
of bmExcludeMask: blendExcludeMask
|
||||
|
||||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
||||
proc blendNormalPremultiplied*(backdrop, source: ColorRGBA): ColorRGBA {.inline.} =
|
||||
if backdrop.a == 0:
|
||||
return source
|
||||
if source.a == 255:
|
||||
return source
|
||||
if source.a == 0:
|
||||
return backdrop
|
||||
|
||||
let k = (255 - source.a.uint32)
|
||||
result.r = source.r + ((backdrop.r.uint32 * k) div 255).uint8
|
||||
result.g = source.g + ((backdrop.g.uint32 * k) div 255).uint8
|
||||
result.b = source.b + ((backdrop.b.uint32 * k) div 255).uint8
|
||||
result.a = source.a + ((backdrop.a.uint32 * k) div 255).uint8
|
||||
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
proc blendNormalPremultiplied*(backdrop, source: M128i): M128i {.inline.} =
|
||||
let
|
||||
alphaMask = mm_set1_epi32(cast[int32](0xff000000))
|
||||
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||
div255 = mm_set1_epi16(cast[int16](0x8081))
|
||||
|
||||
# Shortcuts didn't help (backdrop.a == 0, source.a == 0, source.a == 255)
|
||||
|
||||
var
|
||||
sourceAlpha = mm_and_si128(source, alphaMask)
|
||||
backdropEven = mm_slli_epi16(backdrop, 8)
|
||||
backdropOdd = mm_and_si128(backdrop, oddMask)
|
||||
|
||||
sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16))
|
||||
|
||||
let k = mm_sub_epi32(
|
||||
mm_set1_epi32(cast[int32]([0.uint8, 255, 0, 255])),
|
||||
sourceAlpha
|
||||
)
|
||||
|
||||
backdropEven = mm_mulhi_epu16(backdropEven, k)
|
||||
backdropOdd = mm_mulhi_epu16(backdropOdd, k)
|
||||
|
||||
backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
|
||||
backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)
|
||||
|
||||
mm_add_epi8(
|
||||
source,
|
||||
mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))
|
||||
)
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
|
|
@ -14,7 +14,7 @@ template failInvalid() =
|
|||
raise newException(PixieError, "Invalid SVG data")
|
||||
|
||||
proc initCtx(): Ctx =
|
||||
result.fill = parseHtmlColor("black").rgba
|
||||
result.fill = parseHtmlColor("black").rgba.toPremultipliedAlpha()
|
||||
result.strokeWidth = 1
|
||||
result.transform = mat3()
|
||||
|
||||
|
@ -32,14 +32,14 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
|||
elif fill == "none":
|
||||
result.fill = ColorRGBA()
|
||||
else:
|
||||
result.fill = parseHtmlColor(fill).rgba
|
||||
result.fill = parseHtmlColor(fill).rgba.toPremultipliedAlpha()
|
||||
|
||||
if stroke == "":
|
||||
discard # Inherit
|
||||
elif stroke == "none":
|
||||
result.stroke = ColorRGBA()
|
||||
else:
|
||||
result.stroke = parseHtmlColor(stroke).rgba
|
||||
result.stroke = parseHtmlColor(stroke).rgba.toPremultipliedAlpha()
|
||||
|
||||
if strokeWidth == "":
|
||||
discard # Inherit
|
||||
|
@ -236,6 +236,7 @@ proc decodeSvg*(data: string): Image =
|
|||
result = newImage(width, height)
|
||||
for node in root:
|
||||
result.draw(node, ctxStack)
|
||||
result.toStraightAlpha()
|
||||
except PixieError as e:
|
||||
raise e
|
||||
except:
|
||||
|
|
|
@ -775,7 +775,7 @@ proc fillShapes(
|
|||
var sortedShapes = newSeq[seq[(Segment, bool)]](shapes.len)
|
||||
for i, sorted in sortedShapes.mpairs:
|
||||
for segment in shapes[i].segments:
|
||||
if segment.at.y == segment.to.y or segment.at - segment.to == Vec2():
|
||||
if segment.at.y == segment.to.y:
|
||||
# Skip horizontal and zero-length
|
||||
continue
|
||||
var
|
||||
|
@ -889,8 +889,7 @@ proc fillShapes(
|
|||
# When supported, SIMD blend as much as possible
|
||||
|
||||
let
|
||||
alphaMask = mm_set1_epi32(cast[int32](0xff000000))
|
||||
colorMask = mm_set1_epi32(cast[int32](0x00ffffff))
|
||||
oddMask = mm_set1_epi16(cast[int16](0xff00))
|
||||
div255 = mm_set1_epi16(cast[int16](0x8081))
|
||||
zero = mm_set1_epi32(0)
|
||||
v255 = mm_set1_epi32(255)
|
||||
|
@ -905,20 +904,30 @@ proc fillShapes(
|
|||
if mm_movemask_epi8(mm_cmpeq_epi32(coverage, v255)) != 0xffff:
|
||||
# If the coverages are not all 255
|
||||
|
||||
# Shift the coverages to `a` for multiplying
|
||||
coverage = mm_slli_epi32(coverage, 24)
|
||||
# Shift the coverages from `r` to `g` and `a` for multiplying later
|
||||
coverage = mm_or_si128(
|
||||
mm_slli_epi32(coverage, 8), mm_slli_epi32(coverage, 24)
|
||||
)
|
||||
|
||||
var alpha = mm_and_si128(source, alphaMask)
|
||||
alpha = mm_mulhi_epu16(alpha, coverage)
|
||||
alpha = mm_srli_epi16(mm_mulhi_epu16(alpha, div255), 7)
|
||||
alpha = mm_slli_epi32(alpha, 8)
|
||||
var
|
||||
colorEven = mm_slli_epi16(source, 8)
|
||||
colorOdd = mm_and_si128(source, oddMask)
|
||||
|
||||
source = mm_or_si128(mm_and_si128(source, colorMask), alpha)
|
||||
colorEven = mm_mulhi_epu16(colorEven, coverage)
|
||||
colorOdd = mm_mulhi_epu16(colorOdd, coverage)
|
||||
|
||||
colorEven = mm_srli_epi16(mm_mulhi_epu16(colorEven, div255), 7)
|
||||
colorOdd = mm_srli_epi16(mm_mulhi_epu16(colorOdd, div255), 7)
|
||||
|
||||
source = mm_or_si128(colorEven, mm_slli_epi16(colorOdd, 8))
|
||||
|
||||
let
|
||||
index = image.dataIndex(x, y)
|
||||
backdrop = mm_loadu_si128(image.data[index].addr)
|
||||
mm_storeu_si128(image.data[index].addr, blendNormalSimd(backdrop, source))
|
||||
mm_storeu_si128(
|
||||
image.data[index].addr,
|
||||
blendNormalPremultiplied(backdrop, source)
|
||||
)
|
||||
|
||||
x += 4
|
||||
|
||||
|
@ -933,10 +942,13 @@ proc fillShapes(
|
|||
if coverage != 0:
|
||||
var source = color
|
||||
if coverage != 255:
|
||||
source.r = ((color.r.uint16 * coverage) div 255).uint8
|
||||
source.g = ((color.g.uint16 * coverage) div 255).uint8
|
||||
source.b = ((color.b.uint16 * coverage) div 255).uint8
|
||||
source.a = ((color.a.uint16 * coverage) div 255).uint8
|
||||
|
||||
let backdrop = image.getRgbaUnsafe(x, y)
|
||||
image.setRgbaUnsafe(x, y, blendNormal(backdrop, source))
|
||||
image.setRgbaUnsafe(x, y, blendNormalPremultiplied(backdrop, source))
|
||||
inc x
|
||||
|
||||
proc parseSomePath(path: SomePath): seq[seq[Vec2]] {.inline.} =
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 959 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 813 B After Width: | Height: | Size: 846 B |
Before Width: | Height: | Size: 701 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 356 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
@ -43,17 +43,21 @@ block:
|
|||
path = parsePath(pathStr)
|
||||
|
||||
block:
|
||||
let image = newImage(100, 100)
|
||||
let pathStr = "M 10 10 L 90 90"
|
||||
let color = rgba(255, 0, 0, 255)
|
||||
let
|
||||
image = newImage(100, 100)
|
||||
pathStr = "M 10 10 L 90 90"
|
||||
color = rgba(255, 0, 0, 255)
|
||||
image.strokePath(pathStr, color, 10)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathStroke1.png")
|
||||
|
||||
block:
|
||||
let image = newImage(100, 100)
|
||||
let pathStr = "M 10 10 L 50 60 90 90"
|
||||
let color = rgba(255, 0, 0, 255)
|
||||
let
|
||||
image = newImage(100, 100)
|
||||
pathStr = "M 10 10 L 50 60 90 90"
|
||||
color = rgba(255, 0, 0, 255)
|
||||
image.strokePath(pathStr, color, 10)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathStroke2.png")
|
||||
|
||||
block:
|
||||
|
@ -63,13 +67,16 @@ block:
|
|||
rgba(255, 255, 0, 255),
|
||||
strokeWidth = 10
|
||||
)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathStroke3.png")
|
||||
|
||||
block:
|
||||
let image = newImage(100, 100)
|
||||
let pathStr = "M 10 10 H 90 V 90 H 10 L 10 10"
|
||||
let color = rgba(0, 0, 0, 255)
|
||||
let
|
||||
image = newImage(100, 100)
|
||||
pathStr = "M 10 10 H 90 V 90 H 10 L 10 10"
|
||||
color = rgba(0, 0, 0, 255)
|
||||
image.fillPath(pathStr, color)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathBlackRectangle.png")
|
||||
|
||||
block:
|
||||
|
@ -78,6 +85,7 @@ block:
|
|||
pathStr = "M 10 10 H 90 V 90 H 10 Z"
|
||||
color = rgba(0, 0, 0, 255)
|
||||
image.fillPath(parsePath(pathStr), color)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathBlackRectangleZ.png")
|
||||
|
||||
block:
|
||||
|
@ -86,20 +94,20 @@ block:
|
|||
"M 10 10 H 90 V 90 H 10 L 10 10",
|
||||
rgba(255, 255, 0, 255)
|
||||
)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathYellowRectangle.png")
|
||||
|
||||
block:
|
||||
let image = newImage(100, 100)
|
||||
var path: Path
|
||||
path.moveTo(10, 10)
|
||||
path.lineTo(10, 90)
|
||||
path.lineTo(90, 90)
|
||||
path.lineTo(90, 10)
|
||||
path.lineTo(10, 10)
|
||||
image.fillPath(
|
||||
path,
|
||||
rgba(255, 0, 0, 255)
|
||||
)
|
||||
|
||||
let image = newImage(100, 100)
|
||||
image.fillPath(path, rgba(255, 0, 0, 255))
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathRedRectangle.png")
|
||||
|
||||
block:
|
||||
|
@ -108,6 +116,7 @@ block:
|
|||
"M30 60 A 20 20 0 0 0 90 60 L 30 60",
|
||||
parseHtmlColor("#FC427B").rgba
|
||||
)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathBottomArc.png")
|
||||
|
||||
block:
|
||||
|
@ -122,6 +131,7 @@ block:
|
|||
""",
|
||||
parseHtmlColor("#FC427B").rgba
|
||||
)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathHeart.png")
|
||||
|
||||
block:
|
||||
|
@ -130,6 +140,7 @@ block:
|
|||
"M 20 50 A 20 10 45 1 1 80 50 L 20 50",
|
||||
parseHtmlColor("#FC427B").rgba
|
||||
)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathRotatedArc.png")
|
||||
|
||||
block:
|
||||
|
@ -138,6 +149,7 @@ block:
|
|||
"M 0 50 A 50 50 0 0 0 50 0 L 50 50 L 0 50",
|
||||
parseHtmlColor("#FC427B").rgba
|
||||
)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathInvertedCornerArc.png")
|
||||
|
||||
block:
|
||||
|
@ -146,6 +158,7 @@ block:
|
|||
"M 0 50 A 50 50 0 0 1 50 0 L 50 50 L 0 50",
|
||||
parseHtmlColor("#FC427B").rgba
|
||||
)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathCornerArc.png")
|
||||
|
||||
block:
|
||||
|
@ -157,13 +170,11 @@ block:
|
|||
h = 80.0
|
||||
w = 80.0
|
||||
var path: Path
|
||||
path.moveTo(x+r, y)
|
||||
path.arcTo(x+w, y, x+w, y+h, r)
|
||||
path.arcTo(x+w, y+h, x, y+h, r)
|
||||
path.arcTo(x, y+h, x, y, r)
|
||||
path.arcTo(x, y, x+w, y, r)
|
||||
image.fillPath(
|
||||
path,
|
||||
rgba(255, 0, 0, 255)
|
||||
)
|
||||
path.moveTo(x + r, y)
|
||||
path.arcTo(x + w, y, x + w, y + h, r)
|
||||
path.arcTo(x + w, y + h, x, y + h, r)
|
||||
path.arcTo(x, y + h, x, y, r)
|
||||
path.arcTo(x, y, x + w, y, r)
|
||||
image.fillPath(path, rgba(255, 0, 0, 255))
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/paths/pathRoundRect.png")
|
||||
|
|