Skip to content

Commit 5607279

Browse files
authored
Merge pull request #117 from kevinmehall/idct-scale
Add the ability to scale down images during decode (IDCT scaling)
2 parents f79f57e + 67dc09e commit 5607279

File tree

9 files changed

+244
-36
lines changed

9 files changed

+244
-36
lines changed

src/decoder.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ impl<R: Read> Decoder<R> {
100100
};
101101

102102
Some(ImageInfo {
103-
width: frame.image_size.width,
104-
height: frame.image_size.height,
103+
width: frame.output_size.width,
104+
height: frame.output_size.height,
105105
pixel_format: pixel_format,
106106
})
107107
},
@@ -116,6 +116,23 @@ impl<R: Read> Decoder<R> {
116116
self.decode_internal(true).map(|_| ())
117117
}
118118

119+
/// Configure the decoder to scale the image during decoding.
120+
///
121+
/// This efficiently scales the image by the smallest supported scale
122+
/// factor that produces an image larger than or equal to the requested
123+
/// size in at least one axis. The currently implemented scale factors
124+
/// are 1/8, 1/4, 1/2 and 1.
125+
///
126+
/// To generate a thumbnail of an exact size, pass the desired size and
127+
/// then scale to the final size using a traditional resampling algorithm.
128+
pub fn scale(&mut self, requested_width: u16, requested_height: u16) -> Result<(u16, u16)> {
129+
self.read_info()?;
130+
let frame = self.frame.as_mut().unwrap();
131+
let idct_size = crate::idct::choose_idct_size(frame.image_size, Dimensions{ width: requested_width, height: requested_height });
132+
frame.update_idct_size(idct_size);
133+
Ok((frame.output_size.width, frame.output_size.height))
134+
}
135+
119136
/// Decodes the image and returns the decoded pixels if successful.
120137
pub fn decode(&mut self) -> Result<Vec<u8>> {
121138
self.decode_internal(false)
@@ -329,7 +346,7 @@ impl<R: Read> Decoder<R> {
329346
}
330347

331348
let frame = self.frame.as_ref().unwrap();
332-
compute_image(&frame.components, &planes, frame.image_size, self.is_jfif, self.color_transform)
349+
compute_image(&frame.components, &planes, frame.output_size, self.is_jfif, self.color_transform)
333350
}
334351

