Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fn requests(count: usize, pixelate: bool, event: usize) -> Vec<MaskRequest> {
frame: MaskFrame {
revision: 1,
size: SIZE,
scale: [1_f64, 1_f64].map(f64::to_bits),
content: content.map(f64::to_bits),
padding: 0_f64.to_bits(),
crop: None,
Expand Down
15 changes: 11 additions & 4 deletions apps/desktop-gpui/kernels/src/mask_cache.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::screenshot::mask_filter;
use crate::screenshot::mask_filter_at_scale;
use std::{collections::HashSet, hash::Hash};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MaskFrame {
pub revision: u64,
pub size: (u32, u32),
pub scale: [u64; 2],
pub content: [u64; 4],
pub padding: u64,
pub crop: Option<[u32; 4]>,
Expand All @@ -21,13 +22,15 @@ pub struct MaskRequest {

impl MaskRequest {
pub fn filter(&self, rgba: &[u8]) -> Option<image::RgbaImage> {
mask_filter(
let [x, y] = self.frame.scale.map(f64::from_bits);
mask_filter_at_scale(
rgba,
self.frame.size.0,
self.frame.size.1,
self.region,
self.pixelate,
f64::from_bits(self.level),
(x * y).sqrt(),
)
}
}
Expand Down Expand Up @@ -199,6 +202,7 @@ pub fn mask_region(
#[cfg(test)]
mod tests {
use super::*;
use crate::screenshot::mask_filter;
use std::sync::Arc;

fn requests(count: usize) -> Vec<MaskRequest> {
Expand All @@ -208,6 +212,7 @@ mod tests {
frame: MaskFrame {
revision: 1,
size: (64, 48),
scale: [1_f64, 1_f64].map(f64::to_bits),
content: [0., 0., 64., 48.].map(f64::to_bits),
padding: 0_f64.to_bits(),
crop: None,
Expand Down Expand Up @@ -253,14 +258,15 @@ mod tests {
#[test]
fn mask_build_cancels_frame_and_layout_changes() {
let original = requests(2);
for change in 0..5 {
for change in 0..6 {
let mut next = original.clone();
for request in &mut next {
match change {
0 => request.frame.revision += 1,
1 => request.frame.size.0 += 1,
2 => request.frame.content[0] = 0.25_f64.to_bits(),
3 => request.frame.padding = 8_f64.to_bits(),
4 => request.frame.scale[1] = 0.5_f64.to_bits(),
_ => request.frame.crop = Some([0, 0, 32, 24]),
}
}
Expand Down Expand Up @@ -399,14 +405,15 @@ mod tests {
fn frame_and_layout_changes_invalidate_every_mask() {
let original = requests(4);
let previous = cached(&original);
for change in 0..5 {
for change in 0..6 {
let mut requests = original.clone();
for request in &mut requests {
match change {
0 => request.frame.revision += 1,
1 => request.frame.size.0 += 1,
2 => request.frame.content[0] = 0.25_f64.to_bits(),
3 => request.frame.padding = 1_f64.to_bits(),
4 => request.frame.scale[0] = 0.5_f64.to_bits(),
_ => request.frame.crop = Some([1, 0, 64, 48]),
}
}
Expand Down
58 changes: 53 additions & 5 deletions apps/desktop-gpui/kernels/src/screenshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,21 @@ pub fn mask_filter(
region: (u32, u32, u32, u32),
pixelate: bool,
level: f64,
) -> Option<image::RgbaImage> {
mask_filter_at_scale(rgba, frame_width, frame_height, region, pixelate, level, 1.)
}

/// `mask_filter` for a copy of the frame resampled by `scale`: the block or
/// blur size the full frame would use is scaled with it, so a downscaled
/// preview shows the strength the export applies.
pub fn mask_filter_at_scale(
rgba: &[u8],
frame_width: u32,
frame_height: u32,
region: (u32, u32, u32, u32),
pixelate: bool,
level: f64,
scale: f64,
) -> Option<image::RgbaImage> {
if frame_width == 0 || frame_height == 0 {
return None;
Expand All @@ -300,12 +315,21 @@ pub fn mask_filter(
}

let (x0, y0, width, height) = region;
let size = mask_filter_size(pixelate, level, scale);
if pixelate {
let block = (level.round() as u32).max(2);
return pixelate_region(rgba, stride, region, block);
return pixelate_region(rgba, stride, region, size);
}

blur_triangle(rgba, stride, x0, y0, width, height, level)
blur_triangle(rgba, stride, x0, y0, width, height, size)
}

fn mask_filter_size(pixelate: bool, level: f64, scale: f64) -> u32 {
let full = if pixelate {
(level.round() as u32).max(2)
} else {
((level / 4.).round() as u32).max(2)
};
((f64::from(full) * scale).round() as u32).max(1)
}

fn blur_triangle(
Expand All @@ -315,12 +339,11 @@ fn blur_triangle(
y0: u32,
width: u32,
height: u32,
level: f64,
factor: u32,
) -> Option<image::RgbaImage> {
if width == 0 || height == 0 {
return image::RgbaImage::from_raw(width, height, Vec::new());
}
let factor = ((level / 4.).round() as u32).max(2);
let small_w = (width / factor).max(1);
let small_h = (height / factor).max(1);
let small = resize_triangle(rgba, stride, (x0, y0, width, height), (small_w, small_h));
Expand Down Expand Up @@ -624,6 +647,31 @@ fn nearest_sample(output: u32, input: u32, output_size: u32) -> u32 {
mod tests {
use super::*;

#[test]
fn mask_strength_scales_with_a_resampled_frame() {
assert_eq!(mask_filter_size(true, 8., 1.), 8);
assert_eq!(mask_filter_size(true, 8., 0.25), 2);
assert_eq!(mask_filter_size(true, 1., 1.), 2);
assert_eq!(mask_filter_size(true, 3., 0.25), 1);
assert_eq!(mask_filter_size(false, 16., 1.), 4);
assert_eq!(mask_filter_size(false, 8., 1.), 2);
assert_eq!(mask_filter_size(false, 8., 0.5), 1);
assert_eq!(mask_filter_size(false, 16., 0.5), 2);
}

#[test]
fn the_full_frame_filter_is_unchanged_by_scale_one() {
let rgba: Vec<u8> = (0..16 * 12 * 4).map(|i| (i * 37 % 251) as u8).collect();
for pixelate in [false, true] {
for level in [1., 4., 8., 16., 30.] {
assert_eq!(
mask_filter(&rgba, 16, 12, (2, 3, 9, 7), pixelate, level),
mask_filter_at_scale(&rgba, 16, 12, (2, 3, 9, 7), pixelate, level, 1.)
);
}
}
}

fn reference_pixelate(
rgba: &[u8],
frame_width: u32,
Expand Down
Loading
Loading