1use crate::{Htj2k97CodeBlockAxis, Htj2k97CodeBlockOptions, Htj2k97CodeBlockOptionsError};
6use j2k::J2kSubBandType;
7use j2k_native::irreversible_quantization_step_for_subband;
8
9#[must_use]
15pub fn htj2k97_subband_delta(
16 options: Htj2k97CodeBlockOptions,
17 sub_band_type: J2kSubBandType,
18) -> f64 {
19 let log_gain = match sub_band_type {
20 J2kSubBandType::LowLow => 0,
21 J2kSubBandType::HighLow | J2kSubBandType::LowHigh => 1,
22 J2kSubBandType::HighHigh => 2,
23 };
24 let range_bits = i32::from(options.bit_depth) + log_gain;
25 let (exponent, mantissa) = htj2k97_step(options, sub_band_type);
26 pow2i_f64(range_bits - i32::from(exponent)) * (1.0 + f64::from(mantissa) / 2048.0)
27}
28
29#[must_use]
35pub fn htj2k97_subband_total_bitplanes(
36 options: Htj2k97CodeBlockOptions,
37 sub_band_type: J2kSubBandType,
38) -> u8 {
39 let (exponent, _) = htj2k97_step(options, sub_band_type);
40 options
41 .guard_bits
42 .saturating_add(exponent)
43 .saturating_sub(1)
44}
45
46pub fn validate_htj2k97_codeblock_options(
59 options: Htj2k97CodeBlockOptions,
60) -> Result<(usize, usize), Htj2k97CodeBlockOptionsError> {
61 if options.bit_depth == 0
62 || options.bit_depth > 30
63 || options.guard_bits > 30
64 || !options.irreversible_quantization_scale.is_finite()
65 || options.irreversible_quantization_scale <= 0.0
66 {
67 return Err(Htj2k97CodeBlockOptionsError::NumericOptionsOutOfRange);
68 }
69 let subband_scales = options.irreversible_quantization_subband_scales;
70 if [
71 subband_scales.low_low,
72 subband_scales.high_low,
73 subband_scales.low_high,
74 subband_scales.high_high,
75 ]
76 .iter()
77 .any(|scale| !scale.is_finite() || *scale <= 0.0)
78 {
79 return Err(Htj2k97CodeBlockOptionsError::QuantizationOptionsOutOfRange);
80 }
81
82 let cb_width =
83 checked_code_block_dim(options.code_block_width_exp, Htj2k97CodeBlockAxis::Width)?;
84 let cb_height =
85 checked_code_block_dim(options.code_block_height_exp, Htj2k97CodeBlockAxis::Height)?;
86 if cb_width > 1024
87 || cb_height > 1024
88 || cb_width
89 .checked_mul(cb_height)
90 .is_none_or(|area| area > 4096)
91 {
92 return Err(Htj2k97CodeBlockOptionsError::DimensionsExceedLimits {
93 width: cb_width,
94 height: cb_height,
95 });
96 }
97
98 for subband in [
99 J2kSubBandType::LowLow,
100 J2kSubBandType::HighLow,
101 J2kSubBandType::LowHigh,
102 J2kSubBandType::HighHigh,
103 ] {
104 let delta = htj2k97_subband_delta(options, subband);
105 if !delta.is_finite()
106 || delta <= 0.0
107 || htj2k97_subband_total_bitplanes(options, subband) > 30
108 {
109 return Err(Htj2k97CodeBlockOptionsError::QuantizationOptionsOutOfRange);
110 }
111 }
112
113 Ok((cb_width, cb_height))
114}
115
116fn checked_code_block_dim(
117 exp_minus_two: u8,
118 axis: Htj2k97CodeBlockAxis,
119) -> Result<usize, Htj2k97CodeBlockOptionsError> {
120 1usize.checked_shl(u32::from(exp_minus_two) + 2).ok_or(
121 Htj2k97CodeBlockOptionsError::DimensionExponentUnsupported {
122 axis,
123 exponent_minus_two: exp_minus_two,
124 },
125 )
126}
127
128fn htj2k97_step(options: Htj2k97CodeBlockOptions, sub_band_type: J2kSubBandType) -> (u8, u16) {
130 let step = irreversible_quantization_step_for_subband(
131 options.bit_depth,
132 options.guard_bits,
133 options.irreversible_quantization_scale,
134 options.irreversible_quantization_subband_scales,
135 sub_band_type,
136 );
137 (step.exponent, step.mantissa)
138}
139
140fn pow2i_f64(exp: i32) -> f64 {
141 2.0f64.powi(exp)
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use j2k::IrreversibleQuantizationSubbandScales;
148
149 #[test]
150 fn shared_validator_accepts_standard_options_and_returns_dims() {
151 let options = Htj2k97CodeBlockOptions {
152 bit_depth: 8,
153 guard_bits: 2,
154 code_block_width_exp: 4,
155 code_block_height_exp: 4,
156 irreversible_quantization_scale: 1.0,
157 irreversible_quantization_subband_scales:
158 IrreversibleQuantizationSubbandScales::default(),
159 };
160 assert_eq!(validate_htj2k97_codeblock_options(options), Ok((64, 64)));
161 }
162
163 #[test]
164 fn shared_validator_rejects_out_of_spec_options_on_every_backend() {
165 let valid = Htj2k97CodeBlockOptions {
166 bit_depth: 8,
167 guard_bits: 2,
168 code_block_width_exp: 4,
169 code_block_height_exp: 4,
170 irreversible_quantization_scale: 1.0,
171 irreversible_quantization_subband_scales:
172 IrreversibleQuantizationSubbandScales::default(),
173 };
174
175 let oversized_bit_depth = Htj2k97CodeBlockOptions {
177 bit_depth: 31,
178 ..valid
179 };
180 let oversized_guard_bits = Htj2k97CodeBlockOptions {
181 guard_bits: 31,
182 ..valid
183 };
184 let oversized_area = Htj2k97CodeBlockOptions {
187 code_block_width_exp: 8,
188 code_block_height_exp: 8,
189 ..valid
190 };
191 for options in [oversized_bit_depth, oversized_guard_bits, oversized_area] {
192 assert!(
193 validate_htj2k97_codeblock_options(options).is_err(),
194 "options must be rejected: {options:?}"
195 );
196 }
197
198 let zero_guard_bits = Htj2k97CodeBlockOptions {
201 guard_bits: 0,
202 ..valid
203 };
204 assert!(validate_htj2k97_codeblock_options(zero_guard_bits).is_ok());
205 }
206
207 #[test]
208 fn shared_validator_returns_each_typed_failure_variant() {
209 let valid = Htj2k97CodeBlockOptions {
210 bit_depth: 8,
211 guard_bits: 2,
212 code_block_width_exp: 4,
213 code_block_height_exp: 4,
214 irreversible_quantization_scale: 1.0,
215 irreversible_quantization_subband_scales:
216 IrreversibleQuantizationSubbandScales::default(),
217 };
218
219 let numeric = Htj2k97CodeBlockOptions {
220 bit_depth: 31,
221 ..valid
222 };
223 assert_eq!(
224 validate_htj2k97_codeblock_options(numeric),
225 Err(Htj2k97CodeBlockOptionsError::NumericOptionsOutOfRange)
226 );
227
228 let mut quantization = valid;
229 quantization
230 .irreversible_quantization_subband_scales
231 .low_low = 0.0;
232 assert_eq!(
233 validate_htj2k97_codeblock_options(quantization),
234 Err(Htj2k97CodeBlockOptionsError::QuantizationOptionsOutOfRange)
235 );
236
237 let exponent = Htj2k97CodeBlockOptions {
238 code_block_width_exp: u8::MAX,
239 ..valid
240 };
241 assert_eq!(
242 validate_htj2k97_codeblock_options(exponent),
243 Err(Htj2k97CodeBlockOptionsError::DimensionExponentUnsupported {
244 axis: Htj2k97CodeBlockAxis::Width,
245 exponent_minus_two: u8::MAX,
246 })
247 );
248
249 let dimensions = Htj2k97CodeBlockOptions {
250 code_block_width_exp: 8,
251 code_block_height_exp: 8,
252 ..valid
253 };
254 assert_eq!(
255 validate_htj2k97_codeblock_options(dimensions),
256 Err(Htj2k97CodeBlockOptionsError::DimensionsExceedLimits {
257 width: 1024,
258 height: 1024,
259 })
260 );
261 }
262
263 #[test]
264 fn oracle_subband_profile_changes_only_selected_delta_and_bitplanes() {
265 let mut options = Htj2k97CodeBlockOptions {
266 bit_depth: 8,
267 guard_bits: 2,
268 code_block_width_exp: 2,
269 code_block_height_exp: 2,
270 irreversible_quantization_scale: 1.9,
271 irreversible_quantization_subband_scales:
272 IrreversibleQuantizationSubbandScales::default(),
273 };
274 let high_low_delta = htj2k97_subband_delta(options, J2kSubBandType::HighLow);
275 let high_high_delta = htj2k97_subband_delta(options, J2kSubBandType::HighHigh);
276 let default_hh_bitplanes =
277 htj2k97_subband_total_bitplanes(options, J2kSubBandType::HighHigh);
278
279 options.irreversible_quantization_subband_scales.high_high = 1.5;
280
281 assert_eq!(
282 htj2k97_subband_delta(options, J2kSubBandType::HighLow).to_bits(),
283 high_low_delta.to_bits()
284 );
285 assert!(htj2k97_subband_delta(options, J2kSubBandType::HighHigh) > high_high_delta);
286 assert_ne!(
287 htj2k97_subband_total_bitplanes(options, J2kSubBandType::HighHigh),
288 default_hh_bitplanes
289 );
290 }
291}