README

megemini/millow/src does not have a README file

#
Decoder

pub trait Decoder {
fn decode(Self, data : Array[Byte]) -> Image raise ImageError
}

Pluggable decoder: turn an encoded byte buffer into an image.

Implementations decode data into an Image, raising ImageError on a malformed or unsupported buffer. Register instances with register_decoder to make them available via decode.

#
Encoder

pub trait Encoder {
fn encode(Self, img : Image) -> Array[Byte] raise ImageError
}

Pluggable encoder: turn an image into an encoded byte buffer.

Implementations serialize img to a format-specific byte stream, raising ImageError on failure. Register instances with register_encoder to make them available via encode.

#
ImageError

pub suberror ImageError {
ImageError(String)
}

Error raised by image operations that receive invalid arguments.
impl Show for ImageError

#
Augmentation

pub(all) enum Augmentation {
Crop(Int, Int, Int, Int)
Resize(Int, Int)
FlipHorizontal
FlipVertical
Rotate(Double)
Brightness(Double)
Contrast(Double)
Gamma(Double)
NoiseGaussian(Double)
NoiseSaltPepper(Double)
ColorJitter(Double, Double, Double, Double)
}

Data augmentation operations that can be composed into a pipeline.

Each variant wraps the parameters of one transformation; augment_pipeline applies them in order. Variants:

  • Crop(y, x, h, w): extract the sub-image at (y, x) of size h x w.
  • Resize(dst_h, dst_w): resize to dst_h x dst_w using bilinear sampling.
  • FlipHorizontal / FlipVertical: mirror the image.
  • Rotate(angle): rotate by angle degrees (bilinear).
  • Brightness(factor) / Contrast(factor): multiplicative adjustments.
  • Gamma(g): gamma correction with exponent g.
  • NoiseGaussian(std): add Gaussian noise with standard deviation std.
  • NoiseSaltPepper(prob): corrupt pixels with probability prob.
  • ColorJitter(b, c, s, h): brightness/contrast/saturation/hue jitter.

#
BorderMode

pub(all) enum BorderMode {
Replicate
Reflect
Wrap
Constant(Byte, Byte, Byte, Byte)
}

Border handling mode for convolution and interpolation operations.

  • Replicate: clamp coordinates to the nearest edge pixel.
  • Reflect: mirror coordinates with edge duplication.
  • Wrap: tile coordinates periodically.
  • Constant(r, g, b, a): use the given RGBA color outside the image.

#
Image

pub(all) struct Image {
data : Array[Byte]
h : Int
w : Int
}

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.

#
Image::channel

fn Image::channel(self : Image, ch : Int) -> Array[Byte]

Extract a single channel as an h * w byte array.

  • ch: channel index (0=R, 1=G, 2=B, 3=A).

#
Image::channel_a

fn Image::channel_a(self : Image) -> Array[Byte]

Alpha channel.

#
Image::channel_b

fn Image::channel_b(self : Image) -> Array[Byte]

Blue channel.

#
Image::channel_g

fn Image::channel_g(self : Image) -> Array[Byte]

Green channel.

#
Image::channel_r

fn Image::channel_r(self : Image) -> Array[Byte]

Red channel.

#
Image::clone

fn Image::clone(self : Image) -> Image

Deep copy of the image buffer.

#
Image::for_each_pixel

fn Image::for_each_pixel(self : Image, f : (Int, Int, Byte, Byte, Byte, Byte) -> Unit) -> Unit

Iterate over every pixel with its coordinates and RGBA values.

#
Image::for_each_pixel_mut

fn Image::for_each_pixel_mut(self : Image, f : (Int, Int, Byte, Byte, Byte, Byte) -> (Byte, Byte, Byte, Byte)) -> Image

Iterate over every pixel and produce a new image from the transformed values returned by f.

#
Image::from_channels

fn Image::from_channels(r : Array[Byte], g : Array[Byte], b : Array[Byte], a : Array[Byte]?) -> Image raise ImageError

Build an image from four channel arrays. Alpha defaults to fully opaque when None.

#
Image::from_data

fn Image::from_data(data : Array[Byte], h : Int, w : Int) -> Image raise ImageError

Build an image from raw RGBA8 data. Fails if the length does not match.

#
Image::from_pixel

fn Image::from_pixel(h : Int, w : Int, r : Byte, g : Byte, b : Byte, a : Byte) -> Image

Create a solid-color image.

#
Image::height

fn Image::height(self : Image) -> Int

Image height in pixels.

#
Image::is_empty

fn Image::is_empty(self : Image) -> Bool

Whether the image has no pixels.

#
Image::len

fn Image::len(self : Image) -> Int

Length of the underlying byte buffer (h * w * 4).

#
Image::new

fn Image::new(h : Int, w : Int) -> Image

Create an all-zero (transparent black) image of the given size.

#
Image::offset

fn Image::offset(self : Image, y : Int, x : Int) -> Int

Flat byte offset of pixel (y, x).

#
Image::pixel_at

fn Image::pixel_at(self : Image, y : Int, x : Int) -> (Byte, Byte, Byte, Byte)?

Read pixel (y, x); returns None when out of bounds.

#
Image::pixel_at_unchecked

fn Image::pixel_at_unchecked(self : Image, idx : Int) -> (Byte, Byte, Byte, Byte)

Read pixel at flat pixel index idx without bounds checking. idx ranges over [0, h * w); the byte offset is idx * 4.

#
Image::pixel_set

fn Image::pixel_set(self : Image, y : Int, x : Int, r : Byte, g : Byte, b : Byte, a : Byte) -> Unit raise ImageError

Write pixel (y, x); fails when out of bounds.

#
Image::pixel_set_unchecked

