Skip to content

Commit 6d9fd3e

Browse files
committed
The decoder now treats non-interleaved streams as blocks
In the standard, section 4.8.2 it states: > Minimum Coded Unit > [...] If the compressed image data is non-interleaved, the MCU is > defined to be one data unit [...] If the compressed data is > interleaved, the MCU contains one or more data units from each > component. Previously, things were processed from stream as if they were always batched group size. This typically doesn't cause a problem, but some progressive jpegs perform additional adjustment passes on individual components. In events where (for example) a component has a 2x2 sampling factor, this meant that things would be processed incorrectly. The offsets were correct but, for example in #173, an EOB for 240 blocks would be exhausted after only a quarter of the (incorrectly) expected MCUs were complete. This meant that it would continue to read from the stream as it thought the EOB was over. In this case, a reset marker was incorrectly consumed and things went off the rails shortly after. Now a distinction is made between MCU sizes in interleaved & non-interleaved streams. Most of the time, in non-interleaved streams this manifests as MCUs being treated as blocks instead (and adjusting the indices involved in that). One complication is with how the worker thread pool currently accepts 'MCU rows' from the scan; always in batched size. So, while the stream treats MCUs as blocks, the worker threads are still looking for full-sized units. This means some care must be taken to correctly time when to send the data over to the worker threads. The final change undertaken was to move the reset check logic to the top of the processing loop; this simplified some of the logic and ensured that interleaved & non-interleaved streams were treated correctly. This also (I believe), addresses the concerns of #89.
1 parent 29b9c04 commit 6d9fd3e

File tree

1 file changed

+96
-62
lines changed

1 file changed

+96
-62
lines changed

src/decoder.rs

Lines changed: 96 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -484,9 +484,6 @@ impl<R: Read> Decoder<R> {
484484
}
485485
}
486486

487-
let blocks_per_mcu: Vec<u16> = components.iter()
488-
.map(|c| c.horizontal_sampling_factor as u16 * c.vertical_sampling_factor as u16)
489-
.collect();
490487
let is_progressive = frame.coding_process == CodingProcess::DctProgressive;
491488
let is_interleaved = components.len() > 1;
492489
let mut dummy_block = [0i16; 64];
@@ -504,70 +501,37 @@ impl<R: Read> Decoder<R> {
504501
}
505502
}
506503

