use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AlgorithmFamily {
Skyline,
MaxRects,
Guillotine,
Auto,
}
impl FromStr for AlgorithmFamily {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"skyline" => Ok(Self::Skyline),
"maxrects" => Ok(Self::MaxRects),
"guillotine" => Ok(Self::Guillotine),
"auto" => Ok(Self::Auto),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum MaxRectsHeuristic {
BestAreaFit,
BestShortSideFit,
BestLongSideFit,
BottomLeft,
ContactPoint,
}
impl FromStr for MaxRectsHeuristic {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"baf" | "bestareafit" => Ok(Self::BestAreaFit),
"bssf" | "bestshortsidefit" => Ok(Self::BestShortSideFit),
"blsf" | "bestlongsidefit" => Ok(Self::BestLongSideFit),
"bl" | "bottomleft" => Ok(Self::BottomLeft),
"cp" | "contactpoint" => Ok(Self::ContactPoint),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SkylineHeuristic {
BottomLeft,
MinWaste,
}
impl FromStr for SkylineHeuristic {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"bl" | "bottomleft" => Ok(Self::BottomLeft),
"minwaste" | "mw" => Ok(Self::MinWaste),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum GuillotineChoice {
BestAreaFit,
BestShortSideFit,
BestLongSideFit,
WorstAreaFit,
WorstShortSideFit,
WorstLongSideFit,
}
impl FromStr for GuillotineChoice {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"baf" | "bestareafit" => Ok(Self::BestAreaFit),
"bssf" | "bestshortsidefit" => Ok(Self::BestShortSideFit),
"blsf" | "bestlongsidefit" => Ok(Self::BestLongSideFit),
"waf" | "worstareafit" => Ok(Self::WorstAreaFit),
"wssf" | "worstshortsidefit" => Ok(Self::WorstShortSideFit),
"wlsf" | "worstlongsidefit" => Ok(Self::WorstLongSideFit),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum GuillotineSplit {
SplitShorterLeftoverAxis,
SplitLongerLeftoverAxis,
SplitMinimizeArea,
SplitMaximizeArea,
SplitShorterAxis,
SplitLongerAxis,
}
impl FromStr for GuillotineSplit {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"slas" | "splitshorterleftoveraxis" => Ok(Self::SplitShorterLeftoverAxis),
"llas" | "splitlongerleftoveraxis" => Ok(Self::SplitLongerLeftoverAxis),
"minas" | "splitminimizearea" => Ok(Self::SplitMinimizeArea),
"maxas" | "splitmaximizearea" => Ok(Self::SplitMaximizeArea),
"sas" | "splitshorteraxis" => Ok(Self::SplitShorterAxis),
"las" | "splitlongeraxis" => Ok(Self::SplitLongerAxis),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AutoMode {
Fast,
Quality,
}
impl FromStr for AutoMode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"fast" => Ok(Self::Fast),
"quality" => Ok(Self::Quality),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SortOrder {
AreaDesc,
MaxSideDesc,
HeightDesc,
WidthDesc,
NameAsc,
None,
}
impl FromStr for SortOrder {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"area_desc" => Ok(Self::AreaDesc),
"max_side_desc" => Ok(Self::MaxSideDesc),
"height_desc" => Ok(Self::HeightDesc),
"width_desc" => Ok(Self::WidthDesc),
"name_asc" => Ok(Self::NameAsc),
"none" => Ok(Self::None),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackerConfig {
pub max_width: u32,
pub max_height: u32,
pub allow_rotation: bool,
pub force_max_dimensions: bool,
pub border_padding: u32,
pub texture_padding: u32,
pub texture_extrusion: u32,
pub trim: bool,
pub trim_threshold: u8,
pub texture_outlines: bool,
pub power_of_two: bool,
pub square: bool,
pub use_waste_map: bool,
#[serde(default = "default_family")]
pub family: AlgorithmFamily,
#[serde(default = "default_mr_heuristic")]
pub mr_heuristic: MaxRectsHeuristic,
#[serde(default = "default_skyline_heuristic")]
pub skyline_heuristic: SkylineHeuristic,
#[serde(default = "default_g_choice")]
pub g_choice: GuillotineChoice,
#[serde(default = "default_g_split")]
pub g_split: GuillotineSplit,
#[serde(default = "default_auto_mode")]
pub auto_mode: AutoMode,
#[serde(default = "default_sort_order")]
pub sort_order: SortOrder,
#[serde(default)]
pub time_budget_ms: Option<u64>,
#[serde(default = "default_parallel")]
pub parallel: bool,
#[serde(default)]
pub mr_reference: bool,
#[serde(default)]
pub auto_mr_ref_time_ms_threshold: Option<u64>,
#[serde(default)]
pub auto_mr_ref_input_threshold: Option<usize>,
#[serde(default = "default_transparent_policy")]
pub transparent_policy: TransparentPolicy,
}
impl Default for PackerConfig {
fn default() -> Self {
Self {
max_width: 1024,
max_height: 1024,
allow_rotation: true,
force_max_dimensions: false,
border_padding: 0,
texture_padding: 2,
texture_extrusion: 0,
trim: true,
trim_threshold: 0,
texture_outlines: false,
power_of_two: false,
square: false,
use_waste_map: false,
family: default_family(),
mr_heuristic: default_mr_heuristic(),
skyline_heuristic: default_skyline_heuristic(),
g_choice: default_g_choice(),
g_split: default_g_split(),
auto_mode: default_auto_mode(),
sort_order: default_sort_order(),
time_budget_ms: None,
parallel: default_parallel(),
mr_reference: false,
auto_mr_ref_time_ms_threshold: None,
auto_mr_ref_input_threshold: None,
transparent_policy: default_transparent_policy(),
}
}
}
impl PackerConfig {
pub fn validate(&self) -> crate::error::Result<()> {
use crate::error::TexPackerError;
if self.max_width == 0 || self.max_height == 0 {
return Err(TexPackerError::InvalidDimensions {
width: self.max_width,
height: self.max_height,
});
}
let total_border = self.border_padding.saturating_mul(2);
let total_padding_per_texture = self
.texture_padding
.saturating_add(self.texture_extrusion.saturating_mul(2));
if total_border >= self.max_width || total_border >= self.max_height {
return Err(TexPackerError::InvalidConfig(format!(
"border_padding ({}) * 2 exceeds atlas dimensions ({}x{})",
self.border_padding, self.max_width, self.max_height
)));
}
let usable_width = self.max_width.saturating_sub(total_border);
let usable_height = self.max_height.saturating_sub(total_border);
if usable_width == 0 || usable_height == 0 {
return Err(TexPackerError::InvalidConfig(format!(
"No usable space after border_padding: {}x{} - {} * 2 = {}x{}",
self.max_width, self.max_height, self.border_padding, usable_width, usable_height
)));
}
if total_padding_per_texture > usable_width / 2
|| total_padding_per_texture > usable_height / 2
{
}
Ok(())
}
}
fn default_family() -> AlgorithmFamily {
AlgorithmFamily::Skyline
}
fn default_mr_heuristic() -> MaxRectsHeuristic {
MaxRectsHeuristic::BestAreaFit
}
fn default_skyline_heuristic() -> SkylineHeuristic {
SkylineHeuristic::BottomLeft
}
fn default_g_choice() -> GuillotineChoice {
GuillotineChoice::BestAreaFit
}
fn default_g_split() -> GuillotineSplit {
GuillotineSplit::SplitShorterLeftoverAxis
}
fn default_auto_mode() -> AutoMode {
AutoMode::Quality
}
fn default_sort_order() -> SortOrder {
SortOrder::AreaDesc
}
fn default_parallel() -> bool {
false
}
fn default_transparent_policy() -> TransparentPolicy {
TransparentPolicy::Keep
}
#[derive(Debug, Default, Clone)]
pub struct PackerConfigBuilder {
cfg: PackerConfig,
}
impl PackerConfigBuilder {
pub fn new() -> Self {
Self {
cfg: PackerConfig::default(),
}
}
pub fn with_max_dimensions(mut self, w: u32, h: u32) -> Self {
self.cfg.max_width = w;
self.cfg.max_height = h;
self
}
pub fn allow_rotation(mut self, v: bool) -> Self {
self.cfg.allow_rotation = v;
self
}
pub fn force_max_dimensions(mut self, v: bool) -> Self {
self.cfg.force_max_dimensions = v;
self
}
pub fn border_padding(mut self, v: u32) -> Self {
self.cfg.border_padding = v;
self
}
pub fn texture_padding(mut self, v: u32) -> Self {
self.cfg.texture_padding = v;
self
}
pub fn texture_extrusion(mut self, v: u32) -> Self {
self.cfg.texture_extrusion = v;
self
}
pub fn trim(mut self, v: bool) -> Self {
self.cfg.trim = v;
self
}
pub fn trim_threshold(mut self, v: u8) -> Self {
self.cfg.trim_threshold = v;
self
}
pub fn outlines(mut self, v: bool) -> Self {
self.cfg.texture_outlines = v;
self
}
pub fn pow2(mut self, v: bool) -> Self {
self.cfg.power_of_two = v;
self
}
pub fn square(mut self, v: bool) -> Self {
self.cfg.square = v;
self
}
pub fn family(mut self, v: AlgorithmFamily) -> Self {
self.cfg.family = v;
self
}
pub fn skyline_heuristic(mut self, v: SkylineHeuristic) -> Self {
self.cfg.skyline_heuristic = v;
self
}
pub fn mr_heuristic(mut self, v: MaxRectsHeuristic) -> Self {
self.cfg.mr_heuristic = v;
self
}
pub fn g_choice(mut self, v: GuillotineChoice) -> Self {
self.cfg.g_choice = v;
self
}
pub fn g_split(mut self, v: GuillotineSplit) -> Self {
self.cfg.g_split = v;
self
}
pub fn auto_mode(mut self, v: AutoMode) -> Self {
self.cfg.auto_mode = v;
self
}
pub fn sort_order(mut self, v: SortOrder) -> Self {
self.cfg.sort_order = v;
self
}
pub fn time_budget_ms(mut self, v: Option<u64>) -> Self {
self.cfg.time_budget_ms = v;
self
}
pub fn parallel(mut self, v: bool) -> Self {
self.cfg.parallel = v;
self
}
pub fn mr_reference(mut self, v: bool) -> Self {
self.cfg.mr_reference = v;
self
}
pub fn auto_mr_ref_time_ms_threshold(mut self, v: Option<u64>) -> Self {
self.cfg.auto_mr_ref_time_ms_threshold = v;
self
}
pub fn auto_mr_ref_input_threshold(mut self, v: Option<usize>) -> Self {
self.cfg.auto_mr_ref_input_threshold = v;
self
}
pub fn use_waste_map(mut self, v: bool) -> Self {
self.cfg.use_waste_map = v;
self
}
pub fn transparent_policy(mut self, v: TransparentPolicy) -> Self {
self.cfg.transparent_policy = v;
self
}
pub fn build(self) -> PackerConfig {
self.cfg
}
}
impl PackerConfig {
pub fn builder() -> PackerConfigBuilder {
PackerConfigBuilder::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TransparentPolicy {
Keep,
OneByOne,
Skip,
}
impl FromStr for TransparentPolicy {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"keep" => Ok(Self::Keep),
"one_by_one" | "1x1" | "onebyone" => Ok(Self::OneByOne),
"skip" => Ok(Self::Skip),
_ => Err(()),
}
}
}