【问题标题】:how to make the brush smooth without lines in the middle如何让画笔平滑中间没有线条
【发布时间】:2012-04-05 18:56:20
【问题描述】:

大家好, 正如您在之前的画笔中看到的,中间有线条,
它不是那么平滑 如何使它顺利? (如何删除该行) 我用混合创建它

<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>

【问题讨论】:

  • 你能发布你创建画笔的代码吗?这是渐变画笔吗?
  • 好的,你现在可以检查代码了。

标签: c# wpf blend


【解决方案1】:

条带是梯度算法的产物。它必须将该区域分成带子,每个带子都填充了稍微不同的颜色。边缘实际上是一种视觉错觉,它的效果是让它们比你想象的更明显。要减少这种影响,您需要减小每个波段的宽度。

解决办法是:

  1. 填充较小的区域 - 每个条带都较窄。
  2. 增加波段数。加大两个极端之间的对比。
  3. 提高显示器的颜色分辨率。如果您有更多颜色可供选择,那么两种最终颜色之间的可用范围就会更大。

我确实意识到这些解决方案要么 a) 不可能,要么 b) 不切实际。这是一个你将不得不忍受的问题。

一种实用的解决方案可能是将画笔替换为在 Photoshop 或其他图像处理包中创建的图像。这可能会为您提供条带较少的图像 - 但是您会受到图像大小的限制 - 如果没有像素化,您将无法放大它。

