Merge pull request #62 from guzba/master

fill, etc
This commit is contained in:
treeform 2021-01-22 20:28:58 -08:00 committed by GitHub
commit 4fc124b92f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -4,15 +4,15 @@ const h = 0.5.float32
type type
Image* = ref object Image* = ref object
## Main image object that holds the bitmap data in RGBA format. ## Image object that holds bitmap data in RGBA format.
width*, height*: int width*, height*: int
data*: seq[ColorRGBA] data*: seq[ColorRGBA]
proc draw*(a, b: Image, mat: Mat3, blendMode = bmNormal) when defined(release):
proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.} {.push checks: off.}
proc newImage*(width, height: int): Image = proc newImage*(width, height: int): Image =
## Creates a new image with appropriate dimensions. ## Creates a new image with the parameter dimensions.
result = Image() result = Image()
result.width = width result.width = width
result.height = height result.height = height
@ -23,23 +23,20 @@ proc wh*(image: Image): Vec2 {.inline.} =
vec2(image.width.float32, image.height.float32) vec2(image.width.float32, image.height.float32)
proc copy*(image: Image): Image = proc copy*(image: Image): Image =
## Copies an image creating a new image. ## Copies the image data into a new image.
result = newImage(image.width, image.height) result = newImage(image.width, image.height)
result.data = image.data result.data = image.data
proc `$`*(image: Image): string = proc `$`*(image: Image): string =
## Display the image size and channels. ## Prints the image size.
"<Image " & $image.width & "x" & $image.height & ">" "<Image " & $image.width & "x" & $image.height & ">"
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.} = proc dataIndex*(image: Image, x, y: int): int {.inline.} =
## Returns true if (x, y) is inside the image. image.width * y + x
const px = 1
x >= -px and x < (image.width.float32 + px) and
y >= -px and y < (image.height.float32 + px)
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.
@ -48,11 +45,6 @@ proc getRgbaUnsafe*(image: Image, x, y: int): ColorRGBA {.inline.} =
## Failure in the assumptions will case unsafe memory reads. ## Failure in the assumptions will case unsafe memory reads.
result = image.data[image.width * y + x] result = image.data[image.width * y + x]
proc getAddr*(image: Image, x, y: int): pointer {.inline.} =
## Gets a address of the color from (x, y) coordinates.
## Unsafe make sure x, y are in bounds.
image.data[image.width * y + x].addr
proc `[]`*(image: Image, x, y: int): ColorRGBA {.inline.} = proc `[]`*(image: Image, x, y: int): ColorRGBA {.inline.} =
## Gets a pixel at (x, y) or returns transparent black if outside of bounds. ## Gets a pixel at (x, y) or returns transparent black if outside of bounds.
if image.inside(x, y): if image.inside(x, y):
@ -63,39 +55,51 @@ proc setRgbaUnsafe*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} =
## * No bounds checking * ## * No bounds checking *
## Make sure that x, y are in bounds. ## Make sure that x, y are in bounds.
## Failure in the assumptions will case unsafe memory writes. ## Failure in the assumptions will case unsafe memory writes.
image.data[image.width * y + x] = rgba image.data[image.dataIndex(x, y)] = rgba
proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} = proc `[]=`*(image: Image, x, y: int, rgba: ColorRGBA) {.inline.} =
## Sets a pixel at (x, y) or does nothing if outside of bounds. ## Sets a pixel at (x, y) or does nothing if outside of bounds.
if image.inside(x, y): if image.inside(x, y):
image.setRgbaUnsafe(x, y, rgba) image.setRgbaUnsafe(x, y, rgba)
proc fill*(image: Image, rgba: ColorRgba) = proc fillUnsafe(data: var seq[ColorRGBA], rgba: ColorRGBA, start, len: int) =
## Fills the image with a solid color. ## Fills the image data with a solid color starting at index start and
## continuing for len indices.
# Use memset when every byte has the same value # Use memset when every byte has the same value
if rgba.r == rgba.g and rgba.r == rgba.b and rgba.r == rgba.a: if rgba.r == rgba.g and rgba.r == rgba.b and rgba.r == rgba.a:
nimSetMem(image.data[0].addr, rgba.r.cint, image.data.len * 4) nimSetMem(data[start].addr, rgba.r.cint, len * 4)
else: else:
var i: int var i = start
when defined(amd64): when defined(amd64):
# When supported, SIMD fill until we run out of room. # When supported, SIMD fill until we run out of room
let m = mm_set1_epi32(cast[int32](rgba)) let m = mm_set1_epi32(cast[int32](rgba))
while i < image.data.len - 4: for j in countup(i, start + len - 8, 8):
mm_storeu_si128(image.data[i].addr, m) mm_storeu_si128(data[j].addr, m)
i += 4 mm_storeu_si128(data[j + 4].addr, m)
i += 8
else: else:
when sizeof(int) == 8: when sizeof(int) == 8:
# Fill 8 bytes at a time when possible # Fill 8 bytes at a time when possible
let let
u32 = cast[uint32](rgba) u32 = cast[uint32](rgba)
u64 = cast[uint64]([u32, u32]) u64 = cast[uint64]([u32, u32])
while i < image.data.len - 2: for j in countup(i, start + len - 2, 2):
cast[ptr uint64](image.data[i].addr)[] = u64 cast[ptr uint64](image.data[j].addr)[] = u64
i += 2 i += 2
# Fill whatever is left the slow way # Fill whatever is left the slow way
for j in i ..< image.data.len: for j in i ..< start + len:
image.data[j] = rgba data[j] = rgba
proc fill*(image: Image, rgba: ColorRgba) {.inline.} =
## Fills the image with a solid color.
fillUnsafe(image.data, rgba, 0, image.data.len)
when defined(release):
{.pop.}
proc draw*(a, b: Image, mat: Mat3, blendMode = bmNormal)
proc draw*(a, b: Image, pos = vec2(0, 0), blendMode = bmNormal) {.inline.}
proc invert*(image: Image) = proc invert*(image: Image) =
## Inverts all of the colors and alpha. ## Inverts all of the colors and alpha.
@ -493,7 +497,7 @@ proc drawUber(
if blendMode == bmIntersectMask: if blendMode == bmIntersectMask:
if xMin > 0: if xMin > 0:
zeroMem(a.getAddr(0, y), 4 * xMin) zeroMem(a.data[a.dataIndex(0, y)].addr, 4 * xMin)
for x in xMin ..< xMax: for x in xMin ..< xMax:
let let
@ -510,7 +514,7 @@ proc drawUber(
if blendMode == bmIntersectMask: if blendMode == bmIntersectMask:
if a.width - xMax > 0: if a.width - xMax > 0:
zeroMem(a.getAddr(xMax, y), 4 * (a.width - xMax)) zeroMem(a.data[a.dataIndex(xMax, y)].addr, 4 * (a.width - xMax))
proc draw*(a, b: Image, mat: Mat3, blendMode: BlendMode) = proc draw*(a, b: Image, mat: Mat3, blendMode: BlendMode) =
## Draws one image onto another using matrix with color blending. ## Draws one image onto another using matrix with color blending.