Jump to content
Willicious

Change "FadeOut" code to "FadeIn" code

Recommended Posts

The app I'm working on has different "screens", which are each essentially large bitmaps. When closing a screen to go to the next one, the following code provides a fade-to-black transition effect:

procedure TGameBaseScreen.FadeOut;
var
  Steps: Cardinal;
  i: Integer;
  P: PColor32;
  StartTickCount: Cardinal;
  IterationDiff: Integer;
  RGBDiff: Integer;
const
  TOTAL_STEPS = 32;
  STEP_DELAY = 12;
begin
  Steps := 0;
  StartTickCount := GetTickCount;
  while Steps < TOTAL_STEPS do
  begin
    IterationDiff := ((GetTickCount - StartTickCount) div STEP_DELAY) - Steps;

    if IterationDiff = 0 then
      Continue;

    RGBDiff := IterationDiff * 8;

    with ScreenImg.Bitmap do
    begin
      P := PixelPtr[0, 0];
      for i := 0 to Width * Height - 1 do
      begin
        with TColor32Entry(P^) do
        begin
          if R > RGBDiff then Dec(R, RGBDiff) else R := 0;
          if G > RGBDiff then Dec(G, RGBDiff) else G := 0;
          if B > RGBDiff then Dec(B, RGBDiff) else B := 0;
        end;
        Inc(P);
      end;
    end;
    Inc(Steps, IterationDiff);

    ScreenImg.Bitmap.Changed;
    Changed;
    Update;
  end;
end;

Is there any way this same code can be modified to create a new FadeIn; procedure which allows the screen images to fade-in-from-black?

 

I'm guessing I need to first specify that RGB := 0, 0, 0 and then increase the RGB values until they reach the actual bitmap values. Do I first need to "get" those values somehow? Or, is there a better way to achieve the Fade In/Out procedures? (note that we're dealing with combined bitmaps here, rather than single images)

Edited by Willicious

Share this post


Link to post

I dont know this procedure, but I think that "P" can be the "point"... Inc(P) is growing.... then if P := PixelPtr(  x, y)  = max values  then  dec(P) can "go back" the positions on TColor32Entry(P^) ... I dont know, if just a thinking...

  • would be  X = Width and Y=Height of your Bitmap?

 

here a Github with so much code including Fade In/Out effect in "forms"!!!

https://github.com/tothpaul/Delphi/tree/master/FadingEffect

 

Edited by programmerdelphi2k

Share this post


Link to post
5 hours ago, Willicious said:

Do I first need to "get" those values somehow? 

Yes.

 

For the "fade in" you need two bitmaps, giving you three color values: 1) the source color (black), 2) the target color (your original bitmap), and 3) the current color (the display bitmap).

For the "fade out", the way you've implemented it, you only need one bitmap because the source and the current color are the same (the display bitmap) and the target color value is implicit (black).

 

You can modulate/mix linearly between two colors, ColorFrom and ColorTo, by a value going from 0 to 1 with this formula:

 

  Color := ColorFrom * (1 - Value) + ColorTo * Value

 

or in other words:

 

// Value = 0: ColorFrom
// Value = 1: ColorTo
function ColorModulate(ColorFrom, ColorTo: TColor32Entry; Value: Single): TColor32Entry;
begin
  Result.A := Round(ColorFrom.A * (1 - Value)) + Round(ColorTo.A * Value);
  Result.R := Round(ColorFrom.R * (1 - Value)) + Round(ColorTo.R * Value);
  Result.G := Round(ColorFrom.G * (1 - Value)) + Round(ColorTo.G * Value);
  Result.B := Round(ColorFrom.B * (1 - Value)) + Round(ColorTo.B * Value);
end;

This can both be used to fade in and fade out.

When you're fading to black it follows that you are actually just doing this:

// Value = 0: Color
// Value = 1: Black
function FadeToBlack(Color: TColor32Entry; Value: Single): TColor32Entry;
begin
  Result.A := Round(Color.A * (1 - Value));
  Result.R := Round(Color.R * (1 - Value));
  Result.G := Round(Color.G * (1 - Value));
  Result.B := Round(Color.B * (1 - Value));