fn Image::pixel_set_unchecked(self : Image, idx : Int, r : Byte, g : Byte, b : Byte, a : Byte) -> Unit

Write pixel at flat pixel index idx without bounds checking. idx ranges over [0, h * w); the byte offset is idx * 4.

#
Image::set_channel

fn Image::set_channel(self : Image, ch : Int, data : Array[Byte]) -> Image

Return a copy of the image with one channel replaced.

  • ch: channel index (0=R, 1=G, 2=B, 3=A).
  • data: per-pixel values; length must equal h * w.

#
Image::shape

fn Image::shape(self : Image) -> (Int, Int)

(height, width) pair.

#
Image::split_channels

fn Image::split_channels(self : Image) -> (Array[Byte], Array[Byte], Array[Byte], Array[Byte])

Split into four channel arrays (r, g, b, a).

#
Image::sub_image

fn Image::sub_image(self : Image, y : Int, x : Int, h : Int, w : Int) -> Image raise ImageError

Alias of crop (this library stores contiguous buffers, so no true view).

#
Image::width

fn Image::width(self : Image) -> Int

Image width in pixels.

#
Interp

pub(all) enum Interp {
Nearest
Bilinear
Bicubic
} derive(Eq)

Interpolation method used by geometric resampling operations.

  • Nearest: pick the closest source pixel.
  • Bilinear: weighted average of the 4 nearest pixels.
  • Bicubic: cubic interpolation over a 4x4 neighborhood.

#
Kernel

pub(all) enum Kernel {
Cross(Int)
Square(Int)
Custom(Array[Array[Bool]])
} derive(Eq)

Structuring element for morphological operations.

  • Cross(n): plus-shaped mask with radius n (arm length n).
  • Square(n): full (2n+1) x (2n+1) box.
  • Custom(mask): arbitrary boolean mask given as rows of booleans.

#
PadMode

pub(all) enum PadMode {
Constant(Byte, Byte, Byte, Byte)
Replicate
Reflect
Wrap
} derive(Eq)

Border handling strategy used by padding and neighborhood operations.

  • Constant(r, g, b, a): fill outside pixels with the given RGBA color.
  • Replicate: copy the nearest edge pixel.
  • Reflect: mirror with edge duplication (the boundary pixel is repeated).
  • Wrap: tile the image periodically.

#
PpmEncoder

pub(all) struct PpmEncoder {
}

Built-in encoder that writes binary PPM (P6).

A unit struct implementing Encoder by delegating to to_ppm. Register it with register_encoder("ppm", &PpmEncoder{}) to plug into encode.

#
RegionProps

pub struct RegionProps {
label : Int
area : Int
centroid : (Double, Double)
bbox : (Int, Int, Int, Int)
}

Region properties for labelled images.

  • label: the 1-based label the props describe.
  • area: number of pixels in the region.
  • centroid: (row, col) mean pixel coordinate.
  • bbox: (min_row, min_col, max_row, max_col) inclusive bounds.

#
adjust_brightness

fn adjust_brightness(img : Image, factor : Double) -> Image

Brightness adjustment using a multiplicative factor. factor = 1.0 returns the original image, factor = 0.0 returns a black image.

#
adjust_contrast

fn adjust_contrast(img : Image, factor : Double) -> Image

Contrast adjustment based on image mean. factor = 1.0 returns the original image, factor = 0.0 returns a solid gray image.

#
adjust_gamma

fn adjust_gamma(img : Image, gamma : Double) -> Image

Gamma correction. gamma > 1 darkens, gamma < 1 brightens.

#
affine_transform

fn affine_transform(img : Image, matrix : Array[Double], dst_h : Int, dst_w : Int, interp : Interp, mode? : BorderMode) -> Image

Apply a general affine transform to the image.

matrix is [a, b, c, d, e, f] defining the forward mapping:

  • x' = a*x + b*y + c
  • y' = d*x + e*y + f

  • dst_h, dst_w: output image dimensions.
  • interp: interpolation method (Nearest or Bilinear).
  • mode: border handling for samples outside the source (default Constant(0, 0, 0, 255)).

Each output pixel is sampled from the inverse-mapped source location.

#
apply_lut

fn apply_lut(img : Image, r_lut : Array[Byte], g_lut : Array[Byte], b_lut : Array[Byte], a_lut : Array[Byte]?) -> Image

Apply per-channel lookup tables (each 256 entries). When a_lut is None, the alpha channel is copied unchanged.

  • r_lut, g_lut, b_lut: 256-entry tables indexed by the source byte.
  • a_lut: optional 256-entry table for the alpha channel.

#
apply_lut_uniform

fn apply_lut_uniform(img : Image, lut : Array[Byte]) -> Image

Apply a single 256-entry LUT to the RGB channels uniformly; alpha is copied unchanged.

  • lut: 256-entry table indexed by the source byte.

#
augment_pipeline

fn augment_pipeline(img : Image, pipeline : Array[Augmentation]) -> Image raise

Apply a sequence of augmentations in order.

Folds pipeline left-to-right over img, dispatching each Augmentation variant to its underlying operation. May raise from crop/resize.

#
augment_random_choice

fn augment_random_choice(img : Image, choices : Array[(Double, Augmentation)]) -> Image raise

Randomly choose and apply one augmentation from a weighted list.

choices pairs each Augmentation with a non-negative weight; the chosen entry is sampled proportionally and applied via augment_pipeline. Returns a clone of the input when choices is empty.

#
auto_contrast

fn auto_contrast(img : Image, cutoff : Int) -> Image

Automatic contrast stretch with histogram cutoff.

Builds the image histogram, discards cutoff percent of the pixel count from each end, then linearly stretches the remaining intensity range to the full 0–255 range. The alpha channel is preserved.

#
bilateral_filter

