Skip to content

Commit

Permalink
Add Rust API to grab the contents of a slint::Window into a slint::Sh…
Browse files Browse the repository at this point in the history
…aredImageBuffer
  • Loading branch information
tronical committed Jun 20, 2024
1 parent 11e7b12 commit 40c4b01
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 97 deletions.
6 changes: 6 additions & 0 deletions internal/backends/linuxkms/fullscreenwindowadapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::pin::Pin;
use std::rc::Rc;

use i_slint_core::api::{LogicalPosition, PhysicalSize as PhysicalWindowSize};
use i_slint_core::graphics::SharedImageBuffer;
use i_slint_core::graphics::{euclid, Image};
use i_slint_core::item_rendering::ItemRenderer;
use i_slint_core::lengths::LogicalRect;
Expand All @@ -32,6 +33,7 @@ pub trait FullscreenRenderer {
&self,
event_loop_handle: crate::calloop_backend::EventLoopHandle,
) -> Result<(), PlatformError>;
fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError>;
}

pub struct FullscreenWindowAdapter {
Expand Down Expand Up @@ -69,6 +71,10 @@ impl WindowAdapter for FullscreenWindowAdapter {
}
Ok(())
}

fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
self.renderer.grab_window()
}
}

impl FullscreenWindowAdapter {
Expand Down
5 changes: 5 additions & 0 deletions internal/backends/linuxkms/renderer/femtovg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use std::{num::NonZeroU32, rc::Rc};

use i_slint_core::graphics::SharedImageBuffer;
use i_slint_core::item_rendering::ItemRenderer;
use i_slint_core::platform::PlatformError;
use i_slint_renderer_femtovg::FemtoVGRendererExt;
Expand Down Expand Up @@ -201,4 +202,8 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for FemtoVGRendererAdapt
) -> Result<(), PlatformError> {
self.gbm_display.clone().register_page_flip_handler(event_loop_handle)
}

fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
self.renderer.screenshot()
}
}
5 changes: 5 additions & 0 deletions internal/backends/linuxkms/renderer/skia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::rc::Rc;
use crate::display::RenderingRotation;
use crate::drmoutput::DrmOutput;
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
use i_slint_core::graphics::SharedImageBuffer;
use i_slint_core::item_rendering::ItemRenderer;
use i_slint_core::platform::PlatformError;
use i_slint_renderer_skia::skia_safe;
Expand Down Expand Up @@ -162,6 +163,10 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SkiaRendererAdapter
) -> Result<(), PlatformError> {
self.presenter.clone().register_page_flip_handler(event_loop_handle)
}

fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
self.renderer.screenshot()
}
}
struct DrmDumbBufferAccess {
display: Rc<crate::display::swdisplay::SoftwareBufferDisplay>,
Expand Down
36 changes: 31 additions & 5 deletions internal/backends/linuxkms/renderer/sw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! Delegate the rendering to the [`i_slint_core::software_renderer::SoftwareRenderer`]

use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
use i_slint_core::graphics::SharedImageBuffer;
use i_slint_core::platform::PlatformError;
pub use i_slint_core::software_renderer::SoftwareRenderer;
use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel};
Expand All @@ -16,6 +17,7 @@ pub struct SoftwareRendererAdapter {
renderer: SoftwareRenderer,
display: Rc<crate::display::swdisplay::SoftwareBufferDisplay>,
size: PhysicalWindowSize,
force_next_frame_new_buffer: Cell<bool>,
}

#[repr(transparent)]
Expand Down Expand Up @@ -73,7 +75,12 @@ impl SoftwareRendererAdapter {
let (width, height) = display.drm_output.size();
let size = i_slint_core::api::PhysicalSize::new(width, height);

let renderer = Box::new(Self { renderer: SoftwareRenderer::new(), display, size });
let renderer = Box::new(Self {
renderer: SoftwareRenderer::new(),
display,
size,
force_next_frame_new_buffer: Default::default(),
});

eprintln!("Using Software renderer");

Expand All @@ -97,10 +104,14 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap
ready_for_next_animation_frame: Box<dyn FnOnce()>,
) -> Result<(), PlatformError> {
self.display.map_back_buffer(&mut |mut pixels, age| {
self.renderer.set_repaint_buffer_type(match age {
1 => RepaintBufferType::ReusedBuffer,
2 => RepaintBufferType::SwappedBuffers,
_ => RepaintBufferType::NewBuffer,
self.renderer.set_repaint_buffer_type(if self.force_next_frame_new_buffer.take() {
RepaintBufferType::NewBuffer
} else {
match age {
1 => RepaintBufferType::ReusedBuffer,
2 => RepaintBufferType::SwappedBuffers,
_ => RepaintBufferType::NewBuffer,
}
});

self.renderer.set_rendering_rotation(match rotation {
Expand Down Expand Up @@ -137,4 +148,19 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap
) -> Result<(), PlatformError> {
self.display.drm_output.register_page_flip_handler(event_loop_handle)
}

fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
let width = self.size.width;
let height = self.size.height;

let mut target_buffer =
SharedPixelBuffer::<i_slint_core::graphics::Rgb8Pixel>::new(width, height);

self.force_next_frame_new_buffer.set(true);
self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer);

let _ = self.renderer.render(target_buffer.make_mut_slice(), width as usize);

Ok(SharedImageBuffer::RGB8(target_buffer))
}
}
22 changes: 22 additions & 0 deletions internal/backends/qt/qt_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1979,6 +1979,28 @@ impl WindowAdapter for QtWindow {
fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> {
Some(self)
}

fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
let widget_ptr = self.widget_ptr();

let size = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
return widget_ptr->size();
}};

let rgb8_data = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QByteArray as "QByteArray" {
QPixmap pixmap = widget_ptr->grab();
QImage image = pixmap.toImage();
image.convertTo(QImage::Format_RGB888);
return QByteArray(reinterpret_cast<const char *>(image.constBits()), image.sizeInBytes());
}};

let buffer = i_slint_core::graphics::SharedPixelBuffer::<i_slint_core::graphics::Rgb8Pixel>::clone_from_slice(
rgb8_data.to_slice(),
size.width,
size.height,
);
Ok(i_slint_core::graphics::SharedImageBuffer::RGB8(buffer))
}
}

