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);