Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freshen crate, fix to/from_base64 #30

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ rust-image = ["image"]
bench = []

[dependencies]
bit-vec = "0.4"
bit-vec = "0.6"
rustc-serialize = "0.3"

[dev-dependencies]
rand = "0.3"
image = ">=0.10, <=0.19"
rand = { version = "0.7", features = ["small_rng"] }
image = "0.22.2"

[dependencies.image]
version = ">=0.10, <=0.19"
version = "0.22.2"
optional = true

93 changes: 62 additions & 31 deletions src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ const FLOAT_EQ_MARGIN: f64 = 0.001;

pub fn blockhash<I: HashImage>(img: &I, size: u32) -> BitVec {
let size = next_multiple_of_4(size);
let (width, height) = img.dimensions();
let (width, height) = img.dimensions();

// Skip the floating point math if it's unnecessary
if width % size == 0 && height % size == 0 {
blockhash_fast(img, size)
} else {
blockhash_slow(img, size)
}
}
}
}

macro_rules! gen_hash {
($imgty:ty, $valty:ty, $blocks: expr, $size:expr, $block_width:expr, $block_height:expr, $eq_fn:expr) => ({
($imgty:ty, $valty:ty, $blocks: expr, $size:expr, $block_width:expr, $block_height:expr, $eq_fn:expr) => {{
let channel_count = <$imgty as HashImage>::channel_count() as u32;

let group_len = (($size * $size) / 4) as usize;
Expand All @@ -41,38 +41,44 @@ macro_rules! gen_hash {
let cmp_factor = match channel_count {
3 | 4 => 255u32 as $valty * 3u32 as $valty,
2 | 1 => 255u32 as $valty,
_ => panic!("Unrecognized channel count from HashImage: {}", channel_count),
}
* block_area
_ => panic!(
"Unrecognized channel count from HashImage: {}",
channel_count
),
} * block_area
/ (2u32 as $valty);

let medians: Vec<$valty> = $blocks.chunks(group_len).map(get_median).collect();

$blocks.chunks(group_len).zip(medians)
.flat_map(|(blocks, median)|
blocks.iter().map(move |&block|
block > median ||
($eq_fn(block, median) && median > cmp_factor)
)
)
$blocks
.chunks(group_len)
.zip(medians)
.flat_map(|(blocks, median)| {
blocks.iter().map(move |&block| {
block > median || ($eq_fn(block, median) && median > cmp_factor)
})
})
.collect()
})
}};
}

