// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// The whole surface in one run, so that a reader can see what this is for before
// reading any of it.
//
// moon run src/examples/tour
///|
/// A small picture with a gradient, a little noise and a transparent corner: the
/// three things a codec has to get right.
fn paint(width : Int32, height : Int32) -> @moonimage.Image {
let out = @buffer.Buffer::Buffer()
let mut seed = 12345
for y in 0.. width / 2 { (seed >> 16) & 31 } else { 0 }
let red = (x * 255 / (width - 1) + noise) & 0xFF
let green = (y * 255 / (height - 1)) & 0xFF
let blue = ((x + y) * 3) & 0xFF
let alpha = if x < width / 4 && y < height / 4 { 0 } else { 255 }
out.write_byte(red.to_byte())
out.write_byte(green.to_byte())
out.write_byte(blue.to_byte())
out.write_byte(alpha.to_byte())
}
}
@moonimage.Image::of(
width,
height,
@moonimage.PixelFormat::Rgba8,
out.to_bytes(),
) catch {
error => abort("the painted image is well formed: \{error}")
}
}
///|
#coverage.skip
fn main raise {
let image = paint(64, 48)
println(
"a \{image.width} by \{image.height} picture, \{image.format.bytes()} bytes a pixel",
)
// The same picture in other shapes: a wider sample, a grey one, one with the
// colour multiplied by the alpha.
let wide = image.convert(@moonimage.PixelFormat::Rgb16)
println(
"as sixteen-bit colour: \{wide.format.bytes()} bytes a pixel, \{wide.pixels.length()} in all",
)
let grey = image.convert(@moonimage.PixelFormat::Gray8)
println("as grey: \{grey.format.bytes()} byte a pixel")
// The operations that move pixels without reading them.
let turned = image.rotate(quarters=1)
println("turned a quarter: \{turned.width} by \{turned.height}")
let cropped = image.crop(8, 8, 32, 24) catch {
error => abort("the rectangle fits: \{error}")
}
println("cropped: \{cropped.width} by \{cropped.height}")
let small = image.scale(32, 24, sampling=@moonimage.Sampling::Lanczos) catch {
error => abort("the size is allowed: \{error}")
}
println("scaled down with Lanczos: \{small.width} by \{small.height}")
// Every format the library writes, and how large each one makes this picture.
for
format in [
@moonimage.Format::Png,
@moonimage.Format::Qoi,
@moonimage.Format::Bmp,
@moonimage.Format::Tga,
@moonimage.Format::Gif,
@moonimage.Format::Jpeg,
@moonimage.Format::Webp,
] {
let file = @moonimage.encode(image, format)
let back = @moonimage.decode(file) catch {
error => abort("what was written is read: \{error}")
}
println(
"\{format}: \{file.length()} bytes, \{back.width} by \{back.height}, \{back.format.bytes()} bytes a pixel",
)
}
// WebP is two codecs under one container, and this library writes only the
// lossless one. The lossy one it reads, which is what most WebP on the web is;
// the file below was written by `cwebp -q 75` from a 16 by 16 gradient, and is
// here as the only way to show a decode without an encoder for it.
let lossy : Bytes = b"RIFFR\x00\x00\x00WEBPVP8 F\x00\x00\x00\xf0\x01\x00\x9d\x01*\x10\x00\x10\x00\x02\x004%\xb0" +
b"\x02t\x01\x0f\x0c\x12\xf2\xca\x80\x00\xfe\xfc\x03\xcb\xf2}\x12\xe9p\xe1\x0b\x86=\xa6\xed1" +
b"\xff\xc9\x8at\xfe\xf9\xf0\x851x\x14D3\x97\xff\xfc\x8b\xddO\xff\xa7>\xf7\x0b\xfec\x00\x00\x00"
let from_lossy = @moonimage.decode(lossy) catch {
error => abort("the lossy file is read: \{error}")
}
println(
"a lossy WebP of \{lossy.length()} bytes: \{from_lossy.width} by \{from_lossy.height}, \{from_lossy.format.bytes()} bytes a pixel",
)
// A picture that says what it is: the text goes into a PNG and comes back out.
let annotated = @moonimage.Image::of(
image.width,
image.height,
image.format,
image.pixels,
metadata=@moonimage.Metadata::{
gamma: Some(0.45455),
chromaticities: Some(@moonimage.Chromaticities::srgb()),
intent: Some(@moonimage.Intent::Perceptual),
icc: None,
texts: [@moonimage.Text::Latin(keyword="Title", text="a gradient")],
},
) catch {
error => abort("the annotated image is well formed: \{error}")
}
let file = @moonimage.encode(annotated, @moonimage.Format::Png)
let read_back = @moonimage.decode(file) catch {
error => abort("the file is read: \{error}")
}
println("gamma came back as \{read_back.metadata.gamma.unwrap_or(0.0)}")
println("the note came back as \{read_back.metadata.texts[0].text()}")
// Animation, which three of the formats have.
let dot = paint(8, 8)
let animation = @moonimage.Animation::new(
64,
48,
[
@moonimage.Frame::new(image, delay=100),
@moonimage.Frame::new(
dot,
x=16,
y=16,
delay=50,
dispose=@moonimage.Dispose::Background,
),
],
loops=2,
)
for
format in [
@moonimage.Format::Png,
@moonimage.Format::Gif,
@moonimage.Format::Webp,
] {
let file = @moonimage.encode_animation(animation, format)
let back = @moonimage.decode_animation(file) catch {
error => abort("the animation is read: \{error}")
}
println(
"\{format} animation: \{file.length()} bytes, \{back.frames.length()} frames",
)
}
}