fn bilateral_filter(img : Image, d : Int, sigma_color : Double, sigma_space : Double) -> Image

Bilateral filter: edge-preserving denoising. Uses reflect boundary mode.

  • d: neighbourhood diameter; if <= 0, computed as max(5, 2 * ceil(3 * sigma_space) + 1).
  • sigma_color: tonal weight, in [0, 1] (normalized RGB distance).
  • sigma_space: spatial weight.

#
bilinear_luma

fn bilinear_luma(img : Image, fy : Double, fx : Double, mode : BorderMode) -> Double

Bilinear interpolation of luma at fractional coordinates with specified border mode.

#
black_hat

fn black_hat(img : Image, kernel : Kernel) -> Image

Black-hat: closing minus original.

Isolates small dark structures smaller than kernel that are filled by the closing.

#
box_blur

fn box_blur(img : Image, radius : Int) -> Image

Box blur using per-channel integral images (O(1) per pixel). Border windows are shrunk to the valid area.

#
canny

fn canny(img : Image, low : Double, high : Double) -> Image

Canny edge detector producing a binary edge image.

The pipeline smooths the image with a Gaussian blur, computes Sobel gradients, applies non-maximum suppression along the gradient direction, and uses dual-threshold hysteresis to trace the final edges.

  • low: lower hysteresis threshold (0–255). Responses below this are discarded.
  • high: upper hysteresis threshold (0–255). Responses at or above this become strong edges; responses between low and high are kept only if connected to a strong edge.

Returns a grayscale image where edge pixels are 255 and all others are 0.

#
clahe

fn clahe(img : Image, clip_limit : Double, grid_size : (Int, Int)) -> Image

CLAHE (Contrast-Limited Adaptive Histogram Equalization).

Divides the image into grid_size tiles, clips each tile histogram at clip_limit (as a fraction of the tile pixel count) and redistributes the excess uniformly, equalizes each tile, and blends neighbouring tile LUTs with bilinear interpolation. Operates on luminance and emits a grayscale result; alpha is preserved.

#
clamp_byte

fn clamp_byte(v : Int) -> Byte

Convert an integer to a byte, clamping into [0, 255].

#
clampd

fn clampd(v : Double, lo : Double, hi : Double) -> Double

Clamp a double into the inclusive range [lo, hi].

#
clampi

fn clampi(v : Int, lo : Int, hi : Int) -> Int

Clamp an integer into the inclusive range [lo, hi].

#
composite_color

fn composite_color(img : Image, mask : Image, r : Byte, g : Byte, b : Byte) -> Image

Composite a solid color onto img using mask as the alpha source. Where mask is bright, the color is applied more strongly.

  • mask: its red channel (normalized to [0, 1]) is used as the blend weight.
  • r, g, b: the color to composite.

#
composite_over

fn composite_over(src : Image, dst : Image) -> Image

Alpha "over" compositing of src onto dst; result matches dst size. Source pixels outside its bounds are treated as fully transparent.

#
connected_components

fn connected_components(img : Image, connectivity : Int) -> (Array[Array[Int]], Int)

Connected components labelling (4- or 8-connectivity). Returns (labels, num_labels) where labels[y][x] is the label (1-based).

Non-black pixels (any RGB channel non-zero) are treated as foreground. connectivity should be 4 (orthogonal neighbours) or 8 (also include diagonals). Labels are compacted to a contiguous 1..num_labels range; background pixels keep label 0.

#
convolve

fn convolve(img : Image, kernel : Array[Array[Double]], normalize : Bool) -> Image

General 2D convolution over the RGB channels (border: replicate). When normalize is true, the kernel is divided by its sum (if non-zero).

  • img: input RGBA8 image; alpha is copied unchanged.
  • kernel: 2D weight matrix; the anchor is the centre (kh/2, kw/2).
  • normalize: divide the response by the kernel sum when true, by 1.0 otherwise.

#
corner_harris

fn corner_harris(img : Image, k : Double) -> Array[Array[Double]]

Harris corner response map.

Computes the Gaussian-weighted structure tensor of the image's luma gradients and returns the Harris response det(M) - k * trace(M)² per pixel.

  • k: Harris sensitivity parameter, typically in the range 0.04–0.06.

Returns an h × w array of response values; large positive values indicate corners.

#
corner_shi_tomasi

fn corner_shi_tomasi(img : Image) -> Array[Array[Double]]

Shi-Tomasi corner response map.

Computes the Gaussian-weighted structure tensor of the image's luma gradients and returns the smaller eigenvalue of the tensor per pixel.

Returns an h × w array of response values; large values indicate corner-like regions.

#
count_nonzero

fn count_nonzero(img : Image) -> Int

Count non-zero (non-black) pixels.

A pixel is counted when any of its RGB channels is non-zero; alpha is ignored.

#
count_pixels

fn count_pixels(img : Image, threshold : Byte) -> Int

Count pixels whose luma exceeds threshold.

Uses strict greater-than comparison on the BT.601 luma.

#
crop

fn crop(img : Image, y : Int, x : Int, h : Int, w : Int) -> Image raise ImageError

Crop a rectangular region starting at (y, x) with size h x w.

  • y, x: top-left corner of the region (inclusive).
  • h, w: height and width of the region.

Raises ImageError if the region lies outside the image bounds.

#
decode

fn decode(data : Array[Byte], ext : String) -> Image raise ImageError

Decode a buffer using the decoder registered for ext.

Raises ImageError when no decoder is registered for ext or the decoder rejects the buffer.

#
dilate

fn dilate(img : Image, kernel : Kernel) -> Image

Morphological dilation.

Each output pixel is the maximum of the RGB channel values over the structuring element defined by kernel, growing bright regions. Border coordinates are clamped (replicate); alpha is preserved.

#
draw_circle