fn blockhash_slow<I: HashImage>(img: &I, size: u32) -> BitVec {
let mut blocks = vec![0f64; (size * size) as usize];

let (width, height) = img.dimensions();

// Block dimensions, in pixels
let (block_width, block_height) = (width as f64 / size as f64, height as f64 / size as f64);
let (block_width, block_height) = {
let size = f64::from(size);
(f64::from(width) / size, f64::from(height) / size)
};

let idx = |x, y| (y * size + x) as usize;

img.foreach_pixel(|x, y, px| {
let px_sum = sum_px(px) as f64;
let px_sum = f64::from(sum_px(px));

let (x, y) = (x as f64, y as f64);
let (x, y) = (f64::from(x), f64::from(y));

let block_x = x / block_width;
let block_y = y / block_height;
Expand Down Expand Up @@ -109,9 +115,15 @@ fn blockhash_slow<I: HashImage>(img: &I, size: u32) -> BitVec {
blocks[idx(block_right, block_bottom)] += px_sum * weight_right * weight_bottom;
});


gen_hash!(I, f64, blocks, size, block_width, block_height,
|l: f64, r: f64| (l - r).abs() < FLOAT_EQ_MARGIN)
gen_hash!(
I,
f64,
blocks,
size,
block_width,
block_height,
|l: f64, r: f64| (l - r).abs() < FLOAT_EQ_MARGIN
)
}

fn blockhash_fast<I: HashImage>(img: &I, size: u32) -> BitVec {
Expand All @@ -122,7 +134,7 @@ fn blockhash_fast<I: HashImage>(img: &I, size: u32) -> BitVec {

let idx = |x, y| (y * size + x) as usize;

img.foreach_pixel(|x, y, px| {
img.foreach_pixel(|x, y, px| {
let px_sum = sum_px(px);

let block_x = x / block_width;
Expand All @@ -131,26 +143,40 @@ fn blockhash_fast<I: HashImage>(img: &I, size: u32) -> BitVec {
blocks[idx(block_x, block_y)] += px_sum;
});

gen_hash!(I, u32, blocks, size, block_width, block_height, |l, r| l == r)
gen_hash!(I, u32, blocks, size, block_width, block_height, |l, r| l
== r)
}

#[inline(always)]
fn sum_px(px: &[u8]) -> u32 {
// Branch prediction should eliminate the match after a few iterations
match px.len() {
4 => if px[3] == 0 { 255 * 3 } else { sum_px(&px[..3]) },
3 => px[0] as u32 + px[1] as u32 + px[2] as u32,
2 => if px[1] == 0 { 255 } else { px[0] as u32 },
1 => px[0] as u32,
4 => {
if px[3] == 0 {
255 * 3
} else {
sum_px(&px[..3])
}
}
3 => u32::from(px[0]) + u32::from(px[1]) + u32::from(px[2]),
2 => {
if px[1] == 0 {
255
} else {
u32::from(px[0])
}
}
1 => u32::from(px[0]),
// We can only hit this assertion if there's a bug where the number of values
// per pixel doesn't match HashImage::channel_count
_ => panic!("Channel count was different than actual pixel size"),
}
}

// Get the next multiple of 4 up from x, or x if x is a multiple of 4
#[inline]
fn next_multiple_of_4(x: u32) -> u32 {
x + 3 & !3
(x + 3) & !3
}

fn get_median<T: PartialOrd + Copy>(data: &[T]) -> T {
Expand All @@ -164,7 +190,12 @@ const SORT_THRESH: usize = 8;
fn qselect_inplace<T: PartialOrd>(data: &mut [T], k: usize) -> &mut T {
let len = data.len();

assert!(k < len, "Called qselect_inplace with k = {} and data length: {}", k, len);
assert!(
k < len,
"Called qselect_inplace with k = {} and data length: {}",
k,
len
);

if len < SORT_THRESH {
data.sort_by(|left, right| left.partial_cmp(right).unwrap_or(Ordering::Less));
Expand Down Expand Up @@ -197,7 +228,7 @@ fn partition<T: PartialOrd>(data: &mut [T]) -> usize {

let mut curr = 0;

for i in 0 .. len - 1 {
for i in 0..len - 1 {
if &data[i] < &data[len - 1] {
data.swap(i, curr);
curr += 1;
Expand Down
72 changes: 40 additions & 32 deletions src/dct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ impl<'a, T: 'a> ColumnsMut<'a, T> {
#[inline(always)]
fn from_slice(data: &'a mut [T], rowstride: usize) -> Self {
ColumnsMut {
data: data,
rowstride: rowstride,
data,
rowstride,
curr: 0,
}
}
Expand All @@ -33,10 +33,10 @@ impl<'a, T: 'a> Iterator for ColumnsMut<'a, T> {
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if self.curr < self.rowstride {
let data = unsafe { &mut *(&mut self.data[self.curr..] as *mut [T]) };
let data = unsafe { &mut *(&mut self.data[self.curr..] as *mut [T]) };
self.curr += 1;
Some(ColumnMut {
data: data,
data,
rowstride: self.rowstride,
})
} else {
Expand All @@ -54,14 +54,14 @@ impl<'a, T: 'a> Index<usize> for ColumnMut<'a, T> {
type Output = T;
#[inline(always)]
fn index(&self, idx: usize) -> &T {
&self.data[idx * self.rowstride]
&self.data[idx * self.rowstride]
}
}

impl<'a, T: 'a> IndexMut<usize> for ColumnMut<'a, T> {
#[inline(always)]
fn index_mut(&mut self, idx: usize) -> &mut T {
&mut self.data[idx * self.rowstride]
&mut self.data[idx * self.rowstride]
}
}

Expand Down Expand Up @@ -108,15 +108,17 @@ pub fn clear_precomputed_matrix() {
fn precompute_matrix(size: usize, matrix: &mut Vec<f64>) {
matrix.resize(size * size, 0.0);

for i in 0 .. size {
for j in 0 .. size {
for i in 0..size {
for j in 0..size {
matrix[i * size + j] = (PI * i as f64 * (2 * j + 1) as f64 / (2 * size) as f64).cos();
}
}
}

fn with_precomputed_matrix<F>(size: usize, with_fn: F) -> bool
where F: FnOnce(&[f64]) {
where
F: FnOnce(&[f64]),
{
PRECOMPUTED_MATRIX.with(|matrix| {
let matrix = matrix.borrow();

Expand All @@ -129,35 +131,39 @@ where F: FnOnce(&[f64]) {
})
}

pub fn dct_1d<I: Index<usize, Output=f64> + ?Sized, O: IndexMut<usize, Output=f64> + ?Sized>(input: &I, output: &mut O, len: usize) {
pub fn dct_1d<I: Index<usize, Output = f64> + ?Sized, O: IndexMut<usize, Output = f64> + ?Sized>(
input: &I,
output: &mut O,
len: usize,
) {
if with_precomputed_matrix(len, |matrix| dct_1d_precomputed(input, output, len, matrix)) {
return;
}

for i in 0 .. len {
for i in 0..len {
let mut z = 0.0;

for j in 0 .. len {
z += input[j] * (
PI * i as f64 * (2 * j + 1) as f64
/ (2 * len) as f64
).cos();
}
for j in 0..len {
z += input[j] * (PI * i as f64 * (2 * j + 1) as f64 / (2 * len) as f64).cos();
}

if i == 0 {
z *= 1.0 / SQRT_2;
}

output[i] = z / 2.0;
}
}
}

fn dct_1d_precomputed<I: ?Sized, O: ?Sized>(input: &I, output: &mut O, len: usize, matrix: &[f64])
where I: Index<usize, Output=f64>, O: IndexMut<usize, Output=f64> {
for i in 0 .. len {
where
I: Index<usize, Output = f64>,
O: IndexMut<usize, Output = f64>,
{
for i in 0..len {
let mut z = 0.0;

for j in 0 .. len {
for j in 0..len {
z += input[j] * matrix[i * len + j];
}

Expand All @@ -178,18 +184,23 @@ pub fn dct_2d(packed_2d: &[f64], rowstride: usize) -> Vec<f64> {
assert_eq!(packed_2d.len() % rowstride, 0);

let mut scratch = Vec::with_capacity(packed_2d.len() * 2);
unsafe { scratch.set_len(packed_2d.len() * 2); }
unsafe {
scratch.set_len(packed_2d.len() * 2);
}

{
let (col_pass, row_pass) = scratch.split_at_mut(packed_2d.len());

for (row_in, row_out) in packed_2d.chunks(rowstride)
.zip(row_pass.chunks_mut(rowstride)) {

for (row_in, row_out) in packed_2d
.chunks(rowstride)
.zip(row_pass.chunks_mut(rowstride))
{
dct_1d(row_in, row_out, rowstride);
}

for (col_in, mut col_out) in Columns::from_slice(row_pass, rowstride)
.zip(ColumnsMut::from_slice(col_pass, rowstride)) {
.zip(ColumnsMut::from_slice(col_pass, rowstride))
{
dct_1d(&col_in, &mut col_out, rowstride);
}
}
Expand All @@ -204,7 +215,6 @@ mod dct_simd {
use simdty::f64x2;

use std::f64::consts::{PI, SQRT_2};

macro_rules! valx2 ( ($val:expr) => ( ::simdty::f64x2($val, $val) ) );

const PI: f64x2 = valx2!(PI);
Expand All @@ -223,7 +233,6 @@ mod dct_simd {
dct_1dx2(vals);



}
}

Expand All @@ -236,7 +245,7 @@ mod dct_simd {
for x in 0 .. vec.len() {
z += vec[x] * cos_approx(
PI * valx2!(
u as f64 * (2 * x + 1) as f64
u as f64 * (2 * x + 1) as f64
/ (2 * vec.len()) as f64
)
);
Expand All @@ -249,7 +258,7 @@ mod dct_simd {
out.insert(u, z / valx2!(2.0));
}

out
out
}

fn cos_approx(x2: f64x2) -> f64x2 {
Expand All @@ -263,9 +272,8 @@ mod dct_simd {
let x6 = powi(val, 6);
let x8 = powi(val, 8);

valx2!(1.0) - (x2 / valx2!(2.0)) + (x4 / valx2!(24.0))
valx2!(1.0) - (x2 / valx2!(2.0)) + (x4 / valx2!(24.0))
- (x6 / valx2!(720.0)) + (x8 / valx2!(40320.0))
}
}
*/

Loading