Pango does not give the location for certain graphemes - rust

I was writing a program to render text to an image and draw bounding boxes around characters using Pango, Cairo and PangoCairo. I am using the Rust bindings to these libraries called gtk-rs.
After laying out the next I am splitting the text into graphemes using unicode_segmentation and finding the position of these graphemes using index_to_pos which basically translates to pango_layout_index_to_pos. Here is the code I wrote to draw these bounding boxes.
use unicode_segmentation::UnicodeSegmentation;
use crate::ImageDims;
#[derive(Debug)]
pub struct BoundingBox {
pub x: i32,
pub y: i32,
pub height: i32,
pub width: i32,
pub akshara: String,
}
pub type BoundingBoxes = Vec<BoundingBox>;
pub fn get_bounding_boxes(layout: pango::Layout, dims: ImageDims) -> BoundingBoxes {
let mut boxes = BoundingBoxes::new();
let text = layout.text().unwrap();
for (idx, graphemes) in text.grapheme_indices(true) {
let rect = layout.index_to_pos(idx as i32);
boxes.push(BoundingBox {
x: rect.x(),
y: rect.y(),
height: rect.height(),
width: rect.width(),
akshara: graphemes.to_string(),
});
}
// adjust the values for the cairo context
boxes.iter_mut().for_each(|b| {
b.x = b.x / pango::SCALE + dims.padding;
b.y = b.y / pango::SCALE + dims.padding;
b.width = b.width / pango::SCALE;
b.height = b.height / pango::SCALE;
});
boxes
}
However, in the rendered image some of the characters do not have bounding boxes at all. For example । on the last line or ए in the last word. There are other abnormalities like भी in the third word.
Some characters do not have bounding boxes
How do I fix this?

Related

With Rust imageproc crate, how can i change the color per pixel when using draw_text?

I want to calculate per pixel color when drawing text.
draw_text doesn't take a closure ( lambda) for calculating the color so i thought to make my own canvas and make a custom draw_pixel.
sounds good but when for the life of me i cannot get a simple color to pixel conversion to compile here.
for example below, the c cannot be turned into a Pixel. but when i am writing code outside this, such as code external to imageproc, rgb to pixel works just fine.
pub struct CustomBlend<I>(pub I );
impl<I: GenericImage> Canvas for CustomBlend<I> {
type Pixel = I::Pixel;
fn dimensions(&self) -> (u32, u32) {
self.0.dimensions()
}
fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel {
self.0.get_pixel(x, y)
}
fn draw_pixel(&mut self, x: u32, y: u32, color: Self::Pixel) {
let mut pix = self.0.get_pixel(x, y);
let c = Rgb([1u8,1u8,1u8]);
self.0.put_pixel(x, y,c );
}
}

How can I (should i) include rand::Rng in my own struct?

