Move blends. Better Draw.
This commit is contained in:
parent
67ac0d4f38
commit
7eea5ff2d0
10 changed files with 117 additions and 48 deletions
|
@ -1,7 +1,7 @@
|
||||||
## Public interface to you library.
|
## Public interface to you library.
|
||||||
|
|
||||||
import pixie/images, pixie/masks, pixie/paths, pixie/common, pixie/blends,
|
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
|
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) =
|
proc writeFile*(image: Image, filePath: string, fileFormat: FileFormat) =
|
||||||
## Writes an image to a file.
|
## Writes an image to a file.
|
||||||
writeFile(filePath, image.encodeImage(fileFormat))
|
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))
|
||||||
|
|
|
@ -100,7 +100,7 @@ proc mix*(blendMode: BlendMode, target, blend: Color): Color =
|
||||||
result.a = target.a * (1 - blend.a)
|
result.a = target.a * (1 - blend.a)
|
||||||
return
|
return
|
||||||
elif blendMode == Copy:
|
elif blendMode == Copy:
|
||||||
result = target
|
result = blend
|
||||||
return
|
return
|
||||||
|
|
||||||
proc multiply(Cb, Cs: float32): float32 =
|
proc multiply(Cb, Cs: float32): float32 =
|
||||||
|
|
|
@ -24,7 +24,13 @@ proc `$`*(image: Image): string =
|
||||||
|
|
||||||
proc inside*(image: Image, x, y: int): bool {.inline.} =
|
proc inside*(image: Image, x, y: int): bool {.inline.} =
|
||||||
## Returns true if (x, y) is inside the image.
|
## 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.} =
|
proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBA {.inline.} =
|
||||||
## Gets a color from (x, y) coordinates.
|
## Gets a color from (x, y) coordinates.
|
||||||
|
@ -102,45 +108,24 @@ proc magnifyBy2*(image: Image, scale2x: int): Image =
|
||||||
proc magnifyBy2*(image: Image): Image =
|
proc magnifyBy2*(image: Image): Image =
|
||||||
image.magnifyBy2(2)
|
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.} =
|
func lerp(a, b: Color, v: float): Color {.inline.} =
|
||||||
result.r = lerp(a.r, b.r, v)
|
result.r = lerp(a.r, b.r, v)
|
||||||
result.g = lerp(a.g, b.g, v)
|
result.g = lerp(a.g, b.g, v)
|
||||||
result.b = lerp(a.b, b.b, v)
|
result.b = lerp(a.b, b.b, v)
|
||||||
result.a = lerp(a.a, b.a, v)
|
result.a = lerp(a.a, b.a, v)
|
||||||
|
|
||||||
proc getRgbaSmooth*(image: Image, x, y: float64): ColorRGBA
|
proc getRgbaSmooth*(image: Image, x, y: float64): ColorRGBA {.inline.} =
|
||||||
{.inline, raises: [].} =
|
|
||||||
## Gets a pixel as (x, y) floats.
|
## Gets a pixel as (x, y) floats.
|
||||||
let
|
let
|
||||||
minX = floor(x).int
|
minX = floor(x).int
|
||||||
difX = (x - minX.float32)
|
difX = (x - minX.float32)
|
||||||
|
|
||||||
minY = floor(y).int
|
minY = floor(y).int
|
||||||
difY = (y - minY.float32)
|
difY = (y - minY.float32)
|
||||||
|
|
||||||
vX0Y0 = image.getRgbaUnsafe(
|
vX0Y0 = image[minX, minY].color()
|
||||||
moduloMod(minX, image.width),
|
vX1Y0 = image[minX + 1, minY].color()
|
||||||
moduloMod(minY, image.height),
|
vX0Y1 = image[minX, minY + 1].color()
|
||||||
).color()
|
vX1Y1 = image[minX + 1, minY + 1].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()
|
|
||||||
|
|
||||||
bottomMix = lerp(vX0Y0, vX1Y0, difX)
|
bottomMix = lerp(vX0Y0, vX1Y0, difX)
|
||||||
topMix = lerp(vX0Y1, vX1Y1, difX)
|
topMix = lerp(vX0Y1, vX1Y1, difX)
|
||||||
|
@ -165,25 +150,67 @@ func translate*(v: Vec2): Mat3 =
|
||||||
result[2, 1] = v.y
|
result[2, 1] = v.y
|
||||||
result[2, 2] = 1
|
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.
|
## Draws one image onto another using matrix with color blending.
|
||||||
|
result = newImage(a.width, a.height)
|
||||||
# Todo: if matrix is simple integer translation -> fast pass
|
for y in 0 ..< a.width:
|
||||||
# Todo: if matrix is a simple flip -> fast path
|
for x in 0 ..< a.height:
|
||||||
# Todo: if blend mode is copy -> fast path
|
var rgba = a.getRgbaUnsafe(x, y)
|
||||||
|
|
||||||
result = newImage(destImage.width, destImage.height)
|
|
||||||
for y in 0 ..< destImage.width:
|
|
||||||
for x in 0 ..< destImage.height:
|
|
||||||
let srcPos = mat * vec2(x.float32, y.float32)
|
let srcPos = mat * vec2(x.float32, y.float32)
|
||||||
let destRgba = destImage.getRgbaUnsafe(x, y)
|
if b.inside1px(srcPos.x, srcPos.y):
|
||||||
var rgba = destRgba
|
let rgba2 = b.getRgbaSmooth(srcPos.x, srcPos.y)
|
||||||
var srcRgba = rgba(0, 0, 0, 0)
|
if blendMode.hasEffect(rgba2):
|
||||||
if srcImage.inside(srcPos.x.floor.int, srcPos.y.floor.int):
|
rgba = blendMode.mix(rgba, rgba2)
|
||||||
srcRgba = srcImage.getRgbaSmooth(srcPos.x - 0.5, srcPos.y - 0.5)
|
|
||||||
if blendMode.hasEffect(srcRgba):
|
|
||||||
rgba = blendMode.mix(destRgba, srcRgba)
|
|
||||||
result.setRgbaUnsafe(x, y, rgba)
|
result.setRgbaUnsafe(x, y, rgba)
|
||||||
|
|
||||||
proc draw*(destImage: Image, srcImage: Image, pos = vec2(0, 0), blendMode = Normal): Image =
|
proc draw*(a: Image, b: Image, mat: Mat3, blendMode = Normal): Image =
|
||||||
destImage.draw(srcImage, translate(-pos), blendMode)
|
## 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)
|
||||||
|
|
BIN
tests/images/draw.master.png
Normal file
BIN
tests/images/draw.master.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 328 B |
BIN
tests/images/draw.png
Normal file
BIN
tests/images/draw.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 328 B |
BIN
tests/images/drawCopy.master.png
Normal file
BIN
tests/images/drawCopy.master.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 328 B |
BIN
tests/images/drawCopy.png
Normal file
BIN
tests/images/drawCopy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 328 B |
BIN
tests/images/drawSmooth.master.png
Normal file
BIN
tests/images/drawSmooth.master.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 356 B |
BIN
tests/images/drawSmooth.png
Normal file
BIN
tests/images/drawSmooth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 356 B |
|
@ -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:
|
block:
|
||||||
var image = newImage(10, 10)
|
var image = newImage(10, 10)
|
||||||
|
@ -9,3 +19,27 @@ block:
|
||||||
var image = newImage(10, 10)
|
var image = newImage(10, 10)
|
||||||
image.fill(rgba(255, 0, 0, 255))
|
image.fill(rgba(255, 0, 0, 255))
|
||||||
doAssert image[0, 0] == 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")
|
||||||
|
|
Loading…
Reference in a new issue