Add bmIntersectMask and bmExcludeMask.

This commit is contained in:
treeform 2020-11-23 08:56:11 -08:00
parent 779f813cca
commit 3764063605
3 changed files with 80 additions and 49 deletions

View file

@ -24,6 +24,8 @@ type BlendMode* = enum
bmMask ## Special blend mode that is used for masking bmMask ## Special blend mode that is used for masking
bmOverwrite ## Special that does not blend but copies the pixels from target. bmOverwrite ## Special that does not blend but copies the pixels from target.
bmSubtractMask ## Inverse mask bmSubtractMask ## Inverse mask
bmIntersectMask
bmExcludeMask
proc parseBlendMode*(s: string): BlendMode = proc parseBlendMode*(s: string): BlendMode =
case s: case s:
@ -97,6 +99,18 @@ proc mix*(blendMode: BlendMode, target, blend: Color): Color =
result.b = target.b result.b = target.b
result.a = target.a * (1 - blend.a) result.a = target.a * (1 - blend.a)
return return
elif blendMode == bmIntersectMask:
result.r = target.r
result.g = target.g
result.b = target.b
result.a = target.a * blend.a
return
elif blendMode == bmExcludeMask:
result.r = target.r
result.g = target.g
result.b = target.b
result.a = abs(target.a - blend.a)
return
elif blendMode == bmOverwrite: elif blendMode == bmOverwrite:
result = blend result = blend
return return

View file

