mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-22 02:28:19 -06:00
init
This commit is contained in:
commit
62fbd6fd1a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
23
Cargo.toml
Normal file
23
Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "fimg"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["bend-n <bend.n@outlook.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
edition = "2021"
|
||||||
|
description = "fast image operations"
|
||||||
|
repository = "https://github.com/bend-n/fimg"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
png = { version = "0.17", features = ["unstable"], optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
save = ["png"]
|
||||||
|
default = ["save"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = 2
|
||||||
|
opt-level = 3
|
||||||
|
lto = "thin"
|
||||||
|
incremental = true
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 bendn
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
9
README.md
Normal file
9
README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# fimg
|
||||||
|
|
||||||
|
quick simple image operations
|
||||||
|
|
||||||
|
## supported operations
|
||||||
|
|
||||||
|
- [x] overlay
|
||||||
|
- [x] rotation
|
||||||
|
- [x] flipping
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
255
src/affine.rs
Normal file
255
src/affine.rs
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
use crate::{FromRefMut, Image};
|
||||||
|
|
||||||
|
pub trait Rotations {
|
||||||
|
/// Rotate a image 180 degrees clockwise.
|
||||||
|
fn rot_180(&mut self);
|
||||||
|
/// Rotate a image 90 degrees clockwise.
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// UB if the image is not square
|
||||||
|
unsafe fn rot_90(&mut self);
|
||||||
|
/// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise.
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// UB if the image is not square
|
||||||
|
unsafe fn rot_270(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Flips {
|
||||||
|
/// Flip a image vertically.
|
||||||
|
fn flip_v(&mut self);
|
||||||
|
|
||||||
|
/// Flip a image horizontally.
|
||||||
|
fn flip_h(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> Flips for Image<Vec<u8>, CHANNELS> {
|
||||||
|
fn flip_h(&mut self) {
|
||||||
|
self.as_mut().flip_h();
|
||||||
|
}
|
||||||
|
fn flip_v(&mut self) {
|
||||||
|
self.as_mut().flip_v();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> Flips for Image<&mut [u8], CHANNELS> {
|
||||||
|
fn flip_v(&mut self) {
|
||||||
|
for y in 0..self.height() / 2 {
|
||||||
|
for x in 0..self.width() {
|
||||||
|
let y2 = self.height() - y - 1;
|
||||||
|
// SAFETY: within bounds
|
||||||
|
let p2 = unsafe { self.pixel(x, y2) };
|
||||||
|
let p = unsafe { self.pixel(x, y) };
|
||||||
|
unsafe { self.set_pixel(x, y2, p) };
|
||||||
|
unsafe { self.set_pixel(x, y, p2) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flip_h(&mut self) {
|
||||||
|
for y in 0..self.height() {
|
||||||
|
for x in 0..self.width() / 2 {
|
||||||
|
let x2 = self.width() - x - 1;
|
||||||
|
let p2 = unsafe { self.pixel(x2, y) };
|
||||||
|
let p = unsafe { self.pixel(x, y) };
|
||||||
|
unsafe { self.set_pixel(x2, y, p) };
|
||||||
|
unsafe { self.set_pixel(x, y, p2) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> Rotations for Image<Vec<u8>, CHANNELS> {
|
||||||
|
fn rot_180(&mut self) {
|
||||||
|
self.as_mut().rot_180();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn rot_90(&mut self) {
|
||||||
|
unsafe { self.as_mut().rot_90() }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn rot_270(&mut self) {
|
||||||
|
unsafe { self.as_mut().rot_270() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> Rotations for Image<&mut [u8], CHANNELS> {
|
||||||
|
fn rot_180(&mut self) {
|
||||||
|
for y in 0..self.height() / 2 {
|
||||||
|
for x in 0..self.width() {
|
||||||
|
let p = unsafe { self.pixel(x, y) };
|
||||||
|
let x2 = self.width() - x - 1;
|
||||||
|
let y2 = self.height() - y - 1;
|
||||||
|
let p2 = unsafe { self.pixel(x2, y2) };
|
||||||
|
unsafe { self.set_pixel(x, y, p2) };
|
||||||
|
unsafe { self.set_pixel(x2, y2, p) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.height() % 2 != 0 {
|
||||||
|
let middle = self.height() / 2;
|
||||||
|
|
||||||
|
for x in 0..self.width() / 2 {
|
||||||
|
let p = unsafe { self.pixel(x, middle) };
|
||||||
|
let x2 = self.width() - x - 1;
|
||||||
|
let p2 = unsafe { self.pixel(x2, middle) };
|
||||||
|
unsafe { self.set_pixel(x, middle, p2) };
|
||||||
|
unsafe { self.set_pixel(x2, middle, p) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn rot_90(&mut self) {
|
||||||
|
// This is done by first flipping
|
||||||
|
self.flip_v();
|
||||||
|
// Then transposing the image, to save allocations.
|
||||||
|
// SAFETY: caller ensures rectangularity
|
||||||
|
unsafe { transpose(self) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn rot_270(&mut self) {
|
||||||
|
self.flip_h();
|
||||||
|
// SAFETY: caller ensures rectangularity
|
||||||
|
unsafe { transpose(self) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// UB if supplied image rectangular
|
||||||
|
unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
|
||||||
|
debug_assert_eq!(img.width(), img.height());
|
||||||
|
let size = img.width();
|
||||||
|
for i in 0..size {
|
||||||
|
for j in i..size {
|
||||||
|
for c in 0..CHANNELS {
|
||||||
|
// SAFETY: caller gurantees rectangularity
|
||||||
|
unsafe {
|
||||||
|
img.buffer.swap_unchecked(
|
||||||
|
(i * size + j) as usize * CHANNELS + c,
|
||||||
|
(j * size + i) as usize * CHANNELS + c,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::img;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rotate_90() {
|
||||||
|
let mut from = img![
|
||||||
|
[00, 01]
|
||||||
|
[02, 10]
|
||||||
|
];
|
||||||
|
unsafe { from.rot_90() };
|
||||||
|
assert_eq!(
|
||||||
|
from,
|
||||||
|
img![
|
||||||
|
[02, 00]
|
||||||
|
[10, 01]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rotate_180() {
|
||||||
|
let mut from = img![
|
||||||
|
[00, 01]
|
||||||
|
[02, 10]
|
||||||
|
];
|
||||||
|
from.rot_180();
|
||||||
|
assert_eq!(
|
||||||
|
from,
|
||||||
|
img![
|
||||||
|
[10, 02]
|
||||||
|
[01, 00]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rotate_270() {
|
||||||
|
let mut from = img![
|
||||||
|
[00, 01]
|
||||||
|
[20, 10]
|
||||||
|
];
|
||||||
|
unsafe { from.rot_270() };
|
||||||
|
assert_eq!(
|
||||||
|
from,
|
||||||
|
img![
|
||||||
|
[01, 10]
|
||||||
|
[00, 20]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flip_vertical() {
|
||||||
|
let mut from = img![
|
||||||
|
[90, 01]
|
||||||
|
[21, 42]
|
||||||
|
];
|
||||||
|
from.flip_v();
|
||||||
|
assert_eq!(
|
||||||
|
from,
|
||||||
|
img![
|
||||||
|
[21, 42]
|
||||||
|
[90, 01]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn flip_horizontal() {
|
||||||
|
let mut from = img![
|
||||||
|
[90, 01]
|
||||||
|
[21, 42]
|
||||||
|
];
|
||||||
|
from.flip_h();
|
||||||
|
assert_eq!(
|
||||||
|
from,
|
||||||
|
img![
|
||||||
|
[01, 90]
|
||||||
|
[42, 21]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod bench {
|
||||||
|
use super::*;
|
||||||
|
extern crate test;
|
||||||
|
use crate::Image;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
macro_rules! bench {
|
||||||
|
(fn $name: ident() { run $fn: ident() }) => {
|
||||||
|
#[bench]
|
||||||
|
fn $name(b: &mut Bencher) {
|
||||||
|
let mut img: Image<_, 4> = Image::new(
|
||||||
|
64.try_into().unwrap(),
|
||||||
|
64.try_into().unwrap(),
|
||||||
|
include_bytes!("../test_data/4_180x180.imgbuf").to_vec(),
|
||||||
|
);
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..256 {
|
||||||
|
img.flip_h();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bench!(fn flip_h() { run flip_h() });
|
||||||
|
bench!(fn flip_v() { run flip_v() });
|
||||||
|
bench!(fn rotate_90() { run rot_90() });
|
||||||
|
bench!(fn rotate_180() { run rot_180() });
|
||||||
|
bench!(fn rotate_270() { run rot_270() });
|
||||||
|
}
|
287
src/lib.rs
Normal file
287
src/lib.rs
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
#![feature(
|
||||||
|
slice_swap_unchecked,
|
||||||
|
unchecked_math,
|
||||||
|
portable_simd,
|
||||||
|
array_chunks,
|
||||||
|
test
|
||||||
|
)]
|
||||||
|
#![warn(
|
||||||
|
clippy::multiple_unsafe_ops_per_block,
|
||||||
|
clippy::missing_const_for_fn,
|
||||||
|
clippy::missing_safety_doc,
|
||||||
|
unsafe_op_in_unsafe_fn,
|
||||||
|
clippy::dbg_macro,
|
||||||
|
clippy::perf
|
||||||
|
)]
|
||||||
|
#![allow(clippy::zero_prefixed_literal)]
|
||||||
|
|
||||||
|
use std::{num::NonZeroU32, slice::SliceIndex};
|
||||||
|
|
||||||
|
mod affine;
|
||||||
|
mod overlay;
|
||||||
|
pub use affine::{Flips, Rotations};
|
||||||
|
pub use overlay::{Overlay, OverlayAt};
|
||||||
|
|
||||||
|
pub trait RepeatNew {
|
||||||
|
type Output;
|
||||||
|
/// Repeat self till it fills a new image of size x, y
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// UB if self's width is not a multiple of x, or self's height is not a multiple of y
|
||||||
|
unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_unchecked {
|
||||||
|
($cond:expr) => {{
|
||||||
|
if !$cond {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let _ = ::core::ptr::NonNull::<()>::dangling().as_ref(); // force unsafe wrapping block
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
panic!("assertion failed: {} returned false", stringify!($cond));
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
std::hint::unreachable_unchecked()
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
use assert_unchecked;
|
||||||
|
|
||||||
|
impl RepeatNew for Image<&[u8], 3> {
|
||||||
|
type Output = Image<Vec<u8>, 3>;
|
||||||
|
unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output {
|
||||||
|
let mut img = Image::alloc(x, y); // could probably optimize this a ton but eh
|
||||||
|
for x in 0..(x / self.width()) {
|
||||||
|
for y in 0..(y / self.height()) {
|
||||||
|
let a: &mut Image<&mut [u8], 3> = &mut img.as_mut();
|
||||||
|
// SAFETY: caller upholds
|
||||||
|
unsafe { a.overlay_at(self, x * self.width(), y * self.height()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize {
|
||||||
|
// y * w + x
|
||||||
|
let tmp = unsafe { (y as usize).unchecked_mul(w as usize) };
|
||||||
|
unsafe { tmp.unchecked_add(x as usize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Image<T, const CHANNELS: usize> {
|
||||||
|
pub buffer: T,
|
||||||
|
pub width: NonZeroU32,
|
||||||
|
pub height: NonZeroU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> Default for Image<&'static [u8], CHANNELS> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: &[0; CHANNELS],
|
||||||
|
width: NonZeroU32::new(1).unwrap(),
|
||||||
|
height: NonZeroU32::new(1).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
|
#[inline]
|
||||||
|
pub fn height(&self) -> u32 {
|
||||||
|
self.height.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn width(&self) -> u32 {
|
||||||
|
self.width.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self {
|
||||||
|
Image {
|
||||||
|
buffer,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn copy(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
buffer: self.buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// - UB if x, y is out of bounds
|
||||||
|
/// - UB if buffer is too small
|
||||||
|
#[inline]
|
||||||
|
unsafe fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> {
|
||||||
|
debug_assert!(x < self.width(), "x out of bounds");
|
||||||
|
debug_assert!(y < self.height(), "y out of bounds");
|
||||||
|
let index = unsafe { really_unsafe_index(x, y, self.width()) };
|
||||||
|
let index = unsafe { index.unchecked_mul(CHANNELS) };
|
||||||
|
debug_assert!(self.buffer.len() > index);
|
||||||
|
index..unsafe { index.unchecked_add(CHANNELS) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Returns a iterator over every pixel
|
||||||
|
pub fn chunked(&self) -> impl Iterator<Item = &[u8; CHANNELS]> {
|
||||||
|
// SAFETY: 0 sized images illegal
|
||||||
|
unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) };
|
||||||
|
// SAFETY: no half pixels!
|
||||||
|
unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) };
|
||||||
|
self.buffer.array_chunks::<CHANNELS>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a pixel at (x, y).
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// - UB if x, y is out of bounds
|
||||||
|
/// - UB if buffer is too small
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] {
|
||||||
|
let idx = unsafe { self.slice(x, y) };
|
||||||
|
let ptr = unsafe { self.buffer.get_unchecked(idx).as_ptr().cast() };
|
||||||
|
unsafe { *ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
|
/// Return a mutable reference to a pixel at (x, y).
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// - UB if x, y is out of bounds
|
||||||
|
/// - UB if buffer is too small
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] {
|
||||||
|
let idx = unsafe { self.slice(x, y) };
|
||||||
|
unsafe { self.buffer.get_unchecked_mut(idx) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Returns a iterator over every pixel, mutably
|
||||||
|
pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> {
|
||||||
|
// SAFETY: 0 sized images are not allowed
|
||||||
|
unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) };
|
||||||
|
// SAFETY: buffer cannot have half pixels
|
||||||
|
unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) };
|
||||||
|
self.buffer.array_chunks_mut::<CHANNELS>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the pixel at x, y
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// UB if x, y is out of bounds.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn set_pixel(&mut self, x: u32, y: u32, px: [u8; CHANNELS]) {
|
||||||
|
// SAFETY: Caller says that x, y is in bounds
|
||||||
|
let out = unsafe { self.pixel_mut(x, y) };
|
||||||
|
// SAFETY: px must be CHANNELS long
|
||||||
|
unsafe { std::ptr::copy_nonoverlapping(px.as_ptr(), out.as_mut_ptr(), CHANNELS) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FromRef<const CHANNELS: usize> {
|
||||||
|
/// Reference the buffer
|
||||||
|
fn as_ref(&self) -> Image<&[u8], CHANNELS>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FromRefMut<const CHANNELS: usize> {
|
||||||
|
/// Reference the buffer, mutably
|
||||||
|
fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> FromRef<CHANNELS> for Image<&mut [u8], CHANNELS> {
|
||||||
|
fn as_ref(&self) -> Image<&[u8], CHANNELS> {
|
||||||
|
Image::new(self.width, self.height, self.buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> FromRefMut<CHANNELS> for Image<&mut [u8], CHANNELS> {
|
||||||
|
fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> {
|
||||||
|
Image::new(self.width, self.height, self.buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> FromRef<CHANNELS> for Image<Vec<u8>, CHANNELS> {
|
||||||
|
fn as_ref(&self) -> Image<&[u8], CHANNELS> {
|
||||||
|
Image::new(self.width, self.height, &self.buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> FromRefMut<CHANNELS> for Image<Vec<u8>, CHANNELS> {
|
||||||
|
fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> {
|
||||||
|
Image::new(self.width, self.height, &mut self.buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
|
||||||
|
/// Allocates a new image
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// if width || height == 0
|
||||||
|
#[must_use]
|
||||||
|
pub fn alloc(width: u32, height: u32) -> Self {
|
||||||
|
Image {
|
||||||
|
width: width.try_into().unwrap(),
|
||||||
|
height: height.try_into().unwrap(),
|
||||||
|
buffer: vec![0; CHANNELS * width as usize * height as usize],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! save {
|
||||||
|
($channels:literal == $clr:ident ($clrhuman:literal)) => {
|
||||||
|
impl Image<&[u8], $channels> {
|
||||||
|
#[cfg(feature = "save")]
|
||||||
|
#[doc = "Save this "]
|
||||||
|
#[doc = $clrhuman]
|
||||||
|
#[doc = " image."]
|
||||||
|
pub fn save(&self, f: impl AsRef<std::path::Path>) {
|
||||||
|
let p = std::fs::File::create(f).unwrap();
|
||||||
|
let w = &mut std::io::BufWriter::new(p);
|
||||||
|
let mut enc = png::Encoder::new(w, self.width(), self.height());
|
||||||
|
enc.set_color(png::ColorType::$clr);
|
||||||
|
enc.set_depth(png::BitDepth::Eight);
|
||||||
|
enc.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2));
|
||||||
|
enc.set_source_chromaticities(png::SourceChromaticities::new(
|
||||||
|
(0.31270, 0.32900),
|
||||||
|
(0.64000, 0.33000),
|
||||||
|
(0.30000, 0.60000),
|
||||||
|
(0.15000, 0.06000),
|
||||||
|
));
|
||||||
|
let mut writer = enc.write_header().unwrap();
|
||||||
|
writer.write_image_data(self.buffer).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
save!(3 == Rgb("RGB"));
|
||||||
|
save!(4 == Rgba("RGBA"));
|
||||||
|
save!(2 == GrayscaleAlpha("YA"));
|
||||||
|
save!(1 == Grayscale("Y"));
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
macro_rules! img {
|
||||||
|
[[$($v:literal),+] [$($v2:literal),+]] => {{
|
||||||
|
let from: Image<Vec<u8>, 1> = Image::new(
|
||||||
|
2.try_into().unwrap(),
|
||||||
|
2.try_into().unwrap(),
|
||||||
|
vec![$($v,)+ $($v2,)+]
|
||||||
|
);
|
||||||
|
from
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
use img;
|
250
src/overlay.rs
Normal file
250
src/overlay.rs
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
use super::{assert_unchecked, really_unsafe_index, Image};
|
||||||
|
use std::simd::SimdInt;
|
||||||
|
use std::simd::SimdPartialOrd;
|
||||||
|
use std::simd::{simd_swizzle, Simd};
|
||||||
|
|
||||||
|
pub trait OverlayAt<W> {
|
||||||
|
/// Overlay with => self at coordinates x, y, without blending
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// UB if x, y is out of bounds
|
||||||
|
unsafe fn overlay_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Overlay<W> {
|
||||||
|
/// Overlay with => self (does not blend)
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// UB if a.width != b.width || a.height != b.height
|
||||||
|
unsafe fn overlay(&mut self, with: &W) -> &mut Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn blit(rgb: &mut [u8], rgba: &[u8]) {
|
||||||
|
const LAST4: Simd<u8, 16> = Simd::from_array([
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mut srci = 0;
|
||||||
|
let mut dsti = 0;
|
||||||
|
while dsti + 16 <= rgb.len() {
|
||||||
|
let old: Simd<u8, 16> = Simd::from_slice(unsafe { rgb.get_unchecked(dsti..dsti + 16) });
|
||||||
|
let new: Simd<u8, 16> = Simd::from_slice(unsafe { rgba.get_unchecked(srci..srci + 16) });
|
||||||
|
|
||||||
|
let threshold = new.simd_ge(Simd::splat(128)).to_int().cast::<u8>();
|
||||||
|
let mut mask = simd_swizzle!(
|
||||||
|
threshold,
|
||||||
|
[3, 3, 3, 7, 7, 7, 11, 11, 11, 15, 15, 15, 0, 0, 0, 0]
|
||||||
|
);
|
||||||
|
mask &= LAST4;
|
||||||
|
|
||||||
|
let new_rgb = simd_swizzle!(new, [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0, 0, 0, 0]);
|
||||||
|
let blended = (new_rgb & mask) | (old & !mask);
|
||||||
|
blended.copy_to_slice(unsafe { rgb.get_unchecked_mut(dsti..dsti + 16) });
|
||||||
|
|
||||||
|
srci += 16;
|
||||||
|
dsti += 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
while dsti + 3 <= rgb.len() {
|
||||||
|
if unsafe { *rgba.get_unchecked(srci + 3) } >= 128 {
|
||||||
|
let src = unsafe { rgba.get_unchecked(srci..srci + 3) };
|
||||||
|
let end = unsafe { rgb.get_unchecked_mut(dsti..dsti + 3) };
|
||||||
|
unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), end.as_mut_ptr(), 3) };
|
||||||
|
}
|
||||||
|
|
||||||
|
srci += 4;
|
||||||
|
dsti += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> {
|
||||||
|
#[inline]
|
||||||
|
unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self {
|
||||||
|
debug_assert!(self.width() == with.width());
|
||||||
|
debug_assert!(self.height() == with.height());
|
||||||
|
for (i, other_pixels) in with.chunked().enumerate() {
|
||||||
|
if other_pixels[3] >= 128 {
|
||||||
|
let idx_begin = unsafe { i.unchecked_mul(4) };
|
||||||
|
let idx_end = unsafe { idx_begin.unchecked_add(4) };
|
||||||
|
let own_pixels = unsafe { self.buffer.get_unchecked_mut(idx_begin..idx_end) };
|
||||||
|
unsafe {
|
||||||
|
std::ptr::copy_nonoverlapping(
|
||||||
|
other_pixels.as_ptr(),
|
||||||
|
own_pixels.as_mut_ptr(),
|
||||||
|
4,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> {
|
||||||
|
#[inline]
|
||||||
|
unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self {
|
||||||
|
// SAFETY: caller upholds these
|
||||||
|
unsafe { assert_unchecked!(x + with.width() <= self.width()) };
|
||||||
|
unsafe { assert_unchecked!(y + with.height() <= self.height()) };
|
||||||
|
for j in 0..with.height() {
|
||||||
|
let i_x = j as usize * with.width() as usize * 4
|
||||||
|
..(j as usize + 1) * with.width() as usize * 4;
|
||||||
|
let o_x = ((j as usize + y as usize) * self.width() as usize + x as usize) * 3
|
||||||
|
..((j as usize + y as usize) * self.width() as usize
|
||||||
|
+ x as usize
|
||||||
|
+ with.width() as usize)
|
||||||
|
* 3;
|
||||||
|
let rgb = unsafe { self.buffer.get_unchecked_mut(o_x) };
|
||||||
|
let rgba = unsafe { with.buffer.get_unchecked(i_x) };
|
||||||
|
unsafe { blit(rgb, rgba) }
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayAt<Image<&[u8], 3>> for Image<&mut [u8], 3> {
|
||||||
|
#[inline]
|
||||||
|
unsafe fn overlay_at(&mut self, with: &Image<&[u8], 3>, x: u32, y: u32) -> &mut Self {
|
||||||
|
macro_rules! o3x3 {
|
||||||
|
($n:expr) => {{
|
||||||
|
for j in 0..($n as usize) {
|
||||||
|
let i_x = j * ($n as usize) * 3..(j + 1) * ($n as usize) * 3;
|
||||||
|
let o_x = ((j + y as usize) * self.width() as usize + x as usize) * 3
|
||||||
|
..((j + y as usize) * self.width() as usize + x as usize + ($n as usize))
|
||||||
|
* 3;
|
||||||
|
let a = unsafe { self.buffer.get_unchecked_mut(o_x) };
|
||||||
|
let b = unsafe { with.buffer.get_unchecked(i_x) };
|
||||||
|
a.copy_from_slice(b);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
// let it unroll
|
||||||
|
match with.width() {
|
||||||
|
8 => o3x3!(8),
|
||||||
|
16 => o3x3!(16), // this branch makes 8x8 0.16 times slower; but 16x16 0.2 times faster.
|
||||||
|
_ => o3x3!(with.width()),
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> {
|
||||||
|
#[inline]
|
||||||
|
unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self {
|
||||||
|
debug_assert!(self.width() == with.width());
|
||||||
|
debug_assert!(self.height() == with.height());
|
||||||
|
for (i, chunk) in with
|
||||||
|
.buffer
|
||||||
|
.chunks_exact(with.width() as usize * 4)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let rgb = unsafe {
|
||||||
|
self.buffer.get_unchecked_mut(
|
||||||
|
i * with.width() as usize * 3..(i + 1) * with.width() as usize * 3,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
unsafe { blit(rgb, chunk) };
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 4> {
|
||||||
|
#[inline]
|
||||||
|
unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self {
|
||||||
|
for j in 0..with.height() {
|
||||||
|
for i in 0..with.width() {
|
||||||
|
let index = unsafe { really_unsafe_index(i, j, with.width()) };
|
||||||
|
let their_px = unsafe { with.buffer.get_unchecked(index * 4..index * 4 + 4) };
|
||||||
|
if unsafe { *their_px.get_unchecked(3) } >= 128 {
|
||||||
|
let x = unsafe { i.unchecked_add(x) };
|
||||||
|
let y = unsafe { j.unchecked_add(y) };
|
||||||
|
let index = unsafe { really_unsafe_index(x, y, self.width()) };
|
||||||
|
let our_px = unsafe { self.buffer.get_unchecked_mut(index * 4..index * 4 + 4) };
|
||||||
|
our_px.copy_from_slice(their_px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod bench {
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{FromRef, FromRefMut};
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn overlay_3on3at(bench: &mut Bencher) {
|
||||||
|
let mut v = vec![0u8; 3 * 64 * 64];
|
||||||
|
let mut a: Image<_, 3> = Image::new(
|
||||||
|
64.try_into().unwrap(),
|
||||||
|
64.try_into().unwrap(),
|
||||||
|
v.as_mut_slice(),
|
||||||
|
);
|
||||||
|
let b = Image::<&[u8], 3>::new(
|
||||||
|
4.try_into().unwrap(),
|
||||||
|
4.try_into().unwrap(),
|
||||||
|
*&include_bytes!("../test_data/3_4x4.imgbuf"),
|
||||||
|
);
|
||||||
|
bench.iter(|| unsafe {
|
||||||
|
for x in 0..16 {
|
||||||
|
for y in 0..16 {
|
||||||
|
a.overlay_at(&b, x * 4, y * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
a.as_ref().buffer,
|
||||||
|
include_bytes!("../test_results/3x3_at_out.buf")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn overlay_4on3at(bench: &mut Bencher) {
|
||||||
|
let mut a: Image<_, 3> = Image::alloc(64, 64);
|
||||||
|
let b = Image::<&[u8], 4>::new(
|
||||||
|
4.try_into().unwrap(),
|
||||||
|
4.try_into().unwrap(),
|
||||||
|
*&include_bytes!("../test_data/4_4x4.imgbuf"),
|
||||||
|
);
|
||||||
|
bench.iter(|| unsafe {
|
||||||
|
for x in 0..16 {
|
||||||
|
for y in 0..16 {
|
||||||
|
a.as_mut().overlay_at(&b, x * 4, y * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
a.as_ref().buffer,
|
||||||
|
include_bytes!("../test_results/4x3_at_out.buf")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn overlay_4on4at(bench: &mut Bencher) {
|
||||||
|
let mut a: Image<_, 4> = Image::alloc(64, 64);
|
||||||
|
let b = Image::<&[u8], 4>::new(
|
||||||
|
4.try_into().unwrap(),
|
||||||
|
4.try_into().unwrap(),
|
||||||
|
*&include_bytes!("../test_data/4_4x4.imgbuf"),
|
||||||
|
);
|
||||||
|
bench.iter(|| unsafe {
|
||||||
|
for x in 0..16 {
|
||||||
|
for y in 0..16 {
|
||||||
|
a.as_mut().overlay_at(&b, x * 4, y * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
a.as_ref().buffer,
|
||||||
|
include_bytes!("../test_results/4x4_at_out.buf")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1
test_data/3_4x4.imgbuf
Normal file
1
test_data/3_4x4.imgbuf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ<EFBFBD>
|
BIN
test_data/4_180x180.imgbuf
Normal file
BIN
test_data/4_180x180.imgbuf
Normal file
Binary file not shown.
1
test_data/4_4x4.imgbuf
Normal file
1
test_data/4_4x4.imgbuf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>巍<EFBFBD><EFBFBD>
|
1
test_results/3x3_at_out.buf
Normal file
1
test_results/3x3_at_out.buf
Normal file
File diff suppressed because one or more lines are too long
1
test_results/4x3_at_out.buf
Normal file
1
test_results/4x3_at_out.buf
Normal file
File diff suppressed because one or more lines are too long
1
test_results/4x4_at_out.buf
Normal file
1
test_results/4x4_at_out.buf
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue