Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

Slider with custom style leaves spots outside of its bounds

Open llfab opened this issue 5 months ago • 24 comments

Describe the bug

Slider with custom style leaves spots outside of its bounds when being dragged. See below video. I have tested that the "spots" are outside of the sliders bounds. We have had a similar issue in the past: https://github.com/AvaloniaUI/Avalonia/issues/9659

To Reproduce

Steps to reproduce the behavior:

  1. Load repro app from https://github.com/llfab/Samples/tree/main/AvaloniaAnimationTest
  2. Build and run
  3. Look at slider before draggin it
  4. Start dragging slider and observe left side

Expected behavior

There should not be any slider colored pixels outside of its bounds

Video

If applicable, add screenshots to help explain your problem.

https://github.com/AvaloniaUI/Avalonia/assets/31031996/49f5c4b3-7e3e-422f-bbaf-11d0f47c77c7

Environment

  • OS: Windows 11
  • Avalonia-Version: 11.0.6

llfab avatar Jan 19 '24 08:01 llfab

Two thoughts here:

  1. We had an old issue where there were rendering artifacts. At the time it was thought to be caused by Skia and was most often visible with borders. See: https://github.com/AvaloniaUI/Avalonia/issues/10873#issuecomment-1493046032 (I can't find the original issue for it, perhaps it was a discussion)
  2. ~Border rendering with certain BorderThickness and CornerRadius is now known to have issues. I found this with BackgroundSizing: https://github.com/AvaloniaUI/Avalonia/pull/14048#issue-2057771245~

image

Checking the Fluent Slider theme it does indeed use Border. I suspect this is related to one of the two areas above.

robloo avatar Jan 19 '24 20:01 robloo

Thanks for the feedback. ...and indeed the style I created uses corner radius. So any thoughts and how to work around that?

llfab avatar Jan 19 '24 22:01 llfab

On the off chance it's related to the border rendering issues can you try the BackgroundSizing PR package referenced above? That would eliminate that option fairly quickly if you still see an issue.

robloo avatar Jan 19 '24 23:01 robloo

I realize this does not appear at first. It is an artifact that occurs later on. That means it likely isn't a border rendering issue due to incorrect geometries (which would appear always) and is instead likely case 1 mentioned above.

Also notice that it gets worse the more the control redraws itself -- so transparency is involved. It is likely caused by SkiaSharp anti-aliasing outside expected bounds that aren't invalidated. This keeps stacking some of the anit-aliased pixels so they appear more and more during redraws until its a solid pixel.

You can likely work around it by adjusting the anti-aliasing on the border in the slider's control template. See https://github.com/AvaloniaUI/Avalonia/pull/9584

It's either a bug in SkiaSharp that will go away with 3.0 -- or we have to specially adjust the drawing area invalidation code to account for more than just the bounds of the control. It also needs to account for Skia's anti-aliasing algorithm which currently draws outside what is expected. I suspect something is already done like this for box shadows.

This is all a best guess but I've been watching this class of issue for a while trying to understand it better.

robloo avatar Jan 24 '24 05:01 robloo

Thanks for this analysis. Would you recommend to set RenderOptions.EdgeMode="Aliased" ?

llfab avatar Jan 24 '24 08:01 llfab

Thanks for this analysis. Would you recommend to set RenderOptions.EdgeMode="Aliased" ?

Only if you can tolerate the pixilation in the rounded corners. I'm not sure which looks worse for your scenario.


I also see a case of this in the current ControlCatalog. Notice when the ColorPicker flyout is open, and the color is changing, the selected color (in the DropDownButton content area) gets the artifacts. These artifacts accumulate slowly over time and only occur around the corner radius. As soon as the flyout closes the UI is invalidated properly and the artifacts go away on redraw.

https://github.com/AvaloniaUI/Avalonia/assets/17993847/fc35cf40-7278-4525-a07d-34ad83ace92c

robloo avatar Jan 27 '24 04:01 robloo

I don't believe we can tolerate Aliased. So, we could either do a workaround and try to invalidate the background panel once the slider changes (how would that be done?) or we sit and wait if you think this will solve itself during the next 12 months. What do you think?

llfab avatar Jan 27 '24 10:01 llfab

Could you try playing with RequiresFullOpacityHandling that creates a full layer for the sub tree

Gillibald avatar Jan 27 '24 10:01 Gillibald

Could you try playing with RequiresFullOpacityHandling that creates a full layer for the sub tree

For the slider or the parent panel?

llfab avatar Jan 27 '24 11:01 llfab

Another idea is to use a clipping geometry. That might be able to hide the issue but I haven't tried it myself.

robloo avatar Jan 27 '24 13:01 robloo

Double checked:

  • RequiresFullOpacityHandling does not change the behavior. Neither for the surrounding panel nor for the slider
  • RenderOptions.EdgeMode="Aliased" prevents the problem but indeed the corner radius looks rougher

llfab avatar Jan 28 '24 21:01 llfab

@llfab Good to know, I think the only possible work-around with no visible downsides is a special clipping geometry.

robloo avatar Jan 29 '24 00:01 robloo

@llfab Good to know, I think the only possible work-around with no visible downsides is a special clipping geometry.

Good. Any kind of coding hints on how to do that?

llfab avatar Jan 29 '24 07:01 llfab

