use bit_vec::BitVec;
use image::{FilterType, ImageResult};
use crate::base58::encode;
const HEAD_CID_I: u8 = 0x12;
const HEAD_CID_I_PCF: u8 = 0x13;
pub fn content_id_image(img_path: &str, partial: bool) -> ImageResult<String> {
let pixels = image_normalize(img_path)?;
let hash_digest = image_hash(&pixels);
let mut content_id_digest = if partial {
vec![HEAD_CID_I_PCF]
} else {
vec![HEAD_CID_I]
};
content_id_digest.extend(&hash_digest);
Ok(encode(&content_id_digest))
}
pub fn image_normalize(img_path: &str) -> ImageResult<Vec<Vec<u8>>> {
let img = image::open(img_path)?;
let img = img.grayscale();
let img = img.resize_exact(32, 32, FilterType::Triangle);
let two_dim_image = img
.raw_pixels()
.chunks(32)
.map(|row| row.to_vec())
.collect();
Ok(two_dim_image)
}
pub fn image_hash(pixels: &[Vec<u8>]) -> Vec<u8> {
let mut dct_row_lists: Vec<Vec<f64>> = Vec::new();
for row in pixels.iter() {
let mut row: Vec<f64> = row.iter().map(|&n| f64::from(n)).collect();
dct(&mut row);
dct_row_lists.push(row);
}
let mut dct_col_lists_t: Vec<Vec<f64>> = transpose(&dct_row_lists);
for mut col in dct_col_lists_t.iter_mut() {
dct(&mut col);
}
let dct_lists: Vec<Vec<f64>> = transpose(&dct_col_lists_t);
let flat_list: Vec<f64> = dct_lists
.into_iter()
.take(8)
.map(|l| l.into_iter().take(8))
.flatten()
.collect();
let med = median(&flat_list);
let bv: BitVec = flat_list.into_iter().map(|v| v > med).collect();
bv.to_bytes()
}
fn transpose(m: &[Vec<f64>]) -> Vec<Vec<f64>> {
let mut m_t = Vec::new();
for i in 0..m.len() {
let col: Vec<f64> = m.iter().map(|row| row[i]).collect();
m_t.push(col)
}
m_t
}
fn median(xs: &[f64]) -> f64 {
if xs.is_empty() {
panic!("Median can not be computed since the input is empty.")
}
let mut xs = xs.to_owned();
xs.sort_by(|x, y| x.partial_cmp(y).unwrap());
let n = xs.len();
if n % 2 == 0 {
(xs[n / 2] + xs[n / 2 - 1]) / 2.0
} else {
xs[n / 2]
}
}
pub fn dct(vector: &mut [f64]) {
let n: usize = vector.len();
assert_eq!(n.count_ones(), 1, "Length must be power of 2");
_transform_recursive(vector, &mut vec![0.0f64; n]);
}
fn _transform_recursive(vector: &mut [f64], temp: &mut [f64]) {
let len: usize = vector.len();
if len == 1 {
return;
}
let halflen: usize = len / 2;
for i in 0..halflen {
let x = vector[i];
let y = vector[len - 1 - i];
temp[i] = x + y;
temp[i + halflen] =
(x - y) / ((((i as f64) + 0.5) * std::f64::consts::PI / (len as f64)).cos() * 2.0);
}
_transform_recursive(&mut temp[0..halflen], vector);
_transform_recursive(&mut temp[halflen..len], vector);
for i in 0..halflen - 1 {
vector[i * 2] = temp[i];
vector[i * 2 + 1] = temp[i + halflen] + temp[i + halflen + 1];
}
vector[len - 2] = temp[halflen - 1];
vector[len - 1] = temp[len - 1];
}
#[cfg(test)]
mod tests {
use super::median;
#[test]
#[allow(clippy::float_cmp)]
fn test_median() {
assert_eq!(median(&[1.0, 3.0, 2.0, 2.0]), 2.0);
}
#[test]
#[should_panic]
fn test_median_empty_input() {
median(&[]);
}
}