summaryrefslogtreecommitdiff
path: root/packer/src/lib.rs
blob: 328f90e617f60f00b1506b44c16a41388b70287b (plain)
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;

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();
		self.textures.reverse();
		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))
	}
}