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

Choose a reason for hiding this comment

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

this comment seems outdated since there isn't a grab_window funtion anymore

Copy link
Member Author

Choose a reason for hiding this comment

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

That’s still the name in the public API.

/// 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);
let _ = self.render(target_buffer.make_mut_slice(), width as usize);
tronical marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading