///|
/// Error raised by image operations that receive invalid arguments.
pub suberror ImageError {
ImageError(String)
}
///|
pub impl Show for ImageError with fn output(self, logger) {
let ImageError(m) = self
logger.write_string("ImageError(\{m})")
}
///|
/// Core image type. Internal storage is RGBA8, row-major, `h * w * 4` bytes.
/// The byte at offset `(y * w + x) * 4` is R, then G, B, A.
pub(all) struct Image {
data : Array[Byte]
h : Int
w : Int
}
///|
/// Create an all-zero (transparent black) image of the given size.
pub fn Image::new(h : Int, w : Int) -> Image {
{ data: Array::make(h * w * 4, (0 : Byte)), h, w }
}
///|
/// Create a solid-color image.
pub fn Image::from_pixel(
h : Int,
w : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
) -> Image {
let data = Array::makei(h * w * 4, fn(i) {
match i % 4 {
0 => r
1 => g
2 => b
_ => a
}
})
{ data, h, w }
}
///|
/// Build an image from raw RGBA8 data. Fails if the length does not match.
pub fn Image::from_data(
data : Array[Byte],
h : Int,
w : Int,
) -> Image raise ImageError {
if data.length() != h * w * 4 {
raise ImageError("from_data: length mismatch")
}
{ data, h, w }
}
///|
/// Deep copy of the image buffer.
pub fn Image::clone(self : Image) -> Image {
{ data: self.data.copy(), h: self.h, w: self.w }
}
///|
/// Image height in pixels.
pub fn Image::height(self : Image) -> Int {
self.h
}
///|
/// Image width in pixels.
pub fn Image::width(self : Image) -> Int {
self.w
}
///|
/// `(height, width)` pair.
pub fn Image::shape(self : Image) -> (Int, Int) {
(self.h, self.w)
}
///|
/// Length of the underlying byte buffer (`h * w * 4`).
pub fn Image::len(self : Image) -> Int {
self.data.length()
}
///|
/// Whether the image has no pixels.
pub fn Image::is_empty(self : Image) -> Bool {
self.h == 0 || self.w == 0
}
///|
/// Flat byte offset of pixel `(y, x)`.
pub fn Image::offset(self : Image, y : Int, x : Int) -> Int {
(y * self.w + x) * 4
}
///|
/// Read pixel `(y, x)`; returns `None` when out of bounds.
pub fn Image::pixel_at(
self : Image,
y : Int,
x : Int,
) -> (Byte, Byte, Byte, Byte)? {
if y < 0 || x < 0 || y >= self.h || x >= self.w {
None
} else {
let o = self.offset(y, x)
Some((self.data[o], self.data[o + 1], self.data[o + 2], self.data[o + 3]))
}
}
///|
/// Write pixel `(y, x)`; fails when out of bounds.
pub fn Image::pixel_set(
self : Image,
y : Int,
x : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
) -> Unit raise ImageError {
if y < 0 || x < 0 || y >= self.h || x >= self.w {
raise ImageError("pixel_set: out of bounds")
}
let o = self.offset(y, x)
self.data[o] = r
self.data[o + 1] = g
self.data[o + 2] = b
self.data[o + 3] = a
}
///|
/// Iterate over every pixel with its coordinates and RGBA values.
pub fn Image::for_each_pixel(
self : Image,
f : (Int, Int, Byte, Byte, Byte, Byte) -> Unit,
) -> Unit {
for y = 0; y < self.h; y = y + 1 {
for x = 0; x < self.w; x = x + 1 {
let o = self.offset(y, x)
f(
y,
x,
self.data[o],
self.data[o + 1],
self.data[o + 2],
self.data[o + 3],
)
}
}
}
///|
/// Build an image from four channel arrays. Alpha defaults to fully opaque
/// when `None`.
pub fn Image::from_channels(
r : Array[Byte],
g : Array[Byte],
b : Array[Byte],
a : Array[Byte]?,
) -> Image raise ImageError {
merge_channels(r, g, b, a)
}
///|
/// Read pixel at flat pixel index `idx` without bounds checking.
/// `idx` ranges over `[0, h * w)`; the byte offset is `idx * 4`.
pub fn Image::pixel_at_unchecked(
self : Image,
idx : Int,
) -> (Byte, Byte, Byte, Byte) {
let o = idx * 4
(self.data[o], self.data[o + 1], self.data[o + 2], self.data[o + 3])
}
///|
/// Write pixel at flat pixel index `idx` without bounds checking.
/// `idx` ranges over `[0, h * w)`; the byte offset is `idx * 4`.
pub fn Image::pixel_set_unchecked(
self : Image,
idx : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
) -> Unit {
let o = idx * 4
self.data[o] = r
self.data[o + 1] = g
self.data[o + 2] = b
self.data[o + 3] = a
}
///|
/// Iterate over every pixel and produce a new image from the transformed
/// values returned by `f`.
pub fn Image::for_each_pixel_mut(
self : Image,
f : (Int, Int, Byte, Byte, Byte, Byte) -> (Byte, Byte, Byte, Byte),
) -> Image {
let data = Array::make(self.data.length(), (0 : Byte))
for y = 0; y < self.h; y = y + 1 {
for x = 0; x < self.w; x = x + 1 {
let o = self.offset(y, x)
let (r, g, b, a) = f(
y,
x,
self.data[o],
self.data[o + 1],
self.data[o + 2],
self.data[o + 3],
)
data[o] = r
data[o + 1] = g
data[o + 2] = b
data[o + 3] = a
}
}
{ data, h: self.h, w: self.w }
}