Aero: How to draw solid (opaque) colors on glass?

断了今生、忘了曾经 提交于 2019-11-27 03:50:23

Seems to work OK for me. With the lack of a full code example I'm assuming you've got your compositing mode wrong.

public void RenderGdiPlus()
{
    List<string> colors = new List<string>(new string[] { "000000", "ff0000", "00ff00", "0000ff", "ffffff" });
    List<string> alphas = new List<string>(new string[] { "00", "01", "40", "80", "c0", "fe", "ff" });
    Bitmap bmp = new Bitmap(200, 300, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

    Graphics graphics = Graphics.FromImage(bmp);
    graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
    graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
    graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;

    graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
    graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
    SolidBrush backBrush = new SolidBrush(Color.FromArgb(254, 131, 208, 129));
    graphics.FillRectangle(backBrush, 0, 0, 300, 300);

    graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
    Pen pen = new Pen(Color.Gray);
    for (int row = 0; row < alphas.Count; row++)
    {
        string alpha = alphas[row];
        for (int column=0; column<colors.Count; column++)
        {
            string color = "#" + alpha + colors[column];
            SolidBrush brush = new SolidBrush(ColorTranslator.FromHtml(color));
            graphics.DrawRectangle(pen, 40*column, 40*row, 32, 32);
            graphics.FillRectangle(brush, 1+40*column, 1+40*row, 31, 31);
        }
    }

    Graphics gr2 = Graphics.FromHwnd(this.Handle);
    gr2.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
    gr2.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
    gr2.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
    gr2.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
    gr2.DrawImage(bmp, 0, 0);
}
Spooky

I had a similar issue, but it involved drawing onto a layered window, rather than on Aero's glass. I haven't got any code with which I can test whether this solves your problem, but I figured it's worth a shot, since the symptoms of your problem are the same as mine.

As you have noticed, there seems to be some qwerks with FillRectangle, apparent by the differences between its behaviour and FillEllipse's.

Here are two work-arounds that I came up with, which each solve my issue:

  • Call FillRectangle twice

    SolidBrush b(Color(254, 255, 0, 0));
    gfx.FillRectangle(&b, Rect(0, 0, width, height));
    gfx.FillRectangle(&b, Rect(0, 0, width, height));
    

    Since the same area is being filled twice, they should blend and create RGB(255, 0, 0) regardless of the glass colour, which leads to a result of a 100% opaque shape. I do not prefer this method, as it requires every rectangle to be drawn twice.

  • Use FillPolygon instead

    Just as with FillEllipse, FillPolygon doesn't seem to have the colour/opacity issue, unless you call it like so:

    SolidBrush b(Color(255, 255, 0, 0));
    Point points[4];
    points[0] = Point(0, 0);
    points[1] = Point(width, 0);
    points[2] = Point(width, height);
    points[4] = Point(0, height);
    gfx.FillPolygon(&b, points, 4); //don't copy and paste - this won't work
    

    For me, the above code resulted in a 100% transparent shape. I am guessing that this is either due to some form of optimisation that passes the call to FillRectangle instead. Or - most likely - there is some problem with FillPolygon, which is called by FillRectangle. Regardless, if you add an extra Point to the array, you can get around it:

    SolidBrush b(Color(255, 255, 0, 0));
    Point points[5];
    points[0] = Point(0, 0);
    points[1] = Point(0, 0); //<-
    points[2] = Point(width, 0);
    points[3] = Point(width, height);
    points[4] = Point(0, height);
    gfx.FillPolygon(&b, points, 5);
    

    The above code indeed draws a 100% opaque shape for me. I hope this also resolves your issue.

Another day, another solution by me.

  • Draw everything you want to appear on glass into a bitmap.
  • Then, clear the form background with black color.
  • Immediately after this, draw the bitmap on your form.

However (as with any other solution not using DrawThemeTextEx): Text rendering will not work correctly, because it always takes the back color of your form as an antialias/cleartype hint. Use DrawThemeTextEx instead, which also supports text with a glow effect behind.

I met the same issue with GDI.
GDI uses zero alpha channel value, so the simpliest solution is to fix alpha channel like this code does:

void fix_alpha_channel()
{
    std::vector<COLORREF> pixels(cx * cy);

    BITMAPINFOHEADER bmpInfo = {0};
    bmpInfo.biSize = sizeof(bmpInfo);
    bmpInfo.biWidth = cx;
    bmpInfo.biHeight = -int(cy);
    bmpInfo.biPlanes = 1;
    bmpInfo.biBitCount = 32;
    bmpInfo.biCompression = BI_RGB;

    GetDIBits(memDc, hBmp, 0, cy, &pixels[0], (LPBITMAPINFO)&bmpInfo, DIB_RGB_COLORS);

    std::for_each(pixels.begin(), pixels.end(), [](COLORREF& pixel){
        if(pixel != 0) // black pixels stay transparent
            pixel |= 0xFF000000; // set alpha channel to 100%
    });

    SetDIBits(memDc, hBmp, 0, cy, &pixels[0], (LPBITMAPINFO)&bmpInfo, DIB_RGB_COLORS);
}

I've found another way around it. Use LinearGradientBrush with both colors the same:

LinearGradientBrush brush(Point(0,0), Point(0,0), Color(255,231,45,56), Color(255,231,45,56));
g.FillRectangle(&brush, 25, 25, 30, 30);

This is perhaps slower than SolidBrush, but works fine.

Do you want a stupid solution? Here you get a stupid solution. At least it's just one line of code. And causing a small but ignorable side effect.

Assumption

When drawing solid, right angle rectangles, GDI+ tends to speed things up by drawing them in a faster method than drawing other stuff. This technique is called bitbliting. That is actually pretty clever since it is the fastest way to draw rectangles on a surface. However, the rectangles to be drawn must fulfill the rule that they are right angled.

This clever optimization was done before there was DWM, Aero, Glass and all the new fancy stuff.

Internally, bitblitting just copies the RGBA color data of pixels from one memory area to another (so to say from your drawing on your window). Sadly enough, the RGB format it writes is incompatible with glass areas, resulting in the weird transparency effects you observed.

Solution

So here comes a twist. GDI+ can respect a transformation matrix, with which every drawing can be scaled, skewed, rotated or whatever. If we apply such a matrix, the rule that rectangles are right angled anymore is not guaranteed anymore. So, GDI+ will stop bitblitting these and draw them in a fashion similar to the ellipses.

But we also don't want to skew, scale or rotate our drawing. We simply apply the smallest transformation possible: We create a transformation matrix which moves every drawing down one pixel:

// If you don't get that matrix instance, ignore it, it's just boring math
e.Graphics.Transform = new Matrix(1f, 0.001f, 0f, 1f, 0f, 0f);

Now, bitblitting is off, rectangles are solid, violets are blue. If there would be just an easier way to control that, especially one not moving the drawings!

Thus said, if you want to draw on the first pixel row, use -1 as a Y coordinate.

You can decide if this really is a solution for you, or just ignore it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!