Skip to content

Commit

Permalink
Margin in atlas
Browse files Browse the repository at this point in the history
  • Loading branch information
nanoqsh committed Jul 21, 2023
1 parent 6e654e7 commit c8062a6
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 21 deletions.
10 changes: 5 additions & 5 deletions atlas/src/atlas.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
crate::pack::{self, Pack, Rect},
crate::pack::{self, Margin, Pack, Rect},
png::{Error as ImageError, Format, Image},
serde::Serialize,
std::{collections::BTreeMap, fmt},
Expand All @@ -14,10 +14,10 @@ pub struct ImageData {
///
/// # Errors
/// See [`Error`] type for details.
pub fn make(data: Vec<ImageData>) -> Result<Atlas, Error> {
pub fn make(data: Vec<ImageData>, margin: Margin) -> Result<Atlas, Error> {
let mut sprites = decode_sprites(data)?;
sprites.sort_unstable_by(|a, b| a.name.cmp(&b.name));
Atlas::pack(sprites)
Atlas::pack(sprites, margin)
}

fn decode_sprites(data: Vec<ImageData>) -> Result<Vec<Sprite>, Error> {
Expand All @@ -35,7 +35,7 @@ pub struct Atlas {
}

impl Atlas {
fn pack(sprites: Vec<Sprite>) -> Result<Self, Error> {
fn pack(sprites: Vec<Sprite>, margin: Margin) -> Result<Self, Error> {
use std::iter;

let entries: Vec<_> = sprites
Expand All @@ -57,7 +57,7 @@ impl Atlas {
})
.collect();

let Pack { rects, side } = pack::pack(&entries);
let Pack { rects, side } = pack::pack(&entries, margin);
let mut map = Image::empty((side, side), format);
for (Sprite { image, .. }, rect) in iter::zip(&sprites, &rects) {
map.copy_from(image, rect.point());
Expand Down
5 changes: 4 additions & 1 deletion atlas/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
mod atlas;
mod pack;

pub use crate::atlas::{make, Atlas, Error, ImageData, Map};
pub use crate::{
atlas::{make, Atlas, Error, ImageData, Map},
pack::{Margin, TooLarge},
};
73 changes: 60 additions & 13 deletions atlas/src/pack.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use serde::Serialize;
use {serde::Serialize, std::fmt};

type Size = (u32, u32);
type Point = (u32, u32);
Expand Down Expand Up @@ -32,18 +32,18 @@ pub(crate) struct Pack {
pub side: u32,
}

pub(crate) fn pack(entries: &[Size]) -> Pack {
pub(crate) fn pack(entries: &[Size], margin: Margin) -> Pack {
let mut side = initial_side(entries);
loop {
match try_pack(entries, side) {
match try_pack(entries, side, margin) {
Some(rects) => return Pack { rects, side },
None => side = side.pow(2),
None => side *= 2,
}
}
}

fn initial_side(entries: &[Size]) -> u32 {
const MIN_INITIAL_SIDE: u32 = 128;
const MIN_INITIAL_SIDE: u32 = 64;

let max_size = entries
.iter()
Expand All @@ -57,28 +57,28 @@ fn initial_side(entries: &[Size]) -> u32 {
u32::max(side, MIN_INITIAL_SIDE)
}

fn try_pack(entries: &[Size], side: u32) -> Option<Vec<Rect>> {
let mut x = 0;
let mut y = 0;
fn try_pack(entries: &[Size], side: u32, margin: Margin) -> Option<Vec<Rect>> {
let mut x = margin.horizontal;
let mut y = margin.vertical;
let mut max_height = 0;

entries
.iter()
.map(|&(width, height)| {
max_height = max_height.max(height);

if x + width > side {
x = 0;
y += max_height;
if x + width + margin.horizontal > side {
x = margin.horizontal;
y += max_height + margin.vertical;
max_height = 0;
}

if y + height > side {
if y + height + margin.vertical > side {
return None;
}

let point = (x, y);
x += width;
x += width + margin.horizontal;

Some(Rect {
size: (width, height),
Expand All @@ -87,3 +87,50 @@ fn try_pack(entries: &[Size], side: u32) -> Option<Vec<Rect>> {
})
.collect()
}

#[derive(Clone, Copy)]
pub struct Margin {
horizontal: u32,
vertical: u32,
}

impl Margin {
const MAX_HORIZONTAL: u32 = 4;
const MAX_VERTICAL: u32 = 4;

/// Creates a new margin.
///
/// # Errors
/// This function returns an error if the margin is [too large](TooLarge).
pub fn new(horizontal: u32, vertical: u32) -> Result<Self, TooLarge> {
if horizontal > Self::MAX_HORIZONTAL {
return Err(TooLarge::Horizontal(horizontal));
}

if vertical > Self::MAX_VERTICAL {
return Err(TooLarge::Vertical(vertical));
}

Ok(Self {
horizontal,
vertical,
})
}
}

/// The margin is too large.
pub enum TooLarge {
Horizontal(u32),
Vertical(u32),
}

impl fmt::Display for TooLarge {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (ty, margin, max) = match self {
Self::Horizontal(margin) => ("horizontal", margin, Margin::MAX_HORIZONTAL),
Self::Vertical(margin) => ("vertical", margin, Margin::MAX_VERTICAL),
};

write!(f, "{ty} margin {margin} is greater than maximum {max}",)
}
}
23 changes: 21 additions & 2 deletions staff/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
atlas::{Atlas, Error as AtlasError, ImageData, Map},
atlas::{Atlas, Error as AtlasError, ImageData, Map, Margin, TooLarge},
clap::{Parser, Subcommand},
color::{Color, Error as ColorError},
convert::{Element, Error as ParseError, Parameters, Target, Value},
Expand Down Expand Up @@ -81,6 +81,14 @@ enum Cmd {
/// Specify output directory (current by default)
#[arg(short, long)]
outdir: Option<PathBuf>,

/// Specify horizontal margin
#[arg(long, default_value_t = 0)]
hm: u32,

/// Specify vertical margin
#[arg(long, default_value_t = 0)]
vm: u32,
},
}

Expand Down Expand Up @@ -169,9 +177,12 @@ fn run(cli: Cli) -> Result<(), Error> {
sprites,
name,
outdir,
hm,
vm,
} => {
let data = read_sprites(sprites)?;
let Atlas { png, map } = atlas::make(data)?;
let margin = Margin::new(hm, vm)?;
let Atlas { png, map } = atlas::make(data, margin)?;
let name = name.as_deref().unwrap_or(OUT_NAME);
let outdir = make_outdir(outdir)?;
write_png(&png, name, &outdir)?;
Expand Down Expand Up @@ -300,6 +311,7 @@ enum Error {
WriteToFile(PathBuf),
PalettePathNotSet,
Atlas(AtlasError),
Margin(TooLarge),
Parse(ParseError),
Color(ColorError),
Json(JsonError),
Expand All @@ -311,6 +323,12 @@ impl From<AtlasError> for Error {
}
}

impl From<TooLarge> for Error {
fn from(v: TooLarge) -> Self {
Self::Margin(v)
}
}

impl From<ParseError> for Error {
fn from(v: ParseError) -> Self {
Self::Parse(v)
Expand Down Expand Up @@ -339,6 +357,7 @@ impl fmt::Display for Error {
Self::WriteToFile(path) => write!(f, "failed to write file {path:?}"),
Self::PalettePathNotSet => write!(f, "the palette path is not set"),
Self::Atlas(err) => write!(f, "{err}"),
Self::Margin(err) => write!(f, "{err}"),
Self::Parse(err) => write!(f, "{err}"),
Self::Color(err) => write!(f, "{err}"),
Self::Json(err) => write!(f, "{err}"),
Expand Down

0 comments on commit c8062a6

Please sign in to comment.