简体   繁体   English

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

[英]Draw filled rectangle with Y opacity, on form with X opacity

I am using WinForms with C#. 我在C#中使用WinForms。 I have a form that its opacity is set to X . 我有一个形式 ,其不透明度设置为X。 I want to draw a filled rectangle that its opacity is Y . 我想绘制一个不透明度为Y的填充矩形

If I draw it like this: 如果我这样画:

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

then, the Y value I have for the opacity of the brush for the rectangle drawing is getting multiplied by the X opacity of my form, so instead I get X*Y . 然后,矩形绘图opacity of the brushopacity of the brushY值乘以我的表单的X不透明度,所以我得到X*Y

I think it has to do with e.Graphics , which comes from the form. 我认为这与来自表单的e.Graphics有关。 How can I go about this ? 我该怎么办?

You're not drawing to the screen, you're drawing to an underlying surface. 您不是在绘制到屏幕,而是在绘制到底层表面。 The controls and surfaces are hierarchical, so if you draw something with 100% opacity on something with 50%, the result will be 50% opacity. 控件和曲面是分层的,因此如果在不透明度为50%的对象上绘制不透明度为100%的对象,结果将为不透明度为50%。 This is simply how desktop composition works. 这就是桌面合成的工作原理。 Unlike, say, Photoshop, there's not 30 different composition modes you could exploit, it's all just "normal layers". 与Photoshop不同,没有30种不同的构图模式可供您利用,它们全都是“普通图层”。

The opacity of the form is not hidden in the Graphics element. 表单的不透明度未隐藏在Graphics元素中。 Instead, you're drawing your nice 100% opaque rectangle on a transparent surface; 而是在透明表面上绘制漂亮的100%不透明矩形。 the result is a 100% opaque rectangle on a transparent surface. 结果是透明表面上的100%不透明矩形。 But then this surface is drawn on a surface with 50% opacity - and opacity multiplies. 但是随后在不透明度为50%的表面上绘制表面-并且不透明度成倍增加。 To change this, you would have to change the way the whole form is rendered, not just your control. 要更改此设置,您将不得不更改整个表单的呈现方式,而不仅仅是控件。

The only way around this would be to use a more hacky way to make your form transparent - for example, forcing everything except your rectangle to be drawn with different opacity, including the form borders etc. This is obviously kind of hard - there's no .NET methods to help you do so, you'll have to work with WinAPI directly, override some window message handling etc. It's just way too much work. 解决此问题的唯一方法是使用一种更怪异的方法来使表单透明化-例如,强制以矩形绘制矩形以外的所有内容,包括表单边框等。这显然是很难的-没有。 NET方法可以帮助您做到这一点,您将必须直接使用WinAPI,覆盖某些窗口消息处理等。这只是太多的工作。

There are workarounds, of course. 当然,有解决方法。 For example, you could show your rectangle on a different, border-less form, that moves the same as the underlying form. 例如,您可以以不同的,无边界的形式显示矩形,其移动方式与基础形式相同。

You can use UpdateLayeredWindow() API to draw with per pixel alpha. 您可以使用UpdateLayeredWindow() API绘制每个像素的alpha。 First make your window without borders and add the WS_EX_LAYERED style: 首先,使您的窗口无边界,并添加WS_EX_LAYERED样式:

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: 您在32bpp位图上绘制所有图形,最后调用UpdateLayeredBitmap函数:

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 例如,我使用500x500表格, green具有两个不透明度值X = 50Y = 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: 这些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) 编辑(快速使用GDI)

I used QueryPerformanceCounter for the measurements: 我使用QueryPerformanceCounter进行测量:

[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+接近~12 msec (请参阅更新的Form1_MouseMove )。

GDI: 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 +〜12毫秒
GDI ~ 1.2 msec ten times faster! GDI〜1.2毫秒快十倍!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM