Make the font atlas use a color image (#7298)

* [x] I have followed the instructions in the PR template

Splitting this out from the Parley work as requested. This removes
`FontImage` and makes the font atlas use a `ColorImage`. It converts
alpha to coverage at glyph-drawing time, not at delta-upload time.

This doesn't do much now, but will allow for color emoji rendering once
we start using Parley.

I've changed things around so that we pass in `text_alpha_to_coverage`
to the `Fonts` the same way we do with `pixels_per_point` and
`max_texture_side`, reusing the existing code to check if the setting
differs and recreating the font atlas if so. I'm not quite sure why this
wasn't done in the first place.

I've left `ImageData` as an enum for now, in case we want to add support
for more texture pixel formats in the future (which I personally think
would be worthwhile). If you'd like, I can just remove that enum
entirely.
This commit is contained in:
valadaptive 2025-07-04 07:15:48 -04:00 committed by GitHub
parent d94386de3d
commit 7ac137bfc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 147 additions and 243 deletions

View File

@ -564,19 +564,6 @@ impl Renderer {
);
Cow::Borrowed(&image.pixels)
}
epaint::ImageData::Font(image) => {
assert_eq!(
width as usize * height as usize,
image.pixels.len(),
"Mismatch between texture size and texel count"
);
profiling::scope!("font -> sRGBA");
Cow::Owned(
image
.srgba_pixels(Default::default())
.collect::<Vec<epaint::Color32>>(),
)
}
};
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());

View File

