diff --git a/src/pixie/images.nim b/src/pixie/images.nim index eb475fa..31c0a51 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -317,114 +317,60 @@ proc invert*(target: Image | Mask) = for j in i ..< target.data.len: 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. let radius = round(radius).int if radius == 0: 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) - when type(target) is Image: - # TODO support offBounds for images. - doAssert offBounds == 0 + # TODO support offBounds for images. - template `*`(sample: ColorRGBA, a: uint32): array[4, uint32] = - [ - sample.r * a, - sample.g * a, - sample.b * a, - sample.a * a - ] + 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 `+=`(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 - ) + 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. - var blurX = newImage(target.width, target.height) - for y in 0 ..< target.height: - for x in 0 ..< target.width: - var values: array[4, uint32] - for xb in -radius .. radius: - let - sample = target[x + xb, y] - a = lookup[xb + radius].uint32 - values += sample * a - blurX.setRgbaUnsafe(x, y, values.rgba()) + # Blur in the X direction. + var blurX = newImage(image.width, image.height) + for y in 0 ..< image.height: + for x in 0 ..< image.width: + var values: array[4, uint32] + for xb in -radius .. radius: + let + sample = image[x + xb, y] + a = lookup[xb + radius].uint32 + values += sample * a + blurX.setRgbaUnsafe(x, y, values.rgba()) - # Blur in the Y direction. - for y in 0 ..< target.height: - for x in 0 ..< target.width: - var values: array[4, uint32] - for yb in -radius .. radius: - let - sample = blurX[x, y + yb] - a = lookup[yb + radius].uint32 - values += sample * a - target.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) + # Blur in the Y direction. + for y in 0 ..< image.height: + for x in 0 ..< image.width: + var values: array[4, uint32] + for yb in -radius .. radius: + let + sample = blurX[x, y + yb] + a = lookup[yb + radius].uint32 + values += sample * a + image.setRgbaUnsafe(x, y, values.rgba()) proc newMask*(image: Image): Mask = ## Returns a new mask using the alpha values of the parameter image. diff --git a/src/pixie/internal.nim b/src/pixie/internal.nim index 7ec78ef..895fcfa 100644 --- a/src/pixie/internal.nim +++ b/src/pixie/internal.nim @@ -1,8 +1,29 @@ -import chroma +import chroma, vmath when defined(amd64) and not defined(pixieNoSimd): 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]) = ## Converts an image from premultiplied alpha to straight alpha. ## This is expensive for large images. diff --git a/src/pixie/masks.nim b/src/pixie/masks.nim index dd56bbb..3dc529b 100644 --- a/src/pixie/masks.nim +++ b/src/pixie/masks.nim @@ -1,4 +1,4 @@ -import common, system/memory, vmath +import common, internal, system/memory, vmath when defined(amd64) and not defined(pixieNoSimd): import nimsimd/sse2 @@ -154,5 +154,42 @@ proc ceil*(mask: Mask) = if mask.data[j] != 0: 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): {.pop.}