Skip to content

Fix ugly frame#713

Merged
tsipinakis merged 16 commits into
dunst-project:masterfrom
nick87720z:fix-ugly-frame
May 22, 2020
Merged

Fix ugly frame#713
tsipinakis merged 16 commits into
dunst-project:masterfrom
nick87720z:fix-ugly-frame

Conversation

@nick87720z

@nick87720z nick87720z commented May 3, 2020

Copy link
Copy Markdown
Contributor
  1. Original shaping mask caused antialiasing pixels to be cut, leading to ugly look. Solved by increasing mask corder radus by half-borderwidth, but not less than 1pix (for zero-width border).
  2. And when border width is set to zero, it should not be painted. Current approach caused transparent pixels from AA to sum, producing subtle edge-like artifact at corners.

@tsipinakis tsipinakis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR! These are all code style nitpicks, the code looks good.

Comment thread src/draw.c
y += settings.frame_width;
height -= settings.frame_width;
if (settings.frame_width > 0)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[code style] Braces should be in the same line as the statement

Comment thread src/draw.c Outdated
if (first) {
y += settings.frame_width;
height -= settings.frame_width;
if (settings.frame_width > 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[code style] I'm placing the comment here but it's about the entire PR: The indentation is inconsistent, we're using 8 spaces per indentation level.

Comment thread src/x11/x.c Outdated
* use a circle for every corner and two overlapping rectangles */
int fw = settings.frame_width / 2 + settings.frame_width % 2;
if (fw < 1)
fw=1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[code style] Please use a space before and after the operator fw = 1

@tsipinakis

tsipinakis commented May 3, 2020

Copy link
Copy Markdown
Member

After a closer look I found a bug. There seems to be some artifacts in the corners,

2020-05-03T21-18-39

That's obviously not a bug with your code but it exposed an existing issue, but still I'd prefer it to be fixed before merging.

@tsipinakis

Copy link
Copy Markdown
Member

On closer look this does look like a bug, there are no rounded corners on the window shape, so most likely some of the math is off.

@codecov-io

codecov-io commented May 3, 2020

Copy link
Copy Markdown

Codecov Report

Merging #713 into master will decrease coverage by 0.28%.
The diff coverage is 0.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #713      +/-   ##
==========================================
- Coverage   66.29%   66.00%   -0.29%     
==========================================
  Files          29       29              
  Lines        5076     5098      +22     
==========================================
  Hits         3365     3365              
- Misses       1711     1733      +22     
Flag Coverage Δ
#unittests 66.00% <0.00%> (-0.29%) ⬇️
Impacted Files Coverage Δ
src/draw.c 0.00% <0.00%> (ø)
src/x11/x.c 2.27% <0.00%> (-0.07%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fb2ffd4...0e35c6a. Read the comment docs.

@nick87720z

nick87720z commented May 3, 2020

Copy link
Copy Markdown
Contributor Author

One problem, previously invisible - when some notifications are stacked (even if e.g. startup low priority notification disappeared with new appeared before), it looks as if they all are rendered, with same effect at antialiased edge as with 0px edge before second commit. You might want to comment out shaping call to see, how it's rendered when in stack. No even need to trigger notifications, just recall one many times. I guess, it assumes that notification sequence is in vertical row, so when they are stacked, it looks even more strange).

As for mask painting (if you are about x_win_round_corners()) it is rounded, but somewhat ineffective. Two crossing rectangles, yet circles are full 360deg, while 1/4 would be enough. Though could be more effective in terms of xserver calls, I'm not sure).

Yet can't understand, where 0.5,0.5 displacement is set (originall integer coordinates would point to pixel corner instead of its center).

@nick87720z

nick87720z commented May 4, 2020

Copy link
Copy Markdown
Contributor Author

I casually got to making transparent (argv) override-redirect window, like xfce4-notifyd has. But even better. xfce4-notifyd seems not using xshape, only relying to alpha chan. When some compositing manager (compton) tries to make its shadow, with rounded corners there are just gaps, between square shadow and rounded window edge (although this chan affects to rounded corner detection). XShape(ing) affects shadow geometry as well.

Edit: Previous assumption, about drawing of each notification in stack was wrong, it was simply some accumulation, due to unset background_pixel attr.

Comment thread src/x11/x.c Outdated
0, height - dia - 1,
-fw, -fw,
width - dia - 1, -fw,
-fw, height - dia - 1,

@tsipinakis tsipinakis May 4, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yet can't understand, where 0.5,0.5 displacement is set (originall integer coordinates would point to pixel corner instead of its center).

This was bothering me since the beginning and I finally figured out why: According to the X11 docs, the coordinates in XFillArc "specify the upper-left corner of the bounding rectangle", so at (0,0) this includes the frame, this change over-compensates and moves the arcs too much, breaking the round corners if the frame is large enough.

I'd only put 1 or 2 pixels of leeway here. The transparency changes are awesome, but they only work if there's a running compositor. Limiting the number of transparent pixels preserves the AA effect but minimizes the black artifacts in the corners.

There isn't even any need to modify the centercoords, simply expanding the radius by a few pixels should be enough.

@nick87720z nick87720z May 4, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's not center, XDrawArc takes rectangle coords, containing arc. Did not test all frame width, only 1 and 0 (both with and without compositing). Imho not sure, what's better - aliased border or several too dark pixels. Comparing with Xfce4-notifyd it just disabled rounding for no-comp.
Edit: I meaned displacement in cairo :)

@nick87720z

Copy link
Copy Markdown
Contributor Author

Oh, and with compositing and compton shadow I got white artifacts due to some transparent pixels, above white areas)))
dunst-rounding-comp-shape

