Move blends. Better Draw.

This commit is contained in:
treeform 2020-11-21 10:34:57 -08:00
parent 67ac0d4f38
commit 7eea5ff2d0
10 changed files with 117 additions and 48 deletions

View file

@ -1,7 +1,7 @@
## Public interface to you library.
import pixie/images, pixie/masks, pixie/paths, pixie/common, pixie/blends,
pixie/fileformats/bmp, pixie/fileformats/png, flatty/binny
pixie/fileformats/bmp, pixie/fileformats/png, flatty/binny, os
export images, masks, paths, PixieError, blends
@ -46,3 +46,11 @@ proc encodeImage*(image: Image, fileFormat: FileFormat): string =
proc writeFile*(image: Image, filePath: string, fileFormat: FileFormat) =
## Writes an image to a file.
writeFile(filePath, image.encodeImage(fileFormat))
proc writeFile*(image: Image, filePath: string) =
## Writes an image to a file.
let fileFormat = case splitFile(filePath).ext:
of "png": ffPng
of "bmp": ffBmp
else: ffPng
writeFile(filePath, image.encodeImage(fileFormat))

View file

@ -100,7 +100,7 @@ proc mix*(blendMode: BlendMode, target, blend: Color): Color =
result.a = target.a * (1 - blend.a)
return
elif blendMode == Copy:
result = target
result = blend
return
proc multiply(Cb, Cs: float32): float32 =

View file

@ -24,7 +24,13 @@ proc `$`*(image: Image): string =
proc inside*(image: Image, x, y: int): bool {.inline.} =
## Returns true if (x, y) is inside the image.
x >= 0 and x < image.width and y >= 0 and y < image.height
x >= 0 and x < image.width and
y >= 0 and y < image.height
proc inside1px*(image: Image, x, y: float): bool {.inline.} =
## Returns true if (x, y) is inside the image.
x >= -1 and x < (image.width.float32 + 1) and
y >= -1 and y < (image.height.float32 + 1)
proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBA {.inline.} =
## Gets a color from (x, y) coordinates.
@ -102,45 +108,24 @@ proc magnifyBy2*(image: Image, scale2x: int): Image =
proc magnifyBy2*(image: Image): Image =
image.magnifyBy2(2)
func moduloMod(n, M: int): int {.inline.} =
## Computes "mathematical" modulo vs c modulo.
((n mod M) + M) mod M
func lerp(a, b: Color, v: float): Color {.inline.} =
result.r = lerp(a.r, b.r, v)
result.g = lerp(a.g, b.g, v)
result.b = lerp(a.b, b.b, v)
result.a = lerp(a.a, b.a, v)
proc getRgbaSmooth*(image: Image, x, y: float64): ColorRGBA
{.inline, raises: [].} =
proc getRgbaSmooth*(image: Image, x, y: float64): ColorRGBA {.inline.} =
## Gets a pixel as (x, y) floats.
let
minX = floor(x).int
difX = (x - minX.float32)
minY = floor(y).int
difY = (y - minY.float32)
vX0Y0 = image.getRgbaUnsafe(
moduloMod(minX, image.width),
moduloMod(minY, image.height),
).color()
vX1Y0 = image.getRgbaUnsafe(
moduloMod(minX + 1, image.width),
moduloMod(minY, image.height),
).color()
vX0Y1 = image.getRgbaUnsafe(
moduloMod(minX, image.width),
moduloMod(minY + 1, image.height),
).color()
vX1Y1 = image.getRgbaUnsafe(
moduloMod(minX + 1, image.width),
moduloMod(minY + 1, image.height),
).color()
vX0Y0 = image[minX, minY].color()
vX1Y0 = image[minX + 1, minY].color()
vX0Y1 = image[minX, minY + 1].color()
vX1Y1 = image[minX + 1, minY + 1].color()
bottomMix = lerp(vX0Y0, vX1Y0, difX)
topMix = lerp(vX0Y1, vX1Y1, difX)
@ -165,25 +150,67 @@ func translate*(v: Vec2): Mat3 =
result[2, 1] = v.y
result[2, 2] = 1
proc draw*(destImage: Image, srcImage: Image, mat: Mat3, blendMode = Normal): Image =
proc fraction(v: float32): float32 =
result = abs(v)
result = result - floor(result)
proc drawFast*(a: Image, b: Image, x, y: int): Image =
## Draws one image onto another using integer x,y offset with COPY.
result = newImage(a.width, a.height)
for yd in 0 ..< a.width:
for xd in 0 ..< a.height:
var rgba = a.getRgbaUnsafe(xd, yd)
if b.inside(xd + x, yd + y):
rgba = b.getRgbaUnsafe(xd + x, yd + y)
result.setRgbaUnsafe(xd, yd, rgba)
proc drawFast*(a: Image, b: Image, x, y: int, blendMode: BlendMode): Image =
## Draws one image onto another using integer x,y offset with color blending.
result = newImage(a.width, a.height)
for yd in 0 ..< a.width:
for xd in 0 ..< a.height:
var rgba = a.getRgbaUnsafe(xd, yd)
if b.inside(xd + x, yd + y):
var rgba2 = b.getRgbaUnsafe(xd + x, yd + y)
if blendMode.hasEffect(rgba2):
rgba = blendMode.mix(rgba, rgba2)
result.setRgbaUnsafe(xd, yd, rgba)
proc drawFast*(a: Image, b: Image, mat: Mat3, blendMode: BlendMode): Image =
## Draws one image onto another using matrix with color blending.
# Todo: if matrix is simple integer translation -> fast pass
# Todo: if matrix is a simple flip -> fast path
# Todo: if blend mode is copy -> fast path
result = newImage(destImage.width, destImage.height)
for y in 0 ..< destImage.width:
for x in 0 ..< destImage.height:
result = newImage(a.width, a.height)
for y in 0 ..< a.width:
for x in 0 ..< a.height:
var rgba = a.getRgbaUnsafe(x, y)
let srcPos = mat * vec2(x.float32, y.float32)
let destRgba = destImage.getRgbaUnsafe(x, y)
var rgba = destRgba
var srcRgba = rgba(0, 0, 0, 0)
if srcImage.inside(srcPos.x.floor.int, srcPos.y.floor.int):
srcRgba = srcImage.getRgbaSmooth(srcPos.x - 0.5, srcPos.y - 0.5)
if blendMode.hasEffect(srcRgba):
rgba = blendMode.mix(destRgba, srcRgba)
if b.inside1px(srcPos.x, srcPos.y):
let rgba2 = b.getRgbaSmooth(srcPos.x, srcPos.y)
if blendMode.hasEffect(rgba2):
rgba = blendMode.mix(rgba, rgba2)
result.setRgbaUnsafe(x, y, rgba)
proc draw*(destImage: Image, srcImage: Image, pos = vec2(0, 0), blendMode = Normal): Image =
destImage.draw(srcImage, translate(-pos), blendMode)
proc draw*(a: Image, b: Image, mat: Mat3, blendMode = Normal): Image =
## Draws one image onto another using matrix with color blending.
if mat[0, 0] == 1 and mat[0, 1] == 0 and
mat[1, 0] == 0 and mat[1, 1] == 1 and
mat[2, 0].fraction == 0.0 and mat[2, 1].fraction == 0.0:
# Matrix is simple integer translation fast path:
if blendMode == Copy:
echo "use 1"
return drawFast(
a, b, mat[2, 0].int, mat[2, 1].int
)
else:
echo "use 2"
return drawFast(
a, b, mat[2, 0].int, mat[2, 1].int, blendMode
)
# Todo: if matrix is a simple flip -> fast path
echo "use 3"
return drawFast(a, b, mat, blendMode)
proc draw*(a: Image, b: Image, pos = vec2(0, 0), blendMode = Normal): Image =
a.draw(b, translate(-pos), blendMode)

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

