1use std::{collections::BTreeMap, error, fmt};
34
35#[repr(C)]
45#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub enum AtlasMipFilter {
48 #[default]
49 Nearest,
50 Linear,
51 Cubic,
52 Gaussian,
53 Lanczos3,
54}
55
56impl From<AtlasMipFilter> for image::imageops::FilterType {
57 #[inline]
58 fn from(value: AtlasMipFilter) -> Self {
59 match value {
60 AtlasMipFilter::Nearest => Self::Nearest,
61 AtlasMipFilter::Linear => Self::Triangle,
62 AtlasMipFilter::Cubic => Self::CatmullRom,
63 AtlasMipFilter::Gaussian => Self::Gaussian,
64 AtlasMipFilter::Lanczos3 => Self::Lanczos3,
65 }
66 }
67}
68
69#[repr(C)]
77#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub enum AtlasMipOption {
80 #[default]
81 NoMip,
82 NoMipWithPadding(u32),
83 Mip(AtlasMipFilter),
84 MipWithPadding(AtlasMipFilter, u32),
85 MipWithBlock(AtlasMipFilter, u32),
86}
87
88#[repr(C)]
94#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub enum AtlasEntryMipOption {
97 #[default]
98 Clamp,
99 Repeat,
100 Mirror,
101}
102
103#[derive(Clone, PartialEq, Eq, Default, Debug)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109pub struct AtlasEntry<I: image::GenericImageView> {
110 pub texture: I,
111 pub mip: AtlasEntryMipOption,
112}
113
114#[derive(Clone, PartialEq, Eq, Default, Debug)]
121pub struct AtlasDescriptor<'a, I: image::GenericImageView> {
122 pub max_page_count: u32,
123 pub size: u32,
124 pub mip: AtlasMipOption,
125 pub entries: &'a [AtlasEntry<I>],
126}
127
128#[rustfmt::skip]
158pub fn create_atlas<I>(desc: &AtlasDescriptor<'_, I>) -> Result<Atlas<I::Pixel>, AtlasError>
159where
160 I: image::GenericImage,
161 I::Pixel: 'static,
162{
163 match desc.mip {
164 AtlasMipOption::NoMip => {
165 create_atlas_with_padding(desc.max_page_count, desc.size, 0, desc.entries)
166 }
167 AtlasMipOption::NoMipWithPadding(padding) => {
168 create_atlas_with_padding(desc.max_page_count, desc.size, padding, desc.entries)
169 }
170 AtlasMipOption::Mip(filter) => {
171 create_atlas_mip_with_padding(desc.max_page_count, desc.size, filter, 0, desc.entries)
172 }
173 AtlasMipOption::MipWithPadding(filter, padding) => {
174 create_atlas_mip_with_padding(desc.max_page_count, desc.size, filter, padding, desc.entries)
175 }
176 AtlasMipOption::MipWithBlock(filter, block_size) => {
177 create_atlas_mip_with_block(desc.max_page_count, desc.size, filter, block_size, desc.entries)
178 }
179 }
180}
181
182#[inline]
183fn create_atlas_with_padding<I>(
184 max_page_count: u32,
185 size: u32,
186 padding: u32,
187 entries: &[AtlasEntry<I>],
188) -> Result<Atlas<I::Pixel>, AtlasError>
189where
190 I: image::GenericImage,
191 I::Pixel: 'static,
192{
193 if max_page_count == 0 {
194 return Err(AtlasError::ZeroMaxPageCount);
195 }
196
197 if entries.is_empty() {
198 return Err(AtlasError::ZeroEntry);
199 }
200
201 let mut rects = rectangle_pack::GroupedRectsToPlace::<_, ()>::new();
202 for (i, entry) in entries.iter().enumerate() {
203 let rect = rectangle_pack::RectToInsert::new(
204 entry.texture.width() + padding * 2,
205 entry.texture.height() + padding * 2,
206 1,
207 );
208 rects.push_rect(i, None, rect);
209 }
210
211 let mut target_bins = BTreeMap::new();
212 for i in 0..max_page_count {
213 target_bins.insert(i, rectangle_pack::TargetBin::new(size, size, 1));
214 }
215
216 let locations = rectangle_pack::pack_rects(
217 &rects,
218 &mut target_bins,
219 &rectangle_pack::volume_heuristic,
220 &rectangle_pack::contains_smallest_box,
221 )?;
222
223 let mut page_count = 0;
224 let mut texcoords = vec![Texcoord::default(); entries.len()];
225 for (&i, &(page, location)) in locations.packed_locations() {
226 page_count = u32::max(page_count, page + 1);
227
228 let texcoord = Texcoord {
229 page,
230 min_x: location.x() + padding,
231 min_y: location.y() + padding,
232 max_x: location.x() + location.width() - padding,
233 max_y: location.y() + location.height() - padding,
234 size,
235 };
236 texcoords[i] = texcoord;
237 }
238
239 let mip_level_count = 1;
240 let mut textures = vec![Texture::new(size, mip_level_count); page_count as usize];
241 for (&i, &(page, location)) in locations.packed_locations() {
242 let entry = &entries[i];
243
244 let src = resample(
245 &entry.texture,
246 entry.mip,
247 padding,
248 padding,
249 location.width(),
250 location.height(),
251 );
252
253 let target = &mut textures[page as usize].mip_maps[0];
254 image::imageops::replace(target, &src, location.x() as i64, location.y() as i64);
255 }
256
257 Ok(Atlas {
258 page_count,
259 size,
260 mip_level_count,
261 textures,
262 texcoords,
263 })
264}
265
266#[inline]
267fn create_atlas_mip_with_padding<I>(
268 max_page_count: u32,
269 size: u32,
270 filter: AtlasMipFilter,
271 padding: u32,
272 entries: &[AtlasEntry<I>],
273) -> Result<Atlas<I::Pixel>, AtlasError>
274where
275 I: image::GenericImage,
276 I::Pixel: 'static,
277{
278 if max_page_count == 0 {
279 return Err(AtlasError::ZeroMaxPageCount);
280 }
281
282 if !size.is_power_of_two() {
283 return Err(AtlasError::InvalidSize(size));
284 }
285
286 if entries.is_empty() {
287 return Err(AtlasError::ZeroEntry);
288 }
289
290 let mut rects = rectangle_pack::GroupedRectsToPlace::<_, ()>::new();
291 for (i, entry) in entries.iter().enumerate() {
292 let rect = rectangle_pack::RectToInsert::new(
293 entry.texture.width() + padding * 2,
294 entry.texture.height() + padding * 2,
295 1,
296 );
297 rects.push_rect(i, None, rect);
298 }
299
300 let mut target_bins = BTreeMap::new();
301 for i in 0..max_page_count {
302 target_bins.insert(i, rectangle_pack::TargetBin::new(size, size, 1));
303 }
304
305 let locations = rectangle_pack::pack_rects(
306 &rects,
307 &mut target_bins,
308 &rectangle_pack::volume_heuristic,
309 &rectangle_pack::contains_smallest_box,
310 )?;
311
312 let mut page_count = 0;
313 let mut texcoords = vec![Texcoord::default(); entries.len()];
314 for (&i, &(page, location)) in locations.packed_locations() {
315 page_count = u32::max(page_count, page + 1);
316
317 let texcoord = Texcoord {
318 page,
319 min_x: location.x() + padding,
320 min_y: location.y() + padding,
321 max_x: location.x() + location.width() - padding,
322 max_y: location.y() + location.height() - padding,
323 size,
324 };
325 texcoords[i] = texcoord;
326 }
327
328 let mip_level_count = size.ilog2() + 1;
329 let mut textures = vec![Texture::new(size, mip_level_count); page_count as usize];
330 for (&i, &(page, location)) in locations.packed_locations() {
331 let entry = &entries[i];
332
333 let src = resample(
334 &entry.texture,
335 entry.mip,
336 padding,
337 padding,
338 location.width(),
339 location.height(),
340 );
341
342 let target = &mut textures[page as usize].mip_maps[0];
343 image::imageops::replace(target, &src, location.x() as i64, location.y() as i64);
344 }
345
346 for mip_level in 1..mip_level_count {
347 let size = size >> mip_level;
348
349 for page in 0..page_count {
350 let src = &textures[page as usize].mip_maps[0];
351
352 let mip_map = image::imageops::resize(src, size, size, filter.into());
353
354 let target = &mut textures[page as usize].mip_maps[mip_level as usize];
355 image::imageops::replace(target, &mip_map, 0, 0);
356 }
357 }
358
359 Ok(Atlas {
360 page_count,
361 size,
362 mip_level_count,
363 textures,
364 texcoords,
365 })
366}
367
368#[inline]
369fn create_atlas_mip_with_block<I>(
370 max_page_count: u32,
371 size: u32,
372 filter: AtlasMipFilter,
373 block_size: u32,
374 entries: &[AtlasEntry<I>],
375) -> Result<Atlas<I::Pixel>, AtlasError>
376where
377 I: image::GenericImage,
378 I::Pixel: 'static,
379{
380 if max_page_count == 0 {
381 return Err(AtlasError::ZeroMaxPageCount);
382 }
383
384 if !size.is_power_of_two() {
385 return Err(AtlasError::InvalidSize(size));
386 }
387
388 if !block_size.is_power_of_two() {
389 return Err(AtlasError::InvalidBlockSize(block_size));
390 }
391
392 if entries.is_empty() {
393 return Err(AtlasError::ZeroEntry);
394 }
395
396 let padding = block_size >> 1;
397
398 let mut rects = rectangle_pack::GroupedRectsToPlace::<_, ()>::new();
399 for (i, entry) in entries.iter().enumerate() {
400 let rect = rectangle_pack::RectToInsert::new(
401 ((entry.texture.width() + block_size) as f32 / block_size as f32).ceil() as u32,
402 ((entry.texture.height() + block_size) as f32 / block_size as f32).ceil() as u32,
403 1,
404 );
405 rects.push_rect(i, None, rect);
406 }
407
408 let bin_size = size / block_size;
409 let mut target_bins = BTreeMap::new();
410 for i in 0..max_page_count {
411 target_bins.insert(i, rectangle_pack::TargetBin::new(bin_size, bin_size, 1));
412 }
413
414 let locations = rectangle_pack::pack_rects(
415 &rects,
416 &mut target_bins,
417 &rectangle_pack::volume_heuristic,
418 &rectangle_pack::contains_smallest_box,
419 )?;
420
421 let mut page_count = 0;
422 let mut texcoords = vec![Texcoord::default(); entries.len()];
423 for (&i, &(page, location)) in locations.packed_locations() {
424 page_count = u32::max(page_count, page + 1);
425
426 let texcoord = Texcoord {
427 page,
428 min_x: location.x() * block_size + padding,
429 min_y: location.y() * block_size + padding,
430 max_x: location.x() * block_size + padding + entries[i].texture.width(),
431 max_y: location.y() * block_size + padding + entries[i].texture.height(),
432 size,
433 };
434 texcoords[i] = texcoord;
435 }
436
437 let mip_level_count = block_size.ilog2() + 1;
438 let mut textures = vec![Texture::new(size, mip_level_count); page_count as usize];
439 for (&i, &(page, location)) in locations.packed_locations() {
440 let entry = &entries[i];
441
442 let src = resample(
443 &entry.texture,
444 entry.mip,
445 padding,
446 padding,
447 location.width() * block_size,
448 location.height() * block_size,
449 );
450
451 for mip_level in 0..mip_level_count {
452 let width = src.width() >> mip_level;
453 let height = src.height() >> mip_level;
454 let mip_map = image::imageops::resize(&src, width, height, filter.into());
455
456 let target = &mut textures[page as usize].mip_maps[mip_level as usize];
457 let x = location.x() as i64 * (block_size >> mip_level) as i64;
458 let y = location.y() as i64 * (block_size >> mip_level) as i64;
459 image::imageops::replace(target, &mip_map, x, y);
460 }
461 }
462
463 Ok(Atlas {
464 page_count,
465 size,
466 mip_level_count,
467 textures,
468 texcoords,
469 })
470}
471
472#[inline]
473#[rustfmt::skip]
474fn resample<I>(
475 src: &I,
476 mip: AtlasEntryMipOption,
477 shift_x: u32,
478 shift_y: u32,
479 width: u32,
480 height: u32,
481) -> image::ImageBuffer<I::Pixel, Vec<<I::Pixel as image::Pixel>::Subpixel>>
482where
483 I: image::GenericImage,
484{
485 let mut target = image::ImageBuffer::new(width, height);
486 match mip {
487 AtlasEntryMipOption::Clamp => {
488 for x in 0..width {
489 for y in 0..height {
490 let sx = (x as i32 - shift_x as i32).max(0).min(src.width() as i32 - 1);
491 let sy = (y as i32 - shift_y as i32).max(0).min(src.height() as i32 - 1);
492 *target.get_pixel_mut(x, y) = src.get_pixel(sx as u32, sy as u32);
493 }
494 }
495 }
496 AtlasEntryMipOption::Repeat => {
497 for x in 0..width {
498 for y in 0..height {
499 let sx = (x as i32 - shift_x as i32).rem_euclid(src.width() as i32);
500 let sy = (y as i32 - shift_y as i32).rem_euclid(src.height() as i32);
501 *target.get_pixel_mut(x, y) = src.get_pixel(sx as u32, sy as u32);
502 }
503 }
504 }
505 AtlasEntryMipOption::Mirror => {
506 for x in 0..width {
507 for y in 0..height {
508 let xx = (x as i32 - shift_x as i32).div_euclid(src.width() as i32);
509 let yy = (y as i32 - shift_y as i32).div_euclid(src.height() as i32);
510 let mut sx = (x as i32 - shift_x as i32).rem_euclid(src.width() as i32);
511 let mut sy = (y as i32 - shift_y as i32).rem_euclid(src.height() as i32);
512 if xx & 1 == 0 { sx = src.width() as i32 - 1 - sx; }
513 if yy & 1 == 0 { sy = src.height() as i32 - 1 - sy; }
514 *target.get_pixel_mut(x, y) = src.get_pixel(sx as u32, sy as u32);
515 }
516 }
517 }
518 }
519 target
520}
521
522#[derive(Clone, Default)]
530pub struct Atlas<P: image::Pixel> {
531 pub page_count: u32,
532 pub size: u32,
533 pub mip_level_count: u32,
534 pub textures: Vec<Texture<P>>,
535 pub texcoords: Vec<Texcoord>,
536}
537
538impl<P> fmt::Debug for Atlas<P>
539where
540 P: image::Pixel + fmt::Debug,
541 P::Subpixel: fmt::Debug,
542{
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 f.debug_struct("Atlas")
545 .field("page_count", &self.page_count)
546 .field("size", &self.size)
547 .field("mip_level_count", &self.mip_level_count)
548 .field("textures", &self.textures)
549 .field("texcoords", &self.texcoords)
550 .finish()
551 }
552}
553
554#[derive(Clone, Default)]
560pub struct Texture<P: image::Pixel> {
561 pub size: u32,
562 pub mip_level_count: u32,
563 pub mip_maps: Vec<image::ImageBuffer<P, Vec<P::Subpixel>>>,
564}
565
566impl<P: image::Pixel> Texture<P> {
567 #[inline]
568 pub fn new(size: u32, mip_level_count: u32) -> Self {
569 let mip_maps = (0..mip_level_count)
570 .map(|mip_level| size >> mip_level)
571 .map(|size| image::ImageBuffer::new(size, size))
572 .collect::<Vec<_>>();
573 Self {
574 size,
575 mip_level_count,
576 mip_maps,
577 }
578 }
579}
580
581impl<P> fmt::Debug for Texture<P>
582where
583 P: image::Pixel + fmt::Debug,
584 P::Subpixel: fmt::Debug,
585{
586 #[inline]
587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588 f.debug_struct("Texture")
589 .field("size", &self.size)
590 .field("mip_level_count", &self.mip_level_count)
591 .field("mip_maps", &self.mip_maps)
592 .finish()
593 }
594}
595
596#[repr(C)]
606#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
607#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
608pub struct Texcoord {
609 pub page: u32,
610 pub min_x: u32,
611 pub min_y: u32,
612 pub max_x: u32,
613 pub max_y: u32,
614 pub size: u32,
615}
616
617impl Texcoord {
618 #[inline]
620 pub fn to_f32(self) -> Texcoord32 {
621 Texcoord32 {
622 page: self.page,
623 min_x: self.min_x as f32 / self.size as f32,
624 min_y: self.min_y as f32 / self.size as f32,
625 max_x: self.max_x as f32 / self.size as f32,
626 max_y: self.max_y as f32 / self.size as f32,
627 }
628 }
629
630 #[inline]
632 pub fn to_f64(self) -> Texcoord64 {
633 Texcoord64 {
634 page: self.page,
635 min_x: self.min_x as f64 / self.size as f64,
636 min_y: self.min_y as f64 / self.size as f64,
637 max_x: self.max_x as f64 / self.size as f64,
638 max_y: self.max_y as f64 / self.size as f64,
639 }
640 }
641}
642
643#[repr(C)]
651#[derive(Clone, Copy, PartialEq, Default, Debug)]
652#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
653pub struct Texcoord32 {
654 pub page: u32,
655 pub min_x: f32,
656 pub min_y: f32,
657 pub max_x: f32,
658 pub max_y: f32,
659}
660
661impl From<Texcoord> for Texcoord32 {
662 #[inline]
663 fn from(value: Texcoord) -> Self {
664 value.to_f32()
665 }
666}
667
668#[repr(C)]
676#[derive(Clone, Copy, PartialEq, Default, Debug)]
677#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
678pub struct Texcoord64 {
679 pub page: u32,
680 pub min_x: f64,
681 pub min_y: f64,
682 pub max_x: f64,
683 pub max_y: f64,
684}
685
686impl From<Texcoord> for Texcoord64 {
687 #[inline]
688 fn from(value: Texcoord) -> Self {
689 value.to_f64()
690 }
691}
692
693#[derive(Debug)]
703pub enum AtlasError {
704 ZeroMaxPageCount,
705 InvalidSize(u32),
706 InvalidBlockSize(u32),
707 ZeroEntry,
708 Packing(rectangle_pack::RectanglePackError),
709}
710
711impl fmt::Display for AtlasError {
712 #[rustfmt::skip]
713 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714 match self {
715 AtlasError::ZeroMaxPageCount => write!(f, "max page count is zero."),
716 AtlasError::InvalidSize(size) => write!(f, "size is not power of two: {}.", size),
717 AtlasError::InvalidBlockSize(block_size) => write!(f, "block size is not power of two: {}.", block_size),
718 AtlasError::ZeroEntry => write!(f, "entry is empty."),
719 AtlasError::Packing(err) => err.fmt(f),
720 }
721 }
722}
723
724impl error::Error for AtlasError {}
725
726impl From<rectangle_pack::RectanglePackError> for AtlasError {
727 fn from(value: rectangle_pack::RectanglePackError) -> Self {
728 AtlasError::Packing(value)
729 }
730}