@nick87720z

Copy link
Copy Markdown
Contributor Author

I was slow, thinking cairo had 0.5,0.5 displacement and not noticing, that there are no strokes))).
1pix outside is right enough to contain antialiasing. Btw, bitmap could be filled using cairo with its subpixel coordinate system, to even better tune frame->mask stroke margin (e.g. value 0.9 or 0.8 could drop some pixel with enough transparency).

@tsipinakis

tsipinakis commented May 6, 2020

Copy link
Copy Markdown
Member

e.g. value 0.9 or 0.8 could drop some pixel with enough transparency).

Dropping it in cairo wouldn't help us here though, we need a way to remove them from the window/on the X11 side to fix the shadow artifacts.

This seems like that last hurdle to have this working correctly. Do you know if there are any functions in the XShape extension to do that?
If no we may just have to live with these artifacts.

Edit: I realised you were proposing a different solution here, tuning the cairo strokes to match the window shape, this approach would also work but I haven't worked with this part of cairo extensively enough to know how feasible it is. If you can improve it, please do.

@nick87720z

Copy link
Copy Markdown
Contributor Author

As far as I understand, xshape is 1-bit alpha channel implementation, available without compositing. Applied above all rgb/rgba content. Initial implementation was fine in no-comp, if only paintings were not accumulating (it seems, that window appeared, its content was just a garbage from what was there before, not a true attempt to use background for transparency).
It's my first attempt to touch lower level windowing toolkit than qt/gtk :)

I tried to get internal radius more natural, but could not get satisfied with simple radius-fw difference.
Still tried to stick with integer arithmetics.

@nick87720z

nick87720z commented May 6, 2020

Copy link
Copy Markdown
Contributor Author

One problem (was) ugly xlib drawing quality, even at 1-bit cairo surpasses it. On case less to use xlib drawing. I did not even add that extra pixel for antialiased border, just repeated border painting with given radius against bitmap surface. So that's what I meaned some pixels are cut off, but overall quality is still better.

(well, may be if xlib had floating point support it could have better quality)

I'm not sure, that xshaping makes sence with compositing, if AA quality is important. Of course, those artifacts with shadow, at screenshot above, would appear only at too bright background (differing from shadow color).
dunst-xshape-cairo-2 dunst-xshape-cairo dunst-xbm-cairo

@nick87720z

Copy link
Copy Markdown
Contributor Author

While configuring picom I randomly triggered xfce4-notifyd without compositing. I was certainly wrong about it not using xshape, yet its quality was same as on my last shots above.

Just to not mimic xfce policy about config options, there could be option to force xshape in compositing mode if anyone likes shadows under notifications. But I'm not sure it's necessary. Same picom (former compton, so blamed for ugly shadows with rounded argb windows) now has option for custom shader, could be used for custom shado. Yet there is full-shadow toggle for winlist block (though not a place for arbitrary rules like for "shadow-exclude").

@tsipinakis

tsipinakis commented May 7, 2020

Copy link
Copy Markdown
Member

From my tests this looks perfect now! There are still some artifacts but they're barely visible IMO.