507-
for mcu_y in 0 .. frame.mcu_size.height {
508-
for mcu_x in 0 .. frame.mcu_size.width {
509-
for (i, component) in components.iter().enumerate() {
510-
for j in 0 .. blocks_per_mcu[i] {
511-
let (block_x, block_y) = if is_interleaved {
512-
// Section A.2.3
513-
(mcu_x * component.horizontal_sampling_factor as u16 + j % component.horizontal_sampling_factor as u16,
514-
mcu_y * component.vertical_sampling_factor as u16 + j / component.horizontal_sampling_factor as u16)
515-
}
516-
else {
517-
// Section A.2.2
518-
519-
let blocks_per_row = component.block_size.width as usize;
520-
let block_num = (mcu_y as usize * frame.mcu_size.width as usize +
521-
mcu_x as usize) * blocks_per_mcu[i] as usize + j as usize;
522-
523-
let x = (block_num % blocks_per_row) as u16;
524-
let y = (block_num / blocks_per_row) as u16;
525-
526-
if x * component.dct_scale as u16 >= component.size.width || y * component.dct_scale as u16 >= component.size.height {
527-
continue;
528-
}
504+
// 4.8.2
505+
// When reading from the stream, if the data is non-interleaved then an MCU consists of
506+
// exactly one block (effectively a 1x1 sample).
507+
let (mcu_horizontal_samples, mcu_vertical_samples) = if is_interleaved {
508+
let horizontal = components.iter().map(|component| component.horizontal_sampling_factor as u16).collect::<Vec<_>>();
509+
let vertical = components.iter().map(|component| component.vertical_sampling_factor as u16).collect::<Vec<_>>();
510+
(horizontal, vertical)
511+
} else {
512+
(vec![1], vec![1])
513+
};
529514

530-
(x, y)
531-
};
515+
// This also affects how many MCU values we read from stream. If it's a non-interleaved stream,
516+
// the MCUs will be exactly the block count.
517+
let (max_mcu_x, max_mcu_y) = if is_interleaved {
518+
(frame.mcu_size.width, frame.mcu_size.height)
519+
} else {
520+
(components[0].block_size.width, components[0].block_size.height)
521+
};
532522

533-
let block_offset = (block_y as usize * component.block_size.width as usize + block_x as usize) * 64;
534-
let mcu_row_offset = mcu_y as usize * component.block_size.width as usize * component.vertical_sampling_factor as usize * 64;
535-
let coefficients = if is_progressive {
536-
&mut self.coefficients[scan.component_indices[i]][block_offset .. block_offset + 64]
537-
} else if finished[i] {
538-
&mut mcu_row_coefficients[i][block_offset - mcu_row_offset .. block_offset - mcu_row_offset + 64]
539-
} else {
540-
&mut dummy_block[..]
541-
};
523+
for mcu_y in 0..max_mcu_y {
524+
if mcu_y * 8 >= frame.image_size.height {
525+
break;
526+
}
542527

543-
if scan.successive_approximation_high == 0 {
544-
decode_block(&mut self.reader,
545-
coefficients,
546-
&mut huffman,
547-
self.dc_huffman_tables[scan.dc_table_indices[i]].as_ref(),
548-
self.ac_huffman_tables[scan.ac_table_indices[i]].as_ref(),
549-
scan.spectral_selection.clone(),
550-
scan.successive_approximation_low,
551-
&mut eob_run,
552-
&mut dc_predictors[i])?;
553-
}
554-
else {
555-
decode_block_successive_approximation(&mut self.reader,
556-
coefficients,
557-
&mut huffman,
558-
self.ac_huffman_tables[scan.ac_table_indices[i]].as_ref(),
559-
scan.spectral_selection.clone(),
560-
scan.successive_approximation_low,
561-
&mut eob_run)?;
562-
}
563-
}
528+
for mcu_x in 0..max_mcu_x {
529+
if mcu_x * 8 >= frame.image_size.width {
530+
break;
564531
}
565532

566533
if self.restart_interval > 0 {
567-
let is_last_mcu = mcu_x == frame.mcu_size.width - 1 && mcu_y == frame.mcu_size.height - 1;
568-
mcus_left_until_restart -= 1;
569-
570-
if mcus_left_until_restart == 0 && !is_last_mcu {
534+
if mcus_left_until_restart == 0 {
571535
match huffman.take_marker(&mut self.reader)? {
572536
Some(Marker::RST(n)) => {
573537
if n != expected_rst_num {
@@ -587,16 +551,86 @@ impl<R: Read> Decoder<R> {
587551
None => return Err(Error::Format(format!("no marker found where RST{} was expected", expected_rst_num))),
588552
}
589553
}
554+
555+
mcus_left_until_restart -= 1;
556+
}
557+
558+
for (i, component) in components.iter().enumerate() {
559+
for v_pos in 0..mcu_vertical_samples[i] {
560+
for h_pos in 0..mcu_horizontal_samples[i] {
561+
let coefficients = if is_progressive {
562+
let block_y = (mcu_y * mcu_vertical_samples[i] + v_pos) as usize;
563+
let block_x = (mcu_x * mcu_horizontal_samples[i] + h_pos) as usize;
564+
let block_offset = (block_y * component.block_size.width as usize + block_x) * 64;
565+
&mut self.coefficients[scan.component_indices[i]][block_offset..block_offset + 64]
566+
} else if finished[i] {
567+
// Because the worker thread operates in batches as if we were always interleaved, we
568+
// need to distinguish between a single-shot buffer and one that's currently in process
569+
// (for a non-interleaved) stream
570+
let mcu_batch_current_row = if is_interleaved {
571+
0
572+
} else {
573+
mcu_y % component.vertical_sampling_factor as u16
574+
};
575+
576+
let block_y = (mcu_batch_current_row * mcu_vertical_samples[i] + v_pos) as usize;
577+
let block_x = (mcu_x * mcu_horizontal_samples[i] + h_pos) as usize;
578+
let block_offset = (block_y * component.block_size.width as usize + block_x) * 64;
579+
&mut mcu_row_coefficients[i][block_offset..block_offset + 64]
580+
} else {
581+
&mut dummy_block[..]
582+
};
583+
584+
if scan.successive_approximation_high == 0 {
585+
decode_block(&mut self.reader,
586+
coefficients,
587+
&mut huffman,
588+
self.dc_huffman_tables[scan.dc_table_indices[i]].as_ref(),
589+
self.ac_huffman_tables[scan.ac_table_indices[i]].as_ref(),
590+
scan.spectral_selection.clone(),
591+
scan.successive_approximation_low,
592+
&mut eob_run,
593+
&mut dc_predictors[i])?;
594+
}
595+
else {
596+
decode_block_successive_approximation(&mut self.reader,
597+
coefficients,
598+
&mut huffman,
599+
self.ac_huffman_tables[scan.ac_table_indices[i]].as_ref(),
600+
scan.spectral_selection.clone(),
601+
scan.successive_approximation_low,
602+
&mut eob_run)?;
603+
}
604+
}
605+
}
590606
}
591607
}
592608

593609
// Send the coefficients from this MCU row to the worker thread for dequantization and idct.
594610
for (i, component) in components.iter().enumerate() {
595611
if finished[i] {
612+
// In the event of non-interleaved streams, if we're still building the buffer out,
613+
// keep going; don't send it yet. We also need to ensure we don't skip over the last
614+
// row(s) of the image.
615+
if !is_interleaved && (mcu_y + 1) * 8 < frame.image_size.height {
616+
if (mcu_y + 1) % component.vertical_sampling_factor as u16 > 0 {
617+
continue;
618+
}
619+
}
620+
596621
let coefficients_per_mcu_row = component.block_size.width as usize * component.vertical_sampling_factor as usize * 64;
597622

598623
let row_coefficients = if is_progressive {
599-
let offset = mcu_y as usize * coefficients_per_mcu_row;
624+
// Because non-interleaved streams will have multiple MCU rows concatenated together,
625+
// the row for calculating the offset is different.
626+
let worker_mcu_y = if is_interleaved {
627+
mcu_y
628+
} else {
629+
// Explicitly doing floor-division here
630+
mcu_y / component.vertical_sampling_factor as u16
631+
};
632+
633+
let offset = worker_mcu_y as usize * coefficients_per_mcu_row;
600634
self.coefficients[scan.component_indices[i]][offset .. offset + coefficients_per_mcu_row].to_vec()
601635
} else {
602636
mem::replace(&mut mcu_row_coefficients[i], vec![0i16; coefficients_per_mcu_row])

0 commit comments

Comments
 (0)