end;

 

Share this post


Link to post
11 hours ago, programmerdelphi2k said:

here a Github with so much code including Fade In/Out effect in "forms"!!!

https://github.com/tothpaul/Delphi/tree/master/FadingEffect

 

Yeah, this seems useful. This is using FOpacity... would I need to draw a large black bitmap over the main one and then reduce the opacity of that bitmap to gradually reveal the one underneath?? I'm not sure how this works.

 

5 hours ago, Anders Melander said:

You can modulate/mix linearly between two colors, ColorFrom and ColorTo, by a value going from 0 to 1 with this formula:

 

  Color := ColorFrom * (1 - Value) + ColorTo * Value

 

Right, and I'm guessing that the "Value" needs to be explicitly stated? The bitmap has many different colour values, though. I need a way of saying "for each pixel, get the colour, and set this to some value. Now go from 0 to that value." It's not always going to be "1", so I'm not sure how to make use of your example...?

Edited by Willicious

Share this post


Link to post
1 minute ago, Willicious said:

Right, and I'm guessing that the "Value" needs to be explicitly stated? The bitmap has many different colour values, though. I need a way of saying "for each pixel, get the colour, and set this to some value. Now go from 0 to that value." It's not always going to be "1", so I'm not sure how to make use of your example...?

No. The Value specifies how far you are between fade-in and fade-out. In my example it's a float value between 0 and 1, inclusive, so 0.5 is halfway faded in/out.

I would probably use integer math for performance instead of floats, but it's easier to explain with a float.

 

The two code snippets I gave you should be ready to use for what you're trying:

 

// "Fade" from blue to red
for i := 0 to 100 do
  Color := ColorModulate(clBlue32, clRed32, i / 100);

// "Fade" from green to black
for i := 0 to 100 do
  Color := ColorModulate(clGreen32, clBlack32, i / 100);
// or...
for i := 0 to 100 do
  Color := FadeToBlack(clGreen32, i / 100);

Actually, in my FadeToBlack example, I modulate the Alpha channel too. You probably should leave the Alpha alone in that case. Otherwise, the result will end up being transparent.

Share this post


Link to post
7 minutes ago, Anders Melander said:

I would probably use integer math for performance instead of floats, but it's easier to explain with a float.

 

The two code snippets I gave you should be ready to use for what you're trying

I see what you mean, but I'm trying to fade from black to... some unknown colour (unknown in the sense that the bitmap contains many different colours). So, do I have to set those colour values first, and then call them something like "aValue"? If so, how do you do that for many different colours at once?

 

Your example works for fading to black (and for modulating between known values).

Edited by Willicious

Share this post


Link to post
16 minutes ago, Willicious said:

Your example works for fading to black (and for modulating between known values).

Yes. As I stated:

Quote

For the "fade in" you need two bitmaps, giving you three color values: 1) the source color (black), 2) the target color (your original bitmap), and 3) the current color (the display bitmap).

Right now you only have one bitmap, which is black because you faded it out, so you only have one color.

If you don't have a copy of the image you're supposed to be fading in to, you need to code a time machine that goes back in time and gets it - or maybe save a copy somewhere before you destroy the color information by fading it to black.

image.thumb.png.1d52aefaec025e56afc41b24b1913475.png

Share this post


Link to post

@Anders Melander Ah, maybe there's been a misunderstanding. The idea is not to fade the current image to black, and then fade the same image back in. The idea is to fade the current image to black, and then fade the next image in.

 

In fact, the way that the FadeOut; is written, it's always applied to any image which happens to be onscreen, at the point when that screen is closed. It doesn't need to know what the image is or what its exact colour values are.

 

What's needed is a similarly-written FadeIn; procedure which can be applied to any screen.image, at the point when that screen is opened. Maybe it can be done by simply drawing a black canvas over the screen.image, and then gradually reducing the opacity until it's revealed (i.e. rather than having to get the actual colour values for each image)...?

