在具有X不透明度的窗体上绘制具有Y不透明度的填充矩形

时间:2015-04-24 07:19:34

标签: c# winforms

我在C#中使用WinForms。我有表单,其不透明度设置为 X 。我想绘制一个填充矩形,其不透明度为 Y

如果我这样画:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.FillRectangle(
        new SolidBrush(Color.FromArgb(Y, 255, 0, 0)),//Red brush (Y opacity)
        new Rectangle(100,100,500,500)
        );
}

然后,我对矩形绘图的Y的{​​{1}}值乘以我的表单的opacity of the brush不透明度,所以我得到X

我认为这与来自表单的X*Y有关。我怎么能这样做呢?

2 个答案:

答案 0 :(得分:1)

您没有在屏幕上绘图,而是在绘制基础曲面。控件和曲面是分层的,因此如果你在50%的东西上绘制100%不透明度的东西,结果将是50%的不透明度。这就是桌面组合的工作原理。与Photoshop不同的是,你可以利用30种不同的构图模式,它只是普通的图层"。

表单的不透明度未隐藏在Graphics元素中。相反,您要在透明表面上绘制漂亮的100%不透明矩形;结果是透明表面上的100%不透明矩形。但是这个表面绘制在具有50%不透明度的表面上 - 并且不透明度相乘。要更改此设置,您必须更改整个表单的呈现方式,而不仅仅是您的控件。

解决这个问题的唯一方法是使用更黑巧的方式使表单透明 - 例如,强制除之外的所有以不同的不透明度绘制,包括表单边框等。这显然有点难 - 没有.NET方法可以帮助你这样做,你必须直接使用WinAPI,覆盖一些窗口消息处理等等。这太过分了工作

当然有解决方法。例如,您可以在不同的无边框形式上显示矩形,其移动方式与底层窗体相同。

答案 1 :(得分:1)

You can use UpdateLayeredWindow() API to draw with per pixel alpha. First make your window without borders and add the WS_EX_LAYERED style:

protected override CreateParams CreateParams
{
    get
    {
        // Add the layered extended style (WS_EX_LAYERED) to this window
        CreateParams createParam = base.CreateParams;
        createParam.ExStyle = (createParam.ExStyle | WS_EX_LAYERED);
        return createParam;
    }
}

You do all your drawings on a 32bpp bitmap and finally call the UpdateLayeredBitmap function:

