use crate::error::{JpxError, Result};
use crate::geometry::{BandKind, Rect};
use crate::mq::{MqContext, MqDecoder};
use crate::packet::{CodeBlockInput, CodeBlockSegment};
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct CodeBlockCoefficients {
pub rect: Rect,
pub magnitudes: Vec<u32>,
pub negative: Vec<bool>,
pub decoded_planes: Vec<u8>,
pub corrupt: bool,
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct BandCoefficients {
pub kind: BandKind,
pub level: u8,
pub rect: Rect,
pub blocks: Vec<CodeBlockCoefficients>,
}
const STRIPE_ROWS: usize = 4;
const MAX_BLOCK_SAMPLES: u64 = 4096;
const CTX_RUN_LENGTH: usize = 17;
const CTX_UNIFORM: usize = 18;
const CONTEXT_COUNT: usize = 19;
const FIRST_BYPASS_PASS: u64 = 10;
const STYLE_BYPASS: u8 = 1;
const STYLE_RESET: u8 = 2;
const STYLE_VCAUSAL: u8 = 8;
const STYLE_SEGSYM: u8 = 32;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum PassKind {
SignificancePropagation,
MagnitudeRefinement,
Cleanup,
}
fn pass_schedule(pass: u64) -> (u32, PassKind) {
if pass == 0 {
return (0, PassKind::Cleanup);
}
let offset = pass - 1;
let plane = (offset / 3 + 1) as u32;
let kind = match offset % 3 {
0 => PassKind::SignificancePropagation,
1 => PassKind::MagnitudeRefinement,
_ => PassKind::Cleanup,
};
(plane, kind)
}
fn plane_bit_weight(mb: u32, missing: u32, plane: u32) -> Option<u32> {
let consumed = missing.checked_add(plane)?.checked_add(1)?;
mb.checked_sub(consumed)
}
fn scan_positions(width: usize, height: usize) -> impl Iterator<Item = (usize, usize)> {
(0..height.div_ceil(STRIPE_ROWS)).flat_map(move |stripe| {
let top = stripe * STRIPE_ROWS;
let rows = STRIPE_ROWS.min(height - top);
(0..width).flat_map(move |x| (top..top + rows).map(move |y| (x, y)))
})
}
fn zero_coding_context(kind: BandKind, sh: u32, sv: u32, sd: u32) -> usize {
if kind == BandKind::Hh {
let hv = sh + sv;
return if sd >= 3 {
8
} else if sd == 2 {
if hv >= 1 {
7
} else {
6
}
} else if sd == 1 {
if hv >= 2 {
5
} else if hv == 1 {
4
} else {
3
}
} else if hv >= 2 {
2
} else if hv == 1 {
1
} else {
0
};
}
let (h, v) = if kind == BandKind::Hl {
(sv, sh)
} else {
(sh, sv)
};
if h >= 2 {
8
} else if h == 1 {
if v >= 1 {
7
} else if sd >= 1 {
6
} else {
5
}
} else if v >= 2 {
4
} else if v == 1 {
3
} else if sd >= 2 {
2
} else if sd == 1 {
1
} else {
0
}
}
fn sign_contribution(a: i32, b: i32) -> i32 {
(a + b).signum()
}
fn sign_context(h0: i32, h1: i32, v0: i32, v1: i32) -> (usize, u32) {
let h = sign_contribution(h0, h1);
let v = sign_contribution(v0, v1);
match (h, v) {
(1, 1) => (13, 0),
(1, 0) => (12, 0),
(1, -1) => (11, 0),
(0, 1) => (10, 0),
(0, 0) => (9, 0),
(0, -1) => (10, 1),
(-1, 1) => (11, 1),
(-1, 0) => (12, 1),
_ => (13, 1),
}
}
fn refinement_context(neighbor_sum: u32, first: bool) -> usize {
if !first {
16
} else if neighbor_sum >= 1 {
15
} else {
14
}
}
fn initial_contexts() -> [MqContext; CONTEXT_COUNT] {
let mut contexts = [MqContext::new(0); CONTEXT_COUNT];
contexts[0] = MqContext::new(4);
contexts[CTX_RUN_LENGTH] = MqContext::new(3);
contexts[CTX_UNIFORM] = MqContext::new(46);
contexts
}
struct RawBits<'a> {
data: &'a [u8],
position: usize,
current: u8,
remaining: u32,
previous_was_ff: bool,
}
impl<'a> RawBits<'a> {
fn new(data: &'a [u8]) -> Self {
RawBits {
data,
position: 0,
current: 0,
remaining: 0,
previous_was_ff: false,
}
}
fn next_bit(&mut self) -> u32 {
if self.remaining == 0 {
let byte = self.data.get(self.position).copied().unwrap_or(255);
self.position = self.position.saturating_add(1);
self.current = byte;
self.remaining = if self.previous_was_ff { 7 } else { 8 };
self.previous_was_ff = byte == 255;
}
self.remaining -= 1;
u32::from(self.current >> self.remaining) & 1
}
}
struct TermSegment {
bytes: Vec<u8>,
passes: u64,
}
fn terminated_segments(
contributions: &[CodeBlockSegment],
bitstream: &[u8],
) -> (Vec<TermSegment>, bool) {
let mut segments = Vec::new();
let mut bytes: Vec<u8> = Vec::new();
let mut passes = 0u64;
for part in contributions {
let data = part
.start
.checked_add(part.len)
.and_then(|end| bitstream.get(part.start..end));
let Some(data) = data else {
return (segments, true);
};
bytes.extend_from_slice(data);
passes += u64::from(part.passes);
if part.terminated {
segments.push(TermSegment {
bytes: std::mem::take(&mut bytes),
passes,
});
passes = 0;
}
}
if passes > 0 {
segments.push(TermSegment { bytes, passes });
}
(segments, false)
}
enum Coder<'a> {
Mq(MqDecoder<'a>),
Raw(RawBits<'a>),
}
struct BlockState {
width: usize,
height: usize,
causal: bool,
significant: Vec<bool>,
visited: Vec<bool>,
refined: Vec<bool>,
negative: Vec<bool>,
magnitudes: Vec<u32>,
decoded_planes: Vec<u8>,
}
impl BlockState {
fn new(width: usize, height: usize, causal: bool) -> Self {
let n = width * height;
BlockState {
width,
height,
causal,
significant: vec![false; n],
visited: vec![false; n],
refined: vec![false; n],
negative: vec![false; n],
magnitudes: vec![0; n],
decoded_planes: vec![0; n],
}
}
fn at(&self, x: usize, y: usize) -> usize {
y * self.width + x
}
fn visibility_limit(&self, y: usize) -> isize {
if self.causal {
(y - y % STRIPE_ROWS + STRIPE_ROWS) as isize
} else {
isize::MAX
}
}
fn neighbor_significance(&self, x: isize, y: isize, row_limit: isize) -> u32 {
if x < 0 || y < 0 || x >= self.width as isize || y >= (self.height as isize).min(row_limit)
{
return 0;
}
u32::from(self.significant[y as usize * self.width + x as usize])
}
fn neighbor_sums(&self, x: usize, y: usize) -> (u32, u32, u32) {
let limit = self.visibility_limit(y);
let (xi, yi) = (x as isize, y as isize);
let sh = self.neighbor_significance(xi - 1, yi, limit)
+ self.neighbor_significance(xi + 1, yi, limit);
let sv = self.neighbor_significance(xi, yi - 1, limit)
+ self.neighbor_significance(xi, yi + 1, limit);
let sd = self.neighbor_significance(xi - 1, yi - 1, limit)
+ self.neighbor_significance(xi + 1, yi - 1, limit)
+ self.neighbor_significance(xi - 1, yi + 1, limit)
+ self.neighbor_significance(xi + 1, yi + 1, limit);
(sh, sv, sd)
}
fn sign_state(&self, x: isize, y: isize, row_limit: isize) -> i32 {
if self.neighbor_significance(x, y, row_limit) == 0 {
return 0;
}
if self.negative[y as usize * self.width + x as usize] {
-1
} else {
1
}
}
fn sign_neighbors(&self, x: usize, y: usize) -> (i32, i32, i32, i32) {
let limit = self.visibility_limit(y);
let (xi, yi) = (x as isize, y as isize);
(
self.sign_state(xi - 1, yi, limit),
self.sign_state(xi + 1, yi, limit),
self.sign_state(xi, yi - 1, limit),
self.sign_state(xi, yi + 1, limit),
)
}
fn zero_coding_label(&self, band: BandKind, x: usize, y: usize) -> usize {
let (sh, sv, sd) = self.neighbor_sums(x, y);
zero_coding_context(band, sh, sv, sd)
}
fn run_length_eligible(&self, band: BandKind, x: usize, stripe_top: usize) -> bool {
(0..STRIPE_ROWS).all(|k| {
let y = stripe_top + k;
let i = self.at(x, y);
!self.significant[i] && !self.visited[i] && self.zero_coding_label(band, x, y) == 0
})
}
fn record(&mut self, i: usize, bit: u32, weight: u32, planes: u8) {
if bit == 1 && weight < 32 {
self.magnitudes[i] |= 1u32 << weight;
}
self.decoded_planes[i] = planes;
}
}
fn significance_bit(
coder: &mut Coder<'_>,
contexts: &mut [MqContext; CONTEXT_COUNT],
label: usize,
) -> u32 {
match coder {
Coder::Mq(mq) => mq.decode(&mut contexts[label]),
Coder::Raw(raw) => raw.next_bit(),
}
}
fn decode_sign(
coder: &mut Coder<'_>,
contexts: &mut [MqContext; CONTEXT_COUNT],
state: &BlockState,
x: usize,
y: usize,
) -> bool {
match coder {
Coder::Mq(mq) => {
let (h0, h1, v0, v1) = state.sign_neighbors(x, y);
let (label, xor) = sign_context(h0, h1, v0, v1);
(mq.decode(&mut contexts[label]) ^ xor) == 1
}
Coder::Raw(raw) => raw.next_bit() == 1,
}
}
fn refinement_bit(
coder: &mut Coder<'_>,
contexts: &mut [MqContext; CONTEXT_COUNT],
state: &BlockState,
x: usize,
y: usize,
i: usize,
) -> u32 {
match coder {
Coder::Mq(mq) => {
let (sh, sv, sd) = state.neighbor_sums(x, y);
let label = refinement_context(sh + sv + sd, !state.refined[i]);
mq.decode(&mut contexts[label])
}
Coder::Raw(raw) => raw.next_bit(),
}
}
fn significance_pass(
state: &mut BlockState,
coder: &mut Coder<'_>,
contexts: &mut [MqContext; CONTEXT_COUNT],
band: BandKind,
weight: u32,
planes: u8,
) {
state.visited.fill(false);
for (x, y) in scan_positions(state.width, state.height) {
let i = state.at(x, y);
if state.significant[i] {
continue;
}
let label = state.zero_coding_label(band, x, y);
if label == 0 {
continue;
}
let bit = significance_bit(coder, contexts, label);
state.visited[i] = true;
state.record(i, bit, weight, planes);
if bit == 1 {
state.significant[i] = true;
state.negative[i] = decode_sign(coder, contexts, state, x, y);
}
}
}
fn refinement_pass(
state: &mut BlockState,
coder: &mut Coder<'_>,
contexts: &mut [MqContext; CONTEXT_COUNT],
weight: u32,
planes: u8,
) {
for (x, y) in scan_positions(state.width, state.height) {
let i = state.at(x, y);
if !state.significant[i] || state.visited[i] {
continue;
}
let bit = refinement_bit(coder, contexts, state, x, y, i);
state.refined[i] = true;
state.record(i, bit, weight, planes);
}
}
fn cleanup_pass(
state: &mut BlockState,
coder: &mut Coder<'_>,
contexts: &mut [MqContext; CONTEXT_COUNT],
band: BandKind,
weight: u32,
planes: u8,
segmentation: bool,
) -> bool {
let Coder::Mq(mq) = coder else {
return false;
};
let mut stripe_top = 0;
while stripe_top < state.height {
let rows = STRIPE_ROWS.min(state.height - stripe_top);
for x in 0..state.width {
let mut from = 0;
if rows == STRIPE_ROWS && state.run_length_eligible(band, x, stripe_top) {
if mq.decode(&mut contexts[CTX_RUN_LENGTH]) == 0 {
for k in 0..STRIPE_ROWS {
let i = state.at(x, stripe_top + k);
state.record(i, 0, weight, planes);
}
continue;
}
let msb = mq.decode(&mut contexts[CTX_UNIFORM]);
let lsb = mq.decode(&mut contexts[CTX_UNIFORM]);
let first = ((msb << 1) | lsb) as usize;
for k in 0..first {
let i = state.at(x, stripe_top + k);
state.record(i, 0, weight, planes);
}
let y = stripe_top + first;
let i = state.at(x, y);
state.record(i, 1, weight, planes);
state.significant[i] = true;
let (h0, h1, v0, v1) = state.sign_neighbors(x, y);
let (label, xor) = sign_context(h0, h1, v0, v1);
state.negative[i] = (mq.decode(&mut contexts[label]) ^ xor) == 1;
from = first + 1;
}
for k in from..rows {
let y = stripe_top + k;
let i = state.at(x, y);
if state.significant[i] || state.visited[i] {
continue;
}
let label = state.zero_coding_label(band, x, y);
let bit = mq.decode(&mut contexts[label]);
state.record(i, bit, weight, planes);
if bit == 1 {
state.significant[i] = true;
let (h0, h1, v0, v1) = state.sign_neighbors(x, y);
let (label, xor) = sign_context(h0, h1, v0, v1);
state.negative[i] = (mq.decode(&mut contexts[label]) ^ xor) == 1;
}
}
}
stripe_top += STRIPE_ROWS;
}
if segmentation {
for want in [1u32, 0, 1, 0] {
if mq.decode(&mut contexts[CTX_UNIFORM]) != want {
return false;
}
}
}
true
}
pub(crate) fn decode_code_block(
input: &CodeBlockInput,
bitstream: &[u8],
) -> Result<CodeBlockCoefficients> {
let width = input.rect.width() as usize;
let height = input.rect.height() as usize;
if width as u64 * height as u64 > MAX_BLOCK_SAMPLES {
return Err(JpxError::Malformed(format!(
"code-block {width}x{height} exceeds the {MAX_BLOCK_SAMPLES}-sample bound of Table A.18"
)));
}
let mut state = BlockState::new(width, height, input.style & STYLE_VCAUSAL != 0);
let (segments, mut corrupt) = terminated_segments(&input.segments, bitstream);
let mb = u32::from(input.magnitude_bits);
let available = u64::from(mb.saturating_sub(input.missing_msbs));
let budget = if available == 0 { 0 } else { 3 * available - 2 };
let signalled: u64 = segments.iter().map(|segment| segment.passes).sum();
if signalled > budget {
corrupt = true;
}
let scheduled = signalled.min(budget);
let bypass = input.style & STYLE_BYPASS != 0;
let reset = input.style & STYLE_RESET != 0;
let segmentation = input.style & STYLE_SEGSYM != 0;
let mut contexts = initial_contexts();
let mut coder: Option<Coder<'_>> = None;
let mut segment_index = 0;
let mut passes_left = 0u64;
for pass in 0..scheduled {
let (plane, kind) = pass_schedule(pass);
let raw = bypass && pass >= FIRST_BYPASS_PASS && kind != PassKind::Cleanup;
if passes_left == 0 {
coder = None;
while passes_left == 0 {
let Some(segment) = segments.get(segment_index) else {
break;
};
segment_index += 1;
passes_left = segment.passes;
if passes_left > 0 {
coder = Some(if raw {
Coder::Raw(RawBits::new(&segment.bytes))
} else {
Coder::Mq(MqDecoder::new(&segment.bytes))
});
}
}
}
let Some(active) = coder.as_mut() else {
corrupt = true;
break;
};
if matches!(active, Coder::Raw(_)) != raw {
corrupt = true;
break;
}
passes_left -= 1;
if reset {
contexts = initial_contexts();
}
let Some(weight) = plane_bit_weight(mb, input.missing_msbs, plane) else {
corrupt = true;
break;
};
let planes = (u64::from(input.missing_msbs) + u64::from(plane) + 1).min(255) as u8;
let clean = match kind {
PassKind::SignificancePropagation => {
significance_pass(
&mut state,
active,
&mut contexts,
input.band,
weight,
planes,
);
true
}
PassKind::MagnitudeRefinement => {
refinement_pass(&mut state, active, &mut contexts, weight, planes);
true
}
PassKind::Cleanup => cleanup_pass(
&mut state,
active,
&mut contexts,
input.band,
weight,
planes,
segmentation,
),
};
if !clean {
corrupt = true;
break;
}
}
Ok(CodeBlockCoefficients {
rect: input.rect,
magnitudes: state.magnitudes,
negative: state.negative,
decoded_planes: state.decoded_planes,
corrupt,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Rect;
fn block_input(
width: u32,
height: u32,
magnitude_bits: u8,
missing_msbs: u32,
style: u8,
segments: Vec<CodeBlockSegment>,
) -> CodeBlockInput {
CodeBlockInput {
rect: Rect {
x0: 3,
y0: 5,
x1: 3 + width,
y1: 5 + height,
},
band: BandKind::Ll,
missing_msbs,
magnitude_bits,
style,
segments,
}
}
fn search_bytes(
min_len: usize,
max_len: usize,
start: &[MqContext; CONTEXT_COUNT],
sim: impl Fn(&mut MqDecoder<'_>, &mut [MqContext; CONTEXT_COUNT]) -> bool,
) -> Vec<u8> {
for len in min_len..=max_len {
let mut bytes = vec![0u8; len];
loop {
let mut contexts = *start;
let mut decoder = MqDecoder::new(&bytes);
if sim(&mut decoder, &mut contexts) {
return bytes;
}
let mut k = len;
loop {
if k == 0 {
break;
}
k -= 1;
if bytes[k] == 255 {
bytes[k] = 0;
} else {
bytes[k] += 1;
break;
}
}
if bytes.iter().all(|&b| b == 0) {
break;
}
}
}
panic!("no byte stream produces the wanted decision sequence");
}
#[test]
fn table_d1_ll_and_lh_columns() {
let rows = [
(2, 0, 0, 8),
(2, 2, 4, 8),
(1, 1, 0, 7),
(1, 2, 4, 7),
(1, 0, 1, 6),
(1, 0, 4, 6),
(1, 0, 0, 5),
(0, 2, 0, 4),
(0, 2, 4, 4),
(0, 1, 0, 3),
(0, 1, 4, 3),
(0, 0, 2, 2),
(0, 0, 4, 2),
(0, 0, 1, 1),
(0, 0, 0, 0),
];
for (sh, sv, sd, want) in rows {
assert_eq!(
zero_coding_context(BandKind::Ll, sh, sv, sd),
want,
"LL row ({sh},{sv},{sd})"
);
}
for sh in 0..=2 {
for sv in 0..=2 {
for sd in 0..=4 {
assert_eq!(
zero_coding_context(BandKind::Ll, sh, sv, sd),
zero_coding_context(BandKind::Lh, sh, sv, sd),
"LH must share the LL column at ({sh},{sv},{sd})"
);
}
}
}
}
#[test]
fn table_d1_hl_column_swaps_h_and_v() {
let rows = [
(0, 2, 0, 8),
(2, 2, 4, 8),
(1, 1, 0, 7),
(2, 1, 4, 7),
(0, 1, 1, 6),
(0, 1, 4, 6),
(0, 1, 0, 5),
(2, 0, 0, 4),
(2, 0, 4, 4),
(1, 0, 0, 3),
(1, 0, 4, 3),
(0, 0, 2, 2),
(0, 0, 1, 1),
(0, 0, 0, 0),
];
for (sh, sv, sd, want) in rows {
assert_eq!(
zero_coding_context(BandKind::Hl, sh, sv, sd),
want,
"HL row ({sh},{sv},{sd})"
);
}
for sh in 0..=2 {
for sv in 0..=2 {
for sd in 0..=4 {
assert_eq!(
zero_coding_context(BandKind::Hl, sh, sv, sd),
zero_coding_context(BandKind::Ll, sv, sh, sd),
"HL must be LL with H and V exchanged at ({sh},{sv},{sd})"
);
}
}
}
}
#[test]
fn table_d1_hh_column() {
let rows = [
(0, 0, 3, 8),
(2, 2, 4, 8),
(1, 0, 2, 7),
(2, 2, 2, 7),
(0, 0, 2, 6),
(1, 1, 1, 5),
(2, 2, 1, 5),
(1, 0, 1, 4),
(0, 1, 1, 4),
(0, 0, 1, 3),
(1, 1, 0, 2),
(2, 2, 0, 2),
(1, 0, 0, 1),
(0, 1, 0, 1),
(0, 0, 0, 0),
];
for (sh, sv, sd, want) in rows {
assert_eq!(
zero_coding_context(BandKind::Hh, sh, sv, sd),
want,
"HH row ({sh},{sv},{sd})"
);
}
}
#[test]
fn tables_d2_d3_sign_contexts() {
assert_eq!(sign_contribution(1, 1), 1);
assert_eq!(sign_contribution(-1, 1), 0);
assert_eq!(sign_contribution(0, 1), 1);
assert_eq!(sign_contribution(1, -1), 0);
assert_eq!(sign_contribution(-1, -1), -1);
assert_eq!(sign_contribution(0, -1), -1);
assert_eq!(sign_contribution(1, 0), 1);
assert_eq!(sign_contribution(-1, 0), -1);
assert_eq!(sign_contribution(0, 0), 0);
assert_eq!(sign_context(1, 1, 1, 0), (13, 0));
assert_eq!(sign_context(1, 0, 0, 0), (12, 0));
assert_eq!(sign_context(1, 1, -1, 0), (11, 0));
assert_eq!(sign_context(1, -1, 0, 1), (10, 0));
assert_eq!(sign_context(0, 0, 0, 0), (9, 0));
assert_eq!(sign_context(1, -1, -1, -1), (10, 1));
assert_eq!(sign_context(-1, 0, 1, 1), (11, 1));
assert_eq!(sign_context(-1, -1, 1, -1), (12, 1));
assert_eq!(sign_context(-1, -1, -1, 0), (13, 1));
}
#[test]
fn table_d4_refinement_contexts() {
assert_eq!(refinement_context(0, true), 14);
assert_eq!(refinement_context(1, true), 15);
assert_eq!(refinement_context(8, true), 15);
assert_eq!(refinement_context(0, false), 16);
assert_eq!(refinement_context(3, false), 16);
}
#[test]
fn table_d7_initial_states() {
let contexts = initial_contexts();
assert_eq!(contexts.len(), 19);
for (label, cx) in contexts.iter().enumerate() {
let want = match label {
0 => 4,
CTX_RUN_LENGTH => 3,
CTX_UNIFORM => 46,
_ => 0,
};
assert_eq!(cx.index, want, "context {label}");
assert_eq!(cx.mps, 0, "context {label} MPS");
}
}
#[test]
fn scan_covers_stripes_column_by_column() {
let got: Vec<(usize, usize)> = scan_positions(6, 5).collect();
let want = vec![
(0, 0),
(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 1),
(1, 2),
(1, 3),
(2, 0),
(2, 1),
(2, 2),
(2, 3),
(3, 0),
(3, 1),
(3, 2),
(3, 3),
(4, 0),
(4, 1),
(4, 2),
(4, 3),
(5, 0),
(5, 1),
(5, 2),
(5, 3),
(0, 4),
(1, 4),
(2, 4),
(3, 4),
(4, 4),
(5, 4),
];
assert_eq!(got, want);
}
#[test]
fn pass_schedule_first_plane_cleanup_only() {
use PassKind::{Cleanup, MagnitudeRefinement, SignificancePropagation};
let got: Vec<(u32, PassKind)> = (0..10).map(pass_schedule).collect();
let want = vec![
(0, Cleanup),
(1, SignificancePropagation),
(1, MagnitudeRefinement),
(1, Cleanup),
(2, SignificancePropagation),
(2, MagnitudeRefinement),
(2, Cleanup),
(3, SignificancePropagation),
(3, MagnitudeRefinement),
(3, Cleanup),
];
assert_eq!(got, want);
assert_eq!(pass_schedule(10), (4, SignificancePropagation));
assert_eq!(FIRST_BYPASS_PASS, 10);
}
#[test]
fn plane_bit_weights_follow_e1() {
let got: Vec<Option<u32>> = (0..7).map(|p| plane_bit_weight(8, 2, p)).collect();
assert_eq!(
got,
vec![Some(5), Some(4), Some(3), Some(2), Some(1), Some(0), None]
);
assert_eq!(plane_bit_weight(1, 0, 0), Some(0));
assert_eq!(plane_bit_weight(1, 1, 0), None);
assert_eq!(plane_bit_weight(4, 4000000000, 0), None);
}
#[test]
fn raw_bits_unstuff_after_ff_and_pad_past_the_end() {
let data = [179u8, 255, 80, 255, 255, 37];
let mut reader = RawBits::new(&data);
let want = concat!("10110011", "11111111", "1010000", "11111111", "1111111", "0100101");
let got: String = (0..45)
.map(|_| char::from(b'0' + reader.next_bit() as u8))
.collect();
assert_eq!(got, want);
for i in 0..30 {
assert_eq!(reader.next_bit(), 1, "padding bit {i}");
}
let mut empty = RawBits::new(&[]);
for i in 0..20 {
assert_eq!(empty.next_bit(), 1, "empty-segment padding bit {i}");
}
}
#[test]
fn vertically_causal_hides_the_next_stripe() {
let mut causal = BlockState::new(1, 5, true);
let low = causal.at(0, 4);
causal.significant[low] = true;
assert_eq!(causal.zero_coding_label(BandKind::Ll, 0, 3), 0);
let mut open = BlockState::new(1, 5, false);
open.significant[low] = true;
assert_eq!(open.zero_coding_label(BandKind::Ll, 0, 3), 3);
}
#[test]
fn one_sample_block_decodes_significance_and_sign() {
let bytes = search_bytes(2, 3, &initial_contexts(), |decoder, contexts| {
decoder.decode(&mut contexts[0]) == 1 && decoder.decode(&mut contexts[9]) == 1
});
let input = block_input(
1,
1,
1,
0,
0,
vec![CodeBlockSegment {
start: 0,
len: bytes.len(),
passes: 1,
terminated: true,
}],
);
let block = decode_code_block(&input, &bytes).expect("clean block");
assert_eq!(block.rect, input.rect);
assert_eq!(block.magnitudes, vec![1]);
assert_eq!(block.negative, vec![true]);
assert_eq!(block.decoded_planes, vec![1]);
assert!(!block.corrupt);
let split = block_input(
1,
1,
1,
0,
0,
vec![
CodeBlockSegment {
start: 0,
len: 1,
passes: 1,
terminated: false,
},
CodeBlockSegment {
start: 1,
len: bytes.len() - 1,
passes: 0,
terminated: true,
},
],
);
let again = decode_code_block(&split, &bytes).expect("clean block");
assert_eq!(again.magnitudes, vec![1]);
assert_eq!(again.negative, vec![true]);
assert_eq!(again.decoded_planes, vec![1]);
}
#[test]
fn cleanup_run_length_interrupts_at_the_decoded_position() {
let bytes = search_bytes(2, 3, &initial_contexts(), |decoder, contexts| {
decoder.decode(&mut contexts[CTX_RUN_LENGTH]) == 1
&& decoder.decode(&mut contexts[CTX_UNIFORM]) == 1
&& decoder.decode(&mut contexts[CTX_UNIFORM]) == 0
&& decoder.decode(&mut contexts[9]) == 0
&& decoder.decode(&mut contexts[3]) == 0
});
let input = block_input(
1,
4,
1,
0,
0,
vec![CodeBlockSegment {
start: 0,
len: bytes.len(),
passes: 1,
terminated: true,
}],
);
let block = decode_code_block(&input, &bytes).expect("clean block");
assert_eq!(block.magnitudes, vec![0, 0, 1, 0]);
assert_eq!(block.negative, vec![false; 4]);
assert_eq!(block.decoded_planes, vec![1; 4]);
}
#[test]
fn segmentation_symbol_1010_lets_decoding_continue() {
let bytes = search_bytes(2, 3, &initial_contexts(), |decoder, contexts| {
decoder.decode(&mut contexts[0]) == 0
&& decoder.decode(&mut contexts[CTX_UNIFORM]) == 1
&& decoder.decode(&mut contexts[CTX_UNIFORM]) == 0
&& decoder.decode(&mut contexts[CTX_UNIFORM]) == 1
&& decoder.decode(&mut contexts[CTX_UNIFORM]) == 0
});
let input = block_input(
1,
1,
2,
0,
STYLE_SEGSYM,
vec![CodeBlockSegment {
start: 0,
len: bytes.len(),
passes: 4,
terminated: true,
}],
);
let block = decode_code_block(&input, &bytes).expect("clean block");
assert_eq!(block.decoded_planes, vec![2]);
}
#[test]
fn segmentation_symbol_mismatch_stops_the_block() {
let bytes = search_bytes(1, 3, &initial_contexts(), |decoder, contexts| {
decoder.decode(&mut contexts[0]) == 0 && decoder.decode(&mut contexts[CTX_UNIFORM]) == 0
});
let input = block_input(
1,
1,
2,
0,
STYLE_SEGSYM,
vec![CodeBlockSegment {
start: 0,
len: bytes.len(),
passes: 4,
terminated: true,
}],
);
let block = decode_code_block(&input, &bytes).expect("kept partial block");
assert_eq!(block.magnitudes, vec![0]);
assert_eq!(block.decoded_planes, vec![1]);
assert!(block.corrupt, "the mismatch must flag the block corrupt");
}
#[test]
fn bypass_segments_switch_coders_at_terminations() {
let head = search_bytes(2, 3, &initial_contexts(), |decoder, contexts| {
(0..4).all(|_| decoder.decode(&mut contexts[0]) == 0)
});
let mut evolved = initial_contexts();
{
let mut decoder = MqDecoder::new(&head);
for _ in 0..4 {
decoder.decode(&mut evolved[0]);
}
}
let tail = search_bytes(2, 3, &evolved, |decoder, contexts| {
decoder.decode(&mut contexts[0]) == 1 && decoder.decode(&mut contexts[9]) == 1
});
let mut bitstream = head.clone();
bitstream.extend_from_slice(&tail);
let input = block_input(
1,
1,
5,
0,
STYLE_BYPASS,
vec![
CodeBlockSegment {
start: 0,
len: head.len(),
passes: 10,
terminated: true,
},
CodeBlockSegment {
start: head.len(),
len: 0,
passes: 2,
terminated: true,
},
CodeBlockSegment {
start: head.len(),
len: tail.len(),
passes: 1,
terminated: true,
},
],
);
let block = decode_code_block(&input, &bitstream).expect("clean block");
assert_eq!(block.magnitudes, vec![1]);
assert_eq!(block.negative, vec![true]);
assert_eq!(block.decoded_planes, vec![5]);
assert!(!block.corrupt);
}
#[test]
fn no_segments_decode_all_zero() {
let input = block_input(3, 2, 6, 0, 0, Vec::new());
let block = decode_code_block(&input, &[]).expect("empty block");
assert_eq!(block.rect, input.rect);
assert_eq!(block.magnitudes, vec![0; 6]);
assert_eq!(block.negative, vec![false; 6]);
assert_eq!(block.decoded_planes, vec![0; 6]);
assert!(!block.corrupt, "an absent block is normal, not corrupt");
}
#[test]
fn oversized_block_is_malformed() {
let input = block_input(70, 70, 1, 0, 0, Vec::new());
let got = decode_code_block(&input, &[]);
assert!(
matches!(got, Err(JpxError::Malformed(_))),
"70x70 must be rejected, got {got:?}"
);
}
#[test]
fn out_of_range_segment_keeps_the_block_zero() {
let input = block_input(
2,
2,
3,
0,
0,
vec![CodeBlockSegment {
start: usize::MAX - 1,
len: 5,
passes: 1,
terminated: true,
}],
);
let block = decode_code_block(&input, &[1, 2, 3]).expect("kept zero block");
assert_eq!(block.magnitudes, vec![0; 4]);
assert_eq!(block.decoded_planes, vec![0; 4]);
assert!(block.corrupt, "the dropped contribution must be flagged");
}
#[test]
fn passes_beyond_the_plane_budget_flag_corruption() {
let bytes = search_bytes(1, 3, &initial_contexts(), |decoder, contexts| {
decoder.decode(&mut contexts[0]) == 0
});
let input = block_input(
1,
1,
1,
0,
0,
vec![CodeBlockSegment {
start: 0,
len: bytes.len(),
passes: 4,
terminated: true,
}],
);
let block = decode_code_block(&input, &bytes).expect("kept the budgeted pass");
assert_eq!(block.decoded_planes, vec![1]);
assert!(block.corrupt);
}
}