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 (#5445)
  • Loading branch information
tronical authored Jun 22, 2024
1 parent e232648 commit 977b827
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 84 deletions.
22 changes: 22 additions & 0 deletions internal/backends/qt/qt_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2313,6 +2313,28 @@ impl i_slint_core::renderer::RendererSealed for QtWindow {
fn set_window_adapter(&self, _window_adapter: &Rc<dyn WindowAdapter>) {
// No-op because QtWindow is also the WindowAdapter
}

fn screenshot(&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 accessible_item(item: Option<ItemRc>) -> Option<ItemRc> {
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().renderer().screenshot()
}
}

pub use crate::SharedString;
Expand Down
7 changes: 7 additions & 0 deletions internal/core/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use alloc::rc::Rc;
use core::pin::Pin;

use crate::api::PlatformError;
use crate::graphics::SharedImageBuffer;
use crate::item_tree::ItemTreeRef;
use crate::items::TextWrap;
use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, ScaleFactor};
Expand Down Expand Up @@ -116,4 +117,10 @@ pub trait RendererSealed {
fn resize(&self, _size: crate::api::PhysicalSize) -> Result<(), PlatformError> {
Ok(())
}

/// Re-implement this function to support Window::grab_window(), i.e. return
/// the contents of the window in an image buffer.
fn screenshot(&self) -> Result<SharedImageBuffer, PlatformError> {
Err("WindowAdapter::grab_window is not implemented by the platform".into())
}
}
30 changes: 29 additions & 1 deletion internal/core/software_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod fixed;
mod fonts;

use self::fonts::GlyphRenderer;
use crate::api::Window;
use crate::api::{PlatformError, Window};
use crate::graphics::rendering_metrics_collector::{RefreshMode, RenderingMetricsCollector};
use crate::graphics::{BorderRadius, PixelFormat, SharedImageBuffer, SharedPixelBuffer};
use crate::item_rendering::{CachedRenderingData, DirtyRegion, RenderBorderRectangle, RenderImage};
Expand Down Expand Up @@ -791,6 +791,34 @@ impl RendererSealed for SoftwareRenderer {
*self.maybe_window_adapter.borrow_mut() = Some(Rc::downgrade(window_adapter));
self.partial_cache.borrow_mut().clear();
}

fn screenshot(&self) -> Result<SharedImageBuffer, PlatformError> {
let Some(window_adapter) =
self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
else {
return Err(
"SoftwareRenderer's screenshot called without a window adapter present".into()
);
};

let window = window_adapter.window();
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());
};

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

self.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
self.render(target_buffer.make_mut_slice(), width as usize);
// ensure that caches are clear for the next call
self.set_repaint_buffer_type(RepaintBufferType::NewBuffer);

Ok(SharedImageBuffer::RGB8(target_buffer))
}
}

fn render_window_frame_by_line(
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 @@ -539,6 +539,23 @@ impl RendererSealed for FemtoVGRenderer {
};
return Ok(());
}

/// Returns an image buffer of what was rendered last by reading the previous front buffer (using glReadPixels).
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,
)))
}
}

impl Drop for FemtoVGRenderer {
Expand Down
Loading

0 comments on commit 977b827

Please sign in to comment.