From 7eea5ff2d0ebc563b0f278fb478dc59a24fcc05f Mon Sep 17 00:00:00 2001 From: treeform Date: Sat, 21 Nov 2020 10:34:57 -0800 Subject: [PATCH] Move blends. Better Draw. --- src/pixie.nim | 10 ++- src/pixie/blends.nim | 2 +- src/pixie/images.nim | 117 ++++++++++++++++++----------- tests/images/draw.master.png | Bin 0 -> 328 bytes tests/images/draw.png | Bin 0 -> 328 bytes tests/images/drawCopy.master.png | Bin 0 -> 328 bytes tests/images/drawCopy.png | Bin 0 -> 328 bytes tests/images/drawSmooth.master.png | Bin 0 -> 356 bytes tests/images/drawSmooth.png | Bin 0 -> 356 bytes tests/test_images.nim | 36 ++++++++- 10 files changed, 117 insertions(+), 48 deletions(-) create mode 100644 tests/images/draw.master.png create mode 100644 tests/images/draw.png create mode 100644 tests/images/drawCopy.master.png create mode 100644 tests/images/drawCopy.png create mode 100644 tests/images/drawSmooth.master.png create mode 100644 tests/images/drawSmooth.png diff --git a/src/pixie.nim b/src/pixie.nim index c0ef31b..675a568 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -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)) diff --git a/src/pixie/blends.nim b/src/pixie/blends.nim index d5481e4..1b6447a 100644 --- a/src/pixie/blends.nim +++ b/src/pixie/blends.nim @@ -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 = diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 6efad1c..e82cbdc 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -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) diff --git a/tests/images/draw.master.png b/tests/images/draw.master.png new file mode 100644 index 0000000000000000000000000000000000000000..ef62fc50f96ab6b5a60e3c0a77a6f30d92b90a53 GIT binary patch literal 328 zcmeAS@N?(olHy`uVBq!ia0vp^DImaSW+oe0$N5x0yl2_2T>m zOQU9Gt>9%bP+%mdKI;Vst0N|x}00000 literal 0 HcmV?d00001 diff --git a/tests/images/drawSmooth.png b/tests/images/drawSmooth.png new file mode 100644 index 0000000000000000000000000000000000000000..e695b85194cf77e19897e43cb8adf8481d4fc1a3 GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^DImaSW+oe0$N5x0yl2_2T>m zOQU9Gt>9%bP+%mdKI;Vst0N|x}00000 literal 0 HcmV?d00001 diff --git a/tests/test_images.nim b/tests/test_images.nim index 87c9e33..25b73aa 100644 --- a/tests/test_images.nim +++ b/tests/test_images.nim @@ -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")