【问题标题】:Aero: How to draw solid (opaque) colors on glass?Aero:如何在玻璃上绘制纯色(不透明)颜色?
【发布时间】:2010-11-23 16:26:52
【问题描述】:

使用GDI+绘制各种颜色:

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

您会注意到玻璃上没有正确显示不透明的颜色:

如何在玻璃上绘制纯色?


您还会注意到,完全不透明的颜色的处理方式会有所不同,具体取决于它是什么颜色:

  • 不透明黑色:完全透明
  • 不透明颜色:部分透明
  • 不透明的白色:完全不透明

谁能告诉我桌面合成器上解释如何处理不同颜色的文档?


更新 3

您还会注意到FillRectangle 的行为与FillEllipse 不同:

  • FillEllipse 用不透明颜色绘制不透明颜色
  • FillRectangle 使用不透明颜色绘制部分(或完全)透明

请解释无意义的行为。

更新 4

Alwayslearning 建议我更改合成模式。来自MSDN

CompositingMode 枚举

CompositingMode 枚举指定渲染颜色与背景颜色的组合方式。 Graphics 类的Graphics::GetCompositingMode'Graphics::SetCompositingMode' 方法使用此枚举。

CompositingModeSourceOver

指定当一种颜色被渲染时,它与背景颜色混合。混合由正在渲染的颜色的 alpha 分量决定。

CompositingModeSourceCopy

指定当一种颜色被渲染时,它会覆盖背景颜色。此模式不能与 TextRenderingHintClearTypeGridFit 一起使用。

CompositingModeSourceCopy 的描述来看,这听起来不是我想要的选项。从它施加的限制来看,这听起来像是我想要的选择。在禁用合成或透明度的情况下,它不是我想要的选项,因为它执行的是 SourceCopy,而不是 SourceBlend

幸运的是,这不是我必须考虑的邪恶,因为它不能解决我的实际问题。在构造我的graphics 对象后,我尝试更改合成模式:

graphics = new Graphics(hDC);
graphics.SetCompositingMode(CompositingModeSourceCopy); //CompositingModeSourceCopy = 1

结果对输出没有影响:

注意事项

  • Win32 原生
  • 不是 .NET(本机)
  • 不是 Winforms(本机)
  • GDI+(原生)

另见

【问题讨论】:

  • 需要更多的手绘圆圈。
  • 我一定遗漏了一些东西,因为我的答案是“使用 254 作为 alpha 值”。你真的需要额外的 1/256 不透明度吗?
  • 如果使用 ExtTextOut 而不是 FillRectangle 会发生什么?这是在 GDI 中绘制矩形的“正常”方式,不确定 GDI+。
  • @Tergiver 指的是ExtTextOut 已被用作性能提升。它与PatBlt(是?)一起比FillRect 快。它不需要创建和管理画笔。

标签: aero dwm aero-glass


【解决方案1】:

对我来说似乎工作正常。由于缺少完整的代码示例,我假设您的合成模式有误。

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

【讨论】:

  • 呃,我应该提到的另一件显而易见的事情......你是否将透明键/颜色设置为 #FF000000 ?
  • 没有TransparencyKey 属性。我认为您指的是 .NET WinForm 的 TransparencyKey 属性。
  • 至于合成,我使用CompositingModeSourceOver(默认):指定在渲染颜色时,它与背景颜色混合。混合由正在渲染的颜色的 alpha 分量决定。
  • 还值得注意的是,我不是在渲染位图,而是在设备上下文 (blogs.msdn.com/b/oldnewthing/archive/2006/01/03/508694.aspx) 上渲染。如果您在目标 DC 上渲染并删除多余的选项和后台缓冲区,我很想看看您的结果。
【解决方案2】:

我有一个类似的issue,但它涉及到在layered window 上绘图,而不是在 Aero 的玻璃上。我没有任何代码可以用来测试这是否能解决您的问题,但我认为值得一试,因为您的问题症状与我的相同。