I'm writing a graphical simulation to analyze physics interactions at the subatomic level and doing so by creating my own structures (struct Simiulation, struct Particle,...). For initial testing and development purposes I'm using rand::thread_rng().gen_rang(). In order to use this simulation, I have to add use rand::Rng; at the top of the file. As a new Rust user (1 week), I'm unsure if this can be included automatically, without having to type it, in the event I share the simulation with a colleague for collaborative purposes.
I googled nested namespaces which presented C++ / JS options but not Rust. I checked StackOverflow as well, but didn't find anything. I haven't reached the Cargo section in the Book as of yet. My terminology man be incorrect, so let me know if it's called something else in Rust.
Partial Sample Code
use rand::Rng; // <-- Nest this?
// - global -
const NUM_PARTICLES: usize = 5;
const WINDOW_WIDTH: f64 = 500.0; // TODO: Determine Window Width
const WINDOW_HEIGHT: f64 = 500.0; // TODO: Determine Window Height
const PARTICLE_RADIUS: f64 = 2.0; // TODO: Generate max probability distributon
// cross-sectional area to be used (per particle)
fn main() {
let mut starting_image = generate_starting_image();
println!("{:#?}", starting_image);
}
// Each particle has a position with respect to
// the origin and is denoted by (x, y)
// where x is the projection of the position along
// the x-axis and y is the projection of the position along
// the y-axis.
//
// Each particle has a veloctiy denoted by (Vx, Vy)
// where Vx is the velocity component along the x-axis and
// Vy is the velocity component along the y-axis.
#[derive(Debug, Copy, Clone)]
struct Mass {
mass: f64,
}
impl Mass {
// generate random mass for the particle (in kgs)
fn random() -> Self {
Self {
mass: rand::thread_rng().gen_range(1.00..=6.00),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Position {
x: f64,
y: f64,
}
impl Position {
// generate random starting position (x, y) for a particle
fn random() -> Self {
Self {
x: rand::thread_rng().gen_range(1.0..=(WINDOW_WIDTH - 1.0)),
y: rand::thread_rng().gen_range(1.0..=(WINDOW_HEIGHT - 1.0)),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Velocity {
vx: f64,
vy: f64,
}
impl Velocity {
// generate random starting velocity (vx, vy) for a particle
fn random() -> Self {
Self {
vx: rand::thread_rng().gen_range(5.0..20.0),
vy: rand::thread_rng().gen_range(5.0..20.0),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Particle {
position: Position,
velocity: Velocity,
mass: Mass, // mass in kilograms
}
impl Particle {
fn random() -> Self {
Self {
position: Position::random(),
velocity: Velocity::random(),
mass: Mass::random(),
}
}
}
// An image is a snapshot of all the attribute values for each
// particle at some given time interval dt.
#[derive(Debug, Copy, Clone)]
struct Image {
particles: [Particle; NUM_PARTICLES],
}
// Calculate the distance between two points. Necessary to
// determine if a collision is taken place
fn distance (particle_1: &Particle, particle_2: &Particle) -> f64 {
let base_1: f64 = particle_1.position.x - particle_2.position.x;
let base_2: f64 = particle_1.position.y - particle_2.position.y;
let base: f64 = f64::powf(base_1, 2.0) + f64::powf(base_2, 2.0);
f64::powf(base, 0.5)
}
fn generate_starting_image() -> Image {
let mut starting_image = Image {
particles: [Particle::random(); NUM_PARTICLES]
};
let iter = starting_image.particles.iter();
let mut num_created_particles: usize = 0;
// Loop over the starting_image array to populate the
// array with starting particle values
loop {
// stop the loop when the number of particles has
// been generated
if num_created_particles == NUM_PARTICLES { break; }
// loop over the populated values and check to
// make sure the distance between the newly generated
// particle does not occupy the same position as
// a previously generated particle
// Particle to test
let mut current_particle = Particle::random();
// indices of checked particles
let mut checked_particles: usize = 0;
while checked_particles < num_created_particles {
if distance(&current_particle, &starting_image.particles[checked_particles]) <= 4.0 {
current_particle = Particle::random();
checked_particles = 0;
}
// Newly generated particle is further than the minimum
// needed to be placed. increment to next particle to
// be checked.
checked_particles = checked_particles + 1;
}
// Newly generated particle has been checked for proximity
// against all previously generated particles. Insert
// into the particle array and increment to
// generate the next particle
println!("num_created_particles = {num_created_particles}");
starting_image.particles[num_created_particles] = current_particle;
num_created_particles = num_created_particles + 1;
checked_particles = 0;
}
// The starting values for each particle has been generated.
// Return array to begin the simulation
starting_image
}

get_value returning `f64` instead of `[u8; 4]`

I'm using the noise crate and having trouble understanding how to convert their Color type to an RGB value.
noise = "0.7.0"
pub type Color = [u8; 4];
I'm trying to use the get_value() function, seen here in the docs as:
pub fn get_value(&self, x: usize, y: usize) -> Color {
let (width, height) = self.size;
if x < width && y < height {
self.map[x + y * width]
} else {
self.border_color
}
}
get_value() is implemented for PlaneMapBuilder. So I would expect PlaneMapBuilder::get_value(x,y) to return something of the format [r,g,b,a], but this does not happen:
extern crate noise;
use noise::{utils::*, Billow};
fn main() {
let mut my_noise = PlaneMapBuilder::new(&Billow::new()).build();
let my_val = my_noise.get_value(1,1);
println!("{}", my_val.to_string());
///returns something like -0.610765515150546, not a [u8;4] as I would expect
}
In the docs I see this definition of add_gradient_point() which takes a Color as a parameter:
pub fn add_gradient_point(mut self, pos: f64, color: Color) -> Self {
// check to see if the vector already contains the input point.
if !self
.gradient_points
.iter()
.any(|&x| (x.pos - pos).abs() < std::f64::EPSILON)
{
// it doesn't, so find the correct position to insert the new
// control point.
let insertion_point = self.find_insertion_point(pos);
// add the new control point at the correct position.
self.gradient_points
.insert(insertion_point, GradientPoint { pos, color });
}
self
}
Here they use the [u8; 4] structure I would expect for the Color type:
let jade_gradient = ColorGradient::new()
.clear_gradient()
.add_gradient_point(-1.000, [24, 146, 102, 255])
.add_gradient_point(0.000, [78, 154, 115, 255])
What could explain this behavior?
get_value() is implemented for PlaneMapBuilder
You are correct that PlaneMapBuilder "implements" get_value(). However, it is not get_value() from NoiseImage. It is actually NoiseMap, where its get_value() returns a f64 and not Color.
Depending on what kind of "colors" you'd want, then you could instead use ImageRenderer and call its render() method with &my_noise, which returns a NoiseImage.
// noise = "0.7.0"
use noise::{utils::*, Billow};
fn main() {
let my_noise = PlaneMapBuilder::new(&Billow::new()).build();
let image = ImageRenderer::new().render(&my_noise);
let my_val = image.get_value(1, 1);
println!("{:?}", my_val);
// Prints: `[18, 18, 18, 255]`
}
Here they use the [u8; 4] structure I would expect for the Color type
Just to be clear, those are the same thing in this case. In short the type keyword allows you to define new "type aliases" for an existing types. Essentially, you'd be able to give a complex type a shorthand name. However, they are still the same type.

Why does the #[inline] attribute stop working when a function is moved to a method on a struct?

I have the function get_screen that's specified in a separate module from main.rs. It takes two 2D vectors (one that's 1920x1080 and called screen and another one that's even larger called world) and maps a portion of the world vector to the screen vector. This is the function signature when I first made it:
pub fn get_screen(
screen: &mut Vec<Vec<[u8; 4]>>,
world: &Vec<Vec<Chunk>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
chunk_width: usize,
chunk_height: usize,
)
I had serious issues with execution time, but I optimized it from 14ms down to 3ms by using #[inline].
I then moved the world vector to its own struct (alongside some other related variables like chunk width/height) and made the get_screen function into a method in the new world struct. This is what the function signature looked like after that change:
pub fn get_screen(
&self,
screen: &mut Vec<Vec<[u8; 4]>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
)
Then the execution time increases back to 14ms. I've tried enabling lto=true in Cargo.toml and switching to #[inline(always)] to enforce it, but it seems like the compiler refuses to optimize this function the way it used to.
I attempted to remove the get_screen method from the struct and run it as its own function like before and that seems to fix it, but only if I don't pass anything from the struct. If I attempt to pass even a usize from the world struct to the separate get_screen function, then the execution time increases from 3ms back to 14ms.
To show an example of what I mean, if I pass nothing directly from the world struct and instead pass it a cloned version of the 2D struct in world and the hardcoded chunk_width/chunk_height:
gen::get_screen(
&mut screen.buf,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
CHUNK_WIDTH,
CHUNK_HEIGHT,
);
It runs in 3.3ms. When I pass the usize fields chunk_width/chunk_height directly from the world struct:
gen::get_screen(
&mut screen.buf,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
world.chunk_width,
world.chunk_height,
);
it takes 14.55ms to run
What's up here? How can I get my get_screen function to compile inline while using my World struct? Preferably I'd like to be able to re-add it to my World struct as a method instead of keeping it separate.
Here is a minimal example:
use std::time::Instant;
const SCREEN_HEIGHT: usize = 1080; //528;
const SCREEN_WIDTH: usize = 1920; //960;
const CHUNK_WIDTH: usize = 256;
const CHUNK_HEIGHT: usize = 256;
const GEN_RANGE: isize = 25; //how far out to gen chunks
fn main() {
let batch_size = 1_000;
struct_test(batch_size);
separate_test(batch_size);
}
fn struct_test(batch_size: u32) {
let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
let camera_coords: (isize, isize) = (0, 0); //set camera location
let start = Instant::now();
for _ in 0..batch_size {
get_screen(
&mut screen,
&world.data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
world.chunk_width,
world.chunk_height,
); //gets visible pixels from world as 2d vec
}
println!(
"struct: {:?} {:?}",
start.elapsed(),
start.elapsed() / batch_size
);
}
fn separate_test(batch_size: u32) {
let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
let cloned_world_data = world.data.clone();
let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
let camera_coords: (isize, isize) = (0, 0); //set camera location
let start = Instant::now();
for _ in 0..batch_size {
get_screen(
&mut screen,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
CHUNK_WIDTH,
CHUNK_HEIGHT,
); //gets visible pixels from world as 2d vec
}
println!(
"separate: {:?} {:?}",
start.elapsed(),
start.elapsed() / batch_size
);
}
///gets all visible pixels on screen relative camera position in world
#[inline(always)] //INLINE STOPPED WORKING??
pub fn get_screen(
screen: &mut Vec<Vec<[u8; 4]>>,
world: &Vec<Vec<Chunk>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
chunk_width: usize,
chunk_height: usize,
) {
let camera = get_local_coords(&world, camera_coords, chunk_width, chunk_height); //gets loaded coords of camera in loaded chunks
(camera.1 - screen_height as isize / 2..camera.1 + screen_height as isize / 2)
.enumerate()
.for_each(|(py, y)| {
//for screen pixel index and particle in range of camera loaded y
let (cy, ly) = get_local_pair(y, chunk_height); //calculate chunk y and inner y from loaded y
if let Some(c_row) = world.get(cy) {
//if chunk row at loaded chunk y exists
(camera.0 - screen_width as isize / 2..camera.0 + screen_width as isize / 2)
.enumerate()
.for_each(|(px, x)| {
//for screen pixel index and particle in range of camera loaded x
let (cx, lx) = get_local_pair(x, chunk_width); //get loaded chunk x and inner x from loaded x
if let Some(c) = c_row.get(cx) {
screen[py][px] = c.data[ly][lx];
}
//if chunk in row then copy color of target particle in chunk
else {
screen[py][px] = [0; 4]
} //if target chunk doesn't exist color black
})
} else {
screen[py].iter_mut().for_each(|px| *px = [0; 4])
} //if target chunk row doesn't exist color row black
});
}
///calculates local coordinates in world vec from your global position
///returns negative if above/left of rendered area
pub fn get_local_coords(
world: &Vec<Vec<Chunk>>,
coords: (isize, isize),
chunk_width: usize,
chunk_height: usize,
) -> (isize, isize) {
let (wx, wy) = world[0][0].chunk_coords; //gets coords of first chunk in rendered vec
let lx = coords.0 - (wx * chunk_width as isize); //calculates local x coord based off world coords of first chunk
let ly = (wy * chunk_height as isize) - coords.1; //calculates local y coord based off world coords of first chunk
(lx, ly)
}
pub fn get_local_pair(coord: isize, chunk: usize) -> (usize, usize) {
(coord as usize / chunk, coord as usize % chunk)
}
///contains chunk data
#[derive(Clone)]
pub struct Chunk {
//world chunk object
pub chunk_coords: (isize, isize), //chunk coordinates
pub data: Vec<Vec<[u8; 4]>>, //chunk Particle data
}
impl Chunk {
///generates chunk
fn new(chunk_coords: (isize, isize), chunk_width: usize, chunk_height: usize) -> Self {
let data = vec![vec!([0; 4]; chunk_width); chunk_height];
Self { chunk_coords, data }
}
}
pub struct World {
pub data: Vec<Vec<Chunk>>,
pub chunk_width: usize,
pub chunk_height: usize,
}
impl World {
pub fn new(chunk_width: usize, chunk_height: usize, gen_range: isize) -> Self {
let mut data = Vec::new(); //creates empty vec to hold world
for (yi, world_chunk_y) in (gen_range * -1..gen_range + 1).rev().enumerate() {
//for y index, y in gen range counting down
data.push(Vec::new()); //push new row
for world_chunk_x in gen_range * -1..gen_range + 1 {
//for chunk in gen range of row
data[yi].push(Chunk::new(
(world_chunk_x, world_chunk_y),
chunk_width,
chunk_height,
)); //gen new chunk and put it there
}
}
Self {
data,
chunk_width,
chunk_height,
}
}
}
Probably, when you use world.chunk_width and world.chunk_height as parameters the compiler does not consider these parameters as constants and then actually generates division and modulus operations.
On the other hand, when you provide constants for these parameters, they can be propagated in the algorithm (constant folding) and some expensive operations (division, modulus) are not performed (or transformed into bit-shifts/masks).
Copying/pasting your code in godbolt (compiler explorer), making separate_test() and struct_test() public, and compiling with -C opt-level=3 confirms this since div instructions are present in the generated code for struct_test() but not for separate_test().

How to add threading to a for loop in Rust?

I'm trying to write a raytracer in Rust. I'm having difficulty getting the for loops to run in parallel. I'm not sure where the problem is, but I can't seem to get anything on the screen. Is this the correct approach or am I completely heading in the wrong direction?
I've tried running the for loops without multi-threading and it does correctly produce output. I've also added loggers to the consumer loop and I'm getting the correct values as well. It just doesn't seem to update the window.
#[derive(Clone, Copy)]
pub struct Pixel {
pub x: usize,
pub y: usize,
pub color: Vec3,
}
let mut buffer : Vec<u32> = vec![0; WIDTH * HEIGHT];
let (tx, rx) = mpsc::channel()
for x in 0..HEIGHT {
let tx_t = tx.clone();
thread::spawn(move || {
for y in 0..WIDTH {
let mut color = cast_ray(x, y); // returns vec3
let pixel = Pixel { x: x, y: y, color: color };
tx_t.send(pixel).unwrap();
}
});
}
for received in rx {
buffer[received.x * WIDTH + received.y] = received.color.x << 16 | received.color.y << 8 | received.color.z;
}
while window.is_open() && !window.is_key_down(Key::Escape) {
window.update_with_buffer(&buffer).unwrap();
}
I'm expecting a few spheres or color to appear on the screen, but it's just black.

Resources