use crate::api::{LosslessConfig, LossyConfig, PixelLayout};
use crate::effort::{EntropyMulTable, LosslessInternalParams, LossyInternalParams};
fn synthetic_rgb8() -> Vec<u8> {
let mut out = Vec::with_capacity((W * H * 3) as usize);
let mut state: u32 = 0x1357_9BDF;
for y in 0..H {
for x in 0..W {
let (r, g, b) = if y < 64 {
let v = ((x + y) * 255 / (W + 64 - 2)) as u8;
(v, v.wrapping_add(20), v.wrapping_sub(20))
} else if y < 128 {
let bars_g = if (x / 4) % 2 == 0 { 30 } else { 220 };
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let speckle = ((state >> 24) as u8) & 0x3F;
let bx = (((x ^ y) as u8).wrapping_add(speckle)) | 0x10;
(((x as u8) ^ 0x55), bars_g as u8, bx)
} else if y < 192 {
let qx = (x / 32) % 4;
let qy = ((y - 128) / 32) % 2;
let base = (qx as u8) * 50 + (qy as u8) * 30 + 40;
(base, base.wrapping_add(20), base.wrapping_sub(10))
} else {
let lx = x % 8;
let ly = (y - 192) % 8;
let on = lx == 3 || lx == 4 || ly == 3 || ly == 4;
if on { (240, 230, 220) } else { (10, 18, 30) }
};
out.push(r);
out.push(g);
out.push(b);
}
}
out
}
const W: u32 = 256;
const H: u32 = 256;
fn encode_lossy(cfg: &LossyConfig) -> Vec<u8> {
let pixels = synthetic_rgb8();
cfg.clone()
.encode(&pixels, W, H, PixelLayout::Rgb8)
.expect("lossy encode")
}
fn encode_lossless(cfg: &LosslessConfig) -> Vec<u8> {
let pixels = synthetic_rgb8();
cfg.clone()
.encode(&pixels, W, H, PixelLayout::Rgb8)
.expect("lossless encode")
}
fn baseline_lossy() -> LossyConfig {
LossyConfig::new(1.5).with_effort(7).with_threads(1)
}
fn baseline_lossless() -> LosslessConfig {
LosslessConfig::new().with_effort(7).with_threads(1)
}
fn assert_lossy_field_changes_output(field_name: &str, params: LossyInternalParams) {
let cfg_override = baseline_lossy().with_internal_params(params);
let bytes_override = encode_lossy(&cfg_override);
let bytes_baseline = encode_lossy(&baseline_lossy());
assert_eq!(&bytes_override[..2], &crate::JXL_SIGNATURE);
assert_eq!(&bytes_baseline[..2], &crate::JXL_SIGNATURE);
assert_ne!(
bytes_override, bytes_baseline,
"override of {field_name} must change lossy bitstream",
);
}
#[test]
fn lossy_override_try_dct16() {
assert_lossy_field_changes_output(
"try_dct16",
LossyInternalParams {
try_dct16: Some(false),
try_dct32: Some(false),
try_dct64: Some(false),
..Default::default()
},
);
}
#[test]
fn lossy_override_try_dct32() {
assert_lossy_field_changes_output(
"try_dct32",
LossyInternalParams {
try_dct32: Some(false),
try_dct64: Some(false),
..Default::default()
},
);
}
#[test]
fn lossy_override_try_dct64() {
assert_lossy_field_changes_output(
"try_dct64",
LossyInternalParams {
try_dct64: Some(false),
..Default::default()
},
);
}
#[test]
fn lossy_override_try_dct4x8_afv() {
assert_lossy_field_changes_output(
"try_dct4x8_afv",
LossyInternalParams {
try_dct4x8_afv: Some(false),
..Default::default()
},
);
}
#[test]
fn lossy_override_fine_grained_step() {
assert_lossy_field_changes_output(
"fine_grained_step",
LossyInternalParams {
fine_grained_step: Some(1),
..Default::default()
},
);
}
#[test]
fn lossy_override_k_info_loss_mul_base() {
assert_lossy_field_changes_output(
"k_info_loss_mul_base",
LossyInternalParams {
k_info_loss_mul_base: Some(2.0),
..Default::default()
},
);
}
#[test]
fn lossy_override_entropy_mul_dct8() {
let mut table = EntropyMulTable::reference();
table.dct8 = 1.5;
assert_lossy_field_changes_output(
"entropy_mul_table.dct8",
LossyInternalParams {
entropy_mul_table: Some(table),
..Default::default()
},
);
}
#[test]
fn lossy_override_chromacity_adjustment() {
assert_lossy_field_changes_output(
"chromacity_adjustment",
LossyInternalParams {
chromacity_adjustment: Some(false),
..Default::default()
},
);
}
#[test]
fn lossy_override_cfl_two_pass() {
let mut table = EntropyMulTable::reference();
table.dct8 = 0.95;
assert_lossy_field_changes_output(
"cfl_two_pass",
LossyInternalParams {
cfl_two_pass: Some(false),
entropy_mul_table: Some(table),
..Default::default()
},
);
}
#[test]
fn lossy_override_patch_ref_tree_learning() {
assert_lossy_field_changes_output(
"patch_ref_tree_learning",
LossyInternalParams {
patch_ref_tree_learning: Some(true),
k_info_loss_mul_base: Some(1.5),
..Default::default()
},
);
}
fn assert_lossless_field_changes_output(field_name: &str, params: LosslessInternalParams) {
let cfg_override = baseline_lossless().with_internal_params(params);
let bytes_override = encode_lossless(&cfg_override);
let bytes_baseline = encode_lossless(&baseline_lossless());
assert_eq!(&bytes_override[..2], &crate::JXL_SIGNATURE);
assert_eq!(&bytes_baseline[..2], &crate::JXL_SIGNATURE);
assert_ne!(
bytes_override, bytes_baseline,
"override of {field_name} must change lossless bitstream",
);
}
#[test]
fn lossless_override_nb_rcts_to_try() {
assert_lossless_field_changes_output(
"nb_rcts_to_try",
LosslessInternalParams {
nb_rcts_to_try: Some(0),
..Default::default()
},
);
}
#[test]
fn lossless_override_wp_num_param_sets() {
assert_lossless_field_changes_output(
"wp_num_param_sets",
LosslessInternalParams {
wp_num_param_sets: Some(5),
..Default::default()
},
);
}
#[test]
fn lossless_override_tree_max_buckets() {
assert_lossless_field_changes_output(
"tree_max_buckets",
LosslessInternalParams {
tree_max_buckets: Some(16),
..Default::default()
},
);
}
#[test]
fn lossless_override_tree_num_properties() {
assert_lossless_field_changes_output(
"tree_num_properties",
LosslessInternalParams {
tree_num_properties: Some(1),
..Default::default()
},
);
}
#[test]
fn lossless_override_tree_threshold_base() {
assert_lossless_field_changes_output(
"tree_threshold_base",
LosslessInternalParams {
tree_threshold_base: Some(30.0),
..Default::default()
},
);
}
#[test]
fn lossless_override_tree_sample_fraction() {
assert_lossless_field_changes_output(
"tree_sample_fraction",
LosslessInternalParams {
tree_max_samples_fixed: Some(0),
tree_sample_fraction: Some(0.05),
..Default::default()
},
);
}
#[test]
fn lossless_override_tree_max_samples_fixed() {
assert_lossless_field_changes_output(
"tree_max_samples_fixed",
LosslessInternalParams {
tree_sample_fraction: Some(0.0),
tree_max_samples_fixed: Some(8_192),
..Default::default()
},
);
}
#[test]
fn override_roundtrip_lossy_changes_bitstream() {
let mut table = EntropyMulTable::reference();
table.dct8 = 1.5;
let params = LossyInternalParams {
k_info_loss_mul_base: Some(2.0),
entropy_mul_table: Some(table),
try_dct64: Some(false),
cfl_two_pass: Some(false),
..Default::default()
};
let cfg = baseline_lossy().with_internal_params(params);
let bytes_override = encode_lossy(&cfg);
let bytes_plain = encode_lossy(&baseline_lossy());
assert_eq!(&bytes_override[..2], &crate::JXL_SIGNATURE);
assert_ne!(
bytes_override, bytes_plain,
"override should produce a different bitstream than plain effort=7"
);
}
#[test]
fn override_roundtrip_lossless_changes_bitstream() {
let params = LosslessInternalParams {
tree_max_buckets: Some(16),
tree_num_properties: Some(3),
nb_rcts_to_try: Some(0),
..Default::default()
};
let cfg = baseline_lossless().with_internal_params(params);
let bytes_override = encode_lossless(&cfg);
let bytes_plain = encode_lossless(&baseline_lossless());
assert_eq!(&bytes_override[..2], &crate::JXL_SIGNATURE);
assert_ne!(bytes_override, bytes_plain);
}
#[test]
fn default_params_lossy_match_plain() {
let cfg_override = LossyConfig::new(1.5)
.with_effort(7)
.with_threads(1)
.with_internal_params(LossyInternalParams::default());
let cfg_plain = LossyConfig::new(1.5).with_effort(7).with_threads(1);
let bytes_override = encode_lossy(&cfg_override);
let bytes_plain = encode_lossy(&cfg_plain);
assert_eq!(
bytes_override, bytes_plain,
"default LossyInternalParams override must equal plain with_effort(7) bytes",
);
}
#[test]
fn default_params_lossless_match_plain() {
let cfg_override = LosslessConfig::new()
.with_effort(7)
.with_threads(1)
.with_internal_params(LosslessInternalParams::default());
let cfg_plain = LosslessConfig::new().with_effort(7).with_threads(1);
let bytes_override = encode_lossless(&cfg_override);
let bytes_plain = encode_lossless(&cfg_plain);
assert_eq!(
bytes_override, bytes_plain,
"default LosslessInternalParams override must equal plain with_effort(7) bytes",
);
}