正如您所注意到的,FillRectangle 似乎存在一些 qwerk,其行为与 FillEllipse 之间的差异显而易见。

这是我想出的两个变通办法,每个都解决了我的问题:

  • 致电FillRectangle两次

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

    由于同一区域被填充两次,因此无论玻璃颜色如何,它们都应混合并创建 RGB(255, 0, 0),这会导致 100% 不透明的形状。我不喜欢这种方法,因为它需要每个矩形绘制两次。

  • 使用 FillPolygon 代替

    就像FillEllipse 一样,FillPolygon 似乎没有颜色/不透明度问题,除非你这样称呼它

    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
    

    对我来说,上面的代码生成了 100% 透明的形状。我猜这要么是由于某种形式的优化,而是将调用传递给FillRectangle。或者 - 很可能 - FillPolygon 存在一些问题,由 FillRectangle 调用。无论如何,如果你在数组中添加一个额外的Point,你可以绕过它:

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

    上面的代码确实为我绘制了一个 100% 不透明的形状。我希望这也能解决您的问题。

【讨论】:

    【解决方案3】:

    改天,我的另一个解决方案。

    • 将您想要在玻璃上显示的所有内容绘制成位图。
    • 然后,用黑色清除表单背景。
    • 紧接着,在表单上绘制位图。

    但是(与任何其他不使用 DrawThemeTextEx 的解决方案一样): 文本渲染将无法正常工作,因为它总是将表单的背面颜色作为抗锯齿/cleartype 提示。改用 DrawThemeTextEx,它还支持后面带有发光效果的文本。

    【讨论】:

      【解决方案4】:

      我在使用 GDI 时遇到了同样的问题。
      GDI 使用零 alpha 通道值,因此最简单的解决方案是像以下代码一样修复 alpha 通道:

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

      【讨论】:

        【解决方案5】:

        我找到了另一种解决方法。使用LinearGradientBrush,两种颜色相同:

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

        这可能比 SolidBrush 慢,但工作正常。

        【讨论】:

          【解决方案6】:

          你想要一个愚蠢的解决方案吗?在这里你得到一个愚蠢的解决方案。至少它只是一行代码。并造成很小但可忽略的副作用。

          假设

          在绘制实心直角矩形时,GDI+ 倾向于通过以比绘制其他东西更快的方法绘制它们来加快速度。这种技术称为位块处理。这实际上非常聪明,因为它是在表面上绘制矩形的最快方法。但是,要绘制的矩形必须满足直角的规则。

          这种巧妙的优化是在 DWM、Aero、Glass 和所有新奇的东西出现之前完成的。

          在内部,bitblitting 只是将像素的 RGBA 颜色数据从一个内存区域复制到另一个内存区域(也就是说,从您在窗口上的绘图中说)。可悲的是,它写入的 RGB 格式与玻璃区域不兼容,导致您观察到奇怪的透明效果。

          解决方案

          所以这里有一个转折点。 GDI+ 可以尊重一个转换矩阵,每个绘图都可以使用该矩阵进行缩放、倾斜、旋转或其他任何操作。如果我们应用这样的矩阵,则不再保证矩形不再是直角的规则。因此,GDI+ 将停止对它们进行位传输,并以类似于椭圆的方式绘制它们。

          但我们也不想倾斜、缩放或旋转我们的绘图。我们只是应用可能的最小变换:我们创建一个变换矩阵,将每个绘图向下移动一个像素:

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

          现在,bitblitting 已关闭,矩形是实心的,紫罗兰色是蓝色的。如果有更简单的方法来控制它,尤其是不移动图纸的方法!

          也就是说,如果要在第一行像素上绘制,请使用 -1 作为 Y 坐标。

          您可以决定这是否真的适合您,或者直接忽略它。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-05-14
            • 1970-01-01
            • 2011-06-11
            • 2011-03-21
            • 2010-12-24
            • 1970-01-01
            • 2023-02-21
            • 2011-04-03
            相关资源
            最近更新 更多