Just to not mimic xfce policy about config options, there could be option to force xshape in compositing mode if anyone likes shadows under notifications.

From a quick test with compton, the shadows were rounded and looked like they matched the window. So I'm not sure if there's anything wrong there.
In either cause for this I'll say let's wait until someone complains to add that config option, no reason to over-complicate :).

Edit: I'll try to do the final review on this over the weekend.

@nick87720z

Copy link
Copy Markdown
Contributor Author

While learning code, I noticed, that all drawing is done to image surface. What if it were done to recording surf as some way to draw right to xlib surf? When I tried so for self, I did not notice any visible difference. Of I'm aware that not all cairo ops work via backend, as case with opengl backend (remebering some notes about gradients).

Comment thread src/draw.c Outdated
r2 = r * h / (h + (w - r) * 2); // w >= r

radius_int = (r > w) ? r1 : (r / 2 < r2) ? r / 2 : r2;
radius_int /= s;

@tsipinakis tsipinakis May 10, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've been trying to decipher this block, some things I can't seem to figure out:

  • What's the point of s? In a standard, 64 bit, OS this will evaluate to 8 (4*8/4), my best guess is that it's for increasing the accuracy of the calculations but why 8/4? And why not just use floating points?
  • Why is height involved in r2? In fact, I can't figure out how you came up with these formulas.

@nick87720z

Copy link
Copy Markdown
Contributor Author

*8/4 - to make numbers less magic (I know both formulas are already like magic)))).
Trial and error part was only partial, thanks to gnuplot (last time I simply made surface, involving both radius and border width as x/y parameters). I tried first to make first formula to softly fade to second, but it went up too quickly after (bw > r). It's really hard to imagine them without origin, written with math graphics for ratios, sub/super-scripts, roots :/ (in fact I played with libreoffice math and qalculate, using first to improve qalculate output).

r1 - first supposed to change linearly as well from outer radius to some value for (width == radius), which I indeed got just by taste :) . However, this factor seemed to make less impact with high (radius/width) ratios, so I made it than slightly non linear... more exactly, with linear dependency on another factor.
I wanted to show something I did in lomath to explain it better, when need comes. Got system freeze (can't remember reason), requiring to reboot.

r2 - I made it keeping same ration to outer radius as int height to (outer height outer radius). But since its bigger than r1 at radius/width==1, I involved some coarse connection, implemented with (r/2 < r2) ? condition.

Btw I thought even about different drawing way:

  • get fillabe stroke area
  • subtract it from its origin path to get area inside border
  • join them to get xshape mask
    At least this way - no need to bother with internal radius, since there is just nothing to do with that :) .

@tsipinakis

tsipinakis commented May 14, 2020

Copy link
Copy Markdown
Member

*8/4 - to make numbers less magic (I know both formulas are already like magic)))).

I still don't get what's the purpose, to increase accuracy? Then why not use floating points?

I couldn't find anything wrong functionality wise, so what's left here is:
a) For me to understand what's going on (I can't maintain the code if I don't understand how it came to be) and b) some minor code style fixes that I'll post right after.

