use core::{convert::Infallible, ffi::CStr, marker::PhantomData};
use embedded_graphics_core::{
Pixel,
draw_target::DrawTarget,
geometry::OriginDimensions,
pixelcolor::{
BinaryColor, PixelColor, Rgb565, Rgb888, RgbColor,
raw::{RawData, RawU8},
},
};
pub use embedded_graphics_core::{
geometry::{Point, Size},
primitives::Rectangle,
};
use crate::{
resource::{
font::Font,
image::{DrawCommand, DrawCommandSequence, GBitMap},
},
sys,
ui::TextAlignment,
};
pub(crate) fn gpoint(p: Point) -> sys::GPoint {
sys::GPoint {
x: clamp_i16(p.x),
y: clamp_i16(p.y),
}
}
pub(crate) fn gsize(s: Size) -> sys::GSize {
sys::GSize {
w: clamp_i16(s.width as i32),
h: clamp_i16(s.height as i32),
}
}
pub(crate) fn grect(r: Rectangle) -> sys::GRect {
sys::GRect {
origin: gpoint(r.top_left),
size: gsize(r.size),
}
}
pub(crate) fn point(p: sys::GPoint) -> Point {
Point::new(p.x.into(), p.y.into())
}
pub(crate) fn size(s: sys::GSize) -> Size {
Size::new(s.w.max(0) as u32, s.h.max(0) as u32)
}
pub(crate) fn rect(r: sys::GRect) -> Rectangle {
Rectangle::new(point(r.origin), size(r.size))
}
fn clamp_i16(v: i32) -> i16 {
v.clamp(i16::MIN.into(), i16::MAX.into()) as i16
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(transparent)]
pub struct Colour {
pub argb: u8,
}
pub type Color = Colour;
impl From<Colour> for sys::GColor {
fn from(colour: Colour) -> Self {
sys::GColor8 { argb: colour.argb }
}
}
impl Colour {
pub const fn argb(a: u8, r: u8, g: u8, b: u8) -> Self {
let a = (a & 0b11) << 6;
let r = (r & 0b11) << 4;
let g = (g & 0b11) << 2;
let b = b & 0b11;
Self {
argb: a | r | g | b,
}
}
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self::argb(0b11, r, g, b)
}
pub const fn from_argb8(argb: u32) -> Self {
const fn round2(c: u32) -> u8 {
((c * 3 + 127) / 255) as u8
}
Self::argb(
round2((argb >> 24) & 0xFF),
round2((argb >> 16) & 0xFF),
round2((argb >> 8) & 0xFF),
round2(argb & 0xFF),
)
}
pub const fn from_rgb8(rgb: u32) -> Self {
Self::from_argb8(0xFF00_0000 | rgb)
}
}
impl PixelColor for Colour {
type Raw = RawU8;
}
impl From<RawU8> for Colour {
fn from(raw: RawU8) -> Self {
Self {
argb: raw.into_inner(),
}
}
}
impl From<Rgb888> for Colour {
fn from(c: Rgb888) -> Self {
Self::from_rgb8(u32::from(c.r()) << 16 | u32::from(c.g()) << 8 | u32::from(c.b()))
}
}
impl From<Rgb565> for Colour {
fn from(c: Rgb565) -> Self {
Rgb888::from(c).into()
}
}
impl From<BinaryColor> for Colour {
fn from(c: BinaryColor) -> Self {
match c {
BinaryColor::Off => Self::rgb(0, 0, 0),
BinaryColor::On => Self::rgb(3, 3, 3),
}
}
}
impl Colour {
const fn alpha(self) -> u8 {
self.argb >> 6
}
const fn channel(self, shift: u8) -> u8 {
(self.argb >> shift) & 0b11
}
fn blend_over(self, dest: Self) -> Self {
let (s_weight, d_weight) = match self.alpha() {
0 => return dest,
1 => (1, 2),
2 => (2, 1),
_ => return Self::argb(3, self.channel(4), self.channel(2), self.channel(0)),
};
let mix = |shift| (s_weight * self.channel(shift) + d_weight * dest.channel(shift) + 1) / 3;
Self::argb(3, mix(4), mix(2), mix(0))
}
const fn to_bw(self) -> Option<bool> {
if self.alpha() == 0 {
return None;
}
let luminance = (2126 * self.channel(4) as u32
+ 7152 * self.channel(2) as u32
+ 722 * self.channel(0) as u32)
/ 3;
Some(luminance >= 5000)
}
}
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum CompOp {
Assign = sys::GCompOp_GCompOpAssign,
AssignInverted = sys::GCompOp_GCompOpAssignInverted,
Or = sys::GCompOp_GCompOpOr,
And = sys::GCompOp_GCompOpAnd,
Clear = sys::GCompOp_GCompOpClear,
Set = sys::GCompOp_GCompOpSet,
}
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum TextOverflow {
WordWrap = sys::GTextOverflowMode_GTextOverflowModeWordWrap,
TrailingEllipsis = sys::GTextOverflowMode_GTextOverflowModeTrailingEllipsis,
Fill = sys::GTextOverflowMode_GTextOverflowModeFill,
}
bitflags::bitflags! {
#[derive(Clone, Copy)]
pub struct Corners: u8 {
const TOP_LEFT = sys::GCornerMask_GCornerTopLeft;
const TOP_RIGHT = sys::GCornerMask_GCornerTopRight;
const BOTTOM_LEFT = sys::GCornerMask_GCornerBottomLeft;
const BOTTOM_RIGHT = sys::GCornerMask_GCornerBottomRight;
}
}
pub struct GContext<'r> {
ptr: *mut sys::GContext,
layer: *mut sys::Layer,
_render: PhantomData<&'r mut sys::GContext>,
}
impl GContext<'_> {
pub(crate) unsafe fn new(ctx: *mut sys::GContext, layer: *mut sys::Layer) -> Self {
Self {
ptr: ctx,
layer,
_render: PhantomData,
}
}
pub fn as_ptr(&mut self) -> *mut sys::GContext {
self.ptr
}
pub fn set_fill_colour(&mut self, colour: Colour) {
unsafe { sys::graphics_context_set_fill_color(self.as_ptr(), colour.into()) };
}
pub fn set_stroke_colour(&mut self, colour: Colour) {
unsafe { sys::graphics_context_set_stroke_color(self.as_ptr(), colour.into()) };
}
pub fn set_text_colour(&mut self, colour: Colour) {
unsafe { sys::graphics_context_set_text_color(self.as_ptr(), colour.into()) };
}
pub fn set_stroke_width(&mut self, width: u8) {
unsafe { sys::graphics_context_set_stroke_width(self.as_ptr(), width) };
}
pub fn set_antialiased(&mut self, enable: bool) {
unsafe { sys::graphics_context_set_antialiased(self.as_ptr(), enable) };
}
pub fn set_compositing_mode(&mut self, mode: CompOp) {
unsafe { sys::graphics_context_set_compositing_mode(self.as_ptr(), mode as u8) };
}
pub fn draw_pixel(&mut self, at: Point) {
unsafe { sys::graphics_draw_pixel(self.as_ptr(), gpoint(at)) };
}
pub fn draw_line(&mut self, from: Point, to: Point) {
unsafe { sys::graphics_draw_line(self.as_ptr(), gpoint(from), gpoint(to)) };
}
pub fn draw_rect(&mut self, rect: Rectangle) {
unsafe { sys::graphics_draw_rect(self.as_ptr(), grect(rect)) };
}
pub fn fill_rect(&mut self, rect: Rectangle) {
self.fill_round_rect(rect, 0, Corners::empty());
}
pub fn fill_round_rect(&mut self, rect: Rectangle, radius: u16, corners: Corners) {
unsafe { sys::graphics_fill_rect(self.as_ptr(), grect(rect), radius, corners.bits()) };
}
pub fn draw_round_rect(&mut self, rect: Rectangle, radius: u16) {
unsafe { sys::graphics_draw_round_rect(self.as_ptr(), grect(rect), radius) };
}
pub fn draw_circle(&mut self, centre: Point, radius: u16) {
unsafe { sys::graphics_draw_circle(self.as_ptr(), gpoint(centre), radius) };
}
pub fn fill_circle(&mut self, centre: Point, radius: u16) {
unsafe { sys::graphics_fill_circle(self.as_ptr(), gpoint(centre), radius) };
}
pub fn draw_bitmap(&mut self, bitmap: &GBitMap, rect: Rectangle) {
unsafe { sys::graphics_draw_bitmap_in_rect(self.as_ptr(), bitmap.as_ptr(), grect(rect)) };
}
pub fn draw_pdc(&mut self, image: &DrawCommand, offset: Point) {
unsafe { sys::gdraw_command_image_draw(self.as_ptr(), image.as_ptr(), gpoint(offset)) };
}
pub fn draw_pdc_sequence(
&mut self,
sequence: &DrawCommandSequence,
elapsed_ms: u32,
offset: Point,
) -> bool {
unsafe {
let frame =
sys::gdraw_command_sequence_get_frame_by_elapsed(sequence.as_ptr(), elapsed_ms);
if frame.is_null() {
return false;
}
sys::gdraw_command_frame_draw(self.as_ptr(), sequence.as_ptr(), frame, gpoint(offset));
}
true
}
pub fn draw_text(
&mut self,
text: &CStr,
font: &Font,
rect: Rectangle,
overflow: TextOverflow,
alignment: TextAlignment,
) {
unsafe {
sys::graphics_draw_text(
self.as_ptr(),
text.as_ptr(),
font.as_ptr(),
grect(rect),
overflow as u8,
alignment as u8,
core::ptr::null_mut(),
);
}
}
pub fn with_framebuffer<R>(&mut self, f: impl FnOnce(&mut Framebuffer<'_>) -> R) -> Option<R> {
let layer = self.layer;
let (origin, bounds, frame) = unsafe {
(
point(sys::layer_convert_point_to_screen(layer, sys::GPoint {
x: 0,
y: 0,
})),
rect(sys::layer_get_bounds(layer)),
rect(sys::layer_get_frame(layer)),
)
};
let ctx = self.as_ptr();
let bitmap = unsafe { sys::graphics_capture_frame_buffer(ctx).as_mut()? };
let (format, data, stride, screen) = unsafe {
(
sys::gbitmap_get_format(bitmap),
sys::gbitmap_get_data(bitmap),
usize::from(sys::gbitmap_get_bytes_per_row(bitmap)),
rect(sys::gbitmap_get_bounds(bitmap)),
)
};
let frame_on_screen = Rectangle::new(origin - bounds.top_left, frame.size);
let mut fb = Framebuffer {
ctx,
bitmap,
format,
data,
stride,
origin,
size: bounds.size,
clip: frame_on_screen.intersection(&screen),
row: None,
_ctx: PhantomData,
};
Some(f(&mut fb))
}
}
pub struct Framebuffer<'f> {
ctx: *mut sys::GContext,
bitmap: *mut sys::GBitmap,
format: sys::GBitmapFormat,
data: *mut u8,
stride: usize,
origin: Point,
size: Size,
clip: Rectangle,
row: Option<(i32, sys::GBitmapDataRowInfo)>,
_ctx: PhantomData<&'f mut GContext<'f>>,
}
impl Framebuffer<'_> {
fn row(&mut self, y: i32) -> (*mut u8, i32, i32) {
if self.format != sys::GBitmapFormat_GBitmapFormat8BitCircular {
let data = unsafe { self.data.add(y as usize * self.stride) };
return (data, 0, i32::MAX);
}
let info = match self.row {
Some((cached, info)) if cached == y => info,
_ => {
let info = unsafe { sys::gbitmap_get_data_row_info(self.bitmap, y as u16) };
self.row = Some((y, info));
info
}
};
(info.data, info.min_x.into(), info.max_x.into())
}
fn span(&mut self, y: i32, x0: i32, x1: i32, colour: Colour) {
let (data, min_x, max_x) = self.row(y);
let (x0, x1) = (x0.max(min_x), x1.min(max_x.saturating_add(1)));
for x in x0..x1 {
unsafe {
if self.format == sys::GBitmapFormat_GBitmapFormat1Bit {
let Some(white) = colour.to_bw() else { return };
let byte = data.add(x as usize / 8);
let bit = 1 << (x % 8);
*byte = if white { *byte | bit } else { *byte & !bit };
} else {
let pixel = data.add(x as usize).cast::<Colour>();
*pixel = colour.blend_over(*pixel);
}
}
}
}
}
impl OriginDimensions for Framebuffer<'_> {
fn size(&self) -> Size {
self.size
}
}
impl DrawTarget for Framebuffer<'_> {
type Color = Colour;
type Error = Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Infallible>
where
I: IntoIterator<Item = Pixel<Colour>>,
{
for Pixel(at, colour) in pixels {
let at = at + self.origin;
if self.clip.contains(at) {
self.span(at.y, at.x, at.x + 1, colour);
}
}
Ok(())
}
fn fill_solid(&mut self, area: &Rectangle, colour: Colour) -> Result<(), Infallible> {
let area = Rectangle::new(area.top_left + self.origin, area.size).intersection(&self.clip);
let Some(bottom_right) = area.bottom_right() else {
return Ok(());
};
for y in area.top_left.y..=bottom_right.y {
self.span(y, area.top_left.x, bottom_right.x + 1, colour);
}
Ok(())
}
}
impl Drop for Framebuffer<'_> {
fn drop(&mut self) {
unsafe { sys::graphics_release_frame_buffer(self.ctx, self.bitmap) };
}
}