use std::sync::Arc;
use std::{collections::HashMap, ops::Deref};
use exun::RawUnexpected;
use image::{GenericImage, RgbImage};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rectangle {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Texture {
id: Box<str>,
x: u32,
y: u32,
texture: Arc<RgbImage>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ImageRect(Arc<RgbImage>, Box<str>);
#[derive(Debug, Default, Clone)]
pub struct RectanglePacker {
min_width: u32,
textures: Vec<ImageRect>,
}
impl PartialOrd for ImageRect {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ImageRect {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.height().cmp(&other.0.height())
}
}
impl RectanglePacker {
pub fn new() -> Self {
Self {
min_width: 1,
textures: Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
min_width: 1,
textures: Vec::with_capacity(capacity),
}
}
pub fn capacity(&self) -> usize {
self.textures.capacity()
}
pub fn len(&self) -> usize {
self.textures.len()
}
pub fn is_empty(&self) -> bool {
self.textures.is_empty()
}
pub fn reserve(&mut self, additional: usize) {
self.textures.reserve(additional)
}
pub fn shrink_to_fit(&mut self) {
self.textures.shrink_to_fit()
}
pub fn add_texture(&mut self, name: Box<str>, texture: Arc<RgbImage>) {
if texture.width() > self.min_width {
self.min_width = texture.width() + 1;
}
self.textures.push(ImageRect(texture, name));
}
fn pack(&mut self) -> (Vec<Texture>, u32, u32) {
let image_width = self.min_width;
let mut x_position = 0;
let mut y_position = 0;
let mut largest_row_height = 0;
let mut rectangles = Vec::with_capacity(self.textures.len());
self.textures.sort();
for texture in &self.textures {
// loop to the next row if we've gone off the edge
if (x_position + texture.0.width()) > image_width {
y_position += largest_row_height;
x_position = 0;
largest_row_height = 0;
}
// set the rectangle position
let x = x_position;
let y = y_position;
x_position += texture.0.width();
if texture.0.height() > largest_row_height {
largest_row_height = texture.0.height();
}
rectangles.push(Texture {
id: texture.1.clone(),
x,
y,
texture: texture.0.clone(),
});
}
let total_height = y_position + largest_row_height;
(rectangles, image_width, total_height)
}
pub fn output(&mut self) -> Result<(RgbImage, HashMap<Box<str>, Rectangle>), RawUnexpected> {
let (rectangles, image_width, image_height) = self.pack();
let mut image = RgbImage::new(image_width, image_height);
let mut ids = HashMap::with_capacity(rectangles.len());
for rectangle in rectangles {
image.copy_from(rectangle.texture.deref(), rectangle.x, rectangle.y)?;
ids.insert(
rectangle.id,
Rectangle {
x: rectangle.x,
y: rectangle.y,
width: rectangle.texture.width(),
height: rectangle.texture.height(),
},
);
}
Ok((image, ids))
}
}
|