Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rust API to grab the contents of a slint::Window into a slint::SharedImageBuffer #5445

Merged
merged 11 commits into from
Jun 22, 2024
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};
tronical marked this conversation as resolved.
Show resolved Hide resolved
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
37 changes: 32 additions & 5 deletions internal/backends/linuxkms/renderer/sw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
//! 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, 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::Cell;
use std::rc::Rc;

use crate::display::{Presenter, RenderingRotation};
Expand All @@ -16,6 +18,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 +76,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 +105,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 +149,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};
tronical marked this conversation as resolved.
Show resolved Hide resolved

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need force_next_frame_new_buffer given that you already set the buffer type in the next line? This will clear the caches.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this isn't needed indeed. That said, I think I need to clear caches after this rendering, as the next call - from the windowing system or user - might pass an "age 1" buffer for example and caches don't apply to that one (As it's a different buffer).

self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer);

let _ = self.renderer.render(target_buffer.make_mut_slice(), width as usize);
tronical marked this conversation as resolved.
Show resolved Hide resolved

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
Loading