diff --git a/src/pixie.nim b/src/pixie.nim index b0d21e0..688a4f2 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -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, diff --git a/src/pixie/images.nim b/src/pixie/images.nim index e0f9bf0..bf174b7 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -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) diff --git a/src/pixie/internal.nim b/src/pixie/internal.nim index 9368923..ecd0544 100644 --- a/src/pixie/internal.nim +++ b/src/pixie/internal.nim @@ -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)) diff --git a/tests/benchmark_images.nim b/tests/benchmark_images.nim index fc53bf6..6f761d7 100644 --- a/tests/benchmark_images.nim +++ b/tests/benchmark_images.nim @@ -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) diff --git a/tests/images/mask2image.png b/tests/images/mask2image.png new file mode 100644 index 0000000..91ccc81 Binary files /dev/null and b/tests/images/mask2image.png differ diff --git a/tests/test_images.nim b/tests/test_images.nim index 5f237f0..0141093 100644 --- a/tests/test_images.nim +++ b/tests/test_images.nim @@ -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")