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

前端 未结 6 796
孤独总比滥情好
孤独总比滥情好 2020-11-30 03:02

Using GDI+ to draw various colors:

brush = new SolidBrush(color);
graphics.FillRectangle(brush, x, y, width, height);

You\'ll notice that n

相关标签:
6条回答
  • 2020-11-30 03:42

    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);
    }
    
    0 讨论(0)
  • 2020-11-30 03:52

    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.

    0 讨论(0)
  • 2020-11-30 03:56

    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.

    0 讨论(0)
  • 2020-11-30 03:57

    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.

    0 讨论(0)
  • 2020-11-30 04:00

    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.

    0 讨论(0)
  • 2020-11-30 04:02

    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);
    }
    
    0 讨论(0)
提交回复
热议问题