335352
fn read_marker(&mut self) -> Result<Marker> {
@@ -435,7 +452,7 @@ impl<R: Read> Decoder<R> {
435452
let x = (block_num % blocks_per_row) as u16;
436453
let y = (block_num / blocks_per_row) as u16;
437454

438-
if x * 8 >= component.size.width || y * 8 >= component.size.height {
455+
if x * component.dct_scale as u16 >= component.size.width || y * component.dct_scale as u16 >= component.size.height {
439456
continue;
440457
}
441458

@@ -764,12 +781,15 @@ fn compute_image(components: &[Component],
764781
return Ok(data[0].clone())
765782
}
766783

767-
let mut buffer = vec![0u8; component.size.width as usize * component.size.height as usize];
768-
let line_stride = component.block_size.width as usize * 8;
784+
let width = component.size.width as usize;
785+
let height = component.size.height as usize;
786+
787+
let mut buffer = vec![0u8; width * height];
788+
let line_stride = width * component.dct_scale;
769789

770-
for y in 0 .. component.size.height as usize {
771-
for x in 0 .. component.size.width as usize {
772-
buffer[y * component.size.width as usize + x] = data[0][y * line_stride + x];
790+
for y in 0 .. width {
791+
for x in 0 .. height {
792+
buffer[y * width + x] = data[0][y * line_stride + x];
773793
}
774794
}
775795

src/idct.rs

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,51 @@
11
// Malicious JPEG files can cause operations in the idct to overflow.
22
// One example is tests/crashtest/images/imagetestsuite/b0b8914cc5f7a6eff409f16d8cc236c5.jpg
33
// That's why wrapping operators are needed.
4+
use crate::parser::Dimensions;
5+
6+
pub(crate) fn choose_idct_size(full_size: Dimensions, requested_size: Dimensions) -> usize {
7+
fn scaled(len: u16, scale: usize) -> u16 { ((len as u32 * scale as u32 - 1) / 8 + 1) as u16 }
8+
9+
for &scale in &[1, 2, 4] {
10+
if scaled(full_size.width, scale) >= requested_size.width || scaled(full_size.height, scale) >= requested_size.height {
11+
return scale;
12+
}
13+
}
14+
15+
return 8;
16+
}
17+
18+
#[test]
19+
fn test_choose_idct_size() {
20+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 200, height: 200}), 1);
21+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 500, height: 500}), 1);
22+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 684, height: 456}), 1);
23+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 999, height: 456}), 1);
24+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 684, height: 999}), 1);
25+
assert_eq!(choose_idct_size(Dimensions{width: 500, height: 333}, Dimensions{width: 63, height: 42}), 1);
26+
27+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 685, height: 999}), 2);
28+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 1000, height: 1000}), 2);
29+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 1400, height: 1400}), 4);
30+
31+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 5472, height: 3648}), 8);
32+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 16384, height: 16384}), 8);
33+
assert_eq!(choose_idct_size(Dimensions{width: 1, height: 1}, Dimensions{width: 65535, height: 65535}), 8);
34+
assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 16384, height: 16384}), 8);
35+
}
36+
37+
pub(crate) fn dequantize_and_idct_block(scale: usize, coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) {
38+
match scale {
39+
8 => dequantize_and_idct_block_8x8(coefficients, quantization_table, output_linestride, output),
40+
4 => dequantize_and_idct_block_4x4(coefficients, quantization_table, output_linestride, output),
41+
2 => dequantize_and_idct_block_2x2(coefficients, quantization_table, output_linestride, output),
42+
1 => dequantize_and_idct_block_1x1(coefficients, quantization_table, output_linestride, output),
43+
_ => panic!("Unsupported IDCT scale {}/8", scale),
44+
}
45+
}
446

