WinForms中的图层效果(模糊等)

3
在WinForms中,我正在寻找一种实现类似于Java Swing中JXLayer类的功能的方法。 更具体地说,我想模糊窗口的整个内容,并在其上绘制一些东西(例如等待圆圈)。 非常感谢任何想法 :)
5个回答

13

以下是我的解决方案。

  1. 截取屏幕截图。
  2. 对其进行模糊处理。
  3. 将模糊后的图片放置在所有内容的前面。

假设有一个带有 Button button1 和 Panel panel1(其中包含 ListBox 和 ProgressBar)的表单,在此情况下,下面的代码效果非常好:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;

namespace Blur
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Bitmap bmp = Screenshot.TakeSnapshot(panel1);
            BitmapFilter.GaussianBlur(bmp, 4);

            PictureBox pb = new PictureBox();
            panel1.Controls.Add(pb);
            pb.Image = bmp;
            pb.Dock = DockStyle.Fill;
            pb.BringToFront();            
        }
    }

    public class ConvMatrix
    {
        public int TopLeft = 0, TopMid = 0, TopRight = 0;
        public int MidLeft = 0, Pixel = 1, MidRight = 0;
        public int BottomLeft = 0, BottomMid = 0, BottomRight = 0;
        public int Factor = 1;
        public int Offset = 0;
        public void SetAll(int nVal)
        {
            TopLeft = TopMid = TopRight = MidLeft = Pixel = MidRight = BottomLeft = BottomMid = BottomRight = nVal;
        }
    }

    public class BitmapFilter
    {
        private static bool Conv3x3(Bitmap b, ConvMatrix m)
        {
            // Avoid divide by zero errors
            if (0 == m.Factor) return false;

            Bitmap bSrc = (Bitmap)b.Clone();

            // GDI+ still lies to us - the return format is BGR, NOT RGB.
            BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            int stride = bmData.Stride;
            int stride2 = stride * 2;
            System.IntPtr Scan0 = bmData.Scan0;
            System.IntPtr SrcScan0 = bmSrc.Scan0;

            unsafe
            {
                byte* p = (byte*)(void*)Scan0;
                byte* pSrc = (byte*)(void*)SrcScan0;

                int nOffset = stride + 6 - b.Width * 3;
                int nWidth = b.Width - 2;
                int nHeight = b.Height - 2;

                int nPixel;

                for (int y = 0; y < nHeight; ++y)
                {
                    for (int x = 0; x < nWidth; ++x)
                    {
                        nPixel = ((((pSrc[2] * m.TopLeft) + (pSrc[5] * m.TopMid) + (pSrc[8] * m.TopRight) +
                            (pSrc[2 + stride] * m.MidLeft) + (pSrc[5 + stride] * m.Pixel) + (pSrc[8 + stride] * m.MidRight) +
                            (pSrc[2 + stride2] * m.BottomLeft) + (pSrc[5 + stride2] * m.BottomMid) + (pSrc[8 + stride2] * m.BottomRight)) / m.Factor) + m.Offset);

                        if (nPixel < 0) nPixel = 0;
                        if (nPixel > 255) nPixel = 255;

                        p[5 + stride] = (byte)nPixel;

                        nPixel = ((((pSrc[1] * m.TopLeft) + (pSrc[4] * m.TopMid) + (pSrc[7] * m.TopRight) +
                            (pSrc[1 + stride] * m.MidLeft) + (pSrc[4 + stride] * m.Pixel) + (pSrc[7 + stride] * m.MidRight) +
                            (pSrc[1 + stride2] * m.BottomLeft) + (pSrc[4 + stride2] * m.BottomMid) + (pSrc[7 + stride2] * m.BottomRight)) / m.Factor) + m.Offset);

                        if (nPixel < 0) nPixel = 0;
                        if (nPixel > 255) nPixel = 255;

                        p[4 + stride] = (byte)nPixel;

                        nPixel = ((((pSrc[0] * m.TopLeft) + (pSrc[3] * m.TopMid) + (pSrc[6] * m.TopRight) +
                            (pSrc[0 + stride] * m.MidLeft) + (pSrc[3 + stride] * m.Pixel) + (pSrc[6 + stride] * m.MidRight) +
                            (pSrc[0 + stride2] * m.BottomLeft) + (pSrc[3 + stride2] * m.BottomMid) + (pSrc[6 + stride2] * m.BottomRight)) / m.Factor) + m.Offset);

                        if (nPixel < 0) nPixel = 0;
                        if (nPixel > 255) nPixel = 255;

                        p[3 + stride] = (byte)nPixel;

                        p += 3;
                        pSrc += 3;
                    }

                    p += nOffset;
                    pSrc += nOffset;
                }
            }

            b.UnlockBits(bmData);
            bSrc.UnlockBits(bmSrc);

            return true;
        }

        public static bool GaussianBlur(Bitmap b, int nWeight /* default to 4*/)
        {
            ConvMatrix m = new ConvMatrix();
            m.SetAll(1);
            m.Pixel = nWeight;
            m.TopMid = m.MidLeft = m.MidRight = m.BottomMid = 2;
            m.Factor = nWeight + 12;

            return BitmapFilter.Conv3x3(b, m);
        }
    }

    class Screenshot
    {
        public static Bitmap TakeSnapshot(Control ctl)
        {
            Bitmap bmp = new Bitmap(ctl.Size.Width, ctl.Size.Height);
            using (Graphics g = System.Drawing.Graphics.FromImage(bmp))
            {
                g.CopyFromScreen(
                    ctl.PointToScreen(ctl.ClientRectangle.Location),
                    new Point(0, 0), ctl.ClientRectangle.Size
                );
            }
            return bmp; 
        }            
    }
}

