From 1f71a8ab8b6091ec72219ab38ec8720ca4094edc Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 22 Jan 2024 21:40:45 +0700 Subject: [PATCH] optimize repeat --- Cargo.toml | 2 +- LICENSE | 2 +- src/lib.rs | 84 +++++++++++++++++++++++++++++++++++------------------- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eb51cd7..371ddf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fimg" -version = "0.4.31" +version = "0.4.32" authors = ["bend-n "] license = "MIT" edition = "2021" diff --git a/LICENSE b/LICENSE index 1fafc15..2f002a4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 bendn +Copyright (c) 2024 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 diff --git a/src/lib.rs b/src/lib.rs index dc99df2..07c3f67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ //! without the `real-show` feature, [`Image::show`] will save itself to your temp directory, which you may not want. //! - `default`: \[`save`, `scale`\]. #![feature( + maybe_uninit_write_slice, slice_swap_unchecked, generic_const_exprs, slice_as_chunks, @@ -71,7 +72,7 @@ missing_docs )] #![allow(clippy::zero_prefixed_literal, incomplete_features)] -use std::{num::NonZeroU32, ops::Range}; +use std::{mem::MaybeUninit, num::NonZeroU32, ops::Range}; mod affine; #[cfg(feature = "blur")] @@ -97,6 +98,28 @@ pub use cloner::ImageCloner; pub use overlay::{BlendingOverlay, ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt}; pub use r#dyn::DynImage; +trait CopyWithinUnchecked { + /// # Safety + /// + /// panicless version of [`[T]::copy_within`](`slice::copy_within`), where the slices cant overlap. this uses `memcpy`. + /// your slices must be in bounds. + /// this isnt a public function, so im not going to say exactly what "in bounds" meeans. + unsafe fn copy_within_unchecked(&mut self, src: Range, dest: usize); +} + +impl CopyWithinUnchecked for [T] { + unsafe fn copy_within_unchecked(&mut self, src: Range, dest: usize) { + let std::ops::Range { start, end } = src; + debug_assert!(dest <= self.len() - end - start, "dest is out of bounds"); + #[allow(clippy::multiple_unsafe_ops_per_block)] + // SAFETY: the caller better be good + unsafe { + let ptr = self.as_mut_ptr(); + std::ptr::copy_nonoverlapping(ptr.add(start), ptr.add(dest), end - start) + }; + } +} + /// like assert!(), but causes undefined behaviour at runtime when the condition is not met. /// /// # Safety @@ -129,7 +152,9 @@ impl Image<&[u8], 3> { /// ``` #[must_use = "function does not modify the original image"] pub unsafe fn repeated(&self, out_width: u32, out_height: u32) -> Image, 3> { - let mut img = Image::<_, 3>::alloc(out_width, out_height); + let mut img = Vec::with_capacity(3 * out_width as usize * out_height as usize); + debug_assert!(out_width % self.width() == 0); + debug_assert!(out_height % self.height() == 0); for y in 0..self.height() { // SAFETY: get one row of pixels let from = unsafe { @@ -137,25 +162,43 @@ impl Image<&[u8], 3> { .get_unchecked(self.at(0, y)..self.at(0, y) + (self.width() as usize * 3)) }; debug_assert_eq!(from.len(), self.width() as usize * 3); - let first = img.at(0, y)..img.at(self.width(), y); + let first = + ((y * out_width) as usize * 3)..((y * out_width + self.width()) as usize * 3); // SAFETY: put it in the output - let to = unsafe { img.buffer.get_unchecked_mut(first.clone()) }; + let to = unsafe { img.spare_capacity_mut().get_unchecked_mut(first.clone()) }; // copy it in - to.copy_from_slice(from); + unsafe { assert_unchecked!(to.len() == from.len()) }; + MaybeUninit::write_slice(to, from); + for x in 1..(out_width / self.width()) { - let section = img.at(x * self.width(), y); + let section = (y * out_width + x * self.width()) as usize * 3; // SAFETY: copy each row of the image one by one - unsafe { img.copy_within(first.clone(), section) }; + unsafe { + img.spare_capacity_mut() + .copy_within_unchecked(first.clone(), section) + }; } } - let first_row = 0..img.at(0, self.height()); + let first_row = 0..(self.height() * out_width) as usize * 3; for y in 1..(out_height / self.height()) { - let this_row = img.at(0, y * self.height()); + let this_row = (y * self.height() * out_width) as usize * 3; // SAFETY: copy entire blocks of image at a time - unsafe { img.copy_within(first_row.clone(), this_row) }; + unsafe { + img.spare_capacity_mut() + .copy_within_unchecked(first_row.clone(), this_row) + }; + } + // SAFETY: we init + unsafe { img.set_len(3 * out_width as usize * out_height as usize) }; + // SAFETY: ok + unsafe { + Image::new( + out_width.try_into().unwrap(), + out_height.try_into().unwrap(), + img, + ) } - img } } @@ -520,25 +563,6 @@ impl + AsRef<[u8]>, const CHANNELS: usize> Image { unsafe { self.buffer.as_mut().as_chunks_unchecked_mut::() } } - /// # Safety - /// - /// panicless version of [`[T]::copy_within`](`slice::copy_within`), where the slices cant overlap. this uses `memcpy`. - /// your slices must be in bounds. - /// this isnt a public function, so im not going to say exactly what "in bounds" meeans. - unsafe fn copy_within(&mut self, src: std::ops::Range, dest: usize) { - let std::ops::Range { start, end } = src; - debug_assert!( - dest <= self.bytes().len() - end - start, - "dest is out of bounds" - ); - #[allow(clippy::multiple_unsafe_ops_per_block)] - // SAFETY: the caller better be good - unsafe { - let ptr = self.buffer.as_mut().as_mut_ptr(); - std::ptr::copy_nonoverlapping(ptr.add(start), ptr.add(dest), end - start) - }; - } - /// Set the pixel at x, y /// /// # Safety