Merge pull request #103 from guzba/master
sharpen mask, some mask blends, draw y optimization
This commit is contained in:
commit
e9a98e3dd1
8 changed files with 125 additions and 62 deletions
|
@ -466,12 +466,13 @@ proc blender*(blendMode: BlendMode): Blender =
|
||||||
of bmSubtractMask: blendSubtractMask
|
of bmSubtractMask: blendSubtractMask
|
||||||
of bmIntersectMask: blendIntersectMask
|
of bmIntersectMask: blendIntersectMask
|
||||||
of bmExcludeMask: blendExcludeMask
|
of bmExcludeMask: blendExcludeMask
|
||||||
else:
|
|
||||||
# blendWhite
|
proc maskNormal(backdrop, source: uint8): uint8 =
|
||||||
# blendNormal
|
## Blending masks
|
||||||
raise newException(PixieError, "No blender for " & $blendMode)
|
blendAlpha(backdrop, source)
|
||||||
|
|
||||||
proc maskMask(backdrop, source: uint8): uint8 =
|
proc maskMask(backdrop, source: uint8): uint8 =
|
||||||
|
## Masking masks
|
||||||
((backdrop.uint32 * source) div 255).uint8
|
((backdrop.uint32 * source) div 255).uint8
|
||||||
|
|
||||||
proc maskSubtract(backdrop, source: uint8): uint8 =
|
proc maskSubtract(backdrop, source: uint8): uint8 =
|
||||||
|
@ -488,6 +489,7 @@ proc maskOverwrite(backdrop, source: uint8): uint8 =
|
||||||
|
|
||||||
proc masker*(blendMode: BlendMode): Masker =
|
proc masker*(blendMode: BlendMode): Masker =
|
||||||
case blendMode:
|
case blendMode:
|
||||||
|
of bmNormal: maskNormal
|
||||||
of bmMask: maskMask
|
of bmMask: maskMask
|
||||||
of bmOverwrite: maskOverwrite
|
of bmOverwrite: maskOverwrite
|
||||||
of bmSubtractMask: maskSubtract
|
of bmSubtractMask: maskSubtract
|
||||||
|
@ -499,7 +501,9 @@ proc masker*(blendMode: BlendMode): Masker =
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
import nimsimd/sse2
|
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 =
|
proc blendNormalSimd*(backdrop, source: M128i): M128i =
|
||||||
let
|
let
|
||||||
|
@ -540,5 +544,51 @@ when defined(amd64) and not defined(pixieNoSimd):
|
||||||
else:
|
else:
|
||||||
raise newException(PixieError, "No SIMD blender for " & $blendMode)
|
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):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
|
@ -612,15 +612,6 @@ proc blur*(target: Image | Mask, radius: float32) =
|
||||||
when defined(release):
|
when defined(release):
|
||||||
{.pop.}
|
{.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(
|
proc drawUber(
|
||||||
a, b: Image,
|
a, b: Image,
|
||||||
p, dx, dy: Vec2,
|
p, dx, dy: Vec2,
|
||||||
|
@ -629,12 +620,29 @@ proc drawUber(
|
||||||
smooth: bool
|
smooth: bool
|
||||||
) =
|
) =
|
||||||
let blender = blendMode.blender()
|
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
|
var
|
||||||
xMin = a.width
|
xMin = a.width
|
||||||
xMax = 0
|
xMax = 0
|
||||||
for yOffset in [0.float32, 1]:
|
for yOffset in [0.float32, 1]:
|
||||||
var scanLine = Line(
|
let scanLine = Line(
|
||||||
a: vec2(-1000, y.float32 + yOffset),
|
a: vec2(-1000, y.float32 + yOffset),
|
||||||
b: vec2(1000, y.float32 + yOffset)
|
b: vec2(1000, y.float32 + yOffset)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import common, vmath, system/memory
|
import common, vmath, system/memory
|
||||||
|
|
||||||
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
|
import nimsimd/sse2
|
||||||
|
|
||||||
type
|
type
|
||||||
Mask* = ref object
|
Mask* = ref object
|
||||||
## Mask object that holds mask opacity data.
|
## Mask object that holds mask opacity data.
|
||||||
|
@ -132,5 +135,23 @@ proc spread*(mask: Mask, spread: float32) =
|
||||||
break blurBox
|
break blurBox
|
||||||
mask.setValueUnsafe(x, y, maxValue)
|
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):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
|
@ -373,11 +373,18 @@ proc roundedRect*(
|
||||||
se = min(se, maxRadius)
|
se = min(se, maxRadius)
|
||||||
sw = min(sw, maxRadius)
|
sw = min(sw, maxRadius)
|
||||||
|
|
||||||
path.moveTo(pos.x + nw, pos.y)
|
if clockwise:
|
||||||
path.arcTo(pos.x + wh.x, pos.y, pos.x + wh.x, pos.y + wh.y, ne)
|
path.moveTo(pos.x + nw, pos.y)
|
||||||
path.arcTo(pos.x + wh.x, pos.y + wh.y, pos.x, pos.y + wh.y, se)
|
path.arcTo(pos.x + wh.x, pos.y, pos.x + wh.x, pos.y + wh.y, ne)
|
||||||
path.arcTo(pos.x, pos.y + wh.y, pos.x, pos.y, sw)
|
path.arcTo(pos.x + wh.x, pos.y + wh.y, pos.x, pos.y + wh.y, se)
|
||||||
path.arcTo(pos.x, pos.y, pos.x + wh.x, pos.y, nw)
|
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()
|
path.closePath()
|
||||||
|
|
||||||
proc ellipse*(path: var Path, cx, cy, rx, ry: float32) =
|
proc ellipse*(path: var Path, cx, cy, rx, ry: float32) =
|
||||||
|
@ -1077,51 +1084,17 @@ proc fillShapes(
|
||||||
var x = startX
|
var x = startX
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
# When supported, SIMD blend as much as possible
|
# 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):
|
for _ in countup(x, coverages.len - 16, 16):
|
||||||
var coverage = mm_loadu_si128(coverages[x].addr)
|
var coverage = mm_loadu_si128(coverages[x].addr)
|
||||||
|
|
||||||
let eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128())
|
let eqZero = mm_cmpeq_epi16(coverage, mm_setzero_si128())
|
||||||
if mm_movemask_epi8(eqZero) != 0xffff:
|
if mm_movemask_epi8(eqZero) != 0xffff:
|
||||||
# If the coverages are not all zero
|
# If the coverages are not all zero
|
||||||
var
|
let backdrop = mm_loadu_si128(mask.data[mask.dataIndex(x, y)].addr)
|
||||||
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)
|
|
||||||
|
|
||||||
mm_storeu_si128(
|
mm_storeu_si128(
|
||||||
mask.data[mask.dataIndex(x, y)].addr,
|
mask.data[mask.dataIndex(x, y)].addr,
|
||||||
mm_or_si128(blendedEven, blendedOdd)
|
maskNormalSimd(backdrop, coverage)
|
||||||
)
|
)
|
||||||
|
|
||||||
x += 16
|
x += 16
|
||||||
|
|
|
@ -45,11 +45,6 @@ timeIt "applyOpacity":
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
timeIt "sharpOpacity":
|
|
||||||
image.sharpOpacity()
|
|
||||||
|
|
||||||
reset()
|
|
||||||
|
|
||||||
timeIt "toPremultipliedAlpha":
|
timeIt "toPremultipliedAlpha":
|
||||||
image.toPremultipliedAlpha()
|
image.toPremultipliedAlpha()
|
||||||
|
|
||||||
|
|
|
@ -25,3 +25,8 @@ reset()
|
||||||
|
|
||||||
timeIt "blur":
|
timeIt "blur":
|
||||||
mask.blur(40)
|
mask.blur(40)
|
||||||
|
|
||||||
|
reset()
|
||||||
|
|
||||||
|
timeIt "ceil":
|
||||||
|
mask.ceil()
|
||||||
|
|
BIN
tests/images/masks/circleMaskSharpened.png
Normal file
BIN
tests/images/masks/circleMaskSharpened.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 380 B |
|
@ -91,3 +91,14 @@ block:
|
||||||
a.spread(10)
|
a.spread(10)
|
||||||
|
|
||||||
writeFile("tests/images/masks/spread.png", a.encodePng())
|
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())
|
||||||
|
|
Loading…
Reference in a new issue