1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4#[cfg(feature = "rayon")]
5mod multi_threaded;
6mod position;
7mod size;
8pub mod stride;
9#[cfg(feature = "rayon")]
10pub use multi_threaded::*;
11
12pub use position::*;
13pub use size::Size;
14
15pub fn blit(
22 src: &[u8],
23 src_size: &Size,
24 dst: &mut [u8],
25 dst_position: &PositionU,
26 dst_size: &Size,
27 stride: usize,
28) {
29 if src_size.w > 0 && src_size.h > 0 {
30 let src_w_stride = src_size.w * stride;
31 (0..src_size.h).for_each(|src_y| {
32 let src_index = get_index(0, src_y, src_size.w, stride);
33 let dst_index = get_index(dst_position.x, dst_position.y + src_y, dst_size.w, stride);
34 dst[dst_index..dst_index + src_w_stride]
35 .copy_from_slice(&src[src_index..src_index + src_w_stride]);
36 });
37 }
38}
39
40pub fn clip(dst_position: &PositionI, dst_size: &Size, src_size: &mut Size) -> PositionU {
43 if dst_position.x + (src_size.w.cast_signed()) < 0 || dst_position.y + (src_size.h.cast_signed()) < 0 {
45 src_size.w = 0;
46 src_size.h = 0;
47 PositionU::default()
48 } else {
49 let mut x = 0;
50 if dst_position.x < 0 {
51 src_size.w = src_size.w.saturating_sub(dst_position.x.unsigned_abs());
52 } else {
53 x = dst_position.x.unsigned_abs();
54 }
55 let mut y = 0;
56 if dst_position.y < 0 {
57 src_size.h = src_size.h.saturating_sub(dst_position.y.unsigned_abs());
58 } else {
59 y = dst_position.y.unsigned_abs();
60 }
61 let dst_position = PositionU { x, y };
62 if dst_position.x < dst_size.w && dst_position.y < dst_size.h {
65 src_size.w = src_size.w.min(dst_size.w - dst_position.x);
66 src_size.h = src_size.h.min(dst_size.h - dst_position.y);
67 dst_position
68 } else {
69 *src_size = Size::default();
70 PositionU::default()
71 }
72 }
73}
74
75pub const fn get_index(x: usize, y: usize, w: usize, stride: usize) -> usize {
77 (x + y * w) * stride
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::stride::RGB;
84 use std::{fs::File, io::BufWriter, path::Path};
85
86 const SRC_W: usize = 32;
87 const SRC_H: usize = 17;
88 const DST_W: usize = 64;
89 const DST_H: usize = 64;
90
91 #[test]
92 fn test_blit() {
93 let src = [255u8; SRC_W * SRC_H * RGB];
94 let mut dst = [0u8; DST_W * DST_H * RGB];
95
96 let dst_position = PositionU { x: 2, y: 12 };
97 let dst_size = Size { w: DST_W, h: DST_H };
98 let src_size = Size { w: SRC_W, h: SRC_H };
99
100 blit(&src, &src_size, &mut dst, &dst_position, &dst_size, RGB);
101
102 save_png("blit.png", &dst, DST_W as u32, DST_H as u32);
103 }
104
105 #[test]
106 fn test_clip() {
107 blit_clipped("clip_positive.png", 42, 16);
108 blit_clipped("clip_negative.png", -8, -8);
109 }
110
111 fn blit_clipped(name: &str, x: isize, y: isize) {
112 let src = [255u8; SRC_W * SRC_H * RGB];
113 let mut dst = [0u8; DST_W * DST_H * RGB];
114
115 let dst_position = PositionI { x, y };
116 let dst_size = Size { w: DST_W, h: DST_H };
117 let mut src_size = Size { w: SRC_W, h: SRC_H };
118 let dst_position = clip(&dst_position, &dst_size, &mut src_size);
119
120 blit(&src, &src_size, &mut dst, &dst_position, &dst_size, RGB);
121 save_png(name, &dst, DST_W as u32, DST_H as u32);
122 }
123
124 fn save_png(path: &str, dst: &[u8], dst_w: u32, dst_h: u32) {
125 let path = Path::new(path);
126 let file = File::create(path).unwrap();
127 let w = BufWriter::new(file);
128 let mut encoder = png::Encoder::new(w, dst_w, dst_h);
129 encoder.set_color(png::ColorType::Rgb);
130 encoder.set_depth(png::BitDepth::Eight);
131 let mut writer = encoder.write_header().unwrap();
132 writer.write_image_data(dst).unwrap();
133 }
134}