diff --git a/Cargo.toml b/Cargo.toml index e4f9c6d2..d5339cb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jpeg-decoder" -version = "0.1.2" +version = "0.2.0" authors = ["Ulf Nilsson <kaksmet@gmail.com>"] description = "JPEG decoder" repository = "https://github.com/kaksmet/jpeg-decoder" diff --git a/examples/decode.rs b/examples/decode.rs index 18ebed82..d22b02e6 100644 --- a/examples/decode.rs +++ b/examples/decode.rs @@ -54,28 +54,29 @@ fn main() { } let mut decoder = jpeg::Decoder::new(BufReader::new(file.unwrap())); - let mut data = decoder.decode().expect("decode failed"); + let mut data = decoder.decode_pixels().expect("decode failed"); if let Some(output) = output { - let info = decoder.info().unwrap(); + let metadata = decoder.metadata().unwrap(); let output_file = File::create(output).unwrap(); - let mut encoder = png::Encoder::new(output_file, info.width as u32, info.height as u32); + let mut encoder = png::Encoder::new(output_file, metadata.width as u32, metadata.height as u32); encoder.set(png::BitDepth::Eight); - match info.pixel_format { - jpeg::PixelFormat::L8 => encoder.set(png::ColorType::Grayscale), - jpeg::PixelFormat::RGB24 => encoder.set(png::ColorType::RGB), - jpeg::PixelFormat::CMYK32 => { + match metadata.dst_color_space { + jpeg::ColorSpace::Grayscale => encoder.set(png::ColorType::Grayscale), + jpeg::ColorSpace::RGB => encoder.set(png::ColorType::RGB), + jpeg::ColorSpace::CMYK => { data = cmyk_to_rgb(&mut data); encoder.set(png::ColorType::RGB) }, + _ => panic!(), }; encoder.write_header().expect("writing png header failed").write_image_data(&data).expect("png encoding failed"); } } -fn cmyk_to_rgb(input: &mut [u8]) -> Vec<u8> { +fn cmyk_to_rgb(input: &[u8]) -> Vec<u8> { let size = input.len() - input.len() / 4; let mut output = Vec::with_capacity(size); diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 00000000..d2c60cdc --- /dev/null +++ b/src/color.rs @@ -0,0 +1,142 @@ +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ColorSpace { + /// Monochrome + Grayscale, + /// Red/Green/Blue + RGB, + /// Cyan/Magenta/Yellow/Key(black) + CMYK, + + /// Y/Cb/Cr, also known as YUV. + YCbCr, + /// Y/Cb/Cr/Key(black) + YCCK, +} + +impl ColorSpace { + pub fn num_components(&self) -> usize { + match *self { + ColorSpace::Grayscale => 1, + ColorSpace::RGB | ColorSpace::YCbCr => 3, + ColorSpace::CMYK | ColorSpace::YCCK => 4, + } + } +} + +struct Rgb { + r: u8, + g: u8, + b: u8, +} + +struct YCbCr { + y: u8, + cb: u8, + cr: u8, +} + +struct Cmyk { + c: u8, + m: u8, + y: u8, + k: u8, +} + +struct Ycck { + ycbcr: YCbCr, + k: u8, +} + +impl From<YCbCr> for Rgb { + fn from(color: YCbCr) -> Rgb { + // ITU-R BT.601 + + let y = color.y as f32; + let cb = color.cb as f32 - 128.0; + let cr = color.cr as f32 - 128.0; + + let r = y + 1.40200 * cr; + let g = y - 0.34414 * cb - 0.71414 * cr; + let b = y + 1.77200 * cb; + + Rgb { + r: clamp((r + 0.5) as i32, 0, 255) as u8, + g: clamp((g + 0.5) as i32, 0, 255) as u8, + b: clamp((b + 0.5) as i32, 0, 255) as u8, + } + } +} + +impl From<Ycck> for Cmyk { + fn from(color: Ycck) -> Cmyk { + let rgb = Rgb::from(color.ycbcr); + + Cmyk { + c: rgb.r, + m: rgb.g, + y: rgb.b, + k: color.k, + } + } +} + +fn clamp<T: PartialOrd>(value: T, min: T, max: T) -> T { + if value < min { return min; } + if value > max { return max; } + value +} + +pub trait ConvertColorSpace<To> { + fn convert(&self, to: &To, data: &mut [u8], length: usize); +} + +impl ConvertColorSpace<ColorSpace> for ColorSpace { + fn convert(&self, to: &ColorSpace, data: &mut [u8], length: usize) { + match (*self, *to) { + (ColorSpace::RGB, ColorSpace::RGB) => { + // Nothing to be done. + }, + (ColorSpace::YCbCr, ColorSpace::RGB) => { + for i in 0 .. length { + let rgb = Rgb::from(YCbCr { + y: data[i * 3], + cb: data[i * 3 + 1], + cr: data[i * 3 + 2], + }); + + data[i * 3] = rgb.r; + data[i * 3 + 1] = rgb.g; + data[i * 3 + 2] = rgb.b; + } + }, + (ColorSpace::CMYK, ColorSpace::CMYK) => { + for i in 0 .. length { + // CMYK is stored inversed. + data[i * 4] = 255 - data[i * 4]; + data[i * 4 + 1] = 255 - data[i * 4 + 1]; + data[i * 4 + 2] = 255 - data[i * 4 + 2]; + data[i * 4 + 3] = 255 - data[i * 4 + 3]; + } + }, + (ColorSpace::YCCK, ColorSpace::CMYK) => { + for i in 0 .. length { + let cmyk = Cmyk::from(Ycck { + ycbcr: YCbCr { + y: data[i * 4], + cb: data[i * 4 + 1], + cr: data[i * 4 + 2], + }, + // K is stored inversed, same as CMYK. + k: 255 - data[i * 4 + 3], + }); + + data[i * 4] = cmyk.c; + data[i * 4 + 1] = cmyk.m; + data[i * 4 + 2] = cmyk.y; + data[i * 4 + 3] = cmyk.k; + } + }, + (_, _) => panic!(), + } + } +} diff --git a/src/decoder.rs b/src/decoder.rs index 3b657b18..6dd70328 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -1,4 +1,5 @@ use byteorder::ReadBytesExt; +use color::{ColorSpace, ConvertColorSpace}; use error::{Error, Result, UnsupportedFeature}; use euclid::{Point2D, Rect, Size2D}; use huffman::{HuffmanDecoder, HuffmanTable}; @@ -16,47 +17,69 @@ use worker_thread::{RowData, spawn_worker_thread, WorkerMsg}; pub const MAX_COMPONENTS: usize = 4; -static UNZIGZAG: [u8; 64] = [ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, +// Figure A.6 +pub static ZIGZAG: [u8; 64] = [ + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63, ]; #[derive(Clone, Copy, Debug, PartialEq)] -pub enum PixelFormat { - L8, // Luminance, 8 bits per channel - RGB24, // RGB, 8 bits per channel - CMYK32, // CMYK, 8 bits per channel +enum DataType { + Metadata, + Coefficients, + Pixels, } -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct ImageInfo { +enum Image { + /// Coefficients and quantization tables for each plane, stored in zigzag order. + Coefficients(Vec<Vec<i16>>, Vec<[u8; 64]>), + Pixels(Vec<u8>), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Metadata { + /// Width of the image. pub width: u16, + /// Height of the image. pub height: u16, - pub pixel_format: PixelFormat, + + /// Color space the decoded pixels will be in. + /// It can be either `Grayscale`, `RGB` or `CMYK`. + pub dst_color_space: ColorSpace, + /// Color space the compressed image is stored in. + pub src_color_space: ColorSpace, + + /// Width and height of each plane in number of 8x8 blocks. + pub plane_size_blocks: Vec<(u16, u16)>, + /// Horizontal and vertical sampling factor of each plane. + pub plane_sampling_factor: Vec<(u8, u8)>, + + /// Maximum horizontal and vertical sampling factor. + pub max_sampling_factor: (u8, u8), } pub struct Decoder<R> { reader: R, - // Used for progressive JPEGs. - coefficients: Vec<Vec<i16>>, - frame: Option<FrameInfo>, dc_huffman_tables: Vec<Option<HuffmanTable>>, ac_huffman_tables: Vec<Option<HuffmanTable>>, quantization_tables: [Option<Arc<[u8; 64]>>; 4], + metadata: Option<Metadata>, restart_interval: u16, color_transform: Option<AdobeColorTransform>, is_jfif: bool, - // Used for detecting the last scan for components. + // Used for progressive JPEGs. + coefficients: Vec<Vec<i16>>, + // Used for detecting the last scan of components. coefficient_complete: Vec<[bool; 64]>, } @@ -64,50 +87,50 @@ impl<R: Read> Decoder<R> { pub fn new(reader: R) -> Decoder<R> { Decoder { reader: reader, - coefficients: Vec::new(), frame: None, dc_huffman_tables: vec![None, None, None, None], ac_huffman_tables: vec![None, None, None, None], quantization_tables: [None, None, None, None], + metadata: None, restart_interval: 0, color_transform: None, is_jfif: false, + coefficients: Vec::new(), coefficient_complete: Vec::new(), } } - pub fn info(&self) -> Option<ImageInfo> { - match self.frame { - Some(ref frame) => { - let pixel_format = match frame.components.len() { - 1 => PixelFormat::L8, - 3 => PixelFormat::RGB24, - 4 => PixelFormat::CMYK32, - _ => panic!(), - }; + pub fn metadata(&self) -> Option<Metadata> { + self.metadata.clone() + } - Some(ImageInfo { - width: frame.image_size.width, - height: frame.image_size.height, - pixel_format: pixel_format, - }) - }, - None => None, - } + pub fn read_metadata(&mut self) -> Result<()> { + self.decode(DataType::Metadata).map(|_| ()) } - pub fn read_info(&mut self) -> Result<()> { - self.decode_internal(true).map(|_| ()) + pub fn decode_pixels(&mut self) -> Result<Vec<u8>> { + self.decode(DataType::Pixels).map(|data| { + match data { + Some(Image::Pixels(pixels)) => pixels, + _ => panic!(), + } + }) } - pub fn decode(&mut self) -> Result<Vec<u8>> { - self.decode_internal(false) + /// Returns coefficients and quantization tables for each plane, stored in zigzag order. + pub fn decode_coefficients(&mut self) -> Result<(Vec<Vec<i16>>, Vec<[u8; 64]>)> { + self.decode(DataType::Coefficients).map(|data| { + match data { + Some(Image::Coefficients(coefficients, quantization_tables)) => (coefficients, quantization_tables), + _ => panic!(), + } + }) } - fn decode_internal(&mut self, stop_after_metadata: bool) -> Result<Vec<u8>> { - if stop_after_metadata && self.frame.is_some() { + fn decode(&mut self, data_type: DataType) -> Result<Option<Image>> { + if data_type == DataType::Metadata && self.frame.is_some() { // The metadata has already been read. - return Ok(Vec::new()); + return Ok(None); } else if self.frame.is_none() && (try!(self.reader.read_u8()) != 0xFF || try!(self.reader.read_u8()) != Marker::SOI as u8) { return Err(Error::Format("first two bytes is not a SOI marker".to_owned())); @@ -163,6 +186,13 @@ impl<R: Read> Decoder<R> { return Err(Error::Unsupported(UnsupportedFeature::SubsamplingRatio)); } + let width = frame.image_size.width; + let height = frame.image_size.height; + let h_max = frame.components.iter().map(|c| c.horizontal_sampling_factor).max().unwrap(); + let v_max = frame.components.iter().map(|c| c.vertical_sampling_factor).max().unwrap(); + let mut plane_size_blocks = Vec::with_capacity(frame.components.len()); + let mut plane_sampling_factor = Vec::with_capacity(frame.components.len()); + for component in &frame.components { if frame.coding_process == CodingProcess::DctProgressive { let block_count = component.block_size.width as usize * component.block_size.height as usize; @@ -170,12 +200,26 @@ impl<R: Read> Decoder<R> { } self.coefficient_complete.push([false; 64]); + + plane_size_blocks.push((component.block_size.width, + component.block_size.height)); + plane_sampling_factor.push((component.horizontal_sampling_factor, + component.vertical_sampling_factor)); } self.frame = Some(frame); - - if stop_after_metadata { - return Ok(Vec::new()); + self.metadata = Some(Metadata { + width: width, + height: height, + dst_color_space: try!(self.dst_color_space()), + src_color_space: try!(self.src_color_space()), + plane_size_blocks: plane_size_blocks, + plane_sampling_factor: plane_sampling_factor, + max_sampling_factor: (h_max, v_max), + }); + + if data_type == DataType::Metadata { + return Ok(None); } planes = vec![Vec::new(); component_count]; @@ -186,7 +230,7 @@ impl<R: Read> Decoder<R> { if self.frame.is_none() { return Err(Error::Format("scan encountered before frame".to_owned())); } - if worker_chan.is_none() { + if worker_chan.is_none() && data_type == DataType::Pixels { worker_chan = Some(try!(spawn_worker_thread())); } @@ -202,12 +246,19 @@ impl<R: Read> Decoder<R> { let is_final_scan = scan.component_indices.iter() .map(|&i| self.coefficient_complete[i].iter().all(|&v| v)) .all(|v| v); + let produce_data = if is_final_scan { + Some(data_type) + } else { + None + }; - let (marker, data) = try!(self.decode_scan(&frame, &scan, worker_chan.as_ref().unwrap(), is_final_scan)); + let (marker, samples) = try!(self.decode_scan(&frame, &scan, worker_chan.as_ref(), produce_data)); - if let Some(data) = data { - for (i, plane) in data.into_iter().enumerate().filter(|&(_, ref plane)| !plane.is_empty()) { - planes[i] = plane; + if let Some(samples) = samples { + for (i, plane_samples) in samples.into_iter() + .enumerate() + .filter(|&(_, ref plane_samples)| !plane_samples.is_empty()) { + planes[i] = plane_samples; } } @@ -222,13 +273,7 @@ impl<R: Read> Decoder<R> { for (i, &table) in tables.into_iter().enumerate() { if let Some(table) = table { - let mut unzigzagged_table = [0u8; 64]; - - for j in 0 .. 64 { - unzigzagged_table[UNZIGZAG[j] as usize] = table[j]; - } - - self.quantization_tables[i] = Some(Arc::new(unzigzagged_table)); + self.quantization_tables[i] = Some(Arc::new(table)); } } }, @@ -306,12 +351,37 @@ impl<R: Read> Decoder<R> { previous_marker = marker; } - if planes.iter().all(|plane| !plane.is_empty()) { - let frame = self.frame.as_ref().unwrap(); - compute_image(&frame.components, &planes, frame.image_size, self.is_jfif, self.color_transform) - } - else { - Err(Error::Format("no data found".to_owned())) + match self.frame { + Some(ref frame) => { + match data_type { + DataType::Coefficients => { + let coefficients = mem::replace(&mut self.coefficients, Vec::new()); + let mut quantization_tables = Vec::with_capacity(frame.components.len()); + + for component in &frame.components { + let quantization_table = &self.quantization_tables[component.quantization_table_index]; + quantization_tables.push(*quantization_table.clone().unwrap()); + } + + Ok(Some(Image::Coefficients(coefficients, quantization_tables))) + }, + DataType::Pixels => { + if planes.iter().all(|plane| !plane.is_empty()) { + let image = try!(compute_image(try!(self.src_color_space()), + try!(self.dst_color_space()), + frame.image_size, + &frame.components, + &planes)); + Ok(Some(Image::Pixels(image))) + } + else { + Err(Error::Format("not all components has data".to_owned())) + } + }, + DataType::Metadata => panic!(), + } + }, + None => Err(Error::Format("no frame found".to_owned())), } } @@ -337,8 +407,8 @@ impl<R: Read> Decoder<R> { fn decode_scan(&mut self, frame: &FrameInfo, scan: &ScanInfo, - worker_chan: &Sender<WorkerMsg>, - produce_data: bool) + worker_chan: Option<&Sender<WorkerMsg>>, + produce_data: Option<DataType>) -> Result<(Option<Marker>, Option<Vec<Vec<u8>>>)> { assert!(scan.component_indices.len() <= MAX_COMPONENTS); @@ -361,7 +431,10 @@ impl<R: Read> Decoder<R> { return Err(Error::Format("scan makes use of unset ac huffman table".to_owned())); } - if produce_data { + let is_progressive = frame.coding_process == CodingProcess::DctProgressive; + let mut mcu_row_coefficients = Vec::with_capacity(components.len()); + + if produce_data == Some(DataType::Pixels) { // Prepare the worker thread for the work to come. for (i, component) in components.iter().enumerate() { let row_data = RowData { @@ -370,14 +443,27 @@ impl<R: Read> Decoder<R> { quantization_table: self.quantization_tables[component.quantization_table_index].clone().unwrap(), }; - try!(worker_chan.send(WorkerMsg::Start(row_data))); + try!(worker_chan.unwrap().send(WorkerMsg::Start(row_data))); + } + + if !is_progressive { + for component in &components { + let coefficients_per_mcu_row = component.block_size.width as usize * component.vertical_sampling_factor as usize * 64; + mcu_row_coefficients.push(vec![0i16; coefficients_per_mcu_row]); + } + } + } + + if !is_progressive && produce_data == Some(DataType::Coefficients) && self.coefficients.is_empty() { + for component in &frame.components { + let block_count = component.block_size.width as usize * component.block_size.height as usize; + self.coefficients.push(vec![0i16; block_count * 64]); } } let blocks_per_mcu: Vec<u16> = components.iter() .map(|c| c.horizontal_sampling_factor as u16 * c.vertical_sampling_factor as u16) .collect(); - let is_progressive = frame.coding_process == CodingProcess::DctProgressive; let is_interleaved = components.len() > 1; let mut dummy_block = [0i16; 64]; let mut huffman = HuffmanDecoder::new(); @@ -385,14 +471,6 @@ impl<R: Read> Decoder<R> { let mut restarts_left = self.restart_interval; let mut expected_rst_num = 0; let mut eob_run = 0; - let mut mcu_row_coefficients = Vec::with_capacity(components.len()); - - if produce_data && !is_progressive { - for component in &components { - let coefficients_per_mcu_row = component.block_size.width as usize * component.vertical_sampling_factor as usize * 64; - mcu_row_coefficients.push(vec![0i16; coefficients_per_mcu_row]); - } - } for mcu_y in 0 .. frame.mcu_size.height { for mcu_x in 0 .. frame.mcu_size.width { @@ -425,9 +503,9 @@ impl<R: Read> Decoder<R> { let block_offset = (block_coords.y as usize * component.block_size.width as usize + block_coords.x as usize) * 64; let mcu_row_offset = mcu_y as usize * component.block_size.width as usize * component.vertical_sampling_factor as usize * 64; - let coefficients = if is_progressive { + let coefficients = if is_progressive || produce_data == Some(DataType::Coefficients) { &mut self.coefficients[scan.component_indices[i]][block_offset .. block_offset + 64] - } else if produce_data { + } else if produce_data == Some(DataType::Pixels) { &mut mcu_row_coefficients[i][block_offset - mcu_row_offset .. block_offset - mcu_row_offset + 64] } else { &mut dummy_block[..] @@ -494,7 +572,7 @@ impl<R: Read> Decoder<R> { } } - if produce_data { + if produce_data == Some(DataType::Pixels) { // Send the coefficients from this MCU row to the worker thread for dequantization and idct. for (i, component) in components.iter().enumerate() { let coefficients_per_mcu_row = component.block_size.width as usize * component.vertical_sampling_factor as usize * 64; @@ -506,18 +584,18 @@ impl<R: Read> Decoder<R> { mem::replace(&mut mcu_row_coefficients[i], vec![0i16; coefficients_per_mcu_row]) }; - try!(worker_chan.send(WorkerMsg::AppendRow((i, row_coefficients)))); + try!(worker_chan.unwrap().send(WorkerMsg::AppendRow((i, row_coefficients)))); } } } - if produce_data { + if produce_data == Some(DataType::Pixels) { // Retrieve all the data from the worker thread. let mut data = vec![Vec::new(); frame.components.len()]; for (i, &component_index) in scan.component_indices.iter().enumerate() { let (tx, rx) = mpsc::channel(); - try!(worker_chan.send(WorkerMsg::GetResult((i, tx)))); + try!(worker_chan.unwrap().send(WorkerMsg::GetResult((i, tx)))); data[component_index] = try!(rx.recv()); } @@ -528,6 +606,38 @@ impl<R: Read> Decoder<R> { Ok((huffman.take_marker(), None)) } } + + fn src_color_space(&self) -> Result<ColorSpace> { + let frame = self.frame.as_ref().unwrap(); + + match frame.components.len() { + 1 => Ok(ColorSpace::Grayscale), + 3 => { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + match self.color_transform { + Some(AdobeColorTransform::Unknown) => Ok(ColorSpace::RGB), + _ => Ok(ColorSpace::YCbCr), + } + }, + 4 => { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + match self.color_transform { + Some(AdobeColorTransform::Unknown) => Ok(ColorSpace::CMYK), + Some(_) => Ok(ColorSpace::YCCK), + None => Err(Error::Format("4 components without Adobe APP14 metadata to tell color space".to_owned())), + } + }, + _ => panic!(), + } + } + + fn dst_color_space(&self) -> Result<ColorSpace> { + match try!(self.src_color_space()) { + ColorSpace::Grayscale => Ok(ColorSpace::Grayscale), + ColorSpace::RGB | ColorSpace::YCbCr => Ok(ColorSpace::RGB), + ColorSpace::CMYK | ColorSpace::YCCK => Ok(ColorSpace::CMYK), + } + } } fn decode_block<R: Read>(reader: &mut R, @@ -581,7 +691,7 @@ fn decode_block<R: Read>(reader: &mut R, break; } - coefficients[UNZIGZAG[index as usize] as usize] = value << successive_approximation_low; + coefficients[index as usize] = value << successive_approximation_low; index += 1; } else { @@ -610,7 +720,7 @@ fn decode_block<R: Read>(reader: &mut R, break; } - coefficients[UNZIGZAG[index as usize] as usize] = try!(huffman.receive_extend(reader, s)) << successive_approximation_low; + coefficients[index as usize] = try!(huffman.receive_extend(reader, s)) << successive_approximation_low; index += 1; } } @@ -692,7 +802,7 @@ fn decode_block_successive_approximation<R: Read>(reader: &mut R, index = try!(refine_non_zeroes(reader, coefficients, huffman, index, spectral_selection_end, zero_run_length, bit)); if value != 0 { - coefficients[UNZIGZAG[index as usize] as usize] = value; + coefficients[index as usize] = value; } index += 1; @@ -713,23 +823,21 @@ fn refine_non_zeroes<R: Read>(reader: &mut R, let mut zero_run_length = zrl; - for i in start .. end + 1 { - let index = UNZIGZAG[i as usize] as usize; - - if coefficients[index] == 0 { + for i in start as usize .. (end + 1) as usize { + if coefficients[i] == 0 { if zero_run_length == 0 { - return Ok(i); + return Ok(i as u8); } zero_run_length -= 1; } else { - if try!(huffman.get_bits(reader, 1)) == 1 && coefficients[index] & bit == 0 { - if coefficients[index] > 0 { - coefficients[index] += bit; + if try!(huffman.get_bits(reader, 1)) == 1 && coefficients[i] & bit == 0 { + if coefficients[i] > 0 { + coefficients[i] += bit; } else { - coefficients[index] -= bit; + coefficients[i] -= bit; } } } @@ -738,35 +846,34 @@ fn refine_non_zeroes<R: Read>(reader: &mut R, Ok(end) } -fn compute_image(components: &[Component], - data: &[Vec<u8>], +fn compute_image(src_color_space: ColorSpace, + dst_color_space: ColorSpace, output_size: Size2D<u16>, - is_jfif: bool, - color_transform: Option<AdobeColorTransform>) -> Result<Vec<u8>> { - if data.iter().any(|data| data.is_empty()) { - return Err(Error::Format("not all components has data".to_owned())); - } + components: &[Component], + data: &[Vec<u8>]) -> Result<Vec<u8>> { + assert_eq!(data.len(), src_color_space.num_components()); + assert!(data.iter().all(|data| !data.is_empty())); if components.len() == 1 { let component = &components[0]; if component.size.width % 8 == 0 && component.size.height % 8 == 0 { - return Ok(data[0].clone()) + Ok(data[0].clone()) } + else { + let mut buffer = vec![0u8; component.size.width as usize * component.size.height as usize]; + let line_stride = component.block_size.width as usize * 8; - let mut buffer = vec![0u8; component.size.width as usize * component.size.height as usize]; - let line_stride = component.block_size.width as usize * 8; - - for y in 0 .. component.size.height as usize { - for x in 0 .. component.size.width as usize { - buffer[y * component.size.width as usize + x] = data[0][y * line_stride + x]; + for y in 0 .. component.size.height as usize { + for x in 0 .. component.size.width as usize { + buffer[y * component.size.width as usize + x] = data[0][y * line_stride + x]; + } } - } - Ok(buffer) + Ok(buffer) + } } else { - let color_convert_func = try!(choose_color_convert_func(components.len(), is_jfif, color_transform)); let resampler = Resampler::new(components).unwrap(); let line_size = output_size.width as usize * components.len(); let mut image = vec![0u8; line_size * output_size.height as usize]; @@ -778,91 +885,9 @@ fn compute_image(components: &[Component], .enumerate() .for_each(|(row, line)| { resampler.resample_and_interleave_row(data, row, output_size.width as usize, *line); - color_convert_func(*line, output_size.width as usize); + src_color_space.convert(&dst_color_space, *line, output_size.width as usize); }); Ok(image) } } - -fn choose_color_convert_func(component_count: usize, - _is_jfif: bool, - color_transform: Option<AdobeColorTransform>) - -> Result<fn(&mut [u8], usize)> { - match component_count { - 3 => { - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - // Unknown means the data is RGB, so we don't need to perform any color conversion on it. - if color_transform == Some(AdobeColorTransform::Unknown) { - Ok(color_convert_line_null) - } - else { - Ok(color_convert_line_ycbcr) - } - }, - 4 => { - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - match color_transform { - Some(AdobeColorTransform::Unknown) => Ok(color_convert_line_cmyk), - Some(_) => Ok(color_convert_line_ycck), - None => Err(Error::Format("4 components without Adobe APP14 metadata to tell color space".to_owned())), - } - }, - _ => panic!(), - } -} - -fn color_convert_line_null(_data: &mut [u8], _width: usize) { -} - -fn color_convert_line_ycbcr(data: &mut [u8], width: usize) { - for i in 0 .. width { - let (r, g, b) = ycbcr_to_rgb(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]); - - data[i * 3] = r; - data[i * 3 + 1] = g; - data[i * 3 + 2] = b; - } -} - -fn color_convert_line_ycck(data: &mut [u8], width: usize) { - for i in 0 .. width { - let (r, g, b) = ycbcr_to_rgb(data[i * 4], data[i * 4 + 1], data[i * 4 + 2]); - let k = data[i * 4 + 3]; - - data[i * 4] = r; - data[i * 4 + 1] = g; - data[i * 4 + 2] = b; - data[i * 4 + 3] = 255 - k; - } -} - -fn color_convert_line_cmyk(data: &mut [u8], width: usize) { - for i in 0 .. width { - data[i * 4] = 255 - data[i * 4]; - data[i * 4 + 1] = 255 - data[i * 4 + 1]; - data[i * 4 + 2] = 255 - data[i * 4 + 2]; - data[i * 4 + 3] = 255 - data[i * 4 + 3]; - } -} - -// ITU-R BT.601 -fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) { - let y = y as f32; - let cb = cb as f32 - 128.0; - let cr = cr as f32 - 128.0; - - let r = y + 1.40200 * cr; - let g = y - 0.34414 * cb - 0.71414 * cr; - let b = y + 1.77200 * cb; - - (clamp((r + 0.5) as i32, 0, 255) as u8, - clamp((g + 0.5) as i32, 0, 255) as u8, - clamp((b + 0.5) as i32, 0, 255) as u8) -} - -fn clamp<T: PartialOrd>(value: T, min: T, max: T) -> T { - if value < min { return min; } - if value > max { return max; } - value -} diff --git a/src/idct.rs b/src/idct.rs index 49d1645d..75e8a9b1 100644 --- a/src/idct.rs +++ b/src/idct.rs @@ -1,3 +1,5 @@ +use decoder::ZIGZAG; + // Malicious JPEG files can cause operations in the idct to overflow. // One example is tests/crashtest/imagetestsuite/b0b8914cc5f7a6eff409f16d8cc236c5.jpg // That's why wrapping operators are needed. @@ -11,10 +13,14 @@ pub fn dequantize_and_idct_block(coefficients: &[i16], quantization_table: &[u8; // columns for i in 0 .. 8 { // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if coefficients[i + 8] == 0 && coefficients[i + 16] == 0 && coefficients[i + 24] == 0 && - coefficients[i + 32] == 0 && coefficients[i + 40] == 0 && coefficients[i + 48] == 0 && - coefficients[i + 56] == 0 { - let dcterm = (coefficients[i] as i32 * quantization_table[i] as i32).wrapping_shl(2); + if coefficients[ZIGZAG[i + 8] as usize] == 0 && + coefficients[ZIGZAG[i + 16] as usize] == 0 && + coefficients[ZIGZAG[i + 24] as usize] == 0 && + coefficients[ZIGZAG[i + 32] as usize] == 0 && + coefficients[ZIGZAG[i + 40] as usize] == 0 && + coefficients[ZIGZAG[i + 48] as usize] == 0 && + coefficients[ZIGZAG[i + 56] as usize] == 0 { + let dcterm = (coefficients[ZIGZAG[i] as usize] as i32 * quantization_table[ZIGZAG[i] as usize] as i32).wrapping_shl(2); temp[i] = dcterm; temp[i + 8] = dcterm; temp[i + 16] = dcterm; @@ -25,14 +31,14 @@ pub fn dequantize_and_idct_block(coefficients: &[i16], quantization_table: &[u8; temp[i + 56] = dcterm; } else { - let s0 = coefficients[i] as i32 * quantization_table[i] as i32; - let s1 = coefficients[i + 8] as i32 * quantization_table[i + 8] as i32; - let s2 = coefficients[i + 16] as i32 * quantization_table[i + 16] as i32; - let s3 = coefficients[i + 24] as i32 * quantization_table[i + 24] as i32; - let s4 = coefficients[i + 32] as i32 * quantization_table[i + 32] as i32; - let s5 = coefficients[i + 40] as i32 * quantization_table[i + 40] as i32; - let s6 = coefficients[i + 48] as i32 * quantization_table[i + 48] as i32; - let s7 = coefficients[i + 56] as i32 * quantization_table[i + 56] as i32; + let s0 = coefficients[ZIGZAG[i] as usize] as i32 * quantization_table[ZIGZAG[i] as usize] as i32; + let s1 = coefficients[ZIGZAG[i + 8] as usize] as i32 * quantization_table[ZIGZAG[i + 8] as usize] as i32; + let s2 = coefficients[ZIGZAG[i + 16] as usize] as i32 * quantization_table[ZIGZAG[i + 16] as usize] as i32; + let s3 = coefficients[ZIGZAG[i + 24] as usize] as i32 * quantization_table[ZIGZAG[i + 24] as usize] as i32; + let s4 = coefficients[ZIGZAG[i + 32] as usize] as i32 * quantization_table[ZIGZAG[i + 32] as usize] as i32; + let s5 = coefficients[ZIGZAG[i + 40] as usize] as i32 * quantization_table[ZIGZAG[i + 40] as usize] as i32; + let s6 = coefficients[ZIGZAG[i + 48] as usize] as i32 * quantization_table[ZIGZAG[i + 48] as usize] as i32; + let s7 = coefficients[ZIGZAG[i + 56] as usize] as i32 * quantization_table[ZIGZAG[i + 56] as usize] as i32; let p2 = s2; let p3 = s6; diff --git a/src/lib.rs b/src/lib.rs index 27f66ece..f01ca12d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,11 @@ extern crate euclid; extern crate num; extern crate rayon; -pub use decoder::{Decoder, ImageInfo, PixelFormat}; +pub use color::ColorSpace; +pub use decoder::{Decoder, Metadata}; pub use error::{Error, UnsupportedFeature}; +mod color; mod decoder; mod error; mod huffman; diff --git a/tests/crashtest/mod.rs b/tests/crashtest/mod.rs index 5b007581..9a4e7f6b 100644 --- a/tests/crashtest/mod.rs +++ b/tests/crashtest/mod.rs @@ -12,6 +12,6 @@ fn crashtest() { for path in &files { let file = File::open(path).unwrap(); let mut decoder = jpeg::Decoder::new(BufReader::new(file)); - let _ = decoder.decode(); + let _ = decoder.decode_pixels(); } } diff --git a/tests/lib.rs b/tests/lib.rs index ec2220dc..d200e841 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -10,19 +10,19 @@ mod crashtest; mod reftest; #[test] -fn read_info() { +fn read_metadata() { let path = Path::new("tests").join("reftest").join("images").join("mozilla").join("jpg-progressive.jpg"); let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - let ref_data = decoder.decode().unwrap(); - let ref_info = decoder.info().unwrap(); + let ref_data = decoder.decode_pixels().unwrap(); + let ref_metadata = decoder.metadata().unwrap(); decoder = jpeg::Decoder::new(File::open(&path).unwrap()); - decoder.read_info().unwrap(); - let info = decoder.info().unwrap(); - let data = decoder.decode().unwrap(); + decoder.read_metadata().unwrap(); + let metadata = decoder.metadata().unwrap(); + let data = decoder.decode_pixels().unwrap(); - assert_eq!(info, decoder.info().unwrap()); - assert_eq!(info, ref_info); + assert_eq!(metadata, decoder.metadata().unwrap()); + assert_eq!(metadata, ref_metadata); assert_eq!(data, ref_data); } diff --git a/tests/reftest/mod.rs b/tests/reftest/mod.rs index a35f5ff1..ab45f404 100644 --- a/tests/reftest/mod.rs +++ b/tests/reftest/mod.rs @@ -18,34 +18,34 @@ fn reftest() { fn reftest_file(path: &Path) { let file = File::open(path).unwrap(); let mut decoder = jpeg::Decoder::new(file); - let mut data = decoder.decode().expect(&format!("failed to decode file: {:?}", path)); - let info = decoder.info().unwrap(); - let mut pixel_format = info.pixel_format; + let mut data = decoder.decode_pixels().expect(&format!("failed to decode file: {:?}", path)); + let metadata = decoder.metadata().unwrap(); + let mut color_space = metadata.dst_color_space; - if pixel_format == jpeg::PixelFormat::CMYK32 { + if color_space == jpeg::ColorSpace::CMYK { data = cmyk_to_rgb(&data); - pixel_format = jpeg::PixelFormat::RGB24; + color_space = jpeg::ColorSpace::RGB; } let ref_file = File::open(path.with_extension("png")).unwrap(); - let (ref_info, mut ref_reader) = png::Decoder::new(ref_file).read_info().expect("png failed to read info"); + let (ref_metadata, mut ref_reader) = png::Decoder::new(ref_file).read_info().expect("png failed to read info"); - assert_eq!(ref_info.width, info.width as u32); - assert_eq!(ref_info.height, info.height as u32); - assert_eq!(ref_info.bit_depth, png::BitDepth::Eight); + assert_eq!(ref_metadata.width, metadata.width as u32); + assert_eq!(ref_metadata.height, metadata.height as u32); + assert_eq!(ref_metadata.bit_depth, png::BitDepth::Eight); - let mut ref_data = vec![0; ref_info.buffer_size()]; + let mut ref_data = vec![0; ref_metadata.buffer_size()]; ref_reader.next_frame(&mut ref_data).expect("png decode failed"); - let mut ref_pixel_format = ref_info.color_type; + let mut ref_color_type = ref_metadata.color_type; - if ref_pixel_format == png::ColorType::RGBA { + if ref_color_type == png::ColorType::RGBA { ref_data = rgba_to_rgb(&ref_data); - ref_pixel_format = png::ColorType::RGB; + ref_color_type = png::ColorType::RGB; } - match pixel_format { - jpeg::PixelFormat::L8 => assert_eq!(ref_pixel_format, png::ColorType::Grayscale), - jpeg::PixelFormat::RGB24 => assert_eq!(ref_pixel_format, png::ColorType::RGB), + match color_space { + jpeg::ColorSpace::Grayscale => assert_eq!(ref_color_type, png::ColorType::Grayscale), + jpeg::ColorSpace::RGB => assert_eq!(ref_color_type, png::ColorType::RGB), _ => panic!(), } @@ -72,9 +72,9 @@ fn reftest_file(path: &Path) { if pixels.iter().any(|&a| a < 255) { let output_path = path.with_file_name(format!("{}-diff.png", path.file_stem().unwrap().to_str().unwrap())); let output = File::create(&output_path).unwrap(); - let mut encoder = png::Encoder::new(output, info.width as u32, info.height as u32); + let mut encoder = png::Encoder::new(output, metadata.width as u32, metadata.height as u32); encoder.set(png::BitDepth::Eight); - encoder.set(ref_pixel_format); + encoder.set(ref_color_type); encoder.write_header().expect("png failed to write header").write_image_data(&pixels).expect("png failed to write data"); panic!("decoding difference: {:?}, maximum difference was {}", output_path, max_diff);