Merge pull request #77 from guzba/master

paths.nim expects and returns premultiplied alpha
This commit is contained in:
treeform 2021-01-25 09:22:38 -08:00 committed by GitHub
commit 29099a08eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 115 additions and 49 deletions

View file

@ -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.}

View file

@ -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:

View file

@ -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.} =

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 B

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -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")