ferroui_base/rendering/composition/
alpha_mask.rs1use super::{CompositionBrush, CompositionStretch, CompositionSurfaceBrush, Compositor, ICompositionSurface, LoadedImageSurface};
7use crate::media::imaging::{Bitmap, BitmapBlendingMode, RenderTargetBitmap};
8use crate::media::RenderOptions;
9use crate::platform::{AlphaFormat, PixelFormats};
10use crate::reactive::IDisposable;
11use crate::{LocatorExtensions, PixelSize, Rect, Vector, Visual};
12use std::cell::{Cell, RefCell};
13use std::rc::Rc;
14
15pub const ALPHA_MASK_MAX_SIZE: u32 = 4096;
22
23const BLACK_SIDE: i32 = 4;
25
26#[derive(Default)]
45pub struct AlphaMask {
46 brush: RefCell<Option<CompositionSurfaceBrush>>,
47 connected: Rc<Cell<bool>>,
49 updating: Cell<bool>,
52 subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
55}
56
57impl AlphaMask {
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn get(&self, element: &Visual) -> Option<Rc<CompositionBrush>> {
67 if let Some(brush) = self.brush.borrow().as_ref() {
68 return Some((**brush).clone());
69 }
70 let compositor = element
71 .composition_visual()
72 .map(|visual| visual.compositor().clone())
73 .or_else(Compositor::try_get_default_compositor)?;
74 let brush = compositor.create_surface_brush();
75 brush.set_stretch(CompositionStretch::Fill);
76 let result: Rc<CompositionBrush> = (*brush).clone();
77 *self.brush.borrow_mut() = Some(brush);
78 self.subscribe(element);
79 self.update_if_available(element);
80 Some(result)
81 }
82
83 pub fn is_present(&self) -> bool {
85 self.brush.borrow().is_some()
86 }
87
88 pub fn is_connected(&self) -> bool {
90 self.connected.get()
91 }
92
93 pub fn update_if_available(&self, element: &Visual) {
99 if self.updating.get() {
100 return;
101 }
102 let Some(brush) = self.brush.borrow().clone() else { return };
103
104 let bounds = element.bounds();
105 let (scale_x, scale_y) = realization_scale(element);
106 let width = (scale_x * bounds.width).floor();
107 let height = (scale_y * bounds.height).floor();
108 let drawable = width >= 1.0 && height >= 1.0
109 && element.is_attached_to_visual_tree()
110 && element.is_effectively_visible()
111 && crate::FerroLocator::current().get_service::<dyn crate::platform::IPlatformRenderInterface>().is_some();
112 if !drawable {
113 self.hide();
114 return;
115 }
116
117 let largest = width.max(height);
120 let adjusted = if largest > ALPHA_MASK_MAX_SIZE as f64 { ALPHA_MASK_MAX_SIZE as f64 / largest } else { 1.0 };
121 let pixel_size = PixelSize::new(
122 ((width * adjusted) as i32).clamp(1, ALPHA_MASK_MAX_SIZE as i32),
123 ((height * adjusted) as i32).clamp(1, ALPHA_MASK_MAX_SIZE as i32),
124 );
125 let dpi = Vector::new(
127 96.0 * pixel_size.width as f64 / bounds.width,
128 96.0 * pixel_size.height as f64 / bounds.height,
129 );
130
131 self.updating.set(true);
132 struct Reset<'a>(&'a Cell<bool>);
133 impl Drop for Reset<'_> {
134 fn drop(&mut self) {
135 self.0.set(false);
136 }
137 }
138 let _reset = Reset(&self.updating);
139
140 let image = RenderTargetBitmap::with_dpi(pixel_size, dpi);
141 {
142 let mut context = image.create_drawing_context();
143 element.render(&mut context);
144 let black = black_bitmap();
147 let options = context.push_render_options(RenderOptions {
148 bitmap_blending_mode: BitmapBlendingMode::SourceIn,
149 ..RenderOptions::default()
150 });
151 let side = BLACK_SIDE as f64;
152 context.draw_image_with_rects(
153 &black,
154 Rect::new(1.0, 1.0, side - 2.0, side - 2.0),
155 Rect::new(0.0, 0.0, bounds.width, bounds.height).inflate(1.0),
156 );
157 context.pop(options);
158 context.dispose();
159 black.dispose();
160 }
161
162 let compositor = brush.compositor().clone();
163 let surface: Rc<dyn ICompositionSurface> = LoadedImageSurface::from_bitmap(&compositor, &image);
164 image.dispose();
165 brush.set_surface(Some(surface));
166 let inverse = 1.0 / adjusted;
167 brush.set_scale(Vector::new(inverse, inverse));
168 self.connected.set(true);
169 }
170
171 pub fn hide(&self) {
174 if !self.connected.get() {
175 return;
176 }
177 if let Some(brush) = self.brush.borrow().as_ref() {
178 brush.set_surface(None);
179 }
180 self.connected.set(false);
181 }
182
183 fn subscribe(&self, element: &Visual) {
186 let mut subscriptions = self.subscriptions.borrow_mut();
187 if !subscriptions.is_empty() {
188 return;
189 }
190 let weak = element.to_ref().downgrade();
191 let brush = self.brush.borrow().clone();
192 let connected = self.connected.clone();
193 let hide_brush = move || {
194 if !connected.replace(false) {
195 return;
196 }
197 if let Some(brush) = &brush {
198 brush.set_surface(None);
199 }
200 };
201 let hide = Rc::new(hide_brush);
202 let on_detached = {
203 let hide = hide.clone();
204 element.detached_from_visual_tree(move |_| hide())
205 };
206 let on_visibility = {
207 let hide = hide.clone();
208 element.is_effectively_visible_changed(move || {
209 if weak.upgrade().is_some_and(|element| !element.is_effectively_visible()) {
210 hide();
211 }
212 })
213 };
214 subscriptions.push(on_detached);
215 subscriptions.push(on_visibility);
216 }
217}
218
219impl Drop for AlphaMask {
220 fn drop(&mut self) {
221 self.hide();
224 for subscription in self.subscriptions.get_mut().drain(..) {
225 subscription.dispose();
226 }
227 }
228}
229
230fn realization_scale(element: &Visual) -> (f64, f64) {
234 let scaling = element.presentation_source().map_or(1.0, |source| source.render_scaling());
235 let transform = element
236 .get_visual_root()
237 .and_then(|root| element.transform_to_visual(&root))
238 .unwrap_or(crate::Matrix::IDENTITY);
239 let scale_x = (transform.m11 * transform.m11 + transform.m12 * transform.m12).sqrt();
240 let scale_y = (transform.m21 * transform.m21 + transform.m22 * transform.m22).sqrt();
241 let positive = |scale: f64| if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
242 (positive(scale_x) * scaling, positive(scale_y) * scaling)
243}
244
245fn black_bitmap() -> Bitmap {
247 let pixels = [0u8, 0, 0, 255].repeat((BLACK_SIDE * BLACK_SIDE) as usize);
248 Bitmap::from_pixels(
249 PixelFormats::RGBA8888,
250 AlphaFormat::Premul,
251 &pixels,
252 PixelSize::new(BLACK_SIDE, BLACK_SIDE),
253 Vector::new(96.0, 96.0),
254 BLACK_SIDE * 4,
255 )
256}