perf(k-ui): own pixel loops (pixels.rs): float only in corners, SWAR blends, banded shadow; screen fade only over content toward wallpaper (was full-window tiny-skia fill, ~600ms); blur at quarter res, row/column-sum passes, SWAR 2x upscale; ring frame ~75 -> ~20ms
This commit is contained in:
@@ -9,6 +9,7 @@ use alloc::vec;
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use crate::layout::Rect;
|
use crate::layout::Rect;
|
||||||
|
use crate::pixels::{between_corners, blend, corner_row, cover, height, span};
|
||||||
use crate::theme::{Rgba, Theme};
|
use crate::theme::{Rgba, Theme};
|
||||||
|
|
||||||
/// The backdrop is made at 1 / `SCALE` of the window, each way.
|
/// The backdrop is made at 1 / `SCALE` of the window, each way.
|
||||||
@@ -83,8 +84,10 @@ impl Backdrop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut glass = Image { w: sw, h: sh, px: wall.px.clone() };
|
// Blurred at half this size again (it is soft), then brought back.
|
||||||
blur(&mut glass, (t.blur_radius as usize).div_ceil(SCALE));
|
let mut small = halve(&wall);
|
||||||
|
blur(&mut small, (t.blur_radius as usize).div_ceil(2 * SCALE));
|
||||||
|
let mut glass = double(&small, sw, sh);
|
||||||
let opacity = t.panel_opacity as u32 * 256 / 100;
|
let opacity = t.panel_opacity as u32 * 256 / 100;
|
||||||
for p in &mut glass.px {
|
for p in &mut glass.px {
|
||||||
*p = mix(*p, t.surface, opacity);
|
*p = mix(*p, t.surface, opacity);
|
||||||
@@ -98,46 +101,35 @@ impl Backdrop {
|
|||||||
copy(&self.wall, pm, width, part, 0.0);
|
copy(&self.wall, pm, width, part, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `part` of `pm` moved back toward the wallpaper by `k` 256ths: what
|
||||||
|
/// is drawn there fading in.
|
||||||
|
pub fn fade(&self, pm: &mut [u8], width: usize, part: Rect, k: u32) {
|
||||||
|
let (cols, rows) = span(part, width, height(pm, width));
|
||||||
|
for y in rows {
|
||||||
|
let src = &self.wall.px[(y / SCALE).min(self.wall.h - 1) * self.wall.w..][..self.wall.w];
|
||||||
|
let row = pm[(y * width + cols.start) * 4..(y * width + cols.end) * 4].as_chunks_mut::<4>().0;
|
||||||
|
for (i, d) in row.iter_mut().enumerate() {
|
||||||
|
blend(d, src[((cols.start + i) / SCALE).min(self.wall.w - 1)], k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A glass panel over `r`, its corners rounded by `radius`.
|
/// A glass panel over `r`, its corners rounded by `radius`.
|
||||||
pub fn glass(&self, pm: &mut [u8], width: usize, r: Rect, radius: f32) {
|
pub fn glass(&self, pm: &mut [u8], width: usize, r: Rect, radius: f32) {
|
||||||
copy(&self.glass, pm, width, r, radius);
|
copy(&self.glass, pm, width, r, radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pixels of `r` (whole ones) inside `pm`: columns and rows.
|
|
||||||
fn span(r: Rect, width: usize, height: usize) -> (core::ops::Range<usize>, core::ops::Range<usize>) {
|
|
||||||
let (x0, y0) = (r.x.max(0.0) as usize, r.y.max(0.0) as usize);
|
|
||||||
let (x1, y1) = (((r.x + r.w).max(0.0) as usize).min(width), ((r.y + r.h).max(0.0) as usize).min(height));
|
|
||||||
(x0..x1.max(x0), y0..y1.max(y0))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How much of pixel (`x`, `y`) is inside `r` with its corners rounded by
|
|
||||||
/// `radius`, in 256ths: worked out only in the corners.
|
|
||||||
fn cover(r: Rect, radius: f32, x: usize, y: usize) -> u32 {
|
|
||||||
let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5);
|
|
||||||
let dx = (r.x + radius - fx).max(fx - (r.x + r.w - radius));
|
|
||||||
let dy = (r.y + radius - fy).max(fy - (r.y + r.h - radius));
|
|
||||||
if dx <= 0.0 || dy <= 0.0 {
|
|
||||||
return 256;
|
|
||||||
}
|
|
||||||
((radius + 0.5 - libm::sqrtf(dx * dx + dy * dy)).clamp(0.0, 1.0) * 256.0) as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blend(d: &mut [u8], s: [u8; 4], k: u32) {
|
|
||||||
for (d, s) in d.iter_mut().zip(s) {
|
|
||||||
*d = ((s as u32 * k + *d as u32 * (256 - k)) >> 8) as u8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies `img` (scaled up) into `r` of `pm`, the corners rounded by
|
/// Copies `img` (scaled up) into `r` of `pm`, the corners rounded by
|
||||||
/// `radius` and anti-aliased there.
|
/// `radius` and anti-aliased there.
|
||||||
fn copy(img: &Image, pm: &mut [u8], width: usize, r: Rect, radius: f32) {
|
fn copy(img: &Image, pm: &mut [u8], width: usize, r: Rect, radius: f32) {
|
||||||
let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0);
|
let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0);
|
||||||
let (cols, rows) = span(r, width, pm.len() / 4 / width.max(1));
|
let (cols, rows) = span(r, width, height(pm, width));
|
||||||
|
let mid = between_corners(r, radius, &cols);
|
||||||
let (x0, x1) = (cols.start, cols.end);
|
let (x0, x1) = (cols.start, cols.end);
|
||||||
let mut copied: Option<usize> = None;
|
let mut copied: Option<usize> = None;
|
||||||
for y in rows {
|
for y in rows {
|
||||||
let corner = radius > 0.0 && ((y as f32) < r.y + radius || (y as f32 + 1.0) > r.y + r.h - radius);
|
let corner = corner_row(r, radius, y);
|
||||||
let at = y * width * 4;
|
let at = y * width * 4;
|
||||||
// A row made from the same source row as the one above: the same.
|
// A row made from the same source row as the one above: the same.
|
||||||
if let Some(above) = copied.filter(|a| *a + 1 == y && a / SCALE == y / SCALE && !corner) {
|
if let Some(above) = copied.filter(|a| *a + 1 == y && a / SCALE == y / SCALE && !corner) {
|
||||||
@@ -147,118 +139,150 @@ fn copy(img: &Image, pm: &mut [u8], width: usize, r: Rect, radius: f32) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let src = &img.px[(y / SCALE).min(img.h - 1) * img.w..][..img.w];
|
let src = &img.px[(y / SCALE).min(img.h - 1) * img.w..][..img.w];
|
||||||
let row = &mut pm[at + x0 * 4..at + x1 * 4];
|
let straight = if corner { mid.clone() } else { cols.clone() };
|
||||||
|
let row = &mut pm[at + straight.start * 4..at + straight.end * 4];
|
||||||
|
for (i, d) in row.as_chunks_mut::<4>().0.iter_mut().enumerate() {
|
||||||
|
*d = src[((straight.start + i) / SCALE).min(img.w - 1)];
|
||||||
|
}
|
||||||
if corner {
|
if corner {
|
||||||
for (i, d) in row.as_chunks_mut::<4>().0.iter_mut().enumerate() {
|
let row = pm[at..at + width * 4].as_chunks_mut::<4>().0;
|
||||||
let s = src[((x0 + i) / SCALE).min(img.w - 1)];
|
for x in (x0..mid.start).chain(mid.end..x1) {
|
||||||
match cover(r, radius, x0 + i, y) {
|
let s = src[(x / SCALE).min(img.w - 1)];
|
||||||
256 => d.copy_from_slice(&s),
|
match cover(r, radius, x, y) {
|
||||||
|
256 => row[x] = s,
|
||||||
0 => {}
|
0 => {}
|
||||||
k => blend(d, s, k),
|
k => blend(&mut row[x], s, k),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
copied = None;
|
|
||||||
} else {
|
|
||||||
for (i, d) in row.as_chunks_mut::<4>().0.iter_mut().enumerate() {
|
|
||||||
d.copy_from_slice(&src[((x0 + i) / SCALE).min(img.w - 1)]);
|
|
||||||
}
|
|
||||||
copied = Some(y);
|
|
||||||
}
|
}
|
||||||
|
copied = (!corner).then_some(y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wet highlight: white along the top of `r`, `alpha` (percent) at
|
/// `img` at half the size each way, each pixel the average of four.
|
||||||
/// the edge and gone `depth` pixels down.
|
fn halve(img: &Image) -> Image {
|
||||||
pub fn gloss(pm: &mut [u8], width: usize, r: Rect, radius: f32, alpha: u8, depth: f32) {
|
let (w, h) = (img.w.div_ceil(2), img.h.div_ceil(2));
|
||||||
let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0);
|
let mut px = vec![[0u8; 4]; w * h];
|
||||||
let top = Rect { h: depth.min(r.h), ..r };
|
for y in 0..h {
|
||||||
let (cols, rows) = span(top, width, pm.len() / 4 / width.max(1));
|
let (r0, r1) = (2 * y, (2 * y + 1).min(img.h - 1));
|
||||||
let white = [255u8; 4];
|
for x in 0..w {
|
||||||
for y in rows {
|
let (c0, c1) = (2 * x, (2 * x + 1).min(img.w - 1));
|
||||||
let fade = 1.0 - (y as f32 + 0.5 - r.y) / depth;
|
let mut sum = [0u32; 4];
|
||||||
let a = (fade.max(0.0) * alpha as f32 * 256.0 / 100.0) as u32;
|
for p in [img.px[r0 * img.w + c0], img.px[r0 * img.w + c1], img.px[r1 * img.w + c0], img.px[r1 * img.w + c1]] {
|
||||||
let row = &mut pm[y * width * 4..(y + 1) * width * 4];
|
add(&mut sum, p);
|
||||||
for x in cols.clone() {
|
|
||||||
let k = (cover(r, radius, x, y) * a) >> 8;
|
|
||||||
if k > 0 {
|
|
||||||
blend(&mut row[x * 4..x * 4 + 4], white, k);
|
|
||||||
}
|
}
|
||||||
|
px[y * w + x] = sum.map(|s| (s / 4) as u8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Image { w, h, px }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How far (`x`, `y`) is outside `r` with its corners rounded by `radius`
|
/// The average of two pixels, all four bytes at once.
|
||||||
/// (negative inside).
|
fn mid(a: [u8; 4], b: [u8; 4]) -> [u8; 4] {
|
||||||
fn outside(r: Rect, radius: f32, x: f32, y: f32) -> f32 {
|
let (a, b) = (u32::from_le_bytes(a), u32::from_le_bytes(b));
|
||||||
let (hw, hh) = (r.w / 2.0, r.h / 2.0);
|
((a & b) + (((a ^ b) & 0xfefe_fefe) >> 1)).to_le_bytes()
|
||||||
let qx = libm::fabsf(x - (r.x + hw)) - (hw - radius);
|
|
||||||
let qy = libm::fabsf(y - (r.y + hh)) - (hh - radius);
|
|
||||||
let (ox, oy) = (qx.max(0.0), qy.max(0.0));
|
|
||||||
let out = if ox > 0.0 && oy > 0.0 { libm::sqrtf(ox * ox + oy * oy) } else { ox.max(oy) };
|
|
||||||
out + qx.max(qy).min(0.0) - radius
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A soft shadow under `r` (corners rounded by `radius`): cast `down`
|
/// `img` at twice the size (cut to `w` by `h`), the new pixels between
|
||||||
/// pixels below it, `dark` (percent) at its edge and gone `spread` out.
|
/// the old ones the average of their neighbours: the even rows from the
|
||||||
/// Only where it shows is touched: what `r` covers is drawn over anyway.
|
/// source rows, the odd ones from the rows either side.
|
||||||
pub fn shadow(pm: &mut [u8], width: usize, r: Rect, radius: f32, down: f32, spread: f32, dark: u8) {
|
fn double(img: &Image, w: usize, h: usize) -> Image {
|
||||||
let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0);
|
let mut px = vec![[0u8; 4]; w * h];
|
||||||
let caster = Rect { y: r.y + down, ..r };
|
for y in (0..h).step_by(2) {
|
||||||
let around = Rect { x: r.x - spread, y: r.y - spread, w: r.w + 2.0 * spread, h: r.h + down + 2.0 * spread };
|
let src = &img.px[(y / 2).min(img.h - 1) * img.w..][..img.w];
|
||||||
let (cols, rows) = span(around, width, pm.len() / 4 / width.max(1));
|
let out = &mut px[y * w..(y + 1) * w];
|
||||||
// Well inside `r`: covered by it.
|
for (x, o) in out.iter_mut().enumerate() {
|
||||||
let (in_x, in_y) = (r.x + radius..r.x + r.w - radius, r.y..r.y + r.h);
|
let s = src[(x / 2).min(img.w - 1)];
|
||||||
for y in rows {
|
*o = if x % 2 == 0 { s } else { mid(s, src[(x / 2 + 1).min(img.w - 1)]) };
|
||||||
let fy = y as f32 + 0.5;
|
|
||||||
let row = &mut pm[y * width * 4..(y + 1) * width * 4];
|
|
||||||
for x in cols.clone() {
|
|
||||||
let fx = x as f32 + 0.5;
|
|
||||||
if in_x.contains(&fx) && in_y.contains(&fy) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let d = outside(caster, radius, fx, fy).max(0.0);
|
|
||||||
let fade = (1.0 - d / spread).max(0.0);
|
|
||||||
let k = (fade * fade * dark as f32 * 256.0 / 100.0) as u32;
|
|
||||||
if k > 0 {
|
|
||||||
blend(&mut row[x * 4..x * 4 + 4], [0, 0, 0, 255], k);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for y in (1..h).step_by(2) {
|
||||||
|
let below = if y + 1 < h { y + 1 } else { y - 1 };
|
||||||
|
for x in 0..w {
|
||||||
|
px[y * w + x] = mid(px[(y - 1) * w + x], px[below * w + x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Image { w, h, px }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A box blur run three times over (close to a Gaussian), `r` pixels each
|
/// A box blur run three times over (close to a Gaussian), `r` pixels each
|
||||||
/// way, rows then columns.
|
/// way: along each row, then down every column at once, so memory is
|
||||||
|
/// walked in order.
|
||||||
fn blur(img: &mut Image, r: usize) {
|
fn blur(img: &mut Image, r: usize) {
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let (w, h) = (img.w, img.h);
|
let mut line = vec![[0u8; 4]; img.w];
|
||||||
let mut line = vec![[0u8; 4]; w.max(h)];
|
let mut sums = vec![[0u32; 4]; img.w];
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
for y in 0..h {
|
for y in 0..img.h {
|
||||||
pass(&mut img.px, y * w, 1, w, r, &mut line);
|
across(&mut img.px[y * img.w..(y + 1) * img.w], r, &mut line);
|
||||||
}
|
|
||||||
for x in 0..w {
|
|
||||||
pass(&mut img.px, x, w, h, r, &mut line);
|
|
||||||
}
|
}
|
||||||
|
down(img, r, &mut sums);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One box pass along `n` pixels starting at `start`, `step` apart; the
|
/// Dividing a sum of `2r + 1` bytes by that, as a multiply and a shift:
|
||||||
/// edges are held.
|
/// exact for such sums.
|
||||||
fn pass(px: &mut [[u8; 4]], start: usize, step: usize, n: usize, r: usize, line: &mut [[u8; 4]]) {
|
fn reciprocal(r: usize) -> u32 {
|
||||||
let at = |i: usize| start + i.min(n - 1) * step;
|
(65536 + 2 * r as u32) / (2 * r as u32 + 1)
|
||||||
let width = (2 * r + 1) as u32;
|
}
|
||||||
for c in 0..4 {
|
|
||||||
// The window centred on pixel 0, the edge held.
|
fn add(sum: &mut [u32; 4], p: [u8; 4]) {
|
||||||
let mut sum = px[at(0)][c] as u32 * (r as u32 + 1) + (1..=r).map(|i| px[at(i)][c] as u32).sum::<u32>();
|
sum[0] += p[0] as u32;
|
||||||
for (i, out) in line.iter_mut().take(n).enumerate() {
|
sum[1] += p[1] as u32;
|
||||||
out[c] = (sum / width) as u8;
|
sum[2] += p[2] as u32;
|
||||||
sum = sum + px[at(i + r + 1)][c] as u32 - px[at(i.saturating_sub(r))][c] as u32;
|
sum[3] += p[3] as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub(sum: &mut [u32; 4], p: [u8; 4]) {
|
||||||
|
sum[0] -= p[0] as u32;
|
||||||
|
sum[1] -= p[1] as u32;
|
||||||
|
sum[2] -= p[2] as u32;
|
||||||
|
sum[3] -= p[3] as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn average(sum: &[u32; 4], inv: u32) -> [u8; 4] {
|
||||||
|
[((sum[0] * inv) >> 16) as u8, ((sum[1] * inv) >> 16) as u8, ((sum[2] * inv) >> 16) as u8, ((sum[3] * inv) >> 16) as u8]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One pass along `row`, its ends held.
|
||||||
|
fn across(row: &mut [[u8; 4]], r: usize, line: &mut [[u8; 4]]) {
|
||||||
|
let n = row.len();
|
||||||
|
let line = &mut line[..n];
|
||||||
|
line.copy_from_slice(row);
|
||||||
|
let inv = reciprocal(r);
|
||||||
|
let mut sum = [0u32; 4];
|
||||||
|
for i in 0..=2 * r {
|
||||||
|
add(&mut sum, line[i.saturating_sub(r).min(n - 1)]);
|
||||||
|
}
|
||||||
|
for (i, out) in row.iter_mut().enumerate() {
|
||||||
|
*out = average(&sum, inv);
|
||||||
|
add(&mut sum, line[(i + r + 1).min(n - 1)]);
|
||||||
|
sub(&mut sum, line[i.saturating_sub(r)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One pass down every column, a row at a time; the top and bottom held.
|
||||||
|
fn down(img: &mut Image, r: usize, sums: &mut [[u32; 4]]) {
|
||||||
|
let (w, h) = (img.w, img.h);
|
||||||
|
let src = img.px.clone();
|
||||||
|
let row = |i: usize| &src[i.min(h - 1) * w..][..w];
|
||||||
|
let inv = reciprocal(r);
|
||||||
|
sums.fill([0; 4]);
|
||||||
|
for i in 0..=2 * r {
|
||||||
|
for (s, p) in sums.iter_mut().zip(row(i.saturating_sub(r))) {
|
||||||
|
add(s, *p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i, p) in line.iter().take(n).enumerate() {
|
for y in 0..h {
|
||||||
px[start + i * step] = *p;
|
for (out, s) in img.px[y * w..(y + 1) * w].iter_mut().zip(sums.iter()) {
|
||||||
|
*out = average(s, inv);
|
||||||
|
}
|
||||||
|
for ((s, a), b) in sums.iter_mut().zip(row(y + r + 1)).zip(row(y.saturating_sub(r))) {
|
||||||
|
add(s, *a);
|
||||||
|
sub(s, *b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extern crate alloc;
|
|||||||
pub mod element;
|
pub mod element;
|
||||||
pub mod glass;
|
pub mod glass;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
mod pixels;
|
||||||
pub mod motion;
|
pub mod motion;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
|
|||||||
184
crates/k-ui/src/pixels.rs
Normal file
184
crates/k-ui/src/pixels.rs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
//! K-UI's own pixel loops over a window's bytes (R, G, B, A, premultiplied,
|
||||||
|
//! `width` pixels a row): tiny-skia's translucent fills, gradients and
|
||||||
|
//! patterns go through its float pipeline, which is slow wherever float is
|
||||||
|
//! (QEMU emulates every float operation). Here float is kept to the
|
||||||
|
//! rounded corners; straight runs share one value a row (or column) and
|
||||||
|
//! blend in integers, two channels at a time.
|
||||||
|
|
||||||
|
use crate::layout::Rect;
|
||||||
|
use crate::theme::Rgba;
|
||||||
|
|
||||||
|
/// The pixels of `r` (whole ones) inside a `width` by `height` window:
|
||||||
|
/// columns and rows.
|
||||||
|
pub fn span(r: Rect, width: usize, height: usize) -> (core::ops::Range<usize>, core::ops::Range<usize>) {
|
||||||
|
let (x0, y0) = (r.x.max(0.0) as usize, r.y.max(0.0) as usize);
|
||||||
|
let (x1, y1) = (((r.x + r.w).max(0.0) as usize).min(width), ((r.y + r.h).max(0.0) as usize).min(height));
|
||||||
|
(x0..x1.max(x0), y0..y1.max(y0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(pm: &[u8], width: usize) -> usize {
|
||||||
|
pm.len() / 4 / width.max(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The columns of `cols` clear of `r`'s rounded corners (radius `radius`):
|
||||||
|
/// what lies between them.
|
||||||
|
pub fn between_corners(r: Rect, radius: f32, cols: &core::ops::Range<usize>) -> core::ops::Range<usize> {
|
||||||
|
let lo = (libm::ceilf(r.x + radius).max(0.0) as usize).clamp(cols.start, cols.end);
|
||||||
|
let hi = (libm::floorf(r.x + r.w - radius).max(0.0) as usize).clamp(lo, cols.end);
|
||||||
|
lo..hi
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether row `y` passes through `r`'s rounded corners.
|
||||||
|
pub fn corner_row(r: Rect, radius: f32, y: usize) -> bool {
|
||||||
|
radius > 0.0 && ((y as f32) < r.y + radius || (y as f32 + 1.0) > r.y + r.h - radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How much of pixel (`x`, `y`) is inside `r` with its corners rounded by
|
||||||
|
/// `radius`, in 256ths.
|
||||||
|
pub fn cover(r: Rect, radius: f32, x: usize, y: usize) -> u32 {
|
||||||
|
let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5);
|
||||||
|
let dx = (r.x + radius - fx).max(fx - (r.x + r.w - radius));
|
||||||
|
let dy = (r.y + radius - fy).max(fy - (r.y + r.h - radius));
|
||||||
|
if dx <= 0.0 || dy <= 0.0 {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
((radius + 0.5 - libm::sqrtf(dx * dx + dy * dy)).clamp(0.0, 1.0) * 256.0) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pixel `d` moved toward `s` by `k` 256ths.
|
||||||
|
pub fn blend(d: &mut [u8; 4], s: [u8; 4], k: u32) {
|
||||||
|
let (d32, s32) = (u32::from_le_bytes(*d), u32::from_le_bytes(s));
|
||||||
|
let (keep, take) = (256 - k, k);
|
||||||
|
// Red and blue, then green and alpha, each pair in one word.
|
||||||
|
let rb = ((d32 & 0x00ff_00ff) * keep + (s32 & 0x00ff_00ff) * take) >> 8 & 0x00ff_00ff;
|
||||||
|
let ga = (((d32 >> 8) & 0x00ff_00ff) * keep + ((s32 >> 8) & 0x00ff_00ff) * take) & 0xff00_ff00;
|
||||||
|
*d = (rb | ga).to_le_bytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pixels `cols` of row `y` moved toward `s` by `k` 256ths.
|
||||||
|
pub fn blend_run(pm: &mut [u8], width: usize, y: usize, cols: core::ops::Range<usize>, s: [u8; 4], k: u32) {
|
||||||
|
if k == 0 || cols.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let row = &mut pm[(y * width + cols.start) * 4..(y * width + cols.end) * 4];
|
||||||
|
for px in row.as_chunks_mut::<4>().0 {
|
||||||
|
blend(px, s, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `part` of `pm` moved toward `c` by `k` 256ths: a screen fading in.
|
||||||
|
pub fn veil(pm: &mut [u8], width: usize, part: Rect, c: Rgba, k: u32) {
|
||||||
|
let (cols, rows) = span(part, width, height(pm, width));
|
||||||
|
for y in rows {
|
||||||
|
blend_run(pm, width, y, cols.clone(), [c.0, c.1, c.2, 255], k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The wet highlight: white along the top of `r`, `alpha` (percent) at
|
||||||
|
/// the edge and gone `depth` pixels down.
|
||||||
|
pub fn gloss(pm: &mut [u8], width: usize, r: Rect, radius: f32, alpha: u8, depth: f32) {
|
||||||
|
let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0);
|
||||||
|
let (cols, rows) = span(Rect { h: depth.min(r.h), ..r }, width, height(pm, width));
|
||||||
|
let mid = between_corners(r, radius, &cols);
|
||||||
|
let white = [255u8; 4];
|
||||||
|
for y in rows {
|
||||||
|
let fade = 1.0 - (y as f32 + 0.5 - r.y) / depth;
|
||||||
|
let a = (fade.max(0.0) * alpha as f32 * 256.0 / 100.0) as u32;
|
||||||
|
let corner = corner_row(r, radius, y);
|
||||||
|
let straight = if corner { mid.clone() } else { cols.clone() };
|
||||||
|
blend_run(pm, width, y, straight, white, a);
|
||||||
|
if corner {
|
||||||
|
for x in (cols.start..mid.start).chain(mid.end..cols.end) {
|
||||||
|
let k = (cover(r, radius, x, y) * a) >> 8;
|
||||||
|
if k > 0 {
|
||||||
|
blend_run(pm, width, y, x..x + 1, white, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How dark a shadow is (256ths) `d` pixels outside what casts it.
|
||||||
|
fn shade(d: f32, spread: f32, dark: u8) -> u32 {
|
||||||
|
let fade = (1.0 - d.max(0.0) / spread).max(0.0);
|
||||||
|
(fade * fade * dark as f32 * 256.0 / 100.0) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A soft shadow under `r` (corners rounded by `radius`): cast `down`
|
||||||
|
/// pixels below it, `dark` (percent) at its edge and gone `spread` out.
|
||||||
|
/// What `r` covers is left alone: it is drawn over anyway.
|
||||||
|
pub fn shadow(pm: &mut [u8], width: usize, r: Rect, radius: f32, down: f32, spread: f32, dark: u8) {
|
||||||
|
let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0);
|
||||||
|
let c = Rect { y: r.y + down, ..r };
|
||||||
|
let around = Rect { x: r.x - spread, y: r.y - spread, w: r.w + 2.0 * spread, h: r.h + down + 2.0 * spread };
|
||||||
|
let (cols, rows) = span(around, width, height(pm, width));
|
||||||
|
let (panel, _) = span(r, width, height(pm, width));
|
||||||
|
// Where the caster's edges are straight, across and down.
|
||||||
|
let mid = between_corners(c, radius, &cols);
|
||||||
|
let (sy0, sy1) = (c.y + radius, c.y + c.h - radius);
|
||||||
|
let black = [0, 0, 0, 255];
|
||||||
|
for y in rows {
|
||||||
|
let fy = y as f32 + 0.5;
|
||||||
|
let in_panel = fy >= r.y && fy < r.y + r.h;
|
||||||
|
// Above or below the caster, where its top and bottom are straight:
|
||||||
|
// one darkness for the whole run.
|
||||||
|
let k = shade((c.y - fy).max(fy - (c.y + c.h)), spread, dark);
|
||||||
|
let segments = match in_panel {
|
||||||
|
true => [cols.start..panel.start, panel.end..cols.end],
|
||||||
|
false => [cols.clone(), 0..0],
|
||||||
|
};
|
||||||
|
for seg in segments {
|
||||||
|
let straight = seg.start.max(mid.start)..seg.end.min(mid.end);
|
||||||
|
if !in_panel {
|
||||||
|
blend_run(pm, width, y, straight.clone(), black, k);
|
||||||
|
}
|
||||||
|
for x in seg.filter(|x| in_panel || !straight.contains(x)) {
|
||||||
|
let fx = x as f32 + 0.5;
|
||||||
|
let d = match fy >= sy0 && fy < sy1 {
|
||||||
|
// Beside the caster's straight sides.
|
||||||
|
true => (c.x - fx).max(fx - (c.x + c.w)),
|
||||||
|
false => {
|
||||||
|
let dx = (c.x + radius - fx).max(fx - (c.x + c.w - radius)).max(0.0);
|
||||||
|
let dy = (sy0 - fy).max(fy - sy1).max(0.0);
|
||||||
|
libm::sqrtf(dx * dx + dy * dy) - radius
|
||||||
|
}
|
||||||
|
};
|
||||||
|
blend_run(pm, width, y, x..x + 1, black, shade(d, spread, dark));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blend_moves_every_channel_by_its_share() {
|
||||||
|
let mut d = [0, 100, 200, 255];
|
||||||
|
blend(&mut d, [255, 0, 100, 255], 128);
|
||||||
|
assert_eq!(d, [127, 50, 150, 255]);
|
||||||
|
let mut d = [10, 20, 30, 40];
|
||||||
|
blend(&mut d, [99, 99, 99, 99], 0);
|
||||||
|
assert_eq!(d, [10, 20, 30, 40], "none of it");
|
||||||
|
blend(&mut d, [99, 98, 97, 96], 256);
|
||||||
|
assert_eq!(d, [99, 98, 97, 96], "all of it");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_shadow_darkens_around_and_below_but_not_under_or_far() {
|
||||||
|
let w = 60;
|
||||||
|
let mut pm = vec![200u8; w * w * 4];
|
||||||
|
let r = Rect { x: 20.0, y: 20.0, w: 20.0, h: 20.0 };
|
||||||
|
shadow(&mut pm, w, r, 6.0, 3.0, 7.0, 30);
|
||||||
|
let red = |x: usize, y: usize| pm[(y * w + x) * 4];
|
||||||
|
assert_eq!(red(30, 30), 200, "under the panel: left alone");
|
||||||
|
assert!(red(30, 41) < red(30, 18), "darker below than above");
|
||||||
|
assert!(red(17, 30) < 200, "beside it");
|
||||||
|
assert_eq!(red(5, 5), 200, "far off");
|
||||||
|
assert_eq!(red(30, 55), 200, "past its spread");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern crate std;
|
||||||
|
use std::vec;
|
||||||
|
}
|
||||||
@@ -260,13 +260,13 @@ impl Painter<'_, '_> {
|
|||||||
fn shadow(&mut self, r: Rect) {
|
fn shadow(&mut self, r: Rect) {
|
||||||
let width = self.pm.width() as usize;
|
let width = self.pm.width() as usize;
|
||||||
let (down, spread, dark) = SHADOW;
|
let (down, spread, dark) = SHADOW;
|
||||||
crate::glass::shadow(self.pm.data_mut(), width, r, self.theme.corner_radius, down, spread, dark);
|
crate::pixels::shadow(self.pm.data_mut(), width, r, self.theme.corner_radius, down, spread, dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wet highlight along the top.
|
/// The wet highlight along the top.
|
||||||
fn gloss(&mut self, r: Rect) {
|
fn gloss(&mut self, r: Rect) {
|
||||||
let width = self.pm.width() as usize;
|
let width = self.pm.width() as usize;
|
||||||
crate::glass::gloss(self.pm.data_mut(), width, r, self.theme.corner_radius, self.theme.highlight, GLOSS);
|
crate::pixels::gloss(self.pm.data_mut(), width, r, self.theme.corner_radius, self.theme.highlight, GLOSS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `r` filled with `c`, its corners rounded by `radius`.
|
/// `r` filled with `c`, its corners rounded by `radius`.
|
||||||
@@ -308,8 +308,15 @@ pub fn render<E>(pm: &mut PixmapMut, root: &Element<E>, rects: &[Rect], fonts: &
|
|||||||
p.ring(r);
|
p.ring(r);
|
||||||
}
|
}
|
||||||
if frame.enter < 1.0 {
|
if frame.enter < 1.0 {
|
||||||
let veil = Rgba(bg.0, bg.1, bg.2, ((1.0 - frame.enter) * 255.0) as u8);
|
// The new screen's things fade up over the wallpaper, which stays.
|
||||||
let all = Rect { x: 0.0, y: 0.0, w: p.pm.width() as f32, h: p.pm.height() as f32 };
|
let mut leaves = alloc::vec::Vec::new();
|
||||||
p.fill(all, 0.0, veil);
|
crate::tree::leaf_rects(root, rects, &mut 0, &mut leaves);
|
||||||
|
if let Some(content) = leaves.into_iter().map(|r| r.grow(REACH)).reduce(Rect::union) {
|
||||||
|
let (width, k) = (p.pm.width() as usize, ((1.0 - frame.enter) * 256.0) as u32);
|
||||||
|
match backdrop {
|
||||||
|
Some(b) => b.fade(p.pm.data_mut(), width, content, k),
|
||||||
|
None => crate::pixels::veil(p.pm.data_mut(), width, content, bg, k),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ pub fn whole_elements<E>(view: &Element<E>, rects: &[Rect], part: Rect) -> Rect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leaf_rects<E>(e: &Element<E>, rects: &[Rect], index: &mut usize, out: &mut Vec<Rect>) {
|
pub fn leaf_rects<E>(e: &Element<E>, rects: &[Rect], index: &mut usize, out: &mut Vec<Rect>) {
|
||||||
let me = *index;
|
let me = *index;
|
||||||
*index += 1;
|
*index += 1;
|
||||||
match &e.kind {
|
match &e.kind {
|
||||||
|
|||||||
Reference in New Issue
Block a user