xous-core/services/graphics-server/src/op.rs
Sean Cross af1a7f5ba5 graphics-server: first general merge of gfxlib
Signed-off-by: Sean Cross <sean@xobs.io>
2020-10-30 10:22:03 +08:00

365 lines
11 KiB
Rust

use super::fonts;
use super::fonts::{Font, GlyphHeader};
/// LCD Frame buffer bounds
pub const LCD_WORDS_PER_LINE: usize = 11;
pub const LCD_PX_PER_LINE: usize = 336;
pub const LCD_LINES: usize = 536;
pub const LCD_FRAME_BUF_SIZE: usize = LCD_WORDS_PER_LINE * LCD_LINES;
const WIDTH: usize = 336;
const HEIGHT: usize = 536;
#[derive(Copy, Clone, PartialEq)]
pub enum PixelColor {
On,
Off,
}
/// For passing frame buffer references
pub type LcdFB = [u32; LCD_FRAME_BUF_SIZE];
/// For storing a full-row wide blit pattern
pub type BlitRow = [u32; LCD_WORDS_PER_LINE];
/// For specifying a vertical region contiguous rows in the frame buffer
/// Range is yr.0..yr.1 (yr.0 included, yr.1 excluded)
#[derive(Copy, Clone)]
pub struct YRegion(pub usize, pub usize);
/// For specifying a region of pixels in the frame buffer
/// Ranges are x0..x1 and y0..y1 (x0 & y0 are included, x1 & y1 are excluded)
#[derive(Copy, Clone)]
pub struct ClipRegion {
pub x0: usize,
pub x1: usize,
pub y0: usize,
pub y1: usize,
}
impl ClipRegion {
pub fn screen() -> ClipRegion {
ClipRegion {
x0: 0,
x1: WIDTH - 1,
y0: 0,
y1: HEIGHT - 1,
}
}
}
/// Blit string with: XOR, bold font, align xr left yr top
pub fn string_bold_left(fb: &mut LcdFB, mut cr: ClipRegion, s: &str) {
let f = Font::new(fonts::GlyphSet::Bold);
for c in s.chars() {
cr.x0 += xor_char(fb, cr, c, f);
}
}
/// Blit string with: XOR, regular font, align xr left yr top
pub fn string_regular_left(fb: &mut LcdFB, mut cr: ClipRegion, s: &str) {
let f = Font::new(fonts::GlyphSet::Regular);
for c in s.chars() {
cr.x0 += xor_char(fb, cr, c, f);
}
}
/// Blit string with: XOR, small font, align xr left yr top
pub fn string_small_left(fb: &mut LcdFB, mut cr: ClipRegion, s: &str) {
let f = Font::new(fonts::GlyphSet::Small);
for c in s.chars() {
cr.x0 += xor_char(fb, cr, c, f);
}
}
/// Calculate the width of all glpyhs and padding for a string
pub fn string_width(s: &str, f: Font) -> usize {
let mut w = 0;
for c in s.chars() {
w += char_width(c, f) + 3;
}
// Subtle padding math: 3px between chars, 1px at left and right ends
w - 1
}
/// Blit a char with: XOR, align left:xr.0 top:yr.0, pad L:1px R:2px
/// Precondition: glyph pattern width must be 32px or less
/// Return: width in pixels of character + padding that were blitted (0 for error)
///
/// Examples of word alignment for source data (rows of glpyh pixels)
/// 1. Fits in one word:
/// row_width:8, row:1 => (data[0].bit_27)->(data[0].bit_24), mask:0x0f00_0000
/// | data[0] |
/// | /- 8px -\ /- 8px -\ /- 8px -\ /- 8px -\ |
/// | 0123 4567 0123 4567 |
/// | 89ab cdef 89ab cdef |
/// ^^^^^^^^^
/// 2. Spans word boundary:
/// row_width:11, row:2 => (data[0].bit_09)->(data[1].bit_31), mask:[0x0000_03ff, 0x800_0000]
/// | data[0] | data[1] |
/// | /--- 11px --\/--- 11px ---\/---- 11px --+-\/-----... |
/// | 0123 4567 89a 67 89ab cdef | 0 |
/// | b cdef 0123 45 | 123 45... |
/// ^^^^^^^^^^^^^^^^
///
/// Examples of word alignment for destination frame buffer:
/// 1. Fits in word: xr:1..7 => (data[0].bit_30)->(data[0].bit_26), mask:0x7c00_0000
/// 2. Spans words: xr:30..36 => (data[0].bit_01)->(data[1].bit_29), mask:[0x0000_0003,0xe000_000]
///
pub fn xor_char(fb: &mut LcdFB, cr: ClipRegion, c: char, f: Font) -> usize {
if cr.y1 > LCD_LINES || cr.x1 > LCD_PX_PER_LINE || cr.x0 >= cr.x1 {
return 0;
}
// Look up glyph and unpack its header
let gpo = (f.glyph_pattern_offset)(c);
let gh = GlyphHeader::new((f.glyph_data)(gpo));
if gh.w > 32 {
return 0;
}
// Add 1px pad to left
let x0 = cr.x0 + 1;
// Calculate word alignment for destination buffer
let x1 = x0 + gh.w;
let dest_low_word = x0 >> 5;
let dest_high_word = x1 >> 5;
let px_in_dest_low_word = 32 - (x0 & 0x1f);
// Blit it
let y0 = cr.y0 + gh.y_offset;
let y_max = if (y0 + gh.h) <= cr.y1 {
gh.h
} else {
cr.y1 - y0
};
for y in 0..y_max {
// Unpack pixels for this glyph row.
// px_in_low_word can include some or all of the pixels for this row of
// the pattern. It may also include pixels for the next row, or, in the
// case of the last row, it may include padding bits.
let px_offset = y * gh.w;
let low_word = gpo + 1 + (px_offset >> 5);
let px_in_low_word = 32 - (px_offset & 0x1f);
let mut pattern = (f.glyph_data)(low_word);
// Mask and align pixels from low word of glyph data array
pattern <<= 32 - px_in_low_word;
pattern >>= 32 - gh.w;
if gh.w > px_in_low_word {
// When pixels for this row span two words in the glyph data array,
// get pixels from the high word too
let px_in_high_word = gh.w - px_in_low_word;
let mut pattern_h = (f.glyph_data)(low_word + 1);
pattern_h >>= 32 - px_in_high_word;
pattern |= pattern_h;
}
// XOR glyph pixels onto destination buffer
let base = (y0 + y) * LCD_WORDS_PER_LINE;
fb[base + dest_low_word] ^= pattern << (32 - px_in_dest_low_word);
if px_in_dest_low_word < gh.w {
fb[base + dest_high_word] ^= pattern >> px_in_dest_low_word;
}
}
(x0 + gh.w + 2) - cr.x0
}
/// Calculate the width of glpyh for a char
pub fn char_width(c: char, f: Font) -> usize {
let gpo = (f.glyph_pattern_offset)(c);
let gh = GlyphHeader::new((f.glyph_data)(gpo));
gh.w
}
/// Clear a screen region bounded by (cr.x0,cr.y0)..(cr.x0,cr.y1)
pub fn clear_region(fb: &mut LcdFB, cr: ClipRegion) {
if cr.y1 > LCD_LINES || cr.y0 >= cr.y1 || cr.x1 > LCD_PX_PER_LINE || cr.x0 >= cr.x1 {
return;
}
// Calculate word alignment for destination buffer
let dest_low_word = cr.x0 >> 5;
let dest_high_word = cr.x1 >> 5;
let px_in_dest_low_word = 32 - (cr.x0 & 0x1f);
let px_in_dest_high_word = cr.x1 & 0x1f;
// Blit it
for y in cr.y0..cr.y1 {
let base = y * LCD_WORDS_PER_LINE;
fb[base + dest_low_word] |= 0xffffffff << (32 - px_in_dest_low_word);
for w in dest_low_word + 1..dest_high_word {
fb[base + w] = 0xffffffff;
}
if dest_low_word < dest_high_word {
fb[base + dest_high_word] |= 0xffffffff >> (32 - px_in_dest_high_word);
}
}
}
/// Invert a screen region bounded by (cr.x0,cr.y0)..(cr.x0,cr.y1)
pub fn invert_region(fb: &mut LcdFB, cr: ClipRegion) {
if cr.y1 > LCD_LINES || cr.y0 >= cr.y1 || cr.x1 > LCD_PX_PER_LINE || cr.x0 >= cr.x1 {
return;
}
// Calculate word alignment for destination buffer
let dest_low_word = cr.x0 >> 5;
let dest_high_word = cr.x1 >> 5;
let px_in_dest_low_word = 32 - (cr.x0 & 0x1f);
let px_in_dest_high_word = cr.x1 & 0x1f;
// Blit it
for y in cr.y0..cr.y1 {
let base = y * LCD_WORDS_PER_LINE;
fb[base + dest_low_word] ^= 0xffffffff << (32 - px_in_dest_low_word);
for w in dest_low_word + 1..dest_high_word {
fb[base + w] ^= 0xffffffff;
}
if dest_low_word < dest_high_word {
fb[base + dest_high_word] ^= 0xffffffff >> (32 - px_in_dest_high_word);
}
}
}
/// Outline a full width screen region with pad and border box
pub fn outline_region(fb: &mut LcdFB, yr: YRegion) {
if yr.1 > LCD_LINES || yr.0 + 6 >= yr.1 {
return;
}
line_fill_clear(fb, yr.0);
line_fill_clear(fb, yr.0 + 1);
line_fill_padded_solid(fb, yr.0 + 2);
for y in yr.0 + 3..yr.1 - 3 {
line_fill_padded_border(fb, y);
}
line_fill_padded_solid(fb, yr.1 - 3);
line_fill_clear(fb, yr.1 - 2);
line_fill_clear(fb, yr.1 - 1);
}
/// Clear a line of the screen
pub fn line_fill_clear(fb: &mut LcdFB, y: usize) {
if y >= LCD_LINES {
return;
}
let base = y * LCD_WORDS_PER_LINE;
for i in 0..=9 {
fb[base + i] = 0xffff_ffff;
}
fb[base + 10] = 0x0000_ffff;
}
/// Fill a line of the screen with full-width pattern
pub fn line_fill_pattern(fb: &mut LcdFB, y: usize, pattern: &BlitRow) {
if y >= LCD_LINES {
return;
}
let base = y * LCD_WORDS_PER_LINE;
for (i, v) in pattern.iter().enumerate() {
fb[base + i] = *v;
}
}
/// Fill a line of the screen with black, padded with clear to left and right
fn line_fill_padded_solid(fb: &mut LcdFB, y: usize) {
if y >= LCD_LINES {
return;
}
let base = y * LCD_WORDS_PER_LINE;
fb[base] = 0x0000_0003;
for i in 1..=9 {
fb[base + i] = 0x0000_0000;
}
fb[base + 10] = 0x0000_c000;
}
/// Fill a line of the screen with clear, bordered by black, padded with clear
fn line_fill_padded_border(fb: &mut LcdFB, y: usize) {
if y >= LCD_LINES {
return;
}
let base = y * LCD_WORDS_PER_LINE;
fb[base] = 0xffff_fffb;
for i in 1..=9 {
fb[base + i] = 0xffff_ffff;
}
fb[base + 10] = 0x0000_dfff;
}
fn put_pixel(fb: &mut LcdFB, x: usize, y: usize, color: PixelColor) {
if color == PixelColor::Off {
fb[(x + y * LCD_WORDS_PER_LINE * 32) / 32] |= 1 << (x % 32)
} else {
fb[(x + y * LCD_WORDS_PER_LINE * 32) / 32] &= !(1 << (x % 32))
}
}
// plotLine(int x0, int y0, int x1, int y1)
// dx = abs(x1-x0);
// sx = x0<x1 ? 1 : -1;
// dy = -abs(y1-y0);
// sy = y0<y1 ? 1 : -1;
// err = dx+dy; /* error value e_xy */
// while (true) /* loop */
// plot(x0, y0);
// if (x0 == x1 && y0 == y1) break;
// e2 = 2*err;
// if (e2 >= dy) /* e_xy+e_x > 0 */
// err += dy;
// x0 += sx;
// end if
// if (e2 <= dx) /* e_xy+e_y < 0 */
// err += dx;
// y0 += sy;
// end if
// end while
pub fn line(fb: &mut LcdFB, x0: usize, y0: usize, x1: usize, y1: usize, color: PixelColor) {
let mut x0 = x0 as i32;
let mut y0 = y0 as i32;
let x1 = x1 as i32;
let y1 = y1 as i32;
let dx = (x1 - x0).abs();
let sx = if x0 < x1 { 1 } else { -1 };
let dy = -((y1 - y0).abs());
let sy = if y0 < y1 { 1 } else { -1 };
let mut err = dx + dy; /* error value e_xy */
loop {
/* loop */
if x0 >= 0 && y0 >= 0 && x0 < (WIDTH as _) && y0 < (HEIGHT as _) {
put_pixel(fb, x0 as _, y0 as _, color);
}
if x0 == x1 && y0 == y1 {
break;
}
let e2 = 2 * err;
if e2 >= dy {
/* e_xy+e_x > 0 */
err += dy;
x0 += sx;
}
if e2 <= dx {
/* e_xy+e_y < 0 */
err += dx;
y0 += sy;
}
}
}
#[cfg(test)]
mod tests {
use super::fonts;
#[test]
fn bold_font_at_sign() {
let offset = fonts::bold::get_glyph_pattern_offset('@');
assert_eq!(offset, 197);
assert_eq!(fonts::bold::DATA[offset], 0x00121008);
}
#[test]
fn regular_font_at_sign() {
let offset = fonts::regular::get_glyph_pattern_offset('@');
assert_eq!(offset, 182);
assert_eq!(fonts::regular::DATA[offset], 0x00101008);
}
#[test]
fn small_font_at_sign() {
let offset = fonts::small::get_glyph_pattern_offset('@');
assert_eq!(offset, 143);
assert_eq!(fonts::small::DATA[offset], 0x000e1006);
}
}