fn draw_circle(img : Image, cy : Int, cx : Int, radius : Int, r : Byte, g : Byte, b : Byte, a : Byte, fill : Bool, width : Int) -> Image

Draw a circle. When fill is false, width sets the stroke thickness.

Centred at (cy, cx) with the given radius. Filled circles scan each row over the implicit x² + y² <= r² disc; outlines use the midpoint circle algorithm. Uses source-over alpha blending; the input image is not modified.

#
draw_ellipse

fn draw_ellipse(img : Image, cy : Int, cx : Int, ry : Int, rx : Int, r : Byte, g : Byte, b : Byte, a : Byte, fill : Bool, width : Int) -> Image

Draw an ellipse. When fill is false, width sets the stroke thickness.

Centred at (cy, cx) with semi-axes ry (rows) and rx (columns). Outlines use the midpoint ellipse algorithm; filled ellipses scan each row over the implicit disc. Uses source-over alpha blending; the input image is not modified.

#
draw_line

fn draw_line(img : Image, y0 : Int, x0 : Int, y1 : Int, x1 : Int, r : Byte, g : Byte, b : Byte, a : Byte, width : Int) -> Image

Draw a line with the Bresenham algorithm and the given stroke width.

Connects (y0, x0) to (y1, x1) inclusive, stamping a width x width block at each rasterized pixel using source-over alpha blending. The input image is not modified.

#
draw_pixel

fn draw_pixel(img : Image, y : Int, x : Int, r : Byte, g : Byte, b : Byte, a : Byte) -> Image

Draw a single pixel (alpha blended). Returns a new image.

Sets the pixel at (y, x) to (r, g, b) alpha-composited with the existing colour using source-over blending. Out-of-bounds coordinates are ignored. The input image is not modified.

#
draw_polygon

fn draw_polygon(img : Image, points : Array[(Int, Int)], r : Byte, g : Byte, b : Byte, a : Byte, fill : Bool, width : Int) -> Image

Draw a polygon. When fill is false, width sets the stroke thickness.

points is an array of (y, x) vertices connected in order, closing the path from the last back to the first. Filled polygons use a scanline fill; outlines call draw_line for each edge. Returns the input image unchanged when fewer than two vertices are supplied. The input image is not modified.

#
draw_rect

fn draw_rect(img : Image, y : Int, x : Int, h : Int, w : Int, r : Byte, g : Byte, b : Byte, a : Byte, fill : Bool, width : Int) -> Image

Draw a rectangle. When fill is false, width sets the stroke thickness.

The rectangle spans rows [y, y + h) and columns [x, x + w). When fill is true the interior is filled; otherwise a stroked outline of thickness width is drawn. Uses source-over alpha blending; the input image is not modified.

#
encode

fn encode(img : Image, ext : String) -> Array[Byte] raise ImageError

Encode an image using the encoder registered for ext.

Raises ImageError when no encoder is registered for ext or the encoder fails.

#
equalize_histogram

fn equalize_histogram(img : Image) -> Image

Histogram equalization applied to each RGB channel independently.

Each channel is remapped through its normalized cumulative distribution function so the output spans the full [0, 255] range. The alpha channel is copied unchanged.

#
equals

fn equals(a : Image, b : Image) -> Bool

Pixel-exact equality (same dimensions and bytes).

#
erode

fn erode(img : Image, kernel : Kernel) -> Image

Morphological erosion.

Each output pixel is the minimum of the RGB channel values over the structuring element defined by kernel, shrinking bright regions. Border coordinates are clamped (replicate); alpha is preserved.

#
find_contours

fn find_contours(img : Image) -> Array[Array[(Int, Int)]]

Find contours as arrays of (y, x) boundary pixels (simple boundary trace).

Labels the image with 4-connectivity, then for each label collects pixels that have at least one 4-neighbour with a different label (or lie on the image edge). Returns one array per connected component.

#
flatten_alpha

fn flatten_alpha(img : Image, r : Byte, g : Byte, b : Byte) -> Image

Composite the image over a solid background color, dropping transparency.

  • r, g, b: the solid background color; output alpha is forced to 255.

#
flip_horizontal

fn flip_horizontal(img : Image) -> Image

Mirror horizontally (left-right).

#
flip_vertical

fn flip_vertical(img : Image) -> Image

Mirror vertically (top-bottom).

#
flood_fill

fn flood_fill(img : Image, y : Int, x : Int, r : Byte, g : Byte, b : Byte, a : Byte, tolerance : Byte) -> Image

Flood fill: replace the colour at (y, x) and all connected pixels of similar colour (within tolerance) with the new colour.

4-connected BFS from the seed pixel; a pixel is replaced when each of its RGBA channels is within tolerance of the seed colour. Out-of-bounds seeds and no-op seeds (fill colour equals seed colour) return a clone of the input. The input image is not modified.

#
from_hsv

fn from_hsv(h : Array[Array[Double]], s : Array[Array[Double]], v : Array[Array[Double]]) -> Image

HSV → RGBA8. H in [0, 360), S in [0, 1], V in [0, 1].

  • h, s, v: equally-shaped rows × cols arrays; output alpha is 255.

#
from_ycbcr

fn from_ycbcr(y_arr : Array[Array[Double]], cb_arr : Array[Array[Double]], cr_arr : Array[Array[Double]]) -> Image

YCbCr → RGBA8. Expects Y in [0, 255], Cb/Cr centred at 128. Output alpha is 255.

#
gaussian_blur

fn gaussian_blur(img : Image, sigma : Double) -> Image

Gaussian blur; kernel size is derived from sigma.

  • img: input RGBA8 image; alpha is copied unchanged.
  • sigma: Gaussian standard deviation. The kernel size is clampi(ceil(sigma*3)*2+1, 3, 99); weights are exp(-d²/(2σ²)) normalised, applied as a separable 1D×1D pass with replicate borders.

