Compare commits

..

No commits in common. "8099e065f39322ec7b63a76accbb7044fad507d0" and "bd009489522c2982d981e917bd148332076d286a" have entirely different histories.

6 changed files with 18 additions and 55 deletions

View file

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
rust: [stable, beta, nightly, 1.61.0] # MSRV=1.61 rust: [stable, beta, nightly, 1.51.0] # MSRV=1.51
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: {submodules: true} with: {submodules: true}

View file

@ -1,9 +1,9 @@
[package] [package]
name = "qoi" name = "qoi"
version = "0.4.1" version = "0.4.0"
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 = "2021" edition = "2018"
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.61.0" rust-version = "1.51.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.12" bytemuck = "1.7"
[workspace] [workspace]
members = ["libqoi", "bench"] members = ["libqoi", "bench"]

View file

@ -16,16 +16,6 @@ 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
@ -61,7 +51,8 @@ this library proved to be the fastest one by a noticeable margin.
### Rust version ### Rust version
The minimum required Rust version for the latest crate version is 1.61.0. The minimum required Rust version is 1.51.0 (any changes to this would be
considered to be a breaking change).
### `no_std` ### `no_std`

View file

@ -4,7 +4,6 @@ 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

View file

@ -49,22 +49,10 @@ where
px.update_rgb(*r, *g, *b); px.update_rgb(*r, *g, *b);
data = dtail; data = dtail;
} }
[QOI_OP_RGBA, dtail @ ..] => { [QOI_OP_RGBA, r, g, b, a, dtail @ ..] if RGBA => {
if RGBA {
if let [r, g, b, a, dtail @ ..] = dtail {
px.update_rgba(*r, *g, *b, *a); px.update_rgba(*r, *g, *b, *a);
data = dtail; 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();
let run = ((b1 & 0x3f) as usize).min(pixels.len()); let run = ((b1 & 0x3f) as usize).min(pixels.len());

View file

@ -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, QOI_OP_RUN2}; use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE};
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_u16; let mut run = 0_u8;
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,20 +36,8 @@ 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 as u8 - 1))?; buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
run = 0; run = 0;
} }
} else { } else {
@ -57,18 +45,15 @@ 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 = if run == 1 && index_allowed { buf = buf.write_one(if run == 1 && index_allowed {
buf.write_one(QOI_OP_INDEX | hash_prev)? QOI_OP_INDEX | hash_prev
} else if N == 4 || run < 63 {
buf.write_one(QOI_OP_RUN | (run as u8 - 1))?
} else { } else {
let [b1, b2] = (run-1).to_be_bytes(); QOI_OP_RUN | (run - 1)
buf.write_many(&[QOI_OP_RUN2, b1, b2])? })?;
};
} }
#[cfg(feature = "reference")] #[cfg(feature = "reference")]
{ {
buf = buf.write_one(QOI_OP_RUN | (run as u8 - 1))?; buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
} }
run = 0; run = 0;
} }