9

I'd like to color blend (colorize by specified alpha value) the area of a canvas using pure Windows GDI (so without GDI+, DirectX or similar, no OpenGL, no assembler or a 3rd party libraries).

I've created the following function and I'd like to know if there is a more efficient or easier way to do this:

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;

For the notion of what this function should do see the following (discriminating :-) images:

enter image description here enter image description here

And the code that can render this image to the top left side of the form in the way shown above:

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;

Is there a more efficient way to do this using pure GDI or Delphi VCL ?

TLama
  • 75,147
  • 17
  • 214
  • 392
  • Why do you think what you already have is not efficient? – Remy Lebeau May 10 '12 at 20:43
  • That's why I asked. I know it's efficient but I want more (if possible :-) – TLama May 10 '12 at 20:45
  • What more do you want from it? – Remy Lebeau May 10 '12 at 20:47
  • @Remy, an easier way with keeping efficiency or more efficient in cost of simplicity. If would be possible to leave a bitmap usage or use a memory DC or something. – TLama May 10 '12 at 20:54
  • 1
    `AlphaBlend()` **is** the simple way to do it - let the OS do the work. Otherwise you would have to manipulate the PNG pixels directly. About the only optimization you could make further would be to create the solid bitmap once and reuse it over and over. – Remy Lebeau May 10 '12 at 21:36
  • @Remy, I see, that's why I wanted to use exclusively GDI, not anything else. About the PNG, what I actually want to do is just to colorize the [`existing canvas`](http://stackoverflow.com/a/10537589/960757) area, it's not about PNG. It was a bad example, I know. Maybe you can just post the answer in a way there's nothing more to improve and I'll be glad :-) – TLama May 10 '12 at 21:49
  • 1
    Sense your using a com based consumer of windows api's anyways, why not use direct 2d? it's faster. (if your system is a windows vista/7). Something to look to if it is ;) – johnathan May 10 '12 at 23:46
  • @johnathon, thanks for the idea, but I've mentioned I would like to use just a pure, old style [`Windows GDI`](http://msdn.microsoft.com/en-us/library/dd145203%28v=vs.85%29.aspx) I meant, and nothing else, so even [`Direct2D`](http://msdn.microsoft.com/en-us/library/windows/desktop/dd370990%28v=vs.85%29.aspx) is not what am I looking for, but it's a good tip to learn in the future. – TLama May 11 '12 at 00:12
  • 2
    @TLama: [These articles](http://itinerantdeveloper.blogspot.de/search/label/alpha%20transparency) might be interesting. – Uli Gerhardt May 11 '12 at 06:28
  • @Ulrich, very nice reading, thanks! – TLama May 11 '12 at 07:01

1 Answers1

4

Have you tried the Canvas Drawing with AlphaBlend?

something like

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

combined with a FillRect for the blend color

Update: And here's some code, as close as possible to your interface, but pure VCL.
Might not be as efficient, but much simpler (and somewhat portable).
As Remy said, to paint on a Form in a pseudo persistent way, you'd have to use 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;
Francesca
  • 21,452
  • 4
  • 49
  • 90
  • +1, thanks! I've never seen this overload before (I have Delphi 2009 by hand). Nice to learn something new, however I need to apply the colorize effect as well. – TLama May 10 '12 at 20:59
  • 1
    You could create the solid bitmap as a `TBitmap` object and `Draw()` it with alpha specified onto your image's `Canvas`, then `Draw()` the finished image onto your Form. BTW, if you want the final drawing to be persistent, you have to draw it onto the form's from its `OnPaint` event, not in the button's `OnClick` event. You can prepare the image in the `OnClick` event and then `Invalidate()` the form to trigger a repaint, then draw the current image whenever the `OnPaint` event is fired. Or just put the final image into a `TImage` component. – Remy Lebeau May 10 '12 at 21:40
  • François, accepted, thanks! Btw. `bmp.Canvas.FillRect(bmp.Canvas.ClipRect);` might be also handy ;-) @Remy, thanks for the note about drawing persistency, but here it's not needed since this function was intended to be used in VirtualTreeView's [`OnBeforeCellPaint`](http://stackoverflow.com/a/10537589/960757) where the background might be animated for instance (I forgot to mention that, my bad). – TLama May 11 '12 at 21:01
  • 1
    @TLama, sure, but Canvas.ClipRect involves a bit more than just building the Rect. So I went for more typing instead... ;-) – Francesca May 11 '12 at 21:13
  • The `FillRect` call is not needed. The bitmap, when resized, will use the brush color as the background color. – Sertac Akyuz Sep 15 '13 at 22:08