#
gaussian_blur_double

fn gaussian_blur_double(arr : Array[Array[Double]], sigma : Double) -> Array[Array[Double]]

Gaussian blur for double arrays (used for structure tensor smoothing).

  • arr: 2D h × w array of doubles to smooth in place of an image.
  • sigma: Gaussian standard deviation; the kernel size is clampi(ceil(sigma*3)*2+1, 3, 99). Out-of-bounds samples are treated as 0.0 (zero-padding), unlike the image variants which replicate.

#
gaussian_blur_double3

fn gaussian_blur_double3(arr1 : Array[Array[Double]], arr2 : Array[Array[Double]], arr3 : Array[Array[Double]], sigma : Double) -> (Array[Array[Double]], Array[Array[Double]], Array[Array[Double]])

Fused Gaussian blur of three double arrays simultaneously. Avoids triple flatten/unflatten overhead when smoothing structure tensor components. Returns (out1, out2, out3).

#
gaussian_blur_kernel

fn gaussian_blur_kernel(img : Image, ksize : Int, sigma : Double) -> Image

Gaussian blur with an explicit (odd) kernel size.

  • img: input RGBA8 image; alpha is copied unchanged.
  • ksize: kernel diameter; an even value is rounded up to the next odd integer.
  • sigma: Gaussian standard deviation; weights are exp(-d²/(2σ²)) normalised, applied as a separable 1D×1D pass with replicate borders.

#
get_pixel

fn get_pixel(img : Image, y : Int, x : Int, mode : BorderMode) -> (Byte, Byte, Byte, Byte)

Get pixel at coordinates with specified border mode.

#
grayscale_to_rgb

fn grayscale_to_rgb(img : Image) -> Image

Convert a grayscale image (R==G==B) to RGB by forcing alpha to 255.

#
grayscale_to_rgba

fn grayscale_to_rgba(img : Image) -> Image

Convert a grayscale image to RGBA with fully opaque alpha.

#
histogram

fn histogram(img : Image) -> Array[Int]

256-bin luminance histogram.

Returns an array of length 256 where index i holds the number of pixels whose BT.601 luma equals i.

#
histogram_color

fn histogram_color(img : Image) -> Array[Array[Int]]

Per-channel histograms for R, G, B (each 256 bins).

Returns [hr, hg, hb] where each entry is a 256-element array of pixel counts for the corresponding channel.

#
histogram_correlation

fn histogram_correlation(img : Image, ref_img : Image) -> Double

Histogram correlation coefficient between two images' luminance histograms.

Returns the Pearson correlation of the two 256-bin histograms, in [-1.0, 1.0]. Returns 0.0 when either image is empty or the denominator is zero.

#
hit_or_miss

fn hit_or_miss(img : Image, kernel : Array[Array[Int]]) -> Image

Hit-or-miss transform. The kernel values are: 1 = foreground, 0 = background, -1 = don't care. Only pixels matching the pattern are kept.

Treats non-zero RGB pixels as foreground. A pixel is set to white when its neighbourhood matches kernel exactly (foreground/background), and black otherwise. Output alpha is set to 255.

#
hog

fn hog(img : Image, cell_size : Int, block_size : Int, nbins : Int) -> Array[Double]

Histogram of Oriented Gradients (HOG) feature descriptor.

Computes gradients on the luma field, accumulates unsigned gradient orientations (0–180°) into per-cell histograms of nbins bins, then normalizes each overlapping block_size × block_size block of cells with an L2 norm.

  • cell_size: side length (in pixels) of each cell.
  • block_size: side length (in cells) of each normalization block.
  • nbins: number of orientation bins per cell.

Returns a flat array of normalized histogram values.

#
hu_moments

fn hu_moments(img : Image) -> Array[Double]

Hu moments (7 translation/scale/rotation invariant moments).

Computed from the normalized central moments of the luma image. Returns a 7-element array; returns all zeros when the image is empty (m00 == 0).

#
info

fn info(img : Image) -> String

Human-readable summary of the image.

#
invert

fn invert(img : Image) -> Image

Invert the RGB channels (255 - value), alpha preserved.

#
laplacian

fn laplacian(img : Image) -> Image

Laplacian edge response as a grayscale image.

Applies the 3×3 Laplacian kernel [[0,1,0],[1,-4,1],[0,1,0]] to the image's luma field, takes the absolute value of the response, and scales it to fill the 0–255 range. The alpha channel is set to 255.

#
lbp

fn lbp(img : Image, radius : Int, n_points : Int) -> Array[Int]

Local Binary Pattern (LBP) codes for each pixel.

For every pixel, samples n_points neighbors on a circle of the given radius (using bilinear interpolation) and builds a bitmask of which neighbors are greater than or equal to the center value.

  • radius: circle radius in pixels.
  • n_points: number of sampled neighbors (also the bitmask width).

Returns a flat Array[Int] of length h * w, row-major.

#
lbp_histogram

fn lbp_histogram(img : Image, radius : Int, n_points : Int) -> Array[Int]

Histogram of Local Binary Pattern codes over the whole image.

  • radius: circle radius in pixels, forwarded to lbp.
  • n_points: number of sampled neighbors, forwarded to lbp.

Returns an Array[Int] of length 2 ^ n_points containing per-bin counts.

#
luma_at

fn luma_at(img : Image, y : Int, x : Int) -> Double

Get luma at coordinates with replicate border mode.

#
luma_at_mode

fn luma_at_mode(img : Image, y : Int, x : Int, mode : BorderMode) -> Double

Get luma at coordinates with specified border mode.

#
match_histogram

fn match_histogram(img : Image, ref_img : Image) -> Image

