Merge pull request #103 from guzba/master

sharpen mask, some mask blends, draw y optimization
This commit is contained in:
treeform 2021-02-11 11:16:32 -08:00 committed by GitHub
commit e9a98e3dd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 125 additions and 62 deletions

View file

@ -466,12 +466,13 @@ proc blender*(blendMode: BlendMode): Blender =
of bmSubtractMask: blendSubtractMask
of bmIntersectMask: blendIntersectMask
of bmExcludeMask: blendExcludeMask
else:
# blendWhite
# blendNormal
raise newException(PixieError, "No blender for " & $blendMode)
proc maskNormal(backdrop, source: uint8): uint8 =
## Blending masks
blendAlpha(backdrop, source)
proc maskMask(backdrop, source: uint8): uint8 =
## Masking masks
((backdrop.uint32 * source) div 255).uint8
proc maskSubtract(backdrop, source: uint8): uint8 =
@ -488,6 +489,7 @@ proc maskOverwrite(backdrop, source: uint8): uint8 =
proc masker*(blendMode: BlendMode): Masker =
case blendMode:
of bmNormal: maskNormal
of bmMask: maskMask
of bmOverwrite: maskOverwrite
of bmSubtractMask: maskSubtract
@ -499,7 +501,9 @@ proc masker*(blendMode: BlendMode): Masker =
when defined(amd64) and not defined(pixieNoSimd):
import nimsimd/sse2
type BlenderSimd* = proc(blackdrop, source: M128i): M128i
type
BlenderSimd* = proc(blackdrop, source: M128i): M128i
MaskerSimd* = proc(blackdrop, source: M128i): M128i
proc blendNormalSimd*(backdrop, source: M128i): M128i =
let
@ -540,5 +544,51 @@ when defined(amd64) and not defined(pixieNoSimd):
else:
raise newException(PixieError, "No SIMD blender for " & $blendMode)
proc maskNormalSimd*(backdrop, source: M128i): M128i =
## Blending masks
let
oddMask = mm_set1_epi16(cast[int16](0xff00))
v255high = mm_set1_epi16(cast[int16](255.uint16 shl 8))
div255 = mm_set1_epi16(cast[int16](0x8081))
var
sourceEven = mm_slli_epi16(mm_andnot_si128(oddMask, source), 8)
sourceOdd = mm_and_si128(source, oddMask)
let
evenK = mm_sub_epi16(v255high, sourceEven)
oddK = mm_sub_epi16(v255high, sourceOdd)
var
backdropEven = mm_slli_epi16(mm_andnot_si128(oddMask, backdrop), 8)
backdropOdd = mm_and_si128(backdrop, oddMask)
# backdrop * k
backdropEven = mm_mulhi_epu16(backdropEven, evenK)
backdropOdd = mm_mulhi_epu16(backdropOdd, oddK)
# div 255
backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)
# Shift from high to low bits
sourceEven = mm_srli_epi16(sourceEven, 8)
sourceOdd = mm_srli_epi16(sourceOdd, 8)
var
blendedEven = mm_add_epi16(sourceEven, backdropEven)
blendedOdd = mm_add_epi16(sourceOdd, backdropOdd)
blendedOdd = mm_slli_epi16(blendedOdd, 8)
mm_or_si128(blendedEven, blendedOdd)
proc maskerSimd*(blendMode: BlendMode): MaskerSimd =
case blendMode:
of bmNormal: maskNormalSimd
of bmOverwrite: blendOverwriteSimd
else:
raise newException(PixieError, "No SIMD masker for " & $blendMode)
when defined(release):
{.pop.}

View file

