Skip to content

Commit

Permalink
imager 0.3 release
Browse files Browse the repository at this point in the history
  • Loading branch information
colbyn committed Dec 18, 2019
1 parent ec3cead commit c772428
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 101 deletions.
5 changes: 4 additions & 1 deletion imager/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "imager"
version = "0.2.2"
version = "0.3.0"
authors = ["colbyn <[email protected]>"]
edition = "2018"
license = "MPL-2.0"
Expand Down Expand Up @@ -41,3 +41,6 @@ buildtype-docs-only = []
[package.metadata.docs.rs]
# no-default-features = true
features = ["buildtype-docs-only"]

# [profile.dev]
# opt-level = 2
70 changes: 55 additions & 15 deletions imager/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
use std::convert::AsRef;
use std::path::Path;
use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat};
use either::{Either, Either::*};

use crate::data::{Resolution, OutputFormat};
use crate::codec::jpeg;
use crate::codec::png;
use crate::codec::webp;

pub struct Opt {
pub struct OptJob {
source: DynamicImage,
source_format: ImageFormat,
output_format: ImageFormat,
output_format: OutputFormat,
max_size: Option<Resolution>,
}

impl Opt {
impl OptJob {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ()> {
let source = std::fs::read(path).expect("input file path");
OptJob::new(&source)
}
pub fn new(source: &[u8]) -> Result<Self, ()> {
let source_format = ::image::guess_format(source).map_err(drop)?;
let output_format = match source_format {
ImageFormat::JPEG => OutputFormat::Jpeg,
ImageFormat::PNG => OutputFormat::Png,
ImageFormat::WEBP => OutputFormat::Webp,
_ => OutputFormat::Jpeg
};
match source_format {
ImageFormat::WEBP => {
let source = webp::decode::decode(source);
Ok(Opt {
output_format: source_format,
Ok(OptJob {
output_format,
source,
source_format,
max_size: None,
})
}
_ => {
Expand All @@ -29,30 +44,55 @@ impl Opt {
source_format,
)
.map_err(drop)?;
Ok(Opt {
output_format: source_format,
Ok(OptJob {
output_format,
source,
source_format,
max_size: None,
})
}
}
}
pub fn set_output_format(&mut self, output_format: ImageFormat) {
pub fn output_format(&mut self, output_format: OutputFormat) {
self.output_format = output_format;
}
pub fn max_size(&mut self, max_size: Resolution) {
self.max_size = Some(max_size);
}
pub fn run(self) -> Result<Vec<u8>, ()> {
let input = match self.max_size {
Some(res) if (res.width, res.height) > self.source.dimensions() => {
self.source.resize(res.width, res.height, ::image::FilterType::Lanczos3)
},
_ => self.source.clone(),
};
match self.output_format {
ImageFormat::WEBP => {
Ok(webp::opt::opt(&self.source).0)
OutputFormat::Webp => {
Ok(webp::opt::opt(&input).0)
}
ImageFormat::JPEG => {
Ok(jpeg::OptContext::from_image(self.source.clone()).run_search().0)
OutputFormat::Jpeg => {
Ok(jpeg::OptContext::from_image(input.clone()).run_search().0)
}
ImageFormat::PNG => {
Ok(png::basic_optimize(&self.source))
OutputFormat::Png => {
Ok(png::basic_optimize(&input))
}
_ => unimplemented!()
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_opt_basic() {
let test_image = include_bytes!("../assets/test/1.jpeg");
for output_format in vec![OutputFormat::Jpeg, OutputFormat::Png, OutputFormat::Webp] {
let mut opt_job = OptJob::new(test_image).expect("new opt job");
opt_job.output_format(output_format);
opt_job.max_size(Resolution::new(1000, 1000));
let result = opt_job.run();
assert!(result.is_ok());
}
}
}
4 changes: 2 additions & 2 deletions imager/src/codec/png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn basic_optimize(source: &DynamicImage) -> Vec<u8> {
let vmaf_derivative = VideoBuffer::from_png(&compressed).expect("to VideoBuffer");
vmaf::get_report(&vmaf_source, &vmaf_derivative)
};
println!("vmaf: {}", report);
// println!("vmaf: {}", report);
(compressed, report)
};
let fallback = || {
Expand All @@ -119,7 +119,7 @@ pub fn basic_optimize(source: &DynamicImage) -> Vec<u8> {
};
// RUN
for num_colors in 1..256 {
println!("num_colors: {}", num_colors);
// println!("num_colors: {}", num_colors);
let (compressed, report) = run(num_colors);
if report >= 90.0 || num_colors <= 5 {
return compressed;
Expand Down
56 changes: 48 additions & 8 deletions imager/src/codec/webp/opt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::path::PathBuf;
use itertools::Itertools;
use serde::{Serialize, Deserialize};
use rayon::prelude::*;
use image::{DynamicImage, GenericImage, GenericImageView};
Expand All @@ -7,7 +8,7 @@ use crate::classifier::{self, Class};
use crate::vmaf;
use crate::codec::webp::encode::lossy::{encode};

#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OutMeta {
pub class: Class,
pub score: f64,
Expand Down Expand Up @@ -46,7 +47,7 @@ pub fn opt(source: &DynamicImage) -> (Vec<u8>, OutMeta) {
let terminate = |score: f64| {
let (width, height) = source.dimensions();
let is_small = {
width < 600 || height < 600
(width * height) < (600 * 600)
};
let mut threshold;
match class.class {
Expand All @@ -64,22 +65,61 @@ pub fn opt(source: &DynamicImage) -> (Vec<u8>, OutMeta) {
}
}
Class::H1 | Class::H2 if is_small => {
threshold = 88.0;
threshold = 70.0;
}
Class::H1 => {
threshold = 75.0;
threshold = 60.0;
}
Class::H2 => {
threshold = 65.0;
threshold = 55.0;
}
}
score >= threshold
};
// SEARCH
let start_q = match class.class {
Class::H1 | Class::H2 => 1,
_ => 10,
let start_q = {
let reduce_starting_values = |qs: Vec<u8>| -> Option<u8> {
let mut last_q = 0;
for q in qs {
let vmaf_score = run(q as f32).1;
let passed = terminate(vmaf_score);
if passed && q <= 10 {
return Some(0);
}
if passed {
return Some(last_q);
}
last_q = q;
}
None
};
let bad_fallback_low_range = || reduce_starting_values(vec![
10,
35,
65,
75,
85,
]);
let bad_fallback = || reduce_starting_values(vec![
0,
10,
20,
30,
40,
50,
60,
70,
90,
]);
// TODO:
match class.class {
Class::L0 | Class::L1 | Class::L2 => {
bad_fallback_low_range()
}
_ => bad_fallback()
}
};
let start_q = start_q.unwrap_or(1) as u32;
let mut last_q = None;
let mut last_score = None;
for q in start_q..100 {
Expand Down
59 changes: 56 additions & 3 deletions imager/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::os::raw::{c_char, c_int};
use libc::{size_t, c_float, c_void};
use serde::{Serialize, Deserialize};
use itertools::Itertools;
use image::{DynamicImage, GenericImage, GenericImageView, ImageBuffer};
use image::{DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageFormat};
use rayon::prelude::*;
use webp_dev::sys::webp::{
self as webp_sys,
Expand All @@ -30,7 +30,27 @@ use webp_dev::sys::webp::{
pub enum OutputFormat {
Jpeg,
Png,
WebP,
Webp,
}

impl OutputFormat {
pub fn infer_from_file_container<P: AsRef<Path>>(path: P) -> Option<Self> {
let buffer = std::fs::read(path).ok()?;
let format = ::image::guess_format(&buffer).ok()?;
match format {
ImageFormat::JPEG => Some(OutputFormat::Jpeg),
ImageFormat::PNG => Some(OutputFormat::Png),
ImageFormat::WEBP => Some(OutputFormat::Webp),
_ => None
}
}
pub fn infer_from_path<P: AsRef<Path>>(path: P) -> Option<Self> {
let ext = path
.as_ref()
.extension()?
.to_str()?;
OutputFormat::from_str(ext).ok()
}
}

impl FromStr for OutputFormat {
Expand All @@ -40,7 +60,7 @@ impl FromStr for OutputFormat {
"jpeg" => Ok(OutputFormat::Jpeg),
"jpg" => Ok(OutputFormat::Jpeg),
"png" => Ok(OutputFormat::Png),
"webp" => Ok(OutputFormat::Png),
"webp" => Ok(OutputFormat::Webp),
_ => {
Err(format!("Unknown or unsupported output format {}", s))
}
Expand All @@ -54,6 +74,39 @@ impl Default for OutputFormat {
}
}

#[derive(Debug, Clone)]
pub struct OutputFormats(pub Vec<OutputFormat>);

impl Default for OutputFormats {
fn default() -> Self {
OutputFormats(vec![OutputFormat::Jpeg, OutputFormat::Webp])
}
}

impl FromStr for OutputFormats {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut invalids = Vec::new();
let results = s
.split_whitespace()
.filter_map(|x| {
match OutputFormat::from_str(x) {
Ok(x) => Some(x),
Err(e) => {
invalids.push(e);
None
}
}
})
.collect::<Vec<_>>();
if invalids.is_empty() {
Ok(OutputFormats(results))
} else {
Err(invalids.join(", "))
}
}
}


///////////////////////////////////////////////////////////////////////////////
// RESOLUTION
Expand Down
Loading

0 comments on commit c772428

Please sign in to comment.