Histogram matching: adjust img so its luminance histogram matches ref_img.

Builds the luminance CDFs of both images and remaps each source pixel to the reference value whose CDF is closest. The alpha channel is copied unchanged.

#
max_filter

fn max_filter(img : Image, size : Int) -> Image

Maximum (dilation-like) filter with an odd window size. Each output pixel is the max over a (2r+1)² window with replicate borders; alpha is copied unchanged.

#
median_filter

fn median_filter(img : Image, size : Int) -> Image

Median filter with an odd window size. Reduces salt-and-pepper noise while preserving edges. Uses replicate borders; alpha is copied unchanged.

#
merge_channels

fn merge_channels(r : Array[Byte], g : Array[Byte], b : Array[Byte], a : Array[Byte]?) -> Image raise ImageError

Merge channel arrays into a 1 × n image (single row). Alpha defaults to fully opaque (255) when None. Raises ImageError on length mismatch.

#
min_filter

fn min_filter(img : Image, size : Int) -> Image

Minimum (erosion-like) filter with an odd window size. Each output pixel is the min over a (2r+1)² window with replicate borders; alpha is copied unchanged.

#
moments

fn moments(img : Image) -> Array[Double]

Raw image moments (m00, m10, m01, m20, m11, m02, m30, m21, m12, m03).

Uses millow's (y, x) convention where m_pq = Σ y^p · x^q · luma: the first index p is the row (y) exponent, the second q is the column (x) exponent. Each pixel's BT.601 luma is used as the weight. Returns a 10-element array in the order listed above.

#
morph_close

fn morph_close(img : Image, kernel : Kernel) -> Image

Closing: dilation followed by erosion.

Fills small dark holes and gaps smaller than kernel while preserving the shape of larger regions.

#
morph_gradient

fn morph_gradient(img : Image, kernel : Kernel) -> Image

Morphological gradient: dilation minus erosion.

Highlights region edges by computing the per-channel difference between the dilated and eroded images.

#
morph_open

fn morph_open(img : Image, kernel : Kernel) -> Image

Opening: erosion followed by dilation.

Removes small bright foreground structures smaller than kernel while preserving the shape of larger regions.

#
mse

fn mse(a : Image, b : Image) -> Double

Mean squared error over all RGBA channels; -1.0 on size mismatch.

#
normalize

fn normalize(img : Image, min : Byte, max : Byte) -> Image

Linearly rescale the RGB value range into [min, max].

Finds the current minimum and maximum across all RGB channels, then maps that range linearly onto [min, max]. If the input range is empty the image is returned unchanged. The alpha channel is preserved.

#
pad

fn pad(img : Image, top : Int, right : Int, bottom : Int, left : Int, mode : PadMode) -> Image

Pad the image by adding margins around it.

  • top, right, bottom, left: margin sizes in pixels.
  • mode: PadMode controlling how the new border pixels are filled:
    • Constant(r, g, b, a) — fill with the given color.
    • Replicate — copy the nearest edge pixel.
    • Reflect — mirror with edge duplication.
    • Wrap — tile the source.

The output size is (img.h + top + bottom, img.w + left + right).

#
pad_to_size

fn pad_to_size(img : Image, h : Int, w : Int, mode : PadMode) -> Image

Center-pad the image to at least h x w using the chosen border mode.

  • h, w: target minimum dimensions.
  • mode: PadMode for the new border pixels.

Padding is split evenly (left/top-biased on odd sizes). If the image already meets or exceeds a dimension, no padding is added on that axis.

#
prewitt

fn prewitt(img : Image) -> Image

Prewitt edge magnitude as a grayscale image.

Combines the x and y Prewitt gradients into sqrt(gx² + gy²) per pixel, then scales the result to fill the 0–255 range. The alpha channel is set to 255.

#
process_batch

fn process_batch(imgs : Array[Image], f : (Image) -> Image) -> Array[Image]

Apply a function to a batch of images.

Returns a new array where f has been applied to each element of imgs in index order.

#
psnr

fn psnr(a : Image, b : Image) -> Double

Peak signal-to-noise ratio in dB. Returns a large value for identical input.

#
random_brightness

fn random_brightness(img : Image, max_factor : Double) -> Image

Random brightness adjustment within [1-max_factor, 1+max_factor].

Multiplies each RGB channel by a factor sampled uniformly from the symmetric range around 1.0.

#
random_color_jitter

fn random_color_jitter(img : Image, brightness : Double, contrast : Double, saturation : Double, hue : Double) -> Image

Random colour jitter (brightness, contrast, saturation, hue).

Applies brightness and contrast jitter (multiplicative, via random_brightness / random_contrast) when their magnitudes are positive. Saturation jitter scales the HSV S channel by a factor sampled from [1-saturation, 1+saturation] (clamped to [0, 1]). Hue jitter shifts the H channel by a uniform sample in [-hue, hue] degrees (wrapped to [0, 360)). The HSV round-trip is done once when either saturation or hue is requested.

#
random_contrast

fn random_contrast(img : Image, max_factor : Double) -> Image

Random contrast adjustment within [1-max_factor, 1+max_factor].

Scales pixel deviations from the image mean by a factor sampled uniformly from the symmetric range around 1.0.

#
random_crop

fn random_crop(img : Image, h : Int, w : Int) -> Image raise

Random crop to target size.

Picks a random top-left corner so the h x w window fits inside the image. Returns a clone of the input when it already fits within h x w.

#
random_flip_horizontal

fn random_flip_horizontal(img : Image) -> Image

Random horizontal flip (50% probability).

Returns a mirrored copy with probability 0.5, otherwise a clone of the input. Each call draws a fresh random sample.

#
random_gamma

fn random_gamma(img : Image, max_gamma : Double) -> Image

