///|
/// Clamp a numeric field to the smallest valid camera value.
fn min_one(value : Int) -> Int {
if value < 1 {
1
} else {
value
}
}
///|
/// Normalize a camera capture mode into a safe baseline.
///
/// Width, height, and frames per second are clamped to at least `1`, while the
/// pixel format is preserved. This keeps downstream frame timing and layout
/// calculations from dividing by zero or producing negative sizes.
///
/// # Example
/// ```mbt check
/// test {
/// let mode = @camera.CaptureMode::{
/// width: 0,
/// height: -720,
/// fps: 0,
/// pixel_format: Nv12,
/// }.normalize()
/// inspect(mode.width, content="1")
/// inspect(mode.height, content="1")
/// inspect(mode.fps, content="1")
/// }
/// ```
pub fn CaptureMode::normalize(self : CaptureMode) -> CaptureMode {
{
width: min_one(self.width),
height: min_one(self.height),
fps: min_one(self.fps),
pixel_format: self.pixel_format,
}
}
///|
/// Return the capture interval in milliseconds for one frame.
///
/// The mode is normalized before the calculation, so an invalid `fps` value is
/// treated as `1`. The result uses integer milliseconds because native preview
/// schedulers usually accept whole millisecond delays.
///
/// # Example
/// ```mbt check
/// test {
/// let mode = @camera.CaptureMode::{
/// width: 1280,
/// height: 720,
/// fps: 25,
/// pixel_format: Mjpeg,
/// }
/// inspect(mode.frame_interval_ms(), content="40")
/// }
/// ```
pub fn CaptureMode::frame_interval_ms(self : CaptureMode) -> Int {
1_000 / self.normalize().fps
}
///|
/// Return the stable label for camera placement.
///
/// The label is lowercase and intended for UI copy, logs, and serialized
/// metadata where the enum constructor name would be too MoonBit-specific.
/// Values are stable across releases unless the enum itself changes.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(@camera.Facing::Back.name(), content="back")
/// }
/// ```
pub fn Facing::name(self : Facing) -> String {
match self {
Front => "front"
Back => "back"
External => "external"
}
}
///|
/// Return the stable label for a pixel format.
///
/// The label is lowercase and mirrors common native camera terminology such as
/// `mjpeg`, `yuy2`, and `nv12`. Use it when printing modes or exposing device
/// metadata to configuration files.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(@camera.PixelFormat::Nv12.name(), content="nv12")
/// }
/// ```
pub fn PixelFormat::name(self : PixelFormat) -> String {
match self {
Mjpeg => "mjpeg"
Yuy2 => "yuy2"
Nv12 => "nv12"
Rgba => "rgba"
}
}
///|
/// Whether the device default mode should be treated as HD.
///
/// This checks the normalized default mode and returns `true` for devices whose
/// default capture dimensions are at least `1280x720`.
///
/// # Example
/// ```mbt check
/// test {
/// let device = @camera.CameraDevice::{
/// id: "camera-0",
/// name: "HD Camera",
/// facing: External,
/// mode: { width: 1280, height: 720, fps: 30, pixel_format: Mjpeg },
/// torch: false,
/// }
/// inspect(device.is_high_definition(), content="true")
/// }
/// ```
pub fn CameraDevice::is_high_definition(self : CameraDevice) -> Bool {
let mode = self.mode.normalize()
mode.width >= 1_280 && mode.height >= 720
}
///|
/// Whether the default mode is suitable for low-latency preview.
///
/// A device is considered low-latency friendly when its normalized default mode
/// runs at `60` frames per second or above.
///
/// # Example
/// ```mbt check
/// test {
/// let device = @camera.CameraDevice::{
/// id: "camera-0",
/// name: "Fast Camera",
/// facing: External,
/// mode: { width: 640, height: 480, fps: 60, pixel_format: Yuy2 },
/// torch: false,
/// }
/// inspect(device.supports_low_latency_preview(), content="true")
/// }
/// ```
pub fn CameraDevice::supports_low_latency_preview(self : CameraDevice) -> Bool {
self.mode.normalize().fps >= 60
}
///|
/// Produce a concise device label for camera pickers.
///
/// The label uses `facing:name` so UI code can display predictable values while
/// still preserving the native device name exactly as reported.
///
/// # Example
/// ```mbt check
/// test {
/// let device = @camera.CameraDevice::{
/// id: "camera-0",
/// name: "USB Camera",
/// facing: External,
/// mode: { width: 1280, height: 720, fps: 30, pixel_format: Mjpeg },
/// torch: false,
/// }
/// inspect(device.label(), content="external:USB Camera")
/// }
/// ```
pub fn CameraDevice::label(self : CameraDevice) -> String {
"\{self.facing.name()}:\{self.name}"
}