Skip to content

Commit 2ec8a8b

Browse files
committed
fix(macos): make sure the right left side pixels are not causing an issue on macos
- refactor how transparency is identified into a dedicated function - refactor crop into a dedicated function - write some tests that work with some test data (frames) - refactor feature gate name for all tests that need a real display to `e2e_tests` - remove all unused frames that are not used in tests
1 parent 001fe7a commit 2ec8a8b

29 files changed

+368
-150
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ core-foundation-sys = "0.8.2"
4040
x11rb = "0.7.0"
4141

4242
[features]
43-
test_against_real_display = []
43+
e2e_tests = []

src/common/identify_transparency.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use crate::{Image, Margin, Result};
2+
use image::flat::View;
3+
use image::{Bgra, GenericImageView};
4+
5+
///
6+
/// this helps to identify outer transparent regions
7+
/// since some backends provides transparency either from a compositor effect like drop shadow on ubuntu / GNOME
8+
/// or some strange right side strip on MacOS
9+
pub fn identify_transparency(image: Image) -> Result<Option<Margin>> {
10+
let image: View<_, Bgra<u8>> = image.as_view()?;
11+
let (width, height) = image.dimensions();
12+
let half_width = width / 2;
13+
let half_height = height / 2;
14+
// > 3/4 transparency is good enough to declare the end of transparent regions
15+
let transparency_end: u8 = 0xff - (0xff / 4);
16+
17+
let mut margin = Margin::zero();
18+
// identify top margin
19+
for y in 0..half_height {
20+
let Bgra([_, _, _, a]) = image.get_pixel(half_width, y);
21+
if a > transparency_end {
22+
// the end of the transparent area
23+
margin.top = y as u16;
24+
dbg!(margin.top);
25+
break;
26+
}
27+
}
28+
// identify bottom margin
29+
for y in (half_height..height).rev() {
30+
let Bgra([_, _, _, a]) = image.get_pixel(half_width, y);
31+
if a > transparency_end {
32+
// the end of the transparent area
33+
margin.bottom = (height - y - 1) as u16;
34+
dbg!(margin.bottom);
35+
break;
36+
}
37+
}
38+
// identify left margin
39+
for x in 0..half_width {
40+
let Bgra([_, _, _, a]) = image.get_pixel(x, half_height);
41+
if a > transparency_end {
42+
// the end of the transparent area
43+
margin.left = x as u16;
44+
dbg!(margin.left);
45+
break;
46+
}
47+
}
48+
// identify right margin
49+
for x in (half_width..width).rev() {
50+
let Bgra([_, _, _, a]) = image.get_pixel(x, half_height);
51+
if a > transparency_end {
52+
// the end of the transparent area
53+
margin.right = (width - x - 1) as u16;
54+
dbg!(margin.right);
55+
break;
56+
}
57+
}
58+
59+
Ok(Some(margin))
60+
}
61+
62+
#[cfg(test)]
63+
mod test {
64+
use super::*;
65+
66+
#[test]
67+
fn should_identify_macos_right_side_issue() -> Result<()> {
68+
// given an screen capture with transparency on the right side
69+
let image_org = image::open("tests/frames/frame-macos-right-side-issue.tga")?;
70+
let image = image_org.into_bgra8();
71+
let (width, height) = image.dimensions();
72+
let Bgra([blue, green, red, alpha]) = image.get_pixel(width - 1, height / 2);
73+
assert_eq!(alpha, &0, "the test image was not transparent");
74+
assert_eq!(red, &0, "the test image is not as expected");
75+
assert_eq!(green, &0, "the test image is not as expected");
76+
assert_eq!(blue, &0, "the test image is not as expected");
77+
78+
// when
79+
let image_raw = image.into_flat_samples();
80+
let margin = identify_transparency(image_raw)?;
81+
82+
// then
83+
assert_eq!(margin, Some(Margin::new(0, 14, 0, 0)));
84+
85+
Ok(())
86+
}
87+
}

src/common/image.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use crate::common::Margin;
2+
use crate::{Image, ImageOnHeap, Result};
3+
use image::flat::View;
4+
use image::{imageops, Bgra, GenericImageView, ImageBuffer};
5+
6+
///
7+
/// specialized version of crop for [`ImageOnHeap`] and [`Margin`]
8+
///
9+
#[cfg_attr(not(macos), allow(dead_code))]
10+
pub fn crop(image: Image, margin: &Margin) -> Result<ImageOnHeap> {
11+
let mut img2: View<_, Bgra<u8>> = image.as_view()?;
12+
let (width, height) = (
13+
img2.width() - (margin.left + margin.right) as u32,
14+
img2.height() - (margin.top + margin.bottom) as u32,
15+
);
16+
let image_cropped = imageops::crop(
17+
&mut img2,
18+
margin.left as u32,
19+
margin.top as u32,
20+
width,
21+
height,
22+
);
23+
let mut buf = ImageBuffer::new(image_cropped.width(), image_cropped.height());
24+
25+
for y in 0..height {
26+
for x in 0..width {
27+
buf.put_pixel(x, y, image_cropped.get_pixel(x, y));
28+
}
29+
}
30+
31+
Ok(Box::new(buf.into_flat_samples()))
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
use super::*;
37+
use image::open;
38+
39+
#[test]
40+
fn should_crop() -> Result<()> {
41+
// given
42+
let image_org = open("tests/frames/frame-macos-right-side-issue.tga")?;
43+
let image = image_org.into_bgra8();
44+
let image_raw = ImageOnHeap::new(image.into_flat_samples());
45+
let (width, height) = (image_raw.layout.width, image_raw.layout.height);
46+
47+
// when
48+
let cropped = crop(*image_raw, &Margin::new(1, 1, 1, 1))?;
49+
50+
// then
51+
assert_eq!(cropped.layout.width, width - 2);
52+
assert_eq!(cropped.layout.height, height - 2);
53+
54+
Ok(())
55+
}
56+
}