Random gamma within [1/max_gamma, max_gamma].

Samples the exponent log-uniformly so low and high gamma are equally likely, then applies adjust_gamma.

#
random_noise_gaussian

fn random_noise_gaussian(img : Image, std : Double) -> Image

Add Gaussian noise with the given standard deviation.

Adds independent zero-mean Gaussian noise (via Box-Muller) of standard deviation std to each RGB channel; alpha is preserved.

#
random_noise_salt_pepper

fn random_noise_salt_pepper(img : Image, prob : Double) -> Image

Add salt-and-pepper noise with the given probability.

Each pixel is set to black with probability prob / 2 and to white with probability prob / 2; otherwise it is left unchanged. Only RGB channels are affected; alpha is preserved.

#
random_rotate

fn random_rotate(img : Image, max_angle : Double) -> Image

Random rotation within [-max_angle, max_angle] degrees.

Samples an angle uniformly from the symmetric range and rotates with bilinear sampling.

#
regionprops

fn regionprops(labels : Array[Array[Int]], num_labels : Int) -> Array[RegionProps]

Compute region properties for each label (1..num_labels).

labels is a 2D label array (e.g. from connected_components) and num_labels the corresponding label count. Returns one RegionProps per label in label order; regions with zero area keep their initial zeroed centroid/bbox.

#
register_decoder

fn register_decoder(ext : String, decoder : &Decoder) -> Unit

Register a decoder for a format extension (e.g. "ppm").

Subsequent calls to decode(data, ext) dispatch to decoder. Re-registering an extension replaces the previous entry.

#
register_encoder

fn register_encoder(ext : String, encoder : &Encoder) -> Unit

Register an encoder for a format extension (e.g. "ppm").

Subsequent calls to encode(img, ext) dispatch to encoder. Re-registering an extension replaces the previous entry.

#
rescale

fn rescale(img : Image, scale : Double, interp : Interp) -> Image

Scale both dimensions by a uniform factor.

  • scale: scaling factor (e.g. 0.5 halves, 2.0 doubles).
  • interp: resampling method (Nearest, Bilinear, or Bicubic).

Output dimensions are round(h * scale) x round(w * scale).

#
resize

fn resize(img : Image, dst_h : Int, dst_w : Int, interp : Interp) -> Image

Resize the image to (dst_h, dst_w) using the given interpolation.

  • dst_h, dst_w: target dimensions in pixels.
  • interp: resampling method (Nearest, Bilinear, or Bicubic).

#
resize_to_cover

fn resize_to_cover(img : Image, min_h : Int, min_w : Int, interp : Interp) -> Image raise ImageError

Scale to cover min_h x min_w, preserving aspect ratio, cropping overflow.

  • min_h, min_w: target dimensions to fully cover.
  • interp: resampling method (Nearest, Bilinear, or Bicubic).

The image is scaled then center-cropped to (min_h, min_w). Returns a clone if the image is empty.

#
resize_to_fit

fn resize_to_fit(img : Image, max_h : Int, max_w : Int, interp : Interp) -> Image

Scale to fit inside max_h x max_w, preserving aspect ratio.

  • max_h, max_w: maximum bounding dimensions.
  • interp: resampling method (Nearest, Bilinear, or Bicubic).

The result fits entirely within the box and at least one dimension equals its bound. Returns a clone if the image is empty.

#
rotate_180

fn rotate_180(img : Image) -> Image

Rotate 180 degrees.

#
rotate_270

fn rotate_270(img : Image) -> Image

Rotate 270 degrees clockwise (90 counter-clockwise).

#
rotate_90

fn rotate_90(img : Image) -> Image

Rotate 90 degrees clockwise.

#
rotate_any

fn rotate_any(img : Image, angle : Double, interp : Interp) -> Image

Rotate the image by an arbitrary angle in degrees, clockwise.

  • angle: rotation angle in degrees (positive = clockwise).
  • interp: interpolation method (Nearest or Bilinear).

The output canvas expands to fit the entire rotated image; areas outside the source are filled as opaque black (0, 0, 0, 255).

#
round_byte

fn round_byte(v : Double) -> Byte

Round a double and clamp into a byte range [0, 255].

#
scharr

fn scharr(img : Image) -> Image

Scharr edge magnitude as a grayscale image.

Combines the x and y Scharr gradients into sqrt(gx² + gy²) per pixel, then scales the result to fill the 0–255 range. The Scharr kernel is more rotationally symmetric than Sobel, giving more accurate gradients. The alpha channel is set to 255.

#
sharpen

fn sharpen(img : Image, strength : Double) -> Image

Laplacian-based sharpening. strength scales the high-frequency boost; 0.0 returns the input unchanged.

#
shear

fn shear(img : Image, sh_x : Double, sh_y : Double, interp : Interp, mode? : BorderMode) -> Image

Shear the image.

  • sh_x: horizontal shear factor (x shifts by sh_x * y).
  • sh_y: vertical shear factor (y shifts by sh_y * x).
  • interp: interpolation method (Nearest or Bilinear).
  • mode: border handling for samples outside the source (default Constant(0, 0, 0, 255)).

The output canvas expands to hold the sheared image.

#
skeletonize

fn skeletonize(img : Image) -> Image

Skeletonize via iterative thinning (Zhang-Suen). Works on binary images (non-zero pixels are foreground).

Iteratively removes boundary pixels over two sub-iterations until no further pixels can be thinned, yielding a one-pixel-wide medial axis. Border pixels are left untouched.

#
sobel

fn sobel(img : Image) -> Image

Sobel edge magnitude as a grayscale image.

Combines the x and y Sobel gradients into sqrt(gx² + gy²) per pixel, then scales the result to fill the 0–255 range. The alpha channel is set to 255.