Share this post


Link to post
1 minute ago, Willicious said:

What's needed is a similarly-written FadeIn

That's the ColorModulate function; It "fades" from any color to any color. In your case, you would fade each pixel from black to the color of your new image.

Or if you absolutely must have a FadeFromBlack function:

// Value = 0: Black
// Value = 1: Color
function FadeFromBlack(Color: TColor32Entry; Value: Single): TColor32Entry;
begin
  Result.A := Color.A; // Probably 255
  Result.R := Round(Color.R * Value);
  Result.G := Round(Color.G * Value);
  Result.B := Round(Color.B * Value);
end;

Apply this to each pixel of your "to" bitmap and draw the resulting bitmap on top of whatever you have on the screen.

  • Like 1

Share this post


Link to post

OK, thanks 🙂

 

I'm getting an "Unsatisfied forward or external declaration" when I try to add either function to the program. Not sure what to do about this at present, but I do at least understand what the functions are meant to be doing.

 

@Anders Melander Thanks for your help btw, apologies for my noobishness

Edited by Willicious

Share this post


Link to post

OK, changing it into a procedure instead of a function gets rid of the error. I now have this:

procedure TGameBaseScreen.FadeIn;
var
  A: TColor32;
  R: TColor32;
  G: TColor32;
  B: TColor32;
  Value: Integer;
begin
  Value := 0;
  if Value = 0 then Inc(Value); //I can see what I'm doing wrong here: "Value" needs something that it can increase towards incrementally,
                                //and then I obviously need to put in a tick count as well


  A := 255;
  R := Round(R * Value); //<--- Incompatible types error
  G := Round(G * Value); //<--- Incompatible types error
  B := Round(B * Value); //<--- Incompatible types error
end;

Maybe I should start simple and just see if I can draw a plain black bitmap to the screen. Tried this, it doesn't work (also tried it with A := 255). Not sure what I'm doing wrong here, but it's probably something really basic:

procedure TGameBaseScreen.FadeIn;
var
  A: TColor32;
  R: TColor32;
  G: TColor32;
  B: TColor32;
  i: Integer;
begin
  for i := 0 to Width * Height - 1 do
      begin
        A := 0;
        R := 0;
        G := 0;
        B := 0;
      end;
end;

 

 

Edited by Willicious

Share this post


Link to post
32 minutes ago, Willicious said:

OK, changing it into a procedure instead of a function gets rid of the error. I now have this:


procedure TGameBaseScreen.FadeIn;
var
  A: TColor32;
  R: TColor32;
  G: TColor32;
  B: TColor32;
  Value: Integer;
begin
  Value := 0;
  if Value = 0 then Inc(Value);


  A := 255;
  R := Round(R * Value); //<--- Incompatible types error
  G := Round(G * Value); //<--- Incompatible types error
  B := Round(B * Value); //<--- Incompatible types error
end;

 

A, R, G and B are bytes.

 

TColor32 is a dword (i.e. 4 bytes). It can be cast to a TColor32Entry, which is a record containing the 4 ARGB bytes.

 

Value needs to be a "floating point" type. E.g. Single or Double. And the you cannot use Inc() on it.

If you want to iterate using an integer value then you need to do a division somewhere so you get a value between 0 and 1 that you can multiply the RGB values with. Like I did here:

 

 

38 minutes ago, Willicious said:

Not sure what I'm doing wrong here, but it's probably something really basic:

This doesn't do anything. What did you expect it to do?

Share this post


Link to post
1 hour ago, Anders Melander said:

Value needs to be a "floating point" type. E.g. Single or Double. And the you cannot use Inc() on it.

If you want to iterate using an integer value then you need to do a division somewhere so you get a value between 0 and 1 that you can multiply the RGB values with. Like I did here:

I see, thanks. This helps. I'll have a look at it later today 🙂👍

 

1 hour ago, Anders Melander said:

This doesn't do anything. What did you expect it to do?

I wanted to fill the screen with a black bitmap.

 

If I can do that, then the next step is to either gradually make it more and more transparent using opacity or alpha values (thus revealing the screen image underneath), or use your example code to modulate the black pixels to those of the screen image bitmap. Whichever of these options I go with, it needs to be portable to all of the various game screens across several different units.

 

To begin with though, I just need to draw a black bitmap. If I can do that, then I'll move on to the next step.

Share this post


Link to post

Just giving this a bump.

 

I need it to fill the screen with a black bitmap (for which I also need control of the alpha channel). Do I need to specify a temporary bitmap image first which I then drawto, or is it possible to just fill the screen directly?

 

procedure TGameBaseScreen.FadeIn;
var
  A: TColor32;
  R: TColor32;
  G: TColor32;
  B: TColor32;
  i: Integer;
begin
  for i := 0 to Width * Height - 1 do
      begin
        A := 0;
        R := 0;
        G := 0;
        B := 0;
      end;
end;

 

Share this post


Link to post
7 hours ago, Willicious said:

I need it to fill the screen with a black bitmap (for which I also need control of the alpha channel).

You don't need the alpha channel. The alpha is only needed when you later have to blend stuff together and that is not what you are doing. You are just "removing color" until only black is left.

 

7 hours ago, Willicious said:

Do I need to specify a temporary bitmap image first which I then drawto, or is it possible to just fill the screen directly?

You haven't shown how exactly you draw stuff onto the screen so that is hard to answer. Generally, I would say that you need a temporary bitmap since it's dead slow to draw individual pixels one at a time.

 

 

7 hours ago, Willicious said:

procedure TGameBaseScreen.FadeIn;

I don't know what this is supposed to illustrate since it doesn't do anything but once again: TColor32 is a DWORD (4 bytes) which is meant to contain the R, G, B and A channels simultaneously. You seem to think it represents a single channel.

Share this post


Link to post
2 hours ago, Anders Melander said:

The alpha is only needed when you later have to blend stuff together and that is not what you are doing. You are just "removing color" until only black is left.

No, it's the opposite. I want to start with a black screen and finish with the full-colour screen image. The aesthetic effect would be that of the screen image fading from black.

 

3 hours ago, Anders Melander said:

You haven't shown how exactly you draw stuff onto the screen so that is hard to answer.

I'm more than happy to modify the codebase if there's a better way of doing things.

 

The reason I haven't posted anything to do with how the images are currently drawn is because it's quite complicated (I barely understand it myself); the screen image is composited from a number of individual pngs which get drawn to a single bitmap (I think) - and, this happens across several units of code. I'm not entirely sure which bits are relevant.

 

This is the Create procedure of the unit I'm currently working in, maybe this will shed some light on it?:

constructor TBaseScreen.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);

  fScreenImg := TImage32.Create(Self);
  fScreenImg.Parent := Self;

  ScreenImg.Cursor := crNone;
end;

 

3 hours ago, Anders Melander said:

I don't know what this is supposed to illustrate since it doesn't do anything

I think I've made it quite clear what I'm trying to do to be honest, I'm not sure how much clearer I can be🤷‍♂️

Share this post


Link to post
7 minutes ago, Willicious said:

No, it's the opposite. I want to start with a black screen and finish with the full-colour screen image. The aesthetic effect would be that of the screen image fading from black.

It doesn't matter. You are still not blending anything.

Look, please forget about the alpha and just set it to 255. Even if alpha was somehow involved in what you are doing you are really not ready to deal with that part yet.

 

11 minutes ago, Willicious said:

I think I've made it quite clear what I'm trying to do to be honest, I'm not sure how much clearer I can be🤷‍♂️

Yes, but the code you posted does nothing in itself. It may very well be that you removed some parts that actually do something before you posted it, but I'm not a psychic and I can't guess what it originally did.

Anyhow, moving on...

 

9 minutes ago, Willicious said:

