tags:

views:

54

answers:

4

In WinForms, I’m looking for a way to achieve the functionality similar to that of JXLayer class in Java’s Swing. To be more specific. I’d like to blur entire content of a window, and paint something over it (a waiting circle, for example). Any ideas will be highly appreciated :)

A: 

I did that once with a modal form positioned and sized exactly as the form that would be blurred had. And then having the opacity to 50% on that form.

Proof of concept (create a form, and put a button1 on the form, paste this code):

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

When you press the button1, the whole form will be grayed out and it will go away when pressed by mouse button.

Stefan
Ok, but that won't give me a subtle effect I need -- controls won't be blurred :) I guess I need to try. But I'm sure there must be better solutions.
ua.Skywalker
Ok, this one does not blur the form, it gray out the form. But I leave the example here, maybe someone need that variant sometime.
Stefan
A: 

I'm not sure about blurring, but one thing you can do is have a semi-opaque control in front.

If you really want it in front of the whole window (including title bar etc), this means a semi-opaque form placed and sized directly about your main form. It would need to be modal (ShowDialog()), because otherwise the user can 'lose' one form behind the other...

This presents problems of course if you are trying to simultaneously do anything in the window behind (as your question implies), because ShowDialog blocks the calling code...

In this case, you can set the form to 'TopMost' - this is horribly kludgy because it could prevent the user interacting with another app that he alt-tabs to while waiting for yours, and which just happens to be under your waiting form. I'm not aware of a better solution with vanilla winforms. There may be something you can do with lower-level windows stuff, but I don't know much about that.

Benjol
Clumsy enough :) A better idea would be to make a "screenshot" of a window, apply blur effect, and then present the result as image. But the question rises -- how to make such a "screenshot" :)
ua.Skywalker
@uaSkywalker, you might find something helpful in this lot for screenshots: http://stackoverflow.com/search?q=screenshot+c%23.
Benjol
A: 

This VB.Net code will

  1. Create a picture of the form,
  2. Add Blur to the picture
  3. Add the picture to a picturebox
  4. Add a click-handler that removes the picture when clicked.
  5. Add the picturebox to the form and set as bringtofront

The only thing is that the blur is pretty slow. So I would work on that.

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

I found the code to do the GasuianBlur here: http://www.codeproject.com/KB/GDI-plus/GausianBlur.aspx

And the code to Copy a form to a bitmap here: http://www.daniweb.com/forums/thread94348.html

Stefan
I found a faster way to blur, I would look into this code and replace the pixelbypixel-transformation to the matrix version in this link: http://www.codeproject.com/KB/GDI-plus/csharpfilters.aspx
Stefan
+1  A: 

And here is my solution.

  1. Take screenshot.
  2. Blur it.
  3. Place the blurred picture in front of everything.

Given a form with Button button1 and Panel panel1 (with listbox and progress bar on it), the following code works pretty well:

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 Code for gaussian blurring is borrowed from here.

ua.Skywalker
Great! I like what a little bit brainstorming, google and free time can do. ;)
Stefan
Well, not sure about free time, but as for the rest -- certainly :)
ua.Skywalker
You can replace your big TakeSnapshot function with this 4 liner instead: 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
Great! This looks sexier :) Still have a habit to overuse p/invoke :)
ua.Skywalker