use crate::base::Rect;
use crate::base::Rgba;
use super::cell::Cell;
use super::layer::{add_saturating, Blend, Layer};
use super::surface::Surface;
pub struct Compositor {
max_rects: usize,
full_frame_ratio: f64,
ground: Option<Rgba>,
debug_damage: bool,
damage: Vec<Rect>,
gather: Vec<Rect>,
order: Vec<usize>,
}
impl Default for Compositor {
fn default() -> Self {
Compositor::new()
}
}
impl Compositor {
pub fn new() -> Compositor {
Compositor {
max_rects: 16,
full_frame_ratio: 0.7,
ground: None,
debug_damage: false,
damage: Vec::new(),
gather: Vec::new(),
order: Vec::new(),
}
}
pub fn set_debug_damage(&mut self, on: bool) {
self.debug_damage = on;
}
pub fn debug_damage(&self) -> bool {
self.debug_damage
}
pub fn set_ground(&mut self, ground: Option<Rgba>) {
self.ground = ground;
}
pub fn ground(&self) -> Option<Rgba> {
self.ground
}
pub fn flatten<'a>(&'a mut self, frame: &mut Surface, layers: &mut [Layer]) -> &'a [Rect] {
let bounds = Rect::from_size(frame.size());
self.gather.clear();
for layer in layers.iter_mut() {
Self::gather_layer_damage(layer, &mut self.gather);
}
self.finish_damage(bounds);
if self.damage.is_empty() {
return &self.damage;
}
self.sort_layers(layers);
for di in 0..self.damage.len() {
let rect = self.damage[di];
for y in rect.y..rect.bottom() {
for x in rect.x..rect.right() {
let (composed, owner) = self.compose_cell(layers, x, y);
let composed = match owner {
Some(li) if composed.glyph.is_pooled() || composed.link != 0 => {
frame.adopt_from(composed, layers[li].surface())
}
_ => composed,
};
frame.put_composed(x, y, composed);
}
frame.repair_wide_pairs(y, rect.x, rect.right());
}
}
if self.debug_damage {
for di in 0..self.damage.len() {
outline_damage(frame, self.damage[di]);
}
}
&self.damage
}
fn compose_cell(&self, layers: &[Layer], x: i32, y: i32) -> (Cell, Option<usize>) {
let mut acc = Cell::EMPTY;
let mut owner: Option<usize> = None;
for &li in &self.order {
let layer = &layers[li];
let blend = layer.blend();
let op = layer.opacity();
let Some(src) = layer.contribution(x, y) else {
continue;
};
let bg = scale_alpha(src.bg, op);
let has_glyph = !src.glyph.is_empty();
if !has_glyph && bg.is_transparent() {
continue; }
match blend {
Blend::Normal => {
if bg.is_opaque() {
acc.bg = bg;
if has_glyph {
acc.glyph = src.glyph;
acc.fg = blend_fg(scale_alpha(src.fg, op), bg);
acc.ul = blend_fg(scale_alpha(src.ul, op), bg);
acc.attrs = src.attrs;
acc.link = src.link;
owner = Some(li);
} else {
acc.glyph = super::cell::Glyph::EMPTY;
acc.fg = Cell::EMPTY.fg;
acc.ul = Cell::EMPTY.ul;
acc.attrs = Cell::EMPTY.attrs;
acc.link = 0;
owner = None;
}
continue;
}
if !bg.is_transparent() {
acc.bg = bg.over(self.grounded(acc.bg));
}
if has_glyph {
acc.glyph = src.glyph;
acc.fg = blend_fg(scale_alpha(src.fg, op), acc.bg);
acc.ul = blend_fg(scale_alpha(src.ul, op), acc.bg);
acc.attrs = src.attrs;
acc.link = src.link;
owner = Some(li);
} else if !acc.glyph.is_empty() && !bg.is_transparent() {
acc.fg = bg.over(acc.fg);
if !acc.ul.is_transparent() {
acc.ul = bg.over(acc.ul);
}
}
}
Blend::Additive => {
if !bg.is_transparent() {
acc.bg = add_saturating(self.grounded(acc.bg), bg);
}
if has_glyph {
acc.glyph = src.glyph;
acc.fg = add_saturating(acc.bg, scale_alpha(src.fg, op));
acc.ul = add_saturating(acc.bg, scale_alpha(src.ul, op));
acc.attrs = src.attrs;
acc.link = src.link;
owner = Some(li);
} else {
if !acc.glyph.is_empty() {
acc.fg = add_saturating(acc.fg, bg);
if !acc.ul.is_transparent() {
acc.ul = add_saturating(acc.ul, bg);
}
}
}
}
}
}
(acc, owner)
}
fn grounded(&self, acc_bg: Rgba) -> Rgba {
match self.ground {
Some(g) if acc_bg.is_transparent() => g,
_ => acc_bg,
}
}
fn gather_layer_damage(layer: &mut Layer, gather: &mut Vec<Rect>) {
layer.take_frame_damage(gather);
let origin = layer.origin();
let visible = layer.visible();
let start = gather.len();
layer.surface_mut().take_damage(gather);
if !visible {
gather.truncate(start);
} else {
for r in &mut gather[start..] {
*r = r.translate(origin.x, origin.y);
}
}
}
fn finish_damage(&mut self, bounds: Rect) {
self.damage.clear();
for i in 0..self.gather.len() {
let r = self.gather[i];
let r = Rect::new(r.x - 1, r.y, r.w + 2, r.h).intersect(bounds);
if r.is_empty() {
continue;
}
Self::push_coalesced(&mut self.damage, r);
}
if self.damage.len() > self.max_rects {
let union = self.damage.drain(..).fold(Rect::ZERO, Rect::union);
self.damage.push(union);
}
if let [only] = self.damage[..] {
let frame_area = bounds.area() as f64;
if frame_area > 0.0 && only.area() as f64 / frame_area >= self.full_frame_ratio {
self.damage[0] = bounds;
}
}
}
fn push_coalesced(list: &mut Vec<Rect>, rect: Rect) {
let mut rect = rect;
let mut i = 0;
while i < list.len() {
let other = list[i];
if rect.intersects(other) || contains_rect(other, rect) {
rect = rect.union(other);
list.swap_remove(i);
i = 0;
} else {
i += 1;
}
}
list.push(rect);
}
fn sort_layers(&mut self, layers: &[Layer]) {
self.order.clear();
for (i, layer) in layers.iter().enumerate() {
if !layer.visible() {
continue;
}
let mut pos = self.order.len();
while pos > 0 && layers[self.order[pos - 1]].z() > layer.z() {
pos -= 1;
}
self.order.insert(pos, i);
}
}
pub fn any_dirty(layers: &[Layer]) -> bool {
layers.iter().any(Layer::is_dirty)
}
}
fn contains_rect(a: Rect, b: Rect) -> bool {
a.intersect(b) == b
}
fn outline_damage(frame: &mut Surface, rect: Rect) {
const DEBUG_PINK: Rgba = Rgba::new(255, 0, 200, 255);
let r = rect.intersect(frame.bounds());
if r.is_empty() {
return;
}
let tint = |x: i32, y: i32, frame: &mut Surface| {
let Some(&cell) = frame.get(x, y) else { return };
if cell.is_continuation() {
return; }
let bg = cell.bg.lerp(DEBUG_PINK, 0.5);
frame.put_composed(x, y, Cell { bg, ..cell });
};
for x in r.x..r.right() {
tint(x, r.y, frame);
tint(x, r.bottom() - 1, frame);
}
for y in r.y..r.bottom() {
tint(r.x, y, frame);
tint(r.right() - 1, y, frame);
}
for y in r.y..r.bottom() {
frame.repair_wide_pairs(y, r.x, r.right());
}
}
fn scale_alpha(c: crate::base::Rgba, opacity: f32) -> crate::base::Rgba {
if opacity >= 1.0 {
return c;
}
let a = (c.a as f32 * opacity.clamp(0.0, 1.0)).round() as u8;
c.with_alpha(a)
}
fn blend_fg(fg: crate::base::Rgba, bg: crate::base::Rgba) -> crate::base::Rgba {
if fg.is_opaque() || fg.is_transparent() {
fg
} else {
fg.over(bg)
}
}
#[cfg(test)]
#[path = "compositor_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "profile_tests.rs"]
mod profile;