547
// This is based on stb_image's 'stbi__idct_block'.
6-
pub fn dequantize_and_idct_block(coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) {
48+
fn dequantize_and_idct_block_8x8(coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) {
749
debug_assert_eq!(coefficients.len(), 64);
850

951
let mut temp = [0i32; 64];
@@ -155,6 +197,103 @@ pub fn dequantize_and_idct_block(coefficients: &[i16], quantization_table: &[u16
155197
}
156198
}
157199

200+
// 4x4 and 2x2 IDCT based on Rakesh Dugad and Narendra Ahuja: "A Fast Scheme for Image Size Change in the Compressed Domain" (2001).
201+
// http://sylvana.net/jpegcrop/jidctred/
202+
fn dequantize_and_idct_block_4x4(coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) {
203+
debug_assert_eq!(coefficients.len(), 64);
204+
let mut temp = [0i32; 4*4];
205+
206+
const CONST_BITS: u32 = 12;
207+
const PASS1_BITS: u32 = 2;
208+
const FINAL_BITS: u32 = CONST_BITS + PASS1_BITS + 3;
209+
210+
// columns
211+
for i in 0 .. 4 {
212+
let s0 = coefficients[i + 8*0] as i32 * quantization_table[i + 8*0] as i32;
213+
let s1 = coefficients[i + 8*1] as i32 * quantization_table[i + 8*1] as i32;
214+
let s2 = coefficients[i + 8*2] as i32 * quantization_table[i + 8*2] as i32;
215+
let s3 = coefficients[i + 8*3] as i32 * quantization_table[i + 8*3] as i32;
216+
217+
let x0 = s0.wrapping_add(s2).wrapping_shl(PASS1_BITS);
218+
let x2 = s0.wrapping_sub(s2).wrapping_shl(PASS1_BITS);
219+
220+
let p1 = s1.wrapping_add(s3).wrapping_mul(stbi_f2f(0.541196100));
221+
let t0 = p1.wrapping_add(s3.wrapping_mul(stbi_f2f(-1.847759065))).wrapping_add(512).wrapping_shr(CONST_BITS - PASS1_BITS);
222+
let t2 = p1.wrapping_add(s1.wrapping_mul(stbi_f2f( 0.765366865))).wrapping_add(512).wrapping_shr(CONST_BITS - PASS1_BITS);
223+
224+
temp[i + 4*0] = x0.wrapping_add(t2);
225+
temp[i + 4*3] = x0.wrapping_sub(t2);
226+
temp[i + 4*1] = x2.wrapping_add(t0);
227+
temp[i + 4*2] = x2.wrapping_sub(t0);
228+
}
229+
230+
for i in 0 .. 4 {
231+
let s0 = temp[i * 4 + 0];
232+
let s1 = temp[i * 4 + 1];
233+
let s2 = temp[i * 4 + 2];
234+
let s3 = temp[i * 4 + 3];
235+
236+
let x0 = s0.wrapping_add(s2).wrapping_shl(CONST_BITS);
237+
let x2 = s0.wrapping_sub(s2).wrapping_shl(CONST_BITS);
238+
239+
let p1 = s1.wrapping_add(s3).wrapping_mul(stbi_f2f(0.541196100));
240+
let t0 = p1.wrapping_add(s3.wrapping_mul(stbi_f2f(-1.847759065)));
241+
let t2 = p1.wrapping_add(s1.wrapping_mul(stbi_f2f(0.765366865)));
242+
243+
// constants scaled things up by 1<<12, plus we had 1<<2 from first
244+
// loop, plus horizontal and vertical each scale by sqrt(8) so together
245+
// we've got an extra 1<<3, so 1<<17 total we need to remove.
246+
// so we want to round that, which means adding 0.5 * 1<<17,
247+
// aka 65536. Also, we'll end up with -128 to 127 that we want
248+
// to encode as 0..255 by adding 128, so we'll add that before the shift
249+
let x0 = x0.wrapping_add((1 << (FINAL_BITS - 1)) + (128 << FINAL_BITS));
250+
let x2 = x2.wrapping_add((1 << (FINAL_BITS - 1)) + (128 << FINAL_BITS));
251+
252+
output[i * output_linestride + 0] = stbi_clamp(x0.wrapping_add(t2).wrapping_shr(FINAL_BITS));
253+
output[i * output_linestride + 3] = stbi_clamp(x0.wrapping_sub(t2).wrapping_shr(FINAL_BITS));
254+
output[i * output_linestride + 1] = stbi_clamp(x2.wrapping_add(t0).wrapping_shr(FINAL_BITS));
255+
output[i * output_linestride + 2] = stbi_clamp(x2.wrapping_sub(t0).wrapping_shr(FINAL_BITS));
256+
}
257+
}
258+
259+
fn dequantize_and_idct_block_2x2(coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) {
260+
debug_assert_eq!(coefficients.len(), 64);
261+
262+
const SCALE_BITS: u32 = 3;
263+
264+
// Column 0
265+
let s00 = coefficients[8*0] as i32 * quantization_table[8*0] as i32;
266+
let s10 = coefficients[8*1] as i32 * quantization_table[8*1] as i32;
267+
268+
let x0 = s00.wrapping_add(s10);
269+
let x2 = s00.wrapping_sub(s10);
270+
271+
// Column 1
272+
let s01 = coefficients[8*0+1] as i32 * quantization_table[8*0+1] as i32;
273+
let s11 = coefficients[8*1+1] as i32 * quantization_table[8*1+1] as i32;
274+
275+
let x1 = s01.wrapping_add(s11);
276+
let x3 = s01.wrapping_sub(s11);
277+
278+
let x0 = x0.wrapping_add((1 << (SCALE_BITS-1)) + (128 << SCALE_BITS));
279+
let x2 = x2.wrapping_add((1 << (SCALE_BITS-1)) + (128 << SCALE_BITS));
280+
281+
// Row 0
282+
output[0] = stbi_clamp(x0.wrapping_add(x1).wrapping_shr(SCALE_BITS));
283+
output[1] = stbi_clamp(x0.wrapping_sub(x1).wrapping_shr(SCALE_BITS));
284+
285+
// Row 1
286+
output[output_linestride + 0] = stbi_clamp(x2.wrapping_add(x3).wrapping_shr(SCALE_BITS));
287+
output[output_linestride + 1] = stbi_clamp(x2.wrapping_sub(x3).wrapping_shr(SCALE_BITS));
288+
}
289+
290+
fn dequantize_and_idct_block_1x1(coefficients: &[i16], quantization_table: &[u16; 64], _output_linestride: usize, output: &mut [u8]) {
291+
debug_assert_eq!(coefficients.len(), 64);
292+
293+
let s0 = (coefficients[0] as i32 * quantization_table[0] as i32).wrapping_add(128 * 8) / 8;
294+
output[0] = stbi_clamp(s0);
295+
}
296+
158297
// take a -128..127 value and stbi__clamp it and convert to 0..255
159298
fn stbi_clamp(x: i32) -> u8
160299
{

src/parser.rs

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct FrameInfo {
3434
pub precision: u8,
3535

3636
pub image_size: Dimensions,
37+
pub output_size: Dimensions,
3738
pub mcu_size: Dimensions,
3839
pub components: Vec<Component>,
3940
}
@@ -58,6 +59,8 @@ pub struct Component {
5859

5960
pub quantization_table_index: usize,
6061

62+
pub dct_scale: usize,
63+
6164
pub size: Dimensions,
6265
pub block_size: Dimensions,
6366
}
@@ -79,6 +82,21 @@ pub enum AdobeColorTransform {
7982
YCCK,
8083
}
8184

85+
impl FrameInfo {
86+
pub(crate) fn update_idct_size(&mut self, idct_size: usize) {
87+
for component in &mut self.components {
88+
component.dct_scale = idct_size;
89+
}
90+
91+
update_component_sizes(self.image_size, &mut self.components);
92+
93+
self.output_size = Dimensions {
94+
width: (self.image_size.width as f32 * idct_size as f32 / 8.0).ceil() as u16,
95+
height: (self.image_size.height as f32 * idct_size as f32 / 8.0).ceil() as u16
96+
};
97+
}
98+
}
99+
82100
fn read_length<R: Read>(reader: &mut R, marker: Marker) -> Result<usize> {
83101
assert!(marker.has_length());
84102

@@ -201,36 +219,45 @@ pub fn parse_sof<R: Read>(reader: &mut R, marker: Marker) -> Result<FrameInfo> {
201219
horizontal_sampling_factor: horizontal_sampling_factor,
202220
vertical_sampling_factor: vertical_sampling_factor,
203221
quantization_table_index: quantization_table_index as usize,
222+
dct_scale: 8,
204223
size: Dimensions {width: 0, height: 0},
205224
block_size: Dimensions {width: 0, height: 0},
206225
});
207226
}
208227

228+
let mcu_size = update_component_sizes(Dimensions { width, height }, &mut components);
229+
230+
Ok(FrameInfo {
231+
is_baseline: is_baseline,
232+
is_differential: is_differential,
233+
coding_process: coding_process,
234+
entropy_coding: entropy_coding,
235+
precision: precision,
236+
image_size: Dimensions { width, height },
237+
output_size: Dimensions { width, height },
238+
mcu_size,
239+
components: components,
240+
})
241+
}
242+
243+
fn update_component_sizes(size: Dimensions, components: &mut [Component]) -> Dimensions {
209244
let h_max = components.iter().map(|c| c.horizontal_sampling_factor).max().unwrap();
210245
let v_max = components.iter().map(|c| c.vertical_sampling_factor).max().unwrap();
246+
211247
let mcu_size = Dimensions {
212-
width: (width as f32 / (h_max as f32 * 8.0)).ceil() as u16,
213-
height: (height as f32 / (v_max as f32 * 8.0)).ceil() as u16,
248+
width: (size.width as f32 / (h_max as f32 * 8.0)).ceil() as u16,
249+
height: (size.height as f32 / (v_max as f32 * 8.0)).ceil() as u16,
214250
};
215251

216-
for component in &mut components {
217-
component.size.width = (width as f32 * (component.horizontal_sampling_factor as f32 / h_max as f32)).ceil() as u16;
218-
component.size.height = (height as f32 * (component.vertical_sampling_factor as f32 / v_max as f32)).ceil() as u16;
252+
for component in components {
253+
component.size.width = (size.width as f32 * component.horizontal_sampling_factor as f32 * component.dct_scale as f32 / (h_max as f32 * 8.0)).ceil() as u16;
254+
component.size.height = (size.height as f32 * component.vertical_sampling_factor as f32 * component.dct_scale as f32 / (v_max as f32 * 8.0)).ceil() as u16;
219255

220256
component.block_size.width = mcu_size.width * component.horizontal_sampling_factor as u16;
221257
component.block_size.height = mcu_size.height * component.vertical_sampling_factor as u16;
222258
}
223259

224-
Ok(FrameInfo {
225-
is_baseline: is_baseline,
226-
is_differential: is_differential,
227-
coding_process: coding_process,
228-
entropy_coding: entropy_coding,
229-
precision: precision,
230-
image_size: Dimensions {width: width, height: height},
231-
mcu_size: mcu_size,
232-
components: components,
233-
})
260+
mcu_size
234261
}
235262

236263
// Section B.2.3

src/upsampler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ impl Upsampler {
2929
upsampler: upsampler,
3030
width: component.size.width as usize,
3131
height: component.size.height as usize,
32-
row_stride: component.block_size.width as usize * 8,
32+
row_stride: component.block_size.width as usize * component.dct_scale,
3333
});
3434
}
3535

