Initial commit
This commit is contained in:
commit
f184fc5dbf
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
92
Cargo.lock
generated
Normal file
92
Cargo.lock
generated
Normal file
@ -0,0 +1,92 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "array-init"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "colors-transform"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qoi-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"array-init",
|
||||
"colors-transform",
|
||||
"log",
|
||||
"png",
|
||||
]
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "qoi-test"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
array-init = "2.0.1"
|
||||
png = "0.17.6"
|
||||
log = "0.4.17"
|
||||
colors-transform = "0.2.11"
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# QOI (Quite OK Image Format) Rust De-/Encoder
|
||||
|
||||
This is a (currently not bug free) implementation of the QOI image compression algorithm in Rust.
|
||||
|
||||
Currently supports de- and encoding from and to PNG.
|
||||
|
||||
## To build
|
||||
|
||||
run `cargo build -r` to build a stable version for your rustc toolchain in `./target/release`
|
BIN
checkerboard.qoi
Normal file
BIN
checkerboard.qoi
Normal file
Binary file not shown.
828
src/lib.rs
Normal file
828
src/lib.rs
Normal file
@ -0,0 +1,828 @@
|
||||
//! # qoi_img
|
||||
//! `qoi_img` is a bad, from-scratch implementation of the decoder and encoder for the `.qoi` file format as described as on [qoiformat.org](https://qoiformat.org/qoi-specification.pdf).
|
||||
//! This crate should not be published as better crates are available, e.g. [rapid-qoi](https://crates.io/crates/rapid-qoi).
|
||||
|
||||
#![allow(dead_code, unused_variables)]
|
||||
pub mod qoi_img {
|
||||
|
||||
use std::fmt;
|
||||
use std::io::prelude::*;
|
||||
use std::fs::File;
|
||||
use log::{debug,info, Level, Record, SetLoggerError, LevelFilter};
|
||||
|
||||
use array_init;
|
||||
|
||||
//Custom error for custom error handling
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ImgError {
|
||||
DataError,
|
||||
PixelNumberError,
|
||||
DecodeError,
|
||||
HeaderError,
|
||||
}
|
||||
//inherit from base Error
|
||||
impl std::error::Error for ImgError {}
|
||||
|
||||
|
||||
//Output for error handling
|
||||
impl fmt::Display for ImgError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ImgError::DataError => write!(f, "invalid number of bytes (must be devisible by 4)"),
|
||||
ImgError::PixelNumberError => write!(f, "number of pixels does not match height and width params"),
|
||||
ImgError::DecodeError => write!(f, "decoder failed to construct valid image"),
|
||||
ImgError::HeaderError => write!(f, "not a valid QOI file header")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//boilerplate implementation of the log crate
|
||||
struct SimpleLogger;
|
||||
|
||||
impl log::Log for SimpleLogger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||
metadata.level() <= Level::Debug
|
||||
}
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
eprintln!("{} - {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
fn flush(&self) {
|
||||
|
||||
}
|
||||
}
|
||||
//logging boilerplate
|
||||
static LOGGER: SimpleLogger = SimpleLogger;
|
||||
/// Initialises the logger provided by [log](https://crates.io/crates/log)
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use std::error::Error;
|
||||
/// # use crate::qoi_test::qoi_img::*;
|
||||
/// # fn main() -> Result<(), Box<ImgError>> {
|
||||
/// init().expect("Failed to initialize logger");
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// #
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// If you want to pass the error on replace the `println!`:
|
||||
///
|
||||
/// ```
|
||||
/// # use std::error::Error;
|
||||
/// # use crate::qoi_test::qoi_img::*;
|
||||
/// # fn main() -> Result<(), ImgError> {
|
||||
/// match init() {
|
||||
/// Ok(()) => (),
|
||||
/// Err(e) => println!("Logger failed to initialize!")
|
||||
/// }
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// #
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn init() -> Result<(), SetLoggerError>{
|
||||
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>`,
|
||||
/// which can be generated by [`self::new`] if given byte data. Otherwise, [self.pixels] must be given filled vector.
|
||||
/// `height` and `width` are given as u32 (note that qoi encoding does not guarantee functionality for images containing over 4000000 pixels.)
|
||||
/// `channels` specifies the number of channels 3 (RGB) or 4 (RBGA).
|
||||
/// `colorspace` specifies whether sRGB or all linear channels are used (0,1);
|
||||
/// # Examples
|
||||
/// Create a new image via constructor [`Image::new()`];
|
||||
/// ```rust
|
||||
/// # use std::error::Error;
|
||||
/// # use crate::qoi_test::qoi_img::*;
|
||||
/// # fn main() -> Result<(), Box<ImgError>> {
|
||||
///
|
||||
/// let pixels: Vec<u8> = vec![0;1024*1024*4];
|
||||
/// let height: u32 = 1024;
|
||||
/// let width: u32 = 1024;
|
||||
/// let channels: u8 = 4;
|
||||
/// let colorspace: u8 = 0;
|
||||
/// let img: Image = Image::new(pixels, height, width, channels, colorspace)?;
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Alternatively, [`Image::from_pixels()`] can be used to create an image from pixel values.
|
||||
pub struct Image {
|
||||
pixels: Vec<Pixel>,
|
||||
height: u32,
|
||||
width: u32,
|
||||
channels: u8,
|
||||
colorspace: u8,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
//Image constructor, expects an array of u8 pixels values in order, left to right, top to bottom.
|
||||
pub fn new(data: Vec<u8>, height: u32, width: u32, channels: u8, colorspace: u8,) -> Result<Image, ImgError> {
|
||||
let pixels: Vec<Pixel> = match Image::pixels_from_bytes(data) {
|
||||
Ok(out) => out,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if pixels.len() == (height * width) as usize {
|
||||
let out: Image = Image {
|
||||
pixels,
|
||||
height,
|
||||
width,
|
||||
channels,
|
||||
colorspace,
|
||||
};
|
||||
Ok(out)
|
||||
} else {
|
||||
Err(ImgError::PixelNumberError)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn from_pixels(pixels: Vec<Pixel>, height: u32, width: u32, channels: u8, colorspace: u8) -> Image {
|
||||
let img = Image {
|
||||
pixels,
|
||||
height,
|
||||
width,
|
||||
channels,
|
||||
colorspace
|
||||
};
|
||||
img
|
||||
}
|
||||
|
||||
fn pixels_from_bytes(data: Vec<u8>) -> Result<Vec<Pixel>, ImgError> {
|
||||
let mut pixels: Vec<Pixel> = Vec::with_capacity(data.len()/4);
|
||||
if data.len() % 4 == 0 {
|
||||
for i in 0..data.len()/4 {
|
||||
pixels.push(Pixel {
|
||||
r: data[i*4],
|
||||
g: data[i*4+1],
|
||||
b: data[i*4+2],
|
||||
a: data[i*4+3],
|
||||
});
|
||||
}
|
||||
Ok(pixels)
|
||||
} else {
|
||||
Err(ImgError::DataError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug, PartialEq)]
|
||||
pub struct Pixel {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
a: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ChunkType {
|
||||
Run,
|
||||
Index,
|
||||
Luma,
|
||||
Diff,
|
||||
RGB,
|
||||
RGBA
|
||||
}
|
||||
|
||||
impl Pixel {
|
||||
|
||||
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Pixel {
|
||||
Pixel {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a,
|
||||
}
|
||||
}
|
||||
fn equals(&self, other: &Pixel) -> bool {
|
||||
if (self.r == other.r) && (self.g == other.g) && (self.b == other.b) && (self.a == other.a) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn equals_rgb(&self, other: &Pixel) -> bool {
|
||||
if (self.r == other.r) && (self.g == other.g) && (self.b == other.b) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//self = curr pixel, other = prev pixel
|
||||
pub fn determine_chunk(&self, other: &Pixel, buffer: &Vec<Pixel>) -> (ChunkType, Option<(u8,u8,u8)>){
|
||||
|
||||
if self.equals(&other) {
|
||||
return (ChunkType::Run, None);
|
||||
}
|
||||
|
||||
if self.equals(&buffer[color_hash(&self) as usize]) {
|
||||
return (ChunkType::Index,Some((color_hash(&self), 0,0 )));
|
||||
}
|
||||
|
||||
if self.a != other.a {
|
||||
return (ChunkType::RGBA, None);
|
||||
}
|
||||
|
||||
let diff_tuple: (i16,i16,i16) = self.diff(other);
|
||||
let dr: i16 = diff_tuple.0;
|
||||
let dg: i16 = diff_tuple.1;
|
||||
let db: i16 = diff_tuple.2;
|
||||
|
||||
if (dr > -3 && dr < 2) && (dg > -3 && dg < 2) && (db > -3 && db < 2) {
|
||||
let dr: u8 = (dr + DIFF_BIAS as i16) as u8;
|
||||
let dg: u8 = (dg + DIFF_BIAS as i16) as u8;
|
||||
let db: u8 = (db + DIFF_BIAS as i16) as u8;
|
||||
return (ChunkType::Diff, Some((dr, dg, db)));
|
||||
} else if (dg > -33 && dg < 32) && ((dr - dg) > -9) && ((dr - dg) < 8) && ((db - dg) > -9) && ((db - dg) < 8) {
|
||||
let dg_out: u8 = (dg + LUMA_BIAS_G as i16) as u8;
|
||||
let dr_dg: u8 = (dr - dg + LUMA_BIAS_RB as i16) as u8;
|
||||
let db_dg: u8 = (db - dg + LUMA_BIAS_RB as i16) as u8;
|
||||
return (ChunkType::Luma, Some((dg_out, dr_dg, db_dg)));
|
||||
} else {
|
||||
return (ChunkType::RGB, None);
|
||||
}
|
||||
|
||||
}
|
||||
pub fn diff(&self, other: &Pixel) -> (i16, i16, i16) {
|
||||
let mut dr: i16;
|
||||
let dr_inv: i16;
|
||||
let mut dg: i16;
|
||||
let dg_inv: i16;
|
||||
let mut db: i16;
|
||||
let db_inv: i16;
|
||||
|
||||
dr = self.r.wrapping_sub(other.r) as i16;
|
||||
dr_inv = other.r.wrapping_sub(self.r) as i16;
|
||||
|
||||
if dr.abs() > dr_inv.abs() {
|
||||
dr = dr_inv;
|
||||
dr = -dr;
|
||||
}
|
||||
|
||||
dg = self.g.wrapping_sub(other.g) as i16;
|
||||
dg_inv = other.g.wrapping_sub(self.g) as i16;
|
||||
|
||||
if dg.abs() > dg_inv.abs() {
|
||||
dg = dg_inv;
|
||||
dg = -dg;
|
||||
}
|
||||
|
||||
db = self.b.wrapping_sub(other.b) as i16;
|
||||
db_inv = other.b.wrapping_sub(self.b) as i16;
|
||||
|
||||
if db.abs() > db_inv.abs() {
|
||||
db = db_inv;
|
||||
db = -db;
|
||||
}
|
||||
|
||||
(dr, dg, db)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Definition of header bytes
|
||||
struct Header {
|
||||
magic: [char; 4], //magic bytes "qoif"
|
||||
width: u32, //image width in pixels (BE)
|
||||
height: u32, //image height in pixels (BE)
|
||||
channels: u8, // 3 = RGB, 4 = RBGA
|
||||
colorspace: u8, // 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
}
|
||||
|
||||
impl Header {
|
||||
fn convert_to_bytestream(&self) -> [u8;14] {
|
||||
let mut out: [u8; 14] = [0;14];
|
||||
|
||||
//First, set magic bytes
|
||||
out[0] = self.magic[0] as u8;
|
||||
out[1] = self.magic[1] as u8;
|
||||
out[2] = self.magic[2] as u8;
|
||||
out[3] = self.magic[3] as u8;
|
||||
|
||||
//split width and height into 8-bit chunks
|
||||
let width_bytes = self.width.to_be_bytes();
|
||||
let height_bytes = self.height.to_be_bytes();
|
||||
|
||||
out[4] = width_bytes[0];
|
||||
out[5] = width_bytes[1];
|
||||
out[6] = width_bytes[2];
|
||||
out[7] = width_bytes[3];
|
||||
out[8] = height_bytes[0];
|
||||
out[9] = height_bytes[1];
|
||||
out[10] = height_bytes[2];
|
||||
out[11] = height_bytes[3];
|
||||
|
||||
//Set information bits
|
||||
out[12] = self.channels;
|
||||
out[13] = self.colorspace;
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
//Definition of End of Stream bytes
|
||||
#[derive(Debug)]
|
||||
struct End {
|
||||
bytes: [u8;8]
|
||||
}
|
||||
impl End {
|
||||
fn new() -> End {
|
||||
End {
|
||||
bytes: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//chunks as defined in the QOI spec
|
||||
const QOI_OP_RGB: u8 = 0b1111_1110;
|
||||
const QOI_OP_RGBA: u8 = 0b1111_1111;
|
||||
const QOI_OP_RUN: u8 = 0b1100_0000;
|
||||
const QOI_OP_INDEX: u8 = 0b0000_0000;
|
||||
const QOI_OP_DIFF: u8 = 0b0100_0000;
|
||||
const QOI_OP_LUMA: u8 = 0b1000_0000;
|
||||
|
||||
//Biases as defined in the QOI spec
|
||||
const RUN_BIAS: u8 = 1;
|
||||
|
||||
const DIFF_BIAS: u8 = 2;
|
||||
|
||||
const LUMA_BIAS_G: u8 = 32;
|
||||
const LUMA_BIAS_RB: u8 = 8;
|
||||
|
||||
|
||||
|
||||
//hash function for assigning buffer indices to stored pixels
|
||||
fn color_hash(pixel: &Pixel) -> u8 {
|
||||
let store: u32 = pixel.r as u32 * 3 + pixel.g as u32 * 5 + pixel.b as u32 * 7 + pixel.a as u32 * 11;
|
||||
(store % 64) as u8
|
||||
}
|
||||
|
||||
pub fn encode_from_image(img: Image) -> Vec<u8> {
|
||||
|
||||
let mut prev_pixel: Pixel = Pixel {r: 0u8, b: 0u8, g: 0u8, a: 255u8};
|
||||
|
||||
let mut prev_buffer: Vec<Pixel> = Vec::with_capacity(64);
|
||||
|
||||
for i in 0..64 {
|
||||
let pix: Pixel = Pixel {r:0,g:0,b:0,a:0};
|
||||
prev_buffer.push(pix);
|
||||
}
|
||||
|
||||
let mut encoded_bytes: Vec<u8> = Vec::new();
|
||||
let mut run: u8 = 0;
|
||||
|
||||
|
||||
let head = Header {
|
||||
magic: ['q', 'o', 'i', 'f'],
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
channels: img.channels,
|
||||
colorspace: img.colorspace
|
||||
};
|
||||
let head_stream = head.convert_to_bytestream();
|
||||
|
||||
|
||||
for i in head_stream {
|
||||
encoded_bytes.push(i);
|
||||
}
|
||||
|
||||
let mut counter: u32 = 0;
|
||||
|
||||
for pixel in img.pixels {
|
||||
counter += 1;
|
||||
let chunk: (ChunkType, Option<(u8,u8,u8)>) = pixel.determine_chunk(&prev_pixel, &prev_buffer);
|
||||
if chunk == (ChunkType::Run, None) {
|
||||
run += 1;
|
||||
prev_pixel = pixel.clone();
|
||||
continue;
|
||||
}
|
||||
if run > 0 {
|
||||
if run > 62 {
|
||||
while run > 0 {
|
||||
if run/62 > 0 {
|
||||
encoded_bytes.push(QOI_OP_RUN | (62-RUN_BIAS));
|
||||
run -= 62;
|
||||
} else if run%62 > 0 {
|
||||
encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS));
|
||||
run = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS));
|
||||
run = 0;
|
||||
}
|
||||
}
|
||||
|
||||
match chunk {
|
||||
(ChunkType::Index, Some((index, irr1, irr2))) => {
|
||||
encoded_bytes.push(QOI_OP_INDEX | index);
|
||||
prev_pixel = pixel;
|
||||
},
|
||||
(ChunkType::Diff, Some((dr, dg,db))) => {
|
||||
let mut out: u8 = 0b0000_0000;
|
||||
out = out | db;
|
||||
out = out | (dg << 2);
|
||||
out = out | (dr << 4);
|
||||
encoded_bytes.push(QOI_OP_DIFF | out);
|
||||
prev_pixel = pixel.clone();
|
||||
prev_buffer[color_hash(&pixel) as usize] = pixel;
|
||||
},
|
||||
(ChunkType::Luma, Some((dg, dr_dg, db_dg))) => {
|
||||
let mut out: [u8; 2] = [0b0000_0000;2];
|
||||
out[0] |= dg;
|
||||
out[0] |= QOI_OP_LUMA;
|
||||
out[1] |= db_dg;
|
||||
out[1] |= dr_dg << 4;
|
||||
encoded_bytes.push(out[0]);
|
||||
encoded_bytes.push(out[1]);
|
||||
prev_pixel = pixel.clone();
|
||||
prev_buffer[color_hash(&pixel) as usize] = pixel;
|
||||
},
|
||||
(ChunkType::RGB, None) => {
|
||||
encoded_bytes.push(QOI_OP_RGB);
|
||||
encoded_bytes.push(pixel.r);
|
||||
encoded_bytes.push(pixel.g);
|
||||
encoded_bytes.push(pixel.b);
|
||||
prev_pixel = pixel.clone();
|
||||
prev_buffer[color_hash(&pixel) as usize] = pixel;
|
||||
},
|
||||
(ChunkType::RGBA, None) => {
|
||||
if (pixel.a as i16 - prev_pixel.a as i16) == 0i16 {
|
||||
//this should never be reached, but it is
|
||||
encoded_bytes.push(QOI_OP_RGB);
|
||||
encoded_bytes.push(pixel.r);
|
||||
encoded_bytes.push(pixel.g);
|
||||
encoded_bytes.push(pixel.b);
|
||||
prev_pixel = pixel.clone();
|
||||
prev_buffer[color_hash(&pixel) as usize] = pixel;
|
||||
} else {
|
||||
encoded_bytes.push(QOI_OP_RGBA);
|
||||
encoded_bytes.push(pixel.r);
|
||||
encoded_bytes.push(pixel.g);
|
||||
encoded_bytes.push(pixel.b);
|
||||
encoded_bytes.push(pixel.a);
|
||||
prev_pixel = pixel.clone();
|
||||
prev_buffer[color_hash(&pixel) as usize] = pixel;
|
||||
}
|
||||
},
|
||||
_ => panic!("Critical error at encoding stage: Illegal output from difference function.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if run > 0 {
|
||||
if run > 62 {
|
||||
while run > 0 {
|
||||
if run/62 > 0 {
|
||||
encoded_bytes.push(QOI_OP_RUN | (62-RUN_BIAS));
|
||||
run -= 62;
|
||||
} else if run%62 > 0 {
|
||||
encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS));
|
||||
run = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
encoded_bytes.push(QOI_OP_RUN | (run-RUN_BIAS));
|
||||
}
|
||||
}
|
||||
|
||||
let end_bytes = End::new();
|
||||
for i in end_bytes.bytes {
|
||||
encoded_bytes.push(i)
|
||||
}
|
||||
|
||||
info!("Number of pixels processed: {}.", counter);
|
||||
info!("Number of bytes in encoding: {:?}.", encoded_bytes.len()-22);
|
||||
info!("Compression rate: {:.2}%.", (1.0-(encoded_bytes.len()-22) as f64/(counter*4)as f64)*100.0);
|
||||
|
||||
encoded_bytes
|
||||
|
||||
}
|
||||
|
||||
pub fn write_to_file(bytes: Vec<u8>, filename: &str) -> std::io::Result<()>{
|
||||
let mut file_path: String = String::from(filename);
|
||||
file_path.push_str(".qoi");
|
||||
|
||||
let mut buffer = File::create(file_path)?;
|
||||
let mut pos = 0;
|
||||
|
||||
while pos < bytes.len() {
|
||||
let bytes_written = buffer.write(&bytes[pos..])?;
|
||||
pos += bytes_written;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_header(bytes: &[u8]) -> Result<(u32,u32,u8,u8), ImgError> {
|
||||
if bytes[0] == 'q' as u8 && bytes[1] == 'o' as u8 && bytes[2] == 'i' as u8 && bytes[3] == 'f' as u8 {
|
||||
let mut width: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0000;
|
||||
let mut height: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0000;
|
||||
width |= ((bytes[4] as u32) << 24) as u32;
|
||||
width |= ((bytes[5] as u32) << 16) as u32;
|
||||
width |= ((bytes[6] as u32) << 8) as u32;
|
||||
width |= (bytes[7]) as u32;
|
||||
height |= ((bytes[8] as u32) << 24) as u32;
|
||||
height |= ((bytes[9] as u32) << 16) as u32;
|
||||
height |= ((bytes[10] as u32) << 8) as u32;
|
||||
height |= (bytes[11]) as u32;
|
||||
return Ok((width, height, bytes[12], bytes[13]));
|
||||
} else {
|
||||
return Err(ImgError::HeaderError);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_tag(tag: u8) -> Result<ChunkType,ImgError> {
|
||||
if tag == QOI_OP_RGB {
|
||||
return Ok(ChunkType::RGB);
|
||||
}
|
||||
if tag == QOI_OP_RGBA {
|
||||
return Ok(ChunkType::RGBA);
|
||||
}
|
||||
if (tag & 0b1100_0000) == QOI_OP_DIFF{
|
||||
return Ok(ChunkType::Diff);
|
||||
}
|
||||
if (tag & 0b1100_0000) == QOI_OP_INDEX {
|
||||
return Ok(ChunkType::Index);
|
||||
}
|
||||
if (tag & 0b1100_0000) == QOI_OP_LUMA {
|
||||
return Ok(ChunkType::Luma);
|
||||
}
|
||||
if (tag & 0b1100_0000) == QOI_OP_RUN {
|
||||
return Ok(ChunkType::Run);
|
||||
}
|
||||
return Err(ImgError::DecodeError);
|
||||
}
|
||||
|
||||
fn dec_rgb(bytes: &[u8], alpha: u8) -> Pixel {
|
||||
let pixel: Pixel = Pixel::new(bytes[1], bytes[2], bytes[3], alpha);
|
||||
pixel
|
||||
}
|
||||
|
||||
fn dec_rgba(bytes: &[u8]) -> Pixel {
|
||||
let pixel: Pixel = Pixel::new(bytes[1], bytes[2], bytes[3], bytes[4]);
|
||||
pixel
|
||||
}
|
||||
|
||||
fn dec_diff(byte: u8, prev_pixel: &Pixel) -> Pixel {
|
||||
let dr: u8;
|
||||
let dg: u8;
|
||||
let db: u8;
|
||||
|
||||
dr = (byte & 0b00110000) >> 4;
|
||||
dg = (byte & 0b00001100) >> 2;
|
||||
db = byte & 0b00000011;
|
||||
|
||||
let r: u8 = prev_pixel.r.wrapping_add(dr);
|
||||
let g: u8 = prev_pixel.g.wrapping_add(dg);
|
||||
let b: u8 = prev_pixel.b.wrapping_add(db);
|
||||
|
||||
let r: u8 = r.wrapping_sub(DIFF_BIAS);
|
||||
let b: u8 = b.wrapping_sub(DIFF_BIAS);
|
||||
let g: u8 = g.wrapping_sub(DIFF_BIAS);
|
||||
|
||||
let pixel: Pixel = Pixel::new(r,g,b, prev_pixel.a);
|
||||
pixel
|
||||
}
|
||||
|
||||
fn dec_luma(bytes: &[u8], prev_pixel: &Pixel) -> Pixel {
|
||||
let dr: u8;
|
||||
let dr_dg: u8;
|
||||
let db_dg: u8;
|
||||
let dg: u8;
|
||||
let db: u8;
|
||||
|
||||
dg = bytes[0] & 0b00111111;
|
||||
dr_dg = (bytes[1] & 0b11110000) >> 4;
|
||||
db_dg = bytes[1] & 0b00001111;
|
||||
dr = dr_dg + dg;
|
||||
db = db_dg + dg;
|
||||
|
||||
let r: u8 = prev_pixel.r.wrapping_add(dr);
|
||||
let g: u8 = prev_pixel.g.wrapping_add(dg);
|
||||
let b: u8 = prev_pixel.b.wrapping_add(db);
|
||||
|
||||
let r: u8 = r.wrapping_sub(LUMA_BIAS_RB + LUMA_BIAS_G);
|
||||
let g: u8 = g.wrapping_sub(LUMA_BIAS_G);
|
||||
let b: u8 = b.wrapping_sub(LUMA_BIAS_RB + LUMA_BIAS_G);
|
||||
|
||||
let pixel: Pixel = Pixel::new(r, g, b, prev_pixel.a);
|
||||
pixel
|
||||
}
|
||||
|
||||
fn dec_run() {}
|
||||
|
||||
pub fn decode(mut bytes: Vec<u8>) -> Result<Image, ImgError> {
|
||||
let width: u32;
|
||||
let height: u32;
|
||||
let channels: u8;
|
||||
let colorspace: u8;
|
||||
|
||||
let mut prev_pixel: Pixel = Pixel {r: 0u8, g: 0u8, b: 0u8, a: 255u8};
|
||||
|
||||
let mut prev_buffer: [Pixel; 64] = array_init::array_init(|_| Pixel::new(0,0,0,0));
|
||||
|
||||
match read_header(&bytes[0..14]) {
|
||||
Ok((w, h, ch, c))=> {
|
||||
width = w;
|
||||
height = h;
|
||||
channels = ch;
|
||||
colorspace = c;
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
let mut pixels: Vec<Pixel> = Vec::with_capacity((width*height*4) as usize);
|
||||
|
||||
if bytes[bytes.len()-1] == 1 {
|
||||
for i in 2..9 {
|
||||
if bytes[bytes.len()-i] != 0 {
|
||||
debug!("Ending bytes not present.");
|
||||
return Err(ImgError::DecodeError);
|
||||
}
|
||||
}
|
||||
for i in 0..8 {
|
||||
bytes.pop();
|
||||
}
|
||||
} else {
|
||||
debug!("Ending bytes not present.");
|
||||
return Err(ImgError::DecodeError);
|
||||
}
|
||||
|
||||
let mut i: usize = 14;
|
||||
|
||||
while i < bytes.len() {
|
||||
match read_tag(bytes[i]) {
|
||||
Ok(tag) => {
|
||||
match tag {
|
||||
ChunkType::RGB => {
|
||||
let dec_pix: Pixel = dec_rgb(&bytes[i..i+4], prev_pixel.a);
|
||||
prev_pixel = dec_pix.clone();
|
||||
prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone();
|
||||
pixels.push(dec_pix);
|
||||
i += 3;
|
||||
},
|
||||
ChunkType::RGBA => {
|
||||
let dec_pix: Pixel = dec_rgba(&bytes[i..i+5]);
|
||||
prev_pixel = dec_pix.clone();
|
||||
prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone();
|
||||
pixels.push(dec_pix);
|
||||
i += 4;
|
||||
},
|
||||
ChunkType::Diff => {
|
||||
let dec_pix: Pixel = dec_diff(bytes[i], &prev_pixel);
|
||||
prev_pixel = dec_pix.clone();
|
||||
prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone();
|
||||
pixels.push(dec_pix);
|
||||
},
|
||||
ChunkType::Index => {
|
||||
let dec_pix: Pixel = prev_buffer[bytes[i] as usize];
|
||||
prev_pixel = dec_pix.clone();
|
||||
prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone();
|
||||
pixels.push(dec_pix);
|
||||
},
|
||||
ChunkType::Luma => {
|
||||
let dec_pix: Pixel = dec_luma(&bytes[i..i+2], &prev_pixel);
|
||||
prev_pixel = dec_pix.clone();
|
||||
prev_buffer[color_hash(&dec_pix) as usize] = dec_pix.clone();
|
||||
pixels.push(dec_pix);
|
||||
i += 1;
|
||||
},
|
||||
ChunkType::Run => {
|
||||
let length: u8 = (bytes[i] & 0b00111111) + RUN_BIAS;
|
||||
for j in 0..length {
|
||||
pixels.push(prev_pixel.clone());
|
||||
}
|
||||
prev_buffer[color_hash(&prev_pixel) as usize] = prev_pixel.clone();
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if pixels.len() as u32 != height*width {
|
||||
debug!("h*w: {}", height*width);
|
||||
debug!("n pixels: {}", pixels.len());
|
||||
return Err(ImgError::DecodeError);
|
||||
}
|
||||
|
||||
let img = Image::from_pixels(pixels, height, width, channels, colorspace);
|
||||
Ok(img)
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::io::{Read, BufReader};
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn diff_test() {
|
||||
init().expect("Logger initialisation failed!");
|
||||
let pix1: Pixel = Pixel::new( 0, 0, 0,255);
|
||||
let pix2: Pixel = Pixel::new(255,255,255,255);
|
||||
|
||||
let pix3: Pixel = Pixel::new(155,155,155,255);
|
||||
let pix4: Pixel = Pixel::new(160,160,160,255);
|
||||
|
||||
assert_eq!(pix1.diff(&pix2), ( 1, 1, 1));
|
||||
assert_eq!(pix2.diff(&pix1), (-1,-1,-1));
|
||||
assert_eq!(pix4.diff(&pix3), ( 5, 5, 5));
|
||||
assert_eq!(pix3.diff(&pix4), (-5,-5,-5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_test() -> io::Result<()> {
|
||||
let f: File = File::open("test.qoi")?;
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
|
||||
reader.read_to_end(&mut bytes)?;
|
||||
|
||||
let out_img: super::Image;
|
||||
|
||||
match super::decode(bytes) {
|
||||
Ok(img) => out_img = img,
|
||||
Err(err) => panic!("wallah geht nicht :/ {:?}", err)
|
||||
}
|
||||
write_to_file(encode_from_image(out_img), "test_dec").expect("wallahi!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_test() -> io::Result<()> {
|
||||
let f: File = File::open("testcard_rgba.qoi")?;
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
|
||||
reader.read_to_end(&mut bytes)?;
|
||||
|
||||
let out_img: super::Image;
|
||||
|
||||
match super::decode(bytes) {
|
||||
Ok(img) => out_img = img,
|
||||
Err(err) => panic!("Ja bruder war nicht so erfolgreich ahahahahahha {:?}", err)
|
||||
}
|
||||
|
||||
write_to_file(encode_from_image(out_img), "testcard_new").expect("Boowomb!");
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_test() {
|
||||
//init().expect("Logger initialisation failed!");
|
||||
let test_rgb: u8 = 0b1111_1110;
|
||||
let test_rgba: u8 = 0b1111_1111;
|
||||
let test_luma: u8 = 0b1011_1010;
|
||||
let test_run: u8 = 0b1110_1101;
|
||||
let test_diff: u8 = 0b0110_1010;
|
||||
let test_index: u8 = 0b0010_1010;
|
||||
|
||||
|
||||
assert_eq!(Ok(ChunkType::RGB), super::read_tag(test_rgb));
|
||||
assert_eq!(Ok(ChunkType::RGBA), super::read_tag(test_rgba));
|
||||
assert_eq!(Ok(ChunkType::Luma), super::read_tag(test_luma));
|
||||
assert_eq!(Ok(ChunkType::Diff), super::read_tag(test_diff));
|
||||
assert_eq!(Ok(ChunkType::Index), super::read_tag(test_index));
|
||||
assert_eq!(Ok(ChunkType::Run), super::read_tag(test_run));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_decoders_test() {
|
||||
//init().expect("Logger initialisation failed!");
|
||||
let pix: Pixel = Pixel { r: 255, g: 255, b: 255, a: 255 };
|
||||
let prev: Pixel = Pixel { r: 1, g: 1, b: 1, a: 255 };
|
||||
let byte: u8 = 0b01000000;
|
||||
|
||||
assert_eq!(pix,dec_diff(byte, &prev));
|
||||
|
||||
let pix: Pixel = Pixel { r: 17, g: 22, b: 28, a: 100 };
|
||||
let prev: Pixel = Pixel { r: 10, g: 10, b: 10, a: 100 };
|
||||
let byte: [u8;2] = [ 0b10101100, 0b00111110 ];
|
||||
|
||||
assert_eq!(pix, dec_luma(&byte[0..2], &prev));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
256
src/main.rs
Normal file
256
src/main.rs
Normal file
@ -0,0 +1,256 @@
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use colors_transform::{Color, Hsl, Rgb};
|
||||
use png;
|
||||
use qoi_test::qoi_img::*;
|
||||
|
||||
fn encode_checkerboard() {
|
||||
let mut pixels: Vec<Pixel> = Vec::with_capacity(64 * 64);
|
||||
let red: u8 = 150;
|
||||
let green: u8 = 0;
|
||||
let blue: u8 = 150;
|
||||
//row iterator
|
||||
for i in 0..64 {
|
||||
//column iterator
|
||||
for j in 0..64 {
|
||||
//if row is 0..16, 32..48
|
||||
if (i / 16) == 0 || (i / 16) == 2 {
|
||||
//if column is 0..16, 32..48
|
||||
if (j / 16) == 0 || (j / 16) == 2 {
|
||||
let push_pix: Pixel = Pixel::new(red, green, blue, 255);
|
||||
pixels.push(push_pix);
|
||||
} else {
|
||||
let push_pix: Pixel = Pixel::new(255, 255, 255, 255);
|
||||
pixels.push(push_pix);
|
||||
}
|
||||
} else {
|
||||
if (j / 16) == 1 || (j / 16) == 3 {
|
||||
let push_pix: Pixel = Pixel::new(red, green, blue, 255);
|
||||
pixels.push(push_pix);
|
||||
} else {
|
||||
let push_pix: Pixel = Pixel::new(255, 255, 255, 255);
|
||||
pixels.push(push_pix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let img: Image = Image::from_pixels(pixels, 64, 64, 4, 0);
|
||||
write_to_file(encode_from_image(img), "checkerboard").expect("Error writing file!");
|
||||
}
|
||||
|
||||
fn encode_debug() {
|
||||
let mut img_data: Vec<u8> = Vec::new();
|
||||
//row iterator
|
||||
for i in 0..1024 {
|
||||
//cell iterator
|
||||
for j in 0..1024 {
|
||||
//subpixel iterator
|
||||
for k in 0..4 {
|
||||
let rgb: Hsl = Hsl::from(0.3515625 * j as f32, 100.0, 50.0);
|
||||
let rgb: Rgb = rgb.to_rgb();
|
||||
let alpha: f64 = -(255.0 / 1024.0) * (i as f64) + 255.0;
|
||||
match k {
|
||||
0 => img_data.push(rgb.get_red() as u8),
|
||||
1 => img_data.push(rgb.get_green() as u8),
|
||||
2 => img_data.push(rgb.get_blue() as u8),
|
||||
3 => img_data.push(alpha as u8),
|
||||
_ => panic!("unrecoverable for-loop failure"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let img: Image = match Image::new(img_data, 1024, 1024, 4, 0) {
|
||||
Ok(image) => image,
|
||||
Err(err) => panic!("Problem generating image: {:?}", err),
|
||||
};
|
||||
let start = SystemTime::now();
|
||||
let img_bytes: Vec<u8> = encode_from_image(img);
|
||||
let stop = match start.elapsed() {
|
||||
Ok(elapsed) => elapsed.as_millis(),
|
||||
Err(e) => {
|
||||
println!("Error: {e:?}");
|
||||
return ();
|
||||
}
|
||||
};
|
||||
println!("Encode took: {} ms.", stop);
|
||||
write_to_file(img_bytes, "test").expect("Error writing file!");
|
||||
}
|
||||
|
||||
fn demo() {
|
||||
let decoder = png::Decoder::new(File::open("testcard_rgba.png").unwrap());
|
||||
let mut reader = match decoder.read_info() {
|
||||
Ok(reader) => reader,
|
||||
Err(e) => panic!("ERROR: couldn't read file: {e:}"),
|
||||
};
|
||||
let mut buf = vec![0; reader.output_buffer_size()];
|
||||
|
||||
let info = match reader.next_frame(&mut buf) {
|
||||
Ok(i) => i,
|
||||
Err(e) => panic!("ERROR: {e:?}"),
|
||||
};
|
||||
|
||||
let bytes = &buf[..info.buffer_size()];
|
||||
let byte_vec: Vec<u8> = bytes.to_vec();
|
||||
|
||||
let img: Image = match Image::new(byte_vec, 256, 256, 4, 0) {
|
||||
Ok(image) => image,
|
||||
Err(err) => panic!("Problem generating imag: {:?}", err),
|
||||
};
|
||||
let start = SystemTime::now();
|
||||
write_to_file(encode_from_image(img), "img").expect("Error writing file!");
|
||||
let stop = match start.elapsed() {
|
||||
Ok(elapsed) => elapsed.as_millis(),
|
||||
Err(e) => {
|
||||
println!("Error: {e:?}");
|
||||
return ();
|
||||
}
|
||||
};
|
||||
println!("Encode took: {} ms.", stop);
|
||||
let start = SystemTime::now();
|
||||
encode_checkerboard();
|
||||
let stop = match start.elapsed() {
|
||||
Ok(elapsed) => elapsed.as_millis(),
|
||||
Err(e) => {
|
||||
println!("Error: {e:?}");
|
||||
return ();
|
||||
}
|
||||
};
|
||||
println!("Encode took: {} ms.", stop);
|
||||
encode_debug();
|
||||
}
|
||||
|
||||
//Attempts to encode given png image as second argument into qoi
|
||||
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
|
||||
let decoder = png::Decoder::new(File::open(path).unwrap());
|
||||
let mut reader = match decoder.read_info() {
|
||||
Ok(reader) => reader,
|
||||
Err(e) => panic!("ERROR: couldn't read file: {e:}"),
|
||||
};
|
||||
|
||||
//read image metadata
|
||||
let width: u32 = reader.info().width;
|
||||
let height: u32 = reader.info().height;
|
||||
//for now: hardcoded to 4
|
||||
let channels: u8 = 4;
|
||||
|
||||
//create buffer matching the size of png-decoder output, writing size to output
|
||||
let mut buf = vec![0; reader.output_buffer_size()];
|
||||
let info = match reader.next_frame(&mut buf) {
|
||||
Ok(i) => i,
|
||||
Err(e) => panic!("ERROR: {e:?}"),
|
||||
};
|
||||
|
||||
//convert buffer into vector
|
||||
let bytes = &buf[..info.buffer_size()];
|
||||
let byte_vec: Vec<u8> = bytes.to_vec();
|
||||
|
||||
//create bitmap data from raw byte vector
|
||||
let img: Image = match Image::new(byte_vec, height, width, channels, 0) {
|
||||
Ok(image) => image,
|
||||
Err(err) => panic!("Problem generating image: {:?}", err),
|
||||
};
|
||||
|
||||
//encode generated bitmap
|
||||
if args.len() >= 4 {
|
||||
let filename: &String = &args[3];
|
||||
write_to_file(encode_from_image(img), filename).expect("ERROR: Can't write file.");
|
||||
} 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!).");
|
||||
()
|
||||
}
|
||||
|
||||
let f: File = match File::open(path.as_str()) {
|
||||
Ok(f) => f,
|
||||
Err(e) => panic!("ERROR: {e:?}"),
|
||||
};
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
|
||||
reader.read_to_end(&mut bytes)?;
|
||||
|
||||
match qoi_test::qoi_img::decode(bytes) {
|
||||
Ok(_img) => println!("Decoding successful!"),
|
||||
Err(err) => panic!("ERROR: {err:?}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench(args: &Vec<String>) {
|
||||
if args.len() < 4 {
|
||||
panic!("ERROR: invalid number of arguments!");
|
||||
}
|
||||
|
||||
let start = SystemTime::now();
|
||||
encode(args);
|
||||
match start.elapsed() {
|
||||
Ok(elapsed) => println!("Encode took {} μs", elapsed.as_micros()),
|
||||
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();
|
||||
decode(&new_arg).expect(
|
||||
"ERROR: Unspecified error during io-pipeline. Ensure file path is valid and can be read.",
|
||||
);
|
||||
match start.elapsed() {
|
||||
Ok(elapsed) => println!("Decode took {} μs", elapsed.as_micros()),
|
||||
Err(e) => panic!("ERROR: {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
//Initialize logger
|
||||
init().expect("Failed to initialize logger.");
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() == 1 {
|
||||
panic!("ERROR: no arguments send!");
|
||||
}
|
||||
|
||||
match args[1].as_str() {
|
||||
"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);
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid arguments!")
|
||||
}
|
||||
}
|
||||
}
|
BIN
testcard_rgba.png
Normal file
BIN
testcard_rgba.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
testcard_rgba.qoi
Normal file
BIN
testcard_rgba.qoi
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user