1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! # Hello Image
//!
//! Your first fovea program: create an image, fill pixels, read them back.
//!
//! Run with: `cargo run --example hello_image`
fn main() {
// Import the types we need:
// - Image: owned, heap-allocated image (like Vec<T> for 2D pixel data)
// - ImageView/ImageViewMut: trait-based read/write access (like &[T] / &mut [T])
// - ContiguousImage: trait that provides as_slice() for row-major pixel access
// - Mono8: single-channel 8-bit grayscale pixel
use fovea::image::{ContiguousImage, Image, ImageView, ImageViewMut};
use fovea::pixel::Mono8;
// ── 1. Create an image filled with zeros ─────────────────────────────────
// Image::zero requires the pixel type to be ZeroablePixel (Mono8 qualifies).
// The image owns its pixel data on the heap, just like Vec<T>.
let mut img = Image::<Mono8>::zero(4, 3);
println!("Created {}×{} image", img.width(), img.height());
// ── 2. Set individual pixels ─────────────────────────────────────────────
// pixel_at_mut(x, y) returns &mut Mono8.
// Coordinates are (column, row) — x is horizontal, y is vertical.
// Panics on out-of-bounds, just like slice indexing with [].
*img.pixel_at_mut(0, 0) = Mono8::new(100);
*img.pixel_at_mut(1, 0) = Mono8::new(200);
*img.pixel_at_mut(2, 0) = Mono8::new(50);
*img.pixel_at_mut(3, 0) = Mono8::new(255);
// ── 3. Read pixels back ──────────────────────────────────────────────────
// pixel_at(x, y) returns &Mono8 — the immutable counterpart.
let p = img.pixel_at(0, 0);
// .value() extracts the raw u8 intensity from the Mono8 wrapper.
println!("Pixel at (0,0): {:?} — raw value = {}", p, p.value());
// get(x, y) is the checked version — returns Option<&Mono8> instead of panicking.
let inside = img.get(3, 2);
let outside = img.get(4, 0); // x=4 is out of bounds for width=4
println!("get(3,2) = {:?}", inside); // Some(Mono8(0))
println!("get(4,0) = {:?}", outside); // None — safely handled
// ── 4. Iterate over all pixels ───────────────────────────────────────────
// as_slice() comes from the ContiguousImage trait.
// It gives &[Mono8] — the full pixel buffer in row-major order.
let sum: u32 = img.as_slice().iter().map(|px| px.value() as u32).sum();
println!("Sum of all pixel values: {sum}");
// Count how many pixels are brighter than a threshold.
let bright_count = img.as_slice().iter().filter(|px| px.value() > 100).count();
println!("Pixels with value > 100: {bright_count}");
// ── 5. Create an image from a Vec ────────────────────────────────────────
// from_vec checks that data.len() == width * height at runtime.
// Returns Result — Err if the length doesn't match.
let pixels = vec![Mono8::new(10); 8]; // exactly 4×2 = 8 pixels
let img2 = Image::from_vec(4, 2, pixels).expect("length must match width × height");
println!(
"img2: {}×{}, first pixel = {:?}",
img2.width(),
img2.height(),
img2.pixel_at(0, 0),
);
// Demonstrate the error case: 7 pixels can't fill a 4×2 image.
let bad = Image::from_vec(4, 2, vec![Mono8::new(0); 7]);
println!("from_vec with wrong length: {:?}", bad.err());
// ── 6. Generate an image with a closure ──────────────────────────────────
// Image::generate calls f(x, y) for every pixel position.
// Here we create a horizontal gradient: intensity increases with x.
let gradient = Image::generate(8, 4, |x, _y| {
// Scale x from [0..7] into [0..255] so the gradient spans full range.
let intensity = (x * 255 / 7) as u8;
Mono8::new(intensity)
});
// Print the first row to show the gradient.
print!("Gradient row 0:");
for x in 0..gradient.width() {
print!(" {}", gradient.pixel_at(x, 0).value());
}
println!();
// ── 7. Fill an image with a constant value ───────────────────────────────
// Image::fill is like zero(), but with an arbitrary pixel value.
let white = Image::fill(3, 3, Mono8::new(255));
// Every pixel should be 255.
let all_white = white.as_slice().iter().all(|px| px.value() == 255);
println!("3×3 white image — all pixels 255? {all_white}");
// ── 8. Pixel value manipulation ──────────────────────────────────────────
// Mono8 wraps a Saturating<u8>, but arithmetic ops on the pixel type
// itself are limited (only Mul<f32> is provided for scaling).
// For general math, extract with .value(), compute, then wrap back.
let original = Mono8::new(180);
let brightened = Mono8::new(original.value().saturating_add(50)); // 180 + 50 = 230
let dimmed = Mono8::new(original.value().saturating_sub(200)); // 180 - 200 = 0 (saturates)
println!(
"original = {}, bright = {}, dim = {}",
original.value(),
brightened.value(),
dimmed.value()
);
// Mono8 does support Mul<f32> for scaling:
let scaled = original * 0.5; // 180 * 0.5 = 90
println!("180 * 0.5 = {}", scaled.value());
println!("\nDone! You've created and manipulated images with fovea.");
}