Compare commits
3 commits
022dbec73e
...
7627f93114
Author | SHA1 | Date | |
---|---|---|---|
7627f93114 | |||
db2e9c2c9d | |||
58dbe405d1 |
4 changed files with 168 additions and 1217 deletions
1178
Cargo.lock
generated
1178
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,6 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.22", features = ["derive"] }
|
clap = { version = "4.5.22", features = ["derive"] }
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
image = "0.25.5"
|
adler = "1.0.2"
|
||||||
oxipng = "9.1.3"
|
crc32fast = "1.4.2"
|
||||||
zune-png = "0.4.10"
|
miniz_oxide = "0.8.0"
|
||||||
|
|
134
src/catzou/mod.rs
Normal file
134
src/catzou/mod.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
fs::File,
|
||||||
|
io::{BufReader, BufWriter, Read, Write},
|
||||||
|
usize,
|
||||||
|
};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use adler::Adler32;
|
||||||
|
use crc32fast::Hasher;
|
||||||
|
use miniz_oxide::inflate::core::{
|
||||||
|
decompress,
|
||||||
|
inflate_flags::{
|
||||||
|
TINFL_FLAG_IGNORE_ADLER32, TINFL_FLAG_PARSE_ZLIB_HEADER,
|
||||||
|
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF,
|
||||||
|
},
|
||||||
|
DecompressorOxide,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Chunk {
|
||||||
|
kind: [u8; 4],
|
||||||
|
data: Vec<u8>,
|
||||||
|
crc: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Chunk {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Chunk")
|
||||||
|
.field("kind", &self.kind)
|
||||||
|
.field("data len", &self.data.len())
|
||||||
|
.field("crc", &self.crc)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk {
|
||||||
|
fn kind_to_string(&self) -> String {
|
||||||
|
String::from_utf8_lossy(&self.kind).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encoder(inpath: PathBuf, outpath: PathBuf) {
|
||||||
|
let file = File::open(inpath).unwrap();
|
||||||
|
let mut bufread = BufReader::new(file);
|
||||||
|
|
||||||
|
// read the png header
|
||||||
|
let mut header = [0; 8];
|
||||||
|
bufread.read_exact(&mut header).unwrap();
|
||||||
|
|
||||||
|
let mut chunks = Vec::new();
|
||||||
|
|
||||||
|
let mut lenbuf = [0; 4];
|
||||||
|
while let Ok(_) = bufread.read_exact(&mut lenbuf) {
|
||||||
|
let len = u32::from_be_bytes(lenbuf);
|
||||||
|
|
||||||
|
let mut kind = [0; 4];
|
||||||
|
bufread.read_exact(&mut kind).unwrap();
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
let mut data = vec![0; len as usize];
|
||||||
|
bufread.read_exact(&mut data).unwrap();
|
||||||
|
data
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut crc = [0; 4];
|
||||||
|
bufread.read_exact(&mut crc).unwrap();
|
||||||
|
|
||||||
|
let mut chunk = Chunk { kind, data, crc };
|
||||||
|
println!("{:?}", chunk);
|
||||||
|
|
||||||
|
// recode the compressed image data
|
||||||
|
if chunk.kind == *b"IDAT" {
|
||||||
|
println!("Decompressing IDAT chunk");
|
||||||
|
let mut decompressor = DecompressorOxide::new();
|
||||||
|
decompressor.init();
|
||||||
|
let mut buf = vec![0; 1024 * 1024 * 1024]; // this could probably be smaller
|
||||||
|
let data = decompress(
|
||||||
|
&mut decompressor,
|
||||||
|
&chunk.data,
|
||||||
|
&mut buf,
|
||||||
|
0,
|
||||||
|
TINFL_FLAG_IGNORE_ADLER32
|
||||||
|
| TINFL_FLAG_PARSE_ZLIB_HEADER
|
||||||
|
| TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF,
|
||||||
|
);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Decompressed IDAT chunk status {:?}, bytes read {}, bytes outputted {}",
|
||||||
|
data.0, data.1, data.2
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = buf.split_off(data.2);
|
||||||
|
|
||||||
|
let mut adler = Adler32::new();
|
||||||
|
adler.write_slice(&buf);
|
||||||
|
|
||||||
|
let csum = adler.checksum().to_be_bytes();
|
||||||
|
|
||||||
|
// replace the last 4 bytes of the data with the new checksum
|
||||||
|
let data_len = chunk.data.len();
|
||||||
|
chunk.data[data_len - 4..].copy_from_slice(&csum);
|
||||||
|
println!("Corrected Adler32 checksum");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
hasher.update(&chunk.kind);
|
||||||
|
hasher.update(&chunk.data);
|
||||||
|
let checksum = hasher.finalize();
|
||||||
|
|
||||||
|
if checksum != u32::from_be_bytes(chunk.crc) {
|
||||||
|
println!("CRC error in chunk {:?}", chunk.kind_to_string());
|
||||||
|
chunk.crc = checksum.to_be_bytes();
|
||||||
|
println!("Corrected CRC");
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ofile = File::create(outpath).unwrap();
|
||||||
|
let mut writer = BufWriter::new(ofile);
|
||||||
|
|
||||||
|
// write a new header
|
||||||
|
writer
|
||||||
|
.write_all(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for chunk in chunks {
|
||||||
|
writer
|
||||||
|
.write_all(&u32::to_be_bytes(chunk.data.len() as u32))
|
||||||
|
.unwrap();
|
||||||
|
writer.write_all(&chunk.kind).unwrap();
|
||||||
|
writer.write_all(&chunk.data).unwrap();
|
||||||
|
writer.write_all(&chunk.crc).unwrap();
|
||||||
|
}
|
||||||
|
}
|
67
src/main.rs
67
src/main.rs
|
@ -1,12 +1,11 @@
|
||||||
|
mod catzou;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use image::ImageBuffer;
|
use std::io::{Read};
|
||||||
use std::io::{BufReader, Read};
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::{metadata, File},
|
fs::{metadata},
|
||||||
path::{Path, PathBuf},
|
path::{PathBuf},
|
||||||
};
|
};
|
||||||
use zune_png::{zune_core::options::DecoderOptions, PngDecoder};
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
|
@ -14,6 +13,7 @@ struct Args {
|
||||||
/// Input path
|
/// Input path
|
||||||
///
|
///
|
||||||
/// Include a file path or a directory path. Do not use any asterisks or wildcards.
|
/// Include a file path or a directory path. Do not use any asterisks or wildcards.
|
||||||
|
/// Do not use multiple paths in one command.
|
||||||
/// If the path includes spaces you may find it useful to use speechmarks (" ") around the path.
|
/// If the path includes spaces you may find it useful to use speechmarks (" ") around the path.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -21,9 +21,9 @@ struct Args {
|
||||||
// /// Output path
|
// /// Output path
|
||||||
// #[arg(short, long,default_value="")]
|
// #[arg(short, long,default_value="")]
|
||||||
// out: PathBuf,
|
// out: PathBuf,
|
||||||
/// Debug mode
|
/// Optional Debug mode
|
||||||
///
|
///
|
||||||
/// Either "true" or "false"
|
/// Either "true" or "false" in lowercase.
|
||||||
#[arg(short, long, default_value_t = false)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
debug: bool,
|
debug: bool,
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ struct Args {
|
||||||
fn main() {
|
fn main() {
|
||||||
// argument parsing yep
|
// argument parsing yep
|
||||||
let Args: Args = Args::parse();
|
let Args: Args = Args::parse();
|
||||||
let pathfr = Args.path.clone();
|
let mut pathfr = Args.path.clone();
|
||||||
let debugmode = Args.debug;
|
let debugmode = Args.debug;
|
||||||
|
|
||||||
let mut filelist: Vec<PathBuf> = Vec::new();
|
let mut filelist: Vec<PathBuf> = Vec::new();
|
||||||
|
@ -39,9 +39,12 @@ fn main() {
|
||||||
let pathtype = metadata(&pathfr).unwrap();
|
let pathtype = metadata(&pathfr).unwrap();
|
||||||
if pathtype.is_file() {
|
if pathtype.is_file() {
|
||||||
filelist.push(PathBuf::from(format!("{:?}", pathtype)));
|
filelist.push(PathBuf::from(format!("{:?}", pathtype)));
|
||||||
println!("input path FILE");
|
println!("input path type is FILE");
|
||||||
} else if pathtype.is_dir() {
|
} else if pathtype.is_dir() {
|
||||||
println!("input path DIR");
|
println!("input path type is DIR");
|
||||||
|
if pathfr.ends_with("/") {
|
||||||
|
pathfr.pop();
|
||||||
|
}
|
||||||
// parse file list if it's a dir
|
// parse file list if it's a dir
|
||||||
for entry in glob(&format!("{}/**/*.png", pathfr.clone().display()))
|
for entry in glob(&format!("{}/**/*.png", pathfr.clone().display()))
|
||||||
.expect("Failed to read glob pattern (you should panic)")
|
.expect("Failed to read glob pattern (you should panic)")
|
||||||
|
@ -49,15 +52,17 @@ fn main() {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
// println!("{:?}", path.display());
|
// println!("{:?}", path.display());
|
||||||
filelist.push(path.display().to_string().parse().unwrap());
|
filelist.push(
|
||||||
|
path.display().to_string().parse().unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Shell globbing error");
|
eprintln!("Globbing error... uh oh...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("that input is neither file nor dir and thats scary...")
|
eprint!("that input is neither file nor dir and thats scary... aaa")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if debugmode {
|
// if debugmode {
|
||||||
|
@ -67,38 +72,6 @@ fn main() {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for thatpath in filelist {
|
for thatpath in filelist {
|
||||||
encoder(thatpath, debugmode)
|
catzou::encoder(thatpath.to_owned(), thatpath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encoder(filepath: PathBuf, modeDebug: bool) {
|
|
||||||
let file = File::open(&filepath).unwrap();
|
|
||||||
let mut data = BufReader::new(file);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
data.read_to_end(&mut buf).unwrap();
|
|
||||||
|
|
||||||
let mut decoder = PngDecoder::new_with_options(buf, DecoderOptions::new_cmd());
|
|
||||||
decoder.decode_headers().unwrap();
|
|
||||||
let dim = decoder.get_dimensions().unwrap();
|
|
||||||
//dbg!(dim);
|
|
||||||
let col = decoder.get_colorspace().unwrap();
|
|
||||||
//dbg!(col);
|
|
||||||
let data = decoder.decode_raw().unwrap();
|
|
||||||
//dbg!(&data);
|
|
||||||
let depth = decoder.get_depth().unwrap();
|
|
||||||
|
|
||||||
if modeDebug {
|
|
||||||
println!("Filepath: {:?}", filepath);
|
|
||||||
println!("Dimensions: {:?}", dim);
|
|
||||||
println!("Colorspace: {:?}", col);
|
|
||||||
println!("BitDepth: {:?}", depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: figure out propper color checks rn we assume data is RGBA
|
|
||||||
|
|
||||||
let out: ImageBuffer<image::Rgba<u8>, Vec<u8>> =
|
|
||||||
ImageBuffer::from_raw(dim.0 as u32, dim.1 as u32, data).unwrap();
|
|
||||||
|
|
||||||
out.save(filepath.clone()).unwrap();
|
|
||||||
println!("wrote file {:?}\n",filepath)
|
|
||||||
}
|
|
Loading…
Reference in a new issue