1use emath::{remap_clamp, Rect};
2
3use crate::{FontImage, ImageDelta};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6struct Rectu {
7 min_x: usize,
9
10 min_y: usize,
12
13 max_x: usize,
15
16 max_y: usize,
18}
19
20impl Rectu {
21 const NOTHING: Self = Self {
22 min_x: usize::MAX,
23 min_y: usize::MAX,
24 max_x: 0,
25 max_y: 0,
26 };
27 const EVERYTHING: Self = Self {
28 min_x: 0,
29 min_y: 0,
30 max_x: usize::MAX,
31 max_y: usize::MAX,
32 };
33}
34
35#[derive(Copy, Clone, Debug)]
36struct PrerasterizedDisc {
37 r: f32,
38 uv: Rectu,
39}
40
41#[derive(Copy, Clone, Debug)]
43pub struct PreparedDisc {
44 pub r: f32,
46
47 pub w: f32,
49
50 pub uv: Rect,
53}
54
55#[derive(Clone)]
59pub struct TextureAtlas {
60 image: FontImage,
61
62 dirty: Rectu,
64
65 cursor: (usize, usize),
67
68 row_height: usize,
69
70 overflowed: bool,
72
73 discs: Vec<PrerasterizedDisc>,
75}
76
77impl TextureAtlas {
78 pub fn new(size: [usize; 2]) -> Self {
79 assert!(size[0] >= 1024, "Tiny texture atlas");
80 let mut atlas = Self {
81 image: FontImage::new(size),
82 dirty: Rectu::EVERYTHING,
83 cursor: (0, 0),
84 row_height: 0,
85 overflowed: false,
86 discs: vec![], };
88
89 let (pos, image) = atlas.allocate((1, 1));
91 assert_eq!(pos, (0, 0));
92 image[pos] = 1.0;
93
94 const LARGEST_CIRCLE_RADIUS: f32 = 8.0; for i in 0.. {
102 let r = 2.0_f32.powf(i as f32 / 2.0 - 1.0);
103 if r > LARGEST_CIRCLE_RADIUS {
104 break;
105 }
106 let hw = (r + 0.5).ceil() as i32;
107 let w = (2 * hw + 1) as usize;
108 let ((x, y), image) = atlas.allocate((w, w));
109 for dx in -hw..=hw {
110 for dy in -hw..=hw {
111 let distance_to_center = ((dx * dx + dy * dy) as f32).sqrt();
112 let coverage =
113 remap_clamp(distance_to_center, (r - 0.5)..=(r + 0.5), 1.0..=0.0);
114 image[((x as i32 + hw + dx) as usize, (y as i32 + hw + dy) as usize)] =
115 coverage;
116 }
117 }
118 atlas.discs.push(PrerasterizedDisc {
119 r,
120 uv: Rectu {
121 min_x: x,
122 min_y: y,
123 max_x: x + w,
124 max_y: y + w,
125 },
126 });
127 }
128
129 atlas
130 }
131
132 pub fn size(&self) -> [usize; 2] {
133 self.image.size
134 }
135
136 pub fn prepared_discs(&self) -> Vec<PreparedDisc> {
138 let size = self.size();
139 let inv_w = 1.0 / size[0] as f32;
140 let inv_h = 1.0 / size[1] as f32;
141 self.discs
142 .iter()
143 .map(|disc| {
144 let r = disc.r;
145 let Rectu {
146 min_x,
147 min_y,
148 max_x,
149 max_y,
150 } = disc.uv;
151 let w = max_x - min_x;
152 let uv = Rect::from_min_max(
153 emath::pos2(min_x as f32 * inv_w, min_y as f32 * inv_h),
154 emath::pos2(max_x as f32 * inv_w, max_y as f32 * inv_h),
155 );
156 PreparedDisc { r, w: w as f32, uv }
157 })
158 .collect()
159 }
160
161 fn max_height(&self) -> usize {
162 self.image.height().max(self.image.width())
164 }
165
166 pub fn fill_ratio(&self) -> f32 {
168 if self.overflowed {
169 1.0
170 } else {
171 (self.cursor.1 + self.row_height) as f32 / self.max_height() as f32
172 }
173 }
174
175 #[inline]
177 pub fn texture_options() -> crate::textures::TextureOptions {
178 crate::textures::TextureOptions::LINEAR
179 }
180
181 #[inline]
183 pub fn image(&self) -> &FontImage {
184 &self.image
185 }
186
187 pub fn take_delta(&mut self) -> Option<ImageDelta> {
189 let texture_options = Self::texture_options();
190
191 let dirty = std::mem::replace(&mut self.dirty, Rectu::NOTHING);
192 if dirty == Rectu::NOTHING {
193 None
194 } else if dirty == Rectu::EVERYTHING {
195 Some(ImageDelta::full(self.image.clone(), texture_options))
196 } else {
197 let pos = [dirty.min_x, dirty.min_y];
198 let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y];
199 let region = self.image.region(pos, size);
200 Some(ImageDelta::partial(pos, region, texture_options))
201 }
202 }
203
204 pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) {
207 const PADDING: usize = 1;
211
212 assert!(
213 w <= self.image.width(),
214 "Tried to allocate a {} wide glyph in a {} wide texture atlas",
215 w,
216 self.image.width()
217 );
218 if self.cursor.0 + w > self.image.width() {
219 self.cursor.0 = 0;
221 self.cursor.1 += self.row_height + PADDING;
222 self.row_height = 0;
223 }
224
225 self.row_height = self.row_height.max(h);
226
227 let required_height = self.cursor.1 + self.row_height;
228
229 if required_height > self.max_height() {
230 #[cfg(feature = "log")]
233 log::warn!("epaint texture atlas overflowed!");
234
235 self.cursor = (0, self.image.height() / 3); self.overflowed = true; } else if resize_to_min_height(&mut self.image, required_height) {
238 self.dirty = Rectu::EVERYTHING;
239 }
240
241 let pos = self.cursor;
242 self.cursor.0 += w + PADDING;
243
244 self.dirty.min_x = self.dirty.min_x.min(pos.0);
245 self.dirty.min_y = self.dirty.min_y.min(pos.1);
246 self.dirty.max_x = self.dirty.max_x.max(pos.0 + w);
247 self.dirty.max_y = self.dirty.max_y.max(pos.1 + h);
248
249 (pos, &mut self.image)
250 }
251}
252
253fn resize_to_min_height(image: &mut FontImage, required_height: usize) -> bool {
254 while required_height >= image.height() {
255 image.size[1] *= 2; }
257
258 if image.width() * image.height() > image.pixels.len() {
259 image.pixels.resize(image.width() * image.height(), 0.0);
260 true
261 } else {
262 false
263 }
264}