separate image and masks blur
This commit is contained in:
parent
489e9359ed
commit
117cad90b2
3 changed files with 102 additions and 98 deletions
|
@ -317,114 +317,60 @@ proc invert*(target: Image | Mask) =
|
||||||
for j in i ..< target.data.len:
|
for j in i ..< target.data.len:
|
||||||
target.data[j] = (255 - target.data[j]).uint8
|
target.data[j] = (255 - target.data[j]).uint8
|
||||||
|
|
||||||
proc blur*(target: Image | Mask, radius: float32, offBounds: uint32 = 0) =
|
proc blur*(image: Image, radius: float32) =
|
||||||
## Applies Gaussian blur to the image given a radius.
|
## Applies Gaussian blur to the image given a radius.
|
||||||
let radius = round(radius).int
|
let radius = round(radius).int
|
||||||
if radius == 0:
|
if radius == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
proc gaussianLookup(radius: int): seq[uint32] =
|
|
||||||
## Compute lookup table for 1d Gaussian kernel.
|
|
||||||
## Values are [0, 255] * 1024.
|
|
||||||
result.setLen(radius * 2 + 1)
|
|
||||||
|
|
||||||
var
|
|
||||||
floats = newSeq[float32](result.len)
|
|
||||||
total = 0.0
|
|
||||||
for xb in -radius .. radius:
|
|
||||||
let
|
|
||||||
s = radius.float32 / 2.2 # 2.2 matches Figma.
|
|
||||||
x = xb.float32
|
|
||||||
a = 1 / sqrt(2 * PI * s^2) * exp(-1 * x^2 / (2 * s^2))
|
|
||||||
floats[xb + radius] = a
|
|
||||||
total += a
|
|
||||||
for xb in -radius .. radius:
|
|
||||||
floats[xb + radius] = floats[xb + radius] / total
|
|
||||||
|
|
||||||
for i, f in floats:
|
|
||||||
result[i] = round(f * 255 * 1024).uint32
|
|
||||||
|
|
||||||
let lookup = gaussianLookup(radius)
|
let lookup = gaussianLookup(radius)
|
||||||
|
|
||||||
when type(target) is Image:
|
# TODO support offBounds for images.
|
||||||
# TODO support offBounds for images.
|
|
||||||
doAssert offBounds == 0
|
|
||||||
|
|
||||||
template `*`(sample: ColorRGBA, a: uint32): array[4, uint32] =
|
template `*`(sample: ColorRGBA, a: uint32): array[4, uint32] =
|
||||||
[
|
[
|
||||||
sample.r * a,
|
sample.r * a,
|
||||||
sample.g * a,
|
sample.g * a,
|
||||||
sample.b * a,
|
sample.b * a,
|
||||||
sample.a * a
|
sample.a * a
|
||||||
]
|
]
|
||||||
|
|
||||||
template `+=`(values: var array[4, uint32], sample: array[4, uint32]) =
|
template `+=`(values: var array[4, uint32], sample: array[4, uint32]) =
|
||||||
values[0] += sample[0]
|
values[0] += sample[0]
|
||||||
values[1] += sample[1]
|
values[1] += sample[1]
|
||||||
values[2] += sample[2]
|
values[2] += sample[2]
|
||||||
values[3] += sample[3]
|
values[3] += sample[3]
|
||||||
|
|
||||||
template rgba(values: array[4, uint32]): ColorRGBA =
|
template rgba(values: array[4, uint32]): ColorRGBA =
|
||||||
rgba(
|
rgba(
|
||||||
(values[0] div 1024 div 255).uint8,
|
(values[0] div 1024 div 255).uint8,
|
||||||
(values[1] div 1024 div 255).uint8,
|
(values[1] div 1024 div 255).uint8,
|
||||||
(values[2] div 1024 div 255).uint8,
|
(values[2] div 1024 div 255).uint8,
|
||||||
(values[3] div 1024 div 255).uint8
|
(values[3] div 1024 div 255).uint8
|
||||||
)
|
)
|
||||||
|
|
||||||
# Blur in the X direction.
|
# Blur in the X direction.
|
||||||
var blurX = newImage(target.width, target.height)
|
var blurX = newImage(image.width, image.height)
|
||||||
for y in 0 ..< target.height:
|
for y in 0 ..< image.height:
|
||||||
for x in 0 ..< target.width:
|
for x in 0 ..< image.width:
|
||||||
var values: array[4, uint32]
|
var values: array[4, uint32]
|
||||||
for xb in -radius .. radius:
|
for xb in -radius .. radius:
|
||||||
let
|
let
|
||||||
sample = target[x + xb, y]
|
sample = image[x + xb, y]
|
||||||
a = lookup[xb + radius].uint32
|
a = lookup[xb + radius].uint32
|
||||||
values += sample * a
|
values += sample * a
|
||||||
blurX.setRgbaUnsafe(x, y, values.rgba())
|
blurX.setRgbaUnsafe(x, y, values.rgba())
|
||||||
|
|
||||||
# Blur in the Y direction.
|
# Blur in the Y direction.
|
||||||
for y in 0 ..< target.height:
|
for y in 0 ..< image.height:
|
||||||
for x in 0 ..< target.width:
|
for x in 0 ..< image.width:
|
||||||
var values: array[4, uint32]
|
var values: array[4, uint32]
|
||||||
for yb in -radius .. radius:
|
for yb in -radius .. radius:
|
||||||
let
|
let
|
||||||
sample = blurX[x, y + yb]
|
sample = blurX[x, y + yb]
|
||||||
a = lookup[yb + radius].uint32
|
a = lookup[yb + radius].uint32
|
||||||
values += sample * a
|
values += sample * a
|
||||||
target.setRgbaUnsafe(x, y, values.rgba())
|
image.setRgbaUnsafe(x, y, values.rgba())
|
||||||
|
|
||||||
else: # target is a Mask
|
|
||||||
|
|
||||||
# Blur in the X direction.
|
|
||||||
var blurX = newMask(target.width, target.height)
|
|
||||||
for y in 0 ..< target.height:
|
|
||||||
for x in 0 ..< target.width:
|
|
||||||
var value: uint32
|
|
||||||
for xb in -radius .. radius:
|
|
||||||
var sample: uint32
|
|
||||||
if target.inside(x + xb, y):
|
|
||||||
sample = target.getValueUnsafe(x + xb, y)
|
|
||||||
else:
|
|
||||||
sample = offBounds
|
|
||||||
let a = lookup[xb + radius].uint32
|
|
||||||
value += sample * a
|
|
||||||
blurX.setValueUnsafe(x, y, (value div 1024 div 255).uint8)
|
|
||||||
|
|
||||||
# Blur in the Y direction and modify image.
|
|
||||||
for y in 0 ..< target.height:
|
|
||||||
for x in 0 ..< target.width:
|
|
||||||
var value: uint32
|
|
||||||
for yb in -radius .. radius:
|
|
||||||
var sample: uint32
|
|
||||||
if blurX.inside(x, y + yb):
|
|
||||||
sample = blurX.getValueUnsafe(x, y + yb)
|
|
||||||
else:
|
|
||||||
sample = offBounds
|
|
||||||
let a = lookup[yb + radius].uint32
|
|
||||||
value += sample * a
|
|
||||||
target.setValueUnsafe(x, y, (value div 1024 div 255).uint8)
|
|
||||||
|
|
||||||
proc newMask*(image: Image): Mask =
|
proc newMask*(image: Image): Mask =
|
||||||
## Returns a new mask using the alpha values of the parameter image.
|
## Returns a new mask using the alpha values of the parameter image.
|
||||||
|
|
|
@ -1,8 +1,29 @@
|
||||||
import chroma
|
import chroma, vmath
|
||||||
|
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
import nimsimd/sse2
|
import nimsimd/sse2
|
||||||
|
|
||||||
|
proc gaussianLookup*(radius: int): seq[uint32] =
|
||||||
|
## Compute lookup table for 1d Gaussian kernel.
|
||||||
|
## Values are [0, 255] * 1024.
|
||||||
|
result.setLen(radius * 2 + 1)
|
||||||
|
|
||||||
|
var
|
||||||
|
floats = newSeq[float32](result.len)
|
||||||
|
total = 0.0
|
||||||
|
for xb in -radius .. radius:
|
||||||
|
let
|
||||||
|
s = radius.float32 / 2.2 # 2.2 matches Figma.
|
||||||
|
x = xb.float32
|
||||||
|
a = 1 / sqrt(2 * PI * s^2) * exp(-1 * x^2 / (2 * s^2))
|
||||||
|
floats[xb + radius] = a
|
||||||
|
total += a
|
||||||
|
for xb in -radius .. radius:
|
||||||
|
floats[xb + radius] = floats[xb + radius] / total
|
||||||
|
|
||||||
|
for i, f in floats:
|
||||||
|
result[i] = round(f * 255 * 1024).uint32
|
||||||
|
|
||||||
proc toStraightAlpha*(data: var seq[ColorRGBA | ColorRGBX]) =
|
proc toStraightAlpha*(data: var seq[ColorRGBA | ColorRGBX]) =
|
||||||
## Converts an image from premultiplied alpha to straight alpha.
|
## Converts an image from premultiplied alpha to straight alpha.
|
||||||
## This is expensive for large images.
|
## This is expensive for large images.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import common, system/memory, vmath
|
import common, internal, system/memory, vmath
|
||||||
|
|
||||||
when defined(amd64) and not defined(pixieNoSimd):
|
when defined(amd64) and not defined(pixieNoSimd):
|
||||||
import nimsimd/sse2
|
import nimsimd/sse2
|
||||||
|
@ -154,5 +154,42 @@ proc ceil*(mask: Mask) =
|
||||||
if mask.data[j] != 0:
|
if mask.data[j] != 0:
|
||||||
mask.data[j] = 255
|
mask.data[j] = 255
|
||||||
|
|
||||||
|
proc blur*(mask: Mask, radius: float32, offBounds: uint32 = 0) =
|
||||||
|
## Applies Gaussian blur to the image given a radius.
|
||||||
|
let radius = round(radius).int
|
||||||
|
if radius == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
let lookup = gaussianLookup(radius)
|
||||||
|
|
||||||
|
# Blur in the X direction.
|
||||||
|
var blurX = newMask(mask.width, mask.height)
|
||||||
|
for y in 0 ..< mask.height:
|
||||||
|
for x in 0 ..< mask.width:
|
||||||
|
var value: uint32
|
||||||
|
for xb in -radius .. radius:
|
||||||
|
var sample: uint32
|
||||||
|
if mask.inside(x + xb, y):
|
||||||
|
sample = mask.getValueUnsafe(x + xb, y)
|
||||||
|
else:
|
||||||
|
sample = offBounds
|
||||||
|
let a = lookup[xb + radius].uint32
|
||||||
|
value += sample * a
|
||||||
|
blurX.setValueUnsafe(x, y, (value div 1024 div 255).uint8)
|
||||||
|
|
||||||
|
# Blur in the Y direction and modify image.
|
||||||
|
for y in 0 ..< mask.height:
|
||||||
|
for x in 0 ..< mask.width:
|
||||||
|
var value: uint32
|
||||||
|
for yb in -radius .. radius:
|
||||||
|
var sample: uint32
|
||||||
|
if blurX.inside(x, y + yb):
|
||||||
|
sample = blurX.getValueUnsafe(x, y + yb)
|
||||||
|
else:
|
||||||
|
sample = offBounds
|
||||||
|
let a = lookup[yb + radius].uint32
|
||||||
|
value += sample * a
|
||||||
|
mask.setValueUnsafe(x, y, (value div 1024 div 255).uint8)
|
||||||
|
|
||||||
when defined(release):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
Loading…
Reference in a new issue