pub mod geom;
#[cfg(feature = "image")]
mod image;
pub mod slice;
mod view;
pub mod prelude {
#[cfg(feature = "image")]
pub use crate::ToBlitBuffer;
pub use crate::{
geom::{Size, SubRect},
slice::Slice,
Blit, BlitBuffer,
};
}
use geom::{Size, SubRect};
use std::ops::Range;
use num_traits::ToPrimitive;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use slice::{Slice, SliceProjection};
use view::ImageView;
type Color = u32;
pub trait Blit {
fn blit(&self, dst: &mut [u32], dst_size: Size, options: &BlitOptions);
}
pub trait ToBlitBuffer {
fn to_blit_buffer_with_alpha(&self, alpha_treshold: u8) -> BlitBuffer;
fn to_blit_buffer_with_mask_color(&self, mask_color: u32) -> BlitBuffer;
}
#[derive(Debug, Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BlitOptions {
pub x: i32,
pub y: i32,
pub area: Option<Size>,
pub sub_rect: Option<SubRect>,
pub mask: Option<SubRect>,
pub vertical_slice: Option<Slice>,
pub horizontal_slice: Option<Slice>,
}
impl BlitOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn new_position<X, Y>(x: X, y: Y) -> Self
where
X: ToPrimitive,
Y: ToPrimitive,
{
let (x, y) = (x.to_i32().unwrap_or(0), y.to_i32().unwrap_or(0));
Self {
x,
y,
..Default::default()
}
}
#[must_use]
pub fn new_position_tuple<X, Y>((x, y): (X, Y)) -> Self
where
X: ToPrimitive,
Y: ToPrimitive,
{
let (x, y) = (x.to_i32().unwrap_or(0), y.to_i32().unwrap_or(0));
Self {
x,
y,
..Default::default()
}
}
#[must_use]
pub fn with_area<S>(mut self, area: S) -> Self
where
S: Into<Size>,
{
self.set_area(area.into());
self
}
#[must_use]
pub fn with_mask<R>(mut self, mask: R) -> Self
where
R: Into<SubRect>,
{
self.set_mask(mask.into());
self
}
#[must_use]
pub fn with_sub_rect<R>(mut self, sub_rect: R) -> Self
where
R: Into<SubRect>,
{
self.set_sub_rect(sub_rect.into());
self
}
#[must_use]
pub fn with_slice9<R>(mut self, center: R) -> Self
where
R: Into<SubRect>,
{
self.set_slice9(center);
self
}
pub fn with_horizontal_slice(mut self, slice: Slice) -> Self {
self.horizontal_slice = Some(slice);
self
}
pub fn with_vertical_slice(mut self, slice: Slice) -> Self {
self.vertical_slice = Some(slice);
self
}
#[must_use]
pub fn with_position<X, Y>(mut self, x: X, y: Y) -> Self
where
X: ToPrimitive,
Y: ToPrimitive,
{
self.x = x.to_i32().unwrap_or_default();
self.y = y.to_i32().unwrap_or_default();
self
}
pub fn set_position<P>(&mut self, position: P)
where
P: Into<(i32, i32)>,
{
let (x, y) = position.into();
self.x = x;
self.y = y;
}
pub fn position(&self) -> (i32, i32) {
(self.x, self.y)
}
pub fn area<S>(&self, source_size: S) -> Size
where
S: Into<Size>,
{
self.area.unwrap_or(source_size.into())
}
pub fn set_sub_rect<R>(&mut self, sub_rect: R)
where
R: Into<SubRect>,
{
let sub_rect = sub_rect.into();
self.sub_rect = Some(sub_rect);
if self.area.is_none() {
self.area = Some(sub_rect.size);
}
}
pub fn sub_rect<S>(&self, source_size: S) -> SubRect
where
S: Into<Size>,
{
let mut sub_rect = self
.sub_rect
.unwrap_or_else(|| SubRect::from_size(source_size));
sub_rect.size = match self.area {
Some(area) => sub_rect.size.min(area),
None => sub_rect.size,
};
sub_rect
}
pub fn set_area<S>(&mut self, area: S)
where
S: Into<Size>,
{
self.area = Some(area.into());
}
pub fn set_slice9<R>(&mut self, center: R)
where
R: Into<SubRect>,
{
let center = center.into();
self.vertical_slice = Some(Slice::ternary(center.x, center.right()));
self.horizontal_slice = Some(Slice::ternary(center.y, center.bottom()));
}
pub fn set_mask<R>(&mut self, mask: R)
where
R: Into<SubRect>,
{
self.mask = Some(mask.into());
}
pub fn set_horizontal_slice(&mut self, slice: Slice) {
self.horizontal_slice = Some(slice);
}
pub fn set_vertical_slice(&mut self, slice: Slice) {
self.vertical_slice = Some(slice);
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone)]
pub struct BlitBuffer {
size: Size,
data: Vec<Color>,
}
impl BlitBuffer {
#[must_use]
pub fn from_buffer<S>(src: &[Color], width: S, alpha_treshold: u8) -> Self
where
S: ToPrimitive,
{
Self::from_iter(src.iter().copied(), width, alpha_treshold)
}
#[must_use]
pub fn from_iter<I, S>(iter: I, width: S, alpha_treshold: u8) -> Self
where
I: Iterator<Item = Color>,
S: ToPrimitive,
{
let alpha_treshold = (alpha_treshold as Color) << 24;
let data = iter
.map(|pixel| {
if pixel < alpha_treshold {
0x00_00_00_00
} else {
pixel | 0xFF_00_00_00
}
})
.collect::<Vec<_>>();
let size = Size::from_len(data.len(), width.to_usize().unwrap_or_default());
Self { size, data }
}
pub fn width(&self) -> u32 {
self.size.width
}
pub fn height(&self) -> u32 {
self.size.height
}
pub fn size(&self) -> Size {
self.size
}
pub fn pixels(&self) -> &[Color] {
&self.data
}
pub fn pixels_mut(&mut self) -> &mut [Color] {
&mut self.data
}
fn slice_projections(
&self,
options: &BlitOptions,
target_area: Size,
) -> Vec<(SubRect, SubRect)> {
match (options.vertical_slice, options.horizontal_slice) {
(None, None) => Vec::new(),
(None, Some(horizontal)) => horizontal
.divide_area_iter(self.height(), target_area.height)
.map(|horizontal| horizontal.into_sub_rects_static_x(self.width()))
.collect(),
(Some(vertical), None) => vertical
.divide_area_iter(self.width(), target_area.width)
.map(|vertical| vertical.into_sub_rects_static_y(self.height()))
.collect(),
(Some(vertical), Some(horizontal)) => {
let horizontal_ranges = vertical
.divide_area_iter(self.width(), target_area.width)
.collect::<Vec<_>>();
let vertical_ranges =
horizontal.divide_area_iter(self.height(), target_area.height);
vertical_ranges
.flat_map(|vertical| {
horizontal_ranges.iter().map(move |horizontal| {
SliceProjection::combine_into_sub_rects(horizontal, &vertical)
})
})
.collect()
}
}
}
fn blit_slice(&self, dst: &mut [u32], dst_size: Size, options: &BlitOptions) {
if options.x == 0 && options.y == 0 && dst_size == self.size {
let pixels = dst_size.pixels();
self.blit_horizontal(dst, 0..pixels, 0..pixels);
return;
}
let dst_view = ImageView::full(dst_size);
let src_view = ImageView::full(self.size);
let area = options.area(self.size);
let mut dst_area = match dst_view.sub_i32(options.x, options.y, area) {
Some(dst_area) => dst_area,
None => return,
};
let mut sub_rect_view = match src_view.sub(options.sub_rect(self.size)) {
Some(sub_rect_view) => sub_rect_view,
None => return,
};
if sub_rect_view.size() == area {
if let Some(mask) = options.mask {
let (prev_x, prev_y) = dst_area.coord();
dst_area = dst_area.clip(mask);
if dst_area.width() == 0 || dst_area.height() == 0 {
return;
}
let (new_x, new_y) = dst_area.coord();
sub_rect_view.0.x = (sub_rect_view.0.x + new_x - prev_x).max(0);
sub_rect_view.0.y = (sub_rect_view.0.y + new_y - prev_y).max(0);
sub_rect_view.0.size.width = dst_area.width();
sub_rect_view.0.size.height = dst_area.height();
}
sub_rect_view
.parent_ranges_iter(self.size)
.zip(dst_area.parent_ranges_iter(dst_size))
.for_each(|(src_range, dst_range)| self.blit_horizontal(dst, dst_range, src_range));
} else {
let tiles = area / sub_rect_view.size();
let remainder = area % sub_rect_view.size();
for tile_x in 0..tiles.width {
for tile_y in 0..tiles.height {
let mut new_options = BlitOptions::new_position(
options.x + (tile_x * sub_rect_view.width()) as i32,
options.y + (tile_y * sub_rect_view.height()) as i32,
)
.with_sub_rect(sub_rect_view.as_sub_rect());
new_options.mask = options.mask;
self.blit_slice(dst, dst_size, &new_options);
}
if remainder.height > 0 {
let mut new_options = BlitOptions::new_position(
options.x + (tile_x * sub_rect_view.width()) as i32,
options.y + (tiles.height * sub_rect_view.height()) as i32,
)
.with_sub_rect(sub_rect_view.as_sub_rect())
.with_area((sub_rect_view.width(), remainder.height));
new_options.mask = options.mask;
self.blit_slice(dst, dst_size, &new_options);
}
}
if remainder.width > 0 {
for tile_y in 0..tiles.height {
let mut new_options = BlitOptions::new_position(
options.x + (tiles.width * sub_rect_view.width()) as i32,
options.y + (tile_y * sub_rect_view.height()) as i32,
)
.with_sub_rect(sub_rect_view.as_sub_rect())
.with_area((remainder.width, sub_rect_view.height()));
new_options.mask = options.mask;
self.blit_slice(dst, dst_size, &new_options);
}
if remainder.height > 0 {
let mut new_options = BlitOptions::new_position(
options.x + (tiles.width * sub_rect_view.width()) as i32,
options.y + (tiles.height * sub_rect_view.height()) as i32,
)
.with_sub_rect(sub_rect_view.as_sub_rect())
.with_area(remainder);
new_options.mask = options.mask;
self.blit_slice(dst, dst_size, &new_options);
}
}
}
}
fn blit_horizontal(&self, dst: &mut [u32], dst_index: Range<usize>, blit_index: Range<usize>) {
let blit_iter = self.data[blit_index].iter();
let dst_iter = dst[dst_index].iter_mut();
dst_iter.zip(blit_iter).for_each(|(dst_pixel, blit_pixel)| {
*dst_pixel = Self::blit_pixel(*dst_pixel, *blit_pixel);
});
}
#[inline(always)]
fn blit_pixel(dst_pixel: Color, blit_pixel: Color) -> Color {
if (blit_pixel >> 24) > 0 {
blit_pixel
} else {
dst_pixel
}
}
}
impl Blit for BlitBuffer {
fn blit(&self, dst: &mut [u32], dst_size: Size, options: &BlitOptions) {
let mut options = options.clone();
if options.x.is_negative() || options.y.is_negative() {
let (new_x, new_y) = (options.x.max(0), options.y.max(0));
let (diff_x, diff_y) = ((new_x - options.x) as u32, (new_y - options.y) as u32);
let mut sub_rect = options
.sub_rect
.unwrap_or_else(|| SubRect::from_size(self.size()));
if diff_x > sub_rect.size.width || diff_y > sub_rect.size.height {
return;
}
sub_rect.x += diff_x as i32;
sub_rect.y += diff_y as i32;
sub_rect.size.width -= diff_x;
sub_rect.size.height -= diff_y;
options.set_sub_rect(sub_rect);
options.x = new_x;
options.y = new_y;
}
let area = options.area(self.size);
let slice_projections = self.slice_projections(&options, area);
if slice_projections.is_empty() {
self.blit_slice(dst, dst_size, &options);
} else {
slice_projections.into_iter().for_each(|(source, target)| {
let mut slice_options = options
.clone()
.with_position(options.x + target.x, options.y + target.y)
.with_area(target.size);
slice_options.set_sub_rect(if let Some(sub_rect) = options.sub_rect {
sub_rect.shift(source.x, source.y)
} else {
source
});
self.blit_slice(dst, dst_size, &slice_options)
});
}
}
}
impl std::fmt::Debug for BlitBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlitBuffer")
.field("width", &self.size.width)
.field("height", &self.size.height)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_fit() {
let mut buffer = [0xFF, 0xFF_00, 0xFF_00_00, 0xFF, 0xFF_00, 0xFF_00_00];
let blit = BlitBuffer::from_buffer(
&[
0xFF_00_00_AA,
0xFF_00_AA_00,
0xFF_AA_00_00,
0xBB,
0xBB,
0xBB,
],
2,
127,
);
blit.blit(
&mut buffer,
Size::new(2, 3),
&BlitOptions::new_position(0, 0),
);
let expected = [
0xAA | 0xFF_00_00_00,
0xAA_00 | 0xFF_00_00_00,
0xAA_00_00 | 0xFF_00_00_00,
0xFF,
0xFF_00,
0xFF_00_00,
];
assert_eq!(
buffer, expected,
"\nResult:\n{:08x?}\nExpected:\n{:08x?}",
&buffer, &expected
);
}
}