#
sobel_x

fn sobel_x(img : Image) -> Array[Array[Float]]

Compute the Sobel horizontal (x-direction) gradient response.

Convolves the image's luma field with the 3×3 Sobel-x kernel using Reflect border handling.

Returns an h × w array of per-pixel gradient values as Float.

#
sobel_y

fn sobel_y(img : Image) -> Array[Array[Float]]

Compute the Sobel vertical (y-direction) gradient response.

Convolves the image's luma field with the 3×3 Sobel-y kernel using Reflect border handling.

Returns an h × w array of per-pixel gradient values as Float.

#
ssim

fn ssim(a : Image, b : Image) -> Double

Structural Similarity Index (SSIM) between two images. Returns a value in [-1, 1] where 1 means identical.

#
standardize

fn standardize(img : Image) -> Array[Array[Double]]

Standardize the image to zero mean and unit standard deviation.

Computes the mean and standard deviation of the per-pixel luma, then returns (luma - mean) / std for each pixel. A zero-variance image uses std = 1.0 to avoid division by zero.

Returns an h × w array of Double values.

#
threshold

fn threshold(img : Image, thresh : Byte) -> Image

Binarize by luminance: pixels with luma >= thresh become white.

Each pixel's BT.601 luma is compared against thresh; foreground pixels are set to white (255, 255, 255) and background pixels to black. The alpha channel is preserved.

#
threshold_inv

fn threshold_inv(img : Image, thresh : Byte) -> Image

Inverse binarization: pixels with luma >= thresh become black.

The inverse of threshold: foreground pixels (luma >= thresh) are set to black and background pixels to white. The alpha channel is preserved.

#
threshold_otsu

fn threshold_otsu(img : Image) -> (Byte, Image)

Otsu adaptive binarization. Returns the chosen threshold and the result.

Selects the threshold that maximizes the inter-class variance of the luminance histogram and applies threshold. The returned Byte is the applied cut (one above the background class) so values >= it are foreground.

#
threshold_sauvola

fn threshold_sauvola(img : Image, window_size : Int, k : Double) -> Image

Sauvola local thresholding. Computes a threshold per pixel based on the local mean and standard deviation within a window_size × window_size window. Suitable for images with uneven illumination.

The per-pixel threshold is mean * (1 + k * (std / 128 - 1)). Border coordinates are clamped (replicate). window_size should be odd. Returns a binary image; alpha is preserved.

Uses integral images for O(1) window statistics (O(n) total instead of O(n * window_size²)).

#
thumbnail

fn thumbnail(img : Image, max_h : Int, max_w : Int) -> Image

Downscale to fit within max_h x max_w (nearest-neighbor).

#
tint

fn tint(img : Image, r : Byte, g : Byte, b : Byte) -> Image

Multiplicative color tint: each channel scaled by color / 255.

  • r, g, b: tint color; (255, 255, 255) leaves the image unchanged.

#
to_bgr

fn to_bgr(img : Image) -> Image

Swap the red and blue channels.

#
to_display_bytes

fn to_display_bytes(img : Image) -> Array[Byte]

RGB-only byte buffer (alpha dropped), length h * w * 3.

#
to_grayscale

fn to_grayscale(img : Image) -> Image

ITU-R BT.601 grayscale: (R*77 + G*150 + B*29) >> 8, alpha preserved.

#
to_grayscale_weighted

fn to_grayscale_weighted(img : Image, r_w : Double, g_w : Double, b_w : Double) -> Image

Grayscale with custom linear weights, alpha preserved.

  • r_w, g_w, b_w: per-channel weights (typically summing to 1.0).

#
to_hsv

fn to_hsv(img : Image) -> (Array[Array[Double]], Array[Array[Double]], Array[Array[Double]])

RGB → HSV. Returns three h × w arrays: H in [0, 360), S in [0, 1], V in [0, 1].

#
to_pgm

fn to_pgm(img : Image) -> Array[Byte]

Encode as a binary PGM (P5) grayscale byte stream.

#
to_ppm

fn to_ppm(img : Image) -> Array[Byte]

Encode as a binary PPM (P6) byte stream.

#
to_rgb

fn to_rgb(img : Image) -> Image

Drop the alpha channel, returning an RGB-only image (A=255 forced).

#
to_rgba

fn to_rgba(img : Image) -> Image

Ensure the image has an alpha channel (A=255 if missing). Since all millow images are RGBA8, this is a no-op that forces A=255.

#
to_ycbcr

fn to_ycbcr(img : Image) -> (Array[Array[Double]], Array[Array[Double]], Array[Array[Double]])

RGB → YCbCr (ITU-R BT.601). Returns three h × w arrays (Y, Cb, Cr). Y is in [0, 255]; Cb and Cr are centred at 128.

#
top_hat

fn top_hat(img : Image, kernel : Kernel) -> Image

Top-hat: original minus opening.

Isolates small bright structures smaller than kernel that are removed by the opening.

#
translate

fn translate(img : Image, dy : Double, dx : Double, interp : Interp) -> Image

Translate the image by (dy, dx) pixels.

  • dy: vertical shift in pixels (positive = downward).
  • dx: horizontal shift in pixels (positive = rightward).
  • interp: interpolation method (Nearest or Bilinear).

The output keeps the source dimensions; areas uncovered by the source become opaque black (0, 0, 0, 255).

#
unsharp_mask

fn unsharp_mask(img : Image, radius : Double, amount : Double, threshold : Byte) -> Image

Unsharp masking: add a scaled high-pass component above a threshold.

  • radius: Gaussian blur radius used to derive the low-pass component.
  • amount: scaling factor applied to the high-pass residual.
  • threshold: only pixels whose |orig - blurred|threshold are boosted; smaller differences are passed through unchanged.