@tsipinakis tsipinakis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wanted to show something I did in lomath to explain it better, when need comes. Got system freeze (can't remember reason), requiring to reboot.

I just used geogebra to plot these and understand exactly what's going on in the frame, I've been testing this for the past week with different configurations and I can't find anything wrong with it as-is so I'd say let's merge this as-is and see if it needs further tweaking later on.

I've added some final code style comments, please resolve them first thought :)

Comment thread src/draw.c Outdated
* Simple r-w is not enough for too small r/w ratio.
* simplifications: r/2 == r - w + w*w / (r * 2) with (w == r)
**/
{ const int s = 2 << (sizeof(int) * 8 / 4); // Integer precision scaler

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[code style] There should be a newline after the brace

{
        // code here
}

Comment thread src/x11/x.c Outdated
if (settings.corner_radius != 0)
sprintf(astr, "_NET_WM_CM_S%i", win->cur_screen);
cm_sel = XInternAtom(xctx.dpy, astr, true);
if (settings.corner_radius != 0 && XGetSelectionOwner(xctx.dpy, cm_sel) == None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should probably be a new function bool has_compositing() or something similar, keep things simple

Comment thread src/x11/x.c Outdated
scr_n = DefaultScreen(xctx.dpy);
root = RootWindow(xctx.dpy, scr_n);
if (XMatchVisualInfo(xctx.dpy, scr_n, 32, TrueColor, &vi))
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[code style] Braces should be in the same line as the if/else statements

@nick87720z

Copy link
Copy Markdown
Contributor Author

I decided to go a bit further than has_compositing(). Added few more functions (from my previous changes) and renamed existing one.

x.c
renamed: x_win_corners_shape <- x_win_round_corners // Since all its job is to add xshape
new:
x_win_corners_unshape (win) // From else block, to not merge into x_win_corners_shape
bool x_win_composited (win),

draw.c (new): frame_internal_radius ()

Yet could be nice to reuse draw_rounded_rect() in xshaping, to not duplicate same func.

@codecov-commenter

codecov-commenter commented May 20, 2020

Copy link
Copy Markdown

Codecov Report

Merging #713 into master will decrease coverage by 0.33%.
The diff coverage is 0.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #713      +/-   ##
==========================================
- Coverage   66.29%   65.95%   -0.34%     
==========================================
  Files          29       29              
  Lines        5076     5102      +26     
==========================================
  Hits         3365     3365              
- Misses       1711     1737      +26     
Flag Coverage Δ
#unittests 65.95% <0.00%> (-0.34%) ⬇️
Impacted Files Coverage Δ
src/draw.c 0.00% <0.00%> (ø)
src/x11/x.c 2.26% <0.00%> (-0.08%) ⬇️
src/option_parser.c 86.11% <0.00%> (-0.56%) ⬇️
test/dbus.c 99.11% <0.00%> (+0.44%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fb2ffd4...2de1603. Read the comment docs.

@nick87720z

Copy link
Copy Markdown
Contributor Author

What if move draw_rounded_rect() from degrees to direct M_PI / M_PI_2 use since they are exactly half/quater parts?

@nick87720z

Copy link
Copy Markdown
Contributor Author

Did not mean to push full ARGB support here, but noticed too late loss for one changed line, which went to style fix (gg, blindly selected entire block).

@nick87720z

Copy link
Copy Markdown
Contributor Author

Completed ARGB with proper frame stroke limit, so that it doesn't paint behind background.
Color in config may be specified now in '#aarrggbb', '#rrrrggggbbbb' and '#aaaarrrrggggbbbb' formats.

@tsipinakis

tsipinakis commented May 21, 2020

Copy link
Copy Markdown
Member

Thanks for also working on RGBA support! It's been a widely requested feature. Though can you split it into a new PR? It's easier to review and reference in the future when each PR has a smaller scope.

Looks like the commits are fairly independent, so all it should take is cherry pick the last 4 commits to a new branch and rewrite the latest style commit.

Color in config may be specified now in '#aarrggbb', '#rrrrggggbbbb' and '#aaaarrrrggggbbbb' formats.

That's a bit excessive in my opinion, #rrggbbaa, and #rrggbb for backwards compatibility should be enough to cover all cases, is there any reason to support 4-5 different color formats?
(personally prefer RGBA, find it more readable and widely used)

@nick87720z

Copy link
Copy Markdown
Contributor Author

Hoped to avoid forced push).

About color coding - I tried to remember, which other apps I ever used need alpha first in config (configured by xresources). Rofi accepts as #aarrggbb (from where I took it). But urxvt accepts alpha last, though in specific format.

Yet dunst docs claim format support with single digit per channel #rgb, though no attempts were to differentiate them. Yet I expected first, that alpha-first will be useful to accept both argb and rgb int in single function, but it seems not - if alpha=0, it would mean opaque color without alpha.

@nick87720z

Copy link
Copy Markdown
Contributor Author

Hope PR will not suffer from forced push

@tsipinakis tsipinakis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM 👍

@tsipinakis tsipinakis merged commit 8134179 into dunst-project:master May 22, 2020
@tsipinakis

Copy link
Copy Markdown
Member

@nick87720z Feel free to submit the RGBA patches as a new PR whenever you can - looking forward to having these merged as well

@nick87720z nick87720z deleted the fix-ugly-frame branch May 23, 2020 09:16
@tsipinakis tsipinakis mentioned this pull request May 24, 2020
@tsipinakis

Copy link
Copy Markdown
Member

Found a bug here, in 16e38a9 if the frame width is 0 the separator will not be added at all.
Fixed with 7735c9a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants