I am trying to create gradient blobs. I supply the gen function with a Vec<ColorPoint> and each point should have go from a maximum intensity at the center to no effect at the radius. The main problem is that I am not getting the colors I expect in the output image.
use image::{ImageBuffer, RgbImage, Rgb};
use std::ops::Add;
/// Color with red, green, blue and alpha channels between 0.0 and 1.0
#[derive(Debug)]
struct RgbaColor {
r: f64,
g: f64,
b: f64,
a: f64,
}
impl RgbaColor {
fn to_rgb(&self) -> Rgb<u8> {
let r = (&self.r*255.0) as u8;
let g = (&self.g*255.0) as u8;
let b = (&self.b*255.0) as u8;
Rgb::from([r, g, b])
}
}
impl Add for RgbaColor {
type Output = Self;
fn add(self, fg: Self) -> Self {
// self is the background and fg is the foreground
let new_alpha = fg.a + self.a * (1 - fg.a);
Self {
r: (fg.r * fg.a + self.r * self.a * (1.0 - fg.a)) / new_alpha,
g: (fg.g * fg.a + self.g * self.a * (1.0 - fg.a)) / new_alpha,
b: (fg.b * fg.a + self.b * self.a * (1.0 - fg.a)) / new_alpha,
a: new_alpha
}
}
}
#[derive(Debug)]
struct ColorPoint {
color: RgbaColor,
center: (u32, u32),
radius: u32,
}
impl ColorPoint {
fn rgba_color_at_point(&self, x: u32, y: u32) -> RgbaColor {
let x_dist = x as f64 - self.center.0 as f64;
let y_dist = y as f64 - self.center.1 as f64;
let dist = (x_dist.powf(2.0) + y_dist.powf(2.0)).sqrt();
RgbaColor {
r: self.color.r,
g: self.color.g,
b: self.color.b,
a: self.color.a * dist / self.radius as f64,
}
}
}
fn main() {
let color_points = vec![
ColorPoint{color: RgbaColor{r: 1.0, g:1.0, b:0.0, a:1.0}, center: (0, 50), radius: 20},
ColorPoint{color: RgbaColor{r: 1.0, g:0.0, b:0.0, a:1.0}, center: (10, 0), radius: 30}
];
gen(color_points)
}
fn gen(color_points: Vec<ColorPoint>) {
let geometry = (50, 100);
let mut background: RgbImage = ImageBuffer::new(geometry.0 as u32, geometry.1 as u32);
for (x, y, pixel) in background.enumerate_pixels_mut() {
let mut curr_color = RgbaColor{ r:0.0, g:0.0, b:0.0, a:1.0 }; // hardcoded background color
for color_point in color_points.iter() {
curr_color = curr_color + color_point.rgba_color_at_point(x,y);
}
*pixel = curr_color.to_rgb();
}
background.save("image.png").unwrap();
}
Output:
This code almost does what I expect although the position of the yellow and red blobs seem to have swapped and when I change the hard coded background color to white RgbaColor{ r:1.0, g:1.0, b:1.0, a:1.0 } I seem to have a magenta background.
I'm not sure whether my color model is wrong or if it's something else because when adding individual RgbaColors I get the correct colors.
Related
Problem Statement: Write a function that returns the smallest number of coins needed to make change for the target amount using the given coin denominations.
I'm trying to convert the below i32 integer-based solution to float f32 or f64 based solution so it can take decimal input such as coin denomination of 0.5, 1.5, 2.0 etc.
use std::cmp;
fn min_number_of_change(n: i32, denoms: Vec<u32>) -> i32 {
let mut ways: Vec<i32> = vec![i32::MAX;n as usize + 1];
ways[0] = 0;
for denom in denoms.iter() {
for current in 0..ways.len() {
if *denom <= current as u32 {
ways[current as usize] = cmp::min(ways[current as usize], 1 + ways[current as usize - *denom as usize])
}
}
}
if ways[n as usize] != i32::MAX {
ways[n as usize]
} else {
-1
}
}
fn main() {
let denoms: Vec<u32> = vec![1, 5, 10, 2, 3];
let n: i32 = 6;
let result: i32 = min_number_of_change(n, denoms);
println!("Result: {}", result);
}
Play Ground for above code
Very naively I've tried replacing i32 with f32 and min function to get the float comparisons. When running it, the Compiler complains about mismatched types, cannot subtract f32 from usize, the type [f32] cannot be indexed by f32. I think I'm missing some very fundamental points.
fn min_number_of_change(n: f32, denoms: Vec<f32>) -> f32 {
let mut ways: Vec<f32> = vec![f32::INFINITY; n + 1.0];
ways[0] = 0.0;
for denom in denoms.iter() {
for current in 0..ways.len() {
if *denom <= current {
ways[current] = (ways[current].min(1 + ways[current - *denom ]), 1 + ways[current - *denom ])
}
}
}
if ways[n] != f32::INFINITY {
ways[n]
} else {
-1.0
}
}
fn main() {
let denoms: Vec<f32> = vec![2.00, 1.00, 0.50, 0.20, 0.10, 0.05, 0.02, 0.01];
let n: f32 = 4.55;
let result: f32 = min_number_of_change(n, denoms);
println!("Result: {}", result);
}
Playground for above code
The first error is simple to fix. Instead of current - *denom you can write current as f32 - *denom.
But that leaves us with the second error which is harder.
You can't index a Vec with a f32 because that would require to acces the 0.5th element, an element half an elements width away from the first one. This is simply not possible.
Your best bet is probably using integers and treating them as fixed point values i.e. 1 means 0.01 in your currency etc.
fn main() {
let denoms = [2.00, 1.00, 0.50, 0.20, 0.10, 0.05, 0.02, 0.01].into_iter().map(|v| (v * 100.0) as i32).collect();
let n = (4.55 * 100.0) as i32;
let result = min_number_of_change(n, denoms) as f32 / 100.0;
println!("Result: {}", result);
}
and use the integer version of min_number_of_change
I'm, currently working through Ray Tracing In One Weekend to get familiar with rust. Adding the dielectric material (glass) is causing me some headaches: My refraction isn't flipping upside down!
Here's the code I'm using for my Vector struct:
impl Vec3 {
pub fn new(x: f64, y: f64, z: f64) -> Vec3 { Vec3 {x, y, z} }
pub fn x(&self) -> f64 { self.x }
pub fn y(&self) -> f64 { self.y }
pub fn z(&self) -> f64 { self.z }
pub fn length_squared(&self) -> f64 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn length(&self) -> f64 { self.distance(&Vec3::default()) }
pub fn unit_vector(&self) -> Vec3 {
let length = self.length();
Vec3::new(self.x / length, self.y / length, self.z / length)
}
pub fn dot(&self, v:&Vec3) -> f64 {
self.x * v.x + self.y * v.y + self.z * v.z
}
pub fn cross(&self, v:&Vec3) -> Vec3 {
Vec3::new(
self.y * v.z - self.z * v.y,
self.z * v.x - self.x * v.z,
self.x * v.y - self.y * v.x
)
}
pub fn distance(&self, other: &Vec3) -> f64 {
let dx = self.x - other.x();
let dy = self.y - other.y();
let dz = self.z - other.z();
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn random(min: f64, max:f64) -> Self {
let between = Uniform::from(min..max);
let mut rng = rand::thread_rng();
Vec3::new(
between.sample(&mut rng),
between.sample(&mut rng),
between.sample(&mut rng))
}
pub fn random_in_unit_sphere() -> Self {
loop {
let v = Vec3::random(-1.0, 1.0);
if v.length_squared() < 1.0 {
return v;
}
}
}
pub fn random_in_hemisphere(normal: &Vec3) -> Self {
let vec = Vec3::random_in_unit_sphere();
if vec.dot(normal) > 0.0 {
vec
} else {
-vec
}
}
pub fn random_unit_vector() -> Self { Vec3::random_in_unit_sphere().unit_vector() }
pub fn near_zero(&self) -> bool {
const MAXIMUM_DISTANCE_FROM_ZERO:f64 = 1e-8;
self.x.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
self.y.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
self.z.abs() < MAXIMUM_DISTANCE_FROM_ZERO
}
pub fn reflected(&self, normal: &Vec3) -> Vec3 {
let dp = self.dot(normal);
let dp = dp * 2.0 * (*normal);
*self - dp
}
pub fn refract(&self, normal: &Vec3, etai_over_etat: f64) -> Vec3 {
let dot = (-(*self)).dot(normal);
let cos_theta = dot.min(1.0);
let out_perp = etai_over_etat * ((*self) + cos_theta * (*normal));
let inner = 1.0 - out_perp.length_squared();
let abs = inner.abs();
let r = -(abs.sqrt());
let out_parallel = r * (*normal);
out_perp + out_paralle
}
}
And this is my scatter function for the material:
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
let refraction_ratio = if hit_record.front_face {
1.0/self.index_of_refraction
} else {
self.index_of_refraction
};
let unit_direction = ray.direction().unit_vector();
let cos_theta = ((-unit_direction).dot(&hit_record.normal)).min(1.0);
let sin_theta = (1.0 - cos_theta*cos_theta).sqrt();
let cannot_refract = refraction_ratio * sin_theta > 1.0;
let reflectance = Dielectric::reflectance(cos_theta, refraction_ratio);
let mut rng = rand::thread_rng();
let color = Color::new(1.0, 1.0, 1.0);
if cannot_refract || reflectance > rng.gen::<f64>() {
let reflected = unit_direction.reflected(&hit_record.normal);
let scattered = Ray::new(hit_record.point, reflected);
Some((Some(scattered), color))
} else {
let direction = unit_direction.refract(&hit_record.normal, refraction_ratio);
let scattered = Ray::new(hit_record.point, direction);
Some((Some(scattered), color))
}
}
It sort of works if I negate x and y of the refract-result, but still looks obviously wrong. Additionally, if I go a few steps back in the book and implement the 100% refraction glass, my sphere's are solid black, and I have to negate the z axis to see anything at all. So something is amiss with my refraction code, but I can't figure it out
Full code at: https://phlaym.net/git/phlaym/rustracer/src/commit/89a2333644a82f2645e4ad554eadf7d4f142f2c0/src
In the method src/hittable.rs which checks if a sphere is hit, the c code looks like this.
// Find the nearest root that lies in the acceptable range.
auto root = (-half_b - sqrtd) / a;
if (root < t_min || t_max < root) {
root = (-half_b + sqrtd) / a;
if (root < t_min || t_max < root)
return false;
}
You have ported it to rust code with the following listing:
let root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
let root = (-half_b + sqrtd) / a;
if root < t_min || t_max < root {
return None;
}
}
The problem here is the second let root. You have created a new variable with its own scope for the inner brackets but not changed the already created variable defined before. To do this you have to make it mutable.
let mut root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
root = (-half_b + sqrtd) / a;
if root < t_min || t_max < root {
return None;
}
}
Additionally I changed the following in src/ray.rs
return match scattered {
Some((scattered_ray, albedo)) => {
match scattered_ray {
Some(sr) => {
albedo * sr.pixel_color(world, depth-1)
},
None => albedo
}
},
None => { return Color::default() }
};
to match the corresponding C code. Be aware of the unwrap used.
let scattered = rect.material.scatter(self, &rect);
if let Some((scattered_ray, albedo)) = scattered {
return albedo * scattered_ray.unwrap().pixel_color(world, depth - 1)
}
return Color::default()
And remove your tries to correct the reflections:
let reflected = Vec3::new(-reflected.x(), reflected.y(), -reflected.z());
I am learning Rust and recently went through an exercise where I had to iterate through numbers that could go in either direction. I tried the below with unexpected results.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Point {
x: i32,
y: i32
}
fn test() {
let p1 = Point { x: 1, y: 8 };
let p2 = Point { x: 3, y: 6 };
let all_x = p1.x..=p2.x;
println!("all_x: {:?}", all_x.clone().collect::<Vec<i32>>());
let all_y = p1.y..=p2.y;
println!("all_y: {:?}", all_y.clone().collect::<Vec<i32>>());
let points: Vec<Point> = all_x.zip(all_y).map(|(x, y)| Point { x, y }).collect();
println!("points: {:?}", points);
}
The output was
all_x: [1, 2, 3]
all_y: []
points: []
After some googling I found an explanation and some old answers which basically amount to use (a..b).rev() as needed.
My question is, how do I do this in a dynamic way? If I use an if...else like so
let all_x = if p1.x < p2.x { (p1.x..=p2.x) } else { (p2.x..=p1.x).rev() };
I get a type error because the else is different than the if
|
58 | let all_x = if p1.x < p2.x { (p1.x..=p2.x) }
| - ------------- expected because of this
| _________________|
| |
59 | | else { (p2.x..=p1.x).rev() };
| |____________^^^^^^^^^^^^^^^^^^^_- `if` and `else` have incompatible types
| |
| expected struct `RangeInclusive`, found struct `Rev`
|
= note: expected type `RangeInclusive<_>`
found struct `Rev<RangeInclusive<_>>`
After trying a bunch of different variations on let all_x: dyn Range<Item = i32>, let all_x: dyn Iterator<Item = i32>, etc, the only way I have managed to do this is by turning them into collections and then back to iterators.
let all_x: Vec<i32>;
if p1.x < p2.x { all_x = (p1.x..=p2.x).collect(); }
else { all_x = (p2.x..=p1.x).rev().collect(); }
let all_x = all_x.into_iter();
println!("all_x: {:?}", all_x.clone().collect::<Vec<i32>>());
let all_y: Vec<i32>;
if p1.y < p2.y { all_y = (p1.y..=p2.y).collect(); }
else { all_y = (p2.y..=p1.y).rev().collect(); }
let all_y = all_y.into_iter();
println!("all_y: {:?}", all_y.clone().collect::<Vec<i32>>());
which provides the desired outcome
all_x: [1, 2, 3]
all_y: [8, 7, 6]
points: [Point { x: 1, y: 8 }, Point { x: 2, y: 7 }, Point { x: 3, y: 6 }]
but is a bit repetitive, inelegant and I'm assuming not very efficient at large numbers. Is there a better way to handle this situation?
NOTE: Sorry for including the Point struct. I could not get my example to work with x1, x2, etc. Probably a different question for a different post lol.
You can dynamically dispatch it. Wrapping them into a Box and returning a dynamic object, an Iterator in this case. For example:
fn maybe_reverse_range(init: usize, end: usize, reverse: bool) -> Box<dyn Iterator<Item=usize>> {
if reverse {
Box::new((init..end).rev())
} else {
Box::new((init..end))
}
}
Playground
The enum itertools::Either can be used to solve the incompatible type error in the if/else statement. A function like get_range_iter below using Either can reduce the code repetition.
use itertools::Either;
fn get_range_iter(start: i32, end: i32) -> impl Iterator<Item=i32> {
if start < end {
Either::Left(start..=end)
} else {
Either::Right((end..=start).rev())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Point {
x: i32,
y: i32
}
fn main() {
let p1 = Point { x: 1, y: 8 };
let p2 = Point { x: 3, y: 6 };
let all_x = get_range_iter(p1.x, p2.x);
let all_y = get_range_iter(p1.y, p2.y);
println!("all_x: {:?}", all_x.collect::<Vec<_>>());
println!("all_y: {:?}", all_y.collect::<Vec<_>>());
}
Playground
I am trying to render text in a separate function using piston2d / piston_window. I am able to draw text just fine, but I can't figure out how to pass the appropriate parameters into a separate function.
I have studied What is GlyphCache type in a function to render text in Piston2d and adjusted my code accordingly, but I can't make sense of the error I am getting.
use piston_window::*;
fn main() {
let font = include_bytes!("IBMPlexSans-Regular.ttf");
let opengl = OpenGL::V3_2;
let settings = WindowSettings::new("test", [500, 500])
.graphics_api(opengl)
.fullscreen(false)
.vsync(true)
.exit_on_esc(true);
let mut window: PistonWindow = settings.build().unwrap();
let mut glyphs = Glyphs::from_bytes(
font,
window.create_texture_context(),
TextureSettings::new(),
)
.unwrap();
while let Some(e) = window.next() {
window.draw_2d(&e, |c, gfx, device| {
clear([0.2; 4], gfx);
text::Text::new_color([1.0, 1.0, 1.0, 0.7], 30)
.draw(
"Hi!",
&mut glyphs,
&c.draw_state,
c.transform
.trans(100., 100.),
gfx,
)
.unwrap();
glyphs.factory.encoder.flush(device);
});
}
}
fn render_text(
x: f64,
y: f64,
text: &str,
size: u32,
c: Context,
g: &mut G2d,
glyphs: &mut glyph_cache::rusttype::GlyphCache<GfxFactory, G2dTexture>,
) {
text::Text::new(size)
.draw(text, glyphs, &c.draw_state, c.transform.trans(x, y), g)
.unwrap();
}
I am receiving the following error:
error[E0277]: the trait bound `Texture<gfx_device_gl::Resources>: UpdateTexture<gfx_device_gl::factory::Factory>` is not satisfied
--> src/main.rs:54:10
|
54 | .draw(text, glyphs, &c.draw_state, c.transform.trans(x, y), g)
| ^^^^ the trait `UpdateTexture<gfx_device_gl::factory::Factory>` is not implemented for `Texture<gfx_device_gl::Resources>`
|
= help: the following implementations were found:
<Texture<R> as UpdateTexture<TextureContext<F, R, C>>>
= note: required because of the requirements on the impl of `CharacterCache` for `GlyphCache<'_, gfx_device_gl::factory::Factory, Texture<gfx_device_gl::Resources>>`
I am aware this is probably very piston-specific, but I would be very happy about any pointers.
Just had the same problem. It's almost a year after but documentation for piston_window isn't the best so maybe others will need it.
This worked for me
use piston_window::types::Color;
use piston_window::{text, Context, G2d, Glyphs, Transformed};
pub const text_color: Color = [1.0, 1.0, 1.0, 1.0];
pub fn draw_text(
ctx: &Context,
graphics: &mut G2d,
glyphs: &mut Glyphs,
color: Color,
pos: Position,
text: &str,
) {
text::Text::new_color(color, 20)
.draw(
text,
glyphs,
&ctx.draw_state,
ctx.transform.trans(pos.x as f64, pos.y as f64),
graphics,
)
.unwrap();
}
pub struct Pos {
pub x: f64,
pub y: f64,
}
pub fn main() {
let assets = find_folder::Search::ParentsThenKids(3, 3)
.for_folder("assets")
.unwrap();
let ref font = assets.join("retro-gaming.ttf");
let mut glyphs = window.load_font(font).unwrap();
let size = [500., 500.];
let mut window: PistonWindow = WindowSettings::new("Test", size)
.resizable(false)
.exit_on_esc(true)
.build()
.unwrap();
while let Some(event) = window.next() {
window.draw_2d(&event, |ctx, g, _| {
draw_text(&ctx, g, &mut glyphs, text_color, Pos { x: 0, y: 10 }, "20")
}
}
}
Note that I'm using different "Glyphs" than you are. There's additionally one more dependency in "Cargo.toml" namely find_folder = "0.3.0".
I'm not entirely sure whether the snippet above compiles and works. It is a quick refactor from this commit https://github.com/laszukdawid/rsnake/commit/e6e23563ebdd9c7b972ee17b5d0299e2202358cf.
I'm writing JPEG decoder/encoder in Rust and I have some problem with RGB ↔ YCbCr conversion.
My code:
use std::simd::f32x4;
fn clamp<T>(val: T, min: T, max: T) -> T
where T: PartialOrd {
if val < min { min }
else if max < val { max }
else { val }
}
// in oryginal code there are 2 methods, one for processors with SSE3 and for rest
// both do the same and give the same results
pub fn sum_f32x4(f32x4(a, b, c, d): f32x4) -> f32 {
a + b + c + d
}
pub fn rgb_to_ycbcr(r: u8, g: u8, b: u8) -> (u8, u8, u8) {
let rgb = f32x4(r as f32, g as f32, b as f32, 1.0);
let y = sum_f32x4(rgb * f32x4( 0.2990, 0.5870, 0.1140, 0.0));
let cb = sum_f32x4(rgb * f32x4(-0.1687, -0.3313, 0.5000, 128.0));
let cr = sum_f32x4(rgb * f32x4( 0.5000, -0.4187, -0.0813, 128.0));
(y as u8, cb as u8, cr as u8)
}
pub fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) {
let ycbcr = f32x4(y as f32, cb as f32 - 128.0f32, cr as f32 - 128.0f32, 0.0);
let r = sum_f32x4(ycbcr * f32x4(1.0, 0.00000, 1.40200, 0.0));
let g = sum_f32x4(ycbcr * f32x4(1.0, -0.34414, -0.71414, 0.0));
let b = sum_f32x4(ycbcr * f32x4(1.0, 1.77200, 0.00000, 0.0));
(clamp(r, 0., 255.) as u8, clamp(g, 0., 255.) as u8, clamp(b, 0., 255.) as u8)
}
fn main() {
assert_eq!(rgb_to_ycbcr( 0, 71, 171), ( 61, 189, 84));
// assert_eq!(rgb_to_ycbcr( 0, 71, 169), ( 61, 189, 84)); // will fail
// for some reason we always lose data on blue channel
assert_eq!(ycbcr_to_rgb( 61, 189, 84), ( 0, 71, 169));
}
For some reason booth tests (in comments) passes. I would rather expect that at least one of them will fail. Am I wrong? At least it should stop at some point, but when I change jpeg::color::utils::rgb_to_ycbcr(0, 71, 171) to jpeg::color::utils::rgb_to_ycbcr(0, 71, 169) then test fails as YCbCr value has changed, so I will lose my blue channel forever.
#dbaupp put the nail in the coffin with the suggestion to use round:
#![allow(unstable)]
use std::simd::{f32x4};
use std::num::Float;
fn clamp(val: f32) -> u8 {
if val < 0.0 { 0 }
else if val > 255.0 { 255 }
else { val.round() as u8 }
}
fn sum_f32x4(v: f32x4) -> f32 {
v.0 + v.1 + v.2 + v.3
}
pub fn rgb_to_ycbcr((r, g, b): (u8, u8, u8)) -> (u8, u8, u8) {
let rgb = f32x4(r as f32, g as f32, b as f32, 1.0);
let y = sum_f32x4(rgb * f32x4( 0.299000, 0.587000, 0.114000, 0.0));
let cb = sum_f32x4(rgb * f32x4(-0.168736, -0.331264, 0.500000, 128.0));
let cr = sum_f32x4(rgb * f32x4( 0.500000, -0.418688, -0.081312, 128.0));
(clamp(y), clamp(cb), clamp(cr))
}
pub fn ycbcr_to_rgb((y, cb, cr): (u8, u8, u8)) -> (u8, u8, u8) {
let ycbcr = f32x4(y as f32, cb as f32 - 128.0f32, cr as f32 - 128.0f32, 0.0);
let r = sum_f32x4(ycbcr * f32x4(1.0, 0.00000, 1.40200, 0.0));
let g = sum_f32x4(ycbcr * f32x4(1.0, -0.34414, -0.71414, 0.0));
let b = sum_f32x4(ycbcr * f32x4(1.0, 1.77200, 0.00000, 0.0));
(clamp(r), clamp(g), clamp(b))
}
fn main() {
let mut rgb = (0, 71, 16);
println!("{:?}", rgb);
for _ in 0..100 {
let yuv = rgb_to_ycbcr(rgb);
rgb = ycbcr_to_rgb(yuv);
println!("{:?}", rgb);
}
}
Note that I also increased the precision of your values in rgb_to_ycbcr from the Wikipedia page. I also clamp in both functions, as well as calling round. Now the output is:
(0u8, 71u8, 16u8)
(1u8, 72u8, 16u8)
(1u8, 72u8, 16u8)
With the last value repeating for the entire loop.