1

I have TImage with a preloaded Bitmap (by PNGImage unit) with an Alpha Channel:

enter image description here

The subject is the Great Green Dino. I wanted to be able to change its Alpha Level in runtime, to any value in the range. Like 127 and he would look like this:

enter image description here

Following the answer to another similar question I almost felt in the skin it would work. But that was the result to Alpha Level 0 for example:

enter image description here

So, my question. Could someone know how to improve the answer's routine? Or know another way to achieve the second picture result? Note that I want to be able change this Alpha Level property in runtime be with a Method or any other way you know

Thank you in advance...

Community
  • 1
  • 1
Guill
  • 350
  • 5
  • 17
  • 2
    What you need to do is to keep each image in a separate bitmap. Think of these bitmaps as layers. Then compose them with appropriate alpha levels into a flattened image that is drawn on the screen. I see no use for `TImage` here. – David Heffernan Feb 07 '14 at 22:26
  • How can I show a Bitmap on the screen whitout `TImage` or derived component? (except by `Form.Canvas.Draw`) What do you mean with "compose them"? – Guill Feb 07 '14 at 22:33
  • The exact way I told you in my last answer. The answer that you did not like. – David Heffernan Feb 07 '14 at 22:35
  • @David, see... It is imprevisible what my user will want to do with images in this form. To handle messages to paint them all at a time, in my case would never fit. If you mean to handle a message that would help me to control each single bitmap's transparency then, go on. – Guill Feb 07 '14 at 22:41
  • You don't seem to want to be helped. You've decided that you know how best to solve your problem. In which case, since you already know the answer, there's no point in me trying to help. – David Heffernan Feb 07 '14 at 22:42
  • Compose bitmaps in memory then. What's the problem? – Free Consulting Feb 07 '14 at 22:44
  • @David I don't know how to solve, sir. But I do know it is not the way you told before. If you want to go deep to the current question and help me to see how it could fit, forget the earlier one, I would be very thankful. – Guill Feb 07 '14 at 22:46
  • 1
    Well, I think I've been right all along. But I'm not going to spend significant effort here because you keep telling me that you know better. Maybe you do, but from that starting point, it's not likely to be very fruitful is it. – David Heffernan Feb 07 '14 at 22:54

2 Answers2

5

Using AlphaBlend,

var
  Png: TPngImage;
  Bmp: TBitmap;
  BlendFn: TBlendFunction;
begin

  // suppose you already have a master png
  Png := TPngImage.Create;
  Png.LoadFromFile(
      ExtractFilePath(Application.ExeName) + '\..\..\Attention_128.png');

  // construct a temporary bitmap with the image
  Bmp := TBitmap.Create;
  Bmp.Assign(Png);

  // prepare TImage for accepting a partial transparent image
  Image1.Picture.Bitmap.PixelFormat := pf32bit;
  Image1.Picture.Bitmap.AlphaFormat := afPremultiplied;
  Image1.Picture.Bitmap.Canvas.Brush.Color := clBlack;
  Image1.Picture.Bitmap.SetSize(Png.Width, Png.Height);

  // alpha blend the temporary bitmap to the bitmap of the image
  BlendFn.BlendOp := AC_SRC_OVER;
  BlendFn.BlendFlags := 0;
  BlendFn.SourceConstantAlpha := 128;  // set opacity here
  BlendFn.AlphaFormat := AC_SRC_ALPHA;

  winapi.windows.AlphaBlend(Image1.Picture.Bitmap.Canvas.Handle,
    0, 0, Image1.Picture.Bitmap.Width, Image1.Picture.Bitmap.Height,
    Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, BlendFn);

  // free temporary bitmap, etc.
  ..