alt textalt text 高斯模糊的代码是从这里借鉴而来。


1
太棒了!我喜欢一点头脑风暴、谷歌和空闲时间所能做的事情。;) - Stefan
2
嗯,我不确定有多少空闲时间,但其他方面肯定可以 :) - ua.Skywalker
1
你可以用这4行代码替换掉你的大型TakeSnapshot函数: public Bitmap TakeSnapshot(Control ctl) { Bitmap bmp = new Bitmap(ctl.Size.Width, ctl.Size.Height); System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp); g.CopyFromScreen(ctl.PointToScreen(ctl.ClientRectangle.Location), new Point(0, 0), ctl.ClientRectangle.Size); return bmp; } - Stefan
1
太棒了!看起来更性感了 :) 我仍然有一个过度使用p/invoke的习惯 :) - ua.Skywalker
2
@ua.Skywalker 感谢分享!我该如何增强模糊效果的强度?无论我改变什么值,似乎都没有应用更强的模糊。 - uSeRnAmEhAhAhAhAhA
@uSeRnAmEhAhAhAhAhA,我通过在第一次调用BitmapFilter.GaussianBlur方法后立即再次调用该方法来加深模糊效果,从而进一步模糊了图像。 - Tom West

1

这段 VB.Net 代码将会:

  1. 创建一个窗体图片
  2. 为该图片添加模糊效果
  3. 将该图片添加到 PictureBox 中
  4. 添加一个点击处理程序在单击时移除该图片
  5. 将该 PictureBox 添加到窗体并设置为 bringtofront

唯一的问题是模糊效果比较慢。所以我会着手改进。

Imports System.Drawing.Imaging
Public Class Form1

Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
    ShowBlurredPicture()
End Sub

Sub ShowBlurredPicture()
    Dim blurredpic As Bitmap = gausianBlur(False, New Size(5, 5), GetFormPic)
    Dim p As New PictureBox
    p.Image = blurredpic

    p.Location = New Point(-System.Windows.Forms.SystemInformation.FrameBorderSize.Width, -(System.Windows.Forms.SystemInformation.CaptionHeight + System.Windows.Forms.SystemInformation.FrameBorderSize.Height))
    p.Size = New Size(Me.Size)
    Me.Controls.Add(p)
    p.Visible = True
    p.BringToFront()
    AddHandler p.Click, AddressOf picclick
End Sub
Sub picclick(ByVal sender As Object, ByVal e As System.EventArgs)
    Me.Controls.Remove(sender)