maybe this will shed some light on it?:

Yes, it does. You are using the TImage32 control from the Graphics32 library to display the bitmaps.

 

One approach would be to keep a copy of the bitmap you are fading from or to and then construct the bitmap to be displayed from that. However, I think we have by now established that you can't figure out how to do that.

 

So I suggest you simply do this instead:

// Set these values somewhere.
// For example the place where you create the TImage32.

// Make the background black
fScreenImg.Color := clBlack;
// Have the bitmap blend with the background color
fScreenImg.Bitmap.DrawMode := dmBlend;

...

// Fade out
for var i := 255 downto 0 do
begin
  fScreenImg.Bitmap.MasterAlpha := i;
  fScreenImg.Update;
  Sleep(50);
end;

// Load the new bitmap here
fScreenImg.Bitmap.LoadFromFile(...); // or whatever...

// Fade in
for var i := 0 to 255 do
begin
  fScreenImg.Bitmap.MasterAlpha := i;
  fScreenImg.Update;
  Sleep(50);
end;

 

Share this post


Link to post

Tried this again today. This yields the same result as always (black screen for 1000ms, then the screenimg.bitmap is drawn at full opacity). I've tried placing "Inc(i)" at various points in the loop, and have also tried using "repeat ... until i = 255" to no avail:

 

procedure TGameBaseScreen.FadeIn;
var
  i: Integer;
  RemainingTime: Integer;
  OldRemainingTime: Integer;
  EndTickCount: Cardinal;
const
  MAX_TIME = 1000; // mS
begin
  EndTickCount := GetTickCount + MAX_TIME;
  OldRemainingTime := 0;
  RemainingTime := MAX_TIME;
  ScreenImg.Bitmap.DrawMode := dmBlend; // So MasterAlpha is used to draw the bitmap
  while (RemainingTime >= 0) do
  begin
    if (RemainingTime <> OldRemainingTime) then
    begin
      for i := 0 to 255 do
      begin
        ScreenImg.Bitmap.MasterAlpha := i;
        ScreenImg.Update;
      end;

      OldRemainingTime := RemainingTime;
    end else
      Sleep(1);

    RemainingTime := EndTickCount - GetTickCount;
    Inc(i);
  end;

  Application.ProcessMessages;
end;

 

 

For reference, here's the FadeOut procedure as written by Anders, which works perfectly:

 

procedure TGameBaseScreen.FadeOut;
var
  RemainingTime: integer;
  OldRemainingTime: integer;
  EndTickCount: Cardinal;
const
  MAX_TIME = 300; // mS
begin
  EndTickCount := GetTickCount + MAX_TIME;
  OldRemainingTime := 0;
  RemainingTime := MAX_TIME;
  ScreenImg.Bitmap.DrawMode := dmBlend; // So MasterAlpha is used to draw the bitmap
  while (RemainingTime >= 0) do
  begin
    if (RemainingTime <> OldRemainingTime) then
    begin
      ScreenImg.Bitmap.MasterAlpha := MulDiv(255, RemainingTime, MAX_TIME);
      ScreenImg.Update;

      OldRemainingTime := RemainingTime;
    end else
      Sleep(1);

    RemainingTime := EndTickCount - GetTickCount;
  end;

  Application.ProcessMessages;
end;

 

Any suggestions or comments would be most welcome!

Edited by Willicious

Share this post


Link to post

Something like this:

procedure TGameBaseScreen.FadeIn;
var
  EndTickCount: Cardinal;
  TickCount: Cardinal;
  Progress: Integer;
  Alpha, LastAlpha: integer;
const
  MAX_TIME = 1000; // mS
