Merge pull request #447 from guzba/master

fillCoverage refactor
This commit is contained in:
Andre von Houck 2022-06-20 14:03:02 -07:00 committed by GitHub
commit 576ecfb918
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 269 additions and 145 deletions

View file

@ -238,7 +238,7 @@ proc blendScreen*(backdrop, source: ColorRGBX): ColorRGBX {.inline.} =
# result = alphaFix(backdrop, source, result)
# result = result.toPremultipliedAlpha()
proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX =
proc blendColorDodge*(backdrop, source: ColorRGBX): ColorRGBX =
let
backdrop = backdrop.rgba()
source = source.rgba()
@ -341,7 +341,7 @@ proc blendHardLight*(backdrop, source: ColorRGBX): ColorRGBX =
result.b = hardLight(backdrop.b, backdrop.a, source.b, source.a)
result.a = blendAlpha(backdrop.a, source.a)
proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX =
proc blendDifference*(backdrop, source: ColorRGBX): ColorRGBX =
proc blend(
backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
): uint8 {.inline.} =
@ -357,7 +357,7 @@ proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX =
result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
result.a = blendAlpha(backdrop.a, source.a)
proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX =
proc blendExclusion*(backdrop, source: ColorRGBX): ColorRGBX =
proc blend(backdrop, source: uint32): uint8 {.inline.} =
let v = (backdrop + source).int32 - ((2 * backdrop * source) div 255).int32
max(0, v).uint8
@ -366,28 +366,28 @@ proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX =
result.b = blend(backdrop.b.uint32, source.b.uint32)
result.a = blendAlpha(backdrop.a, source.a)
proc blendColor(backdrop, source: ColorRGBX): ColorRGBX =
proc blendColor*(backdrop, source: ColorRGBX): ColorRGBX =
let
backdrop = backdrop.rgba().color
source = source.rgba().color
blended = SetLum(source, Lum(backdrop))
result = alphaFix(backdrop, source, blended).rgba.rgbx()
proc blendLuminosity(backdrop, source: ColorRGBX): ColorRGBX =
proc blendLuminosity*(backdrop, source: ColorRGBX): ColorRGBX =
let
backdrop = backdrop.rgba().color
source = source.rgba().color
blended = SetLum(backdrop, Lum(source))
result = alphaFix(backdrop, source, blended).rgba.rgbx()
proc blendHue(backdrop, source: ColorRGBX): ColorRGBX =
proc blendHue*(backdrop, source: ColorRGBX): ColorRGBX =
let
backdrop = backdrop.rgba().color
source = source.rgba().color
blended = SetLum(SetSat(source, Sat(backdrop)), Lum(backdrop))
result = alphaFix(backdrop, source, blended).rgba.rgbx()
proc blendSaturation(backdrop, source: ColorRGBX): ColorRGBX =
proc blendSaturation*(backdrop, source: ColorRGBX): ColorRGBX =
let
backdrop = backdrop.rgba().color
source = source.rgba().color

View file

@ -550,6 +550,7 @@ proc newImage*(svg: Svg): Image {.raises: [PixieError].} =
result = newImage(svg.width, svg.height)
try:
var blendMode = OverwriteBlend # Start as overwrite
for (path, props) in svg.elements:
if props.display and props.opacity > 0:
if props.fill != "none":
@ -573,9 +574,12 @@ proc newImage*(svg: Svg): Image {.raises: [PixieError].} =
paint = parseHtmlColor(props.fill).rgbx
paint.opacity = props.fillOpacity * props.opacity
paint.blendMode = blendMode
result.fillPath(path, paint, props.transform, props.fillRule)
blendMode = NormalBlend # Switch to normal when compositing multiple paths
if props.stroke != rgbx(0, 0, 0, 0) and props.strokeWidth > 0:
let paint = newPaint(props.stroke)
paint.color.a *= (props.opacity * props.strokeOpacity)

View file

