crop text images that are too big

This commit is contained in:
2025-11-21 18:18:28 -05:00
parent 97b284e81e
commit 23c5abe5a9
7 changed files with 97 additions and 79 deletions

View File

@@ -1,12 +1,11 @@
use cosmic_text::{
Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCache, SwashContent,
};
use image::{Rgba, RgbaImage};
use std::simd::{Simd, num::SimdUint};
use crate::{
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2},
util::HashMap,
use crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2};
use cosmic_text::{
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
SwashContent,
};
use image::{GenericImageView, RgbaImage};
/// TODO: properly wrap this
pub mod text_lib {
@@ -16,6 +15,7 @@ pub mod text_lib {
pub struct TextData {
pub font_system: FontSystem,
pub swash_cache: SwashCache,
glyph_cache: Vec<(Placement, CacheKey, Color)>,
}
impl Default for TextData {
@@ -23,6 +23,7 @@ impl Default for TextData {
Self {
font_system: FontSystem::new(),
swash_cache: SwashCache::new(),
glyph_cache: Default::default(),
}
}
}
@@ -76,29 +77,30 @@ impl TextData {
buffer: &mut TextBuffer,
attrs: &TextAttrs,
textures: &mut Textures,
) -> TextTexture {
) -> RenderedText {
// TODO: either this or the layout stuff (or both) is super slow,
// should probably do texture packing and things if possible.
// very visible if you add just a couple of wrapping texts and resize window
// should also be timed to figure out exactly what points need to be sped up
let mut pixels = HashMap::<_, [u8; 4]>::default();
// let mut pixels = HashMap::<_, [u8; 4]>::default();
let mut min_x = 0;
let mut min_y = 0;
let mut max_x = 0;
let mut max_y = 0;
let cosmic_color = {
let text_color = {
let c = attrs.color;
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
};
let mut max_width = 0.0f32;
let mut height = 0.0;
for run in buffer.layout_runs() {
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => cosmic_color,
None => text_color,
};
if let Some(img) = self
@@ -112,35 +114,8 @@ impl TextData {
min_y = min_y.min(pos.top);
max_x = max_x.max(pos.left + pos.width as i32);
max_y = max_y.max(pos.top + pos.height as i32);
let mut merge = |i, color: [u8; 4]| {
let x = i % pos.width as usize;
let y = i / pos.width as usize;
let pos = (x as i32 + pos.left, y as i32 + pos.top);
if let Some(pixel) = pixels.get_mut(&pos) {
for i in 0..4 {
// TODO: no clue if proper alpha blending should be done
pixel[i] = color[i].saturating_add(pixel[i]);
}
} else {
pixels.insert(pos, color);
}
};
match img.content {
SwashContent::Mask => {
for (i, a) in img.data.iter().enumerate() {
let mut color = glyph_color.as_rgba();
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
merge(i, color);
}
}
SwashContent::SubpixelMask => todo!(),
SwashContent::Color => {
let (colors, _) = img.data.as_chunks::<4>();
for (i, color) in colors.iter().enumerate() {
merge(i, *color);
}
}
}
self.glyph_cache
.push((pos, physical_glyph.cache_key, glyph_color));
}
}
max_width = max_width.max(run.line_w);
@@ -150,12 +125,52 @@ impl TextData {
let img_height = (max_y - min_y + 1) as u32;
let mut image = RgbaImage::new(img_width, img_height);
for ((x, y), color) in pixels {
let x = (x - min_x) as u32;
let y = (y - min_y) as u32;
image.put_pixel(x, y, Rgba(color));
for (pos, key, color) in self.glyph_cache.drain(..) {
let img = self
.swash_cache
.get_image(&mut self.font_system, key)
.as_ref()
.unwrap();
let mut merge = |i, color: [u8; 4]| {
let i = i as i32;
let x = (i % pos.width as i32 + pos.left - min_x) as u32;
let y = (i / pos.width as i32 + pos.top - min_y) as u32;
let pixel = &mut image[(x, y)].0;
// TODO: no clue if proper alpha blending should be done
*pixel = Simd::from(color).saturating_add(Simd::from(*pixel)).into();
};
match img.content {
SwashContent::Mask => {
for (i, a) in img.data.iter().enumerate() {
let mut color = color.as_rgba();
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
merge(i, color);
}
}
SwashContent::SubpixelMask => todo!("subpixel mask text rendering"),
SwashContent::Color => {
let (colors, _) = img.data.as_chunks::<4>();
for (i, color) in colors.iter().enumerate() {
merge(i, *color);
}
}
}
}
TextTexture {
let max_dim = 8192;
if image.width() > max_dim || image.height() > max_dim {
let width = image.width().min(max_dim);
let height = image.height().min(max_dim);
eprintln!(
"WARNING: image of size {:?} cropped to {:?} (texture too big)",
image.dimensions(),
(width, height)
);
image = image.view(0, 0, width, height).to_image();
}
RenderedText {
handle: textures.add(image),
top_left_offset: Vec2::new(min_x as f32, min_y as f32),
size: Vec2::new(max_width, height),
@@ -164,9 +179,8 @@ impl TextData {
}
#[derive(Clone)]
pub struct TextTexture {
pub struct RenderedText {
pub handle: TextureHandle,
pub top_left_offset: Vec2,
pub size: Vec2,
}