fn into_qsize(logical_size: i_slint_core::api::LogicalSize) -> qttypes::QSize {
Expand Down
7 changes: 6 additions & 1 deletion internal/backends/winit/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct SlintUserEvent(CustomEvent);
mod renderer {
use std::rc::Rc;

use i_slint_core::platform::PlatformError;
use i_slint_core::{graphics::SharedImageBuffer, platform::PlatformError};

pub trait WinitCompatibleRenderer {
fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>;
Expand All @@ -46,6 +46,11 @@ mod renderer {
fn resumed(&self, _winit_window: Rc<winit::window::Window>) -> Result<(), PlatformError> {
Ok(())
}

fn grab_window(
&self,
window: &i_slint_core::api::Window,
) -> Result<SharedImageBuffer, PlatformError>;
}

#[cfg(feature = "renderer-femtovg")]
Expand Down
7 changes: 7 additions & 0 deletions internal/backends/winit/renderer/femtovg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,11 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer {
fn as_core_renderer(&self) -> &dyn Renderer {
&self.renderer
}

fn grab_window(
&self,
_window: &i_slint_core::api::Window,
) -> Result<i_slint_core::graphics::SharedImageBuffer, PlatformError> {
self.renderer.screenshot()
}
}
7 changes: 7 additions & 0 deletions internal/backends/winit/renderer/skia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,11 @@ impl super::WinitCompatibleRenderer for WinitSkiaRenderer {
winit_window.scale_factor() as f32,
)
}

fn grab_window(
&self,
_window: &i_slint_core::api::Window,
) -> Result<i_slint_core::graphics::SharedImageBuffer, PlatformError> {
self.renderer.screenshot()
}
}
47 changes: 40 additions & 7 deletions internal/backends/winit/renderer/sw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

use core::num::NonZeroU32;
use core::ops::DerefMut;
use i_slint_core::graphics::Rgb8Pixel;
use i_slint_core::graphics::{Rgb8Pixel, SharedImageBuffer, SharedPixelBuffer};
use i_slint_core::platform::PlatformError;
pub use i_slint_core::software_renderer::SoftwareRenderer;
use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel};
use std::{cell::RefCell, rc::Rc};
use std::cell::Cell;
use std::cell::RefCell;
use std::rc::Rc;

use super::WinitCompatibleRenderer;

Expand All @@ -18,6 +20,7 @@ pub struct WinitSoftwareRenderer {
_context: softbuffer::Context<Rc<winit::window::Window>>,
surface: RefCell<softbuffer::Surface<Rc<winit::window::Window>, Rc<winit::window::Window>>>,
winit_window: Rc<winit::window::Window>,
force_next_frame_new_buffer: Cell<bool>,
}

#[repr(transparent)]
Expand Down Expand Up @@ -90,6 +93,7 @@ impl WinitSoftwareRenderer {
_context: context,
surface: RefCell::new(surface),
winit_window: winit_window.clone(),
force_next_frame_new_buffer: Default::default(),
}),
winit_window,
))
Expand All @@ -116,11 +120,14 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
.buffer_mut()
.map_err(|e| format!("Error retrieving softbuffer rendering buffer: {e}"))?;

let age = target_buffer.age();
self.renderer.set_repaint_buffer_type(match age {
1 => RepaintBufferType::ReusedBuffer,
2 => RepaintBufferType::SwappedBuffers,
_ => RepaintBufferType::NewBuffer,
self.renderer.set_repaint_buffer_type(if self.force_next_frame_new_buffer.take() {
RepaintBufferType::NewBuffer
} else {
match target_buffer.age() {
1 => RepaintBufferType::ReusedBuffer,
2 => RepaintBufferType::SwappedBuffers,
_ => RepaintBufferType::NewBuffer,
}
});

let region = if std::env::var_os("SLINT_LINE_BY_LINE").is_none() {
Expand Down Expand Up @@ -172,6 +179,7 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
}])
.map_err(|e| format!("Error presenting softbuffer buffer: {e}"))?;
}

