Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to make the brush smooth without lines in the middle

Tags:

c#

wpf

blend

enter image description here

Hi all, as you see in previous brush there is lines in the middle,
it isn't so smooth how to make it smooth? (how to remove that lines) i create it with blend

<Grid x:Name="LayoutRoot">
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.452,1.962" StartPoint="1.164,-0.352">
                <GradientStop Color="#FF202020" Offset="0"/>
                <GradientStop Color="#FF545454" Offset="1"/>
            </LinearGradientBrush>
        </Grid.Background>
    </Grid>
like image 840
HB MAAM Avatar asked Mar 21 '12 09:03

HB MAAM


3 Answers

The banding is an artefact of the gradient algorithm. It has to break the area in to bands each filled with a slightly different colour. The edges are in fact an optical illusion which has the effect of making them more visible than you would think. To reduce this effect you need to reduce the width of each band.

The solutions are:

  1. Fill over a smaller area - each band is narrower.
  2. Increase the number of bands. Make the contrast between the two extremes larger.
  3. Increase the colour resolution of the display. If you have more colours to choose from then there will be a larger usable range between the two end colours.

I do realise that these solutions are either a) not possible or b) not practical. This is a problem you are going to have to live with.

One practical solution might be to replace the brush with an image created in Photoshop or other image processing package. This might give you an image with less banding - but then you are restricted to the size of the image - you can't grow it without pixelation.

like image 67
ChrisF Avatar answered Oct 17 '22 00:10

ChrisF


Some time ago I wrote smooth linear gradient for my WPF project. It removes the banding, but there are two caveats:

  • It cannot be used for databound colors or {DynamicResource}.
  • It currently supports only vertical gradient, as that was all I needed

It is implemented as dynamicly created ImageBrush, using Ordered Dithering. Additionaly, it is also a MarkupExtension, as one cannot simply inherit any Brush class (the're all sealed).

/// <summary>
/// Brush that lets you draw vertical linear gradient without banding.
/// </summary>
[MarkupExtensionReturnType(typeof(Brush))]
public class SmoothLinearGradientBrush : MarkupExtension
{
    private static PropertyInfo dpiX_;
    private static PropertyInfo dpiY_;
    private static byte[,] bayerMatrix_ = 
    {
        { 1, 9, 3, 11 },
        { 13, 5, 15, 7 },
        { 1, 9, 3, 11 },
        { 16, 8, 14, 6 }
    };

    static SmoothLinearGradientBrush()
    {
        dpiX_ = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static);
        dpiY_ = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static);
    }

    /// <summary>
    /// Gradient color at the top
    /// </summary>
    public Color From { get; set; }

    /// <summary>
    /// Gradient color at the bottom
    /// </summary>
    public Color To { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        //If user changes dpi/virtual screen height during applicaiton lifetime,
        //wpf will scale the image up for us.
        int width = 20;
        int height = (int)SystemParameters.VirtualScreenHeight;
        int dpix = (int)dpiX_.GetValue(null);
        int dpiy = (int)dpiY_.GetValue(null);


        int stride = 4 * ((width * PixelFormats.Bgr24.BitsPerPixel + 31) / 32);

        //dithering parameters
        double bayerMatrixCoefficient = 1.0 / (bayerMatrix_.Length + 1);
        int bayerMatrixSize = bayerMatrix_.GetLength(0);

        //Create pixel data of image
        byte[] buffer = new byte[height * stride];

        for (int line = 0; line < height; line++)
        {
            double scale = (double)line / height;

            for (int x = 0; x < width * 3; x += 3)
            {
                //scaling of color
                double blue = ((To.B * scale) + (From.B * (1.0 - scale)));
                double green = ((To.G * scale) + (From.G * (1.0 - scale)));
                double red = ((To.R * scale) + (From.R * (1.0 - scale)));

                //ordered dithering of color
                //source: http://en.wikipedia.org/wiki/Ordered_dithering
                buffer[x + line * stride] = (byte)(blue + bayerMatrixCoefficient * bayerMatrix_[x % bayerMatrixSize, line % bayerMatrixSize]);
                buffer[x + line * stride + 1] = (byte)(green + bayerMatrixCoefficient * bayerMatrix_[x % bayerMatrixSize, line % bayerMatrixSize]);
                buffer[x + line * stride + 2] = (byte)(red + bayerMatrixCoefficient * bayerMatrix_[x % bayerMatrixSize, line % bayerMatrixSize]);
            }
        }

        var image = BitmapSource.Create(width, height, dpix, dpiy, PixelFormats.Bgr24, null, buffer, stride);
        image.Freeze();
        var brush = new ImageBrush(image);
        brush.Freeze();
        return brush;
    }
}

Usage in resource dictionary:

<local:SmoothLinearGradientBrush x:Key="WindowBackgroundBrush" 
                                 From="{StaticResource WindowBackgroundColorLight}" 
                                 To="{StaticResource WindowBackgroundColorDark}" />

and then in control style:

<Style ...>
    <Setter Property="Background" Value="{StaticResource WindowBackgroundBrush}" />
</Style>
like image 7
ghord Avatar answered Oct 16 '22 23:10

ghord


A cheap and dirty option from another answer of mine is this:

Add the gradient to a container, give it a small negative margin so it spills over a little, add a BlurEffect to the gradient, and then turn on ClipToBounds on the parent container. This way the gradient evens out a bit nicer at the expense of performance.

Depending on your use case, this may not be viable, however.

Example:

<Grid Height="26" Margin="-5,0" ClipToBounds="True">
    <Grid Margin="-5">
        <Grid.Effect>
            <BlurEffect Radius="6" />
        </Grid.Effect>
        <Grid.Background>
            <LinearGradientBrush>
                <GradientStop x:Name="GradientStop7" Color="Magenta" Offset="0.0" />
                <GradientStop x:Name="GradientStop8" Color="DarkOrchid" Offset=".2" />
                <GradientStop x:Name="GradientStop9" Color="Purple" Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>
    </Grid>
</Grid>

The negative gradient should be equal to the blur radius so that it doesn't become transparent on the edges.

like image 2
TernaryTopiary Avatar answered Oct 17 '22 00:10

TernaryTopiary