Commented a little, the above code produces the below image here (below image is the 'Image1'): enter image description here

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • I'm bound to a `TImage`. How can I extract a `TPNGImage` form it? Just casting `TPNGImage(Image.Picture.Graphic)` give me a lot of exceptions. There is a way? – Guill Feb 08 '14 at 16:33
  • @Guill It's hard to help when you present artificial constraints. Clearly you are not bound to `TImage`. – David Heffernan Feb 08 '14 at 19:14
  • @David This is not the only thing I'm doing with the images. To be transparent is not the only feature my images need to have. `TImage` is the shorter way to achieve them all. Of course if I found something virtually impossible to be done this way, I will be forced to change my way again. But if my specifications bothers you. I will understand if don't want to help me. – Guill Feb 08 '14 at 19:45
  • `TImage` is good for static images. It's not ideal for more complex composition. I would like to help you but you don't want to be helped. – David Heffernan Feb 08 '14 at 19:58
  • Now you are starting to explain yourself. Feel like it is going somewhere. So, what would you suggest for a complex manipulation of one bitmap? – Guill Feb 08 '14 at 20:11
  • @Guill - That *is* the way. If, for instance, `SomePng.Assign(TPngImage(Image.Picture.Graphic))` is not working, the only explanation is that what you have in your image graphic is *not* a png. You can find out what it is by asking it, e.g. `Image.Picture.Graphic.ClassName` – Sertac Akyuz Feb 09 '14 at 01:33
  • 1
    @Sertac, wouldn't be easier to just call `Image1.Picture.Bitmap.Canvas.Draw(0, 0, Bmp, 128)` ? Guill, `TImage` is for static images. You should rather use `TPaintBox`. – TLama Feb 09 '14 at 01:40
  • @TLama - It would if it worked. I don't see anything on the image when I test it. *Edit* Yes it works if it substitutes only the AlphaBlend part, actually 'TBitmap.DrawTransparent' is the party that calls 'AlphaBlend' then. Yes, it's easier. – Sertac Akyuz Feb 09 '14 at 01:47
  • @Sertac, sorry, I should have been more specific. Yes, I meant to use that single line instead of the `AlphaBlend` function. – TLama Feb 09 '14 at 01:52
  • @Guill Don't manipulate the bitmap. Keep it in its original form. Blend it with the other bitmaps when you need to paint it. Use a paintbox rather than an image. I already said this but you told me that you must use TImage and that my way cannot work. – David Heffernan Feb 09 '14 at 07:57
  • @Sertac, in a new project with a clean form and implementation, the routine works. But when teaming up with all my code, I'm getting "Out of System Resources" (I'm using threads) exception. I must haven't made something free. But I can even see the image transparent. Thank you. – Guill Feb 09 '14 at 20:24
  • @David I get confudes when you say "blend it" how can I manipulate single images if they are blended (In a way I have not gotten yet)? The only way to update images information using a Canvas like on TPaintBox would be cleaning it at every screen update. I am working with Game stuff. Updating images is the base of my project. I wish that `Paint` methods can fit better. Give me a direction, please. – Guill Feb 09 '14 at 20:33
  • You have not told us what you are actually trying to achieve. All you have really told us is that you can't use standard painting techniques, and that you are "bound to `TImage`". How can we give you advice if you decline to tell us what you are trying to achieve, won't listen to anything we tell you, and have a rigid pre-conceived believe that your solution is correct, in spite of you clearly being lacking in experience and knowledge. If you really want help you should change your attitude completely. Explain your goals, ask for help, and listen to the advice you are given. – David Heffernan Feb 09 '14 at 20:36
  • Well I am not very expressive in english. But I think I could have talked about gaming. And again, I was not like despiting your ways. I just thought it would not fit. I thought that based on features of my project, not just because I'm not kind of self-assured or something. I know I need to explain better before asking; You can't say I do not ask for help. And say that I "would not listen" is far from actual. I listen as I comment back. – Guill Feb 09 '14 at 21:05
  • @Guill - Well, that's one of the consequences of not having a test case, we're only trying to understand and provide solutions to what you try to describe.. Anyway, you're welcome. – Sertac Akyuz Feb 09 '14 at 21:11
  • Any idea how fast is this? Do you think that two scree-size images be merged at a decent speed (10-20fps)? – Gabriel Nov 19 '20 at 08:30
  • @Int - I don't know though I'd expect that it doesn't have a definitive answer but depends on a lot of environmental variables, like being able to be hardware accelerated depending on the graphics card and driver... – Sertac Akyuz Nov 19 '20 at 18:38
4

The other question involved using TBitmap to apply alpha blending to GIF images. TPNGImage has its own native alpha support, so you don't need to involve TBitmap. Look at the TPNGImage.CreateAlpha() method and the TPNGImage.AlphaScanline property.

Try something like this:

procedure SetPNGAlpha(PNG: TPNGImage; Alpha: Byte);
var
  pScanline: pByteArray;
  nScanLineCount, nPixelCount : Integer;
begin
  if Alpha = 255 then begin
    PNG.RemoveTransparency;
  end else
  begin
    PNG.CreateAlpha;

    for nScanLineCount := 0 to PNG.Height - 1 do
    begin
      pScanline := PNG.AlphaScanline[nScanLineCount];
      for nPixelCount := 0 to Image.Width - 1 do
        pScanline[nPixelCount] := Alpha;
    end;
  end;

  PNG.Modified := True;
end;

procedure SetBMPAlpha(BMP: TBitmap; Alpha: Byte);
type
  pRGBQuadArray = ^TRGBQuadArray;
  TRGBQuadArray = ARRAY [0 .. 0] OF TRGBQuad;
