image masking
This commit is contained in:
parent
c233894283
commit
bbe207baa0
|
@ -9,6 +9,10 @@ proc fractional*(v: float32): float32 {.inline.} =
|
|||
result = abs(v)
|
||||
result = result - floor(result)
|
||||
|
||||
proc lerp*(a, b: uint8, t: float32): uint8 {.inline.} =
|
||||
let t = round(t * 255).uint32
|
||||
((a * (255 - t) + b * t) div 255).uint8
|
||||
|
||||
proc lerp*(a, b: ColorRGBA, t: float32): ColorRGBA {.inline.} =
|
||||
let x = round(t * 255).uint32
|
||||
result.r = ((a.r.uint32 * (255 - x) + b.r.uint32 * x) div 255).uint8
|
||||
|
|
|
@ -196,17 +196,8 @@ proc draw(
|
|||
rx = parseFloat(node.attr("rx"))
|
||||
ry = parseFloat(node.attr("ry"))
|
||||
|
||||
let
|
||||
magicX = (4.0 * (-1.0 + sqrt(2.0)) / 3) * rx
|
||||
magicY = (4.0 * (-1.0 + sqrt(2.0)) / 3) * ry
|
||||
|
||||
var path: Path
|
||||
path.moveTo(cx + rx, cy)
|
||||
path.bezierCurveTo(cx + rx, cy + magicY, cx + magicX, cy + ry, cx, cy + ry)
|
||||
path.bezierCurveTo(cx - magicX, cy + ry, cx - rx, cy + magicY, cx - rx, cy)
|
||||
path.bezierCurveTo(cx - rx, cy - magicY, cx - magicX, cy - ry, cx, cy - ry)
|
||||
path.bezierCurveTo(cx + magicX, cy - ry, cx + rx, cy - magicY, cx + rx, cy)
|
||||
path.closePath()
|
||||
path.ellipse(cx, cy, rx, ry)
|
||||
|
||||
if ctx.fill != ColorRGBA():
|
||||
img.fillPath(path, ctx.fill, ctx.transform)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import chroma, blends, bumpy, vmath, common, system/memory
|
||||
import chroma, blends, bumpy, vmath, common, system/memory, masks
|
||||
|
||||
when defined(amd64) and not defined(pixieNoSimd):
|
||||
import nimsimd/sse2
|
||||
|
@ -263,6 +263,40 @@ proc toStraightAlpha*(image: Image) =
|
|||
c.g = ((c.g.uint32 * multiplier) div 255).uint8
|
||||
c.b = ((c.b.uint32 * multiplier) div 255).uint8
|
||||
|
||||
proc maskCorrect*(image: Image, mask: Mask, mat = mat3()) =
|
||||
var
|
||||
matInv = mat.inverse()
|
||||
mask = mask
|
||||
|
||||
block: # Shrink mask by 2 as needed
|
||||
var
|
||||
dx = matInv * vec2(1, 0)
|
||||
dy = matInv * vec2(0, 1)
|
||||
while max(dx.length, dy.length) > 2:
|
||||
mask = mask.minifyBy2()
|
||||
dx /= 2
|
||||
dy /= 2
|
||||
matInv = matInv * scale(vec2(0.5, 0.5))
|
||||
|
||||
for y in 0 ..< image.height:
|
||||
for x in 0 ..< image.width:
|
||||
let
|
||||
maskPos = matInv * vec2(x.float32 + h, y.float32 + h)
|
||||
xFloat = maskPos.x - h
|
||||
yFloat = maskPos.y - h
|
||||
value = mask.getValueSmooth(xFloat, yFloat).uint32
|
||||
rgba = image.getRgbaUnsafe(x, y)
|
||||
blended = rgba(
|
||||
((rgba.r * value) div 255).uint8,
|
||||
((rgba.g * value) div 255).uint8,
|
||||
((rgba.b * value) div 255).uint8,
|
||||
((rgba.a * value) div 255).uint8
|
||||
)
|
||||
image.setRgbaUnsafe(x, y, blended)
|
||||
|
||||
proc mask*(image: Image, mask: Mask, pos = vec2(0, 0)) {.inline.} =
|
||||
image.maskCorrect(mask, translate(pos))
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
||||
|
@ -286,15 +320,17 @@ proc invert*(image: Image) =
|
|||
|
||||
proc getRgbaSmooth*(image: Image, x, y: float32): ColorRGBA {.inline.} =
|
||||
let
|
||||
minX = x.floor.int
|
||||
diffX = x - x.floor
|
||||
minY = y.floor.int
|
||||
diffY = y - y.floor
|
||||
minX = floor(x)
|
||||
minY = floor(y)
|
||||
diffX = x - minX
|
||||
diffY = y - minY
|
||||
x = minX.int
|
||||
y = minY.int
|
||||
|
||||
x0y0 = image[minX, minY].toPremultipliedAlpha()
|
||||
x1y0 = image[minX + 1, minY].toPremultipliedAlpha()
|
||||
x0y1 = image[minX, minY + 1].toPremultipliedAlpha()
|
||||
x1y1 = image[minX + 1, minY + 1].toPremultipliedAlpha()
|
||||
x0y0 = image[x + 0, y + 0].toPremultipliedAlpha()
|
||||
x1y0 = image[x + 1, y + 0].toPremultipliedAlpha()
|
||||
x0y1 = image[x + 0, y + 1].toPremultipliedAlpha()
|
||||
x1y1 = image[x + 1, y + 1].toPremultipliedAlpha()
|
||||
|
||||
bottomMix = lerp(x0y0, x1y0, diffX)
|
||||
topMix = lerp(x0y1, x1y1, diffX)
|
||||
|
@ -407,24 +443,21 @@ proc sharpOpacity*(image: Image) =
|
|||
else:
|
||||
rgba = rgba(255, 255, 255, 255)
|
||||
|
||||
proc drawCorrect*(a, b: Image, mat: Mat3, blendMode: BlendMode) =
|
||||
proc drawCorrect*(a, b: Image, mat = mat3(), blendMode = bmNormal) =
|
||||
## Draws one image onto another using matrix with color blending.
|
||||
var
|
||||
matInv = mat.inverse()
|
||||
# Compute movement vectors
|
||||
p = matInv * vec2(0 + h, 0 + h)
|
||||
dx = matInv * vec2(1 + h, 0 + h) - p
|
||||
dy = matInv * vec2(0 + h, 1 + h) - p
|
||||
minFilterBy2 = max(dx.length, dy.length)
|
||||
b = b
|
||||
|
||||
while minFilterBy2 > 2.0:
|
||||
b = b.minifyBy2()
|
||||
p /= 2
|
||||
dx /= 2
|
||||
dy /= 2
|
||||
minFilterBy2 /= 2
|
||||
matInv = matInv * scale(vec2(0.5, 0.5))
|
||||
block: # Shrink image by 2 as needed
|
||||
var
|
||||
dx = matInv * vec2(1, 0)
|
||||
dy = matInv * vec2(0, 1)
|
||||
while max(dx.length, dy.length) > 2:
|
||||
b = b.minifyBy2()
|
||||
dx /= 2
|
||||
dy /= 2
|
||||
matInv = matInv * scale(vec2(0.5, 0.5))
|
||||
|
||||
let blender = blendMode.blender()
|
||||
for y in 0 ..< a.height:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import common, vmath
|
||||
import common, vmath, system/memory
|
||||
|
||||
type
|
||||
Mask* = ref object
|
||||
|
@ -81,5 +81,33 @@ proc minifyBy2*(mask: Mask, power = 1): Mask =
|
|||
mask.getValueUnsafe(x * 2 + 0, y * 2 + 1)
|
||||
result.setValueUnsafe(x, y, (value div 4).uint8)
|
||||
|
||||
proc fillUnsafe(data: var seq[uint8], value: uint8, start, len: int) =
|
||||
## Fills the mask data with the parameter value starting at index start and
|
||||
## continuing for len indices.
|
||||
nimSetMem(data[start].addr, value.cint, len)
|
||||
|
||||
proc fill*(mask: Mask, value: uint8) {.inline.} =
|
||||
## Fills the mask with the parameter value.
|
||||
fillUnsafe(mask.data, value, 0, mask.data.len)
|
||||
|
||||
proc getValueSmooth*(mask: Mask, x, y: float32): uint8 =
|
||||
let
|
||||
minX = floor(x)
|
||||
minY = floor(y)
|
||||
diffX = x - minX
|
||||
diffY = y - minY
|
||||
x = minX.int
|
||||
y = minY.int
|
||||
|
||||
x0y0 = mask[x + 0, y + 0]
|
||||
x1y0 = mask[x + 1, y + 0]
|
||||
x0y1 = mask[x + 0, y + 1]
|
||||
x1y1 = mask[x + 1, y + 1]
|
||||
|
||||
bottomMix = lerp(x0y0, x1y0, diffX)
|
||||
topMix = lerp(x0y1, x1y1, diffX)
|
||||
|
||||
lerp(bottomMix, topMix, diffY)
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
|
|
@ -363,6 +363,18 @@ proc rect*(path: var Path, x, y, w, h: float32) =
|
|||
proc rect*(path: var Path, pos: Vec2, wh: Vec2) {.inline.} =
|
||||
path.rect(pos.x, pos.y, wh.x, wh.y)
|
||||
|
||||
proc ellipse*(path: var Path, cx, cy, rx, ry: float32) =
|
||||
let
|
||||
magicX = (4.0 * (-1.0 + sqrt(2.0)) / 3) * rx
|
||||
magicY = (4.0 * (-1.0 + sqrt(2.0)) / 3) * ry
|
||||
|
||||
path.moveTo(cx + rx, cy)
|
||||
path.bezierCurveTo(cx + rx, cy + magicY, cx + magicX, cy + ry, cx, cy + ry)
|
||||
path.bezierCurveTo(cx - magicX, cy + ry, cx - rx, cy + magicY, cx - rx, cy)
|
||||
path.bezierCurveTo(cx - rx, cy - magicY, cx - magicX, cy - ry, cx, cy - ry)
|
||||
path.bezierCurveTo(cx + magicX, cy - ry, cx + rx, cy - magicY, cx + rx, cy)
|
||||
path.closePath()
|
||||
|
||||
proc polygon*(path: var Path, x, y, size: float32, sides: int) =
|
||||
## Draws a n sided regular polygon at (x, y) with size.
|
||||
path.moveTo(x + size * cos(0.0), y + size * sin(0.0))
|
||||
|
|
|
@ -1,48 +1,80 @@
|
|||
import chroma, pixie, benchy
|
||||
|
||||
let a = newImage(2560, 1440)
|
||||
let image = newImage(2560, 1440)
|
||||
|
||||
proc reset() =
|
||||
image.fill(rgba(63, 127, 191, 191))
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "fill":
|
||||
a.fill(rgba(255, 255, 255, 255))
|
||||
doAssert a[0, 0] == rgba(255, 255, 255, 255)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
doAssert image[0, 0] == rgba(255, 255, 255, 255)
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "fill_rgba":
|
||||
a.fill(rgba(63, 127, 191, 191))
|
||||
doAssert a[0, 0] == rgba(63, 127, 191, 191)
|
||||
image.fill(rgba(63, 127, 191, 191))
|
||||
doAssert image[0, 0] == rgba(63, 127, 191, 191)
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "subImage":
|
||||
keep a.subImage(0, 0, 256, 256)
|
||||
keep image.subImage(0, 0, 256, 256)
|
||||
|
||||
reset()
|
||||
|
||||
# timeIt "superImage":
|
||||
# discard
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "minifyBy2":
|
||||
let minified = a.minifyBy2()
|
||||
let minified = image.minifyBy2()
|
||||
doAssert minified[0, 0] == rgba(63, 127, 191, 191)
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "invert":
|
||||
a.invert()
|
||||
keep(a)
|
||||
image.invert()
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "applyOpacity":
|
||||
a.applyOpacity(0.5)
|
||||
keep(a)
|
||||
image.applyOpacity(0.5)
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "sharpOpacity":
|
||||
a.sharpOpacity()
|
||||
keep(a)
|
||||
image.sharpOpacity()
|
||||
|
||||
a.fill(rgba(63, 127, 191, 191))
|
||||
reset()
|
||||
|
||||
timeIt "toPremultipliedAlpha":
|
||||
a.toPremultipliedAlpha()
|
||||
image.toPremultipliedAlpha()
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "toStraightAlpha":
|
||||
a.toStraightAlpha()
|
||||
image.toStraightAlpha()
|
||||
|
||||
reset()
|
||||
|
||||
block:
|
||||
var path: Path
|
||||
path.ellipse(image.width / 2, image.height / 2, 300, 300)
|
||||
|
||||
let mask = newMask(image.width, image.height)
|
||||
mask.fillPath(path)
|
||||
|
||||
timeIt "mask":
|
||||
image.mask(mask)
|
||||
|
||||
reset()
|
||||
|
||||
timeIt "lerp integers":
|
||||
for i in 0 ..< 100000:
|
||||
let c = a[0, 0]
|
||||
let c = image[0, 0]
|
||||
var z: int
|
||||
for t in 0 .. 100:
|
||||
z += lerp(c, c, t.float32 / 100).a.int
|
||||
|
@ -50,7 +82,7 @@ timeIt "lerp integers":
|
|||
|
||||
timeIt "lerp floats":
|
||||
for i in 0 ..< 100000:
|
||||
let c = a[0, 0]
|
||||
let c = image[0, 0]
|
||||
var z: int
|
||||
for t in 0 .. 100:
|
||||
z += lerp(c.color, c.color, t.float32 / 100).rgba().a.int
|
||||
|
|
|
@ -91,3 +91,17 @@ block:
|
|||
a = readImage("tests/images/flipped1.png")
|
||||
b = a.minifyBy2(2)
|
||||
b.writeFile("tests/images/minifiedBy4.png")
|
||||
|
||||
block:
|
||||
let image = newImage(100, 100)
|
||||
image.fill(rgba(255, 100, 100, 255))
|
||||
|
||||
var path: Path
|
||||
path.ellipse(image.width / 2, image.height / 2, 25, 25)
|
||||
|
||||
let mask = newMask(image.width, image.height)
|
||||
mask.fillPath(path)
|
||||
|
||||
image.mask(mask)
|
||||
image.toStraightAlpha()
|
||||
image.writeFile("tests/images/circleMask.png")
|
||||
|
|
|
@ -16,4 +16,8 @@ block:
|
|||
path.arcTo(x, y, x + w, y, r)
|
||||
mask.fillPath(path)
|
||||
|
||||
writeFile("tests/images/masks/maskMinified.png", mask.minifyBy2().encodePng())
|
||||
let minified = mask.minifyBy2()
|
||||
|
||||
doAssert minified.width == 50 and minified.height == 50
|
||||
|
||||
writeFile("tests/images/masks/maskMinified.png", minified.encodePng())
|
||||
|
|
Loading…
Reference in a new issue