Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 52 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,77 @@ on:
name: CI
env:
CARGO_TERM_COLOR: always
HOST: x86_64-unknown-linux-gnu
RUSTFLAGS: "-D warnings"
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable, beta, nightly, 1.62.0] # MSRV=1.62
rust: [stable, beta, nightly, 1.67.0]
steps:
- uses: actions/checkout@v2
with: {submodules: true}
with: { submodules: true }
- uses: actions-rs/toolchain@v1
with: {profile: minimal, toolchain: '${{ matrix.rust }}', override: true}
with:
{ profile: minimal, toolchain: "${{ matrix.rust }}", override: true }
- run: cargo test
tests-s390x:
runs-on: ubuntu-latest
strategy:
matrix:
features: [default, reference]
steps:
- uses: actions/checkout@v2
with: { submodules: true }
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
target: s390x-unknown-linux-gnu
components: rust-src
- run: sudo apt-get update && sudo apt-get install -y qemu-user-static gcc-s390x-linux-gnu
- run: |
if [ "${{ matrix.features }}" = "reference" ]; then
echo "FEATURES=alloc,reference" >> $GITHUB_ENV
else
echo "FEATURES=alloc" >> $GITHUB_ENV
fi
- run: |
cargo build \
--target s390x-unknown-linux-gnu \
--tests \
-Zbuild-std \
--no-default-features \
--features=${FEATURES}
env:
CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER: "s390x-linux-gnu-gcc"
RUSTFLAGS: "-D warnings -C target-feature=+crt-static"
- run: |
failed=0
for test in ./target/s390x-unknown-linux-gnu/debug/deps/*-[0-9a-f][0-9a-f]*; do
if [ -x "$test" ]; then
echo "Running $test"
qemu-s390x-static "$test" || failed=1
fi
done
exit $failed
reference:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with: {submodules: true}
with: { submodules: true }
- uses: actions-rs/toolchain@v1
with: {profile: minimal, toolchain: stable, override: true}
with: { profile: minimal, toolchain: stable, override: true }
- run: cargo test --features=reference
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with: {profile: minimal, toolchain: beta, override: true, components: clippy}
with:
profile: minimal
toolchain: beta
override: true
components: clippy
- run: cargo clippy
28 changes: 16 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,34 @@ homepage = "https://github.com/aldanor/qoi-rust"
documentation = "https://docs.rs/qoi"
categories = ["multimedia::images", "multimedia::encoding"]
keywords = ["qoi", "graphics", "image", "encoding"]
exclude = [
"assets/*",
]
rust-version = "1.62.0"
exclude = ["assets/*"]
rust-version = "1.67.0"

[features]
default = ["std"]
alloc = [] # provides access to `Vec` without enabling `std` mode
std = [] # std mode (enabled by default) - provides access to `std::io`, `Error` and `Vec`
reference = [] # follows reference encoder implementation precisely, but may be slightly slower
# provides access to `Vec` without enabling `std` mode
alloc = []
# std mode (enabled by default) - provides access to `std::io`, `Error` and `Vec`
std = []
# follows reference encoder implementation precisely, but may be slower
reference = []

[dependencies]
bytemuck = "1.12"
bytemuck = "1.22"

[workspace]
members = ["libqoi", "bench"]

[dev-dependencies]
# external
anyhow = "1.0"
png = "0.17"
walkdir = "2.3"
cfg-if = "1.0"
rand = "0.8"
libqoi = { path = "libqoi"}
png = "0.17"
rand = "0.9"
rand_distr = "0.5"
walkdir = "2.5"
# internal
libqoi = { path = "libqoi" }

[lib]
name = "qoi"
Expand Down
2 changes: 1 addition & 1 deletion libqoi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() {
.include("../ext/qoi")
.define("QOI_NO_STDIO", None)
.define("QOI_IMPLEMENTATION", None)
.flag("-Wno-unsequenced")
.flag_if_supported("-Wno-unsequenced")
.opt_level(3)
.compile("qoi");
}
4 changes: 1 addition & 3 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use_small_heuristics = "Max"
use_field_init_shorthand = true
use_try_shorthand = true
empty_item_single_line = true
edition = "2018"
unstable_features = true
fn_args_layout = "Compressed"
fn_params_layout = "Compressed"
2 changes: 1 addition & 1 deletion src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl<'a> Bytes<'a> {
}
}

impl<'a> Reader for Bytes<'a> {
impl Reader for Bytes<'_> {
#[inline]
fn decode_header(&mut self) -> Result<Header> {
let header = Header::decode(self.0)?;
Expand Down
4 changes: 2 additions & 2 deletions src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl Header {

/// Serializes the header into a bytes array.
#[inline]
pub(crate) fn encode(&self) -> [u8; QOI_HEADER_SIZE] {
pub fn encode(&self) -> [u8; QOI_HEADER_SIZE] {
let mut out = [0; QOI_HEADER_SIZE];
out[..4].copy_from_slice(&QOI_MAGIC.to_be_bytes());
out[4..8].copy_from_slice(&self.width.to_be_bytes());
Expand All @@ -79,7 +79,7 @@ impl Header {

/// Deserializes the header from a byte array.
#[inline]
pub(crate) fn decode(data: impl AsRef<[u8]>) -> Result<Self> {
pub fn decode(data: impl AsRef<[u8]>) -> Result<Self> {
let data = data.as_ref();
if unlikely(data.len() < QOI_HEADER_SIZE) {
return Err(Error::UnexpectedBufferEnd);
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
clippy::module_name_repetitions,
clippy::cargo_common_metadata,
clippy::doc_markdown,
clippy::return_self_not_must_use,
clippy::return_self_not_must_use
)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
#[cfg(all(feature = "alloc", not(any(feature = "std", test))))]
Expand Down
16 changes: 11 additions & 5 deletions src/pixel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,12 @@ impl<const N: usize> Pixel<N> {
{
// credits for the initial idea: @zakarumych
let v = if N == 4 {
u32::from_ne_bytes(cast(self.0))
u32::from_le_bytes(cast(self.0))
} else {
u32::from_ne_bytes([self.0[0], self.0[1], self.0[2], 0xff])
u32::from_le_bytes([self.0[0], self.0[1], self.0[2], 0xff])
} as u64;
let s = ((v & 0xff00_ff00) << 32) | (v & 0x00ff_00ff);
s.wrapping_mul(0x0300_0700_0005_000b_u64).to_le().swap_bytes() as u8 & 63
(s.wrapping_mul(0x0300_0700_0005_000b_u64) >> 56) as u8 & 63
}

#[inline]
Expand All @@ -152,11 +152,11 @@ impl<const N: usize> Pixel<N> {
let (vr_2, vg_2, vb_2) =
(vr.wrapping_add(2), vg.wrapping_add(2), vb.wrapping_add(2));
if vr_2 | vg_2 | vb_2 | 3 == 3 {
buf.write_one(QOI_OP_DIFF | vr_2 << 4 | vg_2 << 2 | vb_2)
buf.write_one(QOI_OP_DIFF | (vr_2 << 4) | (vg_2 << 2) | vb_2)
} else {
let (vg_r_8, vg_b_8) = (vg_r.wrapping_add(8), vg_b.wrapping_add(8));
if vg_r_8 | vg_b_8 | 15 == 15 {
buf.write_many(&[QOI_OP_LUMA | vg_32, vg_r_8 << 4 | vg_b_8])
buf.write_many(&[QOI_OP_LUMA | vg_32, (vg_r_8 << 4) | vg_b_8])
} else {
buf.write_many(&[QOI_OP_RGB, self.r(), self.g(), self.b()])
}
Expand All @@ -170,6 +170,12 @@ impl<const N: usize> Pixel<N> {
}
}

impl<const N: usize> Default for Pixel<N> {
fn default() -> Self {
Self::new()
}
}

impl<const N: usize> From<Pixel<N>> for [u8; N] {
#[inline(always)]
fn from(px: Pixel<N>) -> Self {
Expand Down
2 changes: 1 addition & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl<'a> BytesMut<'a> {
}
}

impl<'a> Writer for BytesMut<'a> {
impl Writer for BytesMut<'_> {
#[inline]
fn write_one(self, v: u8) -> Result<Self> {
Ok(BytesMut::write_one(self, v))
Expand Down
40 changes: 18 additions & 22 deletions tests/test_gen.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
#![cfg_attr(target_endian = "big", allow(unused_imports, dead_code))]

mod common;

use bytemuck::cast_slice;
use std::borrow::Cow;
use std::fmt::Debug;

use cfg_if::cfg_if;
use rand::{
distributions::{Distribution, Standard},
rngs::StdRng,
Rng, SeedableRng,
};
use rand::{rngs::StdRng, Rng, SeedableRng};

use libqoi::{qoi_decode, qoi_encode};
use qoi::consts::{
Expand Down Expand Up @@ -46,7 +44,7 @@ impl<const N: usize> GenState<N> {
}

pub fn pick_from_index(&self, rng: &mut impl Rng) -> [u8; N] {
self.index[rng.gen_range(0_usize..64)]
self.index[rng.random_range(0_usize..64)]
}

pub fn zero() -> [u8; N] {
Expand All @@ -68,7 +66,7 @@ struct ImageGen {

impl ImageGen {
pub fn new_random(rng: &mut impl Rng) -> Self {
let p: [f64; 6] = rng.gen();
let p: [f64; 6] = rng.random();
let t = p.iter().sum::<f64>();
Self {
p_new: p[0] / t,
Expand All @@ -87,18 +85,15 @@ impl ImageGen {
}
}

fn generate_const<R: Rng, const N: usize>(&self, rng: &mut R, min_len: usize) -> Vec<u8>
where
Standard: Distribution<[u8; N]>,
{
fn generate_const<R: Rng, const N: usize>(&self, rng: &mut R, min_len: usize) -> Vec<u8> {
let mut s = GenState::<N>::with_capacity(min_len);
let zero = GenState::<N>::zero();

while s.len < min_len {
let mut p = rng.gen_range(0.0..1.0);
let mut p = rng.random_range(0.0..1.0);

if p < self.p_new {
s.write(rng.gen());
s.write(rng.random());
continue;
}
p -= self.p_new;
Expand All @@ -112,7 +107,7 @@ impl ImageGen {

if p < self.p_repeat {
let px = s.prev;
let n_repeat = rng.gen_range(1_usize..=70);
let n_repeat = rng.random_range(1_usize..=70);
for _ in 0..n_repeat {
s.write(px);
}
Expand All @@ -122,19 +117,19 @@ impl ImageGen {

if p < self.p_diff {
let mut px = s.prev;
px[0] = px[0].wrapping_add(rng.gen_range(0_u8..4).wrapping_sub(2));
px[1] = px[1].wrapping_add(rng.gen_range(0_u8..4).wrapping_sub(2));
px[2] = px[2].wrapping_add(rng.gen_range(0_u8..4).wrapping_sub(2));
px[0] = px[0].wrapping_add(rng.random_range(0_u8..4).wrapping_sub(2));
px[1] = px[1].wrapping_add(rng.random_range(0_u8..4).wrapping_sub(2));
px[2] = px[2].wrapping_add(rng.random_range(0_u8..4).wrapping_sub(2));
s.write(px);
continue;
}
p -= self.p_diff;

if p < self.p_luma {
let mut px = s.prev;
let vg = rng.gen_range(0_u8..64).wrapping_sub(32);
let vr = rng.gen_range(0_u8..16).wrapping_sub(8).wrapping_add(vg);
let vb = rng.gen_range(0_u8..16).wrapping_sub(8).wrapping_add(vg);
let vg = rng.random_range(0_u8..64).wrapping_sub(32);
let vr = rng.random_range(0_u8..16).wrapping_sub(8).wrapping_add(vg);
let vb = rng.random_range(0_u8..16).wrapping_sub(8).wrapping_add(vg);
px[0] = px[0].wrapping_add(vr);
px[1] = px[1].wrapping_add(vg);
px[2] = px[2].wrapping_add(vb);
Expand Down Expand Up @@ -276,13 +271,14 @@ fn check_roundtrip<E, D, VE, VD, EE, ED>(
}

#[test]
#[cfg(target_endian = "little")] // takes too long on big-endian
fn test_generated() {
let mut rng = StdRng::seed_from_u64(0);

let mut n_pixels = 0;
while n_pixels < 20_000_000 {
let min_len = rng.gen_range(1..=5000);
let channels = rng.gen_range(3..=4);
let min_len = rng.random_range(1..=5000);
let channels = rng.random_range(3..=4);
let gen = ImageGen::new_random(&mut rng);
let img = gen.generate(&mut rng, channels, min_len);

Expand Down
26 changes: 25 additions & 1 deletion tests/test_misc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
use qoi::{
consts::{QOI_OP_RGB, QOI_OP_RUN},
decode_to_vec, Channels, ColorSpace, Header, Result,
};

#[test]
fn test_new_encoder() {
// this used to fail due to `Bytes` not being `pub`
let arr = [0u8];
let _ = qoi::Decoder::new(&arr[..]);
}
}

#[test]
fn test_start_with_qoi_op_run() -> Result<()> {
let header = Header::try_new(3, 1, Channels::Rgba, ColorSpace::Linear)?;
let mut qoi_data: Vec<_> = header.encode().into_iter().collect();
qoi_data.extend([QOI_OP_RUN | 1, QOI_OP_RGB, 10, 20, 30]);
qoi_data.extend([0; 7]);
qoi_data.push(1);
let (_, decoded) = decode_to_vec(&qoi_data)?;
assert_eq!(decoded, vec![0, 0, 0, 255, 0, 0, 0, 255, 10, 20, 30, 255]);
Ok(())
}

#[cfg(target_endian = "big")]
#[test]
fn test_big_endian() {
// so we can see it in the CI logs
assert_eq!(u16::to_be_bytes(1), [0, 1]);
}
Loading
Loading