@llfab tbh I would just add another Panel blow the Border which has 1px of Margin.

Hope this sketch makes it a bit more clear:

image

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="AvaloniaAnimationTest.AnimationControl"
             Padding="4">
+ <Panel Background="Tansparent" Padding="1">
    <Border Name="animatedBorderHost" 
            Background="DarkGray" 
            CornerRadius="2"
            ClipToBounds="True"
            Height="8"
            HorizontalAlignment="Stretch">
      <Border Name="innerBorder"
              Background="Green"
              CornerRadius="4"
              Width="200"
              HorizontalAlignment="Left"/>
    </Border>
+ </Panel>
</UserControl>

timunie avatar Jan 29 '24 08:01 timunie

That did not work. The only workaround that seems to work is:

                    <Border x:Name="_myBorder" 
                            Background="Transparent"
                            Padding="2 0 0 0">
                        <Slider x:Name="_mySlider" 
                                Classes="largeSize"
                                Orientation="Horizontal"
                                HorizontalAlignment="Left"
                                VerticalAlignment="Center"
                                ValueChanged="OnSliderValueChanged"
                                Minimum="-1"
                                Maximum="1"
                                TickPlacement="TopLeft"
                                Ticks="0"/>
                    </Border>


    private void OnSliderValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        _myBorder.InvalidateVisual();
    }

llfab avatar Jan 29 '24 09:01 llfab

But placing this everywhere in the UI seems cumbersome. Hence, is you feeling that this will be solved within the this year?

llfab avatar Jan 29 '24 09:01 llfab

We marked the issue with priortiy, to I hope we will be able to find a solution soonish. I had a similar issue where I needed to infalte dirty rect by some px. While this could also solve this, it will add more work on the renderer where not needed. So maybe we can find a better solution here.

timunie avatar Jan 29 '24 09:01 timunie

Great. From my side I would love to see this solved even if I can live with a couple more months...

llfab avatar Jan 29 '24 09:01 llfab

It seems to be a Skia bug. SKCanvas.DrawRoundRect seems to be somewhat ignoring the clip bounds.

I. e.

        private SKRoundRect _rect;
        private SKPaint _paint = new();

        public ClipTest()
        {
            _rect = new SKRoundRect();
            var skArray = new SKPoint[4];
            skArray[0].X = 8;
            skArray[0].Y = 8;
            skArray[1].X = 0;
            skArray[1].Y = 0;
            skArray[2].X =0;
            skArray[2].Y = 0;
            skArray[3].X =8;
            skArray[3].Y = 8;
            _rect.SetRectRadii(new SKRect(0, 0, 40, 40), skArray);
        }
        
        protected override void SkiaRender()
        {
            _paint.IsAntialias = true;
            _paint.Color = SKColors.Aqua;
            Canvas.SetMatrix(SKMatrix.CreateTranslation(200, 200));
            for (var c = 0; c < 255; c++)
            {
                Canvas.Save();
                Canvas.ClipRect(_rect.Rect);
                Canvas.Clear(SKColors.Black);
                _paint.Color = new SKColor(255, (byte)c, 255);
                Canvas.DrawRoundRect(_rect, _paint);
                Canvas.Restore();
            }
        }

produces this: image

kekekeks avatar Feb 06 '24 09:02 kekekeks

It seems to be a Skia bug. SKCanvas.DrawRoundRect seems to be somewhat ignoring the clip bounds.

Thanks Nikita. Do you think Skia or SkiaSharp bug?

llfab avatar Feb 06 '24 13:02 llfab

And: would that be fixed with SkiaSharp 3.x and the according Skia version?

llfab avatar Feb 06 '24 13:02 llfab

No, it wasn't fixed in SkiaSharp 3.0

maxkatz6 avatar Feb 06 '24 21:02 maxkatz6

It goes away if we use an intermediate texture and render it with clipping, but it would require some infrastructure changes.

kekekeks avatar Feb 07 '24 07:02 kekekeks

Thanks Nikita for checking. Expensive but temporary workarounds may not be the best way. Question is whether it's Skia or SkiaSharp and whether we could make either team fix the root cause within say the next 8 months or so...

llfab avatar Feb 07 '24 08:02 llfab

If this is a skia bug, can you confirm with skia fiddle and mayne file a bug with them? Mayne it is fixed in a later skia? I could do another native bump...

mattleibow avatar Mar 02 '24 20:03 mattleibow

void draw(SkCanvas* canvas) {
    SkPaint paint;
    paint.setAntiAlias(true);
    SkPath path;
    SkRRect rrect;
    SkVector corners[] = {{8,8}, {0,0}, {0,0}, {8,8}};
    rrect.setRectRadii({0, 0, 40, 40}, corners);
    canvas->translate(20,20);

    for(int c=0; c<255; c++)
    {
      canvas->save();
      canvas->clipRect(SkRect::MakeWH(40, 40), true);
      canvas->clear(SkColorSetARGB(0xff, 0xFF, 0xff, 0xff)); 
      canvas->drawRRect(rrect, paint); 
      canvas->restore();
    }
}

image image

kekekeks avatar Mar 03 '24 15:03 kekekeks

https://issues.skia.org/issues/327877721

kekekeks avatar Mar 03 '24 15:03 kekekeks

Should have been fixed by #14806.

grokys avatar Mar 07 '24 08:03 grokys