End Sub
Function GetFormPic() As Bitmap
    Dim ScreenSize As Size = Me.Size
    Dim screenGrab As New Bitmap(Me.Width, Me.Height)
    Dim g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(screenGrab)
    g.CopyFromScreen(Me.Location, New Point(0, 0), Me.Size)
    Return screenGrab
End Function
Private Function Average(ByVal Size As Size, ByVal imageSize As SizeF, ByVal PixelX As Integer, ByVal Pixely As Integer, ByVal theimage As Bitmap) As Color
    Dim pixels As New ArrayList
    Dim x As Integer, y As Integer
    Dim bmp As Bitmap = theimage.Clone
    For x = PixelX - CInt(Size.Width / 2) To PixelX + CInt(Size.Width / 2)
        For y = Pixely - CInt(Size.Height / 2) To Pixely + CInt(Size.Height / 2)
            If (x > 0 And x < imageSize.Width) And (y > 0 And y < imageSize.Height) Then
                pixels.Add(bmp.GetPixel(x, y))
            End If
        Next
    Next
    Dim thisColor As Color
    Dim alpha As Integer = 0
    Dim red As Integer = 0
    Dim green As Integer = 0
    Dim blue As Integer = 0

    For Each thisColor In pixels
        alpha += thisColor.A
        red += thisColor.R
        green += thisColor.G
        blue += thisColor.B
    Next

    Return Color.FromArgb(alpha / pixels.Count, red / pixels.Count, green / pixels.Count, blue / pixels.Count)
End Function


Private Function gausianBlur(ByVal alphaEdgesOnly As Boolean, ByVal blurSize As Size, ByVal theimage As Bitmap) As Bitmap
    Dim PixelY As Integer
    Dim PixelX As Integer
    Dim bmp As Bitmap = theimage.Clone

    For PixelY = 0 To bmp.Width - 1
        For PixelX = 0 To bmp.Height - 1
            If Not alphaEdgesOnly Then     ' Blur everything
                bmp.SetPixel(PixelX, PixelY, Average(blurSize, bmp.PhysicalDimension, PixelX, PixelY, theimage))
            ElseIf bmp.GetPixel(PixelX, PixelY).A <> 255 Then  ' Alpha blur channel check
                bmp.SetPixel(PixelX, PixelY, Average(blurSize, bmp.PhysicalDimension, PixelX, PixelY, theimage))
            End If

            Application.DoEvents()
        Next
    Next

    Return bmp.Clone
    bmp.Dispose()
End Function

End Class

alt text

我在这里找到了实现高斯模糊的代码:http://www.codeproject.com/KB/GDI-plus/GausianBlur.aspx

还有将窗体复制到位图的代码:http://www.daniweb.com/forums/thread94348.html


我找到了一种更快的模糊方式,我会查看此代码并将逐个像素转换替换为此链接中的矩阵版本:http://www.codeproject.com/KB/GDI-plus/csharpfilters.aspx - Stefan

0
   Imports System.Drawing
   Imports System.Drawing.Imaging
   Public Class Form1
    #Region "Declarations"
   Dim imagebitmap As Bitmap
   Dim graphicsvariable As Graphics
   Dim WithEvents v As New PictureBox
   Dim panel1 As Panel

  #End Region

  #Region "function for getting screenshot"
     Public Function getscreenshot()
      imagebitmap = New Bitmap(Me.Size.Width, Me.Size.Height) 'creates a new blank bitmap having size of the form'
       graphicsvariable = Graphics.FromImage(imagebitmap)      'creates a      picture template that enables the graphics variable to draw on'
                graphicsvariable.CopyFromScreen(Me.PointToScreen(Me.ClientRectangle.Location), New Point(0, 0), Me.ClientRectangle.Size)    'copies graphics that is on the screen to the imagebitmap'
        Return imagebitmap
       End Function
     #End Region