@ -78,7 +78,7 @@ impl Default for WrappedTextureManager {
// Will be filled in later
let font_id = tex_mngr.alloc(
"egui_font_texture".into(),
epaint::FontImage::new([0, 0]).into(),
epaint::ColorImage::filled([0, 0], Color32::TRANSPARENT).into(),
Default::default(),
);
assert_eq!(
@ -610,6 +610,8 @@ impl ContextImpl {
log::trace!("Adding new fonts");
}
let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage;
let mut is_new = false;
let fonts = self
@ -624,13 +626,14 @@ impl ContextImpl {
Fonts::new(
pixels_per_point,
max_texture_side,
text_alpha_from_coverage,
self.font_definitions.clone(),
)
});
{
profiling::scope!("Fonts::begin_pass");
fonts.begin_pass(pixels_per_point, max_texture_side);
fonts.begin_pass(pixels_per_point, max_texture_side, text_alpha_from_coverage);
}
if is_new && self.memory.options.preload_font_glyphs {
@ -1921,16 +1924,6 @@ impl Context {
}
}
pub(crate) fn reset_font_atlas(&self) {
let pixels_per_point = self.pixels_per_point();
let fonts = self.read(|ctx| {
ctx.fonts
.get(&pixels_per_point.into())
.map(|current_fonts| current_fonts.lock().fonts.definitions().clone())
});
self.memory_mut(|mem| mem.new_font_definitions = fonts);
}
/// Tell `egui` which fonts to use.
///
/// The default `egui` fonts only support latin and cyrillic alphabets,
@ -2066,19 +2059,10 @@ impl Context {
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
pub fn set_style_of(&self, theme: Theme, style: impl Into<Arc<Style>>) {
let style = style.into();
let mut recreate_font_atlas = false;
self.options_mut(|opt| {
let dest = match theme {
Theme::Dark => &mut opt.dark_style,
Theme::Light => &mut opt.light_style,
};
recreate_font_atlas =
dest.visuals.text_alpha_from_coverage != style.visuals.text_alpha_from_coverage;
*dest = style;
self.options_mut(|opt| match theme {
Theme::Dark => opt.dark_style = style,
Theme::Light => opt.light_style = style,
});
if recreate_font_atlas {
self.reset_font_atlas();
}
}
/// The [`crate::Visuals`] used by all subsequent windows, panels etc.
@ -2475,28 +2459,7 @@ impl ContextImpl {
}
// Inform the backend of all textures that have been updated (including font atlas).
let textures_delta = {
// HACK to get much nicer looking text in light mode.
// This assumes all text is black-on-white in light mode,
// and white-on-black in dark mode, which is not necessarily true,
// but often close enough.
// Of course this fails for cases when there is black-on-white text in dark mode,
// and white-on-black text in light mode.
let text_alpha_from_coverage =
self.memory.options.style().visuals.text_alpha_from_coverage;
let mut textures_delta = self.tex_manager.0.write().take_delta();
for (_, delta) in &mut textures_delta.set {
if let ImageData::Font(font) = &mut delta.image {
delta.image =
ImageData::Color(font.to_color_image(text_alpha_from_coverage).into());
}
}
textures_delta
};
let textures_delta = self.tex_manager.0.write().take_delta();
let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output);
@ -3094,17 +3057,9 @@ impl Context {
options.ui(ui);
let text_alpha_from_coverage_changed =
prev_options.style().visuals.text_alpha_from_coverage
!= options.style().visuals.text_alpha_from_coverage;
if options != prev_options {
self.options_mut(move |o| *o = options);
}
if text_alpha_from_coverage_changed {
ui.ctx().reset_font_atlas();
}
}
fn fonts_tweak_ui(&self, ui: &mut Ui) {

View File

@ -467,7 +467,7 @@ pub use emath::{
remap_clamp, vec2,
};
pub use epaint::{
ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback,
ClippedPrimitive, ColorImage, CornerRadius, ImageData, Margin, Mesh, PaintCallback,
PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex,
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta},

View File

@ -168,6 +168,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let fonts = egui::epaint::text::Fonts::new(
pixels_per_point,
max_texture_side,
egui::epaint::AlphaFromCoverage::default(),
egui::FontDefinitions::default(),
);
{
@ -210,7 +211,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let mut rng = rand::rng();
b.iter(|| {
fonts.begin_pass(pixels_per_point, max_texture_side);
fonts.begin_pass(
pixels_per_point,
max_texture_side,
egui::epaint::AlphaFromCoverage::default(),
);
// Delete a random character, simulating a user making an edit in a long file:
let mut new_string = string.clone();

View File

@ -534,23 +534,6 @@ impl Painter {
self.upload_texture_srgb(delta.pos, image.size, delta.options, data);
}
egui::ImageData::Font(image) => {
assert_eq!(
image.width() * image.height(),
image.pixels.len(),
"Mismatch between texture size and texel count"
);
let data: Vec<u8> = {
profiling::scope!("font -> sRGBA");
image
.srgba_pixels(Default::default())
.flat_map(|a| a.to_array())
.collect()
};
self.upload_texture_srgb(delta.pos, image.size, delta.options, &data);
}
};
}

View File

@ -1,8 +1,8 @@
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use epaint::{
ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions,
Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path,
AlphaFromCoverage, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke,
TessellationOptions, Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path,
};
#[global_allocator]
@ -66,7 +66,7 @@ fn tessellate_circles(c: &mut Criterion) {
let pixels_per_point = 2.0;
let options = TessellationOptions::default();
let atlas = TextureAtlas::new([4096, 256]);
let atlas = TextureAtlas::new([4096, 256], AlphaFromCoverage::default());
let font_tex_size = atlas.size();
let prepared_discs = atlas.prepared_discs();

View File

@ -7,24 +7,20 @@ use std::sync::Arc;
///
/// To load an image file, see [`ColorImage::from_rgba_unmultiplied`].
///
/// In order to paint the image on screen, you first need to convert it to
/// This is currently an enum with only one variant, but more image types may be added in the future.
///
/// See also: [`ColorImage`], [`FontImage`].
#[derive(Clone, PartialEq)]
/// See also: [`ColorImage`].
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ImageData {
/// RGBA image.
Color(Arc<ColorImage>),
/// Used for the font texture.
Font(FontImage),
}
impl ImageData {
pub fn size(&self) -> [usize; 2] {
match self {
Self::Color(image) => image.size,
Self::Font(image) => image.size,
}
}
@ -38,7 +34,7 @@ impl ImageData {
pub fn bytes_per_pixel(&self) -> usize {
match self {
Self::Color(_) | Self::Font(_) => 4,
Self::Color(_) => 4,
}
}
}
@ -271,6 +267,37 @@ impl ColorImage {
}
Self::new([width, height], output)
}
/// Clone a sub-region as a new image.
pub fn region_by_pixels(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
assert!(
x + w <= self.width(),
"x + w should be <= self.width(), but x: {}, w: {}, width: {}",
x,
w,
self.width()
);
assert!(
y + h <= self.height(),
"y + h should be <= self.height(), but y: {}, h: {}, height: {}",
y,
h,
self.height()
);
let mut pixels = Vec::with_capacity(w * h);
for y in y..y + h {
let offset = y * self.width() + x;
pixels.extend(&self.pixels[offset..(offset + w)]);
}
assert_eq!(
pixels.len(),
w * h,
"pixels.len should be w * h, but got {}",
pixels.len()
);
Self::new([w, h], pixels)
}
}
impl std::ops::Index<(usize, usize)> for ColorImage {
@ -371,127 +398,12 @@ impl AlphaFromCoverage {
}
}
/// A single-channel image designed for the font texture.
///
/// Each value represents "coverage", i.e. how much a texel is covered by a character.
///
/// This is roughly interpreted as the opacity of a white image.
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct FontImage {
/// width, height
pub size: [usize; 2],
/// The coverage value.
///
/// Often you want to use [`Self::srgba_pixels`] instead.
pub pixels: Vec<f32>,
}
impl FontImage {
pub fn new(size: [usize; 2]) -> Self {
Self {
size,
pixels: vec![0.0; size[0] * size[1]],
}
}
#[inline]
pub fn width(&self) -> usize {
self.size[0]
}
#[inline]
pub fn height(&self) -> usize {
self.size[1]
}
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
#[inline]
pub fn srgba_pixels(
&self,
alpha_from_coverage: AlphaFromCoverage,
) -> impl ExactSizeIterator<Item = Color32> + '_ {
self.pixels
.iter()
.map(move |&coverage| alpha_from_coverage.color_from_coverage(coverage))
}
/// Convert this coverage image to a [`ColorImage`].
pub fn to_color_image(&self, alpha_from_coverage: AlphaFromCoverage) -> ColorImage {
profiling::function_scope!();
let pixels = self.srgba_pixels(alpha_from_coverage).collect();
ColorImage::new(self.size, pixels)
}
/// Clone a sub-region as a new image.
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
assert!(
x + w <= self.width(),
"x + w should be <= self.width(), but x: {}, w: {}, width: {}",
x,
w,
self.width()
);
assert!(
y + h <= self.height(),
"y + h should be <= self.height(), but y: {}, h: {}, height: {}",
y,
h,
self.height()
);
let mut pixels = Vec::with_capacity(w * h);
for y in y..y + h {
let offset = y * self.width() + x;
pixels.extend(&self.pixels[offset..(offset + w)]);
}
assert_eq!(
pixels.len(),
w * h,
"pixels.len should be w * h, but got {}",
pixels.len()
);
Self {
size: [w, h],
pixels,
}
}
}
impl std::ops::Index<(usize, usize)> for FontImage {
type Output = f32;
#[inline]
fn index(&self, (x, y): (usize, usize)) -> &f32 {
let [w, h] = self.size;
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
&self.pixels[y * w + x]
}
}
impl std::ops::IndexMut<(usize, usize)> for FontImage {
#[inline]
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 {
let [w, h] = self.size;
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
&mut self.pixels[y * w + x]
}
}
impl From<FontImage> for ImageData {
#[inline(always)]
fn from(image: FontImage) -> Self {
Self::Font(image)
}
}
// ----------------------------------------------------------------------------
/// A change to an image.
///
/// Either a whole new image, or an update to a rectangular region of it.
#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[must_use = "The painter must take care of this"]
pub struct ImageDelta {

View File

@ -50,7 +50,7 @@ pub use self::{
color::ColorMode,
corner_radius::CornerRadius,
corner_radius_f32::CornerRadiusF32,
image::{AlphaFromCoverage, ColorImage, FontImage, ImageData, ImageDelta},
image::{AlphaFromCoverage, ColorImage, ImageData, ImageDelta},
margin::Margin,
margin_f32::*,
mesh::{Mesh, Mesh16, Vertex},

View File

@ -179,7 +179,12 @@ mod tests {
#[test]
fn text_bounding_box_under_rotation() {
let fonts = Fonts::new(1.0, 1024, FontDefinitions::default());
let fonts = Fonts::new(
1.0,
1024,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);
let font = FontId::monospace(12.0);
let mut t = crate::Shape::text(

View File

@ -279,12 +279,13 @@ impl FontImpl {
} else {
let glyph_pos = {
let atlas = &mut self.atlas.lock();
let text_alpha_from_coverage = atlas.text_alpha_from_coverage;
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
glyph.draw(|x, y, v| {
if 0.0 < v {
let px = glyph_pos.0 + x as usize;
let py = glyph_pos.1 + y as usize;
image[(px, py)] = v;
image[(px, py)] = text_alpha_from_coverage.color_from_coverage(v);
}
});
glyph_pos

View File

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, sync::Arc};
use crate::{
TextureAtlas,
AlphaFromCoverage, TextureAtlas,
mutex::{Mutex, MutexGuard},
text::{
Galley, LayoutJob, LayoutSection,
@ -430,36 +430,56 @@ impl Fonts {
pub fn new(
pixels_per_point: f32,
max_texture_side: usize,
text_alpha_from_coverage: AlphaFromCoverage,
definitions: FontDefinitions,
) -> Self {
let fonts_and_cache = FontsAndCache {
fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions),
fonts: FontsImpl::new(
pixels_per_point,
max_texture_side,
text_alpha_from_coverage,
definitions,
),
galley_cache: Default::default(),
};
Self(Arc::new(Mutex::new(fonts_and_cache)))
}
/// Call at the start of each frame with the latest known
/// `pixels_per_point` and `max_texture_side`.
/// `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`.
///
/// Call after painting the previous frame, but before using [`Fonts`] for the new frame.
///
/// This function will react to changes in `pixels_per_point` and `max_texture_side`,
/// This function will react to changes in `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`,
/// as well as notice when the font atlas is getting full, and handle that.
pub fn begin_pass(&self, pixels_per_point: f32, max_texture_side: usize) {
pub fn begin_pass(
&self,
pixels_per_point: f32,
max_texture_side: usize,
text_alpha_from_coverage: AlphaFromCoverage,
) {
let mut fonts_and_cache = self.0.lock();
let pixels_per_point_changed = fonts_and_cache.fonts.pixels_per_point != pixels_per_point;
let max_texture_side_changed = fonts_and_cache.fonts.max_texture_side != max_texture_side;
let text_alpha_from_coverage_changed =
fonts_and_cache.fonts.atlas.lock().text_alpha_from_coverage != text_alpha_from_coverage;
let font_atlas_almost_full = fonts_and_cache.fonts.atlas.lock().fill_ratio() > 0.8;
let needs_recreate =
pixels_per_point_changed || max_texture_side_changed || font_atlas_almost_full;
let needs_recreate = pixels_per_point_changed
|| max_texture_side_changed
|| text_alpha_from_coverage_changed
|| font_atlas_almost_full;
if needs_recreate {
let definitions = fonts_and_cache.fonts.definitions.clone();
*fonts_and_cache = FontsAndCache {
fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions),
fonts: FontsImpl::new(
pixels_per_point,
max_texture_side,
text_alpha_from_coverage,
definitions,
),
galley_cache: Default::default(),
};
}
@ -497,7 +517,7 @@ impl Fonts {
/// The full font atlas image.
#[inline]
pub fn image(&self) -> crate::FontImage {
pub fn image(&self) -> crate::ColorImage {
self.lock().fonts.atlas.lock().image().clone()
}
@ -642,6 +662,7 @@ impl FontsImpl {
pub fn new(
pixels_per_point: f32,
max_texture_side: usize,
text_alpha_from_coverage: AlphaFromCoverage,
definitions: FontDefinitions,
) -> Self {
assert!(
@ -651,7 +672,7 @@ impl FontsImpl {
let texture_width = max_texture_side.at_most(16 * 1024);
let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways.
let atlas = TextureAtlas::new([texture_width, initial_height]);
let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage);
let atlas = Arc::new(Mutex::new(atlas));
@ -1120,6 +1141,7 @@ mod tests {
let mut fonts = FontsImpl::new(
pixels_per_point,
max_texture_side,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);

View File

@ -1034,11 +1034,18 @@ fn is_cjk_break_allowed(c: char) -> bool {
#[cfg(test)]
mod tests {
use crate::AlphaFromCoverage;
use super::{super::*, *};
#[test]
fn test_zero_max_width() {
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
let mut fonts = FontsImpl::new(
1.0,
1024,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);
let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default());
layout_job.wrap.max_width = 0.0;
let galley = layout(&mut fonts, layout_job.into());
@ -1049,7 +1056,12 @@ mod tests {
fn test_truncate_with_newline() {
// No matter where we wrap, we should be appending the newline character.
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
let mut fonts = FontsImpl::new(
1.0,
1024,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);
let text_format = TextFormat {
font_id: FontId::monospace(12.0),
..Default::default()
@ -1094,7 +1106,12 @@ mod tests {
#[test]
fn test_cjk() {
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
let mut fonts = FontsImpl::new(
1.0,
1024,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);
let mut layout_job = LayoutJob::single_section(
"日本語とEnglishの混在した文章".into(),
TextFormat::default(),
@ -1109,7 +1126,12 @@ mod tests {
#[test]
fn test_pre_cjk() {
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
let mut fonts = FontsImpl::new(
1.0,
1024,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);
let mut layout_job = LayoutJob::single_section(
"日本語とEnglishの混在した文章".into(),
TextFormat::default(),
@ -1124,7 +1146,12 @@ mod tests {
#[test]
fn test_truncate_width() {
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
let mut fonts = FontsImpl::new(
1.0,
1024,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);
let mut layout_job =
LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default());
layout_job.wrap.max_width = f32::INFINITY;

View File

@ -1,6 +1,7 @@
use ecolor::Color32;
use emath::{Rect, remap_clamp};
use crate::{FontImage, ImageDelta};
use crate::{AlphaFromCoverage, ColorImage, ImageDelta};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Rectu {
@ -57,7 +58,7 @@ pub struct PreparedDisc {
/// More characters can be added, possibly expanding the texture.
#[derive(Clone)]
pub struct TextureAtlas {
image: FontImage,
image: ColorImage,
/// What part of the image that is dirty
dirty: Rectu,
@ -72,18 +73,22 @@ pub struct TextureAtlas {
/// pre-rasterized discs of radii `2^i`, where `i` is the index.
discs: Vec<PrerasterizedDisc>,
/// Controls how to convert glyph coverage to alpha.
pub(crate) text_alpha_from_coverage: AlphaFromCoverage,
}
impl TextureAtlas {
pub fn new(size: [usize; 2]) -> Self {
pub fn new(size: [usize; 2], text_alpha_from_coverage: AlphaFromCoverage) -> Self {
assert!(size[0] >= 1024, "Tiny texture atlas");
let mut atlas = Self {
image: FontImage::new(size),
image: ColorImage::filled(size, Color32::TRANSPARENT),
dirty: Rectu::EVERYTHING,
cursor: (0, 0),
row_height: 0,
overflowed: false,
discs: vec![], // will be filled in below
text_alpha_from_coverage,
};
// Make the top left pixel fully white for `WHITE_UV`, i.e. painting something with solid color:
@ -93,7 +98,7 @@ impl TextureAtlas {
(0, 0),
"Expected the first allocation to be at (0, 0), but was at {pos:?}"
);
image[pos] = 1.0;
image[pos] = Color32::WHITE;
// Allocate a series of anti-aliased discs used to render small filled circles:
// TODO(emilk): these circles can be packed A LOT better.
@ -116,7 +121,7 @@ impl TextureAtlas {
let coverage =
remap_clamp(distance_to_center, (r - 0.5)..=(r + 0.5), 1.0..=0.0);
image[((x as i32 + hw + dx) as usize, (y as i32 + hw + dy) as usize)] =
coverage;
text_alpha_from_coverage.color_from_coverage(coverage);
}
}
atlas.discs.push(PrerasterizedDisc {
@ -184,7 +189,7 @@ impl TextureAtlas {
/// The full font atlas image.
#[inline]
pub fn image(&self) -> &FontImage {
pub fn image(&self) -> &ColorImage {
&self.image
}
@ -200,14 +205,14 @@ impl TextureAtlas {
} else {
let pos = [dirty.min_x, dirty.min_y];
let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y];
let region = self.image.region(pos, size);
let region = self.image.region_by_pixels(pos, size);
Some(ImageDelta::partial(pos, region, texture_options))
}
}
/// Returns the coordinates of where the rect ended up,
/// and invalidates the region.
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) {
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut ColorImage) {
/// On some low-precision GPUs (my old iPad) characters get muddled up
/// if we don't add some empty pixels between the characters.
/// On modern high-precision GPUs this is not needed.
@ -254,13 +259,15 @@ impl TextureAtlas {
}
}
fn resize_to_min_height(image: &mut FontImage, required_height: usize) -> bool {
fn resize_to_min_height(image: &mut ColorImage, required_height: usize) -> bool {
while required_height >= image.height() {
image.size[1] *= 2; // double the height
}
if image.width() * image.height() > image.pixels.len() {
image.pixels.resize(image.width() * image.height(), 0.0);
image
.pixels
.resize(image.width() * image.height(), Color32::TRANSPARENT);
true
} else {
false

View File

@ -271,7 +271,7 @@ pub enum TextureWrapMode {
/// What has been allocated and freed during the last period.
///
/// These are commands given to the integration painter.
#[derive(Clone, Default, PartialEq)]
#[derive(Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[must_use = "The painter must take care of this"]
pub struct TexturesDelta {