use skia_rs_core::{Point, Rect, Scalar};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AtlasEntryId(u64);
impl AtlasEntryId {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn raw(&self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct AtlasRegion {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub layer: u32,
}
impl AtlasRegion {
pub fn uv_rect(&self, atlas_width: u32, atlas_height: u32) -> [f32; 4] {
[
self.x as f32 / atlas_width as f32,
self.y as f32 / atlas_height as f32,
(self.x + self.width) as f32 / atlas_width as f32,
(self.y + self.height) as f32 / atlas_height as f32,
]
}
pub fn to_rect(&self) -> Rect {
Rect::from_xywh(
self.x as f32,
self.y as f32,
self.width as f32,
self.height as f32,
)
}
}
#[derive(Debug, Clone)]
pub struct AtlasConfig {
pub width: u32,
pub height: u32,
pub max_layers: u32,
pub padding: u32,
pub allow_resize: bool,
}
impl Default for AtlasConfig {
fn default() -> Self {
Self {
width: 2048,
height: 2048,
max_layers: 4,
padding: 1,
allow_resize: true,
}
}
}
#[derive(Debug)]
pub enum AtlasAllocResult {
Success(AtlasRegion),
Full,
TooLarge,
}
#[derive(Debug)]
struct AtlasLayer {
current_y: u32,
current_shelf_height: u32,
current_x: u32,
width: u32,
height: u32,
}
impl AtlasLayer {
fn new(width: u32, height: u32) -> Self {
Self {
current_y: 0,
current_shelf_height: 0,
current_x: 0,
width,
height,
}
}
fn allocate(&mut self, width: u32, height: u32, padding: u32) -> Option<(u32, u32)> {
let padded_width = width + padding * 2;
let padded_height = height + padding * 2;
if self.current_x + padded_width <= self.width
&& self.current_y + padded_height.max(self.current_shelf_height) <= self.height
{
let x = self.current_x + padding;
let y = self.current_y + padding;
self.current_x += padded_width;
self.current_shelf_height = self.current_shelf_height.max(padded_height);
return Some((x, y));
}
if self.current_y + self.current_shelf_height + padded_height <= self.height {
self.current_y += self.current_shelf_height;
self.current_x = 0;
self.current_shelf_height = padded_height;
if self.current_x + padded_width <= self.width {
let x = self.current_x + padding;
let y = self.current_y + padding;
self.current_x += padded_width;
return Some((x, y));
}
}
None
}
fn reset(&mut self) {
self.current_y = 0;
self.current_shelf_height = 0;
self.current_x = 0;
}
}
#[derive(Debug)]
pub struct TextureAtlas {
config: AtlasConfig,
layers: Vec<AtlasLayer>,
entries: HashMap<AtlasEntryId, AtlasRegion>,
next_id: u64,
generation: u64,
}
impl TextureAtlas {
pub fn new(config: AtlasConfig) -> Self {
let mut layers = Vec::with_capacity(config.max_layers as usize);
layers.push(AtlasLayer::new(config.width, config.height));
Self {
config,
layers,
entries: HashMap::new(),
next_id: 0,
generation: 0,
}
}
pub fn config(&self) -> &AtlasConfig {
&self.config
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn layer_count(&self) -> u32 {
self.layers.len() as u32
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
pub fn lookup(&self, id: AtlasEntryId) -> Option<&AtlasRegion> {
self.entries.get(&id)
}
pub fn allocate(&mut self, width: u32, height: u32) -> AtlasAllocResult {
if width > self.config.width || height > self.config.height {
return AtlasAllocResult::TooLarge;
}
for (layer_idx, layer) in self.layers.iter_mut().enumerate() {
if let Some((x, y)) = layer.allocate(width, height, self.config.padding) {
let id = AtlasEntryId::new(self.next_id);
self.next_id += 1;
let region = AtlasRegion {
x,
y,
width,
height,
layer: layer_idx as u32,
};
self.entries.insert(id, region);
return AtlasAllocResult::Success(region);
}
}
if self.layers.len() < self.config.max_layers as usize {
let mut new_layer = AtlasLayer::new(self.config.width, self.config.height);
if let Some((x, y)) = new_layer.allocate(width, height, self.config.padding) {
let layer_idx = self.layers.len();
self.layers.push(new_layer);
let id = AtlasEntryId::new(self.next_id);
self.next_id += 1;
let region = AtlasRegion {
x,
y,
width,
height,
layer: layer_idx as u32,
};
self.entries.insert(id, region);
return AtlasAllocResult::Success(region);
}
}
AtlasAllocResult::Full
}
pub fn allocate_with_id(
&mut self,
width: u32,
height: u32,
) -> Option<(AtlasEntryId, AtlasRegion)> {
match self.allocate(width, height) {
AtlasAllocResult::Success(region) => {
let id = AtlasEntryId::new(self.next_id - 1);
Some((id, region))
}
_ => None,
}
}
pub fn reset(&mut self) {
for layer in &mut self.layers {
layer.reset();
}
self.entries.clear();
self.generation += 1;
self.layers.truncate(1);
}
pub fn compact(&mut self) -> Vec<AtlasEntryId> {
let ids: Vec<AtlasEntryId> = self.entries.keys().copied().collect();
self.reset();
ids
}
}
#[derive(Debug)]
pub struct AtlasManager {
path_atlas: TextureAtlas,
glyph_atlas: TextureAtlas,
color_atlas: TextureAtlas,
}
impl AtlasManager {
pub fn new() -> Self {
Self {
path_atlas: TextureAtlas::new(AtlasConfig {
width: 2048,
height: 2048,
max_layers: 4,
padding: 2,
allow_resize: true,
}),
glyph_atlas: TextureAtlas::new(AtlasConfig {
width: 1024,
height: 1024,
max_layers: 2,
padding: 1,
allow_resize: true,
}),
color_atlas: TextureAtlas::new(AtlasConfig {
width: 1024,
height: 1024,
max_layers: 2,
padding: 1,
allow_resize: true,
}),
}
}
pub fn path_atlas(&self) -> &TextureAtlas {
&self.path_atlas
}
pub fn path_atlas_mut(&mut self) -> &mut TextureAtlas {
&mut self.path_atlas
}
pub fn glyph_atlas(&self) -> &TextureAtlas {
&self.glyph_atlas
}
pub fn glyph_atlas_mut(&mut self) -> &mut TextureAtlas {
&mut self.glyph_atlas
}
pub fn color_atlas(&self) -> &TextureAtlas {
&self.color_atlas
}
pub fn color_atlas_mut(&mut self) -> &mut TextureAtlas {
&mut self.color_atlas
}
pub fn reset_all(&mut self) {
self.path_atlas.reset();
self.glyph_atlas.reset();
self.color_atlas.reset();
}
}
impl Default for AtlasManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atlas_region_uv() {
let region = AtlasRegion {
x: 100,
y: 200,
width: 50,
height: 75,
layer: 0,
};
let uv = region.uv_rect(1000, 1000);
assert_eq!(uv[0], 0.1);
assert_eq!(uv[1], 0.2);
assert_eq!(uv[2], 0.15);
assert_eq!(uv[3], 0.275);
}
#[test]
fn test_atlas_allocate() {
let config = AtlasConfig {
width: 256,
height: 256,
max_layers: 1,
padding: 0,
allow_resize: false,
};
let mut atlas = TextureAtlas::new(config);
match atlas.allocate(64, 64) {
AtlasAllocResult::Success(region) => {
assert_eq!(region.width, 64);
assert_eq!(region.height, 64);
}
_ => panic!("Expected success"),
}
match atlas.allocate(512, 512) {
AtlasAllocResult::TooLarge => {}
_ => panic!("Expected TooLarge"),
}
}
#[test]
fn test_atlas_multiple_allocations() {
let config = AtlasConfig {
width: 256,
height: 256,
max_layers: 1,
padding: 0,
allow_resize: false,
};
let mut atlas = TextureAtlas::new(config);
for _ in 0..16 {
match atlas.allocate(32, 32) {
AtlasAllocResult::Success(_) => {}
_ => panic!("Expected success"),
}
}
assert_eq!(atlas.entry_count(), 16);
}
#[test]
fn test_atlas_reset() {
let mut atlas = TextureAtlas::new(AtlasConfig::default());
atlas.allocate(100, 100);
atlas.allocate(100, 100);
assert_eq!(atlas.entry_count(), 2);
let gen_before = atlas.generation();
atlas.reset();
assert_eq!(atlas.entry_count(), 0);
assert_eq!(atlas.generation(), gen_before + 1);
}
#[test]
fn test_atlas_manager() {
let mut manager = AtlasManager::new();
manager.path_atlas_mut().allocate(64, 64);
manager.glyph_atlas_mut().allocate(32, 32);
manager.color_atlas_mut().allocate(48, 48);
assert_eq!(manager.path_atlas().entry_count(), 1);
assert_eq!(manager.glyph_atlas().entry_count(), 1);
assert_eq!(manager.color_atlas().entry_count(), 1);
manager.reset_all();
assert_eq!(manager.path_atlas().entry_count(), 0);
}
#[test]
fn test_atlas_entry_id() {
let id = AtlasEntryId::new(42);
assert_eq!(id.raw(), 42);
}
#[test]
fn test_atlas_lookup() {
let mut atlas = TextureAtlas::new(AtlasConfig::default());
if let Some((id, region)) = atlas.allocate_with_id(100, 100) {
let looked_up = atlas.lookup(id);
assert!(looked_up.is_some());
assert_eq!(looked_up.unwrap().width, region.width);
}
}
}