BIN
tests/images/draw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

BIN
tests/images/drawCopy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

BIN
tests/images/drawSmooth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

View file

@ -1,4 +1,14 @@
import pixie, chroma
import pixie, chroma, vmath, os
proc writeAndCheck(image: Image, fileName: string) =
image.writeFile(fileName)
let masterFileName = fileName.changeFileExt(".master.png")
if not existsFile(masterFileName):
quit("Master file: " & masterFileName & " not found!")
var master = readImage(fileName)
assert image.width == master.width
assert image.height == master.height
assert image.data == master.data
block:
var image = newImage(10, 10)
@ -9,3 +19,27 @@ block:
var image = newImage(10, 10)
image.fill(rgba(255, 0, 0, 255))
doAssert image[0, 0] == rgba(255, 0, 0, 255)
block:
var a = newImage(100, 100)
a.fill(rgba(255, 0, 0, 255))
var b = newImage(100, 100)
b.fill(rgba(0, 255, 0, 255))
var c = a.draw(b, pos=vec2(25, 25))
c.writeAndCheck("tests/images/draw.png")
block:
var a = newImage(100, 100)
a.fill(rgba(255, 0, 0, 255))
var b = newImage(100, 100)
b.fill(rgba(0, 255, 0, 255))
var c = a.draw(b, pos=vec2(25, 25), COPY)
c.writeAndCheck("tests/images/drawCopy.png")
block:
var a = newImage(100, 100)
a.fill(rgba(255, 0, 0, 255))
var b = newImage(100, 100)
b.fill(rgba(0, 255, 0, 255))
var c = a.draw(b, pos=vec2(25.15, 25.15))
c.writeAndCheck("tests/images/drawSmooth.png")