use crate::framebuffer::Framebuffer;
#[cfg(all(feature = "canvas-2d", target_arch = "wasm32"))]
pub fn upload_framebuffer(fb: &Framebuffer, canvas_id: &str) -> Result<(), String> {
let rgba_buf = framebuffer_to_rgba8(fb);
upload_rgba(&rgba_buf, fb.width(), fb.height(), canvas_id)
}
#[cfg(all(feature = "canvas-2d", target_arch = "wasm32"))]
pub fn upload_rgba(data: &[u8], width: u32, height: u32, canvas_id: &str) -> Result<(), String> {
use wasm_bindgen::JsCast;
let window =
web_sys::window().ok_or_else(|| "canvas_upload: no global window object".to_owned())?;
let document = window
.document()
.ok_or_else(|| "canvas_upload: window.document is None".to_owned())?;
let element = document
.get_element_by_id(canvas_id)
.ok_or_else(|| format!("canvas_upload: element '{canvas_id}' not found"))?;
let canvas: web_sys::HtmlCanvasElement = element
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| format!("canvas_upload: '{canvas_id}' is not an <canvas>"))?;
canvas.set_width(width);
canvas.set_height(height);
let ctx = canvas
.get_context("2d")
.map_err(|e| format!("canvas_upload: get_context('2d') failed: {e:?}"))?
.ok_or_else(|| format!("canvas_upload: '2d' context not available on '{canvas_id}'"))?
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.map_err(|_| "canvas_upload: context is not CanvasRenderingContext2d".to_owned())?;
let clamped_arr = js_sys::Uint8ClampedArray::from(data);
let image_data =
web_sys::ImageData::new_with_u8_clamped_array_and_sh(&clamped_arr, width, height)
.map_err(|e| format!("canvas_upload: ImageData construction failed: {e:?}"))?;
ctx.put_image_data(&image_data, 0.0, 0.0)
.map_err(|e| format!("canvas_upload: putImageData failed: {e:?}"))?;
Ok(())
}
#[cfg(not(all(feature = "canvas-2d", target_arch = "wasm32")))]
#[allow(unused_variables)]
pub fn upload_framebuffer(fb: &Framebuffer, canvas_id: &str) -> Result<(), String> {
Ok(())
}
#[cfg(not(all(feature = "canvas-2d", target_arch = "wasm32")))]
#[allow(unused_variables)]
pub fn upload_rgba(data: &[u8], width: u32, height: u32, canvas_id: &str) -> Result<(), String> {
Ok(())
}
pub fn framebuffer_to_rgba8(fb: &Framebuffer) -> Vec<u8> {
use crate::framebuffer::unpack;
let mut out = Vec::with_capacity(fb.width() as usize * fb.height() as usize * 4);
for &px in fb.pixels() {
let (r, g, b, a) = unpack(px);
out.extend_from_slice(&[r, g, b, a]);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use oxiui_core::Color;
#[test]
fn framebuffer_to_rgba8_correct_layout() {
let fb = Framebuffer::with_fill(2, 1, Color(10, 20, 30, 200));
let raw = framebuffer_to_rgba8(&fb);
assert_eq!(raw.len(), 8);
assert_eq!(&raw[..4], &[10, 20, 30, 200]);
assert_eq!(&raw[4..], &[10, 20, 30, 200]);
}
#[test]
fn framebuffer_to_rgba8_transparent() {
let fb = Framebuffer::new(1, 1); let raw = framebuffer_to_rgba8(&fb);
assert_eq!(raw, vec![0, 0, 0, 0]);
}
#[test]
fn upload_framebuffer_native_noop() {
let fb = Framebuffer::with_fill(4, 4, Color(255, 0, 0, 255));
let result = upload_framebuffer(&fb, "test-canvas");
assert!(result.is_ok(), "native stub must return Ok: {result:?}");
}
#[test]
fn upload_rgba_native_noop() {
let data: Vec<u8> = (0..64)
.map(|i| match i % 4 {
0 | 3 => 255,
_ => 0,
})
.collect();
let result = upload_rgba(&data, 4, 4, "test-canvas");
assert!(result.is_ok(), "native stub must return Ok: {result:?}");
}
}