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
|
||||
Image* = ref object
|
||||
|
@ -70,3 +70,207 @@ proc fill*(image: Image, rgba: ColorRgba) =
|
|||
## Fills the image with a solid color.
|
||||
for i in 0 ..< image.data.len:
|
||||
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.
|
||||
for i in 0 ..< mask.data.len:
|
||||
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