@ -82,18 +82,31 @@ proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} =
if image.inside(x, y): if image.inside(x, y):
image.setRgbaUnsafe(x, y, rgba) image.setRgbaUnsafe(x, y, rgba)
proc fill*(image: Image, rgba: ColorRgba) = proc newImageFill*(width, height: int, rgba: ColorRgba): Image =
## Fills the image with a solid color. ## Fills the image with a solid color.
for i in 0 ..< image.data.len: result = newImageNoInit(width, height)
image.data[i] = rgba for y in 0 ..< result.height:
for x in 0 ..< result.width:
result.setRgbaUnsafe(x, y, rgba)
proc invert*(image: Image) = proc fill*(image: Image, rgba: ColorRgba): Image =
## Fills the image with a solid color.
result = newImageNoInit(image.width, image.height)
for y in 0 ..< result.height:
for x in 0 ..< result.width:
result.setRgbaUnsafe(x, y, rgba)
proc invert*(image: Image): Image =
## Inverts all of the colors and alpha. ## Inverts all of the colors and alpha.
for rgba in image.data.mitems: result = newImageNoInit(image.width, image.height)
rgba.r = 255 - rgba.r for y in 0 ..< image.height:
rgba.g = 255 - rgba.g for x in 0 ..< image.width:
rgba.b = 255 - rgba.b var rgba = image.getRgbaUnsafe(x, y)
rgba.a = 255 - rgba.a rgba.r = 255 - rgba.r
rgba.g = 255 - rgba.g
rgba.b = 255 - rgba.b
rgba.a = 255 - rgba.a
result.setRgbaUnsafe(x, y, rgba)
proc subImage*(image: Image, x, y, w, h: int): Image = proc subImage*(image: Image, x, y, w, h: int): Image =
## Gets a sub image of the main image. ## Gets a sub image of the main image.
@ -183,9 +196,19 @@ proc hasEffect*(blendMode: BlendMode, rgba: ColorRGBA): bool =
rgba.a != 255 rgba.a != 255
of bmOverwrite: of bmOverwrite:
true true
of bmIntersectMask:
true
else: else:
rgba.a > 0 rgba.a > 0
proc allowCopy*(blendMode: BlendMode): bool =
## Returns true if applying rgba with current blend mode has effect.
case blendMode
of bmIntersectMask:
false
else:
true
proc drawOverwrite*(a: Image, b: Image, mat: Mat3): Image = proc drawOverwrite*(a: Image, b: Image, mat: Mat3): Image =
## Draws one image onto another using integer x,y offset with COPY. ## Draws one image onto another using integer x,y offset with COPY.
result = newImageNoInit(a.width, a.height) result = newImageNoInit(a.width, a.height)
@ -195,7 +218,6 @@ proc drawOverwrite*(a: Image, b: Image, mat: Mat3): Image =
for x in 0 ..< a.width: for x in 0 ..< a.width:
var rgba = a.getRgbaUnsafe(x, y) var rgba = a.getRgbaUnsafe(x, y)
let srcPos = matInv * vec2(x.float32, y.float32) let srcPos = matInv * vec2(x.float32, y.float32)
if b.inside(srcPos.x.floor.int, srcPos.y.floor.int): if b.inside(srcPos.x.floor.int, srcPos.y.floor.int):
rgba = b.getRgbaUnsafe(srcPos.x.floor.int, srcPos.y.floor.int) rgba = b.getRgbaUnsafe(srcPos.x.floor.int, srcPos.y.floor.int)
result.setRgbaUnsafe(x, y, rgba) result.setRgbaUnsafe(x, y, rgba)
@ -206,14 +228,19 @@ proc drawBlend*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode): Image =
var matInv = mat.inverse() var matInv = mat.inverse()
for y in 0 ..< a.height: for y in 0 ..< a.height:
for x in 0 ..< a.width: for x in 0 ..< a.width:
var rgba = a.getRgbaUnsafe(x, y)
let srcPos = matInv * vec2(x.float32, y.float32)
let srcPos = matInv * vec2(x.float32, y.float32)
if b.inside(srcPos.x.floor.int, srcPos.y.floor.int): if b.inside(srcPos.x.floor.int, srcPos.y.floor.int):
var rgba = a.getRgbaUnsafe(x, y)
let rgba2 = b.getRgbaUnsafe(srcPos.x.floor.int, srcPos.y.floor.int) let rgba2 = b.getRgbaUnsafe(srcPos.x.floor.int, srcPos.y.floor.int)
if blendMode.hasEffect(rgba2): if blendMode.hasEffect(rgba2):
rgba = blendMode.mix(rgba, rgba2) rgba = blendMode.mix(rgba, rgba2)
result.setRgbaUnsafe(x, y, rgba) result.setRgbaUnsafe(x, y, rgba)
else:
if blendMode.allowCopy():
var rgba = a.getRgbaUnsafe(x, y)
result.setRgbaUnsafe(x, y, rgba)
proc drawBlendSmooth*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode): Image = proc drawBlendSmooth*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode): Image =
## Draws one image onto another using matrix with color blending. ## Draws one image onto another using matrix with color blending.
@ -224,14 +251,19 @@ proc drawBlendSmooth*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode): Imag
var matInv = mat.inverse() var matInv = mat.inverse()
for y in 0 ..< a.height: for y in 0 ..< a.height:
for x in 0 ..< a.width: for x in 0 ..< a.width:
var rgba = a.getRgbaUnsafe(x, y)
let srcPos = matInv * vec2(x.float32, y.float32)
let srcPos = matInv * vec2(x.float32, y.float32)
if b.inside1px(srcPos.x, srcPos.y): if b.inside1px(srcPos.x, srcPos.y):
var rgba = a.getRgbaUnsafe(x, y)
let rgba2 = b.getRgbaSmooth(srcPos.x, srcPos.y) let rgba2 = b.getRgbaSmooth(srcPos.x, srcPos.y)
if blendMode.hasEffect(rgba2): if blendMode.hasEffect(rgba2):
rgba = blendMode.mix(rgba, rgba2) rgba = blendMode.mix(rgba, rgba2)
result.setRgbaUnsafe(x, y, rgba) result.setRgbaUnsafe(x, y, rgba)
else:
if blendMode.allowCopy():
var rgba = a.getRgbaUnsafe(x, y)
result.setRgbaUnsafe(x, y, rgba)
proc draw*(a: Image, b: Image, mat: Mat3, blendMode = bmNormal): Image = proc draw*(a: Image, b: Image, mat: Mat3, blendMode = bmNormal): Image =
## Draws one image onto another using matrix with color blending. ## Draws one image onto another using matrix with color blending.
@ -359,22 +391,9 @@ proc shadow*(
shadow = shadow.spread(spread) shadow = shadow.spread(spread)
if blur > 0: if blur > 0:
shadow = shadow.blur(blur) shadow = shadow.blur(blur)
result = newImage(mask.width, mask.height) result = newImageFill(mask.width, mask.height, color.rgba)
result.fill(color.rgba)
return result.draw(shadow, blendMode = bmMask) return result.draw(shadow, blendMode = bmMask)
proc invertColor*(image: Image): Image =
## Flips the image around the Y axis.
result = newImageNoInit(image.width, image.height)
for y in 0 ..< image.height:
for x in 0 ..< image.width:
var rgba = image.getRgbaUnsafe(x, y)
rgba.r = 255 - rgba.r
rgba.g = 255 - rgba.g
rgba.b = 255 - rgba.b
rgba.a = 255 - rgba.a
result.setRgbaUnsafe(x, y, rgba)
proc applyOpacity*(image: Image, opacity: float32): Image = proc applyOpacity*(image: Image, opacity: float32): Image =
## Multiplies alpha of the image by opacity. ## Multiplies alpha of the image by opacity.
result = newImageNoInit(image.width, image.height) result = newImageNoInit(image.width, image.height)

View file

@ -587,11 +587,10 @@ proc fillPolygons*(
polys: seq[seq[Vec2]], polys: seq[seq[Vec2]],
color: ColorRGBA, color: ColorRGBA,
quality = 4, quality = 4,
) = ): Image =
const ep = 0.0001 * PI const ep = 0.0001 * PI
if polys.len == 0: result = newImage(image.width, image.height)
image.fill(rgba(0, 0, 0, 0))
proc scanLineHits( proc scanLineHits(
polys: seq[seq[Vec2]], polys: seq[seq[Vec2]],
@ -616,9 +615,9 @@ proc fillPolygons*(
var hits: seq[(float32, bool)] var hits: seq[(float32, bool)]
var alphas = newSeq[float32](image.width) var alphas = newSeq[float32](result.width)
for y in 0 ..< image.height: for y in 0 ..< result.height:
for x in 0 ..< image.width: for x in 0 ..< result.width:
alphas[x] = 0 alphas[x] = 0
for m in 0 ..< quality: for m in 0 ..< quality:
polys.scanLineHits(hits, y, float32(m)/float32(quality)) polys.scanLineHits(hits, y, float32(m)/float32(quality))
@ -627,7 +626,7 @@ proc fillPolygons*(
var var
penFill = 0.0 penFill = 0.0
curHit = 0 curHit = 0
for x in 0 ..< image.width: for x in 0 ..< result.width:
var penEdge = penFill var penEdge = penFill
while true: while true:
if curHit >= hits.len: if curHit >= hits.len:
@ -644,11 +643,11 @@ proc fillPolygons*(
penEdge -= 1.0 - cover penEdge -= 1.0 - cover
inc curHit inc curHit
alphas[x] += penEdge alphas[x] += penEdge
for x in 0 ..< image.width: for x in 0 ..< result.width:
var a = clamp(abs(alphas[x]) / float32(quality), 0.0, 1.0) var a = clamp(abs(alphas[x]) / float32(quality), 0.0, 1.0)
var colorWithAlpha = color var colorWithAlpha = color
colorWithAlpha.a = uint8(clamp(a, 0, 1) * 255.0) colorWithAlpha.a = uint8(clamp(a, 0, 1) * 255.0)
image[x, y] = colorWithAlpha result[x, y] = colorWithAlpha
{.pop.} {.pop.}
@ -656,7 +655,7 @@ proc fillPath*(
image: Image, image: Image,
path: Path, path: Path,
color: ColorRGBA color: ColorRGBA
) = ): Image =
let polys = commandsToPolygons(path.commands) let polys = commandsToPolygons(path.commands)
image.fillPolygons(polys, color) image.fillPolygons(polys, color)
@ -664,7 +663,7 @@ proc fillPath*(
image: Image, image: Image,
path: string, path: string,
color: ColorRGBA color: ColorRGBA
) = ): Image =
image.fillPath(parsePath(path), color) image.fillPath(parsePath(path), color)
proc fillPath*( proc fillPath*(
@ -672,7 +671,7 @@ proc fillPath*(
path: string, path: string,
color: ColorRGBA, color: ColorRGBA,
pos: Vec2 pos: Vec2
) = ): Image =
var polys = commandsToPolygons(parsePath(path).commands) var polys = commandsToPolygons(parsePath(path).commands)
for poly in polys.mitems: for poly in polys.mitems:
for i, p in poly.mpairs: for i, p in poly.mpairs:
@ -684,7 +683,7 @@ proc fillPath*(
path: Path, path: Path,
color: ColorRGBA, color: ColorRGBA,
mat: Mat3 mat: Mat3
) = ): Image =
var polys = commandsToPolygons(path.commands) var polys = commandsToPolygons(path.commands)
for poly in polys.mitems: for poly in polys.mitems:
for i, p in poly.mpairs: for i, p in poly.mpairs:
@ -696,7 +695,7 @@ proc fillPath*(
path: string, path: string,
color: ColorRGBA, color: ColorRGBA,
mat: Mat3 mat: Mat3
) = ): Image =
image.fillPath(parsePath(path), color, mat) image.fillPath(parsePath(path), color, mat)
proc strokePath*( proc strokePath*(
@ -707,7 +706,7 @@ proc strokePath*(
# strokeLocation: StrokeLocation, # strokeLocation: StrokeLocation,
# strokeCap: StorkeCap, # strokeCap: StorkeCap,
# strokeJoin: StorkeJoin # strokeJoin: StorkeJoin
) = ): Image =
let polys = commandsToPolygons(path.commands) let polys = commandsToPolygons(path.commands)
let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2)
let polys2 = strokePolygons(polys, strokeL, strokeR) let polys2 = strokePolygons(polys, strokeL, strokeR)
@ -718,7 +717,7 @@ proc strokePath*(
path: string, path: string,
color: ColorRGBA, color: ColorRGBA,
strokeWidth: float32 strokeWidth: float32
) = ): Image =
image.strokePath(parsePath(path), color, strokeWidth) image.strokePath(parsePath(path), color, strokeWidth)
proc strokePath*( proc strokePath*(
@ -727,7 +726,7 @@ proc strokePath*(
color: ColorRGBA, color: ColorRGBA,
strokeWidth: float32, strokeWidth: float32,
pos: Vec2 pos: Vec2
) = ): Image =
var polys = commandsToPolygons(parsePath(path).commands) var polys = commandsToPolygons(parsePath(path).commands)
let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2)
var polys2 = strokePolygons(polys, strokeL, strokeR) var polys2 = strokePolygons(polys, strokeL, strokeR)
@ -742,7 +741,7 @@ proc strokePath*(
color: ColorRGBA, color: ColorRGBA,
strokeWidth: float32, strokeWidth: float32,
mat: Mat3 mat: Mat3
) = ): Image =
var polys = commandsToPolygons(parsePath(path).commands) var polys = commandsToPolygons(parsePath(path).commands)
let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2)
var polys2 = strokePolygons(polys, strokeL, strokeR) var polys2 = strokePolygons(polys, strokeL, strokeR)
@ -859,7 +858,6 @@ proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) =
] ]
)) ))
proc ellipse*(path: Path) = proc ellipse*(path: Path) =
## Adds an elliptical arc to the path which is centered at (x, y) position with the radii radiusX and radiusY starting at startAngle and ending at endAngle going in the given direction by anticlockwise (defaulting to clockwise). ## Adds an elliptical arc to the path which is centered at (x, y) position with the radii radiusX and radiusY starting at startAngle and ending at endAngle going in the given direction by anticlockwise (defaulting to clockwise).
raise newException(ValueError, "not implemented") raise newException(ValueError, "not implemented")