@ -612,15 +612,6 @@ proc blur*(target: Image | Mask, radius: float32) =
when defined(release):
{.pop.}
proc sharpOpacity*(image: Image) =
## Sharpens the opacity to extreme.
## A = 0 stays 0. Anything else turns into 255.
for rgba in image.data.mitems:
if rgba.a == 0:
rgba = rgba(0, 0, 0, 0)
else:
rgba = rgba(255, 255, 255, 255)
proc drawUber(
a, b: Image,
p, dx, dy: Vec2,
@ -629,12 +620,29 @@ proc drawUber(
smooth: bool
) =
let blender = blendMode.blender()
for y in 0 ..< a.height:
# Determine where we should start and stop drawing in the y dimension
var yMin, yMax: int
if blendMode == bmIntersectMask:
yMin = 0
yMax = a.height
else:
yMin = a.height
yMax = 0
for segment in perimeter:
yMin = min(yMin, segment.at.y.floor.int)
yMax = max(yMax, segment.at.y.ceil.int)
yMin = yMin.clamp(0, a.height)
yMax = yMax.clamp(0, a.height)
for y in yMin ..< yMax:
# Determine where we should start and stop drawing in the x dimension
var
xMin = a.width
xMax = 0
for yOffset in [0.float32, 1]:
var scanLine = Line(
let scanLine = Line(
a: vec2(-1000, y.float32 + yOffset),
b: vec2(1000, y.float32 + yOffset)
)

View file

@ -1,5 +1,8 @@
import common, vmath, system/memory
when defined(amd64) and not defined(pixieNoSimd):
import nimsimd/sse2
type
Mask* = ref object
## Mask object that holds mask opacity data.
@ -132,5 +135,23 @@ proc spread*(mask: Mask, spread: float32) =
break blurBox
mask.setValueUnsafe(x, y, maxValue)
proc ceil*(mask: Mask) =
## A value of 0 stays 0. Anything else turns into 255.
var i: int
when defined(amd64) and not defined(pixieNoSimd):
let
vZero = mm_setzero_si128()
vMax = mm_set1_epi32(cast[int32](uint32.high))
for _ in countup(0, mask.data.len - 16, 16):
var values = mm_loadu_si128(mask.data[i].addr)
values = mm_cmpeq_epi8(values, vZero)
values = mm_andnot_si128(values, vMax)
mm_storeu_si128(mask.data[i].addr, values)
i += 16
for j in i ..< mask.data.len:
if mask.data[j] != 0:
mask.data[j] = 255
when defined(release):
{.pop.}

View file

@ -373,11 +373,18 @@ proc roundedRect*(
se = min(se, maxRadius)
sw = min(sw, maxRadius)
path.moveTo(pos.x + nw, pos.y)
path.arcTo(pos.x + wh.x, pos.y, pos.x + wh.x, pos.y + wh.y, ne)
path.arcTo(pos.x + wh.x, pos.y + wh.y, pos.x, pos.y + wh.y, se)
path.arcTo(pos.x, pos.y + wh.y, pos.x, pos.y, sw)
path.arcTo(pos.x, pos.y, pos.x + wh.x, pos.y, nw)
if clockwise:
path.moveTo(pos.x + nw, pos.y)
path.arcTo(pos.x + wh.x, pos.y, pos.x + wh.x, pos.y + wh.y, ne)
path.arcTo(pos.x + wh.x, pos.y + wh.y, pos.x, pos.y + wh.y, se)
path.arcTo(pos.x, pos.y + wh.y, pos.x, pos.y, sw)
path.arcTo(pos.x, pos.y, pos.x + wh.x, pos.y, nw)
else:
path.moveTo(pos.x + wh.x + ne, pos.y)
path.arcTo(pos.x, pos.y, pos.x, pos.y + wh.y, nw)
path.arcTo(pos.x, pos.y + wh.y, pos.x + wh.x, pos.y + wh.y, sw)
path.arcTo(pos.x + wh.x, pos.y + wh.y, pos.x + wh.x, pos.y, se)
path.arcTo(pos.x + wh.x, pos.y, pos.x, pos.y, ne)
path.closePath()
proc ellipse*(path: var Path, cx, cy, rx, ry: float32) =
@ -1077,51 +1084,17 @@ proc fillShapes(
var x = startX
when defined(amd64) and not defined(pixieNoSimd):
# When supported, SIMD blend as much as possible
let
oddMask = mm_set1_epi16(cast[int16](0xff00))
v255high = mm_set1_epi16(cast[int16](255.uint16 shl 8))
div255 = mm_set1_epi16(cast[int16](0x8081))
for _ in countup(x, coverages.len - 16, 16):
var coverage = mm_loadu_si128(coverages[x].addr)
let eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128())
if mm_movemask_epi8(eqZero) != 0xffff:
# If the coverages are not all zero
var
coverageEven = mm_slli_epi16(mm_andnot_si128(oddMask, coverage), 8)
coverageOdd = mm_and_si128(coverage, oddMask)
let
evenK = mm_sub_epi16(v255high, coverageEven)
oddK = mm_sub_epi16(v255high, coverageOdd)
var
backdrop = mm_loadu_si128(mask.data[mask.dataIndex(x, y)].addr)
backdropEven = mm_slli_epi16(mm_andnot_si128(oddMask, backdrop), 8)
backdropOdd = mm_and_si128(backdrop, oddMask)
# backdrop * k
backdropEven = mm_mulhi_epu16(backdropEven, evenK)
backdropOdd = mm_mulhi_epu16(backdropOdd, oddK)
# div 255
backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)
# Shift from high to low bits
coverageEven = mm_srli_epi16(coverageEven, 8)
coverageOdd = mm_srli_epi16(coverageOdd, 8)
var
blendedEven = mm_add_epi16(coverageEven, backdropEven)
blendedOdd = mm_add_epi16(coverageOdd, backdropOdd)
blendedOdd = mm_slli_epi16(blendedOdd, 8)
let backdrop = mm_loadu_si128(mask.data[mask.dataIndex(x, y)].addr)
mm_storeu_si128(
mask.data[mask.dataIndex(x, y)].addr,
mm_or_si128(blendedEven, blendedOdd)
maskNormalSimd(backdrop, coverage)
)
x += 16

View file

@ -45,11 +45,6 @@ timeIt "applyOpacity":
reset()
timeIt "sharpOpacity":
image.sharpOpacity()
reset()
timeIt "toPremultipliedAlpha":
image.toPremultipliedAlpha()

View file

@ -25,3 +25,8 @@ reset()
timeIt "blur":
mask.blur(40)
reset()
timeIt "ceil":
mask.ceil()

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

View file

@ -91,3 +91,14 @@ block:
a.spread(10)
writeFile("tests/images/masks/spread.png", a.encodePng())
block:
let mask = newMask(100, 100)
var path: Path
path.ellipse(mask.width / 2, mask.height / 2, 25, 25)
mask.fillPath(path)
mask.ceil()
writeFile("tests/images/masks/circleMaskSharpened.png", mask.encodePng())