@ -7,14 +7,14 @@ const
]
knownTags = [
0x0100.uint16, # ImageWidth
0x0101, # ImageLength
0x0102, # BitsPerSample
0x0103, # Compression
0x0106, # PhotometricInterpretation
0x0111, # StripOffsets
0x0116, # RowsPerStrip
0x0117, # StripByteCounts
0x0140, # ColorMap
0x0101, # ImageLength
0x0102, # BitsPerSample
0x0103, # Compression
0x0106, # PhotometricInterpretation
0x0111, # StripOffsets
0x0116, # RowsPerStrip
0x0117, # StripByteCounts
0x0140, # ColorMap
]
type

View file

@ -1337,118 +1337,165 @@ proc fillCoverage(
blendMode: BlendMode
) =
var x = startX
when defined(amd64) and allowSimd:
if blendMode.hasSimdBlender():
# When supported, SIMD blend as much as possible
let
blenderSimd = blendMode.blenderSimd()
oddMask = mm_set1_epi16(cast[int16](0xff00))
div255 = mm_set1_epi16(cast[int16](0x8081))
vec255 = mm_set1_epi32(cast[int32](uint32.high))
vecZero = mm_setzero_si128()
colorVec = mm_set1_epi32(cast[int32](rgbx))
for _ in 0 ..< coverages.len div 16:
let
index = image.dataIndex(x, y)
coverageVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
if mm_movemask_epi8(mm_cmpeq_epi16(coverageVec, vecZero)) != 0xffff:
# If the coverages are not all zero
if mm_movemask_epi8(mm_cmpeq_epi32(coverageVec, vec255)) == 0xffff:
# If the coverages are all 255
if blendMode == OverwriteBlend:
when allowSimd:
when defined(amd64):
iterator simd(
coverages: seq[uint8], x: var int, startX: int
): (M128i, bool, bool) =
for _ in 0 ..< coverages.len div 16:
let
coverageVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
eqZero = mm_cmpeq_epi8(coverageVec, mm_setzero_si128())
eq255 = mm_cmpeq_epi8(coverageVec, mm_set1_epi8(cast[int8](255)))
allZeroes = mm_movemask_epi8(eqZero) == 0xffff
all255 = mm_movemask_epi8(eq255) == 0xffff
yield (coverageVec, allZeroes, all255)
x += 16
proc source(colorVec, coverageVec: M128i): M128i {.inline.} =
let
oddMask = mm_set1_epi16(cast[int16](0xff00))
div255 = mm_set1_epi16(cast[int16](0x8081))
var unpacked = unpackAlphaValues(coverageVec)
unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 16))
var
sourceEven = mm_slli_epi16(colorVec, 8)
sourceOdd = mm_and_si128(colorVec, oddMask)
sourceEven = mm_mulhi_epu16(sourceEven, unpacked)
sourceOdd = mm_mulhi_epu16(sourceOdd, unpacked)
sourceEven = mm_srli_epi16(mm_mulhi_epu16(sourceEven, div255), 7)
sourceOdd = mm_srli_epi16(mm_mulhi_epu16(sourceOdd, div255), 7)
result = mm_or_si128(sourceEven, mm_slli_epi16(sourceOdd, 8))
let colorVec = mm_set1_epi32(cast[int32](rgbx))
proc source(rgbx: ColorRGBX, coverage: uint8): ColorRGBX {.inline.} =
if coverage > 0:
if coverage == 255:
result = rgbx
else:
result = rgbx(
((rgbx.r.uint32 * coverage) div 255).uint8,
((rgbx.g.uint32 * coverage) div 255).uint8,
((rgbx.b.uint32 * coverage) div 255).uint8,
((rgbx.a.uint32 * coverage) div 255).uint8
)
case blendMode:
of OverwriteBlend:
when allowSimd:
when defined(amd64):
for (coverageVec, allZeroes, all255) in simd(coverages, x, startX):
if not allZeroes:
if all255:
for i in 0 ..< 4:
mm_storeu_si128(image.data[index + i * 4].addr, colorVec)
elif blendMode == NormalBlend:
if rgbx.a == 255:
for i in 0 ..< 4:
mm_storeu_si128(image.data[index + i * 4].addr, colorVec)
else:
for i in 0 ..< 4:
let backdrop = mm_loadu_si128(image.data[index + i * 4].addr)
mm_storeu_si128(
image.data[index + i * 4].addr,
blendNormalSimd(backdrop, colorVec)
)
mm_storeu_si128(image.unsafe[x + i * 4, y].addr, colorVec)
else:
for i in 0 ..< 4:
let backdrop = mm_loadu_si128(image.data[index + i * 4].addr)
mm_storeu_si128(
image.data[index + i * 4].addr,
blenderSimd(backdrop, colorVec)
)
else:
# Coverages are not all 255
template useCoverage(blendProc: untyped) =
var coverageVec = coverageVec
for i in 0 ..< 4:
var unpacked = unpackAlphaValues(coverageVec)
# Shift the coverages from `a` to `g` and `a` for multiplying
unpacked = mm_or_si128(unpacked, mm_srli_epi32(unpacked, 16))
var
source = colorVec
sourceEven = mm_slli_epi16(source, 8)
sourceOdd = mm_and_si128(source, oddMask)
sourceEven = mm_mulhi_epu16(sourceEven, unpacked)
sourceOdd = mm_mulhi_epu16(sourceOdd, unpacked)
sourceEven = mm_srli_epi16(mm_mulhi_epu16(sourceEven, div255), 7)
sourceOdd = mm_srli_epi16(mm_mulhi_epu16(sourceOdd, div255), 7)
source = mm_or_si128(sourceEven, mm_slli_epi16(sourceOdd, 8))
if blendMode == OverwriteBlend:
mm_storeu_si128(image.data[index + i * 4].addr, source)
else:
let backdrop = mm_loadu_si128(image.data[index + i * 4].addr)
mm_storeu_si128(
image.data[index + i * 4].addr,
blendProc(backdrop, source)
)
let source = source(colorVec, coverageVec)
mm_storeu_si128(image.unsafe[x + i * 4, y].addr, source)
coverageVec = mm_srli_si128(coverageVec, 4)
if blendMode == NormalBlend:
useCoverage(blendNormalSimd)
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0:
image.unsafe[x, y] = source(rgbx, coverage)
of NormalBlend:
when allowSimd:
when defined(amd64):
for (coverageVec, allZeroes, all255) in simd(coverages, x, startX):
if not allZeroes:
if all255 and rgbx.a == 255:
for i in 0 ..< 4:
mm_storeu_si128(image.unsafe[x + i * 4, y].addr, colorVec)
else:
useCoverage(blenderSimd)
var coverageVec = coverageVec
for i in 0 ..< 4:
let
backdrop = mm_loadu_si128(image.unsafe[x + i * 4, y].addr)
source = source(colorVec, coverageVec)
mm_storeu_si128(
image.unsafe[x + i * 4, y].addr,
blendNormalSimd(backdrop, source)
)
coverageVec = mm_srli_si128(coverageVec, 4)
elif blendMode == MaskBlend:
for i in 0 ..< 4:
mm_storeu_si128(image.data[index + i * 4].addr, vecZero)
x += 16
let blender = blendMode.blender()
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0 or blendMode == ExcludeMaskBlend:
if blendMode == NormalBlend and coverage == 255 and rgbx.a == 255:
# Skip blending
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage == 255 and rgbx.a == 255:
image.unsafe[x, y] = rgbx
continue
var source = rgbx
if coverage != 255:
source.r = ((source.r.uint32 * coverage) div 255).uint8
source.g = ((source.g.uint32 * coverage) div 255).uint8
source.b = ((source.b.uint32 * coverage) div 255).uint8
source.a = ((source.a.uint32 * coverage) div 255).uint8
if blendMode == OverwriteBlend:
image.unsafe[x, y] = source
elif coverage == 0:
discard
else:
let backdrop = image.unsafe[x, y]
image.unsafe[x, y] = blender(backdrop, source)
elif blendMode == MaskBlend:
image.unsafe[x, y] = rgbx(0, 0, 0, 0)
image.unsafe[x, y] = blendNormal(backdrop, source(rgbx, coverage))
of MaskBlend:
{.linearScanEnd.}
when allowSimd:
when defined(amd64):
for (coverageVec, allZeroes, all255) in simd(coverages, x, startX):
if not allZeroes:
if all255:
discard
else:
var coverageVec = coverageVec
for i in 0 ..< 4:
let
backdrop = mm_loadu_si128(image.unsafe[x + i * 4, y].addr)
source = source(colorVec, coverageVec)
mm_storeu_si128(
image.unsafe[x + i * 4, y].addr,
blendMaskSimd(backdrop, source)
)
coverageVec = mm_srli_si128(coverageVec, 4)
else:
for i in 0 ..< 4:
mm_storeu_si128(image.unsafe[x + i * 4, y].addr, mm_setzero_si128())
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage == 0:
image.unsafe[x, y] = rgbx(0, 0, 0, 0)
elif coverage == 255:
discard
else:
let backdrop = image.unsafe[x, y]
image.unsafe[x, y] = blendMask(backdrop, source(rgbx, coverage))
if blendMode == MaskBlend:
image.clearUnsafe(0, y, startX, y)
image.clearUnsafe(startX + coverages.len, y, image.width, y)
of SubtractMaskBlend:
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage == 255 and rgbx.a == 255:
image.unsafe[x, y] = rgbx(0, 0, 0, 0)
elif coverage != 0:
let backdrop = image.unsafe[x, y]
image.unsafe[x, y] = blendSubtractMask(backdrop, source(rgbx, coverage))
of ExcludeMaskBlend:
for x in x ..< startX + coverages.len:
let
coverage = coverages[x - startX]
backdrop = image.unsafe[x, y]
image.unsafe[x, y] = blendExcludeMask(backdrop, source(rgbx, coverage))
else:
let blender = blendMode.blender()
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0:
let backdrop = image.unsafe[x, y]
image.unsafe[x, y] = blender(backdrop, source(rgbx, coverage))
proc fillCoverage(
mask: Mask,
startX, y: int,
@ -1456,45 +1503,83 @@ proc fillCoverage(
blendMode: BlendMode
) =
var x = startX
when defined(amd64) and allowSimd:
if blendMode.hasSimdMaskBlender():
let
maskerSimd = blendMode.maskBlenderSimd()
vecZero = mm_setzero_si128()
for _ in 0 ..< coverages.len div 16:
let
index = mask.dataIndex(x, y)
coverageVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
if mm_movemask_epi8(mm_cmpeq_epi16(coverageVec, vecZero)) != 0xffff:
# If the coverages are not all zero
if blendMode == OverwriteBlend:
mm_storeu_si128(mask.data[index].addr, coverageVec)
else:
let backdrop = mm_loadu_si128(mask.data[index].addr)
template simdBlob(blendProc: untyped) =
when allowSimd:
when defined(amd64):
for _ in 0 ..< coverages.len div 16:
let
coveragesVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
eqZero = mm_cmpeq_epi8(coveragesVec, mm_setzero_si128())
allZeroes = mm_movemask_epi8(eqZero) == 0xffff
if not allZeroes:
let backdrop = mm_loadu_si128(mask.unsafe[x, y].addr)
mm_storeu_si128(
mask.data[index].addr,
maskerSimd(backdrop, coverageVec)
mask.unsafe[x, y].addr,
blendProc(backdrop, coveragesVec)
)
elif blendMode == MaskBlend:
mm_storeu_si128(mask.data[index].addr, vecZero)
x += 16
x += 16
let maskBlender = blendMode.maskBlender()
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0 or blendMode == ExcludeMaskBlend:
if blendMode == OverwriteBlend:
mask.unsafe[x, y] = coverage
else:
template blendBlob(blendProc: untyped) =
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0:
let backdrop = mask.unsafe[x, y]
mask.unsafe[x, y] = maskBlender(backdrop, coverage)
elif blendMode == MaskBlend:
mask.unsafe[x, y] = 0
mask.unsafe[x, y] = blendProc(backdrop, coverage)
case blendMode:
of OverwriteBlend:
copyMem(
mask.unsafe[startX, y].addr,
coverages[0].unsafeAddr,
coverages.len
)
of NormalBlend:
simdBlob(maskBlendNormalSimd)
blendBlob(maskBlendNormal)
of MaskBlend:
when allowSimd:
when defined(amd64):
for _ in 0 ..< coverages.len div 16:
let
coveragesVec = mm_loadu_si128(coverages[x - startX].unsafeAddr)
eqZero = mm_cmpeq_epi8(coveragesVec, mm_setzero_si128())
allZeroes = mm_movemask_epi8(eqZero) == 0xffff
if not allZeroes:
let backdrop = mm_loadu_si128(mask.unsafe[x, y].addr)
mm_storeu_si128(
mask.unsafe[x, y].addr,
maskBlendMaskSimd(backdrop, coveragesVec)
)
else:
mm_storeu_si128(mask.unsafe[x, y].addr, mm_setzero_si128())
x += 16
for x in x ..< startX + coverages.len:
let coverage = coverages[x - startX]
if coverage != 0:
let backdrop = mask.unsafe[x, y]
mask.unsafe[x, y] = maskBlendMask(backdrop, coverage)
else:
mask.unsafe[x, y] = 0
if blendMode == MaskBlend:
mask.clearUnsafe(0, y, startX, y)
mask.clearUnsafe(startX + coverages.len, y, mask.width, y)
of SubtractMaskBlend:
simdBlob(maskBlendSubtractSimd)
blendBlob(maskBlendSubtract)
of ExcludeMaskBlend:
simdBlob(maskBlendExcludeSimd)
blendBlob(maskBlendExclude)
else:
let maskBlender = blendMode.maskBlender()
blendBlob(maskBlender)
proc fillHits(
image: Image,
rgbx: ColorRGBX,
@ -1530,6 +1615,8 @@ proc fillHits(
image.unsafe[x, y] = blendNormal(backdrop, rgbx)
of MaskBlend:
{.linearScanEnd.}
var filledTo = startX
for (start, len) in hits.walkInteger(numHits, windingRule, y, image.width):
block: # Clear any gap between this fill and the previous fill
@ -1553,6 +1640,21 @@ proc fillHits(
image.clearUnsafe(0, y, startX, y)
image.clearUnsafe(filledTo, y, image.width, y)
of SubtractMaskBlend:
for (start, len) in hits.walkInteger(numHits, windingRule, y, image.width):
for x in start ..< start + len:
if rgbx.a == 255:
image.unsafe[x, y] = rgbx(0, 0, 0, 0)
else:
let backdrop = image.unsafe[x, y]
image.unsafe[x, y] = blendSubtractMask(backdrop, rgbx)
of ExcludeMaskBlend:
for (start, len) in hits.walkInteger(numHits, windingRule, y, image.width):
for x in start ..< start + len:
let backdrop = image.unsafe[x, y]
image.unsafe[x, y] = blendExcludeMask(backdrop, rgbx)
else:
let blender = blendMode.blender()
for (start, len) in hits.walkInteger(numHits, windingRule, y, image.width):

View file

@ -1,6 +1,4 @@
import benchy, chroma, pixie/images, vmath
include pixie/blends
import benchy, chroma, pixie/blends, pixie/images, vmath
let
backdrop = newImage(256, 256)

View file

@ -47,6 +47,16 @@ block:
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "rect Image SubtractMaskBlend":
paint.blendMode = SubtractMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "rect Image ExcludeMaskBlend":
paint.blendMode = ExcludeMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(rect, paint)
timeIt "roundedRect Image OverwriteBlend":
paint.blendMode = OverwriteBlend
image.fillPath(roundedRect, paint)
@ -60,6 +70,16 @@ block:
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
timeIt "roundedRect Image SubtractMaskBlend":
paint.blendMode = SubtractMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
timeIt "roundedRect Image ExcludeMaskBlend":
paint.blendMode = ExcludeMaskBlend
image.fill(rgbx(255, 255, 255, 255))
image.fillPath(roundedRect, paint)
block:
let mask = newMask(width, height)