Add a bunch methods to images.
This commit is contained in:
parent
8a5a024cb8
commit
d3e908ba2d
3 changed files with 221 additions and 1 deletions
|
@ -1,4 +1,4 @@
|
||||||
import strformat, chroma, vmath
|
import strformat, chroma, chroma/blends, vmath
|
||||||
|
|
||||||
type
|
type
|
||||||
Image* = ref object
|
Image* = ref object
|
||||||
|
@ -70,3 +70,207 @@ proc fill*(image: Image, rgba: ColorRgba) =
|
||||||
## Fills the image with a solid color.
|
## Fills the image with a solid color.
|
||||||
for i in 0 ..< image.data.len:
|
for i in 0 ..< image.data.len:
|
||||||
image.data[i] = rgba
|
image.data[i] = rgba
|
||||||
|
|
||||||
|
proc subImage*(image: Image, x, y, w, h: int): Image =
|
||||||
|
## Gets a sub image of the main image.
|
||||||
|
doAssert x >= 0 and y >= 0
|
||||||
|
doAssert x + w <= image.width and y + h <= image.height
|
||||||
|
result = newImage(w, h)
|
||||||
|
for y2 in 0 ..< h:
|
||||||
|
for x2 in 0 ..< w:
|
||||||
|
result.setRgbaUnsafe(x2, y2, image.getRgbaUnsafe(x2 + x, y2 + y))
|
||||||
|
|
||||||
|
proc minifyBy2*(image: Image): Image =
|
||||||
|
## Scales the image down by an integer scale.
|
||||||
|
result = newImage(image.width div 2, image.height div 2)
|
||||||
|
for y in 0 ..< result.height:
|
||||||
|
for x in 0 ..< result.width:
|
||||||
|
var color =
|
||||||
|
image.getRgbaUnsafe(x * 2 + 0, y * 2 + 0).color / 4.0 +
|
||||||
|
image.getRgbaUnsafe(x * 2 + 1, y * 2 + 0).color / 4.0 +
|
||||||
|
image.getRgbaUnsafe(x * 2 + 1, y * 2 + 1).color / 4.0 +
|
||||||
|
image.getRgbaUnsafe(x * 2 + 0, y * 2 + 1).color / 4.0
|
||||||
|
result.setRgbaUnsafe(x, y, color.rgba)
|
||||||
|
|
||||||
|
proc blitUnsafe*(destImage: Image, srcImage: Image, src, dest: Rect) =
|
||||||
|
## Blits rectangle from one image to the other image.
|
||||||
|
## * No bounds checking *
|
||||||
|
## Make sure that src and dest rect are in bounds.
|
||||||
|
## Make sure that channels for images are the same.
|
||||||
|
## Failure in the assumptions will case unsafe memory writes.
|
||||||
|
## Note: Does not do alpha or color mixing.
|
||||||
|
for y in 0 ..< int(dest.h):
|
||||||
|
let
|
||||||
|
srcIdx = int(src.x) + (int(src.y) + y) * srcImage.width
|
||||||
|
destIdx = int(dest.x) + (int(dest.y) + y) * destImage.width
|
||||||
|
copyMem(
|
||||||
|
destImage.data[destIdx].addr,
|
||||||
|
srcImage.data[srcIdx].addr,
|
||||||
|
int(dest.w) * 4
|
||||||
|
)
|
||||||
|
|
||||||
|
proc blit*(destImage: Image, srcImage: Image, src, dest: Rect) =
|
||||||
|
## Blits rectangle from one image to the other image.
|
||||||
|
## Note: Does not do alpha or color mixing.
|
||||||
|
doAssert src.w == dest.w and src.h == dest.h
|
||||||
|
doAssert src.x >= 0 and src.x + src.w <= srcImage.width.float32
|
||||||
|
doAssert src.y >= 0 and src.y + src.h <= srcImage.height.float32
|
||||||
|
|
||||||
|
# See if the image hits the bounds and needs to be adjusted.
|
||||||
|
var
|
||||||
|
src = src
|
||||||
|
dest = dest
|
||||||
|
if dest.x < 0:
|
||||||
|
dest.w += dest.x
|
||||||
|
src.x -= dest.x
|
||||||
|
src.w += dest.x
|
||||||
|
dest.x = 0
|
||||||
|
if dest.x + dest.w > destImage.width.float32:
|
||||||
|
let diff = destImage.width.float32 - (dest.x + dest.w)
|
||||||
|
dest.w += diff
|
||||||
|
src.w += diff
|
||||||
|
if dest.y < 0:
|
||||||
|
dest.h += dest.y
|
||||||
|
src.y -= dest.y
|
||||||
|
src.h += dest.y
|
||||||
|
dest.y = 0
|
||||||
|
if dest.y + dest.h > destImage.height.float32:
|
||||||
|
let diff = destImage.height.float32 - (dest.y + dest.h)
|
||||||
|
dest.h += diff
|
||||||
|
src.h += diff
|
||||||
|
|
||||||
|
# See if image is entirely outside the bounds:
|
||||||
|
if dest.x + dest.w < 0 or dest.x > destImage.width.float32:
|
||||||
|
return
|
||||||
|
if dest.y + dest.h < 0 or dest.y > destImage.height.float32:
|
||||||
|
return
|
||||||
|
|
||||||
|
blitUnsafe(destImage, srcImage, src, dest)
|
||||||
|
|
||||||
|
proc blit*(destImage: Image, srcImage: Image, pos: Vec2) =
|
||||||
|
## Blits rectangle from one image to the other image.
|
||||||
|
## Note: Does not do alpha or color mixing.
|
||||||
|
destImage.blit(
|
||||||
|
srcImage,
|
||||||
|
rect(0.0, 0.0, srcImage.width.float32, srcImage.height.float32),
|
||||||
|
rect(pos.x, pos.y, srcImage.width.float32, srcImage.height.float32)
|
||||||
|
)
|
||||||
|
|
||||||
|
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: [].} =
|
||||||
|
## 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()
|
||||||
|
|
||||||
|
bottomMix = lerp(vX0Y0, vX1Y0, difX)
|
||||||
|
topMix = lerp(vX0Y1, vX1Y1, difX)
|
||||||
|
finalMix = lerp(bottomMix, topMix, difY)
|
||||||
|
|
||||||
|
return finalMix.rgba()
|
||||||
|
|
||||||
|
proc draw*(destImage: Image, srcImage: Image, mat: Mat4, blendMode = Normal) =
|
||||||
|
## Draws one image onto another using matrix with color blending.
|
||||||
|
var srcImage = srcImage
|
||||||
|
let
|
||||||
|
matInv = mat.inverse()
|
||||||
|
bounds = [
|
||||||
|
mat * vec3(-1, -1, 0),
|
||||||
|
mat * vec3(-1, float32 srcImage.height + 1, 0),
|
||||||
|
mat * vec3(float32 srcImage.width + 1, -1, 0),
|
||||||
|
mat * vec3(float32 srcImage.width + 1, float32 srcImage.height + 1, 0)
|
||||||
|
]
|
||||||
|
var
|
||||||
|
boundsX: array[4, float32]
|
||||||
|
boundsY: array[4, float32]
|
||||||
|
for i, v in bounds:
|
||||||
|
boundsX[i] = v.x
|
||||||
|
boundsY[i] = v.y
|
||||||
|
let
|
||||||
|
xStart = max(int min(boundsX), 0)
|
||||||
|
yStart = max(int min(boundsY), 0)
|
||||||
|
xEnd = min(int max(boundsX), destImage.width)
|
||||||
|
yEnd = min(int max(boundsY), destImage.height)
|
||||||
|
|
||||||
|
var
|
||||||
|
# compute movement vectors
|
||||||
|
start = matInv * vec3(0.5, 0.5, 0)
|
||||||
|
stepX = matInv * vec3(1.5, 0.5, 0) - start
|
||||||
|
stepY = matInv * vec3(0.5, 1.5, 0) - start
|
||||||
|
|
||||||
|
minFilterBy2 = max(stepX.length, stepY.length)
|
||||||
|
|
||||||
|
while minFilterBy2 > 2.0:
|
||||||
|
srcImage = srcImage.minifyBy2()
|
||||||
|
start /= 2
|
||||||
|
stepX /= 2
|
||||||
|
stepY /= 2
|
||||||
|
minFilterBy2 /= 2
|
||||||
|
|
||||||
|
# fill the bounding rectangle
|
||||||
|
for y in yStart ..< yEnd:
|
||||||
|
for x in xStart ..< xEnd:
|
||||||
|
let srcV = start + stepX * float32(x) + stepY * float32(y)
|
||||||
|
if srcImage.inside(int srcV.x, int srcV.y):
|
||||||
|
let
|
||||||
|
srcRgba = srcImage.getRgbaSmooth(srcV.x - 0.5, srcV.y - 0.5)
|
||||||
|
let
|
||||||
|
destRgba = destImage.getRgbaUnsafe(x, y)
|
||||||
|
color = blendMode.mix(destRgba.color, srcRgba.color)
|
||||||
|
destImage.setRgbaUnsafe(x, y, color.rgba)
|
||||||
|
|
||||||
|
proc draw*(
|
||||||
|
destImage, srcImage: Image, pos = vec2(0, 0), blendMode = Normal,
|
||||||
|
) =
|
||||||
|
## Fast draw of dest + fill using offset with color blending.
|
||||||
|
for y in 0 ..< srcImage.height:
|
||||||
|
for x in 0 ..< srcImage.width:
|
||||||
|
let
|
||||||
|
srcRgba = srcImage.getRgbaUnsafe(x, y)
|
||||||
|
if blendMode == Mask or srcRgba.a > 0 :
|
||||||
|
let
|
||||||
|
destRgba = destImage.getRgbaUnsafe(x + pos.x.int, y + pos.y.int)
|
||||||
|
rgba = blendMode.mix(destRgba, srcRgba)
|
||||||
|
# TODO: Make unsafe
|
||||||
|
destImage[x + pos.x.int, y + pos.y.int] = rgba
|
||||||
|
|
||||||
|
proc invert*(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
|
||||||
|
|
|
@ -53,3 +53,8 @@ proc fill*(mask: Mask, alpha: uint8) =
|
||||||
## Fills the mask with a solid color.
|
## Fills the mask with a solid color.
|
||||||
for i in 0 ..< mask.data.len:
|
for i in 0 ..< mask.data.len:
|
||||||
mask.data[i] = alpha
|
mask.data[i] = alpha
|
||||||
|
|
||||||
|
proc invert*(mask: Mask) =
|
||||||
|
## Inverts all of the colors and alpha.
|
||||||
|
for alpha in mask.data.mitems:
|
||||||
|
alpha = 255 - alpha
|
||||||
|
|
11
tests/testimages.nim
Normal file
11
tests/testimages.nim
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import pixie, chroma
|
||||||
|
|
||||||
|
block:
|
||||||
|
var image = newImage(10, 10)
|
||||||
|
image[0, 0] = rgba(255, 255, 255, 255)
|
||||||
|
doAssert image[0, 0] == rgba(255, 255, 255, 255)
|
||||||
|
|
||||||
|
block:
|
||||||
|
var image = newImage(10, 10)
|
||||||
|
image.fill(rgba(255, 0, 0, 255))
|
||||||
|
doAssert image[0, 0] == rgba(255, 0, 0, 255)
|
Loading…
Reference in a new issue