|
@ -10,11 +10,7 @@ extern crate complex; |
|
|
|
|
|
|
|
|
use std::path::Path;
|
|
|
use std::path::Path;
|
|
|
use std::f64;
|
|
|
use std::f64;
|
|
|
use self::image::{
|
|
|
|
|
|
GenericImage,
|
|
|
|
|
|
Pixel,
|
|
|
|
|
|
FilterType
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
use self::image::{GenericImage, Pixel, FilterType};
|
|
|
use self::dft::Transform;
|
|
|
use self::dft::Transform;
|
|
|
use cache;
|
|
|
use cache;
|
|
|
|
|
|
|
|
@ -40,7 +36,7 @@ const FLOAT_PRECISION_MIN_5: f64 = f64::MIN / 100000_f64; |
|
|
*/
|
|
|
*/
|
|
|
pub struct PreparedImage<'a> {
|
|
|
pub struct PreparedImage<'a> {
|
|
|
orig_path: &'a str,
|
|
|
orig_path: &'a str,
|
|
|
image: image::ImageBuffer<image::Luma<u8>,Vec<u8>>
|
|
|
|
|
|
|
|
|
image: image::ImageBuffer<image::Luma<u8>, Vec<u8>>,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
/**
|
|
@ -50,7 +46,7 @@ pub struct PerceptualHashes<'a> { |
|
|
pub orig_path: &'a str,
|
|
|
pub orig_path: &'a str,
|
|
|
pub ahash: u64,
|
|
|
pub ahash: u64,
|
|
|
pub dhash: u64,
|
|
|
pub dhash: u64,
|
|
|
pub phash: u64 |
|
|
|
|
|
|
|
|
pub phash: u64,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
/**
|
|
@ -66,9 +62,8 @@ pub enum Precision { |
|
|
High,
|
|
|
High,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Get the size of the required image
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
// Get the size of the required image
|
|
|
|
|
|
//
|
|
|
impl Precision {
|
|
|
impl Precision {
|
|
|
fn get_size(&self) -> u32 {
|
|
|
fn get_size(&self) -> u32 {
|
|
|
match *self {
|
|
|
match *self {
|
|
@ -85,7 +80,7 @@ impl Precision { |
|
|
pub enum HashType {
|
|
|
pub enum HashType {
|
|
|
Ahash,
|
|
|
Ahash,
|
|
|
Dhash,
|
|
|
Dhash,
|
|
|
Phash
|
|
|
|
|
|
|
|
|
Phash,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
/**
|
|
@ -102,25 +97,34 @@ pub enum HashType { |
|
|
* A PreparedImage struct with the required information for performing hashing
|
|
|
* A PreparedImage struct with the required information for performing hashing
|
|
|
*
|
|
|
*
|
|
|
*/
|
|
|
*/
|
|
|
pub fn prepare_image<'a>(path: &'a Path, hash_type: &HashType, precision: &Precision) -> PreparedImage<'a> {
|
|
|
|
|
|
|
|
|
pub fn prepare_image<'a>(path: &'a Path,
|
|
|
|
|
|
hash_type: &HashType,
|
|
|
|
|
|
precision: &Precision)
|
|
|
|
|
|
-> PreparedImage<'a> {
|
|
|
let image_path = path.to_str().unwrap();
|
|
|
let image_path = path.to_str().unwrap();
|
|
|
let size: u32 = match *hash_type {
|
|
|
let size: u32 = match *hash_type {
|
|
|
HashType::Phash => precision.get_size() * 4,
|
|
|
HashType::Phash => precision.get_size() * 4,
|
|
|
_ => precision.get_size()
|
|
|
|
|
|
|
|
|
_ => precision.get_size(),
|
|
|
};
|
|
|
};
|
|
|
// Check if we have the already converted image in a cache and use that if possible.
|
|
|
// Check if we have the already converted image in a cache and use that if possible.
|
|
|
match cache::get_image_from_cache(&path, size) {
|
|
|
match cache::get_image_from_cache(&path, size) {
|
|
|
Some(image) => {
|
|
|
Some(image) => {
|
|
|
PreparedImage { orig_path: &*image_path, image: image }
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
PreparedImage {
|
|
|
|
|
|
orig_path: &*image_path,
|
|
|
|
|
|
image: image,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
None => {
|
|
|
None => {
|
|
|
// Otherwise let's do that work now and store it.
|
|
|
// Otherwise let's do that work now and store it.
|
|
|
let image = image::open(path).unwrap();
|
|
|
let image = image::open(path).unwrap();
|
|
|
let small_image = image.resize_exact(size, size, FilterType::Lanczos3);
|
|
|
let small_image = image.resize_exact(size, size, FilterType::Lanczos3);
|
|
|
let grey_image = small_image.to_luma();
|
|
|
let grey_image = small_image.to_luma();
|
|
|
cache::put_image_in_cache(&path, size, &grey_image);
|
|
|
cache::put_image_in_cache(&path, size, &grey_image);
|
|
|
PreparedImage { orig_path: &*image_path, image: grey_image }
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
PreparedImage {
|
|
|
|
|
|
orig_path: &*image_path,
|
|
|
|
|
|
image: grey_image,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
@ -132,7 +136,12 @@ pub fn get_perceptual_hashes<'a>(path: &'a Path, precision: &Precision) -> Perce |
|
|
let ahash = AHash::new(&path, &precision).get_hash();
|
|
|
let ahash = AHash::new(&path, &precision).get_hash();
|
|
|
let dhash = DHash::new(&path, &precision).get_hash();
|
|
|
let dhash = DHash::new(&path, &precision).get_hash();
|
|
|
let phash = PHash::new(&path, &precision).get_hash();
|
|
|
let phash = PHash::new(&path, &precision).get_hash();
|
|
|
PerceptualHashes { orig_path: &*image_path, ahash: ahash, dhash: dhash, phash: phash }
|
|
|
|
|
|
|
|
|
PerceptualHashes {
|
|
|
|
|
|
orig_path: &*image_path,
|
|
|
|
|
|
ahash: ahash,
|
|
|
|
|
|
dhash: dhash,
|
|
|
|
|
|
phash: phash,
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
/**
|
|
@ -147,8 +156,8 @@ pub fn calculate_hamming_distance(hash1: u64, hash2: u64) -> u64 { |
|
|
let mut hamming = 0u64;
|
|
|
let mut hamming = 0u64;
|
|
|
for bit in bin_diff_str.chars() {
|
|
|
for bit in bin_diff_str.chars() {
|
|
|
match bit {
|
|
|
match bit {
|
|
|
'1' => hamming+=1,
|
|
|
|
|
|
_ => continue
|
|
|
|
|
|
|
|
|
'1' => hamming += 1,
|
|
|
|
|
|
_ => continue,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
hamming
|
|
|
hamming
|
|
@ -183,11 +192,11 @@ impl<'a> PerceptualHash for AHash<'a> { |
|
|
let mut total = 0u64;
|
|
|
let mut total = 0u64;
|
|
|
for pixel in self.prepared_image.image.pixels() {
|
|
|
for pixel in self.prepared_image.image.pixels() {
|
|
|
let channels = pixel.channels();
|
|
|
let channels = pixel.channels();
|
|
|
//println!("Pixel is: {}", channels[0]);
|
|
|
|
|
|
|
|
|
// println!("Pixel is: {}", channels[0]);
|
|
|
total += channels[0] as u64;
|
|
|
total += channels[0] as u64;
|
|
|
}
|
|
|
}
|
|
|
let mean = total / (width*height) as u64;
|
|
|
|
|
|
//println!("Mean for {} is {}", prepared_image.orig_path, mean);
|
|
|
|
|
|
|
|
|
let mean = total / (width * height) as u64;
|
|
|
|
|
|
// println!("Mean for {} is {}", prepared_image.orig_path, mean);
|
|
|
|
|
|
|
|
|
// Calculating a hash based on the mean
|
|
|
// Calculating a hash based on the mean
|
|
|
let mut hash = 0u64;
|
|
|
let mut hash = 0u64;
|
|
@ -196,14 +205,14 @@ impl<'a> PerceptualHash for AHash<'a> { |
|
|
let pixel_sum = channels[0] as u64;
|
|
|
let pixel_sum = channels[0] as u64;
|
|
|
if pixel_sum >= mean {
|
|
|
if pixel_sum >= mean {
|
|
|
hash |= 1;
|
|
|
hash |= 1;
|
|
|
//println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
|
|
|
|
|
|
// println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
} else {
|
|
|
} else {
|
|
|
hash |= 0;
|
|
|
hash |= 0;
|
|
|
//println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
|
|
|
|
|
|
// println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
}
|
|
|
}
|
|
|
hash <<= 1;
|
|
|
hash <<= 1;
|
|
|
}
|
|
|
}
|
|
|
//println!("Hash for {} is {}", prepared_image.orig_path, hash);
|
|
|
|
|
|
|
|
|
// println!("Hash for {} is {}", prepared_image.orig_path, hash);
|
|
|
|
|
|
|
|
|
hash
|
|
|
hash
|
|
|
}
|
|
|
}
|
|
@ -291,14 +300,18 @@ impl<'a> PerceptualHash for PHash<'a> { |
|
|
for y in 0..height {
|
|
|
for y in 0..height {
|
|
|
let pos_x = x as u32;
|
|
|
let pos_x = x as u32;
|
|
|
let pos_y = y as u32;
|
|
|
let pos_y = y as u32;
|
|
|
data_matrix[x].push(self.prepared_image.image.get_pixel(pos_x,pos_y).channels()[0] as f64);
|
|
|
|
|
|
|
|
|
data_matrix[x]
|
|
|
|
|
|
.push(self.prepared_image.image.get_pixel(pos_x, pos_y).channels()[0] as f64);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// Perform the 2D DFT operation on our matrix
|
|
|
// Perform the 2D DFT operation on our matrix
|
|
|
calculate_2d_dft(&mut data_matrix);
|
|
|
calculate_2d_dft(&mut data_matrix);
|
|
|
// Store this DFT in the cache
|
|
|
// Store this DFT in the cache
|
|
|
cache::put_matrix_in_cache(&Path::new(self.prepared_image.orig_path),width as u32,&"dft",&data_matrix);
|
|
|
|
|
|
|
|
|
cache::put_matrix_in_cache(&Path::new(self.prepared_image.orig_path),
|
|
|
|
|
|
width as u32,
|
|
|
|
|
|
&"dft",
|
|
|
|
|
|
&data_matrix);
|
|
|
|
|
|
|
|
|
// Only need the top left quadrant
|
|
|
// Only need the top left quadrant
|
|
|
let target_width = (width / 4) as usize;
|
|
|
let target_width = (width / 4) as usize;
|
|
@ -306,7 +319,7 @@ impl<'a> PerceptualHash for PHash<'a> { |
|
|
let dft_width = (width / 4) as f64;
|
|
|
let dft_width = (width / 4) as f64;
|
|
|
let dft_height = (height / 4) as f64;
|
|
|
let dft_height = (height / 4) as f64;
|
|
|
|
|
|
|
|
|
//Calculate the mean
|
|
|
|
|
|
|
|
|
// Calculate the mean
|
|
|
let mut total = 0f64;
|
|
|
let mut total = 0f64;
|
|
|
for x in 0..target_width {
|
|
|
for x in 0..target_width {
|
|
|
for y in 0..target_height {
|
|
|
for y in 0..target_height {
|
|
@ -322,36 +335,35 @@ impl<'a> PerceptualHash for PHash<'a> { |
|
|
for y in 0..target_height {
|
|
|
for y in 0..target_height {
|
|
|
if data_matrix[x][y] >= mean {
|
|
|
if data_matrix[x][y] >= mean {
|
|
|
hash |= 1;
|
|
|
hash |= 1;
|
|
|
//println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
|
|
|
|
|
|
// println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
} else {
|
|
|
} else {
|
|
|
hash |= 0;
|
|
|
hash |= 0;
|
|
|
//println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
|
|
|
|
|
|
// println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
|
|
|
}
|
|
|
}
|
|
|
hash <<= 1;
|
|
|
hash <<= 1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
//println!("Hash for {} is {}", prepared_image.orig_path, hash);
|
|
|
|
|
|
|
|
|
// println!("Hash for {} is {}", prepared_image.orig_path, hash);
|
|
|
hash
|
|
|
hash
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Use a 1D DFT to cacluate the 2D DFT.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is achieved by calculating the DFT for each row, then calculating the
|
|
|
|
|
|
* DFT for each column of DFT row data. This means that a 32x32 image with have
|
|
|
|
|
|
* 1024 1D DFT operations performed on it. (Slightly caclulation intensive)
|
|
|
|
|
|
*
|
|
|
|
|
|
* This operation is in place on the data in the provided vector
|
|
|
|
|
|
*
|
|
|
|
|
|
* Inspired by: |
|
|
|
|
|
* http://www.inf.ufsc.br/~visao/khoros/html-dip/c5/s2/front-page.html
|
|
|
|
|
|
*
|
|
|
|
|
|
* Checked with: |
|
|
|
|
|
* http://calculator.vhex.net/post/calculator-result/2d-discrete-fourier-transform
|
|
|
|
|
|
*/
|
|
|
|
|
|
fn calculate_2d_dft(data_matrix: &mut Vec<Vec<f64>>){
|
|
|
|
|
|
//println!("{:?}", data_matrix);
|
|
|
|
|
|
|
|
|
// Use a 1D DFT to cacluate the 2D DFT.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This is achieved by calculating the DFT for each row, then calculating the
|
|
|
|
|
|
// DFT for each column of DFT row data. This means that a 32x32 image with have
|
|
|
|
|
|
// 1024 1D DFT operations performed on it. (Slightly caclulation intensive)
|
|
|
|
|
|
//
|
|
|
|
|
|
// This operation is in place on the data in the provided vector
|
|
|
|
|
|
//
|
|
|
|
|
|
// Inspired by:
|
|
|
|
|
|
// http://www.inf.ufsc.br/~visao/khoros/html-dip/c5/s2/front-page.html
|
|
|
|
|
|
//
|
|
|
|
|
|
// Checked with:
|
|
|
|
|
|
// http://calculator.vhex.net/post/calculator-result/2d-discrete-fourier-transform
|
|
|
|
|
|
//
|
|
|
|
|
|
fn calculate_2d_dft(data_matrix: &mut Vec<Vec<f64>>) {
|
|
|
|
|
|
// println!("{:?}", data_matrix);
|
|
|
let width = data_matrix.len();
|
|
|
let width = data_matrix.len();
|
|
|
let height = data_matrix[0].len();
|
|
|
let height = data_matrix[0].len();
|
|
|
|
|
|
|
|
@ -365,11 +377,11 @@ fn calculate_2d_dft(data_matrix: &mut Vec<Vec<f64>>){ |
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// Perform the DCT on this column
|
|
|
// Perform the DCT on this column
|
|
|
//println!("column[{}] before: {:?}", x, column);
|
|
|
|
|
|
|
|
|
// println!("column[{}] before: {:?}", x, column);
|
|
|
let forward_plan = dft::Plan::new(dft::Operation::Forward, column.len());
|
|
|
let forward_plan = dft::Plan::new(dft::Operation::Forward, column.len());
|
|
|
column.transform(&forward_plan);
|
|
|
column.transform(&forward_plan);
|
|
|
let complex_column = dft::unpack(&column);
|
|
|
let complex_column = dft::unpack(&column);
|
|
|
//println!("column[{}] after: {:?}", x, complex_column);
|
|
|
|
|
|
|
|
|
// println!("column[{}] after: {:?}", x, complex_column);
|
|
|
complex_data_matrix.push(complex_column);
|
|
|
complex_data_matrix.push(complex_column);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
@ -380,10 +392,10 @@ fn calculate_2d_dft(data_matrix: &mut Vec<Vec<f64>>){ |
|
|
row.push(complex_data_matrix[x][y]);
|
|
|
row.push(complex_data_matrix[x][y]);
|
|
|
}
|
|
|
}
|
|
|
// Perform DCT on the row
|
|
|
// Perform DCT on the row
|
|
|
//println!("row[{}] before: {:?}", y, row);
|
|
|
|
|
|
|
|
|
// println!("row[{}] before: {:?}", y, row);
|
|
|
let forward_plan = dft::Plan::new(dft::Operation::Forward, row.len());
|
|
|
let forward_plan = dft::Plan::new(dft::Operation::Forward, row.len());
|
|
|
row.transform(&forward_plan);
|
|
|
row.transform(&forward_plan);
|
|
|
//println!("row[{}] after: {:?}", y, row);
|
|
|
|
|
|
|
|
|
// println!("row[{}] after: {:?}", y, row);
|
|
|
|
|
|
|
|
|
// Put the row values back
|
|
|
// Put the row values back
|
|
|
for x in 0..width {
|
|
|
for x in 0..width {
|
|
@ -395,20 +407,15 @@ fn calculate_2d_dft(data_matrix: &mut Vec<Vec<f64>>){ |
|
|
fn round_float(f: f64) -> f64 {
|
|
|
fn round_float(f: f64) -> f64 {
|
|
|
if f >= FLOAT_PRECISION_MAX_1 || f <= FLOAT_PRECISION_MIN_1 {
|
|
|
if f >= FLOAT_PRECISION_MAX_1 || f <= FLOAT_PRECISION_MIN_1 {
|
|
|
f
|
|
|
f
|
|
|
}
|
|
|
|
|
|
else if f >= FLOAT_PRECISION_MAX_2 || f <= FLOAT_PRECISION_MIN_2 {
|
|
|
|
|
|
|
|
|
} else if f >= FLOAT_PRECISION_MAX_2 || f <= FLOAT_PRECISION_MIN_2 {
|
|
|
(f * 10_f64).round() / 10_f64
|
|
|
(f * 10_f64).round() / 10_f64
|
|
|
}
|
|
|
|
|
|
else if f >= FLOAT_PRECISION_MAX_3 || f <= FLOAT_PRECISION_MIN_3 {
|
|
|
|
|
|
|
|
|
} else if f >= FLOAT_PRECISION_MAX_3 || f <= FLOAT_PRECISION_MIN_3 {
|
|
|
(f * 100_f64).round() / 100_f64
|
|
|
(f * 100_f64).round() / 100_f64
|
|
|
}
|
|
|
|
|
|
else if f >= FLOAT_PRECISION_MAX_4 || f <= FLOAT_PRECISION_MIN_4 {
|
|
|
|
|
|
|
|
|
} else if f >= FLOAT_PRECISION_MAX_4 || f <= FLOAT_PRECISION_MIN_4 {
|
|
|
(f * 1000_f64).round() / 1000_f64
|
|
|
(f * 1000_f64).round() / 1000_f64
|
|
|
}
|
|
|
|
|
|
else if f >= FLOAT_PRECISION_MAX_5 || f <= FLOAT_PRECISION_MIN_5 {
|
|
|
|
|
|
|
|
|
} else if f >= FLOAT_PRECISION_MAX_5 || f <= FLOAT_PRECISION_MIN_5 {
|
|
|
(f * 10000_f64).round() / 10000_f64
|
|
|
(f * 10000_f64).round() / 10000_f64
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
|
} else {
|
|
|
(f * 100000_f64).round() / 100000_f64
|
|
|
(f * 100000_f64).round() / 100000_f64
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@ -416,23 +423,23 @@ fn round_float(f: f64) -> f64 { |
|
|
#[test]
|
|
|
#[test]
|
|
|
fn test_2d_dft() {
|
|
|
fn test_2d_dft() {
|
|
|
let mut test_matrix: Vec<Vec<f64>> = Vec::new();
|
|
|
let mut test_matrix: Vec<Vec<f64>> = Vec::new();
|
|
|
test_matrix.push(vec![1f64,1f64,1f64,3f64]);
|
|
|
|
|
|
test_matrix.push(vec![1f64,2f64,2f64,1f64]);
|
|
|
|
|
|
test_matrix.push(vec![1f64,2f64,2f64,1f64]);
|
|
|
|
|
|
test_matrix.push(vec![3f64,1f64,1f64,1f64]);
|
|
|
|
|
|
|
|
|
test_matrix.push(vec![1f64, 1f64, 1f64, 3f64]);
|
|
|
|
|
|
test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
|
|
|
|
|
|
test_matrix.push(vec![1f64, 2f64, 2f64, 1f64]);
|
|
|
|
|
|
test_matrix.push(vec![3f64, 1f64, 1f64, 1f64]);
|
|
|
|
|
|
|
|
|
println!("{:?}",test_matrix[0]);
|
|
|
|
|
|
println!("{:?}",test_matrix[1]);
|
|
|
|
|
|
println!("{:?}",test_matrix[2]);
|
|
|
|
|
|
println!("{:?}",test_matrix[3]);
|
|
|
|
|
|
|
|
|
println!("{:?}", test_matrix[0]);
|
|
|
|
|
|
println!("{:?}", test_matrix[1]);
|
|
|
|
|
|
println!("{:?}", test_matrix[2]);
|
|
|
|
|
|
println!("{:?}", test_matrix[3]);
|
|
|
|
|
|
|
|
|
println!("Performing 2d DFT");
|
|
|
println!("Performing 2d DFT");
|
|
|
calculate_2d_dft(&mut test_matrix);
|
|
|
calculate_2d_dft(&mut test_matrix);
|
|
|
|
|
|
|
|
|
println!("{:?}",test_matrix[0]);
|
|
|
|
|
|
println!("{:?}",test_matrix[1]);
|
|
|
|
|
|
println!("{:?}",test_matrix[2]);
|
|
|
|
|
|
println!("{:?}",test_matrix[3]);
|
|
|
|
|
|
|
|
|
println!("{:?}", test_matrix[0]);
|
|
|
|
|
|
println!("{:?}", test_matrix[1]);
|
|
|
|
|
|
println!("{:?}", test_matrix[2]);
|
|
|
|
|
|
println!("{:?}", test_matrix[3]);
|
|
|
|
|
|
|
|
|
assert!(test_matrix[0][0] == 24_f64);
|
|
|
assert!(test_matrix[0][0] == 24_f64);
|
|
|
assert!(test_matrix[0][1] == 0_f64);
|
|
|
assert!(test_matrix[0][1] == 0_f64);
|
|
|