mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-21 18: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