Ok(())
}

Expand All @@ -184,4 +192,29 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
// and the buffer age doesn't respect that, so clean the partial rendering cache
self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
}

fn grab_window(
&self,
window: &i_slint_core::api::Window,
) -> Result<SharedImageBuffer, PlatformError> {
let size = window.size();

let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok())
else {
// Nothing to render
return Err("grab_window() called on window with invalid size".into());
};

// We could use Surface::fetch() here, but it's only implemented on X11 and Web.

let mut target_buffer =
SharedPixelBuffer::<i_slint_core::graphics::Rgb8Pixel>::new(width, height);

self.force_next_frame_new_buffer.set(true);
self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer);

let _ = self.renderer.render(target_buffer.make_mut_slice(), width as usize);

Ok(SharedImageBuffer::RGB8(target_buffer))
}
}
4 changes: 4 additions & 0 deletions internal/backends/winit/winitwindowadapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,10 @@ impl WindowAdapter for WinitWindowAdapter {
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
raw_window_handle::HasDisplayHandle::display_handle(&self.winit_window)
}

fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
self.renderer().grab_window(self.window())
}
}

impl WindowAdapterInternal for WinitWindowAdapter {
Expand Down
9 changes: 9 additions & 0 deletions internal/core/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This module contains types that are public and re-exported in the slint-rs as we

#[cfg(target_has_atomic = "ptr")]
pub use crate::future::*;
use crate::graphics::SharedImageBuffer;
use crate::input::{KeyEventType, MouseEvent};
use crate::item_tree::ItemTreeVTable;
use crate::window::{WindowAdapter, WindowInner};
Expand Down Expand Up @@ -622,6 +623,14 @@ impl Window {
pub fn window_handle(&self) -> WindowHandle {
WindowHandle { adapter: self.0.window_adapter() }
}

/// Returns an image buffer with the rendered contents.
///
/// Note that this function may be slow to call. Reading from the framebuffer previously
/// rendered, too, may take a long time.
pub fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
self.0.window_adapter().grab_window()
}
}

pub use crate::SharedString;
Expand Down
8 changes: 7 additions & 1 deletion internal/core/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::api::{
CloseRequestResponse, LogicalPosition, PhysicalPosition, PhysicalSize, PlatformError, Window,
WindowPosition, WindowSize,
};
use crate::graphics::Point;
use crate::graphics::{Point, SharedImageBuffer};
use crate::input::{
key_codes, ClickState, InternalKeyboardModifierState, KeyEvent, KeyEventType, MouseEvent,
MouseInputState, TextCursorBlinker,
Expand Down Expand Up @@ -153,6 +153,12 @@ pub trait WindowAdapter {
) -> Result<raw_window_handle_06::DisplayHandle<'_>, raw_window_handle_06::HandleError> {
Err(raw_window_handle_06::HandleError::NotSupported)
}

/// Re-implement this function to support [`Window::grab_window()`], i.e. return
/// the contents of the window in an image buffer.
fn grab_window(&self) -> Result<SharedImageBuffer, PlatformError> {
Err("WindowAdapter::grab_window is not implemented by the platform".into())
}
}

/// Implementation details behind [`WindowAdapter`], but since this
Expand Down
21 changes: 19 additions & 2 deletions internal/renderers/femtovg/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ use std::rc::{Rc, Weak};

use i_slint_common::sharedfontdb;
use i_slint_core::api::{RenderingNotifier, RenderingState, SetRenderingNotifierError};
use i_slint_core::graphics::BorderRadius;
use i_slint_core::graphics::FontRequest;
use i_slint_core::graphics::{euclid, rendering_metrics_collector::RenderingMetricsCollector};
use i_slint_core::graphics::{BorderRadius, SharedImageBuffer};
use i_slint_core::graphics::{FontRequest, SharedPixelBuffer};
use i_slint_core::item_rendering::ItemRenderer;
use i_slint_core::items::TextWrap;
use i_slint_core::lengths::{
Expand Down Expand Up @@ -172,6 +172,23 @@ impl FemtoVGRenderer {
})
}

/// Returns an image buffer of what was rendered last by reading the previous front buffer (using glReadPixels).
pub fn screenshot(&self) -> Result<SharedImageBuffer, PlatformError> {
self.opengl_context.ensure_current()?;
let screenshot = self
.canvas
.borrow_mut()
.screenshot()
.map_err(|e| format!("FemtoVG error reading current back buffer: {e}"))?;

use rgb::ComponentBytes;
Ok(SharedImageBuffer::RGBA8(SharedPixelBuffer::clone_from_slice(
screenshot.buf().as_bytes(),
screenshot.width() as u32,
screenshot.height() as u32,
)))
}

/// Render the scene using OpenGL.
pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
self.internal_render_with_post_callback(
Expand Down
Loading

0 comments on commit 40c4b01

Please sign in to comment.