1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
//! This crate defines a trait of `Compositor` .
//! A compositor composites a list of sources buffers to a single destination buffer.
#![allow(clippy::range_plus_one)]
#![no_std]
extern crate framebuffer;
extern crate shapes;
use core::iter::IntoIterator;
use framebuffer::{Framebuffer, Pixel};
use shapes::{Coord, Rectangle};
use core::ops::Range;
/// A compositor composites (combines or blends) a series of "source" framebuffers onto a single "destination" framebuffer.
/// The type parameter `B` allows a compositor to support multiple types of regions or "bounding boxes",
/// given by the trait bound `CompositableRegion`.
pub trait Compositor {
/// Composites the framebuffers in the list of source framebuffers `src_fbs` onto the destination framebuffer `dest_fb`.
///
/// # Arguments
/// * `src_fbs`: an iterator over the source framebuffers to be composited,
/// along with where in the `dest_fb` they should be composited.
/// * `dest_fb`: the destination framebuffer that will hold the source framebuffers to be composited.
/// * `dest_bounding_boxes`: an iterator over bounding boxes that specify which regions
/// in the destination framebuffer should be updated.
/// For each source framebuffer in `src_fbs`, the compositor will iterate over every bounding box
/// and find the corresponding region in that source framebuffer and then blend that region into the destination.
///
/// For example, if the window manager wants to draw a new partially-transparent window,
/// it will pass the framebuffers for all existing windows plus the new window (in bottom-to-top order)
/// to the compositor, in the argument `src_fbs`.
/// The `dest_fb` would be the final framebuffer mapped to the display device (screen memory),
/// and the `bounding_boxes` would be an iterator over just a single region in the final framebuffer
/// where that new window will be located.
/// When the source framebuffers are composited from bottom to top, the compositor will redraw the region of every source framebuffer
/// that intersects with that bounding box.
///
/// For another example, suppose the window manager wants to draw a transparent mouse pointer on top of all windows.
/// It will pass the framebuffers of existing windows as well as a top framebuffer that contains the mouse pointer image.
/// In this case, the `bounding_boxes` could be the coordinates of all individual pixels in the mouse pointer image
/// (expressed as coordinates in the final framebuffer).
fn composite<'a, B: CompositableRegion + Clone, P: 'a + Pixel>(
&mut self,
src_fbs: impl IntoIterator<Item = FramebufferUpdates<'a, P>>,
dest_fb: &mut Framebuffer<P>,
dest_bounding_boxes: impl IntoIterator<Item = B> + Clone,
) -> Result<(), &'static str>;
}
/// A source framebuffer to be composited, along with its target position.
pub struct FramebufferUpdates<'a, P: Pixel> {
/// The source framebuffer to be composited.
pub src_framebuffer: &'a Framebuffer<P>,
/// The coordinate in the destination framebuffer where the source `framebuffer`
/// should be composited.
/// This coordinate is expressed relative to the top-left corner of the destination framebuffer.
pub coordinate_in_dest_framebuffer: Coord,
}
/// A `CompositableRegion` is an abstract region (i.e., a bounding box)
/// that can optimize the compositing (blending) of one framebuffer into another framebuffer
/// according to the specifics of the region's shape.
/// For example, a single 2-D point (`Coord`) offers no real room for optimization
/// because only one pixel will be composited,
/// but a rectangle **does** allow for optimization, as a large chunk of pixels can be composited all at once.
///
/// In addition, a `CompositableRegion` makes it easier for a compositor to only composite pixels in a subset of a given source framebuffer
/// rather than forcing it to composite the whole framebuffer, which vastly improves performance.
pub trait CompositableRegion {
/// Returns the number of pixels in the region.
fn size(&self) -> usize;
/// Returns the range of rows covered by this region,
/// given as row indices where row `0` is the top row in the region.
fn row_range(&self) -> Range<isize>;
/// Blends the pixels in the source framebuffer `src_fb` within the range of rows (`src_fb_row_range`)
/// into the pixels in the destination framebuffer `dest_fb`.
/// The `dest_coord` is the coordinate in the destination buffer (relative to its top-left corner)
/// where the `src_fb` will be composited (starting at the `src_fb`'s top-left corner).
/// `src_fb_row_range` is the index range of rows in the source framebuffer to blend.
fn blend_buffers<P: Pixel>(
&self,
src_fb: &Framebuffer<P>,
dest_fb: &mut Framebuffer<P>,
dest_coord: Coord,
src_fb_row_range: Range<usize>
) -> Result<(), &'static str>;
}
impl CompositableRegion for Coord {
#[inline]
fn row_range(&self) -> Range<isize> {
self.y..self.y + 1
}
#[inline]
fn size(&self) -> usize {
1
}
fn blend_buffers<P: Pixel>(
&self,
src_fb: &Framebuffer<P>,
dest_fb: &mut Framebuffer<P>,
dest_coord: Coord,
_src_fb_row_range: Range<usize>,
) -> Result<(), &'static str>{
let relative_coord = *self - dest_coord;
if let Some(pixel) = src_fb.get_pixel(relative_coord) {
dest_fb.draw_pixel(*self, pixel);
}
Ok(())
}
}
impl CompositableRegion for Rectangle {
#[inline]
fn row_range(&self) -> Range<isize> {
self.top_left.y..self.bottom_right.y
}
#[inline]
fn size(&self) -> usize {
(self.bottom_right.x - self.top_left.x) as usize * (self.bottom_right.y - self.top_left.y) as usize
}
fn blend_buffers<P: Pixel>(
&self,
src_fb: &Framebuffer<P>,
dest_fb: &mut Framebuffer<P>,
dest_coord: Coord,
src_fb_row_range: Range<usize>,
) -> Result<(), &'static str> {
let (dest_width, dest_height) = dest_fb.get_size();
let (src_width, src_height) = src_fb.get_size();
let start_y = core::cmp::max(src_fb_row_range.start as isize + dest_coord.y, self.top_left.y);
let end_y = core::cmp::min(src_fb_row_range.end as isize + dest_coord.y, self.bottom_right.y);
// skip if the updated part is not in the dest framebuffer
let dest_start = Coord::new(
core::cmp::max(0, self.top_left.x),
core::cmp::max(0, start_y)
);
let dest_end = Coord::new(
core::cmp::min(dest_width as isize, self.bottom_right.x),
core::cmp::min(dest_height as isize, end_y)
);
if dest_end.x < 0
|| dest_start.x > dest_width as isize
|| dest_end.y < 0
|| dest_start.y > dest_height as isize
{
return Ok(());
}
// skip if the updated part is not in the source framebuffer
let coordinate_start = dest_start - dest_coord;
let coordinate_end = dest_end - dest_coord;
if coordinate_end.x < 0
|| coordinate_start.x > src_width as isize
|| coordinate_end.y < 0
|| coordinate_start.y > src_height as isize
{
return Ok(());
}
let src_x_start = core::cmp::max(0, coordinate_start.x) as usize;
let src_y_start = core::cmp::max(0, coordinate_start.y) as usize;
// draw only the part within the dest buffer
let width = core::cmp::min(coordinate_end.x as usize, src_width) - src_x_start;
let height = core::cmp::min(coordinate_end.y as usize, src_height) - src_y_start;
// copy every line of the block to the dest framebuffer.
let src_buffer = &src_fb.buffer();
for i in 0..height {
let src_start = Coord::new(src_x_start as isize, (src_y_start + i) as isize);
let src_start_index = match src_fb.index_of(src_start) {
Some(index) => index,
None => {continue;}
};
let src_end_index = src_start_index + width;
let dest_start = src_start + dest_coord;
let dest_start_index = match dest_fb.index_of(dest_start) {
Some(index) => index,
None => {continue;}
};
dest_fb.composite_buffer(&src_buffer[src_start_index..src_end_index], dest_start_index);
}
Ok(())
}
}