如何使用纯GDI实现颜色混合(根据指定的alpha值进行着色)画布区域?

9
我希望您能使用纯 Windows GDI(因此不使用GDI +、DirectX或类似工具,也不使用OpenGL、汇编语言或第三方库)来混合颜色(通过指定的alpha值着色)画布区域。 我已创建以下函数,并想知道是否有更有效或更简单的方法来实现此目标:
procedure ColorBlend(const ACanvas: HDC; const ARect: TRect;
  const ABlendColor: TColor; const ABlendValue: Integer);
var
  DC: HDC;
  Brush: HBRUSH;
  Bitmap: HBITMAP;
  BlendFunction: TBlendFunction;
begin
  DC := CreateCompatibleDC(ACanvas);
  Bitmap := CreateCompatibleBitmap(ACanvas, ARect.Right - ARect.Left,
    ARect.Bottom - ARect.Top);
  Brush := CreateSolidBrush(ColorToRGB(ABlendColor));
  try
    SelectObject(DC, Bitmap);
    Windows.FillRect(DC, Rect(0, 0, ARect.Right - ARect.Left,
      ARect.Bottom - ARect.Top), Brush);
    BlendFunction.BlendOp := AC_SRC_OVER;
    BlendFunction.BlendFlags := 0;
    BlendFunction.AlphaFormat := 0;
    BlendFunction.SourceConstantAlpha := ABlendValue;
    Windows.AlphaBlend(ACanvas, ARect.Left, ARect.Top,
      ARect.Right - ARect.Left, ARect.Bottom - ARect.Top, DC, 0, 0,
      ARect.Right - ARect.Left, ARect.Bottom - ARect.Top, BlendFunction);
  finally
    DeleteObject(Brush);
    DeleteObject(Bitmap);
    DeleteDC(DC);
  end;
end;

对于这个函数应该做什么的概念,请参见以下(有区别的 :-) 图像:

enter image description here enter image description here

以下是能够呈现this image在表单左上角的代码,如上所示:

uses
  PNGImage;

procedure TForm1.Button1Click(Sender: TObject);
var
  Image: TPNGImage;
begin
  Image := TPNGImage.Create;
  try
    Image.LoadFromFile('d:\6G3Eg.png');
    ColorBlend(Image.Canvas.Handle, Image.Canvas.ClipRect, $0000FF80, 175);
    Canvas.Draw(0, 0, Image);
  finally
    Image.Free;
  end;
end;

有没有使用纯GDI或Delphi VCL更高效的方法来完成这个任务?


你为什么认为你已经拥有的不够高效? - Remy Lebeau
1
AlphaBlend()是实现这一功能的简单方法 - 让操作系统来完成工作。否则,您将不得不直接操作PNG像素。您可以进一步进行的唯一优化是创建一次固定位图并反复重用它。 - Remy Lebeau
@Remy,我明白了,这就是为什么我想要专门使用GDI而不是其他任何东西。关于PNG,我实际上想做的只是给现有画布区域上色,与PNG无关。那是个糟糕的例子,我知道。也许您可以以一种没有更多改进的方式发布答案,那样我会很高兴的 :-) - TLama
1
既然你正在使用基于COM的Windows API消费者,为什么不使用Direct 2D呢?它更快(如果你的系统是Windows Vista/7)。这是值得一看的东西;) - johnathan
2
@TLama:这些文章可能会很有趣。 - Uli Gerhardt
显示剩余5条评论
1个回答

4
你尝试过使用AlphaBlend进行画布绘制吗?类似于

这样的内容


Canvas.Draw(Arect.Left, ARect.Top, ABitmap, AAlphaBlendValue);

与混合颜色的FillRect结合使用。 更新: 这里提供一些代码,尽可能接近您的界面,但纯VCL。 可能效率不高,但更简单(也有点可移植性)。 正如Remy所说,要以伪持久的方式在Form上绘制,必须使用OnPaint...
procedure ColorBlend(const ACanvas: TCanvas; const ARect: TRect;
  const ABlendColor: TColor; const ABlendValue: Integer);
var
  bmp: TBitmap;
begin
  bmp := TBitmap.Create;
  try
    bmp.Canvas.Brush.Color := ABlendColor;
    bmp.Width := ARect.Right - ARect.Left;
    bmp.Height := ARect.Bottom - ARect.Top;
    bmp.Canvas.FillRect(Rect(0,0,bmp.Width, bmp.Height));
    ACanvas.Draw(ARect.Left, ARect.Top, bmp, ABlendValue);
  finally
    bmp.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Image: TPNGImage;
begin
  Image := TPNGImage.Create;
  try
    Image.LoadFromFile('d:\6G3Eg.png');
    ColorBlend(Image.Canvas, Image.Canvas.ClipRect, $0000FF80, 175);
    Canvas.Draw(0, 0, Image);
    // then for fun do it to the Form itself
    ColorBlend(Canvas, ClientRect, clYellow, 15);
  finally
    Image.Free;
  end;
end;

+1,谢谢!我以前从未见过这种重载(我手头有Delphi 2009)。很高兴学到了新东西,但是我还需要应用着色效果。 - TLama
1
你可以创建一个TBitmap对象并将其设置为不透明的位图,然后在图像的Canvas上指定alpha值进行绘制,最后将完成的图像绘制到窗体上。顺便说一下,如果你想让最终的绘图持久化,你必须在窗体的OnPaint事件中绘制它,而不是在按钮的OnClick事件中。你可以在OnClick事件中准备好图像,然后Invalidate()窗体来触发重绘,在每次OnPaint事件触发时绘制当前图像。或者只需将最终图像放入一个TImage组件中即可。 - Remy Lebeau
弗朗索瓦,已接受,谢谢!顺便说一下,bmp.Canvas.FillRect(bmp.Canvas.ClipRect);也可能很方便;-) @Remy,感谢您提醒有关绘图持久性的问题,但这里不需要,因为此函数旨在用于VirtualTreeView的OnBeforeCellPaint,其中背景可能会被动画化(我忘了提到这一点,我的错)。 - TLama
1
@TLama,当然,但是Canvas.ClipRect涉及的内容比仅构建Rect要多一些。因此,我选择更多的打字...;-) - Francesca
不需要FillRect调用。当调整位图大小时,将使用画刷颜色作为背景颜色。 - Sertac Akyuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接