This commit is contained in:
bendn 2023-09-05 06:45:23 +07:00
commit 62fbd6fd1a
No known key found for this signature in database
GPG key ID: 0D9D3A2A3B2A93D6
14 changed files with 854 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

23
Cargo.toml Normal file
View 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
View 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
View file

@ -0,0 +1,9 @@
# fimg
quick simple image operations
## supported operations
- [x] overlay
- [x] rotation
- [x] flipping

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

255
src/affine.rs Normal file
View 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
View 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
View 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
View file

@ -0,0 +1 @@
モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD><EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ桜ヒ、<EFBFBD>、モョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ砕ョ<EFBFBD>

BIN
test_data/4_180x180.imgbuf Normal file

Binary file not shown.

1
test_data/4_4x4.imgbuf Normal file
View 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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long