mirror of
https://github.com/Dangoware/sqp.git
synced 2025-04-19 07:12:55 -05:00
Initial work on Discrete Cosine Transform
This commit is contained in:
parent
60749ed834
commit
521bb01c87
2 changed files with 237 additions and 3 deletions
|
@ -1 +1,177 @@
|
||||||
|
use std::f32::consts::{PI, SQRT_2};
|
||||||
|
|
||||||
|
/// Perform a Discrete Cosine Transform on the input matrix.
|
||||||
|
pub fn dct(input: &[u8], width: usize, height: usize) -> Vec<f32> {
|
||||||
|
if input.len() != width * height {
|
||||||
|
panic!("Input matrix size must be width×height")
|
||||||
|
}
|
||||||
|
|
||||||
|
let sqrt_width_zero = 1.0 / (width as f32).sqrt();
|
||||||
|
let sqrt_width = SQRT_2 / (width as f32).sqrt();
|
||||||
|
|
||||||
|
let sqrt_height_zero = 1.0 / (height as f32).sqrt();
|
||||||
|
let sqrt_height = SQRT_2 / (height as f32).sqrt();
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
for u in 0..width {
|
||||||
|
for v in 0..height {
|
||||||
|
|
||||||
|
// according to the formula of DCT
|
||||||
|
let cu = if u == 0 {
|
||||||
|
sqrt_width_zero
|
||||||
|
} else {
|
||||||
|
sqrt_width
|
||||||
|
};
|
||||||
|
|
||||||
|
let cv = if v == 0 {
|
||||||
|
sqrt_height_zero
|
||||||
|
} else {
|
||||||
|
sqrt_height
|
||||||
|
};
|
||||||
|
|
||||||
|
// calculate DCT
|
||||||
|
let mut tmp_sum = 0.0;
|
||||||
|
for x in 0..width {
|
||||||
|
for y in 0..height {
|
||||||
|
let dct = (input[x * width + y] as f32 - 128.0) *
|
||||||
|
f32::cos((2.0 * x as f32 + 1.0) * u as f32 * PI / (2.0 * width as f32)) *
|
||||||
|
f32::cos((2.0 * y as f32 + 1.0) * v as f32 * PI / (2.0 * height as f32));
|
||||||
|
|
||||||
|
tmp_sum += dct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(cu * cv * tmp_sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform an inverse Discrete Cosine Transform on the input matrix.
|
||||||
|
pub fn idct(input: &[f32], width: usize, height: usize) -> Vec<u8> {
|
||||||
|
if input.len() != width * height {
|
||||||
|
panic!("Input matrix size must be width×height")
|
||||||
|
}
|
||||||
|
|
||||||
|
let sqrt_width_zero = 1.0 / (width as f32).sqrt();
|
||||||
|
let sqrt_width = SQRT_2 / (width as f32).sqrt();
|
||||||
|
|
||||||
|
let sqrt_height_zero = 1.0 / (height as f32).sqrt();
|
||||||
|
let sqrt_height = SQRT_2 / (height as f32).sqrt();
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
for x in 0..width {
|
||||||
|
for y in 0..height {
|
||||||
|
|
||||||
|
let mut tmp_sum = 0.0;
|
||||||
|
for u in 0..width {
|
||||||
|
for v in 0..height {
|
||||||
|
let cu = if u == 0 {
|
||||||
|
sqrt_width_zero
|
||||||
|
} else {
|
||||||
|
sqrt_width
|
||||||
|
};
|
||||||
|
|
||||||
|
let cv = if v == 0 {
|
||||||
|
sqrt_height_zero
|
||||||
|
} else {
|
||||||
|
sqrt_height
|
||||||
|
};
|
||||||
|
|
||||||
|
let idct = input[u * width + v] as f32 *
|
||||||
|
f32::cos((2.0 * x as f32 + 1.0) * u as f32 * PI / (2.0 * width as f32)) *
|
||||||
|
f32::cos((2.0 * y as f32 + 1.0) * v as f32 * PI / (2.0 * height as f32));
|
||||||
|
|
||||||
|
tmp_sum += cu * cv * idct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push((tmp_sum + 128.0) as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JPEG 8x8 Base Quantization Matrix for a quality level of 50.
|
||||||
|
///
|
||||||
|
/// Instead of using this, utilize the [`quantization_matrix`] function to
|
||||||
|
/// get a quantization matrix corresponding to the image quality value.
|
||||||
|
const BASE_QUANTIZATION_MATRIX: [u16; 64] = [
|
||||||
|
16, 11, 10, 16, 24, 40, 51, 61,
|
||||||
|
12, 12, 14, 19, 26, 58, 60, 55,
|
||||||
|
14, 13, 16, 24, 40, 57, 69, 56,
|
||||||
|
14, 17, 22, 29, 51, 87, 80, 62,
|
||||||
|
18, 22, 37, 56, 68, 109, 103, 77,
|
||||||
|
24, 35, 55, 64, 81, 104, 113, 92,
|
||||||
|
49, 64, 78, 87, 103, 121, 120, 101,
|
||||||
|
72, 92, 95, 98, 112, 100, 103, 99,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Generate the 8x8 quantization matrix for the given quality level.
|
||||||
|
pub fn quantization_matrix(quality: u32) -> [u16; 64] {
|
||||||
|
let factor = if quality < 50 {
|
||||||
|
5000.0 / quality as f32
|
||||||
|
} else {
|
||||||
|
200.0 - 2.0 * quality as f32
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_matrix = BASE_QUANTIZATION_MATRIX.map(|i|
|
||||||
|
f32::floor((factor * i as f32 + 50.0) / 100.0) as u16
|
||||||
|
);
|
||||||
|
new_matrix.map(|i| if i == 0 { 1 } else { i })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quantize an input matrix, returning the result.
|
||||||
|
pub fn quantize(input: &[f32], quant_matrix: [u16; 64]) -> Vec<i16> {
|
||||||
|
input.iter().zip(quant_matrix).map(|(v, q)| (v / q as f32).round() as i16).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dequantize an input matrix, returning an approximation of the original.
|
||||||
|
pub fn dequantize(input: &[i16], quant_matrix: [u16; 64]) -> Vec<f32> {
|
||||||
|
input.iter().zip(quant_matrix).map(|(v, q)| (*v as i16 * q as i16) as f32).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quantization_matrix_q80() {
|
||||||
|
let result = gen_quantization_matrix(80);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
[
|
||||||
|
6, 4, 4, 6, 10, 16, 20, 24,
|
||||||
|
5, 5, 6, 8, 10, 23, 24, 22,
|
||||||
|
6, 5, 6, 10, 16, 23, 28, 22,
|
||||||
|
6, 7, 9, 12, 20, 35, 32, 25,
|
||||||
|
7, 9, 15, 22, 27, 44, 41, 31,
|
||||||
|
10, 14, 22, 26, 32, 42, 45, 37,
|
||||||
|
20, 26, 31, 35, 41, 48, 48, 40,
|
||||||
|
29, 37, 38, 39, 45, 40, 41, 40
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quantization_matrix_q100() {
|
||||||
|
let result = gen_quantization_matrix(100);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
[
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
64
src/main.rs
64
src/main.rs
|
@ -5,7 +5,7 @@ mod compression {
|
||||||
mod binio;
|
mod binio;
|
||||||
mod header;
|
mod header;
|
||||||
mod operations;
|
mod operations;
|
||||||
mod picture;
|
pub mod picture;
|
||||||
|
|
||||||
use picture::DangoPicture;
|
use picture::DangoPicture;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -13,18 +13,75 @@ use std::{
|
||||||
io::{BufReader, BufWriter},
|
io::{BufReader, BufWriter},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
use compression::dct::{dct, dequantize, idct, quantization_matrix, quantize};
|
||||||
|
|
||||||
use image::RgbaImage;
|
use image::{ColorType, DynamicImage, GenericImage, GrayImage, Rgba};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let image_data = image::open("transparent2.png").unwrap().to_rgba8();
|
let input = image::open("test_input.png").unwrap().to_luma8();
|
||||||
|
input.save("original.png").unwrap();
|
||||||
|
|
||||||
|
let mut dct_image = Vec::new();
|
||||||
|
for h in 0..input.height() as usize / 8 {
|
||||||
|
for w in 0..input.width() as usize / 8 {
|
||||||
|
let mut chunk = Vec::new();
|
||||||
|
for i in 0..8 {
|
||||||
|
let start = (w * 8) + (h * 8) + (i * input.width() as usize);
|
||||||
|
let row = &input.as_raw()[start..start + 8];
|
||||||
|
chunk.extend_from_slice(&row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if h + w == 0 {
|
||||||
|
println!("{:?}", chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the DCT on the image section
|
||||||
|
let dct: Vec<f32> = dct(&chunk, 8, 8);
|
||||||
|
let quantzied_dct = quantize(&dct, quantization_matrix(50));
|
||||||
|
|
||||||
|
dct_image.push(quantzied_dct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut decoded_image = DynamicImage::new(input.width(), input.height(), ColorType::L8);
|
||||||
|
for (i, chunk) in dct_image.iter().enumerate() {
|
||||||
|
let dequantized_dct = dequantize(chunk, quantization_matrix(50));
|
||||||
|
let original = idct(&dequantized_dct, 8, 8);
|
||||||
|
|
||||||
|
// Write rows of blocks
|
||||||
|
let start_x = (i * 8) % (input.width() as usize - 2);
|
||||||
|
let start_y = (i * 8) / input.width() as usize * 8;
|
||||||
|
dbg!(start_x);
|
||||||
|
dbg!(start_y);
|
||||||
|
let mut sub = decoded_image.sub_image(start_x as u32, start_y as u32, 8, 8);
|
||||||
|
for y in 0..8 {
|
||||||
|
for x in 0..8 {
|
||||||
|
let value = original[(y as usize * 8) + x as usize];
|
||||||
|
sub.put_pixel(x, y, Rgba([value, value, value, 255]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoded_image.save(format!("test.png")).unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Reverse the DCT
|
||||||
|
let idct: Vec<u8> = idct(&dct, 8, 8).iter().map(|c| *c as u8).collect();
|
||||||
|
|
||||||
|
let img = GrayImage::from_raw(input.width(), input.height(), idct).unwrap();
|
||||||
|
img.save("test.png").unwrap();
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
let image_data = image::open("bw.jpg").unwrap().to_rgba8();
|
||||||
let encoded_dpf = DangoPicture::from_raw(image_data.width(), image_data.height(), &image_data);
|
let encoded_dpf = DangoPicture::from_raw(image_data.width(), image_data.height(), &image_data);
|
||||||
|
|
||||||
|
println!("ENCODING ---");
|
||||||
let timer = Instant::now();
|
let timer = Instant::now();
|
||||||
let mut outfile = BufWriter::new(File::create("test.dpf").unwrap());
|
let mut outfile = BufWriter::new(File::create("test.dpf").unwrap());
|
||||||
encoded_dpf.encode(&mut outfile);
|
encoded_dpf.encode(&mut outfile);
|
||||||
println!("Encoding took {}ms", timer.elapsed().as_millis());
|
println!("Encoding took {}ms", timer.elapsed().as_millis());
|
||||||
|
|
||||||
|
println!("DECODING ---");
|
||||||
let timer = Instant::now();
|
let timer = Instant::now();
|
||||||
let mut infile = BufReader::new(File::open("test.dpf").unwrap());
|
let mut infile = BufReader::new(File::open("test.dpf").unwrap());
|
||||||
let decoded_dpf = DangoPicture::decode(&mut infile).unwrap();
|
let decoded_dpf = DangoPicture::decode(&mut infile).unwrap();
|
||||||
|
@ -37,4 +94,5 @@ fn main() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
out_image.save("test.png").unwrap();
|
out_image.save("test.png").unwrap();
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue