use crate::error::ImageError;
use crate::ramp::{Ramp, RampType, validate_ramp};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LuminanceMethod {
Average,
#[default]
Bt601,
Bt709,
}
impl LuminanceMethod {
#[inline]
pub fn calculate(&self, r: u8, g: u8, b: u8) -> f32 {
let r = r as f32 / 255.0;
let g = g as f32 / 255.0;
let b = b as f32 / 255.0;
match self {
Self::Average => (r + g + b) / 3.0,
Self::Bt601 => 0.299 * r + 0.587 * g + 0.114 * b,
Self::Bt709 => 0.2126 * r + 0.7152 * g + 0.0722 * b,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Quality {
Good,
#[default]
Better,
Best,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputSizing {
Explicit { width: usize, height: usize },
MaxWidth(usize),
}
#[derive(Debug)]
pub struct Config {
pub ramp: RampType,
pub luminance_method: LuminanceMethod,
pub quality: Quality,
pub aspect_ratio_correction: f32,
pub output_sizing: OutputSizing,
pub parallel_threshold: usize,
}
impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
}
#[derive(Debug)]
pub struct ConfigBuilder {
ramp: Option<RampType>,
luminance_method: LuminanceMethod,
quality: Quality,
aspect_ratio_correction: f32,
output_sizing: OutputSizing,
parallel_threshold: usize,
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self {
ramp: Some(RampType::Dark(Ramp::ascii())),
luminance_method: LuminanceMethod::default(),
quality: Quality::default(),
aspect_ratio_correction: 0.5, output_sizing: OutputSizing::MaxWidth(80),
parallel_threshold: 4096, }
}
}
impl ConfigBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn ramp(mut self, ramp: RampType) -> Self {
self.ramp = Some(ramp);
self
}
pub fn luminance_method(mut self, method: LuminanceMethod) -> Self {
self.luminance_method = method;
self
}
pub fn quality(mut self, quality: Quality) -> Self {
self.quality = quality;
self
}
pub fn aspect_ratio_correction(mut self, factor: f32) -> Self {
self.aspect_ratio_correction = factor;
self
}
pub fn output_sizing(mut self, sizing: OutputSizing) -> Self {
self.output_sizing = sizing;
self
}
pub fn max_width(mut self, max_width: usize) -> Self {
self.output_sizing = OutputSizing::MaxWidth(max_width);
self
}
pub fn explicit_dimensions(mut self, width: usize, height: usize) -> Self {
self.output_sizing = OutputSizing::Explicit { width, height };
self
}
pub fn parallel_threshold(mut self, min_cells: usize) -> Self {
self.parallel_threshold = min_cells;
self
}
pub fn build(self) -> Result<Config, ImageError> {
let ramp_ref = self.ramp.as_ref().map(|r| r.inner());
validate_ramp(ramp_ref)?;
let ramp = self.ramp.ok_or(ImageError::EmptyRamp)?;
if !self.aspect_ratio_correction.is_finite() || self.aspect_ratio_correction <= 0.0 {
return Err(ImageError::InvalidAspectRatio(format!(
"Aspect ratio correction factor must be a positive finite number, got {}",
self.aspect_ratio_correction
)));
}
match self.output_sizing {
OutputSizing::Explicit { width, height } => {
if width == 0 || height == 0 {
return Err(ImageError::ZeroSizedImage(width, height));
}
}
OutputSizing::MaxWidth(width) => {
if width == 0 {
return Err(ImageError::ZeroSizedImage(0, 0));
}
}
}
Ok(Config {
ramp,
luminance_method: self.luminance_method,
quality: self.quality,
aspect_ratio_correction: self.aspect_ratio_correction,
output_sizing: self.output_sizing,
parallel_threshold: self.parallel_threshold,
})
}
}