src/common/mod.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
pub mod identify_transparency;
2+
pub mod image;
3+
14
use crate::{ImageOnHeap, Result, WindowId, WindowList};
25

36
pub trait PlatformApi: Send {
@@ -9,3 +12,70 @@ pub trait PlatformApi: Send {
912
fn capture_window_screenshot(&self, window_id: WindowId) -> Result<ImageOnHeap>;
1013
fn get_active_window(&self) -> Result<WindowId>;
1114
}
15+
16+
#[derive(Debug, PartialEq)]
17+
pub struct Margin {
18+
pub top: u16,
19+
pub right: u16,
20+
pub bottom: u16,
21+
pub left: u16,
22+
}
23+
24+
impl Margin {
25+
pub fn new(top: u16, right: u16, bottom: u16, left: u16) -> Self {
26+
Self {
27+
top,
28+
right,
29+
bottom,
30+
left,
31+
}
32+
}
33+
34+
pub fn new_equal(margin: u16) -> Self {
35+
Self::new(margin, margin, margin, margin)
36+
}
37+
38+
pub fn zero() -> Self {
39+
Self::new_equal(0)
40+
}
41+
42+
pub fn is_zero(&self) -> bool {
43+
self.top == 0
44+
&& self.right == self.left
45+
&& self.left == self.bottom
46+
&& self.bottom == self.top
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
mod test {
52+
use super::*;
53+
54+
#[test]
55+
fn margin_new() {
56+
let m = Margin::new(1, 2, 3, 4);
57+
assert_eq!(m.top, 1);
58+
assert_eq!(m.right, 2);
59+
assert_eq!(m.bottom, 3);
60+
assert_eq!(m.left, 4);
61+
}
62+
63+
#[test]
64+
fn margin_new_equal() {
65+
let m = Margin::new_equal(1);
66+
assert_eq!(m.top, 1);
67+
assert_eq!(m.right, 1);
68+
assert_eq!(m.bottom, 1);
69+
assert_eq!(m.left, 1);
70+
}
71+
72+
#[test]
73+
fn margin_zero() {
74+
let m = Margin::zero();
75+
assert_eq!(m.top, 0);
76+
assert_eq!(m.right, 0);
77+
assert_eq!(m.bottom, 0);
78+
assert_eq!(m.left, 0);
79+
assert!(m.is_zero());
80+
}
81+
}

src/linux/mod.rs

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,70 +8,3 @@ pub fn setup() -> Result<Box<dyn PlatformApi>> {
88
}
99

1010
pub const DEFAULT_SHELL: &str = "/bin/sh";
11-
12-
#[derive(Debug)]
13-
pub struct Margin {
14-
pub top: u16,
15-
pub right: u16,
16-
pub bottom: u16,
17-
pub left: u16,
18-
}
19-
20-
impl Margin {
21-
pub fn new(top: u16, right: u16, bottom: u16, left: u16) -> Self {
22-
Self {
23-
top,
24-
right,
25-
bottom,
26-
left,
27-
}
28-
}
29-
30-
pub fn new_equal(margin: u16) -> Self {
31-
Self::new(margin, margin, margin, margin)
32-
}
33-
34-
pub fn zero() -> Self {
35-
Self::new_equal(0)
36-
}
37-
38-
pub fn is_zero(&self) -> bool {
39-
self.top == 0
40-
&& self.right == self.left
41-
&& self.left == self.bottom
42-
&& self.bottom == self.top
43-
}
44-
}
45-
46-
#[cfg(test)]
47-
mod test {
48-
use super::*;
49-
50-
#[test]
51-
fn margin_new() {
52-
let m = Margin::new(1, 2, 3, 4);
53-
assert_eq!(m.top, 1);
54-
assert_eq!(m.right, 2);
55-
assert_eq!(m.bottom, 3);
56-
assert_eq!(m.left, 4);
57-
}
58-
59-
#[test]
60-
fn margin_new_equal() {
61-
let m = Margin::new_equal(1);
62-
assert_eq!(m.top, 1);
63-
assert_eq!(m.right, 1);
64-
assert_eq!(m.bottom, 1);
65-
assert_eq!(m.left, 1);
66-
}
67-
68-
#[test]
69-
fn margin_zero() {
70-
let m = Margin::zero();
71-
assert_eq!(m.top, 0);
72-
assert_eq!(m.right, 0);
73-
assert_eq!(m.bottom, 0);
74-
assert_eq!(m.left, 0);
75-
assert!(m.is_zero());
76-
}
77-
}

0 commit comments

Comments
 (0)