Compare commits
5 commits
bd00948952
...
8099e065f3
Author | SHA1 | Date | |
---|---|---|---|
|
8099e065f3 | ||
|
e97077e527 | ||
|
fe83d1b3ee | ||
|
0e799be72d | ||
|
e30c034738 |
6 changed files with 55 additions and 18 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust: [stable, beta, nightly, 1.51.0] # MSRV=1.51
|
rust: [stable, beta, nightly, 1.61.0] # MSRV=1.61
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with: {submodules: true}
|
with: {submodules: true}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
[package]
|
[package]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
description = "VERY fast encoder/decoder for QOI (Quite Okay Image) format"
|
description = "VERY fast encoder/decoder for QOI (Quite Okay Image) format"
|
||||||
authors = ["Ivan Smirnov <rust@ivan.smirnov.ie>"]
|
authors = ["Ivan Smirnov <rust@ivan.smirnov.ie>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
repository = "https://github.com/aldanor/qoi-rust"
|
repository = "https://github.com/aldanor/qoi-rust"
|
||||||
|
@ -14,7 +14,7 @@ keywords = ["qoi", "graphics", "image", "encoding"]
|
||||||
exclude = [
|
exclude = [
|
||||||
"assets/*",
|
"assets/*",
|
||||||
]
|
]
|
||||||
rust-version = "1.51.0"
|
rust-version = "1.61.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
@ -23,7 +23,7 @@ std = [] # std mode (enabled by default) - provides access to `std::io`,
|
||||||
reference = [] # follows reference encoder implementation precisely, but may be slightly slower
|
reference = [] # follows reference encoder implementation precisely, but may be slightly slower
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.12"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["libqoi", "bench"]
|
members = ["libqoi", "bench"]
|
||||||
|
|
13
README.md
13
README.md
|
@ -16,6 +16,16 @@ Fast encoder/decoder for [QOI image format](https://qoiformat.org/), implemented
|
||||||
- `no_std` support.
|
- `no_std` support.
|
||||||
- Roundtrip-tested vs the reference C implementation; fuzz-tested.
|
- Roundtrip-tested vs the reference C implementation; fuzz-tested.
|
||||||
|
|
||||||
|
### Note about this fork
|
||||||
|
|
||||||
|
This fork implements a slight improvement to the original specs, which leaves unused the `QOI_OP_RGBA` chunk flag with RGB.
|
||||||
|
|
||||||
|
Here, we use this flag for the new `QOI_OP_RUN2` chunk. It's like the `QOI_OP_RUN` chunk, but followed by two bytes representing `run` (BE). (only for RGB, as the flag is already used for RGBA)
|
||||||
|
|
||||||
|
The decoder remains fully compatible with the original one (except when using `QOI_OP_RGBA` in a RGB image). The encoder is fully compatible for RGBA, not for RGB (except using the `reference` feature).
|
||||||
|
|
||||||
|
Why this? Because it enables significant improvements for compressing images with large uniform areas (such as screen captures), or for encoding a diff-filtered video stream where successive frames often have identical regions. (see [syeve](https://framagit.org/ZettaScript/syeve) for the video encoding)
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -51,8 +61,7 @@ this library proved to be the fastest one by a noticeable margin.
|
||||||
|
|
||||||
### Rust version
|
### Rust version
|
||||||
|
|
||||||
The minimum required Rust version is 1.51.0 (any changes to this would be
|
The minimum required Rust version for the latest crate version is 1.61.0.
|
||||||
considered to be a breaking change).
|
|
||||||
|
|
||||||
### `no_std`
|
### `no_std`
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub const QOI_OP_LUMA: u8 = 0x80; // 10xxxxxx
|
||||||
pub const QOI_OP_RUN: u8 = 0xc0; // 11xxxxxx
|
pub const QOI_OP_RUN: u8 = 0xc0; // 11xxxxxx
|
||||||
pub const QOI_OP_RGB: u8 = 0xfe; // 11111110
|
pub const QOI_OP_RGB: u8 = 0xfe; // 11111110
|
||||||
pub const QOI_OP_RGBA: u8 = 0xff; // 11111111
|
pub const QOI_OP_RGBA: u8 = 0xff; // 11111111
|
||||||
|
pub const QOI_OP_RUN2: u8 = 0xff; // 11111111
|
||||||
|
|
||||||
pub const QOI_MASK_2: u8 = 0xc0; // (11)000000
|
pub const QOI_MASK_2: u8 = 0xc0; // (11)000000
|
||||||
|
|
||||||
|
|
|
@ -49,9 +49,21 @@ where
|
||||||
px.update_rgb(*r, *g, *b);
|
px.update_rgb(*r, *g, *b);
|
||||||
data = dtail;
|
data = dtail;
|
||||||
}
|
}
|
||||||
[QOI_OP_RGBA, r, g, b, a, dtail @ ..] if RGBA => {
|
[QOI_OP_RGBA, dtail @ ..] => {
|
||||||
px.update_rgba(*r, *g, *b, *a);
|
if RGBA {
|
||||||
data = dtail;
|
if let [r, g, b, a, dtail @ ..] = dtail {
|
||||||
|
px.update_rgba(*r, *g, *b, *a);
|
||||||
|
data = dtail;
|
||||||
|
}
|
||||||
|
} else if let [b1, b2, dtail @ ..] = dtail {
|
||||||
|
*px_out = px.into();
|
||||||
|
let run = (u16::from_be_bytes([*b1, *b2]) as usize).min(pixels.len());
|
||||||
|
let (phead, ptail) = pixels.split_at_mut(run); // can't panic
|
||||||
|
phead.fill(px.into());
|
||||||
|
pixels = ptail;
|
||||||
|
data = dtail;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
[b1 @ QOI_OP_RUN..=QOI_OP_RUN_END, dtail @ ..] => {
|
[b1 @ QOI_OP_RUN..=QOI_OP_RUN_END, dtail @ ..] => {
|
||||||
*px_out = px.into();
|
*px_out = px.into();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::io::Write;
|
||||||
|
|
||||||
use bytemuck::Pod;
|
use bytemuck::Pod;
|
||||||
|
|
||||||
use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE};
|
use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE, QOI_OP_RUN2};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::header::Header;
|
use crate::header::Header;
|
||||||
use crate::pixel::{Pixel, SupportedChannels};
|
use crate::pixel::{Pixel, SupportedChannels};
|
||||||
|
@ -26,7 +26,7 @@ where
|
||||||
let mut index = [Pixel::new(); 256];
|
let mut index = [Pixel::new(); 256];
|
||||||
let mut px_prev = Pixel::new().with_a(0xff);
|
let mut px_prev = Pixel::new().with_a(0xff);
|
||||||
let mut hash_prev = px_prev.hash_index();
|
let mut hash_prev = px_prev.hash_index();
|
||||||
let mut run = 0_u8;
|
let mut run = 0_u16;
|
||||||
let mut px = Pixel::<N>::new().with_a(0xff);
|
let mut px = Pixel::<N>::new().with_a(0xff);
|
||||||
let mut index_allowed = false;
|
let mut index_allowed = false;
|
||||||
|
|
||||||
|
@ -36,8 +36,20 @@ where
|
||||||
px.read(chunk);
|
px.read(chunk);
|
||||||
if px == px_prev {
|
if px == px_prev {
|
||||||
run += 1;
|
run += 1;
|
||||||
|
#[cfg(not(feature = "reference"))]
|
||||||
|
if N == 3 {
|
||||||
|
if run == 65535 || unlikely(i == n_pixels - 1) {
|
||||||
|
let [b1, b2] = (run-1).to_be_bytes();
|
||||||
|
buf = buf.write_many(&[QOI_OP_RUN2, b1, b2])?;
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
} else if run == 62 || unlikely(i == n_pixels - 1) {
|
||||||
|
buf = buf.write_one(QOI_OP_RUN | (run as u8 - 1))?;
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "reference")]
|
||||||
if run == 62 || unlikely(i == n_pixels - 1) {
|
if run == 62 || unlikely(i == n_pixels - 1) {
|
||||||
buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
|
buf = buf.write_one(QOI_OP_RUN | (run as u8 - 1))?;
|
||||||
run = 0;
|
run = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,15 +57,18 @@ where
|
||||||
#[cfg(not(feature = "reference"))]
|
#[cfg(not(feature = "reference"))]
|
||||||
{
|
{
|
||||||
// credits for the original idea: @zakarumych (had to be fixed though)
|
// credits for the original idea: @zakarumych (had to be fixed though)
|
||||||
buf = buf.write_one(if run == 1 && index_allowed {
|
buf = if run == 1 && index_allowed {
|
||||||
QOI_OP_INDEX | hash_prev
|
buf.write_one(QOI_OP_INDEX | hash_prev)?
|
||||||
|
} else if N == 4 || run < 63 {
|
||||||
|
buf.write_one(QOI_OP_RUN | (run as u8 - 1))?
|
||||||
} else {
|
} else {
|
||||||
QOI_OP_RUN | (run - 1)
|
let [b1, b2] = (run-1).to_be_bytes();
|
||||||
})?;
|
buf.write_many(&[QOI_OP_RUN2, b1, b2])?
|
||||||
|
};
|
||||||
}
|
}
|
||||||
#[cfg(feature = "reference")]
|
#[cfg(feature = "reference")]
|
||||||
{
|
{
|
||||||
buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
|
buf = buf.write_one(QOI_OP_RUN | (run as u8 - 1))?;
|
||||||
}
|
}
|
||||||
run = 0;
|
run = 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue