Compare commits
No commits in common. "main" and "0.9.0" have entirely different histories.
27
.github/workflows/release.yml
vendored
27
.github/workflows/release.yml
vendored
@ -1,27 +0,0 @@
|
|||||||
# .github/workflows/release.yml
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: release ${{ matrix.target }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- target: x86_64-pc-windows-gnu
|
|
||||||
archive: zip
|
|
||||||
- target: x86_64-unknown-linux-musl
|
|
||||||
archive: tar.gz tar.xz tar.zst
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: Compile and release
|
|
||||||
uses: rust-build/rust-build.action@v1.4.5
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
RUSTTARGET: ${{ matrix.target }}
|
|
||||||
ARCHIVE_TYPES: ${{ matrix.archive }}
|
|
22
.github/workflows/rust.yml
vendored
22
.github/workflows/rust.yml
vendored
@ -1,22 +0,0 @@
|
|||||||
name: Rust
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --verbose
|
|
||||||
- name: Run tests
|
|
||||||
run: cargo test --verbose
|
|
87
Cargo.lock
generated
87
Cargo.lock
generated
@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@ -59,9 +65,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "array-init"
|
name = "array-init"
|
||||||
version = "2.1.0"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
|
checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@ -82,7 +88,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -97,18 +102,6 @@ dependencies = [
|
|||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_derive"
|
|
||||||
version = "4.5.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@ -129,9 +122,9 @@ checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.4.2"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
@ -147,20 +140,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.35"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.5.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
@ -173,6 +160,15 @@ version = "0.4.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@ -193,21 +189,12 @@ dependencies = [
|
|||||||
"crc32fast",
|
"crc32fast",
|
||||||
"fdeflate",
|
"fdeflate",
|
||||||
"flate2",
|
"flate2",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.8.0",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.89"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
version = "0.9.9"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"array-init",
|
"array-init",
|
||||||
"clap",
|
"clap",
|
||||||
@ -216,15 +203,6 @@ dependencies = [
|
|||||||
"png",
|
"png",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -237,23 +215,6 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.87"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
version = "0.9.9"
|
version = "0.9.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["nihil carcosa <nihil@valhrafnaz.gay>"]
|
authors = ["nihil carcosa <nihil@valhrafnaz.gay>"]
|
||||||
description = "CLI tool and rust library for the de- and encoding of images to the QOI format."
|
description = "CLI tool and rust library for the de- and encoding of images to the QOI format."
|
||||||
@ -10,7 +10,7 @@ repository = "https://git.valhrafnaz.gay/valhrafnaz/qoi-img"
|
|||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
keywords = ["image_compression", "encoding", "qoi", "cli", "library"]
|
keywords = ["image_compression", "encoding", "qoi", "cli", "library"]
|
||||||
categories = ["command-line-utilities", "encoding", "graphics", "compression"]
|
categories = ["command-line-utilities", "encoding", "graphics", "compression"]
|
||||||
exclude = ["/qoi_test_images"]
|
exlude = ["/qoi_test_images"]
|
||||||
publish = ["gitea"]
|
publish = ["gitea"]
|
||||||
|
|
||||||
|
|
||||||
@ -21,4 +21,4 @@ array-init = "2.0.1"
|
|||||||
png = "0.17.14"
|
png = "0.17.14"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
colors-transform = "0.2.11"
|
colors-transform = "0.2.11"
|
||||||
clap = { version = "4.5.18", features = ["derive"] }
|
clap = "4.5.21"
|
||||||
|
18
README.md
18
README.md
@ -1,21 +1,11 @@
|
|||||||
# QOI (Quite OK Image Format) Rust De-/Encoder
|
# QOI (Quite OK Image Format) Rust De-/Encoder
|
||||||
|
|
||||||
This is a rust CLI application and library to decode and encode raw pixel data to and from the .qoi image format.
|
This is a (currently not bug free) implementation of the QOI image compression algorithm in Rust.
|
||||||
See [qoiformat.org](https://qoiformat.org/) for more information on the format. Both decoder and encoder pass all the test images provided by the format's maintainers.
|
|
||||||
|
|
||||||
I have created this largely to learn rust myself and do not recommend using this crate over the [Image crate](https://crates.io/crates/image).
|
Currently supports de- and encoding from and to PNG.
|
||||||
Please observe that this crate is licensed under the GPL-v3-or-later only and can thus not be for non-FOSS projects. No MIT/BSD dual-licensing will be considered.
|
|
||||||
|
|
||||||
## To install
|
Current state is highly buggy and strangely only functions with 256 x 256 images.
|
||||||
|
|
||||||
Simply grab the [latest release](https://git.valhrafnaz.gay/valhrafnaz/qoi-img/releases/latest) and place the binary in a path that is searched via your `$PATH` variable. Please keep in mind that the release binary provided is for linux-x86_64 only (code uses u64 and as thus is likely not functional on IA-32). Should you wish to run the program on different operating systems, please refer to build instructions below.
|
|
||||||
|
|
||||||
## To build
|
## To build
|
||||||
|
|
||||||
Make sure rustup has cargo and your preferred toolchain installed.
|
run `cargo build -r` to build a stable version for your rustc toolchain in `./target/release`
|
||||||
|
|
||||||
Clone the repository by running `git clone https://git.valhrafnaz.gay/valhrafnaz/qoi-img.git`
|
|
||||||
|
|
||||||
Move into the directory `cd qoi-img`
|
|
||||||
|
|
||||||
Run `cargo build -r` to build a stable version for your rustc toolchain in `./target/release`.
|
|
||||||
|
86
src/lib.rs
86
src/lib.rs
@ -8,9 +8,6 @@ pub mod qoi_lib {
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::*;
|
use std::fs::*;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::BufWriter;
|
|
||||||
use std::path::Path;
|
|
||||||
use png;
|
|
||||||
|
|
||||||
|
|
||||||
use array_init;
|
use array_init;
|
||||||
@ -43,7 +40,7 @@ pub mod qoi_lib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//boilerplate implementation of the log crate
|
//boilerplate implementation of the log crate
|
||||||
pub struct SimpleLogger;
|
struct SimpleLogger;
|
||||||
|
|
||||||
impl log::Log for SimpleLogger {
|
impl log::Log for SimpleLogger {
|
||||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
@ -64,10 +61,8 @@ pub mod qoi_lib {
|
|||||||
/// ```
|
/// ```
|
||||||
/// # use std::error::Error;
|
/// # use std::error::Error;
|
||||||
/// # use crate::qoi::qoi_lib::*;
|
/// # use crate::qoi::qoi_lib::*;
|
||||||
/// # use log::*;
|
|
||||||
/// # fn main() -> Result<(), Box<ImgError>> {
|
/// # fn main() -> Result<(), Box<ImgError>> {
|
||||||
/// let level = LevelFilter::Debug;
|
/// init().expect("Failed to initialize logger");
|
||||||
/// init(level).expect("Failed to initialize logger");
|
|
||||||
/// #
|
/// #
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// #
|
/// #
|
||||||
@ -79,10 +74,8 @@ pub mod qoi_lib {
|
|||||||
/// ```
|
/// ```
|
||||||
/// # use std::error::Error;
|
/// # use std::error::Error;
|
||||||
/// # use crate::qoi::qoi_lib::*;
|
/// # use crate::qoi::qoi_lib::*;
|
||||||
/// # use log::*;
|
|
||||||
/// # fn main() -> Result<(), ImgError> {
|
/// # fn main() -> Result<(), ImgError> {
|
||||||
/// let level = LevelFilter::Debug;
|
/// match init() {
|
||||||
/// match init(level) {
|
|
||||||
/// Ok(()) => (),
|
/// Ok(()) => (),
|
||||||
/// Err(e) => println!("Logger failed to initialize!")
|
/// Err(e) => println!("Logger failed to initialize!")
|
||||||
/// }
|
/// }
|
||||||
@ -91,8 +84,8 @@ pub mod qoi_lib {
|
|||||||
/// #
|
/// #
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn init(level: LevelFilter) -> Result<(), SetLoggerError> {
|
pub fn init() -> Result<(), SetLoggerError> {
|
||||||
log::set_logger(&LOGGER).map(|()| log::set_max_level(level))
|
log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Debug))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Custom image struct, which is used to store decoded data. Used by [encode_from_image] to encode the necessary data in bytes. Requires a Vector over [Pixel] values, `Vec<Pixel>`,
|
/// Custom image struct, which is used to store decoded data. Used by [encode_from_image] to encode the necessary data in bytes. Requires a Vector over [Pixel] values, `Vec<Pixel>`,
|
||||||
@ -211,49 +204,6 @@ pub mod qoi_lib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(self.height as usize * self.width as usize * 4 as usize);
|
|
||||||
for pixel in &self.pixels {
|
|
||||||
buf.push(pixel.r);
|
|
||||||
buf.push(pixel.g);
|
|
||||||
buf.push(pixel.b);
|
|
||||||
buf.push(pixel.a);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
pub fn write_png(&self, path: &str) {
|
|
||||||
let mut file_path: String = String::new();
|
|
||||||
file_path.push_str(path);
|
|
||||||
if !path.contains(".png") {
|
|
||||||
file_path.push_str(".png");
|
|
||||||
}
|
|
||||||
let path = Path::new(&file_path);
|
|
||||||
let file = match File::create(path) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(e) => panic!("ERROR during writing output file: {e:?}")
|
|
||||||
};
|
|
||||||
let buf: Vec<u8> = self.to_bytes();
|
|
||||||
let ref mut w = BufWriter::new(file);
|
|
||||||
let mut encoder = png::Encoder::new(w, self.width, self.height);
|
|
||||||
|
|
||||||
encoder.set_color(png::ColorType::Rgba);
|
|
||||||
encoder.set_depth(png::BitDepth::Eight);
|
|
||||||
|
|
||||||
encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2)); // 1.0 / 2.2, unscaled, but rounded
|
|
||||||
let source_chromaticities = png::SourceChromaticities::new( // Using unscaled instantiation here
|
|
||||||
(0.31270, 0.32900),
|
|
||||||
(0.64000, 0.33000),
|
|
||||||
(0.30000, 0.60000),
|
|
||||||
(0.15000, 0.06000)
|
|
||||||
);
|
|
||||||
encoder.set_source_chromaticities(source_chromaticities);
|
|
||||||
let mut writer = encoder.write_header().unwrap();
|
|
||||||
match writer.write_image_data(&buf) {
|
|
||||||
Ok(_a) => (),
|
|
||||||
Err(e) => panic!("Cannot write output file! {e:?}")
|
|
||||||
}
|
|
||||||
writer.finish().unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
@ -452,6 +402,7 @@ pub mod qoi_lib {
|
|||||||
(store % 64) as u8
|
(store % 64) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Definitely broken in some way
|
||||||
pub fn encode_from_image(img: Image) -> Vec<u8> {
|
pub fn encode_from_image(img: Image) -> Vec<u8> {
|
||||||
let mut prev_pixel: Pixel = Pixel {
|
let mut prev_pixel: Pixel = Pixel {
|
||||||
r: 0u8,
|
r: 0u8,
|
||||||
@ -617,23 +568,10 @@ pub mod qoi_lib {
|
|||||||
|
|
||||||
encoded_bytes
|
encoded_bytes
|
||||||
}
|
}
|
||||||
/// Writes Image as byte vector to file with name given as string slice.
|
|
||||||
/// ```rust
|
|
||||||
/// # use qoi::qoi_lib::*;
|
|
||||||
/// # fn main() {
|
|
||||||
///
|
|
||||||
/// let bytes: Vec<u8> = vec![];
|
|
||||||
/// let name = "qoi-image";
|
|
||||||
/// write_to_file(bytes, name);
|
|
||||||
/// #
|
|
||||||
/// #
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn write_to_file(bytes: Vec<u8>, filename: &str) -> std::io::Result<()> {
|
pub fn write_to_file(bytes: Vec<u8>, filename: &str) -> std::io::Result<()> {
|
||||||
let mut file_path: String = String::from(filename);
|
let mut file_path: String = String::from(filename);
|
||||||
if !filename.contains(".qoi") {
|
|
||||||
file_path.push_str(".qoi");
|
file_path.push_str(".qoi");
|
||||||
}
|
|
||||||
|
|
||||||
let mut buffer = File::create(file_path)?;
|
let mut buffer = File::create(file_path)?;
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
@ -859,11 +797,11 @@ pub mod qoi_lib {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
|
use std::path::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn diff_test() {
|
fn diff_test() {
|
||||||
let level: LevelFilter = LevelFilter::Debug;
|
init().expect("Logger initialisation failed!");
|
||||||
init(level).expect("Logger initialisation failed!");
|
|
||||||
let pix1: Pixel = Pixel::new(0, 0, 0, 255);
|
let pix1: Pixel = Pixel::new(0, 0, 0, 255);
|
||||||
let pix2: Pixel = Pixel::new(255, 255, 255, 255);
|
let pix2: Pixel = Pixel::new(255, 255, 255, 255);
|
||||||
|
|
||||||
@ -876,7 +814,7 @@ pub mod qoi_lib {
|
|||||||
assert_eq!(pix3.diff(&pix4), (-5, -5, -5));
|
assert_eq!(pix3.diff(&pix4), (-5, -5, -5));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #[test]
|
#[test]
|
||||||
fn qoi_to_qoi_test() -> io::Result<()> {
|
fn qoi_to_qoi_test() -> io::Result<()> {
|
||||||
//Open path to test images
|
//Open path to test images
|
||||||
let path: &Path = Path::new("./qoi_test_images/");
|
let path: &Path = Path::new("./qoi_test_images/");
|
||||||
@ -952,7 +890,7 @@ pub mod qoi_lib {
|
|||||||
if !(file_path_str.contains(".png")) {
|
if !(file_path_str.contains(".png")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
debug!("{:}",file_path_str);
|
println!("{:}",file_path_str);
|
||||||
let file = match File::open(&file_path) {
|
let file = match File::open(&file_path) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => panic!("Cannot read file! \n {e:?}")
|
Err(e) => panic!("Cannot read file! \n {e:?}")
|
||||||
@ -1004,7 +942,7 @@ pub mod qoi_lib {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tag_test() {
|
fn tag_test() {
|
||||||
//init().expect("Logger initialisation failed!");
|
//init().expect("Logger initialisation failed!");
|
||||||
|
229
src/main.rs
229
src/main.rs
@ -1,14 +1,12 @@
|
|||||||
|
use std::env;
|
||||||
use clap::{Args,Parser, Subcommand};
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
use std::process;
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use colors_transform::{Color, Hsl, Rgb};
|
use colors_transform::{Color, Hsl, Rgb};
|
||||||
use png;
|
use png;
|
||||||
use qoi::qoi_lib::*;
|
use qoi::qoi_lib::*;
|
||||||
use log::{error,info};
|
|
||||||
|
|
||||||
fn encode_checkerboard() {
|
fn encode_checkerboard() {
|
||||||
let mut pixels: Vec<Pixel> = Vec::with_capacity(64 * 64);
|
let mut pixels: Vec<Pixel> = Vec::with_capacity(64 * 64);
|
||||||
@ -61,7 +59,7 @@ fn encode_debug() {
|
|||||||
1 => img_data.push(rgb.get_green() as u8),
|
1 => img_data.push(rgb.get_green() as u8),
|
||||||
2 => img_data.push(rgb.get_blue() as u8),
|
2 => img_data.push(rgb.get_blue() as u8),
|
||||||
3 => img_data.push(alpha as u8),
|
3 => img_data.push(alpha as u8),
|
||||||
_ => error!("unrecoverable for-loop failure"),
|
_ => panic!("unrecoverable for-loop failure"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,11 +73,11 @@ fn encode_debug() {
|
|||||||
let stop = match start.elapsed() {
|
let stop = match start.elapsed() {
|
||||||
Ok(elapsed) => elapsed.as_millis(),
|
Ok(elapsed) => elapsed.as_millis(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error: {e:?}");
|
println!("Error: {e:?}");
|
||||||
return ();
|
return ();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
info!("Encode took: {} ms.", stop);
|
println!("Encode took: {} ms.", stop);
|
||||||
write_to_file(img_bytes, "test").expect("Error writing file!");
|
write_to_file(img_bytes, "test").expect("Error writing file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +87,7 @@ fn demo() {
|
|||||||
let stop = match start.elapsed() {
|
let stop = match start.elapsed() {
|
||||||
Ok(elapsed) => elapsed.as_millis(),
|
Ok(elapsed) => elapsed.as_millis(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error: {e:?}");
|
println!("Error: {e:?}");
|
||||||
return ();
|
return ();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -98,14 +96,12 @@ fn demo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Attempts to encode given png image as second argument into qoi
|
//Attempts to encode given png image as second argument into qoi
|
||||||
fn encode(in_path: &str, out_path: &str) {
|
fn encode(args: &Vec<String>) {
|
||||||
|
//Path is fetched from arguments
|
||||||
|
let path = &args[2];
|
||||||
|
|
||||||
//Init png decoder, attempt to decode png into bitmap, throw error if unsuccessful
|
//Init png decoder, attempt to decode png into bitmap, throw error if unsuccessful
|
||||||
let file:File = File::open(in_path).unwrap_or_else(|e| {
|
let decoder = png::Decoder::new(File::open(path).unwrap());
|
||||||
println!("Error: {:?}", e.to_string());
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
let decoder = png::Decoder::new(file);
|
|
||||||
let mut reader = match decoder.read_info() {
|
let mut reader = match decoder.read_info() {
|
||||||
Ok(reader) => reader,
|
Ok(reader) => reader,
|
||||||
Err(e) => panic!("ERROR: couldn't read file: {e:}"),
|
Err(e) => panic!("ERROR: couldn't read file: {e:}"),
|
||||||
@ -134,19 +130,30 @@ fn encode(in_path: &str, out_path: &str) {
|
|||||||
Err(err) => panic!("Problem generating image: {:?}", err),
|
Err(err) => panic!("Problem generating image: {:?}", err),
|
||||||
};
|
};
|
||||||
|
|
||||||
//in case out_path is erroneously passed with suffix
|
//encode generated bitmap
|
||||||
let filename = match out_path.strip_suffix(".png") {
|
if args.len() >= 4 {
|
||||||
Some(s) => s,
|
let filename: &String = &args[3];
|
||||||
None => out_path
|
|
||||||
};
|
|
||||||
|
|
||||||
write_to_file(encode_from_image(img), filename).expect("ERROR: Can't write file.");
|
write_to_file(encode_from_image(img), filename).expect("ERROR: Can't write file.");
|
||||||
info!("Encoding successful!");
|
} else {
|
||||||
|
let mut filename = path.clone();
|
||||||
|
for _i in 0..4 {
|
||||||
|
filename.pop();
|
||||||
|
}
|
||||||
|
write_to_file(encode_from_image(img), filename.as_str()).expect("ERROR: Can't write file.");
|
||||||
|
}
|
||||||
|
println!("Encoding successful!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode(args: &Vec<String>) -> io::Result<()> {
|
||||||
|
let mut path: String = String::new();
|
||||||
|
if args.len() > 2 {
|
||||||
|
path.push_str(args[2].as_str());
|
||||||
|
} else {
|
||||||
|
println!("ERROR: incorrect number of arguments! (specify file to decode!).");
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
fn decode(path: &str) -> Result<Image, std::io::Error> {
|
let f: File = match File::open(path.as_str()) {
|
||||||
let f: File = match File::open(path) {
|
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => panic!("ERROR: {e:?}"),
|
Err(e) => panic!("ERROR: {e:?}"),
|
||||||
};
|
};
|
||||||
@ -156,158 +163,68 @@ fn decode(path: &str) -> Result<Image, std::io::Error> {
|
|||||||
reader.read_to_end(&mut bytes)?;
|
reader.read_to_end(&mut bytes)?;
|
||||||
|
|
||||||
match qoi::qoi_lib::decode(bytes) {
|
match qoi::qoi_lib::decode(bytes) {
|
||||||
Ok(img) => {
|
Ok(_img) => println!("Decoding successful!"),
|
||||||
info!("Decoding successful!");
|
|
||||||
return Ok(img);
|
|
||||||
},
|
|
||||||
Err(err) => panic!("ERROR: {err:?}"),
|
Err(err) => panic!("ERROR: {err:?}"),
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench(input: &str, output: Option<String>) {
|
fn bench(args: &Vec<String>) {
|
||||||
|
if args.len() < 4 {
|
||||||
|
panic!("ERROR: invalid number of arguments!");
|
||||||
|
}
|
||||||
|
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
let out_path = match output {
|
encode(args);
|
||||||
Some(s) => s,
|
|
||||||
None => input.strip_suffix(".png").unwrap_or(input).to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
encode(input, &out_path);
|
|
||||||
|
|
||||||
match start.elapsed() {
|
match start.elapsed() {
|
||||||
Ok(elapsed) => {
|
Ok(elapsed) => println!("Encode took {} μs", elapsed.as_micros()),
|
||||||
if elapsed.as_millis() == 0 {
|
|
||||||
println!("Encode took {:?} μs to complete", elapsed.as_micros());
|
|
||||||
} else if elapsed.as_millis() > 999 {
|
|
||||||
println!("Encode took {:.3} s to complete", elapsed.as_secs_f32());
|
|
||||||
} else {
|
|
||||||
println!("Encode took {:?} ms to complete", elapsed.as_millis());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => panic!("ERROR: {e:?}"),
|
Err(e) => panic!("ERROR: {e:?}"),
|
||||||
}
|
}
|
||||||
|
let mut new_arg: Vec<String> = Vec::new();
|
||||||
|
new_arg.push(String::from(""));
|
||||||
|
new_arg.push(String::from(""));
|
||||||
|
let mut to_push: String = args[3].clone();
|
||||||
|
to_push.push_str(".qoi");
|
||||||
|
new_arg.push(to_push);
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
let mut out_path: String = out_path.to_owned();
|
decode(&new_arg).expect(
|
||||||
if !out_path.contains(".qoi") {
|
"ERROR: Unspecified error during io-pipeline. Ensure file path is valid and can be read.",
|
||||||
out_path.push_str(".qoi");
|
);
|
||||||
}
|
|
||||||
match decode(&out_path) {
|
|
||||||
Ok(img) => {
|
|
||||||
//Never fails as long as memory does not corrupt thanks to above push_str op.
|
|
||||||
let png_path = out_path.strip_suffix(".qoi").unwrap();
|
|
||||||
img.write_png(&png_path);
|
|
||||||
},
|
|
||||||
Err(e) => panic!("Error: {e:?}")
|
|
||||||
}
|
|
||||||
match start.elapsed() {
|
match start.elapsed() {
|
||||||
Ok(elapsed) => {
|
Ok(elapsed) => println!("Decode took {} μs", elapsed.as_micros()),
|
||||||
if elapsed.as_millis() == 0 {
|
|
||||||
println!("Encode took {:?} μs to complete", elapsed.as_micros());
|
|
||||||
} else if elapsed.as_millis() > 999 {
|
|
||||||
println!("Encode took {:.3} s to complete", elapsed.as_secs_f32());
|
|
||||||
} else {
|
|
||||||
println!("Encode took {:?} ms to complete", elapsed.as_millis());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => panic!("ERROR: {e:?}"),
|
Err(e) => panic!("ERROR: {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(name = "QOI Image Transcoder")]
|
|
||||||
#[command(version, about, long_about = None)]
|
|
||||||
#[command(next_line_help = true)]
|
|
||||||
struct Cli {
|
|
||||||
/// Specify log verbosity, respects multiple applications.
|
|
||||||
#[arg(short,long, action = clap::ArgAction::Count)]
|
|
||||||
verbose: Option<u8>,
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
/// Encode given [IMAGE] from { png } to qoi.
|
|
||||||
Encode(EncodeArgs),
|
|
||||||
/// Decode given qoi to specified [FORMAT].
|
|
||||||
Decode(DecodeArgs),
|
|
||||||
/// Benchmark en- and decoder by passing in [IMAGE] and optionally specifying [OUTPUT] file.
|
|
||||||
Bench(BenchArgs),
|
|
||||||
/// Demo the application.
|
|
||||||
Demo {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
struct BenchArgs {
|
|
||||||
/// File to be encoded.
|
|
||||||
#[arg(short,long)]
|
|
||||||
input: String,
|
|
||||||
/// Optional output path.
|
|
||||||
#[arg(short,long)]
|
|
||||||
output: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
struct DecodeArgs {
|
|
||||||
/// Qoi file to be decoded
|
|
||||||
#[arg(short,long)]
|
|
||||||
input: String,
|
|
||||||
/// Format to transcode into
|
|
||||||
#[arg(short,long)]
|
|
||||||
format: String,
|
|
||||||
/// Optional file path
|
|
||||||
#[arg(short,long)]
|
|
||||||
output: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
struct EncodeArgs {
|
|
||||||
// File to be encoded
|
|
||||||
#[arg(short,long)]
|
|
||||||
input: String,
|
|
||||||
// Optional output path
|
|
||||||
#[arg(short,long)]
|
|
||||||
output: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cli: Cli = Cli::parse();
|
//Initialize logger
|
||||||
|
init().expect("Failed to initialize logger.");
|
||||||
|
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
match &cli.command {
|
if args.len() == 1 {
|
||||||
Commands::Bench(args) => {
|
panic!("ERROR: no arguments send!");
|
||||||
bench(&args.input, args.output.clone());
|
|
||||||
},
|
|
||||||
Commands::Decode(args)=> {
|
|
||||||
if args.format != "png" {
|
|
||||||
panic!("Unsupported output format!")
|
|
||||||
} else {
|
|
||||||
let img = match decode(&args.input) {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(e) => panic!("Error: {e:?}")
|
|
||||||
};
|
|
||||||
let out_path = match &args.output {
|
|
||||||
Some(s) => s,
|
|
||||||
None => &args.input
|
|
||||||
};
|
|
||||||
img.write_png(&out_path);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Commands::Encode(args) => {
|
|
||||||
let out_path = match &args.output {
|
|
||||||
Some(s) => s,
|
|
||||||
None => args.input.strip_suffix(".png").unwrap_or_else(||{
|
|
||||||
println!("Error: Could not construct output arg from input arg. Please provide explicitly");
|
|
||||||
process::exit(1);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
encode(&args.input, &out_path);
|
match args[1].as_str() {
|
||||||
},
|
"demo" => {
|
||||||
Commands::Demo { } => demo()
|
demo();
|
||||||
|
}
|
||||||
|
//can only handle pngs for now
|
||||||
|
"encode" => {
|
||||||
|
encode(&args);
|
||||||
|
}
|
||||||
|
"decode" => {
|
||||||
|
decode(&args).expect("ERROR: Unspecified error during io-pipeline. Ensure file path is valid and can be read.");
|
||||||
|
}
|
||||||
|
"bench" => {
|
||||||
|
bench(&args);
|
||||||
|
}
|
||||||
|
"help" => {
|
||||||
|
println!("qoi supports the following commands: \n encode [IMAGE] (encodes given png-encoded into .qoi) \n decode [IMAGE] decodes given .qoi to .png \n bench [INPUT] [OUTPUT] encodes input .png into .qoi with encoding speed measured in microseconds.")
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Invalid arguments!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user