private void UpdateLayeredBitmap(Bitmap bitmap)
    {
    // Does this bitmap contain an alpha channel?
    if (bitmap.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
    {
        //"The bitmap must be 32bpp with alpha-channel."
    }

    // Get device contexts
    IntPtr screenDc = GetDC(IntPtr.Zero);
    IntPtr memDc = CreateCompatibleDC(screenDc);
    IntPtr hBitmap = IntPtr.Zero;
    IntPtr hOldBitmap = IntPtr.Zero;

    try
    {            
        // Get handle to the new bitmap and select it into the current device context
        hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
        hOldBitmap = SelectObject(memDc, hBitmap);

        newLocation = new POINT(this.Location.X, this.Location.Y);

        // Update the window
        UpdateLayeredWindow(Handle, IntPtr.Zero, ref newLocation, ref newSize, memDc, ref sourceLocation, 0, ref blend, ULW_ALPHA);
    }

    catch (Exception e)
    {
        Console.WriteLine("{0} Exception caught.", e);
    }

    if (hBitmap != IntPtr.Zero)
    {
        SelectObject(memDc, hOldBitmap);
        // Remove bitmap resources
        DeleteObject(hBitmap);
    }
    DeleteDC(memDc);
    DeleteDC(screenDc);
}

For an example I am using a 500x500 form, and the the color green with two opacity values X = 50 and Y = 180

Point pnt;
private POINT sourceLocation = new POINT (0, 0);
private SIZE newSize = new SIZE(500, 500); //Form size
private POINT newLocation;

BLENDFUNCTION blend;
public const int ULW_ALPHA = 2;
public const byte AC_SRC_OVER = 0;
public const byte AC_SRC_ALPHA = 1;
public const int WS_EX_LAYERED = 524288;
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HTCAPTION = 0x2;

public Form1()
{
    InitializeComponent();

    this.Location = new Point(200, 200);
    bmp = new Bitmap(500, 500, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

    //the form region
    Region greenRegionOpacityX = new Region(new Rectangle (0, 0, 500, 500));

    Graphics g;
    g = Graphics.FromImage(bmp);
    g.Clear(Color.Transparent);
    g.FillRegion(new SolidBrush(Color.FromArgb(50, 0, 255, 0)), greenRegionOpacityX);
    g.Dispose();
    greenRegionOpacityX.Dispose();

    blend = new BLENDFUNCTION();

    // Only works with a 32bpp bitmap
    blend.BlendOp = AC_SRC_OVER;
    // Always 0
    blend.BlendFlags = 0;
    // Set to 255 for per-pixel alpha values
    blend.SourceConstantAlpha = 255;
    // Only works when the bitmap contains an alpha channel
    blend.AlphaFormat = AC_SRC_ALPHA;

    UpdateLayeredBitmap(bmp);
}

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        pnt = new Point(e.X, e.Y);
    }
}

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    long start;
    long stop;
    long frequency;
    double elapsedTime;

    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        QueryPerformanceFrequency(out frequency);
        QueryPerformanceCounter(out start);

        //the form region
        Region greenRegionOpacityX = new Region(new Rectangle(0, 0, 500, 500));
        //we exclude the rectangle that we draw with the mouse
        greenRegionOpacityX.Exclude(new Rectangle(Math.Min(pnt.X, e.X), Math.Min(pnt.Y, e.Y),
                                                      Math.Abs(pnt.X - e.X), Math.Abs(pnt.Y - e.Y)));
        //the rectangle that we draw with the mouse
        Region greenRegionOpacityY = new Region(new Rectangle(Math.Min(pnt.X, e.X), Math.Min(pnt.Y, e.Y),
                                                                  Math.Abs(pnt.X - e.X), Math.Abs(pnt.Y - e.Y)));

        Graphics g;
        g = Graphics.FromImage(bmp);
        g.Clear(Color.Transparent); //we always clear the bitmap

        //draw the two regions
        g.FillRegion(new SolidBrush(Color.FromArgb(50, 0, 255, 0)), greenRegionOpacityX);
        g.FillRegion(new SolidBrush(Color.FromArgb(180, 0, 255, 0)), greenRegionOpacityY);

        g.Dispose();
        greenRegionOpacityX.Dispose();
        greenRegionOpacityY.Dispose();

        //upadate the layered window
        UpdateLayeredBitmap(bmp);

       QueryPerformanceCounter(out stop);

       elapsedTime = ((double)(stop - start) * 1000000.0d) / (double)frequency;
       Console.WriteLine("{0} μsec", elapsedTime);
       //In my pc it is ~12msec
    }
}

And these API:

[DllImport("user32.dll")]
static extern IntPtr GetDC(IntPtr hWnd);

[DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC", SetLastError=true)]
static extern IntPtr CreateCompatibleDC([In] IntPtr hdc);

[DllImport("gdi32.dll", EntryPoint = "SelectObject")]
static extern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr hgdiobj);

[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
    ref POINT pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pptSrc, uint crKey, 
        [In] ref BLENDFUNCTION pblend, uint dwFlags);

[DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
static extern bool DeleteDC([In] IntPtr hdc);

[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteObject([In] IntPtr hObject);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct SIZE
{
    public int cx;
    public int cy;

    public SIZE(int cx, int cy)
    {
        this.cx = cx;
        this.cy = cy;
    }
}

public struct BLENDFUNCTION
{
    public byte BlendOp;

    public byte BlendFlags;

    public byte SourceConstantAlpha;

    public byte AlphaFormat;
}

EDIT (Fast with GDI)

I used QueryPerformanceCounter for the measurements:

[DllImport("KERNEL32")]
private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

[DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceFrequency(out long lpFrequency);

long start;
long stop;
long frequency;
double elapsedTime;

QueryPerformanceFrequency(out frequency);
QueryPerformanceCounter(out start);

//code for time measurement

QueryPerformanceCounter(out stop);

elapsedTime = ((double)(stop - start) * 1000000.0d) / (double)frequency;
Console.WriteLine("{0} μsec", elapsedTime); //in micro seconds!

The GDI+ approach ~12 msec (see the updated Form1_MouseMove).

GDI:

[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern int BitBlt(
      IntPtr hdcDest,     // handle to destination DC (device context)
      int nXDest,         // x-coord of destination upper-left corner
      int nYDest,         // y-coord of destination upper-left corner
      int nWidth,         // width of destination rectangle
      int nHeight,        // height of destination rectangle
      IntPtr hdcSrc,      // handle to source DC
      int nXSrc,          // x-coordinate of source upper-left corner
      int nYSrc,          // y-coordinate of source upper-left corner
      int dwRop  // raster operation code
      );

public const int SRCCOPY = 0x00CC0020;

IntPtr hdcMemTransparent = IntPtr.Zero, hdcMemGreenOpacityX = IntPtr.Zero, 
       hdcMemGreenOpacityY = IntPtr.Zero, hdcMemForm = IntPtr.Zero;
IntPtr hBitmapTransparent = IntPtr.Zero, hBitmapGreenOpacityX = IntPtr.Zero,
       hBitmapGreenOpacityY = IntPtr.Zero, hBitmapForm = IntPtr.Zero;
IntPtr hBitmapTransparentOld = IntPtr.Zero, hBitmapGreenOpacityXOld = IntPtr.Zero, 
       hBitmapGreenOpacityYOld = IntPtr.Zero, hBitmapFormOld = IntPtr.Zero;

Bitmap bitmapTransparent, bitmapGreenOpacityX, bitmapGreenOpacityY, bitmapForm;

public Form1()
{
    InitializeComponent();

    this.Location = new Point(200, 200);

    bitmapForm = new Bitmap(500, 500, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    bitmapTransparent = new Bitmap(500, 500, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    bitmapGreenOpacityX = new Bitmap(500, 500, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    bitmapGreenOpacityY = new Bitmap(500, 500, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

    Graphics g;

    g = Graphics.FromImage(bitmapTransparent);
    g.Clear(Color.Transparent);
    g.Dispose();

    g = Graphics.FromImage(bitmapGreenOpacityX);
    g.Clear(Color.FromArgb(50, 0, 255, 0));
    g.Dispose();

    g = Graphics.FromImage(bitmapGreenOpacityY);
    g.Clear(Color.FromArgb(180, 0, 255, 0));
    g.Dispose();

    //Create hdc's
    IntPtr screenDc = GetDC(IntPtr.Zero);
    hdcMemForm = CreateCompatibleDC(screenDc);
    hdcMemTransparent = CreateCompatibleDC(screenDc);
    hdcMemGreenOpacityX = CreateCompatibleDC(screenDc);
    hdcMemGreenOpacityY = CreateCompatibleDC(screenDc);

    hBitmapForm = bitmapForm.GetHbitmap(Color.FromArgb(0));
    hBitmapFormOld = SelectObject(hdcMemForm, hBitmapForm);

    hBitmapTransparent = bitmapTransparent.GetHbitmap(Color.FromArgb(0));
    hBitmapTransparentOld = SelectObject(hdcMemTransparent, hBitmapTransparent);

    hBitmapGreenOpacityX = bitmapGreenOpacityX.GetHbitmap(Color.FromArgb(0));
    hBitmapGreenOpacityXOld = SelectObject(hdcMemGreenOpacityX, hBitmapGreenOpacityX);

    hBitmapGreenOpacityY = bitmapGreenOpacityY.GetHbitmap(Color.FromArgb(0));
    hBitmapGreenOpacityYOld = SelectObject(hdcMemGreenOpacityY, hBitmapGreenOpacityY);

    DeleteDC(screenDc);

    //copy hdcMemGreenOpacityX to hdcMemForm
    BitBlt(hdcMemForm, 0, 0, 500, 500, hdcMemGreenOpacityX, 0, 0, SRCCOPY);

    blend = new BLENDFUNCTION();

    // Only works with a 32bpp bitmap
    blend.BlendOp = AC_SRC_OVER;
    // Always 0
    blend.BlendFlags = 0;
    // Set to 255 for per-pixel alpha values
    blend.SourceConstantAlpha = 255;
    // Only works when the bitmap contains an alpha channel
    blend.AlphaFormat = AC_SRC_ALPHA;

    newLocation = new POINT(this.Location.X, this.Location.Y);

    //Update the window
    UpdateLayeredWindow(Handle, IntPtr.Zero, ref newLocation, ref newSize, hdcMemForm, ref sourceLocation,
             0, ref blend, ULW_ALPHA);
}

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    long start;
    long stop;
    long frequency;
     double elapsedTime;

    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        QueryPerformanceFrequency(out frequency);
        QueryPerformanceCounter(out start);

        //copy hdcMemGreenOpacityX to hdcMemForm
        BitBlt(hdcMemForm, 0, 0, 500, 500, hdcMemGreenOpacityX, 0, 0, SRCCOPY);

        //clear the rectangle that we draw with mouse with transparent color
        BitBlt(hdcMemForm, Math.Min(pnt.X, e.X), Math.Min(pnt.Y, e.Y),
                   Math.Abs(pnt.X - e.X), Math.Abs(pnt.Y - e.Y), hdcMemTransparent, 0, 0, SRCCOPY);

        //copy hdcMemGreenOpacityY to hdcMemForm
        BitBlt(hdcMemForm, Math.Min(pnt.X, e.X), Math.Min(pnt.Y, e.Y),
                   Math.Abs(pnt.X - e.X), Math.Abs(pnt.Y - e.Y), hdcMemGreenOpacityY, 0, 0, SRCCOPY);

        newLocation = new POINT(this.Location.X, this.Location.Y);

        //Update the window
        UpdateLayeredWindow(Handle, IntPtr.Zero, ref newLocation, ref newSize, hdcMemForm, ref sourceLocation,
             0, ref blend, ULW_ALPHA);

        QueryPerformanceCounter(out stop);

        elapsedTime = ((double)(stop - start) * 1000000.0d) / (double)frequency;
        Console.WriteLine("{0} μsec", elapsedTime);

        //GDI ~ 1.2 msec!!
    }
}

Release resources:

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if (hBitmapForm != IntPtr.Zero)
    {
        SelectObject(hdcMemForm, hBitmapFormOld);
        DeleteObject(hBitmapForm);
        DeleteDC(hdcMemForm);
        hdcMemForm = IntPtr.Zero;
        hBitmapForm = IntPtr.Zero;
    }

    if (hBitmapTransparent != IntPtr.Zero)
    {
        SelectObject(hdcMemTransparent, hBitmapTransparentOld);
        DeleteObject(hBitmapTransparent);
        DeleteDC(hdcMemTransparent);
        hdcMemTransparent = IntPtr.Zero;
        hBitmapTransparent = IntPtr.Zero;
    }

    if (hBitmapGreenOpacityX != IntPtr.Zero)
    {
        SelectObject(hdcMemGreenOpacityX, hBitmapGreenOpacityXOld);
        DeleteObject(hBitmapGreenOpacityX);
        DeleteDC(hdcMemGreenOpacityX);
        hdcMemGreenOpacityX = IntPtr.Zero;
        hBitmapGreenOpacityX = IntPtr.Zero;
    }

    if (hBitmapGreenOpacityY != IntPtr.Zero)
    {
        SelectObject(hdcMemGreenOpacityY, hBitmapGreenOpacityYOld);
        DeleteObject(hBitmapGreenOpacityY);
        DeleteDC(hdcMemGreenOpacityY);
        hdcMemGreenOpacityY = IntPtr.Zero;
        hBitmapGreenOpacityY = IntPtr.Zero;
    }

    if (bitmapForm != null)
    {
        bitmapForm.Dispose();
        bitmapForm = null;
    }

    if (bitmapTransparent != null)
    {
        bitmapTransparent.Dispose();
        bitmapTransparent = null;
    }

    if (bitmapGreenOpacityX != null)
    {
        bitmapGreenOpacityX.Dispose();
        bitmapGreenOpacityX = null;
    }

    if (bitmapGreenOpacityY != null)
    {
        bitmapGreenOpacityY.Dispose();
        bitmapGreenOpacityY = null;
    }

}

Final results:

GDI+ ~ 12 msec
GDI ~ 1.2 msec ten times faster!