Skip to main content

xgpui_media/
lib.rs

1//! Opt-in source-sized SDR images. Existing GPUI images keep their atlas path.
2mod filter;
3mod loader;
4pub mod pipeline;
5pub use filter::{FilteredImage, ImageFilter};
6use pipeline::ImageAdjustments;
7mod mipmaps;
8mod texture;
9mod upload;
10mod video;
11#[cfg(target_os = "macos")]
12pub use gpui_apple::custom_draw::core_video::{CoreVideoFrame, Nv12Matrix};
13#[cfg(all(unix, feature = "wgpu"))]
14pub use gpui_wgpu::custom_draw::dma_buf::{DmaBufFrame, DmaBufLayout};
15#[cfg(target_os = "windows")]
16pub use gpui_windows::custom_draw::dxgi::DxgiFrame;
17pub use video::{VideoFrame, VideoSender, VideoStats, VideoStream, VideoView, video, video_frame};
18
19pub use loader::{ImageJob, LoadStatus, MediaLimits, MediaLoader};
20pub use texture::{MediaImage, UploadStatus};
21pub use upload::{FrameUploadLimits, UploadLimits, UploadStats};
22
23use gpui::{
24    App, Bounds, Corners, CustomGeometry, Element, ElementId, GlobalElementId, InspectorElementId,
25    IntoElement, LayoutId, Pixels, Style, StyleRefinement, Styled, Window,
26};
27use std::sync::Arc;
28
29/// Paint an immutable source-sized image with native clipping and rounded corners.
30/// Layout size is explicit. This first API stretches to the supplied rectangle.
31impl MediaImage {
32    pub fn paint(&self, window: &mut Window, bounds: Bounds<Pixels>, radii: Corners<Pixels>) {
33        self.paint_filtered(window, bounds, radii, ImageFilter::new());
34    }
35    pub fn paint_filtered(
36        &self,
37        window: &mut Window,
38        bounds: Bounds<Pixels>,
39        radii: Corners<Pixels>,
40        filter: ImageFilter,
41    ) {
42        self.paint_content(window, bounds, radii, || Arc::new(self.filtered(filter)));
43    }
44    pub fn paint_adjusted(
45        &self,
46        window: &mut Window,
47        bounds: Bounds<Pixels>,
48        radii: Corners<Pixels>,
49        adjustments: ImageAdjustments,
50    ) {
51        self.paint_content(window, bounds, radii, || {
52            Arc::new(self.adjusted(adjustments))
53        });
54    }
55    fn paint_content(
56        &self,
57        window: &mut Window,
58        bounds: Bounds<Pixels>,
59        radii: Corners<Pixels>,
60        content: impl FnOnce() -> Arc<dyn gpui::CustomContent>,
61    ) {
62        if bounds.is_empty()
63            || bounds.intersect(&window.content_mask().bounds).is_empty()
64            || window.element_opacity() <= 0.
65        {
66            return;
67        }
68        if !matches!(
69            self.upload_status(),
70            UploadStatus::Ready | UploadStatus::Failed(_)
71        ) {
72            window.request_animation_frame();
73        }
74        window.paint_custom(
75            CustomGeometry::RoundedRect {
76                bounds,
77                corner_radii: radii,
78            },
79            content(),
80        );
81    }
82}
83
84/// A styled image viewport. Construct once-loaded images with `MediaLoader` and
85/// retain the image in the host view. Uses a direct source texture, not the atlas.
86/// The image stretches to this element's bounds; set an appropriate aspect ratio.
87pub fn image_view(image: MediaImage) -> MediaImageView {
88    MediaImageView {
89        image,
90        filter: ImageFilter::new(),
91        adjustments: None,
92        wide: false,
93        style: StyleRefinement::default(),
94    }
95}
96/// Styled image viewport with native background, opacity, and corner coverage.
97pub struct MediaImageView {
98    image: MediaImage,
99    filter: ImageFilter,
100    adjustments: Option<ImageAdjustments>,
101    wide: bool,
102    style: StyleRefinement,
103}
104impl MediaImageView {
105    /// Opt into native linear-P3 presentation. The renderer also requires an
106    /// accepted WideGamutSdr window; unsupported hosts return an explicit error.
107    pub fn wide_gamut(mut self) -> anyhow::Result<Self> {
108        self.image.wide(None)?;
109        anyhow::ensure!(
110            self.filter == ImageFilter::new(),
111            "wide images require linear ImageAdjustments"
112        );
113        self.wide = true;
114        Ok(self)
115    }
116
117    /// Opt into linear-sRGB adjustments. Last filter/adjustments setter wins;
118    /// encoded and linear transforms are never implicitly combined.
119    pub fn adjustments(mut self, adjustments: ImageAdjustments) -> Self {
120        self.adjustments = Some(adjustments);
121        self
122    }
123
124    pub fn filter(mut self, filter: ImageFilter) -> Self {
125        self.filter = filter;
126        self.wide = false;
127        self.adjustments = None;
128        self
129    }
130}
131impl Styled for MediaImageView {
132    fn style(&mut self) -> &mut StyleRefinement {
133        &mut self.style
134    }
135}
136impl IntoElement for MediaImageView {
137    type Element = Self;
138    fn into_element(self) -> Self {
139        self
140    }
141}
142impl Element for MediaImageView {
143    type RequestLayoutState = Style;
144    type PrepaintState = ();
145    fn id(&self) -> Option<ElementId> {
146        None
147    }
148    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
149        None
150    }
151    fn request_layout(
152        &mut self,
153        _id: Option<&GlobalElementId>,
154        _inspector: Option<&InspectorElementId>,
155        window: &mut Window,
156        cx: &mut App,
157    ) -> (LayoutId, Style) {
158        use refineable::Refineable;
159        let mut style = Style::default();
160        style.refine(&self.style);
161        (window.request_layout(style.clone(), [], cx), style)
162    }
163    fn prepaint(
164        &mut self,
165        _id: Option<&GlobalElementId>,
166        _inspector: Option<&InspectorElementId>,
167        _bounds: Bounds<Pixels>,
168        _style: &mut Style,
169        _window: &mut Window,
170        _cx: &mut App,
171    ) {
172    }
173    fn paint(
174        &mut self,
175        _id: Option<&GlobalElementId>,
176        _inspector: Option<&InspectorElementId>,
177        bounds: Bounds<Pixels>,
178        style: &mut Style,
179        _prepaint: &mut (),
180        window: &mut Window,
181        cx: &mut App,
182    ) {
183        let radii = style
184            .corner_radii
185            .to_pixels(window.rem_size())
186            .clamp_radii_for_quad_size(bounds.size);
187        window.with_element_opacity(style.opacity, |window| {
188            style.paint(bounds, window, cx, |window, _| {
189                if self.wide {
190                    self.image.paint_content(window, bounds, radii, || {
191                        Arc::new(
192                            self.image
193                                .wide(self.adjustments)
194                                .expect("validated wide image"),
195                        )
196                    });
197                } else if let Some(adjustments) = self.adjustments {
198                    self.image
199                        .paint_adjusted(window, bounds, radii, adjustments);
200                } else {
201                    self.image
202                        .paint_filtered(window, bounds, radii, self.filter);
203                }
204            });
205        });
206    }
207}
208
209#[cfg(test)]
210mod tests;
211
212/// Build a complete alpha-aware, linear-light RGBA8 mip chain synchronously.
213/// Intended for already-decoded small fixtures and hosts with their own task
214/// scheduling. Large images should use native `MediaLoader` workers; this call
215/// does not move CPU work off the calling thread or reserve loader budgets.
216pub fn mipmapped_rgba8(
217    width: u32,
218    height: u32,
219    rgba: Vec<u8>,
220) -> anyhow::Result<gpui::ImageTextureData> {
221    mipmaps::build(width, height, rgba, || false)
222}