This commit is contained in:
Ryan Oldenburg 2021-02-09 21:13:23 -06:00
parent b074e9bfc7
commit 3ec1710e2e
3 changed files with 82 additions and 56 deletions

View file

@ -411,7 +411,7 @@ proc newMask*(image: Image): Mask =
i += 16 i += 16
for j in i ..< image.data.len: for j in i ..< image.data.len:
result.data[i] = image.data[j].a result.data[j] = image.data[j].a
proc getRgbaSmooth*(image: Image, x, y: float32): ColorRGBA = proc getRgbaSmooth*(image: Image, x, y: float32): ColorRGBA =
let let
@ -508,70 +508,81 @@ proc draw*(
) {.inline.} = ) {.inline.} =
mask.draw(image, translate(pos), blendMode) mask.draw(image, translate(pos), blendMode)
proc gaussianLookup(radius: int): seq[float32] =
## Compute lookup table for 1d Gaussian kernel.
result.setLen(radius * 2 + 1)
var 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))
result[xb + radius] = a
total += a
for xb in -radius .. radius:
result[xb + radius] = result[xb + radius] / total
when defined(release):
{.pop.}
proc blur*(target: Image | Mask, radius: float32) = proc blur*(target: Image | Mask, 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: when type(target) is Image:
template `*`(sample: ColorRGBA, a: uint32): array[4, uint32] =
[
sample.r * a,
sample.g * a,
sample.b * a,
sample.a * a
]
template `+=`(values: var array[4, uint32], sample: array[4, uint32]) =
values[0] += sample[0]
values[1] += sample[1]
values[2] += sample[2]
values[3] += sample[3]
template rgba(values: array[4, uint32]): ColorRGBA =
rgba(
(values[0] div 1024 div 255).uint8,
(values[1] div 1024 div 255).uint8,
(values[2] 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(target.width, target.height)
for y in 0 ..< target.height: for y in 0 ..< target.height:
for x in 0 ..< target.width: for x in 0 ..< target.width:
var c: Color var values: array[4, uint32]
var totalA = 0.0
for xb in -radius .. radius: for xb in -radius .. radius:
let c2 = target[x + xb, y].color let
let a = lookup[xb + radius] sample = target[x + xb, y]
let aa = c2.a * a a = lookup[xb + radius].uint32
totalA += aa values += sample * a
c.r += c2.r * aa blurX.setRgbaUnsafe(x, y, values.rgba())
c.g += c2.g * aa
c.b += c2.b * aa
c.a += c2.a * a
c.r = c.r / totalA
c.g = c.g / totalA
c.b = c.b / totalA
blurX.setRgbaUnsafe(x, y, c.rgba)
# Blur in the Y direction. # Blur in the Y direction.
for y in 0 ..< target.height: for y in 0 ..< target.height:
for x in 0 ..< target.width: for x in 0 ..< target.width:
var c: Color var values: array[4, uint32]
var totalA = 0.0
for yb in -radius .. radius: for yb in -radius .. radius:
let c2 = blurX[x, y + yb].color let
let a = lookup[yb + radius] sample = blurX[x, y + yb]
let aa = c2.a * a a = lookup[yb + radius].uint32
totalA += aa values += sample * a
c.r += c2.r * aa target.setRgbaUnsafe(x, y, values.rgba())
c.g += c2.g * aa
c.b += c2.b * aa
c.a += c2.a * a
c.r = c.r / totalA
c.g = c.g / totalA
c.b = c.b / totalA
target.setRgbaUnsafe(x, y, c.rgba)
else: # target is a Mask else: # target is a Mask
@ -579,22 +590,27 @@ proc blur*(target: Image | Mask, radius: float32) =
var blurX = newMask(target.width, target.height) var blurX = newMask(target.width, target.height)
for y in 0 ..< target.height: for y in 0 ..< target.height:
for x in 0 ..< target.width: for x in 0 ..< target.width:
var alpha: float32 var value: uint32
for xb in -radius .. radius: for xb in -radius .. radius:
let c2 = target[x + xb, y] let
let a = lookup[xb + radius] sample = target[x + xb, y]
alpha += c2.float32 * a a = lookup[xb + radius].uint32
blurX.setValueUnsafe(x, y, alpha.uint8) value += sample * a
blurX.setValueUnsafe(x, y, (value div 1024 div 255).uint8)
# Blur in the Y direction and modify image. # Blur in the Y direction and modify image.
for y in 0 ..< target.height: for y in 0 ..< target.height:
for x in 0 ..< target.width: for x in 0 ..< target.width:
var alpha: float32 var value: uint32
for yb in -radius .. radius: for yb in -radius .. radius:
let c2 = blurX[x, y + yb] let
let a = lookup[yb + radius] sample = blurX[x, y + yb]
alpha += c2.float32 * a a = lookup[yb + radius].uint32
target.setValueUnsafe(x, y, alpha.uint8) value += sample * a
target.setValueUnsafe(x, y, (value div 1024 div 255).uint8)
when defined(release):
{.pop.}
proc sharpOpacity*(image: Image) = proc sharpOpacity*(image: Image) =
## Sharpens the opacity to extreme. ## Sharpens the opacity to extreme.

View file

@ -78,6 +78,11 @@ timeIt "newMask":
reset() reset()
timeIt "blur":
image.blur(40)
reset()
timeIt "lerp integers": timeIt "lerp integers":
for i in 0 ..< 100000: for i in 0 ..< 100000:
let c = image[0, 0] let c = image[0, 0]

View file

@ -20,3 +20,8 @@ reset()
timeIt "applyOpacity": timeIt "applyOpacity":
mask.applyOpacity(0.5) mask.applyOpacity(0.5)
reset()
timeIt "blur":
mask.blur(40)