mod raw;
#[cfg(feature = "png")]
mod png;
use crate::{Canvas, Pixel};
#[derive(Debug, Clone, Copy)]
pub struct Window {
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum ImageFormat {
RawGamma8Bpp,
RawLinear10BppLE,
RawLinear12BppLE,
PngGamma8Bpp,
PngLinear16Bpp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EncoderError {
NotImplemented,
BrokenWindow,
InvalidSubsamplingRate,
}
pub struct WindowSpans<'a> {
canvas: &'a Canvas,
window: Window,
scanline: u32,
}
impl<'a> Iterator for WindowSpans<'a> {
type Item = &'a [Pixel];
fn next(&mut self) -> Option<Self::Item> {
if self.scanline >= self.window.y + self.window.h {
return None;
}
let base = (self.canvas.width * self.scanline + self.window.x) as usize;
let end = base + self.window.w as usize;
self.scanline += 1;
Some(&self.canvas.pixbuf[base..end])
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = (self.window.y + self.window.h - self.scanline) as usize;
(size, Some(size))
}
}
impl<'a> ExactSizeIterator for WindowSpans<'a> {}
impl From<((u32, u32), (u32, u32))> for Window {
fn from(tuple: ((u32, u32), (u32, u32))) -> Self {
let ((x, y), (w, h)) = tuple;
Window { x, y, w, h }
}
}
impl std::fmt::Display for Window {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "({}, {})+({}, {})", self.x, self.y, self.w, self.h)
}
}
impl std::fmt::Display for EncoderError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for EncoderError {}
impl Window {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
Window {
x: 0,
y: 0,
w: width,
h: height,
}
}
#[must_use]
pub fn at(&self, x: u32, y: u32) -> Window {
let w = self.w;
let h = self.h;
Window { x, y, w, h }
}
#[must_use]
fn is_inside(&self, width: u32, height: u32) -> bool {
self.x + self.w <= width && self.y + self.h <= height
}
#[must_use]
fn len(&self) -> usize {
(self.w * self.h) as usize
}
}
const MAX_SUBSAMPLING_RATE: u32 = 16;
fn validate_subsampling_rate(factors: (u32, u32)) -> Result<(), EncoderError> {
let is_valid = |x| x > 0 && x <= MAX_SUBSAMPLING_RATE;
if is_valid(factors.0) && is_valid(factors.1) {
Ok(())
} else {
Err(EncoderError::InvalidSubsamplingRate)
}
}
impl Canvas {
#[must_use]
pub fn window_spans(&self, window: Window) -> Option<WindowSpans<'_>> {
if !window.is_inside(self.width, self.height) {
return None;
}
let canvas = self;
let scanline = window.y;
let iter = WindowSpans {
canvas,
window,
scanline,
};
Some(iter)
}
#[cfg(not(feature = "png"))]
pub fn export_image(&self, format: ImageFormat) -> Result<Vec<u8>, EncoderError> {
let window = Window::new(self.width, self.height);
match format {
ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
_ => Err(EncoderError::NotImplemented),
}
}
#[cfg(not(feature = "png"))]
pub fn export_window_image(
&self,
window: Window,
format: ImageFormat,
) -> Result<Vec<u8>, EncoderError> {
if !window.is_inside(self.width, self.height) {
return Err(EncoderError::BrokenWindow);
}
match format {
ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
_ => Err(EncoderError::NotImplemented),
}
}
#[cfg(not(feature = "png"))]
pub fn export_subsampled_image(
&self,
factors: (u32, u32),
format: ImageFormat,
) -> Result<Vec<u8>, EncoderError> {
validate_subsampling_rate(factors)?;
match format {
ImageFormat::RawGamma8Bpp => self.export_sub_raw8bpp(factors),
ImageFormat::RawLinear10BppLE => self.export_sub_raw1xbpp::<10>(factors),
ImageFormat::RawLinear12BppLE => self.export_sub_raw1xbpp::<12>(factors),
_ => Err(EncoderError::NotImplemented),
}
}
#[cfg(feature = "png")]
pub fn export_image(&self, format: ImageFormat) -> Result<Vec<u8>, EncoderError> {
let window = Window::new(self.width, self.height);
match format {
ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
ImageFormat::PngGamma8Bpp => self.export_png8bpp(window),
ImageFormat::PngLinear16Bpp => self.export_png16bpp(window),
}
}
#[cfg(feature = "png")]
pub fn export_window_image(
&self,
window: Window,
format: ImageFormat,
) -> Result<Vec<u8>, EncoderError> {
if !window.is_inside(self.width, self.height) {
return Err(EncoderError::BrokenWindow);
}
match format {
ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
ImageFormat::PngGamma8Bpp => self.export_png8bpp(window),
ImageFormat::PngLinear16Bpp => self.export_png16bpp(window),
}
}
#[cfg(feature = "png")]
pub fn export_subsampled_image(
&self,
factors: (u32, u32),
format: ImageFormat,
) -> Result<Vec<u8>, EncoderError> {
validate_subsampling_rate(factors)?;
match format {
ImageFormat::RawGamma8Bpp => self.export_sub_raw8bpp(factors),
ImageFormat::RawLinear10BppLE => self.export_sub_raw1xbpp::<10>(factors),
ImageFormat::RawLinear12BppLE => self.export_sub_raw1xbpp::<12>(factors),
ImageFormat::PngGamma8Bpp => self.export_sub_png8bpp(factors),
ImageFormat::PngLinear16Bpp => self.export_sub_png16bpp(factors),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SpotShape;
#[cfg(not(feature = "png"))]
#[test]
fn image_format_error() {
let c = Canvas::new(0, 0);
assert_eq!(
c.export_image(ImageFormat::PngGamma8Bpp),
Err(EncoderError::NotImplemented)
);
}
#[test]
fn broken_window_error() {
let c = Canvas::new(10, 10);
let wnd = Window::new(8, 8).at(5, 5);
assert_eq!(
c.export_window_image(wnd, ImageFormat::RawGamma8Bpp),
Err(EncoderError::BrokenWindow)
);
}
#[test]
fn subsampling_rate_error() {
let c = Canvas::new(0, 0);
assert_eq!(
c.export_subsampled_image((1, 0), ImageFormat::RawGamma8Bpp),
Err(EncoderError::InvalidSubsamplingRate)
);
assert_eq!(
c.export_subsampled_image((0, 1), ImageFormat::RawLinear10BppLE),
Err(EncoderError::InvalidSubsamplingRate)
);
assert_eq!(
c.export_subsampled_image((4, 17), ImageFormat::RawLinear12BppLE),
Err(EncoderError::InvalidSubsamplingRate)
);
}
#[test]
fn window_ops() {
let wnd = Window::new(128, 64).at(200, 100);
assert_eq!(wnd.len(), 128 * 64);
assert!(wnd.is_inside(400, 500));
assert!(!wnd.is_inside(100, 100));
assert!(!wnd.at(300, 100).is_inside(400, 500));
}
#[test]
fn get_window_spans() {
let mut c = Canvas::new(100, 100);
c.add_spot((50.75, 50.5), SpotShape::default(), 1.0);
c.draw();
let wnd1 = Window::new(4, 3).at(50, 50);
let mut vec = Vec::new();
for span in c.window_spans(wnd1).unwrap() {
vec.extend_from_slice(span);
}
assert_eq!(
vec,
[542, 18087, 1146, 0, 542, 18087, 1146, 0, 193, 731, 0, 0]
);
let wnd2 = wnd1.at(48, 51);
vec.clear();
for span in c.window_spans(wnd2).unwrap() {
vec.extend_from_slice(span);
}
assert_eq!(vec, [0, 0, 542, 18087, 0, 0, 193, 731, 0, 0, 0, 0]);
}
#[test]
fn broken_windows() {
let c = Canvas::new(100, 100);
let wnd1 = Window::new(4, 100).at(50, 50);
assert!(c.window_spans(wnd1).is_none());
let wnd2 = Window::new(4, 5).at(100, 100);
assert!(c.window_spans(wnd2).is_none());
let wnd3 = Window::new(1, 1).at(100, 100);
assert!(c.window_spans(wnd3).is_none());
let wnd4 = Window::new(0, 0).at(100, 100);
let mut spans = c.window_spans(wnd4).unwrap();
assert_eq!(spans.len(), 0);
assert!(spans.next().is_none());
}
}