├── src ├── workspace.rs ├── lib.rs ├── run.rs ├── jpeg │ ├── decoder │ │ ├── worker │ │ │ ├── mod.rs │ │ │ ├── immediate.rs │ │ │ └── multithreaded.rs │ │ ├── error.rs │ │ ├── marker.rs │ │ ├── mod.rs │ │ ├── upsampler.rs │ │ ├── huffman.rs │ │ ├── idct.rs │ │ └── parser.rs │ ├── transform.rs │ └── encoder.rs ├── manifest.rs ├── exec.rs ├── main.rs ├── jpeg.rs ├── build.rs └── meme.rs ├── .gitignore ├── resources ├── debug.jpg ├── demo.gif ├── exe.ico ├── hank.jpg ├── release.jpg ├── rust-expert.jpg ├── trade-offer.jpg └── borrow-checker.jpg ├── .github └── workflows │ └── ci.yml ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── LICENSE-APACHE /src/workspace.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | # IDE 5 | /.idea/ 6 | -------------------------------------------------------------------------------- /resources/debug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/debug.jpg -------------------------------------------------------------------------------- /resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/demo.gif -------------------------------------------------------------------------------- /resources/exe.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/exe.ico -------------------------------------------------------------------------------- /resources/hank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/hank.jpg -------------------------------------------------------------------------------- /resources/release.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/release.jpg -------------------------------------------------------------------------------- /resources/rust-expert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/rust-expert.jpg -------------------------------------------------------------------------------- /resources/trade-offer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/trade-offer.jpg -------------------------------------------------------------------------------- /resources/borrow-checker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattsse/cargo-memex/HEAD/resources/borrow-checker.jpg -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod build; 2 | pub mod exec; 3 | pub mod jpeg; 4 | mod manifest; 5 | mod meme; 6 | pub mod run; 7 | mod workspace; 8 | -------------------------------------------------------------------------------- /src/run.rs: -------------------------------------------------------------------------------- 1 | use crate::build::BuildCommand; 2 | use crate::exec::ExecCommand; 3 | use structopt::StructOpt; 4 | 5 | /// Similarly to cargo run, but with memes 6 | #[derive(Debug, StructOpt)] 7 | #[structopt(name = "run")] 8 | pub struct RunCommand { 9 | #[structopt(flatten)] 10 | build: BuildCommand, 11 | } 12 | 13 | impl RunCommand { 14 | pub fn run(&self) -> anyhow::Result<()> { 15 | let output = self.build.run()?; 16 | ExecCommand::new(output.meme_path).run() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | 20 | test: 21 | name: Test Suite 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | override: true 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: test -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-memex" 3 | version = "0.2.0" 4 | authors = ["Matthias Seitz "] 5 | edition = "2018" 6 | description = "Compile and execute rust code as memes" 7 | license = "MIT OR Apache-2.0" 8 | readme = "README.md" 9 | homepage = "https://github.com/mattsse/cargo-memex" 10 | repository = "https://github.com/mattsse/cargo-memex" 11 | keywords = ["meme"] 12 | include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE*", "resources"] 13 | 14 | [dependencies] 15 | structopt = "0.3.21" 16 | image = { version = "0.23.14", features = ["jpeg"] } 17 | num-traits = "0.2.0" 18 | num-iter = "0.1.32" 19 | anyhow = "1.0.40" 20 | toml = "0.5.8" 21 | log = "0.4.14" 22 | pretty_env_logger = "0.4.0" 23 | reqwest = { version = "0.11.3", features = ["blocking", "json"] } 24 | serde_json = "1.0.64" 25 | -------------------------------------------------------------------------------- /src/jpeg/decoder/worker/mod.rs: -------------------------------------------------------------------------------- 1 | mod immediate; 2 | mod multithreaded; 3 | use super::error::Result; 4 | use crate::jpeg::decoder::parser::Component; 5 | use std::sync::Arc; 6 | 7 | #[cfg(any(target_arch = "asmjs", target_arch = "wasm32"))] 8 | pub use self::immediate::ImmediateWorker as PlatformWorker; 9 | #[cfg(not(any(target_arch = "asmjs", target_arch = "wasm32")))] 10 | pub use multithreaded::MultiThreadedWorker as PlatformWorker; 11 | 12 | pub struct RowData { 13 | pub index: usize, 14 | pub component: Component, 15 | pub quantization_table: Arc<[u16; 64]>, 16 | } 17 | 18 | pub trait Worker: Sized { 19 | fn new() -> Result; 20 | fn start(&mut self, row_data: RowData) -> Result<()>; 21 | fn append_row(&mut self, row: (usize, Vec)) -> Result<()>; 22 | fn get_result(&mut self, index: usize) -> Result>; 23 | } 24 | -------------------------------------------------------------------------------- /src/manifest.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use std::path::{Path, PathBuf}; 3 | use toml::value; 4 | 5 | pub struct Manifest { 6 | pub manifest_path: PathBuf, 7 | pub cargo_toml: value::Table, 8 | } 9 | 10 | impl Manifest { 11 | pub fn new(manifest_path: impl AsRef) -> anyhow::Result { 12 | let manifest_path = manifest_path.as_ref().to_path_buf(); 13 | let toml = std::fs::read_to_string(manifest_path.as_path())?; 14 | log::debug!("Parsing toml file {}", manifest_path.display()); 15 | let cargo_toml = toml::from_str::(&toml)?; 16 | Ok(Manifest { 17 | manifest_path, 18 | cargo_toml, 19 | }) 20 | } 21 | 22 | pub fn name(&self) -> anyhow::Result<&str> { 23 | self.cargo_toml 24 | .get("package") 25 | .context("package not found in toml")? 26 | .get("name") 27 | .context("package.name field not found in toml")? 28 | .as_str() 29 | .context("package.name field must be a string") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/exec.rs: -------------------------------------------------------------------------------- 1 | use crate::meme::Meme; 2 | use anyhow::Context; 3 | use std::path::{Path, PathBuf}; 4 | use std::process::Command; 5 | use structopt::StructOpt; 6 | 7 | /// Executes a memex meme 8 | #[derive(Debug, StructOpt)] 9 | #[structopt(name = "exec")] 10 | pub struct ExecCommand { 11 | #[structopt(parse(from_os_str))] 12 | meme: PathBuf, 13 | } 14 | 15 | impl ExecCommand { 16 | pub fn new(meme: impl AsRef) -> Self { 17 | Self { 18 | meme: meme.as_ref().to_path_buf(), 19 | } 20 | } 21 | 22 | pub fn run(&self) -> anyhow::Result<()> { 23 | let mut meme_exe = self.meme.clone(); 24 | meme_exe.set_extension("meme"); 25 | 26 | Meme::decode_bin_to(&self.meme, &meme_exe)?; 27 | 28 | let mut cmd = Command::new(format!("./{}", meme_exe.display())); 29 | log::debug!("Executing meme `{:?}`", cmd); 30 | let child = cmd.spawn()?; 31 | child 32 | .wait_with_output() 33 | .context(format!("Error executing `{:?}`", cmd))?; 34 | let _ = std::fs::remove_file(meme_exe); 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matthias Seitz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use cargo_memex::build::BuildCommand; 2 | use cargo_memex::exec::ExecCommand; 3 | use cargo_memex::run::RunCommand; 4 | use structopt::StructOpt; 5 | 6 | #[derive(Debug, StructOpt)] 7 | #[structopt(bin_name = "cargo")] 8 | pub(crate) enum Opts { 9 | #[structopt(name = "memex")] 10 | Memex(MemexArgs), 11 | } 12 | 13 | #[derive(Debug, StructOpt)] 14 | pub(crate) struct MemexArgs { 15 | #[structopt(subcommand)] 16 | cmd: Command, 17 | } 18 | 19 | #[derive(Debug, StructOpt)] 20 | enum Command { 21 | /// Compiles the project into a meme 22 | #[structopt(name = "build")] 23 | Build(BuildCommand), 24 | /// Builds and runs a meme 25 | #[structopt(name = "run")] 26 | Run(RunCommand), 27 | /// Executes a meme 28 | #[structopt(name = "exec")] 29 | Exec(ExecCommand), 30 | } 31 | 32 | fn main() -> Result<(), Box> { 33 | pretty_env_logger::init(); 34 | let Opts::Memex(args) = Opts::from_args(); 35 | match args.cmd { 36 | Command::Build(cmd) => { 37 | cmd.run()?; 38 | } 39 | Command::Run(cmd) => cmd.run()?, 40 | Command::Exec(cmd) => cmd.run()?, 41 | } 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /src/jpeg/decoder/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | use std::io::Error as IoError; 4 | 5 | pub type Result = ::std::result::Result; 6 | 7 | /// An enumeration over JPEG features (currently) unsupported by this library. 8 | /// 9 | /// Support for features listed here may be included in future versions of this library. 10 | #[derive(Debug)] 11 | pub enum UnsupportedFeature { 12 | /// Hierarchical JPEG. 13 | Hierarchical, 14 | /// Lossless JPEG. 15 | Lossless, 16 | /// JPEG using arithmetic entropy coding instead of Huffman coding. 17 | ArithmeticEntropyCoding, 18 | /// Sample precision in bits. 8 bit sample precision is what is currently supported. 19 | SamplePrecision(u8), 20 | /// Number of components in an image. 1, 3 and 4 components are currently supported. 21 | ComponentCount(u8), 22 | /// An image can specify a zero height in the frame header and use the DNL (Define Number of 23 | /// Lines) marker at the end of the first scan to define the number of lines in the frame. 24 | DNL, 25 | /// Subsampling ratio. 26 | SubsamplingRatio, 27 | /// A subsampling ratio not representable as an integer. 28 | NonIntegerSubsamplingRatio, 29 | } 30 | 31 | /// Errors that can occur while decoding a JPEG image. 32 | #[derive(Debug)] 33 | pub enum Error { 34 | /// The image is not formatted properly. The string contains detailed information about the 35 | /// error. 36 | Format(String), 37 | /// The image makes use of a JPEG feature not (currently) supported by this library. 38 | Unsupported(UnsupportedFeature), 39 | /// An I/O error occurred while decoding the image. 40 | Io(IoError), 41 | /// An internal error occurred while decoding the image. 42 | Internal(Box), //TODO: not used, can be removed with the next version bump 43 | } 44 | 45 | impl fmt::Display for Error { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | match *self { 48 | Error::Format(ref desc) => write!(f, "invalid JPEG format: {}", desc), 49 | Error::Unsupported(ref feat) => write!(f, "unsupported JPEG feature: {:?}", feat), 50 | Error::Io(ref err) => err.fmt(f), 51 | Error::Internal(ref err) => err.fmt(f), 52 | } 53 | } 54 | } 55 | 56 | impl StdError for Error { 57 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 58 | match *self { 59 | Error::Io(ref err) => Some(err), 60 | Error::Internal(ref err) => Some(&**err), 61 | _ => None, 62 | } 63 | } 64 | } 65 | 66 | impl From for Error { 67 | fn from(err: IoError) -> Error { 68 | Error::Io(err) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/jpeg.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | pub mod decoder; 4 | pub mod encoder; 5 | mod transform; 6 | 7 | /// There are several APP markers used by "popular" tools https://github.com/corkami/formats/blob/master/image/jpeg.md#jpeg-1997 8 | /// 9 | /// randomly chose 8 (what is a SPIFF?) to store all data in consecutive APP8 markers 10 | pub const APP7: u8 = 0xE8; 11 | 12 | /// app0 marker 13 | pub const APP0_DECIMAL: u8 = 224; 14 | 15 | /// It's common practice to put the vendor name at the start 16 | static MEMEX_VENDOR: &[u8] = b"memex\0"; 17 | 18 | pub struct AppMarkerConfig { 19 | pub vendor: Cow<'static, [u8]>, 20 | pub marker: u8, 21 | } 22 | 23 | impl AppMarkerConfig { 24 | /// Maximum amount of data that can we can store inside a single APP marker. 25 | /// The length of the segment that follows the app marker is encoded as be u16 (2 bytes). 26 | /// The length is part of the segment which leaves us with 2^16-2-len(vendor) as usable space. 27 | pub fn data_len(&self) -> usize { 28 | self.segment_data_len() - self.vendor.len() 29 | } 30 | 31 | /// The length of the segment including the vendor name 32 | pub fn segment_data_len(&self) -> usize { 33 | u16::MAX as usize - 2 34 | } 35 | } 36 | 37 | impl Default for AppMarkerConfig { 38 | fn default() -> Self { 39 | Self { 40 | vendor: MEMEX_VENDOR.into(), 41 | marker: APP7, 42 | } 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | use crate::{ 50 | jpeg::decoder::AppMarkerJpegDecoder, jpeg::encoder::AppMarkerJpegEncoder, meme::TRADE_OFFER, 51 | }; 52 | use image::{ 53 | codecs::jpeg::{JpegDecoder, JpegEncoder}, 54 | ColorType, DynamicImage, ImageDecoder, 55 | }; 56 | use std::{ 57 | fs::{File, OpenOptions}, 58 | io::{BufReader, BufWriter, Cursor, Read}, 59 | os::unix::prelude::OpenOptionsExt, 60 | }; 61 | 62 | #[test] 63 | #[ignore] 64 | fn decode_encode() { 65 | let cursor = Cursor::new(TRADE_OFFER); 66 | let decoder = JpegDecoder::new(cursor).unwrap(); 67 | let color_type = decoder.color_type(); 68 | 69 | let img = DynamicImage::from_decoder(decoder).unwrap(); 70 | 71 | let meta = BufReader::new(File::open("./resources/demo").unwrap()); 72 | let output = File::create("./resources/encoded.jpeg").unwrap(); 73 | let mut out = BufWriter::new(output); 74 | let mut encoder = 75 | AppMarkerJpegEncoder::new_with_quality(&mut out, meta, AppMarkerConfig::default(), 100); 76 | encoder 77 | .encode(img.as_bytes(), 500, 654, color_type) 78 | .unwrap(); 79 | } 80 | 81 | #[test] 82 | #[ignore] 83 | fn demo_jpeg() { 84 | let f = OpenOptions::new() 85 | .write(true) 86 | .create(true) 87 | .truncate(true) 88 | .mode(0o777) 89 | .open("./resources/decoded") 90 | .unwrap(); 91 | let output = BufWriter::new(f); 92 | let input = BufReader::new(File::open("./resources/encoded.jpeg").unwrap()); 93 | let decoder = AppMarkerJpegDecoder::new(input, output, AppMarkerConfig::default()).unwrap(); 94 | decoder.dimensions(); 95 | // let img = DynamicImage::from_decoder(decoder).unwrap(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/jpeg/decoder/worker/immediate.rs: -------------------------------------------------------------------------------- 1 | use super::{RowData, Worker}; 2 | use crate::jpeg::decoder::*; 3 | use decoder::MAX_COMPONENTS; 4 | use error::Result; 5 | use idct::dequantize_and_idct_block; 6 | use parser::Component; 7 | use std::mem; 8 | use std::sync::Arc; 9 | 10 | pub struct ImmediateWorker { 11 | offsets: [usize; MAX_COMPONENTS], 12 | results: Vec>, 13 | components: Vec>, 14 | quantization_tables: Vec>>, 15 | } 16 | 17 | impl ImmediateWorker { 18 | pub fn new_immediate() -> ImmediateWorker { 19 | ImmediateWorker { 20 | offsets: [0; MAX_COMPONENTS], 21 | results: vec![Vec::new(); MAX_COMPONENTS], 22 | components: vec![None; MAX_COMPONENTS], 23 | quantization_tables: vec![None; MAX_COMPONENTS], 24 | } 25 | } 26 | pub fn start_immediate(&mut self, data: RowData) { 27 | assert!(self.results[data.index].is_empty()); 28 | 29 | self.offsets[data.index] = 0; 30 | self.results[data.index].resize( 31 | data.component.block_size.width as usize 32 | * data.component.block_size.height as usize 33 | * data.component.dct_scale 34 | * data.component.dct_scale, 35 | 0u8, 36 | ); 37 | self.components[data.index] = Some(data.component); 38 | self.quantization_tables[data.index] = Some(data.quantization_table); 39 | } 40 | pub fn append_row_immediate(&mut self, (index, data): (usize, Vec)) { 41 | // Convert coefficients from a MCU row to samples. 42 | 43 | let component = self.components[index].as_ref().unwrap(); 44 | let quantization_table = self.quantization_tables[index].as_ref().unwrap(); 45 | let block_count = 46 | component.block_size.width as usize * component.vertical_sampling_factor as usize; 47 | let line_stride = component.block_size.width as usize * component.dct_scale; 48 | 49 | assert_eq!(data.len(), block_count * 64); 50 | 51 | for i in 0..block_count { 52 | let x = (i % component.block_size.width as usize) * component.dct_scale; 53 | let y = (i / component.block_size.width as usize) * component.dct_scale; 54 | 55 | let coefficients = &data[i * 64..(i + 1) * 64]; 56 | let output = &mut self.results[index][self.offsets[index] + y * line_stride + x..]; 57 | 58 | dequantize_and_idct_block( 59 | component.dct_scale, 60 | coefficients, 61 | quantization_table, 62 | line_stride, 63 | output, 64 | ); 65 | } 66 | 67 | self.offsets[index] += block_count * component.dct_scale * component.dct_scale; 68 | } 69 | pub fn get_result_immediate(&mut self, index: usize) -> Vec { 70 | mem::replace(&mut self.results[index], Vec::new()) 71 | } 72 | } 73 | 74 | impl Worker for ImmediateWorker { 75 | fn new() -> Result { 76 | Ok(ImmediateWorker::new_immediate()) 77 | } 78 | fn start(&mut self, data: RowData) -> Result<()> { 79 | self.start_immediate(data); 80 | Ok(()) 81 | } 82 | fn append_row(&mut self, row: (usize, Vec)) -> Result<()> { 83 | self.append_row_immediate(row); 84 | Ok(()) 85 | } 86 | fn get_result(&mut self, index: usize) -> Result> { 87 | Ok(self.get_result_immediate(index)) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/jpeg/decoder/worker/multithreaded.rs: -------------------------------------------------------------------------------- 1 | //! This module implements per-component parallelism. 2 | //! It should be possible to implement per-row parallelism as well, 3 | //! which should also boost performance of grayscale images 4 | //! and allow scaling to more cores. 5 | //! However, that would be more complex, so we use this as a starting point. 6 | use super::immediate::ImmediateWorker; 7 | use super::{RowData, Worker}; 8 | use crate::jpeg::decoder::*; 9 | use decoder::MAX_COMPONENTS; 10 | use error::Result; 11 | use std::thread; 12 | use std::{ 13 | mem, 14 | sync::mpsc::{self, Sender}, 15 | }; 16 | 17 | enum WorkerMsg { 18 | Start(RowData), 19 | AppendRow(Vec), 20 | GetResult(Sender>), 21 | } 22 | pub struct MultiThreadedWorker { 23 | senders: [Option>; MAX_COMPONENTS], 24 | } 25 | 26 | impl Worker for MultiThreadedWorker { 27 | fn new() -> Result { 28 | Ok(MultiThreadedWorker { 29 | senders: [None, None, None, None], 30 | }) 31 | } 32 | fn start(&mut self, row_data: RowData) -> Result<()> { 33 | // if there is no worker thread for this component yet, start one 34 | let component = row_data.index; 35 | if let None = self.senders[component] { 36 | let sender = spawn_worker_thread(component)?; 37 | self.senders[component] = Some(sender); 38 | } 39 | // we do the "take out value and put it back in once we're done" dance here 40 | // and in all other message-passing methods because there's not that many rows 41 | // and this should be cheaper than spawning MAX_COMPONENTS many threads up front 42 | let sender = mem::replace(&mut self.senders[component], None).unwrap(); 43 | sender 44 | .send(WorkerMsg::Start(row_data)) 45 | .expect("jpeg-decoder worker thread error"); 46 | self.senders[component] = Some(sender); 47 | Ok(()) 48 | } 49 | fn append_row(&mut self, row: (usize, Vec)) -> Result<()> { 50 | let component = row.0; 51 | let sender = mem::replace(&mut self.senders[component], None).unwrap(); 52 | sender 53 | .send(WorkerMsg::AppendRow(row.1)) 54 | .expect("jpeg-decoder worker thread error"); 55 | self.senders[component] = Some(sender); 56 | Ok(()) 57 | } 58 | fn get_result(&mut self, index: usize) -> Result> { 59 | let (tx, rx) = mpsc::channel(); 60 | let sender = mem::replace(&mut self.senders[index], None).unwrap(); 61 | sender 62 | .send(WorkerMsg::GetResult(tx)) 63 | .expect("jpeg-decoder worker thread error"); 64 | Ok(rx.recv().expect("jpeg-decoder worker thread error")) 65 | } 66 | } 67 | 68 | fn spawn_worker_thread(component: usize) -> Result> { 69 | let thread_builder = 70 | thread::Builder::new().name(format!("worker thread for component {}", component)); 71 | let (tx, rx) = mpsc::channel(); 72 | 73 | thread_builder.spawn(move || { 74 | let mut worker = ImmediateWorker::new_immediate(); 75 | 76 | while let Ok(message) = rx.recv() { 77 | match message { 78 | WorkerMsg::Start(mut data) => { 79 | // we always set component index to 0 for worker threads 80 | // because they only ever handle one per thread and we don't want them 81 | // to attempt to access nonexistent components 82 | data.index = 0; 83 | worker.start_immediate(data); 84 | } 85 | WorkerMsg::AppendRow(row) => { 86 | worker.append_row_immediate((0, row)); 87 | } 88 | WorkerMsg::GetResult(chan) => { 89 | let _ = chan.send(worker.get_result_immediate(0)); 90 | break; 91 | } 92 | } 93 | } 94 | })?; 95 | 96 | Ok(tx) 97 | } 98 | -------------------------------------------------------------------------------- /src/build.rs: -------------------------------------------------------------------------------- 1 | use crate::manifest::Manifest; 2 | use crate::meme::Meme; 3 | use anyhow::Context; 4 | use std::path::PathBuf; 5 | use std::process::Command; 6 | use structopt::StructOpt; 7 | 8 | /// Executes build of the memex executable meme which produces meme "binary". 9 | /// 10 | /// It does so by invoking `cargo build` and then post processing the final binary. 11 | #[derive(Debug, StructOpt)] 12 | #[structopt(name = "build")] 13 | pub struct BuildCommand { 14 | /// Path to the Cargo.toml of the cargo project 15 | #[structopt(long, parse(from_os_str))] 16 | manifest_path: Option, 17 | 18 | /// The targeted meme 19 | meme: Option, 20 | 21 | /// Build the specified binary 22 | #[structopt(long)] 23 | bin: Option, 24 | 25 | /// Build the specified examples 26 | #[structopt(long)] 27 | example: Option, 28 | 29 | /// Build the meme in release mode, with optimizations 30 | #[structopt(long)] 31 | release: bool, 32 | 33 | /// Fetch a random meme from this subreddit 34 | #[structopt(long)] 35 | subreddit: Option, 36 | } 37 | 38 | impl BuildCommand { 39 | /// execute the build command 40 | pub fn run(&self) -> anyhow::Result { 41 | let meme = if let Some(ref subreddit) = self.subreddit { 42 | Meme::fetch_random_meme(subreddit)? 43 | .context(format!("No jpeg meme found on subreddit {}", subreddit))? 44 | } else { 45 | if let Some(ref meme) = self.meme { 46 | Meme::new(meme)? 47 | } else { 48 | if let Ok(Some(meme)) = Meme::fetch_random_meme("rustjerk") { 49 | meme 50 | } else { 51 | if self.release { 52 | Meme::new("release")? 53 | } else { 54 | Meme::new("debug")? 55 | } 56 | } 57 | } 58 | }; 59 | 60 | let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); 61 | let mut cmd = Command::new(cargo); 62 | cmd.arg("build"); 63 | 64 | let (manifest, mut bin_path) = if let Some(ref path) = self.manifest_path { 65 | cmd.arg("--manifest-path").arg(path); 66 | ( 67 | Manifest::new(path)?, 68 | path.parent() 69 | .map(|p| p.to_path_buf()) 70 | .unwrap_or_else(|| PathBuf::from(".")), 71 | ) 72 | } else { 73 | (Manifest::new("./Cargo.toml")?, PathBuf::from(".")) 74 | }; 75 | bin_path.push("target"); 76 | 77 | if self.release { 78 | cmd.arg("--release"); 79 | bin_path.push("release"); 80 | } else { 81 | bin_path.push("debug"); 82 | } 83 | 84 | let bin_name = if let Some(ref bin) = self.bin { 85 | cmd.arg("--bin").arg(bin); 86 | bin.clone() 87 | } else if let Some(ref example) = self.example { 88 | cmd.arg("--example").arg(example); 89 | bin_path.push("examples"); 90 | example.clone() 91 | } else { 92 | manifest.name()?.to_string() 93 | }; 94 | bin_path.push(&bin_name); 95 | 96 | log::debug!("Executing: `{:?}`", cmd); 97 | let child = cmd.spawn()?; 98 | let output = child 99 | .wait_with_output() 100 | .context(format!("Error executing `{:?}`", cmd))?; 101 | 102 | if !output.status.success() { 103 | anyhow::bail!( 104 | "`{:?}` failed with exit code: {:?}", 105 | cmd, 106 | output.status.code() 107 | ); 108 | } 109 | 110 | let mut meme_path = bin_path.clone(); 111 | meme_path.set_extension("jpeg"); 112 | meme.write_with_bin_to(&bin_path, &meme_path)?; 113 | Ok(BuildOutput { 114 | meme_path, 115 | bin_path, 116 | bin_name, 117 | }) 118 | } 119 | } 120 | 121 | pub struct BuildOutput { 122 | pub meme_path: PathBuf, 123 | /// Path to the cargo binary. 124 | pub bin_path: PathBuf, 125 | /// Name of the executable 126 | pub bin_name: String, 127 | } 128 | -------------------------------------------------------------------------------- /src/jpeg/decoder/marker.rs: -------------------------------------------------------------------------------- 1 | // Table B.1 2 | #[derive(Clone, Copy, Debug, PartialEq)] 3 | pub enum Marker { 4 | /// Start Of Frame markers 5 | /// 6 | /// - SOF(0): Baseline DCT (Huffman coding) 7 | /// - SOF(1): Extended sequential DCT (Huffman coding) 8 | /// - SOF(2): Progressive DCT (Huffman coding) 9 | /// - SOF(3): Lossless (sequential) (Huffman coding) 10 | /// - SOF(5): Differential sequential DCT (Huffman coding) 11 | /// - SOF(6): Differential progressive DCT (Huffman coding) 12 | /// - SOF(7): Differential lossless (sequential) (Huffman coding) 13 | /// - SOF(9): Extended sequential DCT (arithmetic coding) 14 | /// - SOF(10): Progressive DCT (arithmetic coding) 15 | /// - SOF(11): Lossless (sequential) (arithmetic coding) 16 | /// - SOF(13): Differential sequential DCT (arithmetic coding) 17 | /// - SOF(14): Differential progressive DCT (arithmetic coding) 18 | /// - SOF(15): Differential lossless (sequential) (arithmetic coding) 19 | SOF(u8), 20 | /// Reserved for JPEG extensions 21 | JPG, 22 | /// Define Huffman table(s) 23 | DHT, 24 | /// Define arithmetic coding conditioning(s) 25 | DAC, 26 | /// Restart with modulo 8 count `m` 27 | RST(u8), 28 | /// Start of image 29 | SOI, 30 | /// End of image 31 | EOI, 32 | /// Start of scan 33 | SOS, 34 | /// Define quantization table(s) 35 | DQT, 36 | /// Define number of lines 37 | DNL, 38 | /// Define restart interval 39 | DRI, 40 | /// Define hierarchical progression 41 | DHP, 42 | /// Expand reference component(s) 43 | EXP, 44 | /// Reserved for application segments 45 | APP(u8), 46 | /// Reserved for JPEG extensions 47 | JPGn(u8), 48 | /// Comment 49 | COM, 50 | /// For temporary private use in arithmetic coding 51 | TEM, 52 | /// Reserved 53 | RES, 54 | } 55 | 56 | impl Marker { 57 | pub fn has_length(self) -> bool { 58 | use self::Marker::*; 59 | match self { 60 | RST(..) | SOI | EOI | TEM => false, 61 | _ => true, 62 | } 63 | } 64 | 65 | pub fn from_u8(n: u8) -> Option { 66 | use super::marker::Marker::*; 67 | match n { 68 | 0x00 => None, // Byte stuffing 69 | 0x01 => Some(TEM), 70 | 0x02..=0xBF => Some(RES), 71 | 0xC0 => Some(SOF(0)), 72 | 0xC1 => Some(SOF(1)), 73 | 0xC2 => Some(SOF(2)), 74 | 0xC3 => Some(SOF(3)), 75 | 0xC4 => Some(DHT), 76 | 0xC5 => Some(SOF(5)), 77 | 0xC6 => Some(SOF(6)), 78 | 0xC7 => Some(SOF(7)), 79 | 0xC8 => Some(JPG), 80 | 0xC9 => Some(SOF(9)), 81 | 0xCA => Some(SOF(10)), 82 | 0xCB => Some(SOF(11)), 83 | 0xCC => Some(DAC), 84 | 0xCD => Some(SOF(13)), 85 | 0xCE => Some(SOF(14)), 86 | 0xCF => Some(SOF(15)), 87 | 0xD0 => Some(RST(0)), 88 | 0xD1 => Some(RST(1)), 89 | 0xD2 => Some(RST(2)), 90 | 0xD3 => Some(RST(3)), 91 | 0xD4 => Some(RST(4)), 92 | 0xD5 => Some(RST(5)), 93 | 0xD6 => Some(RST(6)), 94 | 0xD7 => Some(RST(7)), 95 | 0xD8 => Some(SOI), 96 | 0xD9 => Some(EOI), 97 | 0xDA => Some(SOS), 98 | 0xDB => Some(DQT), 99 | 0xDC => Some(DNL), 100 | 0xDD => Some(DRI), 101 | 0xDE => Some(DHP), 102 | 0xDF => Some(EXP), 103 | 0xE0 => Some(APP(0)), 104 | 0xE1 => Some(APP(1)), 105 | 0xE2 => Some(APP(2)), 106 | 0xE3 => Some(APP(3)), 107 | 0xE4 => Some(APP(4)), 108 | 0xE5 => Some(APP(5)), 109 | 0xE6 => Some(APP(6)), 110 | 0xE7 => Some(APP(7)), 111 | 0xE8 => Some(APP(8)), 112 | 0xE9 => Some(APP(9)), 113 | 0xEA => Some(APP(10)), 114 | 0xEB => Some(APP(11)), 115 | 0xEC => Some(APP(12)), 116 | 0xED => Some(APP(13)), 117 | 0xEE => Some(APP(14)), 118 | 0xEF => Some(APP(15)), 119 | 0xF0 => Some(JPGn(0)), 120 | 0xF1 => Some(JPGn(1)), 121 | 0xF2 => Some(JPGn(2)), 122 | 0xF3 => Some(JPGn(3)), 123 | 0xF4 => Some(JPGn(4)), 124 | 0xF5 => Some(JPGn(5)), 125 | 0xF6 => Some(JPGn(6)), 126 | 0xF7 => Some(JPGn(7)), 127 | 0xF8 => Some(JPGn(8)), 128 | 0xF9 => Some(JPGn(9)), 129 | 0xFA => Some(JPGn(10)), 130 | 0xFB => Some(JPGn(11)), 131 | 0xFC => Some(JPGn(12)), 132 | 0xFD => Some(JPGn(13)), 133 | 0xFE => Some(COM), 134 | 0xFF => None, // Fill byte 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/meme.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::{BufReader, BufWriter, Cursor}; 3 | #[cfg(unix)] 4 | use std::os::unix::prelude::OpenOptionsExt; 5 | use std::path::{Path, PathBuf}; 6 | 7 | use anyhow::Context; 8 | use image::codecs::jpeg::JpegDecoder; 9 | use image::{DynamicImage, ImageDecoder}; 10 | 11 | use crate::jpeg::decoder::AppMarkerJpegDecoder; 12 | use crate::jpeg::encoder::AppMarkerJpegEncoder; 13 | use crate::jpeg::AppMarkerConfig; 14 | 15 | pub static TRADE_OFFER: &[u8] = include_bytes!("../resources/trade-offer.jpg"); 16 | 17 | pub static BORROW_CHECKER: &[u8] = include_bytes!("../resources/borrow-checker.jpg"); 18 | 19 | pub static RUST_EXPERT: &[u8] = include_bytes!("../resources/rust-expert.jpg"); 20 | 21 | pub static DEBUG: &[u8] = include_bytes!("../resources/debug.jpg"); 22 | 23 | pub static RELEASE: &[u8] = include_bytes!("../resources/release.jpg"); 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct Meme { 27 | pub content: Vec, 28 | } 29 | 30 | impl Meme { 31 | pub fn new(s: impl AsRef) -> anyhow::Result { 32 | let s = s.as_ref(); 33 | let content = if let Ok(url) = reqwest::Url::parse(s) { 34 | log::debug!("Requesting meme from {:?}", url); 35 | reqwest::blocking::get(url)?.bytes()?.to_vec() 36 | } else { 37 | match s.to_lowercase().replace("-", "").as_str() { 38 | "debug" => DEBUG.to_vec(), 39 | "release" => RELEASE.to_vec(), 40 | "trader" | "tradeoffer" => TRADE_OFFER.to_vec(), 41 | "expert" | "rustexpert" => RUST_EXPERT.to_vec(), 42 | "borrowchecker" => BORROW_CHECKER.to_vec(), 43 | _ => { 44 | log::debug!("Reading meme file {}", s); 45 | std::fs::read(s)? 46 | } 47 | } 48 | }; 49 | Ok(Self { content }) 50 | } 51 | 52 | /// Fetches a random meme from a subreddit using https://github.com/D3vd/Meme_Api 53 | pub fn fetch_random_meme(subreddit: impl AsRef) -> anyhow::Result> { 54 | let url = format!( 55 | "https://meme-api.herokuapp.com/gimme/{}/50", 56 | subreddit.as_ref() 57 | ); 58 | let resp = reqwest::blocking::get(url)?.json::()?; 59 | if let Some(meme) = resp["memes"] 60 | .as_array() 61 | .context("No memes in response")? 62 | .iter() 63 | .filter_map(|meme| meme["url"].as_str()) 64 | .find(|url| url.ends_with(".jpg") || url.ends_with(".jpeg")) 65 | { 66 | Ok(Some(Self { 67 | content: reqwest::blocking::get(meme)?.bytes()?.to_vec(), 68 | })) 69 | } else { 70 | Ok(None) 71 | } 72 | } 73 | 74 | /// Puts the bin file into the meme and write to dest 75 | pub fn write_with_bin_to( 76 | &self, 77 | bin: impl AsRef, 78 | dest: impl AsRef, 79 | ) -> anyhow::Result { 80 | let cursor = Cursor::new(&self.content); 81 | let decoder = JpegDecoder::new(cursor)?; 82 | let color_type = decoder.color_type(); 83 | let (width, height) = decoder.dimensions(); 84 | let img = DynamicImage::from_decoder(decoder)?; 85 | 86 | log::debug!("Reading cargo binary from `{}`", bin.as_ref().display()); 87 | let bin = BufReader::new(File::open(bin)?); 88 | 89 | let dest = dest.as_ref(); 90 | log::debug!("Creating meme exe at `{}`", dest.display()); 91 | let mut out = BufWriter::new(File::create(dest)?); 92 | 93 | let mut encoder = 94 | AppMarkerJpegEncoder::new_with_quality(&mut out, bin, AppMarkerConfig::default(), 100); 95 | 96 | encoder.encode(img.as_bytes(), width, height, color_type)?; 97 | 98 | Ok(dest.to_path_buf()) 99 | } 100 | 101 | /// Decodes the binary file from a meme jpeg and writes it as executable to dest 102 | pub fn decode_bin_to( 103 | meme: impl AsRef, 104 | dest: impl AsRef, 105 | ) -> anyhow::Result { 106 | let meme = meme.as_ref(); 107 | let dest = dest.as_ref(); 108 | log::debug!("Creating executable `{}`", dest.display()); 109 | let f = BufWriter::new({ 110 | #[cfg(unix)] 111 | { 112 | OpenOptions::new() 113 | .write(true) 114 | .create(true) 115 | .truncate(true) 116 | .mode(0o777) 117 | .open(dest)? 118 | } 119 | #[cfg(windows)] 120 | { 121 | OpenOptions::new() 122 | .write(true) 123 | .create(true) 124 | .truncate(true) 125 | .open(dest)? 126 | } 127 | }); 128 | let output = BufWriter::new(f); 129 | let input = BufReader::new(File::open(meme)?); 130 | log::debug!("Decoding meme binary from `{}`", meme.display()); 131 | AppMarkerJpegDecoder::new(input, output, AppMarkerConfig::default())?; 132 | Ok(dest.to_path_buf()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cargo-memex 2 | ========================= 3 | 4 | [github](https://github.com/mattsse/cargo-memex) 5 | [crates.io](https://crates.io/crates/cargo-memex) 6 | [docs.rs](https://docs.rs/cargo-memex) 7 | [build status](https://github.com/mattsse/cargo-memex/actions?query=branch%3Amain) 8 | 9 | Besides their size, rust binaries have a significant disadvantage: rust binaries are not memes yet. 10 | 11 | `cargo-memex` is a `cargo` subcommand that makes it easy to ditch regular rust binaries in favor of memes, which is arguably the far superior container format for rust executables. Rather than to ordinary rust binaries, `cargo-memex` compiles rust code directly into a meme of your liking. 12 | 13 | `cargo-memex` executeable rust memes thus provide a number of significant improvements over regular rust executables: 14 | 15 | Standard rust executables always look the same, which means consistently boring, e.g.: ![alt text](./resources/exe.ico). 16 | 17 | With `cargo-memex` they can look like this: 18 | 19 | ![](./resources/trade-offer.jpg) 20 | 21 | Also, normal rust executables cannot be easily posted on imageboards like tumbler, or to show off your rust skills on [r/rustjerk](https://www.reddit.com/r/rustjerk/) or on slack or twitter. This also makes it possible to use imagehosting services to distribute your executables. 22 | 23 | Another limitation of rust binaries is, that `debug` and `release` executables are visually indistinguishable. 24 | 25 | `cargo-memex` fixes this problem by fetching a random meme from [r/rustjerk](https://www.reddit.com/r/rustjerk/) using [D3vd/Meme_Api](https://github.com/D3vd/Meme_Api) each time. 26 | 27 | If no meme could be found it falls back to the corresponding segment of: 28 | 29 | ![](./resources/hank.jpg) 30 | 31 | You can also request a random meme from any subreddit with the `--subreddit ""` option. 32 | 33 | The same problem exists for different release versions which visually always look completely identical. With `cargo-memex` you can compile each new release into a different meme to highlight the differences. This makes support much easier since everyone can directly see which version is installed. 34 | 35 | 36 | ## Installation 37 | 38 | ##### `cargo install cargo-memex` 39 | 40 | ## Usage 41 | 42 | Compile a new cargo project into the built in [rust borrow checker meme](./resources/borrow-checker.jpg): 43 | 44 | ![](./resources/demo.gif) 45 | 46 | Compile your current project into the meme above, for other built in memes see [resources](./resources) 47 | 48 | ##### `cargo memex build tradeoffer` 49 | 50 | Compile your current project in release mode with a random meme from [r/rustjerk](https://www.reddit.com/r/rustjerk/) 51 | 52 | ##### `cargo memex build --release` 53 | 54 | Compile with local meme 55 | 56 | ##### `cargo memex build ./meme.jpg` 57 | 58 | Use a jpeg meme on the web 59 | 60 | ##### `cargo memex build "https://meme.jpg"` 61 | 62 | Use a random jpeg meme from a subreddit 63 | 64 | ##### `cargo memex build --subreddit dankmemes` 65 | 66 | To execute a memex executable meme 67 | 68 | ##### `cargo memex exec meme.jpg` 69 | 70 | Build then execute in one step 71 | 72 | ##### `cargo memex run` 73 | 74 | ## Roadmap 75 | 76 | Once memes are more widely adapted as rust executables, which is expected to happen soon, support for publishing memes will be required. 77 | 78 | The recent NFT frenzy has shown that there is a high demand for NFT based solutions, and that especially memes can be very [lucrative](https://www.nytimes.com/2021/04/29/arts/disaster-girl-meme-nft.html). 79 | Hence, solutions for cargo-memex are currently being investigated in order to tap this growing market. 80 | Ideally, the `cargo publish` subcommand should be extended by an `--nft` option, so that developers can directly publish a new version of their rust project as a memex NFT. 81 | 82 | ## Known Limitations 83 | 84 | * only jpeg supported at this point 85 | 86 | Licensed under either of these: 87 | 88 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 89 | https://www.apache.org/licenses/LICENSE-2.0) 90 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 91 | https://opensource.org/licenses/MIT) 92 | * The Jpeg encoder/decoder adapter are slightly modified copies of [image-rs/image](https://github.com/image-rs/image) which are license under [MIT](https://github.com/image-rs/image/blob/master/LICENSE) 93 | * The Jpeg decoder is a slightly modified copy of [image-rs/jpeg-decoder](https://github.com/image-rs/image) which are license under either of 94 | * [MIT](https://github.com/image-rs/jpeg-decoder/blob/master/LICENSE-MIT) 95 | * [Apache 2.0](https://github.com/image-rs/jpeg-decoder/blob/master/LICENSE-APACHE) 96 | -------------------------------------------------------------------------------- /src/jpeg/decoder/mod.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 PistonDevelopers 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | //! Modified Jpeg decoder 24 | //! 25 | //! Adapted from https://github.com/image-rs/image/blob/master/src/codecs/jpeg/decoder.rs 26 | 27 | use std::convert::TryFrom; 28 | use std::io::{self, Cursor, Read, Write}; 29 | use std::marker::PhantomData; 30 | use std::mem; 31 | 32 | use image::error::{ 33 | DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, 34 | }; 35 | use image::ColorType; 36 | use image::{ImageDecoder, ImageFormat}; 37 | 38 | use crate::jpeg::AppMarkerConfig; 39 | pub use decoder::{Decoder, ImageInfo, PixelFormat}; 40 | pub use error::{Error, UnsupportedFeature}; 41 | 42 | mod decoder; 43 | mod error; 44 | mod huffman; 45 | mod idct; 46 | mod marker; 47 | mod parser; 48 | mod upsampler; 49 | mod worker; 50 | 51 | fn read_u8(reader: &mut R) -> std::io::Result { 52 | let mut buf = [0]; 53 | reader.read_exact(&mut buf)?; 54 | Ok(buf[0]) 55 | } 56 | 57 | fn read_u16_from_be(reader: &mut R) -> std::io::Result { 58 | let mut buf = [0, 0]; 59 | reader.read_exact(&mut buf)?; 60 | Ok(u16::from_be_bytes(buf)) 61 | } 62 | 63 | /// JPEG decoder 64 | pub struct AppMarkerJpegDecoder { 65 | decoder: self::Decoder, 66 | metadata: self::ImageInfo, 67 | } 68 | 69 | impl AppMarkerJpegDecoder { 70 | /// Create a new decoder that decodes from the stream ```r``` 71 | pub fn new(r: R, w: W, config: AppMarkerConfig) -> ImageResult> { 72 | let mut decoder = self::Decoder::new(r, w, config); 73 | 74 | decoder.read_info().map_err(jpeg_error_to_image_error)?; 75 | let mut metadata = decoder.info().ok_or_else(|| { 76 | ImageError::Decoding(DecodingError::from_format_hint(ImageFormat::Jpeg.into())) 77 | })?; 78 | 79 | // We convert CMYK data to RGB before returning it to the user. 80 | if metadata.pixel_format == self::PixelFormat::CMYK32 { 81 | metadata.pixel_format = self::PixelFormat::RGB24; 82 | } 83 | 84 | Ok(AppMarkerJpegDecoder { decoder, metadata }) 85 | } 86 | 87 | /// Configure the decoder to scale the image during decoding. 88 | /// 89 | /// This efficiently scales the image by the smallest supported 90 | /// scale factor that produces an image larger than or equal to 91 | /// the requested size in at least one axis. The currently 92 | /// implemented scale factors are 1/8, 1/4, 1/2 and 1. 93 | /// 94 | /// To generate a thumbnail of an exact size, pass the desired 95 | /// size and then scale to the final size using a traditional 96 | /// resampling algorithm. 97 | /// 98 | /// The size of the image to be loaded, with the scale factor 99 | /// applied, is returned. 100 | pub fn scale( 101 | &mut self, 102 | requested_width: u16, 103 | requested_height: u16, 104 | ) -> ImageResult<(u16, u16)> { 105 | let result = self 106 | .decoder 107 | .scale(requested_width, requested_height) 108 | .map_err(jpeg_error_to_image_error)?; 109 | 110 | self.metadata.width = result.0; 111 | self.metadata.height = result.1; 112 | 113 | Ok(result) 114 | } 115 | } 116 | 117 | /// Wrapper struct around a `Cursor>` 118 | pub struct JpegReader(Cursor>, PhantomData); 119 | impl Read for JpegReader { 120 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 121 | self.0.read(buf) 122 | } 123 | fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { 124 | if self.0.position() == 0 && buf.is_empty() { 125 | mem::swap(buf, self.0.get_mut()); 126 | Ok(buf.len()) 127 | } else { 128 | self.0.read_to_end(buf) 129 | } 130 | } 131 | } 132 | 133 | impl<'a, R: 'a + Read, W: Write> ImageDecoder<'a> for AppMarkerJpegDecoder { 134 | type Reader = JpegReader; 135 | 136 | fn dimensions(&self) -> (u32, u32) { 137 | ( 138 | u32::from(self.metadata.width), 139 | u32::from(self.metadata.height), 140 | ) 141 | } 142 | 143 | fn color_type(&self) -> ColorType { 144 | pixel_format_to_color_type(self.metadata.pixel_format) 145 | } 146 | 147 | fn into_reader(mut self) -> ImageResult { 148 | let mut data = self.decoder.decode().map_err(jpeg_error_to_image_error)?; 149 | data = match self.decoder.info().unwrap().pixel_format { 150 | self::PixelFormat::CMYK32 => cmyk_to_rgb(&data), 151 | _ => data, 152 | }; 153 | 154 | Ok(JpegReader(Cursor::new(data), PhantomData)) 155 | } 156 | 157 | fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { 158 | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); 159 | 160 | let mut data = self.decoder.decode().map_err(jpeg_error_to_image_error)?; 161 | data = match self.decoder.info().unwrap().pixel_format { 162 | self::PixelFormat::CMYK32 => cmyk_to_rgb(&data), 163 | _ => data, 164 | }; 165 | 166 | buf.copy_from_slice(&data); 167 | Ok(()) 168 | } 169 | } 170 | 171 | fn cmyk_to_rgb(input: &[u8]) -> Vec { 172 | let count = input.len() / 4; 173 | let mut output = vec![0; 3 * count]; 174 | 175 | let in_pixels = input[..4 * count].chunks_exact(4); 176 | let out_pixels = output[..3 * count].chunks_exact_mut(3); 177 | 178 | for (pixel, outp) in in_pixels.zip(out_pixels) { 179 | let c = 255 - u16::from(pixel[0]); 180 | let m = 255 - u16::from(pixel[1]); 181 | let y = 255 - u16::from(pixel[2]); 182 | let k = 255 - u16::from(pixel[3]); 183 | // CMY -> RGB 184 | let r = (k * c) / 255; 185 | let g = (k * m) / 255; 186 | let b = (k * y) / 255; 187 | 188 | outp[0] = r as u8; 189 | outp[1] = g as u8; 190 | outp[2] = b as u8; 191 | } 192 | 193 | output 194 | } 195 | 196 | fn pixel_format_to_color_type(pixel_format: self::PixelFormat) -> ColorType { 197 | use self::PixelFormat::*; 198 | match pixel_format { 199 | L8 => ColorType::L8, 200 | RGB24 => ColorType::Rgb8, 201 | CMYK32 => panic!(), 202 | } 203 | } 204 | 205 | fn jpeg_error_to_image_error(err: self::Error) -> ImageError { 206 | use self::Error::*; 207 | match err { 208 | err @ Format(_) => ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)), 209 | Unsupported(desc) => ImageError::Unsupported(UnsupportedError::from_format_and_kind( 210 | ImageFormat::Jpeg.into(), 211 | UnsupportedErrorKind::GenericFeature(format!("{:?}", desc)), 212 | )), 213 | Io(err) => ImageError::IoError(err), 214 | Internal(err) => ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)), 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/jpeg/decoder/upsampler.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use parser::Component; 3 | 4 | pub struct Upsampler { 5 | components: Vec, 6 | line_buffer_size: usize, 7 | } 8 | 9 | struct UpsamplerComponent { 10 | upsampler: Box, 11 | width: usize, 12 | height: usize, 13 | row_stride: usize, 14 | } 15 | 16 | impl Upsampler { 17 | pub fn new( 18 | components: &[Component], 19 | output_width: u16, 20 | output_height: u16, 21 | ) -> error::Result { 22 | let h_max = components 23 | .iter() 24 | .map(|c| c.horizontal_sampling_factor) 25 | .max() 26 | .unwrap(); 27 | let v_max = components 28 | .iter() 29 | .map(|c| c.vertical_sampling_factor) 30 | .max() 31 | .unwrap(); 32 | let mut upsampler_components = Vec::with_capacity(components.len()); 33 | 34 | for component in components { 35 | let upsampler = choose_upsampler( 36 | ( 37 | component.horizontal_sampling_factor, 38 | component.vertical_sampling_factor, 39 | ), 40 | (h_max, v_max), 41 | output_width, 42 | output_height, 43 | )?; 44 | upsampler_components.push(UpsamplerComponent { 45 | upsampler: upsampler, 46 | width: component.size.width as usize, 47 | height: component.size.height as usize, 48 | row_stride: component.block_size.width as usize * component.dct_scale, 49 | }); 50 | } 51 | 52 | let buffer_size = 53 | components.iter().map(|c| c.size.width).max().unwrap() as usize * h_max as usize; 54 | 55 | Ok(Upsampler { 56 | components: upsampler_components, 57 | line_buffer_size: buffer_size, 58 | }) 59 | } 60 | 61 | pub fn upsample_and_interleave_row( 62 | &self, 63 | component_data: &[Vec], 64 | row: usize, 65 | output_width: usize, 66 | output: &mut [u8], 67 | ) { 68 | let component_count = component_data.len(); 69 | let mut line_buffer = vec![0u8; self.line_buffer_size]; 70 | 71 | debug_assert_eq!(component_count, self.components.len()); 72 | 73 | for (i, component) in self.components.iter().enumerate() { 74 | component.upsampler.upsample_row( 75 | &component_data[i], 76 | component.width, 77 | component.height, 78 | component.row_stride, 79 | row, 80 | output_width, 81 | &mut line_buffer, 82 | ); 83 | for x in 0..output_width { 84 | output[x * component_count + i] = line_buffer[x]; 85 | } 86 | } 87 | } 88 | } 89 | 90 | struct UpsamplerH1V1; 91 | struct UpsamplerH2V1; 92 | struct UpsamplerH1V2; 93 | struct UpsamplerH2V2; 94 | 95 | struct UpsamplerGeneric { 96 | horizontal_scaling_factor: u8, 97 | vertical_scaling_factor: u8, 98 | } 99 | 100 | fn choose_upsampler( 101 | sampling_factors: (u8, u8), 102 | max_sampling_factors: (u8, u8), 103 | output_width: u16, 104 | output_height: u16, 105 | ) -> error::Result> { 106 | let h1 = sampling_factors.0 == max_sampling_factors.0 || output_width == 1; 107 | let v1 = sampling_factors.1 == max_sampling_factors.1 || output_height == 1; 108 | let h2 = sampling_factors.0 * 2 == max_sampling_factors.0; 109 | let v2 = sampling_factors.1 * 2 == max_sampling_factors.1; 110 | 111 | if h1 && v1 { 112 | Ok(Box::new(UpsamplerH1V1)) 113 | } else if h2 && v1 { 114 | Ok(Box::new(UpsamplerH2V1)) 115 | } else if h1 && v2 { 116 | Ok(Box::new(UpsamplerH1V2)) 117 | } else if h2 && v2 { 118 | Ok(Box::new(UpsamplerH2V2)) 119 | } else { 120 | if max_sampling_factors.0 % sampling_factors.0 != 0 121 | || max_sampling_factors.1 % sampling_factors.1 != 0 122 | { 123 | Err(Error::Unsupported( 124 | UnsupportedFeature::NonIntegerSubsamplingRatio, 125 | )) 126 | } else { 127 | Ok(Box::new(UpsamplerGeneric { 128 | horizontal_scaling_factor: max_sampling_factors.0 / sampling_factors.0, 129 | vertical_scaling_factor: max_sampling_factors.1 / sampling_factors.1, 130 | })) 131 | } 132 | } 133 | } 134 | 135 | trait Upsample { 136 | fn upsample_row( 137 | &self, 138 | input: &[u8], 139 | input_width: usize, 140 | input_height: usize, 141 | row_stride: usize, 142 | row: usize, 143 | output_width: usize, 144 | output: &mut [u8], 145 | ); 146 | } 147 | 148 | impl Upsample for UpsamplerH1V1 { 149 | fn upsample_row( 150 | &self, 151 | input: &[u8], 152 | _input_width: usize, 153 | _input_height: usize, 154 | row_stride: usize, 155 | row: usize, 156 | output_width: usize, 157 | output: &mut [u8], 158 | ) { 159 | let input = &input[row * row_stride..]; 160 | 161 | output[..output_width].copy_from_slice(&input[..output_width]); 162 | } 163 | } 164 | 165 | impl Upsample for UpsamplerH2V1 { 166 | fn upsample_row( 167 | &self, 168 | input: &[u8], 169 | input_width: usize, 170 | _input_height: usize, 171 | row_stride: usize, 172 | row: usize, 173 | _output_width: usize, 174 | output: &mut [u8], 175 | ) { 176 | let input = &input[row * row_stride..]; 177 | 178 | if input_width == 1 { 179 | output[0] = input[0]; 180 | output[1] = input[0]; 181 | return; 182 | } 183 | 184 | output[0] = input[0]; 185 | output[1] = ((input[0] as u32 * 3 + input[1] as u32 + 2) >> 2) as u8; 186 | 187 | for i in 1..input_width - 1 { 188 | let sample = 3 * input[i] as u32 + 2; 189 | output[i * 2] = ((sample + input[i - 1] as u32) >> 2) as u8; 190 | output[i * 2 + 1] = ((sample + input[i + 1] as u32) >> 2) as u8; 191 | } 192 | 193 | output[(input_width - 1) * 2] = 194 | ((input[input_width - 1] as u32 * 3 + input[input_width - 2] as u32 + 2) >> 2) as u8; 195 | output[(input_width - 1) * 2 + 1] = input[input_width - 1]; 196 | } 197 | } 198 | 199 | impl Upsample for UpsamplerH1V2 { 200 | fn upsample_row( 201 | &self, 202 | input: &[u8], 203 | _input_width: usize, 204 | input_height: usize, 205 | row_stride: usize, 206 | row: usize, 207 | output_width: usize, 208 | output: &mut [u8], 209 | ) { 210 | let row_near = row as f32 / 2.0; 211 | // If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we 212 | // want it to be the next row. 213 | let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32); 214 | 215 | let input_near = &input[row_near as usize * row_stride..]; 216 | let input_far = &input[row_far as usize * row_stride..]; 217 | 218 | for i in 0..output_width { 219 | output[i] = ((3 * input_near[i] as u32 + input_far[i] as u32 + 2) >> 2) as u8; 220 | } 221 | } 222 | } 223 | 224 | impl Upsample for UpsamplerH2V2 { 225 | fn upsample_row( 226 | &self, 227 | input: &[u8], 228 | input_width: usize, 229 | input_height: usize, 230 | row_stride: usize, 231 | row: usize, 232 | _output_width: usize, 233 | output: &mut [u8], 234 | ) { 235 | let row_near = row as f32 / 2.0; 236 | // If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we 237 | // want it to be the next row. 238 | let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32); 239 | 240 | let input_near = &input[row_near as usize * row_stride..]; 241 | let input_far = &input[row_far as usize * row_stride..]; 242 | 243 | if input_width == 1 { 244 | let value = ((3 * input_near[0] as u32 + input_far[0] as u32 + 2) >> 2) as u8; 245 | output[0] = value; 246 | output[1] = value; 247 | return; 248 | } 249 | 250 | let mut t1 = 3 * input_near[0] as u32 + input_far[0] as u32; 251 | output[0] = ((t1 + 2) >> 2) as u8; 252 | 253 | for i in 1..input_width { 254 | let t0 = t1; 255 | t1 = 3 * input_near[i] as u32 + input_far[i] as u32; 256 | 257 | output[i * 2 - 1] = ((3 * t0 + t1 + 8) >> 4) as u8; 258 | output[i * 2] = ((3 * t1 + t0 + 8) >> 4) as u8; 259 | } 260 | 261 | output[input_width * 2 - 1] = ((t1 + 2) >> 2) as u8; 262 | } 263 | } 264 | 265 | impl Upsample for UpsamplerGeneric { 266 | // Uses nearest neighbor sampling 267 | fn upsample_row( 268 | &self, 269 | input: &[u8], 270 | input_width: usize, 271 | _input_height: usize, 272 | row_stride: usize, 273 | row: usize, 274 | _output_width: usize, 275 | output: &mut [u8], 276 | ) { 277 | let mut index = 0; 278 | let start = (row / self.vertical_scaling_factor as usize) * row_stride; 279 | let input = &input[start..(start + input_width)]; 280 | for val in input { 281 | for _ in 0..self.horizontal_scaling_factor { 282 | output[index] = *val; 283 | index += 1; 284 | } 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/jpeg/transform.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 PistonDevelopers 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | fdct is a Rust translation of jfdctint.c from the 25 | Independent JPEG Group's libjpeg version 9a 26 | obtained from http://www.ijg.org/files/jpegsr9a.zip 27 | It comes with the following conditions of distribution and use: 28 | 29 | In plain English: 30 | 31 | 1. We don't promise that this software works. (But if you find any bugs, 32 | please let us know!) 33 | 2. You can use this software for whatever you want. You don't have to pay us. 34 | 3. You may not pretend that you wrote this software. If you use it in a 35 | program, you must acknowledge somewhere in your documentation that 36 | you've used the IJG code. 37 | 38 | In legalese: 39 | 40 | The authors make NO WARRANTY or representation, either express or implied, 41 | with respect to this software, its quality, accuracy, merchantability, or 42 | fitness for a particular purpose. This software is provided "AS IS", and you, 43 | its user, assume the entire risk as to its quality and accuracy. 44 | 45 | This software is copyright (C) 1991-2014, Thomas G. Lane, Guido Vollbeding. 46 | All Rights Reserved except as specified below. 47 | 48 | Permission is hereby granted to use, copy, modify, and distribute this 49 | software (or portions thereof) for any purpose, without fee, subject to these 50 | conditions: 51 | (1) If any part of the source code for this software is distributed, then this 52 | README file must be included, with this copyright and no-warranty notice 53 | unaltered; and any additions, deletions, or changes to the original files 54 | must be clearly indicated in accompanying documentation. 55 | (2) If only executable code is distributed, then the accompanying 56 | documentation must state that "this software is based in part on the work of 57 | the Independent JPEG Group". 58 | (3) Permission for use of this software is granted only if the user accepts 59 | full responsibility for any undesirable consequences; the authors accept 60 | NO LIABILITY for damages of any kind. 61 | 62 | These conditions apply to any software derived from or based on the IJG code, 63 | not just to the unmodified library. If you use our work, you ought to 64 | acknowledge us. 65 | 66 | Permission is NOT granted for the use of any IJG author's name or company name 67 | in advertising or publicity relating to this software or products derived from 68 | it. This software may be referred to only as "the Independent JPEG Group's 69 | software". 70 | 71 | We specifically permit and encourage the use of this software as the basis of 72 | commercial products, provided that all warranty or liability claims are 73 | assumed by the product vendor. 74 | */ 75 | 76 | static CONST_BITS: i32 = 13; 77 | static PASS1_BITS: i32 = 2; 78 | 79 | static FIX_0_298631336: i32 = 2446; 80 | static FIX_0_390180644: i32 = 3196; 81 | static FIX_0_541196100: i32 = 4433; 82 | static FIX_0_765366865: i32 = 6270; 83 | static FIX_0_899976223: i32 = 7373; 84 | static FIX_1_175875602: i32 = 9633; 85 | static FIX_1_501321110: i32 = 12_299; 86 | static FIX_1_847759065: i32 = 15_137; 87 | static FIX_1_961570560: i32 = 16_069; 88 | static FIX_2_053119869: i32 = 16_819; 89 | static FIX_2_562915447: i32 = 20_995; 90 | static FIX_3_072711026: i32 = 25_172; 91 | 92 | pub(crate) fn fdct(samples: &[u8; 64], coeffs: &mut [i32; 64]) { 93 | // Pass 1: process rows. 94 | // Results are scaled by sqrt(8) compared to a true DCT 95 | // furthermore we scale the results by 2**PASS1_BITS 96 | for y in 0usize..8 { 97 | let y0 = y * 8; 98 | 99 | // Even part 100 | let t0 = i32::from(samples[y0]) + i32::from(samples[y0 + 7]); 101 | let t1 = i32::from(samples[y0 + 1]) + i32::from(samples[y0 + 6]); 102 | let t2 = i32::from(samples[y0 + 2]) + i32::from(samples[y0 + 5]); 103 | let t3 = i32::from(samples[y0 + 3]) + i32::from(samples[y0 + 4]); 104 | 105 | let t10 = t0 + t3; 106 | let t12 = t0 - t3; 107 | let t11 = t1 + t2; 108 | let t13 = t1 - t2; 109 | 110 | let t0 = i32::from(samples[y0]) - i32::from(samples[y0 + 7]); 111 | let t1 = i32::from(samples[y0 + 1]) - i32::from(samples[y0 + 6]); 112 | let t2 = i32::from(samples[y0 + 2]) - i32::from(samples[y0 + 5]); 113 | let t3 = i32::from(samples[y0 + 3]) - i32::from(samples[y0 + 4]); 114 | 115 | // Apply unsigned -> signed conversion 116 | coeffs[y0] = (t10 + t11 - 8 * 128) << PASS1_BITS as usize; 117 | coeffs[y0 + 4] = (t10 - t11) << PASS1_BITS as usize; 118 | 119 | let mut z1 = (t12 + t13) * FIX_0_541196100; 120 | // Add fudge factor here for final descale 121 | z1 += 1 << (CONST_BITS - PASS1_BITS - 1) as usize; 122 | 123 | coeffs[y0 + 2] = (z1 + t12 * FIX_0_765366865) >> (CONST_BITS - PASS1_BITS) as usize; 124 | coeffs[y0 + 6] = (z1 - t13 * FIX_1_847759065) >> (CONST_BITS - PASS1_BITS) as usize; 125 | 126 | // Odd part 127 | let t12 = t0 + t2; 128 | let t13 = t1 + t3; 129 | 130 | let mut z1 = (t12 + t13) * FIX_1_175875602; 131 | // Add fudge factor here for final descale 132 | z1 += 1 << (CONST_BITS - PASS1_BITS - 1) as usize; 133 | 134 | let mut t12 = t12 * (-FIX_0_390180644); 135 | let mut t13 = t13 * (-FIX_1_961570560); 136 | t12 += z1; 137 | t13 += z1; 138 | 139 | let z1 = (t0 + t3) * (-FIX_0_899976223); 140 | let mut t0 = t0 * FIX_1_501321110; 141 | let mut t3 = t3 * FIX_0_298631336; 142 | t0 += z1 + t12; 143 | t3 += z1 + t13; 144 | 145 | let z1 = (t1 + t2) * (-FIX_2_562915447); 146 | let mut t1 = t1 * FIX_3_072711026; 147 | let mut t2 = t2 * FIX_2_053119869; 148 | t1 += z1 + t13; 149 | t2 += z1 + t12; 150 | 151 | coeffs[y0 + 1] = t0 >> (CONST_BITS - PASS1_BITS) as usize; 152 | coeffs[y0 + 3] = t1 >> (CONST_BITS - PASS1_BITS) as usize; 153 | coeffs[y0 + 5] = t2 >> (CONST_BITS - PASS1_BITS) as usize; 154 | coeffs[y0 + 7] = t3 >> (CONST_BITS - PASS1_BITS) as usize; 155 | } 156 | 157 | // Pass 2: process columns 158 | // We remove the PASS1_BITS scaling but leave the results scaled up an 159 | // overall factor of 8 160 | for x in (0usize..8).rev() { 161 | // Even part 162 | let t0 = coeffs[x] + coeffs[x + 8 * 7]; 163 | let t1 = coeffs[x + 8] + coeffs[x + 8 * 6]; 164 | let t2 = coeffs[x + 8 * 2] + coeffs[x + 8 * 5]; 165 | let t3 = coeffs[x + 8 * 3] + coeffs[x + 8 * 4]; 166 | 167 | // Add fudge factor here for final descale 168 | let t10 = t0 + t3 + (1 << (PASS1_BITS - 1) as usize); 169 | let t12 = t0 - t3; 170 | let t11 = t1 + t2; 171 | let t13 = t1 - t2; 172 | 173 | let t0 = coeffs[x] - coeffs[x + 8 * 7]; 174 | let t1 = coeffs[x + 8] - coeffs[x + 8 * 6]; 175 | let t2 = coeffs[x + 8 * 2] - coeffs[x + 8 * 5]; 176 | let t3 = coeffs[x + 8 * 3] - coeffs[x + 8 * 4]; 177 | 178 | coeffs[x] = (t10 + t11) >> PASS1_BITS as usize; 179 | coeffs[x + 8 * 4] = (t10 - t11) >> PASS1_BITS as usize; 180 | 181 | let mut z1 = (t12 + t13) * FIX_0_541196100; 182 | // Add fudge factor here for final descale 183 | z1 += 1 << (CONST_BITS + PASS1_BITS - 1) as usize; 184 | 185 | coeffs[x + 8 * 2] = (z1 + t12 * FIX_0_765366865) >> (CONST_BITS + PASS1_BITS) as usize; 186 | coeffs[x + 8 * 6] = (z1 - t13 * FIX_1_847759065) >> (CONST_BITS + PASS1_BITS) as usize; 187 | 188 | // Odd part 189 | let t12 = t0 + t2; 190 | let t13 = t1 + t3; 191 | 192 | let mut z1 = (t12 + t13) * FIX_1_175875602; 193 | // Add fudge factor here for final descale 194 | z1 += 1 << (CONST_BITS - PASS1_BITS - 1) as usize; 195 | 196 | let mut t12 = t12 * (-FIX_0_390180644); 197 | let mut t13 = t13 * (-FIX_1_961570560); 198 | t12 += z1; 199 | t13 += z1; 200 | 201 | let z1 = (t0 + t3) * (-FIX_0_899976223); 202 | let mut t0 = t0 * FIX_1_501321110; 203 | let mut t3 = t3 * FIX_0_298631336; 204 | t0 += z1 + t12; 205 | t3 += z1 + t13; 206 | 207 | let z1 = (t1 + t2) * (-FIX_2_562915447); 208 | let mut t1 = t1 * FIX_3_072711026; 209 | let mut t2 = t2 * FIX_2_053119869; 210 | t1 += z1 + t13; 211 | t2 += z1 + t12; 212 | 213 | coeffs[x + 8] = t0 >> (CONST_BITS + PASS1_BITS) as usize; 214 | coeffs[x + 8 * 3] = t1 >> (CONST_BITS + PASS1_BITS) as usize; 215 | coeffs[x + 8 * 5] = t2 >> (CONST_BITS + PASS1_BITS) as usize; 216 | coeffs[x + 8 * 7] = t3 >> (CONST_BITS + PASS1_BITS) as usize; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/jpeg/decoder/huffman.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use error::{Error, Result}; 3 | use marker::Marker; 4 | use parser::ScanInfo; 5 | use std::io::Read; 6 | 7 | const LUT_BITS: u8 = 8; 8 | 9 | #[derive(Debug)] 10 | pub struct HuffmanDecoder { 11 | bits: u64, 12 | num_bits: u8, 13 | marker: Option, 14 | } 15 | 16 | impl HuffmanDecoder { 17 | pub fn new() -> HuffmanDecoder { 18 | HuffmanDecoder { 19 | bits: 0, 20 | num_bits: 0, 21 | marker: None, 22 | } 23 | } 24 | 25 | // Section F.2.2.3 26 | // Figure F.16 27 | pub fn decode(&mut self, reader: &mut R, table: &HuffmanTable) -> Result { 28 | if self.num_bits < 16 { 29 | self.read_bits(reader)?; 30 | } 31 | 32 | let (value, size) = table.lut[self.peek_bits(LUT_BITS) as usize]; 33 | 34 | if size > 0 { 35 | self.consume_bits(size); 36 | Ok(value) 37 | } else { 38 | let bits = self.peek_bits(16); 39 | 40 | for i in LUT_BITS..16 { 41 | let code = (bits >> (15 - i)) as i32; 42 | 43 | if code <= table.maxcode[i as usize] { 44 | self.consume_bits(i + 1); 45 | 46 | let index = (code + table.delta[i as usize]) as usize; 47 | return Ok(table.values[index]); 48 | } 49 | } 50 | 51 | Err(Error::Format("failed to decode huffman code".to_owned())) 52 | } 53 | } 54 | 55 | pub fn decode_fast_ac( 56 | &mut self, 57 | reader: &mut R, 58 | table: &HuffmanTable, 59 | ) -> Result> { 60 | if let Some(ref ac_lut) = table.ac_lut { 61 | if self.num_bits < LUT_BITS { 62 | self.read_bits(reader)?; 63 | } 64 | 65 | let (value, run_size) = ac_lut[self.peek_bits(LUT_BITS) as usize]; 66 | 67 | if run_size != 0 { 68 | let run = run_size >> 4; 69 | let size = run_size & 0x0f; 70 | 71 | self.consume_bits(size); 72 | return Ok(Some((value, run))); 73 | } 74 | } 75 | 76 | Ok(None) 77 | } 78 | 79 | #[inline] 80 | pub fn get_bits(&mut self, reader: &mut R, count: u8) -> Result { 81 | if self.num_bits < count { 82 | self.read_bits(reader)?; 83 | } 84 | 85 | let bits = self.peek_bits(count); 86 | self.consume_bits(count); 87 | 88 | Ok(bits) 89 | } 90 | 91 | #[inline] 92 | pub fn receive_extend(&mut self, reader: &mut R, count: u8) -> Result { 93 | let value = self.get_bits(reader, count)?; 94 | Ok(extend(value, count)) 95 | } 96 | 97 | pub fn reset(&mut self) { 98 | self.bits = 0; 99 | self.num_bits = 0; 100 | } 101 | 102 | pub fn take_marker(&mut self, reader: &mut R) -> Result> { 103 | self.read_bits(reader).map(|_| self.marker.take()) 104 | } 105 | 106 | #[inline] 107 | fn peek_bits(&mut self, count: u8) -> u16 { 108 | debug_assert!(count <= 16); 109 | debug_assert!(self.num_bits >= count); 110 | 111 | ((self.bits >> (64 - count)) & ((1 << count) - 1)) as u16 112 | } 113 | 114 | #[inline] 115 | fn consume_bits(&mut self, count: u8) { 116 | debug_assert!(self.num_bits >= count); 117 | 118 | self.bits <<= count as usize; 119 | self.num_bits -= count; 120 | } 121 | 122 | fn read_bits(&mut self, reader: &mut R) -> Result<()> { 123 | while self.num_bits <= 56 { 124 | // Fill with zero bits if we have reached the end. 125 | let byte = match self.marker { 126 | Some(_) => 0, 127 | None => read_u8(reader)?, 128 | }; 129 | 130 | if byte == 0xFF { 131 | let mut next_byte = read_u8(reader)?; 132 | 133 | // Check for byte stuffing. 134 | if next_byte != 0x00 { 135 | // We seem to have reached the end of entropy-coded data and encountered a 136 | // marker. Since we can't put data back into the reader, we have to continue 137 | // reading to identify the marker so we can pass it on. 138 | 139 | // Section B.1.1.2 140 | // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code X’FF’." 141 | while next_byte == 0xFF { 142 | next_byte = read_u8(reader)?; 143 | } 144 | 145 | match next_byte { 146 | 0x00 => { 147 | return Err(Error::Format( 148 | "FF 00 found where marker was expected".to_owned(), 149 | )) 150 | } 151 | _ => self.marker = Some(Marker::from_u8(next_byte).unwrap()), 152 | } 153 | 154 | continue; 155 | } 156 | } 157 | 158 | self.bits |= (byte as u64) << (56 - self.num_bits); 159 | self.num_bits += 8; 160 | } 161 | 162 | Ok(()) 163 | } 164 | } 165 | 166 | // Section F.2.2.1 167 | // Figure F.12 168 | fn extend(value: u16, count: u8) -> i16 { 169 | let vt = 1 << (count as u16 - 1); 170 | 171 | if value < vt { 172 | value as i16 + (-1 << count as i16) + 1 173 | } else { 174 | value as i16 175 | } 176 | } 177 | 178 | #[derive(Clone, Copy, Debug, PartialEq)] 179 | pub enum HuffmanTableClass { 180 | DC, 181 | AC, 182 | } 183 | 184 | pub struct HuffmanTable { 185 | values: Vec, 186 | delta: [i32; 16], 187 | maxcode: [i32; 16], 188 | 189 | lut: [(u8, u8); 1 << LUT_BITS], 190 | ac_lut: Option<[(i16, u8); 1 << LUT_BITS]>, 191 | } 192 | 193 | impl HuffmanTable { 194 | pub fn new(bits: &[u8; 16], values: &[u8], class: HuffmanTableClass) -> Result { 195 | let (huffcode, huffsize) = derive_huffman_codes(bits)?; 196 | 197 | // Section F.2.2.3 198 | // Figure F.15 199 | // delta[i] is set to VALPTR(I) - MINCODE(I) 200 | let mut delta = [0i32; 16]; 201 | let mut maxcode = [-1i32; 16]; 202 | let mut j = 0; 203 | 204 | for i in 0..16 { 205 | if bits[i] != 0 { 206 | delta[i] = j as i32 - huffcode[j] as i32; 207 | j += bits[i] as usize; 208 | maxcode[i] = huffcode[j - 1] as i32; 209 | } 210 | } 211 | 212 | // Build a lookup table for faster decoding. 213 | let mut lut = [(0u8, 0u8); 1 << LUT_BITS]; 214 | 215 | for (i, &size) in huffsize 216 | .iter() 217 | .enumerate() 218 | .filter(|&(_, &size)| size <= LUT_BITS) 219 | { 220 | let bits_remaining = LUT_BITS - size; 221 | let start = (huffcode[i] << bits_remaining) as usize; 222 | 223 | for j in 0..1 << bits_remaining { 224 | lut[start + j] = (values[i], size); 225 | } 226 | } 227 | 228 | // Build a lookup table for small AC coefficients which both decodes the value and does the 229 | // equivalent of receive_extend. 230 | let ac_lut = match class { 231 | HuffmanTableClass::DC => None, 232 | HuffmanTableClass::AC => { 233 | let mut table = [(0i16, 0u8); 1 << LUT_BITS]; 234 | 235 | for (i, &(value, size)) in lut.iter().enumerate() { 236 | let run_length = value >> 4; 237 | let magnitude_category = value & 0x0f; 238 | 239 | if magnitude_category > 0 && size + magnitude_category <= LUT_BITS { 240 | let unextended_ac_value = (((i << size) & ((1 << LUT_BITS) - 1)) 241 | >> (LUT_BITS - magnitude_category)) 242 | as u16; 243 | let ac_value = extend(unextended_ac_value, magnitude_category); 244 | 245 | table[i] = (ac_value, (run_length << 4) | (size + magnitude_category)); 246 | } 247 | } 248 | 249 | Some(table) 250 | } 251 | }; 252 | 253 | Ok(HuffmanTable { 254 | values: values.to_vec(), 255 | delta: delta, 256 | maxcode: maxcode, 257 | lut: lut, 258 | ac_lut: ac_lut, 259 | }) 260 | } 261 | } 262 | 263 | // Section C.2 264 | fn derive_huffman_codes(bits: &[u8; 16]) -> Result<(Vec, Vec)> { 265 | // Figure C.1 266 | let huffsize = bits 267 | .iter() 268 | .enumerate() 269 | .fold(Vec::new(), |mut acc, (i, &value)| { 270 | acc.extend(std::iter::repeat((i + 1) as u8).take(value as usize)); 271 | acc 272 | }); 273 | 274 | // Figure C.2 275 | let mut huffcode = vec![0u16; huffsize.len()]; 276 | let mut code_size = huffsize[0]; 277 | let mut code = 0u32; 278 | 279 | for (i, &size) in huffsize.iter().enumerate() { 280 | while code_size < size { 281 | code <<= 1; 282 | code_size += 1; 283 | } 284 | 285 | if code >= (1u32 << size) { 286 | return Err(Error::Format("bad huffman code length".to_owned())); 287 | } 288 | 289 | huffcode[i] = code as u16; 290 | code += 1; 291 | } 292 | 293 | Ok((huffcode, huffsize)) 294 | } 295 | 296 | // https://www.loc.gov/preservation/digital/formats/fdd/fdd000063.shtml 297 | // "Avery Lee, writing in the rec.video.desktop newsgroup in 2001, commented that "MJPEG, or at 298 | // least the MJPEG in AVIs having the MJPG fourcc, is restricted JPEG with a fixed -- and 299 | // *omitted* -- Huffman table. The JPEG must be YCbCr colorspace, it must be 4:2:2, and it must 300 | // use basic Huffman encoding, not arithmetic or progressive.... You can indeed extract the 301 | // MJPEG frames and decode them with a regular JPEG decoder, but you have to prepend the DHT 302 | // segment to them, or else the decoder won't have any idea how to decompress the data. 303 | // The exact table necessary is given in the OpenDML spec."" 304 | pub fn fill_default_mjpeg_tables( 305 | scan: &ScanInfo, 306 | dc_huffman_tables: &mut [Option], 307 | ac_huffman_tables: &mut [Option], 308 | ) { 309 | // Section K.3.3 310 | 311 | if dc_huffman_tables[0].is_none() && scan.dc_table_indices.iter().any(|&i| i == 0) { 312 | // Table K.3 313 | dc_huffman_tables[0] = Some( 314 | HuffmanTable::new( 315 | &[ 316 | 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 317 | 0x00, 0x00, 0x00, 318 | ], 319 | &[ 320 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 321 | ], 322 | HuffmanTableClass::DC, 323 | ) 324 | .unwrap(), 325 | ); 326 | } 327 | if dc_huffman_tables[1].is_none() && scan.dc_table_indices.iter().any(|&i| i == 1) { 328 | // Table K.4 329 | dc_huffman_tables[1] = Some( 330 | HuffmanTable::new( 331 | &[ 332 | 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 333 | 0x00, 0x00, 0x00, 334 | ], 335 | &[ 336 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 337 | ], 338 | HuffmanTableClass::DC, 339 | ) 340 | .unwrap(), 341 | ); 342 | } 343 | if ac_huffman_tables[0].is_none() && scan.ac_table_indices.iter().any(|&i| i == 0) { 344 | // Table K.5 345 | ac_huffman_tables[0] = Some( 346 | HuffmanTable::new( 347 | &[ 348 | 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 349 | 0x00, 0x01, 0x7D, 350 | ], 351 | &[ 352 | 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 353 | 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 354 | 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 355 | 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 356 | 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 357 | 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 358 | 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 359 | 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 360 | 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 361 | 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 362 | 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 363 | 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 364 | 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 365 | ], 366 | HuffmanTableClass::AC, 367 | ) 368 | .unwrap(), 369 | ); 370 | } 371 | if ac_huffman_tables[1].is_none() && scan.ac_table_indices.iter().any(|&i| i == 1) { 372 | // Table K.6 373 | ac_huffman_tables[1] = Some( 374 | HuffmanTable::new( 375 | &[ 376 | 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 377 | 0x01, 0x02, 0x77, 378 | ], 379 | &[ 380 | 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 381 | 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 382 | 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 383 | 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 384 | 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 385 | 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 386 | 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 387 | 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 388 | 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 389 | 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 390 | 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 391 | 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 392 | 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 393 | ], 394 | HuffmanTableClass::AC, 395 | ) 396 | .unwrap(), 397 | ); 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/jpeg/decoder/idct.rs: -------------------------------------------------------------------------------- 1 | // Malicious JPEG files can cause operations in the idct to overflow. 2 | // One example is tests/crashtest/images/imagetestsuite/b0b8914cc5f7a6eff409f16d8cc236c5.jpg 3 | // That's why wrapping operators are needed. 4 | use super::parser::Dimensions; 5 | use std::{convert::TryFrom, num::Wrapping}; 6 | 7 | pub(crate) fn choose_idct_size(full_size: Dimensions, requested_size: Dimensions) -> usize { 8 | fn scaled(len: u16, scale: usize) -> u16 { 9 | ((len as u32 * scale as u32 - 1) / 8 + 1) as u16 10 | } 11 | 12 | for &scale in &[1, 2, 4] { 13 | if scaled(full_size.width, scale) >= requested_size.width 14 | || scaled(full_size.height, scale) >= requested_size.height 15 | { 16 | return scale; 17 | } 18 | } 19 | 20 | 8 21 | } 22 | 23 | #[test] 24 | fn test_choose_idct_size() { 25 | assert_eq!( 26 | choose_idct_size( 27 | Dimensions { 28 | width: 5472, 29 | height: 3648 30 | }, 31 | Dimensions { 32 | width: 200, 33 | height: 200 34 | } 35 | ), 36 | 1 37 | ); 38 | assert_eq!( 39 | choose_idct_size( 40 | Dimensions { 41 | width: 5472, 42 | height: 3648 43 | }, 44 | Dimensions { 45 | width: 500, 46 | height: 500 47 | } 48 | ), 49 | 1 50 | ); 51 | assert_eq!( 52 | choose_idct_size( 53 | Dimensions { 54 | width: 5472, 55 | height: 3648 56 | }, 57 | Dimensions { 58 | width: 684, 59 | height: 456 60 | } 61 | ), 62 | 1 63 | ); 64 | assert_eq!( 65 | choose_idct_size( 66 | Dimensions { 67 | width: 5472, 68 | height: 3648 69 | }, 70 | Dimensions { 71 | width: 999, 72 | height: 456 73 | } 74 | ), 75 | 1 76 | ); 77 | assert_eq!( 78 | choose_idct_size( 79 | Dimensions { 80 | width: 5472, 81 | height: 3648 82 | }, 83 | Dimensions { 84 | width: 684, 85 | height: 999 86 | } 87 | ), 88 | 1 89 | ); 90 | assert_eq!( 91 | choose_idct_size( 92 | Dimensions { 93 | width: 500, 94 | height: 333 95 | }, 96 | Dimensions { 97 | width: 63, 98 | height: 42 99 | } 100 | ), 101 | 1 102 | ); 103 | 104 | assert_eq!( 105 | choose_idct_size( 106 | Dimensions { 107 | width: 5472, 108 | height: 3648 109 | }, 110 | Dimensions { 111 | width: 685, 112 | height: 999 113 | } 114 | ), 115 | 2 116 | ); 117 | assert_eq!( 118 | choose_idct_size( 119 | Dimensions { 120 | width: 5472, 121 | height: 3648 122 | }, 123 | Dimensions { 124 | width: 1000, 125 | height: 1000 126 | } 127 | ), 128 | 2 129 | ); 130 | assert_eq!( 131 | choose_idct_size( 132 | Dimensions { 133 | width: 5472, 134 | height: 3648 135 | }, 136 | Dimensions { 137 | width: 1400, 138 | height: 1400 139 | } 140 | ), 141 | 4 142 | ); 143 | 144 | assert_eq!( 145 | choose_idct_size( 146 | Dimensions { 147 | width: 5472, 148 | height: 3648 149 | }, 150 | Dimensions { 151 | width: 5472, 152 | height: 3648 153 | } 154 | ), 155 | 8 156 | ); 157 | assert_eq!( 158 | choose_idct_size( 159 | Dimensions { 160 | width: 5472, 161 | height: 3648 162 | }, 163 | Dimensions { 164 | width: 16384, 165 | height: 16384 166 | } 167 | ), 168 | 8 169 | ); 170 | assert_eq!( 171 | choose_idct_size( 172 | Dimensions { 173 | width: 1, 174 | height: 1 175 | }, 176 | Dimensions { 177 | width: 65535, 178 | height: 65535 179 | } 180 | ), 181 | 8 182 | ); 183 | assert_eq!( 184 | choose_idct_size( 185 | Dimensions { 186 | width: 5472, 187 | height: 3648 188 | }, 189 | Dimensions { 190 | width: 16384, 191 | height: 16384 192 | } 193 | ), 194 | 8 195 | ); 196 | } 197 | 198 | pub(crate) fn dequantize_and_idct_block( 199 | scale: usize, 200 | coefficients: &[i16], 201 | quantization_table: &[u16; 64], 202 | output_linestride: usize, 203 | output: &mut [u8], 204 | ) { 205 | match scale { 206 | 8 => dequantize_and_idct_block_8x8( 207 | coefficients, 208 | quantization_table, 209 | output_linestride, 210 | output, 211 | ), 212 | 4 => dequantize_and_idct_block_4x4( 213 | coefficients, 214 | quantization_table, 215 | output_linestride, 216 | output, 217 | ), 218 | 2 => dequantize_and_idct_block_2x2( 219 | coefficients, 220 | quantization_table, 221 | output_linestride, 222 | output, 223 | ), 224 | 1 => dequantize_and_idct_block_1x1( 225 | coefficients, 226 | quantization_table, 227 | output_linestride, 228 | output, 229 | ), 230 | _ => panic!("Unsupported IDCT scale {}/8", scale), 231 | } 232 | } 233 | 234 | pub fn dequantize_and_idct_block_8x8( 235 | coefficients: &[i16], 236 | quantization_table: &[u16; 64], 237 | output_linestride: usize, 238 | output: &mut [u8], 239 | ) { 240 | let output = output.chunks_mut(output_linestride); 241 | dequantize_and_idct_block_8x8_inner(coefficients, quantization_table, output) 242 | } 243 | 244 | // This is based on stb_image's 'stbi__idct_block'. 245 | fn dequantize_and_idct_block_8x8_inner<'a, I>( 246 | coefficients: &[i16], 247 | quantization_table: &[u16; 64], 248 | output: I, 249 | ) where 250 | I: IntoIterator, 251 | I::IntoIter: ExactSizeIterator, 252 | { 253 | let output = output.into_iter(); 254 | debug_assert!( 255 | output.len() >= 8, 256 | "Output iterator has the wrong length: {}", 257 | output.len() 258 | ); 259 | 260 | // optimizer hint to eliminate bounds checks within loops 261 | assert!(coefficients.len() == 64); 262 | 263 | let mut temp = [Wrapping(0); 64]; 264 | 265 | // columns 266 | for i in 0..8 { 267 | if coefficients[i + 8] == 0 268 | && coefficients[i + 16] == 0 269 | && coefficients[i + 24] == 0 270 | && coefficients[i + 32] == 0 271 | && coefficients[i + 40] == 0 272 | && coefficients[i + 48] == 0 273 | && coefficients[i + 56] == 0 274 | { 275 | let dcterm = dequantize(coefficients[i], quantization_table[i]) << 2; 276 | temp[i] = dcterm; 277 | temp[i + 8] = dcterm; 278 | temp[i + 16] = dcterm; 279 | temp[i + 24] = dcterm; 280 | temp[i + 32] = dcterm; 281 | temp[i + 40] = dcterm; 282 | temp[i + 48] = dcterm; 283 | temp[i + 56] = dcterm; 284 | } else { 285 | let s0 = dequantize(coefficients[i], quantization_table[i]); 286 | let s1 = dequantize(coefficients[i + 8], quantization_table[i + 8]); 287 | let s2 = dequantize(coefficients[i + 16], quantization_table[i + 16]); 288 | let s3 = dequantize(coefficients[i + 24], quantization_table[i + 24]); 289 | let s4 = dequantize(coefficients[i + 32], quantization_table[i + 32]); 290 | let s5 = dequantize(coefficients[i + 40], quantization_table[i + 40]); 291 | let s6 = dequantize(coefficients[i + 48], quantization_table[i + 48]); 292 | let s7 = dequantize(coefficients[i + 56], quantization_table[i + 56]); 293 | 294 | let Kernel { 295 | xs: [x0, x1, x2, x3], 296 | ts: [t0, t1, t2, t3], 297 | } = kernel( 298 | [s0, s1, s2, s3, s4, s5, s6, s7], 299 | // constants scaled things up by 1<<12; let's bring them back 300 | // down, but keep 2 extra bits of precision 301 | 512, 302 | ); 303 | 304 | temp[i] = (x0 + t3) >> 10; 305 | temp[i + 56] = (x0 - t3) >> 10; 306 | temp[i + 8] = (x1 + t2) >> 10; 307 | temp[i + 48] = (x1 - t2) >> 10; 308 | temp[i + 16] = (x2 + t1) >> 10; 309 | temp[i + 40] = (x2 - t1) >> 10; 310 | temp[i + 24] = (x3 + t0) >> 10; 311 | temp[i + 32] = (x3 - t0) >> 10; 312 | } 313 | } 314 | 315 | for (chunk, output_chunk) in temp.chunks_exact(8).zip(output) { 316 | let chunk = <&[_; 8]>::try_from(chunk).unwrap(); 317 | 318 | // constants scaled things up by 1<<12, plus we had 1<<2 from first 319 | // loop, plus horizontal and vertical each scale by sqrt(8) so together 320 | // we've got an extra 1<<3, so 1<<17 total we need to remove. 321 | // so we want to round that, which means adding 0.5 * 1<<17, 322 | // aka 65536. Also, we'll end up with -128 to 127 that we want 323 | // to encode as 0..255 by adding 128, so we'll add that before the shift 324 | const X_SCALE: i32 = 65536 + (128 << 17); 325 | 326 | // TODO When the minimum rust version supports it 327 | // let [s0, rest @ ..] = chunk; 328 | let (s0, rest) = chunk.split_first().unwrap(); 329 | if *rest == [Wrapping(0); 7] { 330 | let dcterm = stbi_clamp((stbi_fsh(*s0) + Wrapping(X_SCALE)) >> 17); 331 | output_chunk[0] = dcterm; 332 | output_chunk[1] = dcterm; 333 | output_chunk[2] = dcterm; 334 | output_chunk[3] = dcterm; 335 | output_chunk[4] = dcterm; 336 | output_chunk[5] = dcterm; 337 | output_chunk[6] = dcterm; 338 | output_chunk[7] = dcterm; 339 | } else { 340 | let Kernel { 341 | xs: [x0, x1, x2, x3], 342 | ts: [t0, t1, t2, t3], 343 | } = kernel(*chunk, X_SCALE); 344 | 345 | output_chunk[0] = stbi_clamp((x0 + t3) >> 17); 346 | output_chunk[7] = stbi_clamp((x0 - t3) >> 17); 347 | output_chunk[1] = stbi_clamp((x1 + t2) >> 17); 348 | output_chunk[6] = stbi_clamp((x1 - t2) >> 17); 349 | output_chunk[2] = stbi_clamp((x2 + t1) >> 17); 350 | output_chunk[5] = stbi_clamp((x2 - t1) >> 17); 351 | output_chunk[3] = stbi_clamp((x3 + t0) >> 17); 352 | output_chunk[4] = stbi_clamp((x3 - t0) >> 17); 353 | } 354 | } 355 | } 356 | 357 | struct Kernel { 358 | xs: [Wrapping; 4], 359 | ts: [Wrapping; 4], 360 | } 361 | 362 | #[inline] 363 | fn kernel_x([s0, s2, s4, s6]: [Wrapping; 4], x_scale: i32) -> [Wrapping; 4] { 364 | // Even `chunk` indicies 365 | let (t2, t3); 366 | { 367 | let p2 = s2; 368 | let p3 = s6; 369 | 370 | let p1 = (p2 + p3) * stbi_f2f(0.5411961); 371 | t2 = p1 + p3 * stbi_f2f(-1.847759065); 372 | t3 = p1 + p2 * stbi_f2f(0.765366865); 373 | } 374 | 375 | let (t0, t1); 376 | { 377 | let p2 = s0; 378 | let p3 = s4; 379 | 380 | t0 = stbi_fsh(p2 + p3); 381 | t1 = stbi_fsh(p2 - p3); 382 | } 383 | 384 | let x0 = t0 + t3; 385 | let x3 = t0 - t3; 386 | let x1 = t1 + t2; 387 | let x2 = t1 - t2; 388 | 389 | let x_scale = Wrapping(x_scale); 390 | 391 | [x0 + x_scale, x1 + x_scale, x2 + x_scale, x3 + x_scale] 392 | } 393 | 394 | #[inline] 395 | fn kernel_t([s1, s3, s5, s7]: [Wrapping; 4]) -> [Wrapping; 4] { 396 | // Odd `chunk` indicies 397 | let mut t0 = s7; 398 | let mut t1 = s5; 399 | let mut t2 = s3; 400 | let mut t3 = s1; 401 | 402 | let p3 = t0 + t2; 403 | let p4 = t1 + t3; 404 | let p1 = t0 + t3; 405 | let p2 = t1 + t2; 406 | let p5 = (p3 + p4) * stbi_f2f(1.175875602); 407 | 408 | t0 *= stbi_f2f(0.298631336); 409 | t1 *= stbi_f2f(2.053119869); 410 | t2 *= stbi_f2f(3.072711026); 411 | t3 *= stbi_f2f(1.501321110); 412 | 413 | let p1 = p5 + p1 * stbi_f2f(-0.899976223); 414 | let p2 = p5 + p2 * stbi_f2f(-2.562915447); 415 | let p3 = p3 * stbi_f2f(-1.961570560); 416 | let p4 = p4 * stbi_f2f(-0.390180644); 417 | 418 | t3 += p1 + p4; 419 | t2 += p2 + p3; 420 | t1 += p2 + p4; 421 | t0 += p1 + p3; 422 | 423 | [t0, t1, t2, t3] 424 | } 425 | 426 | #[inline] 427 | fn kernel([s0, s1, s2, s3, s4, s5, s6, s7]: [Wrapping; 8], x_scale: i32) -> Kernel { 428 | Kernel { 429 | xs: kernel_x([s0, s2, s4, s6], x_scale), 430 | ts: kernel_t([s1, s3, s5, s7]), 431 | } 432 | } 433 | 434 | #[inline(always)] 435 | fn dequantize(c: i16, q: u16) -> Wrapping { 436 | Wrapping(i32::from(c) * i32::from(q)) 437 | } 438 | 439 | // 4x4 and 2x2 IDCT based on Rakesh Dugad and Narendra Ahuja: "A Fast Scheme for Image Size Change in the Compressed Domain" (2001). 440 | // http://sylvana.net/jpegcrop/jidctred/ 441 | fn dequantize_and_idct_block_4x4( 442 | coefficients: &[i16], 443 | quantization_table: &[u16; 64], 444 | output_linestride: usize, 445 | output: &mut [u8], 446 | ) { 447 | debug_assert_eq!(coefficients.len(), 64); 448 | let mut temp = [Wrapping(0i32); 4 * 4]; 449 | 450 | const CONST_BITS: usize = 12; 451 | const PASS1_BITS: usize = 2; 452 | const FINAL_BITS: usize = CONST_BITS + PASS1_BITS + 3; 453 | 454 | // columns 455 | for i in 0..4 { 456 | let s0 = Wrapping(coefficients[i + 8 * 0] as i32 * quantization_table[i + 8 * 0] as i32); 457 | let s1 = Wrapping(coefficients[i + 8 * 1] as i32 * quantization_table[i + 8 * 1] as i32); 458 | let s2 = Wrapping(coefficients[i + 8 * 2] as i32 * quantization_table[i + 8 * 2] as i32); 459 | let s3 = Wrapping(coefficients[i + 8 * 3] as i32 * quantization_table[i + 8 * 3] as i32); 460 | 461 | let x0 = (s0 + s2) << PASS1_BITS; 462 | let x2 = (s0 - s2) << PASS1_BITS; 463 | 464 | let p1 = (s1 + s3) * stbi_f2f(0.541196100); 465 | let t0 = (p1 + s3 * stbi_f2f(-1.847759065) + Wrapping(512)) >> (CONST_BITS - PASS1_BITS); 466 | let t2 = (p1 + s1 * stbi_f2f(0.765366865) + Wrapping(512)) >> (CONST_BITS - PASS1_BITS); 467 | 468 | temp[i + 4 * 0] = x0 + t2; 469 | temp[i + 4 * 3] = x0 - t2; 470 | temp[i + 4 * 1] = x2 + t0; 471 | temp[i + 4 * 2] = x2 - t0; 472 | } 473 | 474 | for i in 0..4 { 475 | let s0 = temp[i * 4 + 0]; 476 | let s1 = temp[i * 4 + 1]; 477 | let s2 = temp[i * 4 + 2]; 478 | let s3 = temp[i * 4 + 3]; 479 | 480 | let x0 = (s0 + s2) << CONST_BITS; 481 | let x2 = (s0 - s2) << CONST_BITS; 482 | 483 | let p1 = (s1 + s3) * stbi_f2f(0.541196100); 484 | let t0 = p1 + s3 * stbi_f2f(-1.847759065); 485 | let t2 = p1 + s1 * stbi_f2f(0.765366865); 486 | 487 | // constants scaled things up by 1<<12, plus we had 1<<2 from first 488 | // loop, plus horizontal and vertical each scale by sqrt(8) so together 489 | // we've got an extra 1<<3, so 1<<17 total we need to remove. 490 | // so we want to round that, which means adding 0.5 * 1<<17, 491 | // aka 65536. Also, we'll end up with -128 to 127 that we want 492 | // to encode as 0..255 by adding 128, so we'll add that before the shift 493 | let x0 = x0 + Wrapping(1 << (FINAL_BITS - 1)) + Wrapping(128 << FINAL_BITS); 494 | let x2 = x2 + Wrapping(1 << (FINAL_BITS - 1)) + Wrapping(128 << FINAL_BITS); 495 | 496 | output[i * output_linestride + 0] = stbi_clamp((x0 + t2) >> FINAL_BITS); 497 | output[i * output_linestride + 3] = stbi_clamp((x0 - t2) >> FINAL_BITS); 498 | output[i * output_linestride + 1] = stbi_clamp((x2 + t0) >> FINAL_BITS); 499 | output[i * output_linestride + 2] = stbi_clamp((x2 - t0) >> FINAL_BITS); 500 | } 501 | } 502 | 503 | fn dequantize_and_idct_block_2x2( 504 | coefficients: &[i16], 505 | quantization_table: &[u16; 64], 506 | output_linestride: usize, 507 | output: &mut [u8], 508 | ) { 509 | debug_assert_eq!(coefficients.len(), 64); 510 | 511 | const SCALE_BITS: usize = 3; 512 | 513 | // Column 0 514 | let s00 = Wrapping(coefficients[8 * 0] as i32 * quantization_table[8 * 0] as i32); 515 | let s10 = Wrapping(coefficients[8 * 1] as i32 * quantization_table[8 * 1] as i32); 516 | 517 | let x0 = s00 + s10; 518 | let x2 = s00 - s10; 519 | 520 | // Column 1 521 | let s01 = Wrapping(coefficients[8 * 0 + 1] as i32 * quantization_table[8 * 0 + 1] as i32); 522 | let s11 = Wrapping(coefficients[8 * 1 + 1] as i32 * quantization_table[8 * 1 + 1] as i32); 523 | 524 | let x1 = s01 + s11; 525 | let x3 = s01 - s11; 526 | 527 | let x0 = x0 + Wrapping(1 << (SCALE_BITS - 1)) + Wrapping(128 << SCALE_BITS); 528 | let x2 = x2 + Wrapping(1 << (SCALE_BITS - 1)) + Wrapping(128 << SCALE_BITS); 529 | 530 | // Row 0 531 | output[0] = stbi_clamp((x0 + x1) >> SCALE_BITS); 532 | output[1] = stbi_clamp((x0 - x1) >> SCALE_BITS); 533 | 534 | // Row 1 535 | output[output_linestride + 0] = stbi_clamp((x2 + x3) >> SCALE_BITS); 536 | output[output_linestride + 1] = stbi_clamp((x2 - x3) >> SCALE_BITS); 537 | } 538 | 539 | fn dequantize_and_idct_block_1x1( 540 | coefficients: &[i16], 541 | quantization_table: &[u16; 64], 542 | _output_linestride: usize, 543 | output: &mut [u8], 544 | ) { 545 | debug_assert_eq!(coefficients.len(), 64); 546 | 547 | let s0 = (Wrapping(coefficients[0] as i32 * quantization_table[0] as i32) + Wrapping(128 * 8)) 548 | / Wrapping(8); 549 | output[0] = stbi_clamp(s0); 550 | } 551 | 552 | // take a -128..127 value and stbi__clamp it and convert to 0..255 553 | fn stbi_clamp(x: Wrapping) -> u8 { 554 | x.0.max(0).min(255) as u8 555 | } 556 | 557 | fn stbi_f2f(x: f32) -> Wrapping { 558 | Wrapping((x * 4096.0 + 0.5) as i32) 559 | } 560 | 561 | fn stbi_fsh(x: Wrapping) -> Wrapping { 562 | x << 12 563 | } 564 | 565 | #[test] 566 | fn test_dequantize_and_idct_block_8x8() { 567 | let coefficients: [i16; 8 * 8] = [ 568 | -14, -39, 58, -2, 3, 3, 0, 1, 11, 27, 4, -3, 3, 0, 1, 0, -6, -13, -9, -1, -2, -1, 0, 0, -4, 569 | 0, -1, -2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, -3, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 570 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 571 | ]; 572 | 573 | let quantization_table: [u16; 8 * 8] = [ 574 | 8, 6, 5, 8, 12, 20, 26, 31, 6, 6, 7, 10, 13, 29, 30, 28, 7, 7, 8, 12, 20, 29, 35, 28, 7, 9, 575 | 11, 15, 26, 44, 40, 31, 9, 11, 19, 28, 34, 55, 52, 39, 12, 18, 28, 32, 41, 52, 57, 46, 25, 576 | 32, 39, 44, 52, 61, 60, 51, 36, 46, 48, 49, 56, 50, 52, 50, 577 | ]; 578 | let output_linestride: usize = 8; 579 | let mut output = [0u8; 8 * 8]; 580 | dequantize_and_idct_block_8x8( 581 | &coefficients, 582 | &quantization_table, 583 | output_linestride, 584 | &mut output, 585 | ); 586 | let expected_output = [ 587 | 118, 92, 110, 83, 77, 93, 144, 198, 172, 116, 114, 87, 78, 93, 146, 191, 194, 107, 91, 76, 588 | 71, 93, 160, 198, 196, 100, 80, 74, 67, 92, 174, 209, 182, 104, 88, 81, 68, 89, 178, 206, 589 | 105, 64, 59, 59, 63, 94, 183, 201, 35, 27, 28, 37, 72, 121, 203, 204, 37, 45, 41, 47, 98, 590 | 154, 223, 208, 591 | ]; 592 | assert_eq!(&output[..], &expected_output[..]); 593 | } 594 | 595 | #[test] 596 | fn test_dequantize_and_idct_block_8x8_all_zero() { 597 | let mut output = [0u8; 8 * 8]; 598 | dequantize_and_idct_block_8x8(&[0; 8 * 8], &[666; 8 * 8], 8, &mut output); 599 | assert_eq!(&output[..], &[128; 8 * 8][..]); 600 | } 601 | 602 | #[test] 603 | fn test_dequantize_and_idct_block_8x8_saturated() { 604 | let mut output = [0u8; 8 * 8]; 605 | dequantize_and_idct_block_8x8( 606 | &[std::i16::MAX; 8 * 8], 607 | &[std::u16::MAX; 8 * 8], 608 | 8, 609 | &mut output, 610 | ); 611 | let expected = [ 612 | 0, 0, 0, 255, 255, 0, 0, 255, 0, 0, 215, 0, 0, 255, 255, 0, 255, 255, 255, 255, 255, 0, 0, 613 | 255, 0, 0, 255, 0, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 0, 0, 255, 255, 0, 255, 255, 614 | 255, 170, 0, 0, 255, 0, 0, 0, 0, 0, 255, 255, 255, 0, 255, 0, 255, 0, 0, 615 | ]; 616 | assert_eq!(&output[..], &expected[..]); 617 | } 618 | -------------------------------------------------------------------------------- /src/jpeg/decoder/parser.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use error::{Error, Result, UnsupportedFeature}; 3 | use huffman::{HuffmanTable, HuffmanTableClass}; 4 | use marker::Marker; 5 | use marker::Marker::*; 6 | use std::io::{self, Read}; 7 | use std::ops::Range; 8 | 9 | #[derive(Clone, Copy, Debug, PartialEq)] 10 | pub struct Dimensions { 11 | pub width: u16, 12 | pub height: u16, 13 | } 14 | 15 | #[derive(Clone, Copy, Debug, PartialEq)] 16 | pub enum EntropyCoding { 17 | Huffman, 18 | Arithmetic, 19 | } 20 | 21 | #[derive(Clone, Copy, Debug, PartialEq)] 22 | pub enum CodingProcess { 23 | DctSequential, 24 | DctProgressive, 25 | Lossless, 26 | } 27 | 28 | #[derive(Clone)] 29 | pub struct FrameInfo { 30 | pub is_baseline: bool, 31 | pub is_differential: bool, 32 | pub coding_process: CodingProcess, 33 | pub entropy_coding: EntropyCoding, 34 | pub precision: u8, 35 | 36 | pub image_size: Dimensions, 37 | pub output_size: Dimensions, 38 | pub mcu_size: Dimensions, 39 | pub components: Vec, 40 | } 41 | 42 | #[derive(Debug)] 43 | pub struct ScanInfo { 44 | pub component_indices: Vec, 45 | pub dc_table_indices: Vec, 46 | pub ac_table_indices: Vec, 47 | 48 | pub spectral_selection: Range, 49 | pub successive_approximation_high: u8, 50 | pub successive_approximation_low: u8, 51 | } 52 | 53 | #[derive(Clone, Debug)] 54 | pub struct Component { 55 | pub identifier: u8, 56 | 57 | pub horizontal_sampling_factor: u8, 58 | pub vertical_sampling_factor: u8, 59 | 60 | pub quantization_table_index: usize, 61 | 62 | pub dct_scale: usize, 63 | 64 | pub size: Dimensions, 65 | pub block_size: Dimensions, 66 | } 67 | 68 | #[derive(Debug)] 69 | pub enum AppData { 70 | Adobe(AdobeColorTransform), 71 | Jfif, 72 | Avi1, 73 | Icc(IccChunk), 74 | } 75 | 76 | // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe 77 | #[derive(Clone, Copy, Debug, PartialEq)] 78 | pub enum AdobeColorTransform { 79 | // RGB or CMYK 80 | Unknown, 81 | YCbCr, 82 | // YCbCrK 83 | YCCK, 84 | } 85 | #[derive(Debug)] 86 | pub struct IccChunk { 87 | pub num_markers: u8, 88 | pub seq_no: u8, 89 | pub data: Vec, 90 | } 91 | 92 | impl FrameInfo { 93 | pub(crate) fn update_idct_size(&mut self, idct_size: usize) -> Result<()> { 94 | for component in &mut self.components { 95 | component.dct_scale = idct_size; 96 | } 97 | 98 | update_component_sizes(self.image_size, &mut self.components)?; 99 | 100 | self.output_size = Dimensions { 101 | width: (self.image_size.width as f32 * idct_size as f32 / 8.0).ceil() as u16, 102 | height: (self.image_size.height as f32 * idct_size as f32 / 8.0).ceil() as u16, 103 | }; 104 | 105 | Ok(()) 106 | } 107 | } 108 | 109 | pub(crate) fn read_length(reader: &mut R, marker: Marker) -> Result { 110 | assert!(marker.has_length()); 111 | 112 | // length is including itself. 113 | let length = usize::from(read_u16_from_be(reader)?); 114 | 115 | if length < 2 { 116 | return Err(Error::Format(format!( 117 | "encountered {:?} with invalid length {}", 118 | marker, length 119 | ))); 120 | } 121 | 122 | Ok(length - 2) 123 | } 124 | 125 | fn skip_bytes(reader: &mut R, length: usize) -> Result<()> { 126 | let length = length as u64; 127 | let to_skip = &mut reader.by_ref().take(length); 128 | let copied = io::copy(to_skip, &mut io::sink())?; 129 | if copied < length { 130 | Err(Error::Io(io::ErrorKind::UnexpectedEof.into())) 131 | } else { 132 | Ok(()) 133 | } 134 | } 135 | 136 | // Section B.2.2 137 | pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { 138 | let length = read_length(reader, marker)?; 139 | 140 | if length <= 6 { 141 | return Err(Error::Format("invalid length in SOF".to_owned())); 142 | } 143 | 144 | let is_baseline = marker == SOF(0); 145 | let is_differential = match marker { 146 | SOF(0..=3) | SOF(9..=11) => false, 147 | SOF(5..=7) | SOF(13..=15) => true, 148 | _ => panic!(), 149 | }; 150 | let coding_process = match marker { 151 | SOF(0) | SOF(1) | SOF(5) | SOF(9) | SOF(13) => CodingProcess::DctSequential, 152 | SOF(2) | SOF(6) | SOF(10) | SOF(14) => CodingProcess::DctProgressive, 153 | SOF(3) | SOF(7) | SOF(11) | SOF(15) => CodingProcess::Lossless, 154 | _ => panic!(), 155 | }; 156 | let entropy_coding = match marker { 157 | SOF(0..=3) | SOF(5..=7) => EntropyCoding::Huffman, 158 | SOF(9..=11) | SOF(13..=15) => EntropyCoding::Arithmetic, 159 | _ => panic!(), 160 | }; 161 | 162 | let precision = read_u8(reader)?; 163 | 164 | match precision { 165 | 8 => {} 166 | 12 => { 167 | if is_baseline { 168 | return Err(Error::Format( 169 | "12 bit sample precision is not allowed in baseline".to_owned(), 170 | )); 171 | } 172 | } 173 | _ => { 174 | if coding_process != CodingProcess::Lossless { 175 | return Err(Error::Format(format!( 176 | "invalid precision {} in frame header", 177 | precision 178 | ))); 179 | } 180 | } 181 | } 182 | 183 | let height = read_u16_from_be(reader)?; 184 | let width = read_u16_from_be(reader)?; 185 | 186 | // height: 187 | // "Value 0 indicates that the number of lines shall be defined by the DNL marker and 188 | // parameters at the end of the first scan (see B.2.5)." 189 | if height == 0 { 190 | return Err(Error::Unsupported(UnsupportedFeature::DNL)); 191 | } 192 | 193 | if width == 0 { 194 | return Err(Error::Format("zero width in frame header".to_owned())); 195 | } 196 | 197 | let component_count = read_u8(reader)?; 198 | 199 | if component_count == 0 { 200 | return Err(Error::Format( 201 | "zero component count in frame header".to_owned(), 202 | )); 203 | } 204 | if coding_process == CodingProcess::DctProgressive && component_count > 4 { 205 | return Err(Error::Format( 206 | "progressive frame with more than 4 components".to_owned(), 207 | )); 208 | } 209 | 210 | if length != 6 + 3 * component_count as usize { 211 | return Err(Error::Format("invalid length in SOF".to_owned())); 212 | } 213 | 214 | let mut components: Vec = Vec::with_capacity(component_count as usize); 215 | 216 | for _ in 0..component_count { 217 | let identifier = read_u8(reader)?; 218 | 219 | // Each component's identifier must be unique. 220 | if components.iter().any(|c| c.identifier == identifier) { 221 | return Err(Error::Format(format!( 222 | "duplicate frame component identifier {}", 223 | identifier 224 | ))); 225 | } 226 | 227 | let byte = read_u8(reader)?; 228 | let horizontal_sampling_factor = byte >> 4; 229 | let vertical_sampling_factor = byte & 0x0f; 230 | 231 | if horizontal_sampling_factor == 0 || horizontal_sampling_factor > 4 { 232 | return Err(Error::Format(format!( 233 | "invalid horizontal sampling factor {}", 234 | horizontal_sampling_factor 235 | ))); 236 | } 237 | if vertical_sampling_factor == 0 || vertical_sampling_factor > 4 { 238 | return Err(Error::Format(format!( 239 | "invalid vertical sampling factor {}", 240 | vertical_sampling_factor 241 | ))); 242 | } 243 | 244 | let quantization_table_index = read_u8(reader)?; 245 | 246 | if quantization_table_index > 3 247 | || (coding_process == CodingProcess::Lossless && quantization_table_index != 0) 248 | { 249 | return Err(Error::Format(format!( 250 | "invalid quantization table index {}", 251 | quantization_table_index 252 | ))); 253 | } 254 | 255 | components.push(Component { 256 | identifier: identifier, 257 | horizontal_sampling_factor: horizontal_sampling_factor, 258 | vertical_sampling_factor: vertical_sampling_factor, 259 | quantization_table_index: quantization_table_index as usize, 260 | dct_scale: 8, 261 | size: Dimensions { 262 | width: 0, 263 | height: 0, 264 | }, 265 | block_size: Dimensions { 266 | width: 0, 267 | height: 0, 268 | }, 269 | }); 270 | } 271 | 272 | let mcu_size = update_component_sizes(Dimensions { width, height }, &mut components)?; 273 | 274 | Ok(FrameInfo { 275 | is_baseline: is_baseline, 276 | is_differential: is_differential, 277 | coding_process: coding_process, 278 | entropy_coding: entropy_coding, 279 | precision: precision, 280 | image_size: Dimensions { width, height }, 281 | output_size: Dimensions { width, height }, 282 | mcu_size, 283 | components: components, 284 | }) 285 | } 286 | 287 | /// Returns ceil(x/y), requires x>0 288 | fn ceil_div(x: u32, y: u32) -> Result { 289 | if x == 0 || y == 0 { 290 | // TODO Determine how this error is reached. Can we validate input 291 | // earlier and error out then? 292 | return Err(Error::Format("invalid dimensions".to_owned())); 293 | } 294 | Ok((1 + ((x - 1) / y)) as u16) 295 | } 296 | 297 | fn update_component_sizes(size: Dimensions, components: &mut [Component]) -> Result { 298 | let h_max = components 299 | .iter() 300 | .map(|c| c.horizontal_sampling_factor) 301 | .max() 302 | .unwrap() as u32; 303 | let v_max = components 304 | .iter() 305 | .map(|c| c.vertical_sampling_factor) 306 | .max() 307 | .unwrap() as u32; 308 | 309 | let mcu_size = Dimensions { 310 | width: ceil_div(size.width as u32, h_max * 8)?, 311 | height: ceil_div(size.height as u32, v_max * 8)?, 312 | }; 313 | 314 | for component in components { 315 | component.size.width = ceil_div( 316 | size.width as u32 317 | * component.horizontal_sampling_factor as u32 318 | * component.dct_scale as u32, 319 | h_max * 8, 320 | )?; 321 | component.size.height = ceil_div( 322 | size.height as u32 323 | * component.vertical_sampling_factor as u32 324 | * component.dct_scale as u32, 325 | v_max * 8, 326 | )?; 327 | 328 | component.block_size.width = mcu_size.width * component.horizontal_sampling_factor as u16; 329 | component.block_size.height = mcu_size.height * component.vertical_sampling_factor as u16; 330 | } 331 | 332 | Ok(mcu_size) 333 | } 334 | 335 | #[test] 336 | fn test_update_component_sizes() { 337 | let mut components = [Component { 338 | identifier: 1, 339 | horizontal_sampling_factor: 2, 340 | vertical_sampling_factor: 2, 341 | quantization_table_index: 0, 342 | dct_scale: 8, 343 | size: Dimensions { 344 | width: 0, 345 | height: 0, 346 | }, 347 | block_size: Dimensions { 348 | width: 0, 349 | height: 0, 350 | }, 351 | }]; 352 | let mcu = update_component_sizes( 353 | Dimensions { 354 | width: 800, 355 | height: 280, 356 | }, 357 | &mut components, 358 | ) 359 | .unwrap(); 360 | assert_eq!( 361 | mcu, 362 | Dimensions { 363 | width: 50, 364 | height: 18 365 | } 366 | ); 367 | assert_eq!( 368 | components[0].block_size, 369 | Dimensions { 370 | width: 100, 371 | height: 36 372 | } 373 | ); 374 | assert_eq!( 375 | components[0].size, 376 | Dimensions { 377 | width: 800, 378 | height: 280 379 | } 380 | ); 381 | } 382 | 383 | // Section B.2.3 384 | pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result { 385 | let length = read_length(reader, SOS)?; 386 | if 0 == length { 387 | return Err(Error::Format("zero length in SOS".to_owned())); 388 | } 389 | 390 | let component_count = read_u8(reader)?; 391 | 392 | if component_count == 0 || component_count > 4 { 393 | return Err(Error::Format(format!( 394 | "invalid component count {} in scan header", 395 | component_count 396 | ))); 397 | } 398 | 399 | if length != 4 + 2 * component_count as usize { 400 | return Err(Error::Format("invalid length in SOS".to_owned())); 401 | } 402 | 403 | let mut component_indices = Vec::with_capacity(component_count as usize); 404 | let mut dc_table_indices = Vec::with_capacity(component_count as usize); 405 | let mut ac_table_indices = Vec::with_capacity(component_count as usize); 406 | 407 | for _ in 0..component_count { 408 | let identifier = read_u8(reader)?; 409 | 410 | let component_index = match frame.components.iter().position(|c| c.identifier == identifier) { 411 | Some(value) => value, 412 | None => return Err(Error::Format(format!("scan component identifier {} does not match any of the component identifiers defined in the frame", identifier))), 413 | }; 414 | 415 | // Each of the scan's components must be unique. 416 | if component_indices.contains(&component_index) { 417 | return Err(Error::Format(format!( 418 | "duplicate scan component identifier {}", 419 | identifier 420 | ))); 421 | } 422 | 423 | // "... the ordering in the scan header shall follow the ordering in the frame header." 424 | if component_index < *component_indices.iter().max().unwrap_or(&0) { 425 | return Err(Error::Format( 426 | "the scan component order does not follow the order in the frame header".to_owned(), 427 | )); 428 | } 429 | 430 | let byte = read_u8(reader)?; 431 | let dc_table_index = byte >> 4; 432 | let ac_table_index = byte & 0x0f; 433 | 434 | if dc_table_index > 3 || (frame.is_baseline && dc_table_index > 1) { 435 | return Err(Error::Format(format!( 436 | "invalid dc table index {}", 437 | dc_table_index 438 | ))); 439 | } 440 | if ac_table_index > 3 || (frame.is_baseline && ac_table_index > 1) { 441 | return Err(Error::Format(format!( 442 | "invalid ac table index {}", 443 | ac_table_index 444 | ))); 445 | } 446 | 447 | component_indices.push(component_index); 448 | dc_table_indices.push(dc_table_index as usize); 449 | ac_table_indices.push(ac_table_index as usize); 450 | } 451 | 452 | let blocks_per_mcu = component_indices 453 | .iter() 454 | .map(|&i| { 455 | frame.components[i].horizontal_sampling_factor as u32 456 | * frame.components[i].vertical_sampling_factor as u32 457 | }) 458 | .fold(0, ::std::ops::Add::add); 459 | 460 | if component_count > 1 && blocks_per_mcu > 10 { 461 | return Err(Error::Format( 462 | "scan with more than one component and more than 10 blocks per MCU".to_owned(), 463 | )); 464 | } 465 | 466 | let spectral_selection_start = read_u8(reader)?; 467 | let spectral_selection_end = read_u8(reader)?; 468 | 469 | let byte = read_u8(reader)?; 470 | let successive_approximation_high = byte >> 4; 471 | let successive_approximation_low = byte & 0x0f; 472 | 473 | if frame.coding_process == CodingProcess::DctProgressive { 474 | if spectral_selection_end > 63 475 | || spectral_selection_start > spectral_selection_end 476 | || (spectral_selection_start == 0 && spectral_selection_end != 0) 477 | { 478 | return Err(Error::Format(format!( 479 | "invalid spectral selection parameters: ss={}, se={}", 480 | spectral_selection_start, spectral_selection_end 481 | ))); 482 | } 483 | if spectral_selection_start != 0 && component_count != 1 { 484 | return Err(Error::Format( 485 | "spectral selection scan with AC coefficients can't have more than one component" 486 | .to_owned(), 487 | )); 488 | } 489 | 490 | if successive_approximation_high > 13 || successive_approximation_low > 13 { 491 | return Err(Error::Format(format!( 492 | "invalid successive approximation parameters: ah={}, al={}", 493 | successive_approximation_high, successive_approximation_low 494 | ))); 495 | } 496 | 497 | // Section G.1.1.1.2 498 | // "Each scan which follows the first scan for a given band progressively improves 499 | // the precision of the coefficients by one bit, until full precision is reached." 500 | if successive_approximation_high != 0 501 | && successive_approximation_high != successive_approximation_low + 1 502 | { 503 | return Err(Error::Format( 504 | "successive approximation scan with more than one bit of improvement".to_owned(), 505 | )); 506 | } 507 | } else { 508 | if spectral_selection_start != 0 || spectral_selection_end != 63 { 509 | return Err(Error::Format( 510 | "spectral selection is not allowed in non-progressive scan".to_owned(), 511 | )); 512 | } 513 | if successive_approximation_high != 0 || successive_approximation_low != 0 { 514 | return Err(Error::Format( 515 | "successive approximation is not allowed in non-progressive scan".to_owned(), 516 | )); 517 | } 518 | } 519 | 520 | Ok(ScanInfo { 521 | component_indices: component_indices, 522 | dc_table_indices: dc_table_indices, 523 | ac_table_indices: ac_table_indices, 524 | spectral_selection: Range { 525 | start: spectral_selection_start, 526 | end: spectral_selection_end + 1, 527 | }, 528 | successive_approximation_high: successive_approximation_high, 529 | successive_approximation_low: successive_approximation_low, 530 | }) 531 | } 532 | 533 | // Section B.2.4.1 534 | pub fn parse_dqt(reader: &mut R) -> Result<[Option<[u16; 64]>; 4]> { 535 | let mut length = read_length(reader, DQT)?; 536 | let mut tables = [None; 4]; 537 | 538 | // Each DQT segment may contain multiple quantization tables. 539 | while length > 0 { 540 | let byte = read_u8(reader)?; 541 | let precision = (byte >> 4) as usize; 542 | let index = (byte & 0x0f) as usize; 543 | 544 | // The combination of 8-bit sample precision and 16-bit quantization tables is explicitly 545 | // disallowed by the JPEG spec: 546 | // "An 8-bit DCT-based process shall not use a 16-bit precision quantization table." 547 | // "Pq: Quantization table element precision – Specifies the precision of the Qk 548 | // values. Value 0 indicates 8-bit Qk values; value 1 indicates 16-bit Qk values. Pq 549 | // shall be zero for 8 bit sample precision P (see B.2.2)." 550 | // libjpeg allows this behavior though, and there are images in the wild using it. So to 551 | // match libjpeg's behavior we are deviating from the JPEG spec here. 552 | if precision > 1 { 553 | return Err(Error::Format(format!( 554 | "invalid precision {} in DQT", 555 | precision 556 | ))); 557 | } 558 | if index > 3 { 559 | return Err(Error::Format(format!( 560 | "invalid destination identifier {} in DQT", 561 | index 562 | ))); 563 | } 564 | if length < 65 + 64 * precision { 565 | return Err(Error::Format("invalid length in DQT".to_owned())); 566 | } 567 | 568 | let mut table = [0u16; 64]; 569 | 570 | for item in table.iter_mut() { 571 | *item = match precision { 572 | 0 => u16::from(read_u8(reader)?), 573 | 1 => read_u16_from_be(reader)?, 574 | _ => unreachable!(), 575 | }; 576 | } 577 | 578 | if table.iter().any(|&val| val == 0) { 579 | return Err(Error::Format( 580 | "quantization table contains element with a zero value".to_owned(), 581 | )); 582 | } 583 | 584 | tables[index] = Some(table); 585 | length -= 65 + 64 * precision; 586 | } 587 | 588 | Ok(tables) 589 | } 590 | 591 | // Section B.2.4.2 592 | pub fn parse_dht( 593 | reader: &mut R, 594 | is_baseline: Option, 595 | ) -> Result<(Vec>, Vec>)> { 596 | let mut length = read_length(reader, DHT)?; 597 | let mut dc_tables = vec![None, None, None, None]; 598 | let mut ac_tables = vec![None, None, None, None]; 599 | 600 | // Each DHT segment may contain multiple huffman tables. 601 | while length > 17 { 602 | let byte = read_u8(reader)?; 603 | let class = byte >> 4; 604 | let index = (byte & 0x0f) as usize; 605 | 606 | if class != 0 && class != 1 { 607 | return Err(Error::Format(format!("invalid class {} in DHT", class))); 608 | } 609 | if is_baseline == Some(true) && index > 1 { 610 | return Err(Error::Format( 611 | "a maximum of two huffman tables per class are allowed in baseline".to_owned(), 612 | )); 613 | } 614 | if index > 3 { 615 | return Err(Error::Format(format!( 616 | "invalid destination identifier {} in DHT", 617 | index 618 | ))); 619 | } 620 | 621 | let mut counts = [0u8; 16]; 622 | reader.read_exact(&mut counts)?; 623 | 624 | let size = counts 625 | .iter() 626 | .map(|&val| val as usize) 627 | .fold(0, ::std::ops::Add::add); 628 | 629 | if size == 0 { 630 | return Err(Error::Format( 631 | "encountered table with zero length in DHT".to_owned(), 632 | )); 633 | } else if size > 256 { 634 | return Err(Error::Format( 635 | "encountered table with excessive length in DHT".to_owned(), 636 | )); 637 | } else if size > length - 17 { 638 | return Err(Error::Format("invalid length in DHT".to_owned())); 639 | } 640 | 641 | let mut values = vec![0u8; size]; 642 | reader.read_exact(&mut values)?; 643 | 644 | match class { 645 | 0 => { 646 | dc_tables[index] = Some(HuffmanTable::new(&counts, &values, HuffmanTableClass::DC)?) 647 | } 648 | 1 => { 649 | ac_tables[index] = Some(HuffmanTable::new(&counts, &values, HuffmanTableClass::AC)?) 650 | } 651 | _ => unreachable!(), 652 | } 653 | 654 | length -= 17 + size; 655 | } 656 | 657 | if length != 0 { 658 | return Err(Error::Format("invalid length in DHT".to_owned())); 659 | } 660 | 661 | Ok((dc_tables, ac_tables)) 662 | } 663 | 664 | // Section B.2.4.4 665 | pub fn parse_dri(reader: &mut R) -> Result { 666 | let length = read_length(reader, DRI)?; 667 | 668 | if length != 2 { 669 | return Err(Error::Format("DRI with invalid length".to_owned())); 670 | } 671 | 672 | Ok(read_u16_from_be(reader)?) 673 | } 674 | 675 | // Section B.2.4.5 676 | pub fn parse_com(reader: &mut R) -> Result> { 677 | let length = read_length(reader, COM)?; 678 | let mut buffer = vec![0u8; length]; 679 | 680 | reader.read_exact(&mut buffer)?; 681 | 682 | Ok(buffer) 683 | } 684 | 685 | // Section B.2.4.6 686 | pub fn parse_app(reader: &mut R, marker: Marker) -> Result> { 687 | let length = read_length(reader, marker)?; 688 | let mut bytes_read = 0; 689 | let mut result = None; 690 | 691 | match marker { 692 | APP(0) => { 693 | if length >= 5 { 694 | let mut buffer = [0u8; 5]; 695 | reader.read_exact(&mut buffer)?; 696 | bytes_read = buffer.len(); 697 | 698 | // http://www.w3.org/Graphics/JPEG/jfif3.pdf 699 | if &buffer[0..5] == &[b'J', b'F', b'I', b'F', b'\0'] { 700 | result = Some(AppData::Jfif); 701 | // https://sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#AVI1 702 | } else if &buffer[0..5] == &[b'A', b'V', b'I', b'1', b'\0'] { 703 | result = Some(AppData::Avi1); 704 | } 705 | } 706 | } 707 | APP(2) => { 708 | if length > 14 { 709 | let mut buffer = [0u8; 14]; 710 | reader.read_exact(&mut buffer)?; 711 | bytes_read = buffer.len(); 712 | 713 | // http://www.color.org/ICC_Minor_Revision_for_Web.pdf 714 | // B.4 Embedding ICC profiles in JFIF files 715 | if &buffer[0..12] == b"ICC_PROFILE\0" { 716 | let mut data = vec![0; length - bytes_read]; 717 | reader.read_exact(&mut data)?; 718 | bytes_read += data.len(); 719 | result = Some(AppData::Icc(IccChunk { 720 | seq_no: buffer[12], 721 | num_markers: buffer[13], 722 | data, 723 | })); 724 | } 725 | } 726 | } 727 | APP(14) => { 728 | if length >= 12 { 729 | let mut buffer = [0u8; 12]; 730 | reader.read_exact(&mut buffer)?; 731 | bytes_read = buffer.len(); 732 | 733 | // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe 734 | if &buffer[0..6] == &[b'A', b'd', b'o', b'b', b'e', b'\0'] { 735 | let color_transform = match buffer[11] { 736 | 0 => AdobeColorTransform::Unknown, 737 | 1 => AdobeColorTransform::YCbCr, 738 | 2 => AdobeColorTransform::YCCK, 739 | _ => { 740 | return Err(Error::Format( 741 | "invalid color transform in adobe app segment".to_owned(), 742 | )) 743 | } 744 | }; 745 | 746 | result = Some(AppData::Adobe(color_transform)); 747 | } 748 | } 749 | } 750 | _ => {} 751 | } 752 | 753 | skip_bytes(reader, length - bytes_read)?; 754 | Ok(result) 755 | } 756 | -------------------------------------------------------------------------------- /src/jpeg/encoder.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2014 PistonDevelopers 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | //! Modified Jpeg encoder 24 | //! 25 | //! Adapted from https://github.com/image-rs/image/blob/master/src/codecs/jpeg/encoder.rs 26 | 27 | #![allow(clippy::too_many_arguments)] 28 | 29 | use crate::jpeg::{transform, AppMarkerConfig}; 30 | use image::codecs::jpeg::PixelDensity; 31 | use image::codecs::jpeg::PixelDensityUnit; 32 | use image::error::{ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind}; 33 | use image::{ 34 | Bgr, Bgra, ColorType, EncodableLayout, GenericImageView, ImageBuffer, ImageError, ImageFormat, 35 | ImageResult, Luma, LumaA, Pixel, Rgb, Rgba, 36 | }; 37 | use num_iter::range_step; 38 | use std::convert::TryFrom; 39 | use std::io::{self, Read, Write}; 40 | 41 | // Markers 42 | // Baseline DCT 43 | static SOF0: u8 = 0xC0; 44 | // Huffman Tables 45 | static DHT: u8 = 0xC4; 46 | // Start of Image (standalone) 47 | static SOI: u8 = 0xD8; 48 | // End of image (standalone) 49 | static EOI: u8 = 0xD9; 50 | // Start of Scan 51 | static SOS: u8 = 0xDA; 52 | // Quantization Tables 53 | static DQT: u8 = 0xDB; 54 | // Application segments start and end 55 | static APP0: u8 = 0xE0; 56 | 57 | // section K.1 58 | // table K.1 59 | #[rustfmt::skip] 60 | static STD_LUMA_QTABLE: [u8; 64] = [ 61 | 16, 11, 10, 16, 24, 40, 51, 61, 62 | 12, 12, 14, 19, 26, 58, 60, 55, 63 | 14, 13, 16, 24, 40, 57, 69, 56, 64 | 14, 17, 22, 29, 51, 87, 80, 62, 65 | 18, 22, 37, 56, 68, 109, 103, 77, 66 | 24, 35, 55, 64, 81, 104, 113, 92, 67 | 49, 64, 78, 87, 103, 121, 120, 101, 68 | 72, 92, 95, 98, 112, 100, 103, 99, 69 | ]; 70 | 71 | // table K.2 72 | #[rustfmt::skip] 73 | static STD_CHROMA_QTABLE: [u8; 64] = [ 74 | 17, 18, 24, 47, 99, 99, 99, 99, 75 | 18, 21, 26, 66, 99, 99, 99, 99, 76 | 24, 26, 56, 99, 99, 99, 99, 99, 77 | 47, 66, 99, 99, 99, 99, 99, 99, 78 | 99, 99, 99, 99, 99, 99, 99, 99, 79 | 99, 99, 99, 99, 99, 99, 99, 99, 80 | 99, 99, 99, 99, 99, 99, 99, 99, 81 | 99, 99, 99, 99, 99, 99, 99, 99, 82 | ]; 83 | 84 | // section K.3 85 | // Code lengths and values for table K.3 86 | static STD_LUMA_DC_CODE_LENGTHS: [u8; 16] = [ 87 | 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | ]; 89 | 90 | static STD_LUMA_DC_VALUES: [u8; 12] = [ 91 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 92 | ]; 93 | 94 | // Code lengths and values for table K.4 95 | static STD_CHROMA_DC_CODE_LENGTHS: [u8; 16] = [ 96 | 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 97 | ]; 98 | 99 | static STD_CHROMA_DC_VALUES: [u8; 12] = [ 100 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 101 | ]; 102 | 103 | // Code lengths and values for table k.5 104 | static STD_LUMA_AC_CODE_LENGTHS: [u8; 16] = [ 105 | 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 106 | ]; 107 | 108 | static STD_LUMA_AC_VALUES: [u8; 162] = [ 109 | 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 110 | 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 111 | 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 112 | 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 113 | 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 114 | 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 115 | 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 116 | 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 117 | 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 118 | 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 119 | 0xF9, 0xFA, 120 | ]; 121 | 122 | // Code lengths and values for table k.6 123 | static STD_CHROMA_AC_CODE_LENGTHS: [u8; 16] = [ 124 | 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 125 | ]; 126 | static STD_CHROMA_AC_VALUES: [u8; 162] = [ 127 | 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 128 | 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 129 | 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 130 | 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 131 | 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 132 | 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 133 | 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 134 | 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 135 | 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 136 | 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 137 | 0xF9, 0xFA, 138 | ]; 139 | 140 | static DCCLASS: u8 = 0; 141 | static ACCLASS: u8 = 1; 142 | 143 | static LUMADESTINATION: u8 = 0; 144 | static CHROMADESTINATION: u8 = 1; 145 | 146 | static LUMAID: u8 = 1; 147 | static CHROMABLUEID: u8 = 2; 148 | static CHROMAREDID: u8 = 3; 149 | 150 | /// The permutation of dct coefficients. 151 | #[rustfmt::skip] 152 | static UNZIGZAG: [u8; 64] = [ 153 | 0, 1, 8, 16, 9, 2, 3, 10, 154 | 17, 24, 32, 25, 18, 11, 4, 5, 155 | 12, 19, 26, 33, 40, 48, 41, 34, 156 | 27, 20, 13, 6, 7, 14, 21, 28, 157 | 35, 42, 49, 56, 57, 50, 43, 36, 158 | 29, 22, 15, 23, 30, 37, 44, 51, 159 | 58, 59, 52, 45, 38, 31, 39, 46, 160 | 53, 60, 61, 54, 47, 55, 62, 63, 161 | ]; 162 | 163 | pub struct AppMarkerJpegEncoder<'a, W: 'a, R> { 164 | /// data to put into app data 165 | reader: R, 166 | config: AppMarkerConfig, 167 | writer: BitWriter<'a, W>, 168 | 169 | components: Vec, 170 | tables: Vec<[u8; 64]>, 171 | 172 | luma_dctable: Box<[(u8, u16); 256]>, 173 | luma_actable: Box<[(u8, u16); 256]>, 174 | chroma_dctable: Box<[(u8, u16); 256]>, 175 | chroma_actable: Box<[(u8, u16); 256]>, 176 | 177 | pixel_density: PixelDensity, 178 | } 179 | 180 | impl<'a, W: Write, R: Read> AppMarkerJpegEncoder<'a, W, R> { 181 | /// Create a new encoder that writes its output to ```w``` 182 | pub fn new(w: &mut W, r: R, config: AppMarkerConfig) -> AppMarkerJpegEncoder { 183 | AppMarkerJpegEncoder::new_with_quality(w, r, config, 75) 184 | } 185 | 186 | /// Create a new encoder that writes its output to ```w```, and has 187 | /// the quality parameter ```quality``` with a value in the range 1-100 188 | /// where 1 is the worst and 100 is the best. 189 | pub fn new_with_quality( 190 | w: &mut W, 191 | reader: R, 192 | config: AppMarkerConfig, 193 | quality: u8, 194 | ) -> AppMarkerJpegEncoder { 195 | let ld = Box::new(build_huff_lut( 196 | &STD_LUMA_DC_CODE_LENGTHS, 197 | &STD_LUMA_DC_VALUES, 198 | )); 199 | let la = Box::new(build_huff_lut( 200 | &STD_LUMA_AC_CODE_LENGTHS, 201 | &STD_LUMA_AC_VALUES, 202 | )); 203 | 204 | let cd = Box::new(build_huff_lut( 205 | &STD_CHROMA_DC_CODE_LENGTHS, 206 | &STD_CHROMA_DC_VALUES, 207 | )); 208 | let ca = Box::new(build_huff_lut( 209 | &STD_CHROMA_AC_CODE_LENGTHS, 210 | &STD_CHROMA_AC_VALUES, 211 | )); 212 | 213 | let components = vec![ 214 | Component { 215 | id: LUMAID, 216 | h: 1, 217 | v: 1, 218 | tq: LUMADESTINATION, 219 | dc_table: LUMADESTINATION, 220 | ac_table: LUMADESTINATION, 221 | _dc_pred: 0, 222 | }, 223 | Component { 224 | id: CHROMABLUEID, 225 | h: 1, 226 | v: 1, 227 | tq: CHROMADESTINATION, 228 | dc_table: CHROMADESTINATION, 229 | ac_table: CHROMADESTINATION, 230 | _dc_pred: 0, 231 | }, 232 | Component { 233 | id: CHROMAREDID, 234 | h: 1, 235 | v: 1, 236 | tq: CHROMADESTINATION, 237 | dc_table: CHROMADESTINATION, 238 | ac_table: CHROMADESTINATION, 239 | _dc_pred: 0, 240 | }, 241 | ]; 242 | 243 | // Derive our quantization table scaling value using the libjpeg algorithm 244 | let scale = u32::from(clamp(quality, 1, 100)); 245 | let scale = if scale < 50 { 246 | 5000 / scale 247 | } else { 248 | 200 - scale * 2 249 | }; 250 | 251 | let mut tables = vec![STD_LUMA_QTABLE, STD_CHROMA_QTABLE]; 252 | tables.iter_mut().for_each(|t| { 253 | t.iter_mut().for_each(|v| { 254 | *v = clamp( 255 | (u32::from(*v) * scale + 50) / 100, 256 | 1, 257 | u32::from(u8::max_value()), 258 | ) as u8; 259 | }) 260 | }); 261 | 262 | AppMarkerJpegEncoder { 263 | writer: BitWriter::new(w), 264 | reader, 265 | config, 266 | components, 267 | tables, 268 | 269 | luma_dctable: ld, 270 | luma_actable: la, 271 | chroma_dctable: cd, 272 | chroma_actable: ca, 273 | 274 | pixel_density: PixelDensity::default(), 275 | } 276 | } 277 | 278 | /// Set the pixel density of the images the encoder will encode. 279 | /// If this method is not called, then a default pixel aspect ratio of 1x1 will be applied, 280 | /// and no DPI information will be stored in the image. 281 | pub fn set_pixel_density(&mut self, pixel_density: PixelDensity) { 282 | self.pixel_density = pixel_density; 283 | } 284 | 285 | /// Encodes the image stored in the raw byte buffer ```image``` 286 | /// that has dimensions ```width``` and ```height``` 287 | /// and ```ColorType``` ```c``` 288 | /// 289 | /// The Image in encoded with subsampling ratio 4:2:2 290 | pub fn encode( 291 | &mut self, 292 | image: &[u8], 293 | width: u32, 294 | height: u32, 295 | color_type: ColorType, 296 | ) -> ImageResult<()> { 297 | match color_type { 298 | ColorType::L8 => { 299 | let image: ImageBuffer, _> = 300 | ImageBuffer::from_raw(width, height, image).unwrap(); 301 | self.encode_image(&image) 302 | } 303 | ColorType::La8 => { 304 | let image: ImageBuffer, _> = 305 | ImageBuffer::from_raw(width, height, image).unwrap(); 306 | self.encode_image(&image) 307 | } 308 | ColorType::Rgb8 => { 309 | let image: ImageBuffer, _> = 310 | ImageBuffer::from_raw(width, height, image).unwrap(); 311 | self.encode_image(&image) 312 | } 313 | ColorType::Rgba8 => { 314 | let image: ImageBuffer, _> = 315 | ImageBuffer::from_raw(width, height, image).unwrap(); 316 | self.encode_image(&image) 317 | } 318 | ColorType::Bgr8 => { 319 | let image: ImageBuffer, _> = 320 | ImageBuffer::from_raw(width, height, image).unwrap(); 321 | self.encode_image(&image) 322 | } 323 | ColorType::Bgra8 => { 324 | let image: ImageBuffer, _> = 325 | ImageBuffer::from_raw(width, height, image).unwrap(); 326 | self.encode_image(&image) 327 | } 328 | _ => Err(ImageError::Unsupported( 329 | UnsupportedError::from_format_and_kind( 330 | ImageFormat::Jpeg.into(), 331 | UnsupportedErrorKind::Color(color_type.into()), 332 | ), 333 | )), 334 | } 335 | } 336 | 337 | /// Encodes the given image. 338 | /// 339 | /// As a special feature this does not require the whole image to be present in memory at the 340 | /// same time such that it may be computed on the fly, which is why this method exists on this 341 | /// encoder but not on others. Instead the encoder will iterate over 8-by-8 blocks of pixels at 342 | /// a time, inspecting each pixel exactly once. You can rely on this behaviour when calling 343 | /// this method. 344 | /// 345 | /// The Image in encoded with subsampling ratio 4:2:2 346 | pub fn encode_image(&mut self, image: &I) -> ImageResult<()> { 347 | let n = I::Pixel::CHANNEL_COUNT; 348 | let num_components = if n == 1 || n == 2 { 1 } else { 3 }; 349 | 350 | self.writer.write_marker(SOI)?; 351 | 352 | let mut buf = Vec::new(); 353 | 354 | build_jfif_header(&mut buf, self.pixel_density); 355 | self.writer.write_segment(APP0, &buf)?; 356 | 357 | // memex adaption 358 | // this will write the data from the reader in consecutive segments 359 | let data_len = self.config.data_len(); 360 | let segment_len = self.config.segment_data_len(); 361 | loop { 362 | let mut buf = Vec::with_capacity(segment_len); 363 | buf.extend_from_slice(self.config.vendor.as_bytes()); 364 | let read = self 365 | .reader 366 | .by_ref() 367 | .take(data_len as u64) 368 | .read_to_end(&mut buf)?; 369 | self.writer.write_segment(self.config.marker, &buf)?; 370 | if read == 0 { 371 | break; 372 | } 373 | } 374 | 375 | build_frame_header( 376 | &mut buf, 377 | 8, 378 | // TODO: not idiomatic yet. Should be an EncodingError and mention jpg. Further it 379 | // should check dimensions prior to writing. 380 | u16::try_from(image.width()).map_err(|_| { 381 | ImageError::Parameter(ParameterError::from_kind( 382 | ParameterErrorKind::DimensionMismatch, 383 | )) 384 | })?, 385 | u16::try_from(image.height()).map_err(|_| { 386 | ImageError::Parameter(ParameterError::from_kind( 387 | ParameterErrorKind::DimensionMismatch, 388 | )) 389 | })?, 390 | &self.components[..num_components], 391 | ); 392 | self.writer.write_segment(SOF0, &buf)?; 393 | 394 | assert_eq!(self.tables.len(), 2); 395 | let numtables = if num_components == 1 { 1 } else { 2 }; 396 | 397 | for (i, table) in self.tables[..numtables].iter().enumerate() { 398 | build_quantization_segment(&mut buf, 8, i as u8, table); 399 | self.writer.write_segment(DQT, &buf)?; 400 | } 401 | 402 | build_huffman_segment( 403 | &mut buf, 404 | DCCLASS, 405 | LUMADESTINATION, 406 | &STD_LUMA_DC_CODE_LENGTHS, 407 | &STD_LUMA_DC_VALUES, 408 | ); 409 | self.writer.write_segment(DHT, &buf)?; 410 | 411 | build_huffman_segment( 412 | &mut buf, 413 | ACCLASS, 414 | LUMADESTINATION, 415 | &STD_LUMA_AC_CODE_LENGTHS, 416 | &STD_LUMA_AC_VALUES, 417 | ); 418 | self.writer.write_segment(DHT, &buf)?; 419 | 420 | if num_components == 3 { 421 | build_huffman_segment( 422 | &mut buf, 423 | DCCLASS, 424 | CHROMADESTINATION, 425 | &STD_CHROMA_DC_CODE_LENGTHS, 426 | &STD_CHROMA_DC_VALUES, 427 | ); 428 | self.writer.write_segment(DHT, &buf)?; 429 | 430 | build_huffman_segment( 431 | &mut buf, 432 | ACCLASS, 433 | CHROMADESTINATION, 434 | &STD_CHROMA_AC_CODE_LENGTHS, 435 | &STD_CHROMA_AC_VALUES, 436 | ); 437 | self.writer.write_segment(DHT, &buf)?; 438 | } 439 | 440 | build_scan_header(&mut buf, &self.components[..num_components]); 441 | self.writer.write_segment(SOS, &buf)?; 442 | 443 | if I::Pixel::COLOR_TYPE.has_color() { 444 | self.encode_rgb(image) 445 | } else { 446 | self.encode_gray(image) 447 | }?; 448 | 449 | self.writer.pad_byte()?; 450 | self.writer.write_marker(EOI)?; 451 | Ok(()) 452 | } 453 | 454 | fn encode_gray(&mut self, image: &I) -> io::Result<()> { 455 | let mut yblock = [0u8; 64]; 456 | let mut y_dcprev = 0; 457 | let mut dct_yblock = [0i32; 64]; 458 | 459 | for y in range_step(0, image.height(), 8) { 460 | for x in range_step(0, image.width(), 8) { 461 | copy_blocks_gray(image, x, y, &mut yblock); 462 | 463 | // Level shift and fdct 464 | // Coeffs are scaled by 8 465 | transform::fdct(&yblock, &mut dct_yblock); 466 | 467 | // Quantization 468 | for (i, dct) in dct_yblock.iter_mut().enumerate() { 469 | *dct = ((*dct / 8) as f32 / f32::from(self.tables[0][i])).round() as i32; 470 | } 471 | 472 | let la = &*self.luma_actable; 473 | let ld = &*self.luma_dctable; 474 | 475 | y_dcprev = self.writer.write_block(&dct_yblock, y_dcprev, ld, la)?; 476 | } 477 | } 478 | 479 | Ok(()) 480 | } 481 | 482 | fn encode_rgb(&mut self, image: &I) -> io::Result<()> { 483 | let mut y_dcprev = 0; 484 | let mut cb_dcprev = 0; 485 | let mut cr_dcprev = 0; 486 | 487 | let mut dct_yblock = [0i32; 64]; 488 | let mut dct_cb_block = [0i32; 64]; 489 | let mut dct_cr_block = [0i32; 64]; 490 | 491 | let mut yblock = [0u8; 64]; 492 | let mut cb_block = [0u8; 64]; 493 | let mut cr_block = [0u8; 64]; 494 | 495 | for y in range_step(0, image.height(), 8) { 496 | for x in range_step(0, image.width(), 8) { 497 | // RGB -> YCbCr 498 | copy_blocks_ycbcr(image, x, y, &mut yblock, &mut cb_block, &mut cr_block); 499 | 500 | // Level shift and fdct 501 | // Coeffs are scaled by 8 502 | transform::fdct(&yblock, &mut dct_yblock); 503 | transform::fdct(&cb_block, &mut dct_cb_block); 504 | transform::fdct(&cr_block, &mut dct_cr_block); 505 | 506 | // Quantization 507 | for i in 0usize..64 { 508 | dct_yblock[i] = 509 | ((dct_yblock[i] / 8) as f32 / f32::from(self.tables[0][i])).round() as i32; 510 | dct_cb_block[i] = ((dct_cb_block[i] / 8) as f32 / f32::from(self.tables[1][i])) 511 | .round() as i32; 512 | dct_cr_block[i] = ((dct_cr_block[i] / 8) as f32 / f32::from(self.tables[1][i])) 513 | .round() as i32; 514 | } 515 | 516 | let la = &*self.luma_actable; 517 | let ld = &*self.luma_dctable; 518 | let cd = &*self.chroma_dctable; 519 | let ca = &*self.chroma_actable; 520 | 521 | y_dcprev = self.writer.write_block(&dct_yblock, y_dcprev, ld, la)?; 522 | cb_dcprev = self.writer.write_block(&dct_cb_block, cb_dcprev, cd, ca)?; 523 | cr_dcprev = self.writer.write_block(&dct_cr_block, cr_dcprev, cd, ca)?; 524 | } 525 | } 526 | 527 | Ok(()) 528 | } 529 | } 530 | 531 | /// A representation of a JPEG component 532 | #[derive(Copy, Clone)] 533 | struct Component { 534 | /// The Component's identifier 535 | id: u8, 536 | 537 | /// Horizontal sampling factor 538 | h: u8, 539 | 540 | /// Vertical sampling factor 541 | v: u8, 542 | 543 | /// The quantization table selector 544 | tq: u8, 545 | 546 | /// Index to the Huffman DC Table 547 | dc_table: u8, 548 | 549 | /// Index to the AC Huffman Table 550 | ac_table: u8, 551 | 552 | /// The dc prediction of the component 553 | _dc_pred: i32, 554 | } 555 | 556 | pub(crate) struct BitWriter<'a, W: 'a> { 557 | w: &'a mut W, 558 | accumulator: u32, 559 | nbits: u8, 560 | } 561 | 562 | impl<'a, W: Write + 'a> BitWriter<'a, W> { 563 | fn new(w: &'a mut W) -> Self { 564 | BitWriter { 565 | w, 566 | accumulator: 0, 567 | nbits: 0, 568 | } 569 | } 570 | 571 | fn write_bits(&mut self, bits: u16, size: u8) -> io::Result<()> { 572 | if size == 0 { 573 | return Ok(()); 574 | } 575 | 576 | self.nbits += size; 577 | self.accumulator |= u32::from(bits) << (32 - self.nbits) as usize; 578 | 579 | while self.nbits >= 8 { 580 | let byte = self.accumulator >> 24; 581 | self.w.write_all(&[byte as u8])?; 582 | 583 | if byte == 0xFF { 584 | self.w.write_all(&[0x00])?; 585 | } 586 | 587 | self.nbits -= 8; 588 | self.accumulator <<= 8; 589 | } 590 | 591 | Ok(()) 592 | } 593 | 594 | fn pad_byte(&mut self) -> io::Result<()> { 595 | self.write_bits(0x7F, 7) 596 | } 597 | 598 | fn huffman_encode(&mut self, val: u8, table: &[(u8, u16); 256]) -> io::Result<()> { 599 | let (size, code) = table[val as usize]; 600 | 601 | if size > 16 { 602 | panic!("bad huffman value"); 603 | } 604 | 605 | self.write_bits(code, size) 606 | } 607 | 608 | fn write_block( 609 | &mut self, 610 | block: &[i32; 64], 611 | prevdc: i32, 612 | dctable: &[(u8, u16); 256], 613 | actable: &[(u8, u16); 256], 614 | ) -> io::Result { 615 | // Differential DC encoding 616 | let dcval = block[0]; 617 | let diff = dcval - prevdc; 618 | let (size, value) = encode_coefficient(diff); 619 | 620 | self.huffman_encode(size, dctable)?; 621 | self.write_bits(value, size)?; 622 | 623 | // Figure F.2 624 | let mut zero_run = 0; 625 | 626 | for &k in &UNZIGZAG[1..] { 627 | if block[k as usize] == 0 { 628 | zero_run += 1; 629 | } else { 630 | while zero_run > 15 { 631 | self.huffman_encode(0xF0, actable)?; 632 | zero_run -= 16; 633 | } 634 | 635 | let (size, value) = encode_coefficient(block[k as usize]); 636 | let symbol = (zero_run << 4) | size; 637 | 638 | self.huffman_encode(symbol, actable)?; 639 | self.write_bits(value, size)?; 640 | 641 | zero_run = 0; 642 | } 643 | } 644 | 645 | if block[UNZIGZAG[63] as usize] == 0 { 646 | self.huffman_encode(0x00, actable)?; 647 | } 648 | 649 | Ok(dcval) 650 | } 651 | 652 | fn write_marker(&mut self, marker: u8) -> io::Result<()> { 653 | self.w.write_all(&[0xFF, marker]) 654 | } 655 | 656 | fn write_segment(&mut self, marker: u8, data: &[u8]) -> io::Result<()> { 657 | self.w.write_all(&[0xFF, marker])?; 658 | self.w.write_all(&(data.len() as u16 + 2).to_be_bytes())?; 659 | self.w.write_all(data) 660 | } 661 | } 662 | 663 | #[inline] 664 | pub(crate) fn clamp(a: N, min: N, max: N) -> N 665 | where 666 | N: PartialOrd, 667 | { 668 | if a < min { 669 | min 670 | } else if a > max { 671 | max 672 | } else { 673 | a 674 | } 675 | } 676 | 677 | /// Given an array containing the number of codes of each code length, 678 | /// this function generates the huffman codes lengths and their respective 679 | /// code lengths as specified by the JPEG spec. 680 | fn derive_codes_and_sizes(bits: &[u8; 16]) -> ([u8; 256], [u16; 256]) { 681 | let mut huffsize = [0u8; 256]; 682 | let mut huffcode = [0u16; 256]; 683 | 684 | let mut k = 0; 685 | 686 | // Annex C.2 687 | // Figure C.1 688 | // Generate table of individual code lengths 689 | for i in 0u8..16 { 690 | let mut j = 0; 691 | 692 | while j < bits[usize::from(i)] { 693 | huffsize[k] = i + 1; 694 | k += 1; 695 | j += 1; 696 | } 697 | } 698 | 699 | huffsize[k] = 0; 700 | 701 | // Annex C.2 702 | // Figure C.2 703 | // Generate table of huffman codes 704 | k = 0; 705 | let mut code = 0u16; 706 | let mut size = huffsize[0]; 707 | 708 | while huffsize[k] != 0 { 709 | huffcode[k] = code; 710 | code += 1; 711 | k += 1; 712 | 713 | if huffsize[k] == size { 714 | continue; 715 | } 716 | 717 | // FIXME there is something wrong with this code 718 | let diff = huffsize[k].wrapping_sub(size); 719 | code = if diff < 16 { code << diff as usize } else { 0 }; 720 | 721 | size = size.wrapping_add(diff); 722 | } 723 | 724 | (huffsize, huffcode) 725 | } 726 | 727 | pub(crate) fn build_huff_lut(bits: &[u8; 16], huffval: &[u8]) -> [(u8, u16); 256] { 728 | let mut lut = [(17u8, 0u16); 256]; 729 | let (huffsize, huffcode) = derive_codes_and_sizes(bits); 730 | 731 | for (i, &v) in huffval.iter().enumerate() { 732 | lut[v as usize] = (huffsize[i], huffcode[i]); 733 | } 734 | 735 | lut 736 | } 737 | 738 | fn build_jfif_header(m: &mut Vec, density: PixelDensity) { 739 | m.clear(); 740 | m.extend_from_slice(b"JFIF"); 741 | m.extend_from_slice(&[ 742 | 0, 743 | 0x01, 744 | 0x02, 745 | match density.unit { 746 | PixelDensityUnit::PixelAspectRatio => 0x00, 747 | PixelDensityUnit::Inches => 0x01, 748 | PixelDensityUnit::Centimeters => 0x02, 749 | }, 750 | ]); 751 | m.extend_from_slice(&density.density.0.to_be_bytes()); 752 | m.extend_from_slice(&density.density.1.to_be_bytes()); 753 | m.extend_from_slice(&[0, 0]); 754 | } 755 | 756 | fn build_frame_header( 757 | m: &mut Vec, 758 | precision: u8, 759 | width: u16, 760 | height: u16, 761 | components: &[Component], 762 | ) { 763 | m.clear(); 764 | 765 | m.push(precision); 766 | m.extend_from_slice(&height.to_be_bytes()); 767 | m.extend_from_slice(&width.to_be_bytes()); 768 | m.push(components.len() as u8); 769 | 770 | for &comp in components.iter() { 771 | let hv = (comp.h << 4) | comp.v; 772 | m.extend_from_slice(&[comp.id, hv, comp.tq]); 773 | } 774 | } 775 | 776 | fn build_scan_header(m: &mut Vec, components: &[Component]) { 777 | m.clear(); 778 | 779 | m.push(components.len() as u8); 780 | 781 | for &comp in components.iter() { 782 | let tables = (comp.dc_table << 4) | comp.ac_table; 783 | m.extend_from_slice(&[comp.id, tables]); 784 | } 785 | 786 | // spectral start and end, approx. high and low 787 | m.extend_from_slice(&[0, 63, 0]); 788 | } 789 | 790 | fn build_huffman_segment( 791 | m: &mut Vec, 792 | class: u8, 793 | destination: u8, 794 | numcodes: &[u8; 16], 795 | values: &[u8], 796 | ) { 797 | m.clear(); 798 | 799 | let tcth = (class << 4) | destination; 800 | m.push(tcth); 801 | 802 | m.extend_from_slice(numcodes); 803 | 804 | let sum: usize = numcodes.iter().map(|&x| x as usize).sum(); 805 | 806 | assert_eq!(sum, values.len()); 807 | 808 | m.extend_from_slice(values); 809 | } 810 | 811 | fn build_quantization_segment(m: &mut Vec, precision: u8, identifier: u8, qtable: &[u8; 64]) { 812 | m.clear(); 813 | 814 | let p = if precision == 8 { 0 } else { 1 }; 815 | 816 | let pqtq = (p << 4) | identifier; 817 | m.push(pqtq); 818 | 819 | for &i in &UNZIGZAG[..] { 820 | m.push(qtable[i as usize]); 821 | } 822 | } 823 | 824 | fn encode_coefficient(coefficient: i32) -> (u8, u16) { 825 | let mut magnitude = coefficient.abs() as u16; 826 | let mut num_bits = 0u8; 827 | 828 | while magnitude > 0 { 829 | magnitude >>= 1; 830 | num_bits += 1; 831 | } 832 | 833 | let mask = (1 << num_bits as usize) - 1; 834 | 835 | let val = if coefficient < 0 { 836 | (coefficient - 1) as u16 & mask 837 | } else { 838 | coefficient as u16 & mask 839 | }; 840 | 841 | (num_bits, val) 842 | } 843 | 844 | #[inline] 845 | fn rgb_to_ycbcr(pixel: P) -> (u8, u8, u8) { 846 | use num_traits::{bounds::Bounded, cast::ToPrimitive}; 847 | let [r, g, b] = pixel.to_rgb().0; 848 | let max: f32 = P::Subpixel::max_value().to_f32().unwrap(); 849 | let r: f32 = r.to_f32().unwrap(); 850 | let g: f32 = g.to_f32().unwrap(); 851 | let b: f32 = b.to_f32().unwrap(); 852 | 853 | // Coefficients from JPEG File Interchange Format (Version 1.02), multiplied for 255 maximum. 854 | let y = 76.245 / max * r + 149.685 / max * g + 29.07 / max * b; 855 | let cb = -43.0185 / max * r - 84.4815 / max * g + 127.5 / max * b + 128.; 856 | let cr = 127.5 / max * r - 106.7685 / max * g - 20.7315 / max * b + 128.; 857 | 858 | (y as u8, cb as u8, cr as u8) 859 | } 860 | 861 | /// Returns the pixel at (x,y) if (x,y) is in the image, 862 | /// otherwise the closest pixel in the image 863 | #[inline] 864 | fn pixel_at_or_near(source: &I, x: u32, y: u32) -> I::Pixel { 865 | if source.in_bounds(x, y) { 866 | source.get_pixel(x, y) 867 | } else { 868 | source.get_pixel(x.min(source.width() - 1), y.min(source.height() - 1)) 869 | } 870 | } 871 | 872 | fn copy_blocks_ycbcr( 873 | source: &I, 874 | x0: u32, 875 | y0: u32, 876 | yb: &mut [u8; 64], 877 | cbb: &mut [u8; 64], 878 | crb: &mut [u8; 64], 879 | ) { 880 | for y in 0..8 { 881 | for x in 0..8 { 882 | let pixel = pixel_at_or_near(source, x + x0, y + y0); 883 | let (yc, cb, cr) = rgb_to_ycbcr(pixel); 884 | 885 | yb[(y * 8 + x) as usize] = yc; 886 | cbb[(y * 8 + x) as usize] = cb; 887 | crb[(y * 8 + x) as usize] = cr; 888 | } 889 | } 890 | } 891 | 892 | fn copy_blocks_gray(source: &I, x0: u32, y0: u32, gb: &mut [u8; 64]) { 893 | use num_traits::cast::ToPrimitive; 894 | for y in 0..8 { 895 | for x in 0..8 { 896 | let pixel = pixel_at_or_near(source, x0 + x, y0 + y); 897 | let [luma] = pixel.to_luma().0; 898 | gb[(y * 8 + x) as usize] = luma.to_u8().unwrap(); 899 | } 900 | } 901 | } 902 | --------------------------------------------------------------------------------