use ferroui_base::platform::{AlphaFormat, PixelFormat};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
#[allow(dead_code, non_camel_case_types)]
pub(crate) enum AndroidPixelFormat {
WINDOW_FORMAT_RGBA_8888 = 1,
WINDOW_FORMAT_RGBX_8888 = 2,
WINDOW_FORMAT_RGB_565 = 4,
}
pub(crate) fn buffer_layout(format: i32, stride: i32) -> (PixelFormat, AlphaFormat, i32) {
if format == AndroidPixelFormat::WINDOW_FORMAT_RGB_565 as i32 {
(PixelFormat::RGB565, AlphaFormat::Opaque, stride * 2)
} else {
(PixelFormat::RGBA8888, AlphaFormat::Premul, stride * 4)
}
}
#[cfg(target_os = "android")]
pub(crate) use imp::{AndroidFramebuffer, DiscardedFramebuffer};
#[cfg(target_os = "android")]
mod imp {
use super::buffer_layout;
use crate::interop::ndk::{LockedBuffer, NativeWindow};
use ferroui_base::platform::{AlphaFormat, ILockedFramebuffer, PixelFormat};
use ferroui_base::{PixelSize, Vector};
use std::cell::RefCell;
pub(crate) struct AndroidFramebuffer {
buffer: RefCell<Option<LockedBuffer>>,
size: PixelSize,
row_bytes: i32,
dpi: Vector,
format: PixelFormat,
alpha_format: AlphaFormat,
}
impl AndroidFramebuffer {
pub fn new(window: &NativeWindow, scaling: f64) -> Option<AndroidFramebuffer> {
let size = PixelSize::new(window.width(), window.height());
let buffer = window.lock()?;
let (format, alpha_format, row_bytes) = buffer_layout(buffer.format, buffer.stride);
let size = PixelSize::new(size.width.min(buffer.width), size.height.min(buffer.height));
Some(AndroidFramebuffer {
buffer: RefCell::new(Some(buffer)),
size,
row_bytes,
dpi: Vector::new(96.0, 96.0) * scaling,
format,
alpha_format,
})
}
}
impl ILockedFramebuffer for AndroidFramebuffer {
fn address(&self) -> *mut u8 {
self.buffer.borrow().as_ref().map_or(std::ptr::null_mut(), LockedBuffer::bits)
}
fn with_data(&self, access: &mut dyn FnMut(&mut [u8])) {
let buffer = self.buffer.borrow();
let Some(buffer) = buffer.as_ref() else {
access(&mut []);
return;
};
let length = self.row_bytes.max(0) as usize * self.size.height.max(0) as usize;
let data = unsafe { std::slice::from_raw_parts_mut(buffer.bits(), length) };
access(data);
}
fn size(&self) -> PixelSize {
self.size
}
fn row_bytes(&self) -> i32 {
self.row_bytes
}
fn dpi(&self) -> Vector {
self.dpi
}
fn format(&self) -> PixelFormat {
self.format
}
fn alpha_format(&self) -> AlphaFormat {
self.alpha_format
}
fn dispose(&self) {
let buffer = self.buffer.borrow_mut().take();
drop(buffer);
}
}
pub(crate) struct DiscardedFramebuffer {
data: RefCell<Vec<u8>>,
dpi: Vector,
}
impl DiscardedFramebuffer {
pub fn new(scaling: f64) -> Self {
Self { data: RefCell::new(vec![0; 4]), dpi: Vector::new(96.0, 96.0) * scaling }
}
}
impl ILockedFramebuffer for DiscardedFramebuffer {
fn address(&self) -> *mut u8 {
self.data.borrow_mut().as_mut_ptr()
}
fn with_data(&self, access: &mut dyn FnMut(&mut [u8])) {
access(&mut self.data.borrow_mut());
}
fn size(&self) -> PixelSize {
PixelSize::new(1, 1)
}
fn row_bytes(&self) -> i32 {
4
}
fn dpi(&self) -> Vector {
self.dpi
}
fn format(&self) -> PixelFormat {
PixelFormat::RGBA8888
}
fn alpha_format(&self) -> AlphaFormat {
AlphaFormat::Premul
}
fn dispose(&self) {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_565_window_is_opaque_with_two_bytes_a_pixel() {
assert_eq!(
buffer_layout(AndroidPixelFormat::WINDOW_FORMAT_RGB_565 as i32, 1088),
(PixelFormat::RGB565, AlphaFormat::Opaque, 2176)
);
}
#[test]
fn every_other_window_is_premultiplied_rgba() {
assert_eq!(
buffer_layout(AndroidPixelFormat::WINDOW_FORMAT_RGBA_8888 as i32, 1088),
(PixelFormat::RGBA8888, AlphaFormat::Premul, 4352)
);
assert_eq!(
buffer_layout(AndroidPixelFormat::WINDOW_FORMAT_RGBX_8888 as i32, 720),
(PixelFormat::RGBA8888, AlphaFormat::Premul, 2880)
);
}
}