-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
182 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
//! Check if a reduction makes file smaller, and keep best reductions. | ||
//! Works asynchronously when possible | ||
use atomicmin::AtomicMin; | ||
use deflate; | ||
use png::PngData; | ||
use png::PngImage; | ||
use png::STD_COMPRESSION; | ||
use png::STD_FILTERS; | ||
use png::STD_STRATEGY; | ||
use png::STD_WINDOW; | ||
#[cfg(feature = "parallel")] | ||
use rayon::prelude::*; | ||
use std::sync::mpsc::*; | ||
use std::sync::Arc; | ||
use std::sync::Mutex; | ||
use std::thread; | ||
|
||
/// Collect image versions and pick one that compresses best | ||
pub struct Evaluator { | ||
/// images are sent to the thread for evaluation | ||
eval_send: Option<SyncSender<(Arc<PngImage>, bool, bool)>>, | ||
// the thread helps evaluate images asynchronously | ||
eval_thread: thread::JoinHandle<Option<PngData>>, | ||
} | ||
|
||
impl Evaluator { | ||
pub fn new() -> Self { | ||
// queue size ensures we're not using too much memory for pending reductions | ||
let (tx, rx) = sync_channel(4); | ||
Self { | ||
eval_send: Some(tx), | ||
eval_thread: thread::spawn(move || Self::evaluate_images(rx)), | ||
} | ||
} | ||
|
||
/// Wait for all evaluations to finish and return smallest reduction | ||
/// Or `None` if all reductions were worse than baseline. | ||
pub fn get_result(mut self) -> Option<PngData> { | ||
let _ = self.eval_send.take(); // disconnect the sender, breaking the loop in the thread | ||
self.eval_thread.join().expect("eval thread") | ||
} | ||
|
||
/// Set baseline image. It will be used only to measure minimum compression level required | ||
pub fn set_baseline(&self, image: Arc<PngImage>) { | ||
self.try_image_inner(image, false, true) | ||
} | ||
|
||
/// Check if the image is smaller than others | ||
/// If more_filters is false, only filter 0 is going to be used. | ||
/// If more_filters is true, all STD_FILTERS (0 & 5) are going to be used. | ||
/// Filter 5 is needed for alpha reduction, but rarely useful for palette. | ||
pub fn try_image(&self, image: Arc<PngImage>, more_filters: bool) { | ||
self.try_image_inner(image, true, more_filters) | ||
} | ||
|
||
fn try_image_inner(&self, image: Arc<PngImage>, is_reduction: bool, more_filters: bool) { | ||
self.eval_send.as_ref().expect("not finished yet").send((image, is_reduction, more_filters)).expect("send") | ||
} | ||
|
||
/// Main loop of evaluation thread | ||
fn evaluate_images(from_channel: Receiver<(Arc<PngImage>, bool, bool)>) -> Option<PngData> { | ||
let best_candidate_size = AtomicMin::new(None); | ||
let best_result = Mutex::new(None); | ||
// ends when sender is dropped | ||
for (image, is_reduction, more_filters) in from_channel.iter() { | ||
let filters = if more_filters {&STD_FILTERS[..]} else {&STD_FILTERS[..1]}; | ||
#[cfg(feature = "parallel")] | ||
let filters_iter = filters.par_iter().with_max_len(1); | ||
#[cfg(not(feature = "parallel"))] | ||
let filters_iter = filters.iter(); | ||
|
||
filters_iter.for_each(|f| { | ||
if let Ok(idat_data) = deflate::deflate( | ||
&image.filter_image(*f), | ||
STD_COMPRESSION, | ||
STD_STRATEGY, | ||
STD_WINDOW, | ||
&best_candidate_size, | ||
) { | ||
let mut res = best_result.lock().unwrap(); | ||
if best_candidate_size.get().map_or(true, |len| len >= idat_data.len()) { | ||
best_candidate_size.set_min(idat_data.len()); | ||
*res = if is_reduction { | ||
Some(PngData { | ||
idat_data, | ||
raw: Arc::clone(&image), | ||
}) | ||
} else { | ||
None | ||
}; | ||
} | ||
} | ||
}); | ||
} | ||
best_result.into_inner().expect("filters should be done") | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.