【讨论】:

    【解决方案2】:

    前段时间,我为我的 WPF 项目编写了平滑的线性渐变。它消除了条带,但有两个警告:

    • 不能用于数据绑定颜色或{DynamicResource}
    • 它目前仅支持垂直渐变,因为这就是我所需要的

    它被实现为动态创建的ImageBrush,使用Ordered Dithering。另外,它也是一个MarkupExtension,因为不能简单地继承任何Brush 类(它们都是密封的)。

    /// <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;
        }
    }
    

    在资源字典中的使用:

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

    然后是控制风格:

    <Style ...>
        <Setter Property="Background" Value="{StaticResource WindowBackgroundBrush}" />
    </Style>
    

    【讨论】:

      【解决方案3】:

      我的另一个答案中的一个廉价而肮脏的选择是这样的:

      将渐变添加到容器,给它一个小的负边距,使其溢出一点,向渐变添加一个 BlurEffect,然后在父容器上打开 ClipToBounds。这样,梯度会以牺牲性能为代价变得更好。

      但是,根据您的用例,这可能不可行。

      例子:

      <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>
      

      负梯度应该等于模糊半径,这样它就不会在边缘变得透明。

      【讨论】:

        【解决方案4】:

        基于 ghord 的解决方案,我实现了一个更通用的渐变。我将它实现为 FrameworkElement 中的动态图像。您可以指定所有四个角的颜色,它特别适用于非常浅色的渐变,没有任何条纹。即使使用高 DPI 显示器,图像的缩放方式也能产生完美像素的抖动。

        /// <summary>
        /// A class that draws a pixel-perfect, dithered gradient image
        /// </summary>
        public class DitheredGradientImage : FrameworkElement
        {
            public static readonly DependencyProperty TopLeftColorProperty =
                DependencyProperty.Register(nameof(TopLeftColor), typeof(Color), typeof(DitheredGradientImage),
            new FrameworkPropertyMetadata(Colors.Transparent, FrameworkPropertyMetadataOptions.AffectsRender, OnColorsChanged));
        
            public static readonly DependencyProperty TopRightColorProperty =
                DependencyProperty.Register(nameof(TopRightColor), typeof(Color), typeof(DitheredGradientImage),
                    new FrameworkPropertyMetadata(Colors.Transparent, FrameworkPropertyMetadataOptions.AffectsRender, OnColorsChanged));
        
            public static readonly DependencyProperty BottomLeftColorProperty =
                DependencyProperty.Register(nameof(BottomLeftColor), typeof(Color), typeof(DitheredGradientImage),
                    new FrameworkPropertyMetadata(Colors.Transparent, FrameworkPropertyMetadataOptions.AffectsRender, OnColorsChanged));
        
            public static readonly DependencyProperty BottomRightColorProperty =
                DependencyProperty.Register(nameof(BottomRightColor), typeof(Color), typeof(DitheredGradientImage),
                    new FrameworkPropertyMetadata(Colors.Transparent, FrameworkPropertyMetadataOptions.AffectsRender, OnColorsChanged));
        
            public Color TopLeftColor
            {
                get { return (Color)this.GetValue(TopLeftColorProperty); }
                set { this.SetValue(TopLeftColorProperty, value); }
            }
        
            public Color TopRightColor
            {
                get { return (Color)this.GetValue(TopRightColorProperty); }
                set { this.SetValue(TopRightColorProperty, value); }
            }
        
            public Color BottomLeftColor
            {
                get { return (Color)this.GetValue(BottomLeftColorProperty); }
                set { this.SetValue(BottomLeftColorProperty, value); }
            }
        
            public Color BottomRightColor
            {
                get { return (Color)this.GetValue(BottomRightColorProperty); }
                set { this.SetValue(BottomRightColorProperty, value); }
            }
        
            public Color TopColor
            {
                get { return (TopLeftColor == TopRightColor) ? TopLeftColor : default(Color); }
                set { TopLeftColor = value; TopRightColor = value; }
            }
        
            public Color BottomColor
            {
                get { return (BottomLeftColor == BottomRightColor) ? BottomLeftColor : default(Color); }
                set { BottomLeftColor = value; BottomRightColor = value; }
            }
        
            public Color LeftColor
            {
                get { return (TopLeftColor == BottomLeftColor) ? TopLeftColor : default(Color); }
                set { TopLeftColor = value; BottomLeftColor = value; }
            }
        
            public Color RightColor
            {
                get { return (TopRightColor == BottomRightColor) ? TopRightColor : default(Color); }
                set { TopRightColor = value; BottomRightColor = value; }
            }
        
            public double DpiScaleX => VisualTreeHelper.GetDpi(this).DpiScaleX;
            public double DpiScaleY => VisualTreeHelper.GetDpi(this).DpiScaleY;
        
            public double DpiX => VisualTreeHelper.GetDpi(this).PixelsPerInchX;
            public double DpiY => VisualTreeHelper.GetDpi(this).PixelsPerInchX;
        
            private WriteableBitmap bitmap = null;
            private int lastPixelWidth = 0;
            private int lastPixelHeight = 0;
        
            protected static void OnColorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                // Force redraw by resetting width and height
                if (d is DitheredGradientImage di)
                {
                    di.lastPixelWidth = 0;
                    di.lastPixelHeight = 0;
                }
            }
        
            protected void Update(int pixelWidth, int pixelHeight)
            {
                // Check if bitmap needs recreation, because it it is either
                // not initialized, or too small to fit the current element size
                if ((bitmap == null) ||
                    (bitmap.PixelWidth < pixelWidth) ||
                    (bitmap.PixelHeight < pixelHeight))
                {
                    // Oversize, so that continuous resizing doesn't lead to constant bitmap recreation
                    bitmap = new WriteableBitmap(pixelWidth + 256, pixelHeight + 256, DpiX, DpiY, PixelFormats.Bgra32, null);
        
                    // As the old bitmap is gone, reset values so it gets redrawn anyway
                    lastPixelWidth = 0;
                    lastPixelHeight = 0;
                }
        
                // The gradient needs to be drawn to the exact size required
                if ((lastPixelWidth != pixelWidth) || (lastPixelHeight != pixelHeight))
                {
                    // Draw a line-based test pattern to verify proper pixel scaling (or the lack of)
                    //DrawTestPattern(pixelWidth, pixelHeight);
        
                    // Draw the actual gradient
                    DrawGradient(pixelWidth, pixelHeight);
        
                    // Store values to avoid redrawing when nothing has changed
                    lastPixelWidth = pixelWidth;
                    lastPixelHeight = pixelHeight;
                }
            }
        
            protected void DrawTestPattern(int pixelWidth, int pixelHeight)
            {
                try
                {
                    // Reserve the back buffer for updates.
                    bitmap.Lock();
        
                    unsafe
                    {
                        // Get a pointer to the back buffer.
                        IntPtr pBackBuffer = bitmap.BackBuffer;
        
                        for (int y = 0; y < pixelHeight; y++)
                        {
                            for (int x = 0; x < pixelWidth; x++)
                            {
                                // Draw alternate black and white lines
                                uint colorData = (y % 2 == 0) ? 0xFF000000 : 0xFFFFFFFF;
                                *(uint*)(pBackBuffer + (y * bitmap.BackBufferStride) + (x * 4)) = colorData;
                            }
                        }
                    }
        
                    // Specify the area of the bitmap that changed.
                    bitmap.AddDirtyRect(new Int32Rect(0, 0, pixelWidth, pixelHeight));
                }
                finally
                {
                    // Release the back buffer and make it available for display.
                    bitmap.Unlock();
                }
            }
        
            private const byte matrixSize = 4;
        
            private static readonly float[,] scaledMatrix = CalculateMatrix(matrixSize);
        
            private static float[,] CalculateMatrix(byte n)
            {
                uint[,] uintMatrix = new uint[n, n];
                uint divisor = uint.MaxValue;
                for (ushort i = 0; i < n; i++)
                {
                    for (ushort j = 0; j < n; j++)
                    {
                        uintMatrix[i, j] = BitReverse(8, BitInterleave((ushort)(i ^ j), i));
                        if ((uintMatrix[i, j] != 0) && (divisor > uintMatrix[i, j]))
                            divisor = uintMatrix[i, j];
                    }
                }
        
                float[,] floatMatrix = new float[n, n];
                float coefficient = 1.0f / (uintMatrix.Length * divisor);
                for (ushort i = 0; i < n; i++)
                {
                    for (ushort j = 0; j < n; j++)
                    {
                        floatMatrix[i, j] = uintMatrix[i, j] * coefficient;
                    }
                }
        
                return floatMatrix;
            }
        
            /// <summary>
            /// Reverse all bits
            /// </summary>
            private static uint BitReverse(byte bits, uint value)
            {
                uint left = (uint)1 << (bits - 1);
                uint right = 1;
                uint result = 0;
        
                for (int i = (bits - 1); i >= 1; i -= 2)
                {
                    result |= (value & left) >> i;
                    result |= (value & right) << i;
                    left >>= 1;
                    right <<= 1;
                }
                return result;
            }
        
            /// <summary>
            /// Bitwise interleave of two 16-bit unsigned integers
            /// </summary>
            private static uint BitInterleave(ushort x, ushort y)
            {
                uint _x = x;
                _x = (_x | (_x << 8)) & 0x00FF00FF;
                _x = (_x | (_x << 4)) & 0x0F0F0F0F;
                _x = (_x | (_x << 2)) & 0x33333333;
                _x = (_x | (_x << 1)) & 0x55555555;
        
                uint _y = y;
                _y = (_y | (_y << 8)) & 0x00FF00FF;
                _y = (_y | (_y << 4)) & 0x0F0F0F0F;
                _y = (_y | (_y << 2)) & 0x33333333;
                _y = (_y | (_y << 1)) & 0x55555555;
        
                return (uint)(_x | (_y << 1));
            }
        
            /// <summary>
            /// Draws the gradient
            /// </summary>
            protected void DrawGradient(int pixelWidth, int pixelHeight)
            {
                // Caching the color values is mandatory, JIT compiler doesn't seem to cache access
                byte topLeftR = TopLeftColor.R, topLeftG = TopLeftColor.G, topLeftB = TopLeftColor.B, topLeftA = TopLeftColor.A;
                byte topRightR = TopRightColor.R, topRightG = TopRightColor.G, topRightB = TopRightColor.B, topRightA = TopRightColor.A;
                byte bottomLeftR = BottomLeftColor.R, bottomLeftG = BottomLeftColor.G, bottomLeftB = BottomLeftColor.B, bottomLeftA = BottomLeftColor.A;
                byte bottomRightR = BottomRightColor.R, bottomRightG = BottomRightColor.G, bottomRightB = BottomRightColor.B, bottomRightA = BottomRightColor.A;
        
                try
                {
                    // Reserve the back buffer for updates.
                    bitmap.Lock();
        
                    unsafe
                    {
                        // Get a pointer to the back buffer.
                        IntPtr pBackBuffer = bitmap.BackBuffer;
                        int backBufferStride = bitmap.BackBufferStride;
        
                        for (int y = 0; y < pixelHeight; y++)
                        {
                            float incYScale = y / (float)pixelHeight;
                            float decYScale = 1 - (y / (float)pixelHeight);
        
                            for (int x = 0; x < pixelWidth; x++)
                            {
                                float incXScale = x / (float)pixelWidth;
                                float decXScale = 1 - (x / (float)pixelWidth);
        
                                float r = (((topLeftR * decXScale) + (topRightR * incXScale)) * decYScale) +
                                            (((bottomLeftR * decXScale) + (bottomRightR * incXScale)) * incYScale);
                                float g = (((topLeftG * decXScale) + (topRightG * incXScale)) * decYScale) +
                                            (((bottomLeftG * decXScale) + (bottomRightG * incXScale)) * incYScale);
                                float b = (((topLeftB * decXScale) + (topRightB * incXScale)) * decYScale) +
                                            (((bottomLeftB * decXScale) + (bottomRightB * incXScale)) * incYScale);
                                float a = (((topLeftA * decXScale) + (topRightA * incXScale)) * decYScale) +
                                            (((bottomLeftA * decXScale) + (bottomRightA * incXScale)) * incYScale);
        
                                byte* p = (byte*)(pBackBuffer + (y * backBufferStride) + (x * 4));
        
                                p[0] = (byte)(b + scaledMatrix[x % matrixSize, y % matrixSize]);
                                p[1] = (byte)(g + scaledMatrix[x % matrixSize, y % matrixSize]);
                                p[2] = (byte)(r + scaledMatrix[x % matrixSize, y % matrixSize]);
                                p[3] = (byte)a;
                            }
                        }
        
                        // Specify the area of the bitmap that changed.
                        bitmap.AddDirtyRect(new Int32Rect(0, 0, pixelWidth, pixelHeight));
                    }
                }
                finally
                {
                    bitmap.Unlock();
                }
            }
        
            protected override void OnRender(DrawingContext dc)
            {
                // Math.Round seems to be used in WPF, so it's important to use the 
                // same function, otherwise you sometimes don't get pixel-perfect drawing
                int pixelWidth = (int)Math.Round(RenderSize.Width * DpiScaleX);
                int pixelHeight = (int)Math.Round(RenderSize.Height * DpiScaleY);
        
                // Exit if there is nothing to draw
                if ((pixelWidth == 0) || (pixelHeight == 0)) return;
        
                // Refresh the bitmap
                Update(pixelWidth, pixelHeight);
        
                if (bitmap != null)
                {
                    // The bitmap will be oversized, and DrawingContext.DrawImage doesn't provide
                    // any other way to specify a source image rect
                    CroppedBitmap croppedBitmap = new CroppedBitmap(bitmap, new Int32Rect(0, 0, pixelWidth, pixelHeight));
                    dc.DrawImage(croppedBitmap, new Rect(default(Point), RenderSize));
                }
            }
        }
        

        在 XAML 中使用它:

        <local:DitheredGradientImage TopLeftColor="#e5e5e5" TopRightColor="#ffffff" BottomLeftColor="#ffffff" BottomRightColor="#f5f5f5" />
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-14
          • 1970-01-01
          • 1970-01-01
          • 2023-01-12
          • 1970-01-01
          相关资源
          最近更新 更多