From 3764063605538e05d39e15d50c8c9f2b75c7ad68 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 23 Nov 2020 08:56:11 -0800 Subject: [PATCH] Add bmIntersectMask and bmExcludeMask. --- src/pixie/blends.nim | 14 ++++++++ src/pixie/images.nim | 79 +++++++++++++++++++++++++++----------------- src/pixie/paths.nim | 36 ++++++++++---------- 3 files changed, 80 insertions(+), 49 deletions(-) diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index a380d68..ec88596 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -24,6 +24,8 @@ type BlendMode* = enum bmMask ## Special blend mode that is used for masking bmOverwrite ## Special that does not blend but copies the pixels from target. bmSubtractMask ## Inverse mask + bmIntersectMask + bmExcludeMask proc parseBlendMode*(s: string): BlendMode = case s: @@ -97,6 +99,18 @@ proc mix*(blendMode: BlendMode, target, blend: Color): Color = result.b = target.b result.a = target.a * (1 - blend.a) 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: result = blend return diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 2553330..4334d16 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -82,18 +82,31 @@ proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} = if image.inside(x, y): 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. - for i in 0 ..< image.data.len: - image.data[i] = rgba + result = newImageNoInit(width, height) + 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. - for rgba in image.data.mitems: - rgba.r = 255 - rgba.r - rgba.g = 255 - rgba.g - rgba.b = 255 - rgba.b - rgba.a = 255 - rgba.a + 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 subImage*(image: Image, x, y, w, h: int): Image = ## Gets a sub image of the main image. @@ -183,9 +196,19 @@ proc hasEffect*(blendMode: BlendMode, rgba: ColorRGBA): bool = rgba.a != 255 of bmOverwrite: true + of bmIntersectMask: + true else: 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 = ## Draws one image onto another using integer x,y offset with COPY. 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: var rgba = a.getRgbaUnsafe(x, y) let srcPos = matInv * vec2(x.float32, y.float32) - if b.inside(srcPos.x.floor.int, srcPos.y.floor.int): rgba = b.getRgbaUnsafe(srcPos.x.floor.int, srcPos.y.floor.int) result.setRgbaUnsafe(x, y, rgba) @@ -206,14 +228,19 @@ proc drawBlend*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode): Image = var matInv = mat.inverse() for y in 0 ..< a.height: 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): + var rgba = a.getRgbaUnsafe(x, y) let rgba2 = b.getRgbaUnsafe(srcPos.x.floor.int, srcPos.y.floor.int) if blendMode.hasEffect(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 = ## 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() for y in 0 ..< a.height: 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): + var rgba = a.getRgbaUnsafe(x, y) let rgba2 = b.getRgbaSmooth(srcPos.x, srcPos.y) if blendMode.hasEffect(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 = ## Draws one image onto another using matrix with color blending. @@ -359,22 +391,9 @@ proc shadow*( shadow = shadow.spread(spread) if blur > 0: shadow = shadow.blur(blur) - result = newImage(mask.width, mask.height) - result.fill(color.rgba) + result = newImageFill(mask.width, mask.height, color.rgba) 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 = ## Multiplies alpha of the image by opacity. result = newImageNoInit(image.width, image.height) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 891a6b9..e12a174 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -587,11 +587,10 @@ proc fillPolygons*( polys: seq[seq[Vec2]], color: ColorRGBA, quality = 4, - ) = + ): Image = const ep = 0.0001 * PI - if polys.len == 0: - image.fill(rgba(0, 0, 0, 0)) + result = newImage(image.width, image.height) proc scanLineHits( polys: seq[seq[Vec2]], @@ -616,9 +615,9 @@ proc fillPolygons*( var hits: seq[(float32, bool)] - var alphas = newSeq[float32](image.width) - for y in 0 ..< image.height: - for x in 0 ..< image.width: + var alphas = newSeq[float32](result.width) + for y in 0 ..< result.height: + for x in 0 ..< result.width: alphas[x] = 0 for m in 0 ..< quality: polys.scanLineHits(hits, y, float32(m)/float32(quality)) @@ -627,7 +626,7 @@ proc fillPolygons*( var penFill = 0.0 curHit = 0 - for x in 0 ..< image.width: + for x in 0 ..< result.width: var penEdge = penFill while true: if curHit >= hits.len: @@ -644,11 +643,11 @@ proc fillPolygons*( penEdge -= 1.0 - cover inc curHit 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 colorWithAlpha = color colorWithAlpha.a = uint8(clamp(a, 0, 1) * 255.0) - image[x, y] = colorWithAlpha + result[x, y] = colorWithAlpha {.pop.} @@ -656,7 +655,7 @@ proc fillPath*( image: Image, path: Path, color: ColorRGBA - ) = + ): Image = let polys = commandsToPolygons(path.commands) image.fillPolygons(polys, color) @@ -664,7 +663,7 @@ proc fillPath*( image: Image, path: string, color: ColorRGBA - ) = + ): Image = image.fillPath(parsePath(path), color) proc fillPath*( @@ -672,7 +671,7 @@ proc fillPath*( path: string, color: ColorRGBA, pos: Vec2 - ) = + ): Image = var polys = commandsToPolygons(parsePath(path).commands) for poly in polys.mitems: for i, p in poly.mpairs: @@ -684,7 +683,7 @@ proc fillPath*( path: Path, color: ColorRGBA, mat: Mat3 - ) = + ): Image = var polys = commandsToPolygons(path.commands) for poly in polys.mitems: for i, p in poly.mpairs: @@ -696,7 +695,7 @@ proc fillPath*( path: string, color: ColorRGBA, mat: Mat3 - ) = + ): Image = image.fillPath(parsePath(path), color, mat) proc strokePath*( @@ -707,7 +706,7 @@ proc strokePath*( # strokeLocation: StrokeLocation, # strokeCap: StorkeCap, # strokeJoin: StorkeJoin - ) = + ): Image = let polys = commandsToPolygons(path.commands) let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) let polys2 = strokePolygons(polys, strokeL, strokeR) @@ -718,7 +717,7 @@ proc strokePath*( path: string, color: ColorRGBA, strokeWidth: float32 - ) = + ): Image = image.strokePath(parsePath(path), color, strokeWidth) proc strokePath*( @@ -727,7 +726,7 @@ proc strokePath*( color: ColorRGBA, strokeWidth: float32, pos: Vec2 - ) = + ): Image = var polys = commandsToPolygons(parsePath(path).commands) let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) var polys2 = strokePolygons(polys, strokeL, strokeR) @@ -742,7 +741,7 @@ proc strokePath*( color: ColorRGBA, strokeWidth: float32, mat: Mat3 - ) = + ): Image = var polys = commandsToPolygons(parsePath(path).commands) let (strokeL, strokeR) = (strokeWidth/2, strokeWidth/2) var polys2 = strokePolygons(polys, strokeL, strokeR) @@ -859,7 +858,6 @@ proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) = ] )) - 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). raise newException(ValueError, "not implemented")