Compare commits
No commits in common. "7627f9311446aa5a64d345cd107dd141eb0ee233" and "022dbec73e60fff230e5b322125471956462e203" have entirely different histories.
7627f93114
...
022dbec73e
4 changed files with 1217 additions and 168 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"
|
||||||
adler = "1.0.2"
|
image = "0.25.5"
|
||||||
crc32fast = "1.4.2"
|
oxipng = "9.1.3"
|
||||||
miniz_oxide = "0.8.0"
|
zune-png = "0.4.10"
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
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,11 +1,12 @@
|
||||||
mod catzou;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use std::io::{Read};
|
use image::ImageBuffer;
|
||||||
|
use std::io::{BufReader, Read};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{metadata},
|
fs::{metadata, File},
|
||||||
path::{PathBuf},
|
path::{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)]
|
||||||
|
@ -13,7 +14,6 @@ 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,
|
||||||
/// Optional Debug mode
|
/// Debug mode
|
||||||
///
|
///
|
||||||
/// Either "true" or "false" in lowercase.
|
/// Either "true" or "false"
|
||||||
#[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 mut pathfr = Args.path.clone();
|
let 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,12 +39,9 @@ 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 type is FILE");
|
println!("input path FILE");
|
||||||
} else if pathtype.is_dir() {
|
} else if pathtype.is_dir() {
|
||||||
println!("input path type is DIR");
|
println!("input path 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)")
|
||||||
|
@ -52,17 +49,15 @@ fn main() {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
// println!("{:?}", path.display());
|
// println!("{:?}", path.display());
|
||||||
filelist.push(
|
filelist.push(path.display().to_string().parse().unwrap());
|
||||||
path.display().to_string().parse().unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Globbing error... uh oh...");
|
println!("Shell globbing error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprint!("that input is neither file nor dir and thats scary... aaa")
|
panic!("that input is neither file nor dir and thats scary...")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if debugmode {
|
// if debugmode {
|
||||||
|
@ -72,6 +67,38 @@ fn main() {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for thatpath in filelist {
|
for thatpath in filelist {
|
||||||
catzou::encoder(thatpath.to_owned(), thatpath)
|
encoder(thatpath, debugmode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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