Skip to content

Commit

Permalink
feat: add border and its radius, encapsulate code
Browse files Browse the repository at this point in the history
  • Loading branch information
JarKz committed Jul 10, 2024
1 parent eeb4589 commit dc4cb4f
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 88 deletions.
79 changes: 79 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"

[dependencies]
clap = "4.5.7"
derive_builder = "0.20.0"
derive_more = "0.99.18"
fontdue = "0.9.2"
image = "0.25.1"
Expand Down
1 change: 1 addition & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod font;
mod color;
mod image;
mod text;
mod border;
mod layer;

pub(crate) struct Renderer {
Expand Down
142 changes: 142 additions & 0 deletions src/render/border.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use derive_builder::Builder;

use super::color::Bgra;

#[derive(Default, Builder)]
pub(crate) struct Border {
color: Bgra,
background_color: Bgra,

frame_width: usize,
frame_height: usize,

#[builder(setter(into))]
width: Option<usize>,
#[builder(setter(into))]
radius: Option<usize>,
}

impl Border {
pub(crate) fn draw<O: FnMut(usize, usize, Bgra)>(&self, mut callback: O) {
let coverage = match (self.width, self.radius) {
(None, None) => return,
(Some(width), None) => self.get_bordered_coverage(width),
(None, Some(radius)) => self.get_rounding_coverage(radius),
(Some(width), Some(radius)) => self.get_bordered_rounding_coverage(width, radius),
};

let coverage_size = coverage.len();

for (frame_y, y) in (0..coverage_size).zip((0..coverage_size).rev()) {
for (frame_x, x) in (0..coverage_size).zip((0..coverage_size).rev()) {
callback(frame_x, frame_y, coverage[x][y].clone());
}
}

for (frame_y, y) in (0..coverage_size).zip((0..coverage_size).rev()) {
for (frame_x, x) in
(self.frame_width - coverage_size..self.frame_width).zip(0..coverage_size)
{
callback(frame_x, frame_y, coverage[x][y].clone());
}
}

for (frame_y, y) in
(self.frame_height - coverage_size..self.frame_height).zip(0..coverage_size)
{
for (frame_x, x) in
(self.frame_width - coverage_size..self.frame_width).zip(0..coverage_size)
{
callback(frame_x, frame_y, coverage[x][y].clone());
}
}

for (frame_y, y) in
(self.frame_height - coverage_size..self.frame_height).zip(0..coverage_size)
{
for (frame_x, x) in (0..coverage_size).zip((0..coverage_size).rev()) {
callback(frame_x, frame_y, coverage[x][y].clone());
}
}

if let Some(width) = self.width {
for y in 0..width {
for x in coverage_size..self.frame_width - coverage_size {
callback(x, y, self.color.clone())
}
}

for y in self.frame_height - width..self.frame_height {
for x in coverage_size..self.frame_width - coverage_size {
callback(x, y, self.color.clone())
}
}

for x in 0..width {
for y in coverage_size..self.frame_height - coverage_size {
callback(x, y, self.color.clone())
}
}

for x in self.frame_width - width..self.frame_width {
for y in coverage_size..self.frame_height - coverage_size {
callback(x, y, self.color.clone())
}
}
}
}

fn get_bordered_coverage(&self, width: usize) -> Vec<Vec<Bgra>> {
vec![vec![self.color.clone(); width]; width]
}

fn get_rounding_coverage(&self, radius: usize) -> Vec<Vec<Bgra>> {
let mut coverage = vec![vec![Bgra::new(); radius]; radius];
for y in 0..radius {
for x in 0..radius {
coverage[x][y] = self.background_color.clone()
* Self::get_coverage_by(radius as f32, x as f32, y as f32);
}
}

coverage
}

fn get_bordered_rounding_coverage(&self, width: usize, radius: usize) -> Vec<Vec<Bgra>> {
let mut coverage = vec![vec![Bgra::new(); radius]; radius];
let inner_radius = radius.saturating_sub(width);

for y in 0..radius {
for x in 0..radius {
let (x_f32, y_f32) = (x as f32, y as f32);
let outer_color =
self.color.clone() * Self::get_coverage_by(radius as f32, x_f32, y_f32);

let inner_color = if inner_radius != 0 {
self.background_color.clone()
* Self::get_coverage_by(inner_radius as f32, x_f32, y_f32)
} else {
Bgra::new()
};

coverage[x][y] = inner_color.overlay_on(&outer_color);
}
}

coverage
}

fn get_coverage_by(radius: f32, x: f32, y: f32) -> f32 {
let inner_hypot = f32::hypot(x, y);
let inner_diff = radius - inner_hypot;
let outer_hypot = f32::hypot(x + 1.0, y + 1.0);

if inner_hypot >= radius {
0.0
} else if outer_hypot >= radius {
inner_diff
} else {
1.0
}
}
}
45 changes: 45 additions & 0 deletions src/render/color.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::ops::{Mul, MulAssign};

#[derive(Clone, Default)]
pub(crate) struct Bgra {
pub(crate) blue: f32,
pub(crate) green: f32,
Expand Down Expand Up @@ -83,6 +86,27 @@ impl From<Bgra> for Rgba {
}
}

impl Mul<f32> for Bgra {
type Output = Bgra;

fn mul(mut self, rhs: f32) -> Self::Output {
self.blue *= rhs;
self.green *= rhs;
self.red *= rhs;
self.alpha *= rhs;
self
}
}

impl MulAssign<f32> for Bgra {
fn mul_assign(&mut self, rhs: f32) {
self.blue *= rhs;
self.green *= rhs;
self.red *= rhs;
self.alpha *= rhs;
}
}

pub(crate) struct Rgba {
pub(crate) red: f32,
pub(crate) green: f32,
Expand Down Expand Up @@ -179,6 +203,27 @@ impl From<Rgba> for [u8; 4] {
}
}

impl Mul<f32> for Rgba {
type Output = Rgba;

fn mul(mut self, rhs: f32) -> Self::Output {
self.blue *= rhs;
self.green *= rhs;
self.red *= rhs;
self.alpha *= rhs;
self
}
}

impl MulAssign<f32> for Rgba {
fn mul_assign(&mut self, rhs: f32) {
self.blue *= rhs;
self.green *= rhs;
self.red *= rhs;
self.alpha *= rhs;
}
}

// SOURCE: https://stackoverflow.com/questions/726549/algorithm-for-additive-color-mixing-for-rgb-values
// r.A = 1 - (1 - fg.A) * (1 - bg.A);
// if (r.A < 1.0e-6) return r; // Fully transparent -- R,G,B not important
Expand Down
Loading

0 comments on commit dc4cb4f

Please sign in to comment.