var
  pScanLine32_src, pScanLine32_dst: pRGBQuadArray;
  nScanLineCount, nPixelCount : Integer;
  Tmp: TBitmap;
begin
  BMP.PixelFormat := pf32Bit;

  Tmp := TBitmap.Create;
  try
    Tmp.SetSize(BMP.Width, BMP.Height);
    Tmp.AlphaFormat := afDefined;

    for nScanLineCount := 0 to BMP.Height - 1 do
    begin
      pScanLine32_src := BMP.ScanLine[nScanLineCount];
      pScanLine32_dst := Tmp.Scanline[nScanLineCount];
      for nPixelCount := 0 to BMP.Width - 1 do
      begin
        pScanLine32_dst[nPixelCount].rgbReserved := Alpha;
        pScanLine32_dst[nPixelCount].rgbBlue := pScanLine32_src[nPixelCount].rgbBlue;
        pScanLine32_dst[nPixelCount].rgbRed  := pScanLine32_src[nPixelCount].rgbRed;
        pScanLine32_dst[nPixelCount].rgbGreen:= pScanLine32_src[nPixelCount].rgbGreen;
      end;
    end;

    BMP.Assign(Tmp);
  finally
    Tmp.Free;
  end;
end;

procedure SetImageAlpha(Image: TImage; Alpha: Byte);
var
  Tmp: TBitmap;
begin
  if Image.Picture.Graphic is TPNGImage then
    SetPNGAlpha(TPNGImage(Image.Picture.Graphic), Alpha)

  else if (not Assigned(Image.Picture.Graphic)) or (Image.Picture.Graphic is TBitmap) then
    SetBMPAlpha(Image.Picture.Bitmap, Alpha)

  else
  begin
    Tmp := TBitmap.Create;
    try
      Tmp.Assign(Image.Picture.Graphic);
      SetBMPAlpha(Tmp, Alpha);
      Image.Picture.Assign(Tmp);
    finally
      Tmp.Free;
    end;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 2
    Wouldn't you use `AlphaBlend`? – David Heffernan Feb 07 '14 at 22:36
  • 1
    @DavidHeffernan: are you referring to `TForm.AlphaBlend` property, or the Win32 API `AlphaBlend()` function? There is no `AlphaBlend` in `TPNGImage` or `TBitmap`. – Remy Lebeau Feb 07 '14 at 22:38
  • The Win32 `AlphaBlend()` function – David Heffernan Feb 07 '14 at 22:38
  • 1
    @DavidHerrernan: No, you would not use the `AlphaBlend()` function directly. It is for drawing one `HDC` to another `HDC`. `TBitmap` supports alpha values for 32bit bitmap pixels and uses those values when rendering itself onto a Canvas, such as in a `TImage`. `TBitmap.Draw()` ues `AlphaBlend()` internally. `TPNGImage.Draw()` handles its own alpha-blended drawing as well. – Remy Lebeau Feb 07 '14 at 22:43
  • @FreeConsulting: `TBitmap` supports the `Opacity` parameter of `TCanvas.Draw()`, but `TPNGImage` does not. – Remy Lebeau Feb 07 '14 at 22:44
  • 1
    You mean `TCanvas.Draw` rather than `TBitmap.Draw` I guess. And yes, that's what I am referring to. You can call `AlphaBlend` that way if you prefer. Ultimately that's what does the work. Fiddling with alpha values is not the right way to merge multiple images together. – David Heffernan Feb 07 '14 at 22:50
  • @RemyLebeau, `TPNGImage` will produce a volatile bitmap when asked to be `Draw`n anyway. But if you really insist on doing work directly with `TPNGImage` you'd better off change values in `AlphaScanline` property. – Free Consulting Feb 07 '14 at 22:52
  • Worked well. And unlike any other I have read, I did understand the code inside `SetPNGAlpha`. Just one thing. Do you know why the already fully transparent pixels becomes White? If so, there is a way to "fix" it? I almost did, except for aliasing... – Guill Feb 07 '14 at 23:07
  • Add a condition Testing `(pScanLine[nPixelCount] = 0)`, to perform the Alpha. So I can accpet your answer! – Guill Feb 07 '14 at 23:27
  • 2
    Why do you want to test `pScanLine[nPixelCount]` for 0? – Remy Lebeau Feb 07 '14 at 23:49
  • 1
    So the pixels which are already fully transparent, remains transparent. Otherwise they would become white. – Guill Feb 08 '14 at 11:42
  • @Guill - Need to test against the "opacity" you're setting, not '0'. So not only pixels which are fully transparent, but also pixels that are already more transparent then the opacity don't become more opaque. – Sertac Akyuz Feb 09 '14 at 22:56