begin
  ScreenImg.Bitmap.DrawMode := dmBlend; // So MasterAlpha is used to draw the bitmap

  TickCount := GetTickCount;
  EndTickCount := TickCount + MAX_TIME;
  LastAlpha := -1;

  while (TickCount <= EndTickCount) do
  begin
    Progress := Max(TickCount - EndTickCount - MAX_TIME, MAX_TIME);

    Alpha := MulDiv(255, Progress, MAX_TIME);

    if (Alpha = LastAlpha) then
    begin
      Sleep(1);
      continue;
    end;

    ScreenImg.Bitmap.MasterAlpha := Alpha;
    ScreenImg.Update;

    LastAlpha := Alpha;
    TickCount := GetTickCount;
  end;
  ScreenImg.Bitmap.MasterAlpha := 255;

  Application.ProcessMessages;
end;

 

Share this post


Link to post

Thanks for the code snippet Anders, I've given it a quick try and just get a black screen. I'll investigate further later this evening or tomorrow, what you've written looks like it should work. I'll do some debugging and see if I can figure out what's going wrong.

Share this post


Link to post

By bad; Change this:

Progress := Max(TickCount - EndTickCount - MAX_TIME, MAX_TIME);

to this:

Progress := Max(TickCount - (EndTickCount - MAX_TIME), MAX_TIME);

 

EndTickCount = StartTickCount + MAX_TIME <=>
StartTickCount = EndTickCount - MAX_TIME

so

Elapsed time = TickCount - StartTickCount <=>

Elapsed time = TickCount - (EndTickCount - MAX_TIME)

 

Share this post


Link to post

Thanks again. Made the change, still just getting a black screen. I'll have more time to look over it tomorrow properly, it may be because of where I'm calling the procedure from or something like that.

Share this post


Link to post

OK, I think the reason why none of the attempts at a FadeIn procedure are working is because the UI isn't actually responding to the updates. This could either be because the App is idle at the point the FadeIn is being called, or because it's only processing the first message from the FadeIn procedure, and then none of the others.

 

At one point, I tried displaying an animated "Fade" graphic (basically a large black square which gradually faded to transparent over 8 frames) over the existing screen image - I could only ever get it to display one frame of this animation.

 

The app is definitely capable of processing messages and animations in the menu screens, though, because other animated images (a text scroller) are shown, clickable regions have graphical effects applied to them, and the FadeOut procedure works. Here's where FadeOut is called, maybe there's a clue in here somewhere:
 

procedure TGameBaseScreen.CloseScreen(aNextScreen: TGameScreenType);
begin
  Self.OnKeyDown := nil;
  Self.OnKeyPress := nil;
  Self.OnClick := nil;
  Self.OnMouseDown := nil;
  Self.OnMouseMove := nil;
  ScreenImg.OnMouseDown := nil;
  ScreenImg.OnMouseMove := nil;
  Application.OnIdle := nil;
  fScreenIsClosing := True;
  if fCloseDelay > 0 then
  begin
    Update;
    Sleep(fCloseDelay);
  end;

  FadeOut;

  if GameParams <> nil then
  begin
    GameParams.NextScreen := aNextScreen;
    GameParams.MainForm.Cursor := crNone;
  end;

  Close;

  PostMessage(MainFormHandle, LM_NEXT, 0, 0);
end;

And, here's where FadeIn is currently being called:

 

constructor TGameBaseMenuScreen.Create(aOwner: TComponent);
begin
  inherited;

  fKeyStates := TDictionary<Word, UInt64>.Create;
  fClickableRegions := TObjectList<TClickableRegion>.Create;

  fMenuFont := TMenuFont.Create;
  fMenuFont.Load;

  fBasicCursor := TNLCursor.Create(Min(Screen.Width div 320, Screen.Height div 200) + EXTRA_ZOOM_LEVELS);
  LoadBasicCursor('amiga.png');
  SetBasicCursor;

  InitializeImage;

  OnKeyDown := Form_KeyDown;
  OnKeyUp := Form_KeyUp;
  OnMouseDown := Form_MouseDown;
  OnMouseMove := Form_MouseMove;
  ScreenImg.OnMouseDown := Img_MouseDown;
  ScreenImg.OnMouseMove := Img_MouseMove;

  fCalledFromClassicModeButton := False;

  FadeIn;
end;

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×