makepad-draw 1.0.0

Makepad 2d drawing API
Documentation
use {
    super::{
        font::{FontId, GlyphId},
        geom::{Point, Rect, Size},
        image::{Image, Bgra, Subimage, SubimageMut, R},
        num::Zero,
    },
    std::{collections::HashMap, fs::File, io::BufWriter, path::Path, slice},
};

#[derive(Clone, Debug)]
pub struct FontAtlas<T> {
    needs_reset: bool,
    image: Image<T>,
    dirty_rect: Rect<usize>,
    current_point: Point<usize>,
    current_row_height: usize,
    cached_glyph_image_rects: HashMap<GlyphImageKey, Rect<usize>>,
}

impl<T> FontAtlas<T> {
    pub fn new(size: Size<usize>) -> Self
    where
        T: Clone + Default,
    {
        Self {
            needs_reset: false,
            image: Image::new(size),
            dirty_rect: Rect::ZERO,
            current_point: Point::ZERO,
            current_row_height: 0,
            cached_glyph_image_rects: HashMap::new(),
        }
    }

    pub fn needs_reset(&self) -> bool {
        self.needs_reset
    }

    pub fn size(&self) -> Size<usize> {
        self.image.size()
    }

    pub fn dirty_rect(&self) -> Rect<usize> {
        self.dirty_rect
    }

    pub fn image(&self) -> &Image<T> {
        &self.image
    }

    pub unsafe fn replace_pixels(&mut self, pixels: Vec<T>) -> Vec<T> {
        self.image.replace_pixels(pixels)
    }

    pub fn take_dirty_image(&mut self) -> Subimage<'_, T> {
        let dirty_rect = self.dirty_rect;
        self.dirty_rect = Rect::ZERO;
        self.image.subimage(dirty_rect)
    }

    pub fn get_or_allocate_glyph_image(&mut self, key: GlyphImageKey) -> Option<GlyphImage<'_, T>> {
        if let Some(rect) = self.cached_glyph_image_rects.get(&key) {
            return Some(GlyphImage::Cached(*rect));
        }
        let rect = self.allocate_glyph_image(key.size)?;
        self.cached_glyph_image_rects.insert(key.clone(), rect);
        Some(GlyphImage::Allocated(self.image.subimage_mut(rect)))
    }

    fn allocate_glyph_image(&mut self, size: Size<usize>) -> Option<Rect<usize>> {
        if self.current_point.x + size.width > self.size().width {
            self.current_point.x = 0;
            self.current_point.y += self.current_row_height;
            self.current_row_height = 0;
        }
        if self.current_point.y + size.height > self.size().height {
            self.needs_reset = true;
            return None;
        }
        let origin = self.current_point;
        self.current_point.x += size.width;
        self.current_row_height = self.current_row_height.max(size.height);
        let rect = Rect::new(origin, size);
        self.dirty_rect = self.dirty_rect.union(rect);
        Some(rect)
    }

    pub fn reset_if_needed(&mut self) -> bool {
        if !self.needs_reset() {
            return false;
        }
        self.needs_reset = false;
        self.dirty_rect = Rect::ZERO;
        self.current_point = Point::ZERO;
        self.current_row_height = 0;
        self.cached_glyph_image_rects.clear();
        true
    }
}

pub type GrayscaleAtlas = FontAtlas<R>;

impl GrayscaleAtlas {
    pub fn save_to_png(&self, path: impl AsRef<Path>) {
        let file = File::create(path).unwrap();
        let writer = BufWriter::new(file);
        let size = self.size();
        let mut encoder = png::Encoder::new(writer, size.width as u32, size.height as u32);
        encoder.set_color(png::ColorType::Grayscale);
        encoder.set_depth(png::BitDepth::Eight);
        let mut writer = encoder.write_header().unwrap();
        let pixels = self.image.as_pixels();
        let data = unsafe { slice::from_raw_parts(pixels.as_ptr() as *const u8, pixels.len()) };
        writer.write_image_data(&data).unwrap();
    }
}

pub type ColorAtlas = FontAtlas<Bgra>;

impl ColorAtlas {
    pub fn save_to_png(&self, path: impl AsRef<Path>) {
        let file = File::create(path).unwrap();
        let writer = BufWriter::new(file);
        let size = self.size();
        let mut encoder = png::Encoder::new(writer, size.width as u32, size.height as u32);
        encoder.set_color(png::ColorType::Rgba);
        encoder.set_depth(png::BitDepth::Eight);
        let mut writer = encoder.write_header().unwrap();
        let pixels = self.image.as_pixels();
        let data = unsafe { slice::from_raw_parts(pixels.as_ptr() as *const u8, pixels.len() * 4) };
        writer.write_image_data(&data).unwrap();
    }
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct GlyphImageKey {
    pub font_id: FontId,
    pub glyph_id: GlyphId,
    pub size: Size<usize>,
}

#[derive(Debug)]
pub enum GlyphImage<'a, T> {
    Cached(Rect<usize>),
    Allocated(SubimageMut<'a, T>),
}