Merge pull request #235 from guzba/master

masks and images conversion
This commit is contained in:
treeform 2021-06-25 09:35:18 -07:00 committed by GitHub
commit 4e5b2cd91e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 98 additions and 21 deletions

View file

@ -33,7 +33,7 @@ converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline.} =
c.rgbx()
proc decodeImage*(data: string | seq[uint8]): Image =
## Loads an image from a memory.
## Loads an image from memory.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
decodePng(data)
elif data.len > 2 and data.readUint16(0) == cast[uint16](jpgStartOfImage):
@ -48,10 +48,21 @@ proc decodeImage*(data: string | seq[uint8]): Image =
else:
raise newException(PixieError, "Unsupported image file format")
proc decodeMask*(data: string | seq[uint8]): Mask =
## Loads a mask from memory.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
newMask(decodePng(data))
else:
raise newException(PixieError, "Unsupported mask file format")
proc readImage*(filePath: string): Image =
## Loads an image from a file.
decodeImage(readFile(filePath))
proc readMask*(filePath: string): Mask =
## Loads a mask from a file.
decodeMask(readFile(filePath))
proc encodeImage*(image: Image, fileFormat: FileFormat): string =
## Encodes an image into memory.
case fileFormat:
@ -62,7 +73,15 @@ proc encodeImage*(image: Image, fileFormat: FileFormat): string =
of ffBmp:
image.encodeBmp()
of ffGif:
raise newException(PixieError, "Unsupported image format")
raise newException(PixieError, "Unsupported file format")
proc encodeMask*(mask: Mask, fileFormat: FileFormat): string =
## Encodes a mask into memory.
case fileFormat:
of ffPng:
mask.encodePng()
else:
raise newException(PixieError, "Unsupported file format")
proc writeFile*(image: Image, filePath: string, fileFormat: FileFormat) =
## Writes an image to a file.
@ -75,9 +94,23 @@ proc writeFile*(image: Image, filePath: string) =
of ".bmp": ffBmp
of ".jpg", ".jpeg": ffJpg
else:
raise newException(PixieError, "Unsupported image file extension")
raise newException(PixieError, "Unsupported file extension")
image.writeFile(filePath, fileformat)
proc writeFile*(mask: Mask, filePath: string, fileFormat: FileFormat) =
## Writes an mask to a file.
writeFile(filePath, mask.encodeMask(fileFormat))
proc writeFile*(mask: Mask, filePath: string) =
## Writes a mask to a file.
let fileFormat = case splitFile(filePath).ext.toLowerAscii():
of ".png": ffPng
of ".bmp": ffBmp
of ".jpg", ".jpeg": ffJpg
else:
raise newException(PixieError, "Unsupported file extension")
mask.writeFile(filePath, fileformat)
proc fillRect*(
mask: Mask,
rect: Rect,

View file

@ -24,6 +24,22 @@ proc newImage*(width, height: int): Image =
result.height = height
result.data = newSeq[ColorRGBX](width * height)
proc newImage*(mask: Mask): Image =
result = newImage(mask.width, mask.height)
var i: int
when defined(amd64) and not defined(pixieNoSimd):
for _ in countup(0, mask.data.len - 16, 4):
let values = mm_loadu_si128(mask.data[i].addr)
var alphas = unpackAlphaValues(values)
alphas = mm_or_si128(alphas, mm_srli_epi32(alphas, 8))
alphas = mm_or_si128(alphas, mm_srli_epi32(alphas, 16))
mm_storeu_si128(result.data[i].addr, alphas)
i += 4
for i in i ..< mask.data.len:
let v = mask.data[i]
result.data[i] = rgbx(v, v, v, v)
proc wh*(image: Image): Vec2 {.inline.} =
## Return with and height as a size vector.
vec2(image.width.float32, image.height.float32)

View file

@ -95,25 +95,18 @@ when defined(amd64) and not defined(pixieNoSimd):
proc unpackAlphaValues*(v: M128i): M128i {.inline.} =
## Unpack the first 32 bits into 4 rgba(0, 0, 0, value)
let
first32 = cast[M128i]([uint32.high, 0, 0, 0]) # First 32 bits
alphaMask = mm_set1_epi32(cast[int32](0xff000000)) # Only `a`
result = mm_shuffle_epi32(v, MM_SHUFFLE(0, 0, 0, 0))
let mask = cast[M128i]([uint8.high.uint64, 0])
var
i = mm_and_si128(result, first32)
j = mm_and_si128(result, mm_slli_si128(first32, 4))
k = mm_and_si128(result, mm_slli_si128(first32, 8))
l = mm_and_si128(result, mm_slli_si128(first32, 12))
i = mm_and_si128(v, mask)
j = mm_and_si128(v, mm_slli_si128(mask, 1))
k = mm_and_si128(v, mm_slli_si128(mask, 2))
l = mm_and_si128(v, mm_slli_si128(mask, 3))
# Shift the values to `a`
# Shift the values to uint32 `a`
i = mm_slli_si128(i, 3)
j = mm_slli_si128(j, 2)
k = mm_slli_si128(k, 1)
# l = mm_slli_si128(l, 0)
j = mm_slli_si128(j, 6)
k = mm_slli_si128(k, 9)
l = mm_slli_si128(l, 12)
result = mm_and_si128(
mm_or_si128(mm_or_si128(i, j), mm_or_si128(k, l)),
alphaMask
)
result = mm_or_si128(mm_or_si128(i, j), mm_or_si128(k, l))

View file

@ -73,12 +73,21 @@ block:
reset()
timeIt "newMask":
timeIt "newMask(image)":
let mask = image.newMask()
doAssert mask[0, 0] == image[0, 0].a
reset()
block:
let mask = image.newMask()
timeIt "newImage(mask)":
let image = newImage(mask)
doAssert mask[0, 0] == image[0, 0].a
reset()
timeIt "blur":
image.blur(40)

BIN
tests/images/mask2image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

View file

@ -138,3 +138,29 @@ block:
ctx.fillRect(rect(25, 25, 50, 50))
ctx.image.blur(20, rgba(0, 0, 0, 255))
ctx.image.writeFile("tests/images/imageblur20oob.png")
block: # Test conversion between image and mask
let
originalImage = newImage(100, 100)
originalMask = newMask(100, 100)
var p: Path
p.rect(10, 10, 80, 80)
originalImage.fillPath(p, rgba(255, 0, 0, 255))
originalMask.fillPath(p)
# Converting an image to a mask == a mask of the same fill
doAssert newMask(originalImage).data == originalMask.data
# Converting a mask to an image == converting an image to a mask as an image
doAssert newImage(newMask(originalImage)).data == newImage(originalMask).data
block:
var p: Path
p.roundedRect(10, 10, 80, 80, 10, 10, 10, 10)
let image = newImage(100, 100)
image.fillPath(p, rgba(255, 0, 0, 255))
newImage(newMask(image)).writeFile("tests/images/mask2image.png")