1use ecolor::Color32;
2use emath::{Rect, remap_clamp};
3
4use crate::{AlphaFromCoverage, ColorImage, ImageDelta};
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7struct Rectu {
8    min_x: usize,
10
11    min_y: usize,
13
14    max_x: usize,
16
17    max_y: usize,
19}
20
21impl Rectu {
22    const NOTHING: Self = Self {
23        min_x: usize::MAX,
24        min_y: usize::MAX,
25        max_x: 0,
26        max_y: 0,
27    };
28    const EVERYTHING: Self = Self {
29        min_x: 0,
30        min_y: 0,
31        max_x: usize::MAX,
32        max_y: usize::MAX,
33    };
34}
35
36#[derive(Copy, Clone, Debug)]
37struct PrerasterizedDisc {
38    r: f32,
39    uv: Rectu,
40}
41
42#[derive(Copy, Clone, Debug)]
44pub struct PreparedDisc {
45    pub r: f32,
47
48    pub w: f32,
50
51    pub uv: Rect,
54}
55
56#[derive(Clone)]
60pub struct TextureAtlas {
61    image: ColorImage,
62
63    dirty: Rectu,
65
66    cursor: (usize, usize),
68
69    row_height: usize,
70
71    overflowed: bool,
73
74    discs: Vec<PrerasterizedDisc>,
76
77    pub(crate) text_alpha_from_coverage: AlphaFromCoverage,
79}
80
81impl TextureAtlas {
82    pub fn new(size: [usize; 2], text_alpha_from_coverage: AlphaFromCoverage) -> Self {
83        assert!(size[0] >= 1024, "Tiny texture atlas");
84        let mut atlas = Self {
85            image: ColorImage::filled(size, Color32::TRANSPARENT),
86            dirty: Rectu::EVERYTHING,
87            cursor: (0, 0),
88            row_height: 0,
89            overflowed: false,
90            discs: vec![], text_alpha_from_coverage,
92        };
93
94        let (pos, image) = atlas.allocate((1, 1));
96        assert_eq!(
97            pos,
98            (0, 0),
99            "Expected the first allocation to be at (0, 0), but was at {pos:?}"
100        );
101        image[pos] = Color32::WHITE;
102
103        const LARGEST_CIRCLE_RADIUS: f32 = 8.0; for i in 0.. {
111            let r = 2.0_f32.powf(i as f32 / 2.0 - 1.0);
112            if r > LARGEST_CIRCLE_RADIUS {
113                break;
114            }
115            let hw = (r + 0.5).ceil() as i32;
116            let w = (2 * hw + 1) as usize;
117            let ((x, y), image) = atlas.allocate((w, w));
118            for dx in -hw..=hw {
119                for dy in -hw..=hw {
120                    let distance_to_center = ((dx * dx + dy * dy) as f32).sqrt();
121                    let coverage =
122                        remap_clamp(distance_to_center, (r - 0.5)..=(r + 0.5), 1.0..=0.0);
123                    image[((x as i32 + hw + dx) as usize, (y as i32 + hw + dy) as usize)] =
124                        text_alpha_from_coverage.color_from_coverage(coverage);
125                }
126            }
127            atlas.discs.push(PrerasterizedDisc {
128                r,
129                uv: Rectu {
130                    min_x: x,
131                    min_y: y,
132                    max_x: x + w,
133                    max_y: y + w,
134                },
135            });
136        }
137
138        atlas
139    }
140
141    pub fn size(&self) -> [usize; 2] {
142        self.image.size
143    }
144
145    pub fn prepared_discs(&self) -> Vec<PreparedDisc> {
147        let size = self.size();
148        let inv_w = 1.0 / size[0] as f32;
149        let inv_h = 1.0 / size[1] as f32;
150        self.discs
151            .iter()
152            .map(|disc| {
153                let r = disc.r;
154                let Rectu {
155                    min_x,
156                    min_y,
157                    max_x,
158                    max_y,
159                } = disc.uv;
160                let w = max_x - min_x;
161                let uv = Rect::from_min_max(
162                    emath::pos2(min_x as f32 * inv_w, min_y as f32 * inv_h),
163                    emath::pos2(max_x as f32 * inv_w, max_y as f32 * inv_h),
164                );
165                PreparedDisc { r, w: w as f32, uv }
166            })
167            .collect()
168    }
169
170    fn max_height(&self) -> usize {
171        self.image.height().max(self.image.width())
173    }
174
175    pub fn fill_ratio(&self) -> f32 {
177        if self.overflowed {
178            1.0
179        } else {
180            (self.cursor.1 + self.row_height) as f32 / self.max_height() as f32
181        }
182    }
183
184    #[inline]
186    pub fn texture_options() -> crate::textures::TextureOptions {
187        crate::textures::TextureOptions::LINEAR
188    }
189
190    #[inline]
192    pub fn image(&self) -> &ColorImage {
193        &self.image
194    }
195
196    pub fn take_delta(&mut self) -> Option<ImageDelta> {
198        let texture_options = Self::texture_options();
199
200        let dirty = std::mem::replace(&mut self.dirty, Rectu::NOTHING);
201        if dirty == Rectu::NOTHING {
202            None
203        } else if dirty == Rectu::EVERYTHING {
204            Some(ImageDelta::full(self.image.clone(), texture_options))
205        } else {
206            let pos = [dirty.min_x, dirty.min_y];
207            let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y];
208            let region = self.image.region_by_pixels(pos, size);
209            Some(ImageDelta::partial(pos, region, texture_options))
210        }
211    }
212
213    pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut ColorImage) {
216        const PADDING: usize = 1;
220
221        assert!(
222            w <= self.image.width(),
223            "Tried to allocate a {} wide glyph in a {} wide texture atlas",
224            w,
225            self.image.width()
226        );
227        if self.cursor.0 + w > self.image.width() {
228            self.cursor.0 = 0;
230            self.cursor.1 += self.row_height + PADDING;
231            self.row_height = 0;
232        }
233
234        self.row_height = self.row_height.max(h);
235
236        let required_height = self.cursor.1 + self.row_height;
237
238        if required_height > self.max_height() {
239            log::warn!("epaint texture atlas overflowed!");
242
243            self.cursor = (0, self.image.height() / 3); self.overflowed = true; } else if resize_to_min_height(&mut self.image, required_height) {
246            self.dirty = Rectu::EVERYTHING;
247        }
248
249        let pos = self.cursor;
250        self.cursor.0 += w + PADDING;
251
252        self.dirty.min_x = self.dirty.min_x.min(pos.0);
253        self.dirty.min_y = self.dirty.min_y.min(pos.1);
254        self.dirty.max_x = self.dirty.max_x.max(pos.0 + w);
255        self.dirty.max_y = self.dirty.max_y.max(pos.1 + h);
256
257        (pos, &mut self.image)
258    }
259}
260
261fn resize_to_min_height(image: &mut ColorImage, required_height: usize) -> bool {
262    while required_height >= image.height() {
263        image.size[1] *= 2; }
265
266    if image.width() * image.height() > image.pixels.len() {
267        image
268            .pixels
269            .resize(image.width() * image.height(), Color32::TRANSPARENT);
270        true
271    } else {
272        false
273    }
274}