use core::ops::RangeInclusive;
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error)]
pub enum ValidationError {
#[error("distance {value} out of valid range {valid:?}")]
DistanceOutOfRange {
value: f32,
valid: RangeInclusive<f32>,
},
#[error("distance must be finite, got {value}")]
DistanceNotFinite { value: f32 },
#[error("effort {value} out of valid range {valid:?}")]
EffortOutOfRange {
value: u8,
valid: RangeInclusive<u8>,
},
#[error("{name} iter count {value} out of valid range {valid:?}")]
IterCountOutOfRange {
name: &'static str,
value: u32,
valid: RangeInclusive<u32>,
},
#[error("mutually exclusive quality loops: {first} and {second} both have nonzero iter count")]
QualityLoopMutuallyExclusive {
first: &'static str,
second: &'static str,
},
#[error("fine_grained_step {value} out of valid range {valid:?}")]
FineGrainedStepOutOfRange {
value: u8,
valid: RangeInclusive<u8>,
},
#[error("k_info_loss_mul_base {value} must be finite and > 0.0")]
KInfoLossMulBaseInvalid { value: f32 },
#[error("k_ac_quant {value} must be finite and > 0.0")]
KAcQuantInvalid { value: f32 },
#[error("nb_rcts_to_try {value} out of valid range {valid:?}")]
NbRctsToTryOutOfRange {
value: u8,
valid: RangeInclusive<u8>,
},
#[error("wp_num_param_sets {value} out of valid range {valid:?}")]
WpNumParamSetsOutOfRange {
value: u8,
valid: RangeInclusive<u8>,
},
#[error("tree_max_buckets must be > 0, got 0")]
TreeMaxBucketsZero,
#[error("tree_num_properties {value} out of valid range {valid:?}")]
TreeNumPropertiesOutOfRange {
value: u8,
valid: RangeInclusive<u8>,
},
#[error("tree_threshold_base {value} must be finite and >= 0.0")]
TreeThresholdBaseInvalid { value: f32 },
#[error("tree_sample_fraction {value} out of valid range {valid:?}")]
TreeSampleFractionOutOfRange {
value: f32,
valid: RangeInclusive<f32>,
},
}
pub(crate) const DISTANCE_MAX: f32 = 25.0;
pub(crate) const EFFORT_RANGE: RangeInclusive<u8> = 1..=10;
#[cfg(any(
feature = "butteraugli-loop",
feature = "ssim2-loop",
feature = "zensim-loop"
))]
pub(crate) const ITER_MAX: u32 = 16;
#[cfg(feature = "__expert")]
pub(crate) const FINE_GRAINED_STEP_RANGE: RangeInclusive<u8> = 1..=8;
#[cfg(feature = "__expert")]
pub(crate) const NB_RCTS_RANGE: RangeInclusive<u8> = 0..=19;
#[cfg(feature = "__expert")]
pub(crate) const WP_NUM_PARAM_SETS_RANGE: RangeInclusive<u8> = 0..=5;
#[cfg(feature = "__expert")]
pub(crate) const TREE_NUM_PROPERTIES_RANGE: RangeInclusive<u8> = 0..=16;
#[cfg(feature = "__expert")]
pub(crate) const TREE_SAMPLE_FRACTION_RANGE: RangeInclusive<f32> = 0.0..=1.0;
#[inline]
fn check_effort(effort: u8) -> Result<(), ValidationError> {
if EFFORT_RANGE.contains(&effort) {
Ok(())
} else {
Err(ValidationError::EffortOutOfRange {
value: effort,
valid: EFFORT_RANGE,
})
}
}
#[cfg(any(
feature = "butteraugli-loop",
feature = "ssim2-loop",
feature = "zensim-loop"
))]
#[inline]
fn check_iter(name: &'static str, value: u32) -> Result<(), ValidationError> {
let valid = 0..=ITER_MAX;
if valid.contains(&value) {
Ok(())
} else {
Err(ValidationError::IterCountOutOfRange { name, value, valid })
}
}
#[cfg(feature = "__expert")]
pub(crate) fn validate_lossy_profile_overrides(
profile: &crate::effort::EffortProfile,
) -> Result<(), ValidationError> {
if !FINE_GRAINED_STEP_RANGE.contains(&profile.fine_grained_step) {
return Err(ValidationError::FineGrainedStepOutOfRange {
value: profile.fine_grained_step,
valid: FINE_GRAINED_STEP_RANGE,
});
}
if !profile.k_info_loss_mul_base.is_finite() || profile.k_info_loss_mul_base <= 0.0 {
return Err(ValidationError::KInfoLossMulBaseInvalid {
value: profile.k_info_loss_mul_base,
});
}
if !profile.k_ac_quant.is_finite() || profile.k_ac_quant <= 0.0 {
return Err(ValidationError::KAcQuantInvalid {
value: profile.k_ac_quant,
});
}
Ok(())
}
#[cfg(feature = "__expert")]
pub(crate) fn validate_lossless_profile_overrides(
profile: &crate::effort::EffortProfile,
) -> Result<(), ValidationError> {
if !NB_RCTS_RANGE.contains(&profile.nb_rcts_to_try) {
return Err(ValidationError::NbRctsToTryOutOfRange {
value: profile.nb_rcts_to_try,
valid: NB_RCTS_RANGE,
});
}
if !WP_NUM_PARAM_SETS_RANGE.contains(&profile.wp_num_param_sets) {
return Err(ValidationError::WpNumParamSetsOutOfRange {
value: profile.wp_num_param_sets,
valid: WP_NUM_PARAM_SETS_RANGE,
});
}
if profile.tree_max_buckets == 0 {
return Err(ValidationError::TreeMaxBucketsZero);
}
if !TREE_NUM_PROPERTIES_RANGE.contains(&profile.tree_num_properties) {
return Err(ValidationError::TreeNumPropertiesOutOfRange {
value: profile.tree_num_properties,
valid: TREE_NUM_PROPERTIES_RANGE,
});
}
if !profile.tree_threshold_base.is_finite() || profile.tree_threshold_base < 0.0 {
return Err(ValidationError::TreeThresholdBaseInvalid {
value: profile.tree_threshold_base,
});
}
if !profile.tree_sample_fraction.is_finite()
|| !TREE_SAMPLE_FRACTION_RANGE.contains(&profile.tree_sample_fraction)
{
return Err(ValidationError::TreeSampleFractionOutOfRange {
value: profile.tree_sample_fraction,
valid: TREE_SAMPLE_FRACTION_RANGE,
});
}
Ok(())
}
impl crate::api::LossyConfig {
pub fn validate(&self) -> Result<(), ValidationError> {
let d = self.distance();
if !d.is_finite() {
return Err(ValidationError::DistanceNotFinite { value: d });
}
if d <= 0.0 || d > DISTANCE_MAX {
return Err(ValidationError::DistanceOutOfRange {
value: d,
valid: 0.0..=DISTANCE_MAX,
});
}
check_effort(self.effort())?;
#[cfg(feature = "butteraugli-loop")]
let bi = self.butteraugli_iters();
#[cfg(not(feature = "butteraugli-loop"))]
let bi = 0u32;
#[cfg(feature = "butteraugli-loop")]
check_iter("butteraugli_iters", bi)?;
#[cfg(feature = "ssim2-loop")]
let si = self.ssim2_iters_value();
#[cfg(not(feature = "ssim2-loop"))]
let si = 0u32;
#[cfg(feature = "ssim2-loop")]
check_iter("ssim2_iters", si)?;
#[cfg(feature = "zensim-loop")]
let zi = self.zensim_iters_value();
#[cfg(not(feature = "zensim-loop"))]
let zi = 0u32;
#[cfg(feature = "zensim-loop")]
check_iter("zensim_iters", zi)?;
let active: &[(&'static str, u32)] = &[
("butteraugli_iters", bi),
("ssim2_iters", si),
("zensim_iters", zi),
];
let mut first_active: Option<&'static str> = None;
for &(name, val) in active {
if val > 0 {
if let Some(prev) = first_active {
return Err(ValidationError::QualityLoopMutuallyExclusive {
first: prev,
second: name,
});
}
first_active = Some(name);
}
}
#[cfg(feature = "__expert")]
if let Some(profile) = self.profile_override_ref() {
validate_lossy_profile_overrides(profile)?;
}
Ok(())
}
}
impl crate::api::LosslessConfig {
pub fn validate(&self) -> Result<(), ValidationError> {
check_effort(self.effort())?;
#[cfg(feature = "__expert")]
if let Some(profile) = self.profile_override_ref() {
validate_lossless_profile_overrides(profile)?;
}
Ok(())
}
}
#[cfg(feature = "__expert")]
impl crate::effort::LossyInternalParams {
pub fn validate(&self) -> Result<(), ValidationError> {
if let Some(step) = self.fine_grained_step
&& !FINE_GRAINED_STEP_RANGE.contains(&step)
{
return Err(ValidationError::FineGrainedStepOutOfRange {
value: step,
valid: FINE_GRAINED_STEP_RANGE,
});
}
if let Some(v) = self.k_info_loss_mul_base
&& (!v.is_finite() || v <= 0.0)
{
return Err(ValidationError::KInfoLossMulBaseInvalid { value: v });
}
if let Some(v) = self.k_ac_quant
&& (!v.is_finite() || v <= 0.0)
{
return Err(ValidationError::KAcQuantInvalid { value: v });
}
Ok(())
}
}
#[cfg(feature = "__expert")]
impl crate::effort::LosslessInternalParams {
pub fn validate(&self) -> Result<(), ValidationError> {
if let Some(v) = self.nb_rcts_to_try
&& !NB_RCTS_RANGE.contains(&v)
{
return Err(ValidationError::NbRctsToTryOutOfRange {
value: v,
valid: NB_RCTS_RANGE,
});
}
if let Some(v) = self.wp_num_param_sets
&& !WP_NUM_PARAM_SETS_RANGE.contains(&v)
{
return Err(ValidationError::WpNumParamSetsOutOfRange {
value: v,
valid: WP_NUM_PARAM_SETS_RANGE,
});
}
if let Some(0) = self.tree_max_buckets {
return Err(ValidationError::TreeMaxBucketsZero);
}
if let Some(v) = self.tree_num_properties
&& !TREE_NUM_PROPERTIES_RANGE.contains(&v)
{
return Err(ValidationError::TreeNumPropertiesOutOfRange {
value: v,
valid: TREE_NUM_PROPERTIES_RANGE,
});
}
if let Some(v) = self.tree_threshold_base
&& (!v.is_finite() || v < 0.0)
{
return Err(ValidationError::TreeThresholdBaseInvalid { value: v });
}
if let Some(v) = self.tree_sample_fraction
&& (!v.is_finite() || !TREE_SAMPLE_FRACTION_RANGE.contains(&v))
{
return Err(ValidationError::TreeSampleFractionOutOfRange {
value: v,
valid: TREE_SAMPLE_FRACTION_RANGE,
});
}
Ok(())
}
}