#Region "method for blurring"
Sub BlurBitmap(ByRef image As Bitmap, Optional ByVal BlurForce As Integer = 2)
    'We get a graphics object from the image'
      Dim g As Graphics = Graphics.FromImage(image)
    'declare an ImageAttributes to use it when drawing'
      Dim att As New ImageAttributes
    'declare a ColorMatrix'
      Dim m As New ColorMatrix
    ' set Matrix33 to 0.5, which represents the opacity. so the drawing will be semi-trasparent.'
      m.Matrix33 = 0.4
    'Setting this ColorMatrix to the ImageAttributes.'
      att.SetColorMatrix(m)
    'drawing the image on it self, but not in the same coordinates, in a way that every pixel will be drawn on the pixels arround it.'
      For x = -1 To BlurForce
          For y = -1 To BlurForce
            'Drawing image on it self using out ImageAttributes to draw it semi-transparent.'
              g.DrawImage(image, New Rectangle(x, y, image.Width, image.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, att)
          Next
      Next
    'disposing ImageAttributes and Graphics. the effect is then applied. '
      att.Dispose() 'dispose att'
      g.Dispose() 'dispose g'
    End Sub
  #End Region

 #Region "event that handles the removal of the blurred image when clicked"
    Private Sub v_Click(sender As Object, e As EventArgs) Handles v.Click
    Panel1.Controls.Remove(sender) 'removes the picturebox from the panel'
     Me.Controls.Remove(Panel1)    'removes the panel from the form'
  End Sub
#End Region


   Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
      panel1 = New Panel
      panel1.Size = New Size(763, 441)
      Me.Controls.Add(panel1)

     panel1.Controls.Add(v)  'add picturebox to panel1, pls also note that i made the panel cover the whole form'
     Dim b As Bitmap = getscreenshot() 'get the screen shot'
     BlurBitmap(b) 'blur the screen shot'
     v.Image = b 'set the picturebox image as b (the blurred image)'
     v.Dock = DockStyle.Fill
     v.BringToFront()
     v.Size = Me.Size
     panel1.BringToFront()
    End Sub
End Class

-1

我曾经使用一个模态窗体,其定位和大小完全与需要模糊的窗体相同。然后,在该窗体上将不透明度设置为50%。

概念证明(创建一个窗体,在窗体上放置一个按钮1,粘贴此代码):

Public Class Form1
Private BlurrForm As New Form
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    BlurrForm.StartPosition = FormStartPosition.Manual
    BlurrForm.Opacity = 0.5
    BlurrForm.Location = Me.Location
    BlurrForm.Size = Me.Size
    BlurrForm.Owner = Me
    BlurrForm.FormBorderStyle = Windows.Forms.FormBorderStyle.None
    AddHandler BlurrForm.Click, AddressOf BlurredFormClicked
    BlurrForm.Show(Me)
End Sub
Sub BlurredFormClicked(ByVal sender As System.Object, ByVal e As EventArgs)
    BlurrForm.Hide()
End Sub

End Class

当您按下button1按钮时,整个表单将变为灰色,并在鼠标按钮按下时消失。

好的,但这不会给我需要的微妙效果——控件不会变模糊:) 我想我需要尝试一下。但我相信肯定有更好的解决方案。 - ua.Skywalker
好的,这个不会使表格模糊,它会将表格变灰。但我还是把例子留在这里,也许有人会需要这种变体。 - Stefan

-1

我不确定模糊的效果,但你可以在前面放一个半透明的控件。

如果你真的想要它在整个窗口前面(包括标题栏等),这意味着需要放置一个半透明的表单并直接设置其大小,位于主表单上方。它需要是模态的(ShowDialog()),因为否则用户可能会在另一个表单后面“丢失”一个表单...

当然,如果你正在尝试同时在后面的窗口中执行任何操作(就像你的问题暗示的那样),这会带来问题,因为ShowDialog会阻止调用代码...

在这种情况下,你可以将表单设置为“TopMost” - 这是一种非常拙劣的方法,因为它可能会阻止用户与他 alt-tab 到的另一个应用程序交互,而该应用程序恰好位于你等待的表单下面。我不知道基本 winforms 中是否有更好的解决方案。也许有些东西你可以用较低级别的窗口处理方法去实现,但我对此了解甚少。


比较笨的方法是直接截取窗口,然后应用模糊效果,最后呈现结果图像。但问题是如何做这样的“截屏” :) - ua.Skywalker
@uaSkywalker,你可能会在这里找到一些有用的内容来截屏:http://stackoverflow.com/search?q=screenshot+c#。 - Benjol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接