src/worker/immediate.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl ImmediateWorker {
2626
assert!(self.results[data.index].is_empty());
2727

2828
self.offsets[data.index] = 0;
29-
self.results[data.index].resize(data.component.block_size.width as usize * data.component.block_size.height as usize * 64, 0u8);
29+
self.results[data.index].resize(data.component.block_size.width as usize * data.component.block_size.height as usize * data.component.dct_scale * data.component.dct_scale, 0u8);
3030
self.components[data.index] = Some(data.component);
3131
self.quantization_tables[data.index] = Some(data.quantization_table);
3232
}
@@ -36,20 +36,21 @@ impl ImmediateWorker {
3636
let component = self.components[index].as_ref().unwrap();
3737
let quantization_table = self.quantization_tables[index].as_ref().unwrap();
3838
let block_count = component.block_size.width as usize * component.vertical_sampling_factor as usize;
39-
let line_stride = component.block_size.width as usize * 8;
39+
let line_stride = component.block_size.width as usize * component.dct_scale;
4040

4141
assert_eq!(data.len(), block_count * 64);
4242

4343
for i in 0..block_count {
44-
let x = (i % component.block_size.width as usize) * 8;
45-
let y = (i / component.block_size.width as usize) * 8;
46-
dequantize_and_idct_block(&data[i * 64..(i + 1) * 64],
47-
quantization_table,
48-
line_stride,
49-
&mut self.results[index][self.offsets[index] + y * line_stride + x..]);
44+
let x = (i % component.block_size.width as usize) * component.dct_scale;
45+
let y = (i / component.block_size.width as usize) * component.dct_scale;
46+
47+
let coefficients = &data[i * 64..(i + 1) * 64];
48+
let output = &mut self.results[index][self.offsets[index] + y * line_stride + x..];
49+
50+
dequantize_and_idct_block(component.dct_scale, coefficients, quantization_table, line_stride, output);
5051
}
5152

52-
self.offsets[index] += data.len();
53+
self.offsets[index] += block_count * component.dct_scale * component.dct_scale;
5354
}
5455
pub fn get_result_immediate(&mut self, index: usize) -> Vec<u8> {
5556
mem::replace(&mut self.results[index], Vec::new())

tests/reftest/images/rgb_125x84.png

25.7 KB
Loading

tests/reftest/images/rgb_250x167.png

101 KB
Loading

tests/reftest/images/rgb_63x42.png

6.69 KB
Loading

0 commit comments

Comments
 (0)