PDA

View Full Version : Fading an image



cairnswm
08-10-2003, 06:37 AM
How do I fade part of my screen. When the game finishes I want to fade out the screen and draw text over it.

:( Alpha draw with DelphiX is obviously not a good idea. (Software)
:?: What about changing the color of each pixel? Effectivly just fading the pixels. There wont be any animation so while this may slow it down it shouldn't be too much of a problem.
:?: Any other ideas....

Traveler
08-10-2003, 07:45 AM
A couple years ago I came across this sample. It's perhaps not the fastest way, but it is a start.

It uses the DXDraw, DXImagelist components. Add two images in the list copy/paste the code below and you're set.


// Hi,
// There was talk about how to fade screens in and out. This is my version of doing it.
// It does about 30 FPS with my 350 MHz AMD. (40 FPS if I check out the VBlank)
// I also tried using a fixed value for the steps. When I used 128 I could change the div to shr and double the speed...
// See how it's done in FadeOut(128); I'm sure that some good optimizer like
// Christopher Oezbek will double the FPS in no time... This does the fading in a bit
// different way than his code did. Christopher used a fixed value to drop down the color
// value, and I'm interpolating the values to have a smoother result.
//
// I hope you like this,
// Ilkka
//

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DXDraws, ExtCtrls, DXClass;

type
TForm1 = class(TForm)
DXDraw: TDXDraw;
Images: TDXImageList;
procedure DXDrawMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
// Fading procedures.
procedure FadeIn(Steps: Integer; Src, Dest: Integer);
procedure FadeOut(Steps: Integer; Src, Dest: Integer);
procedure Mix(Pic1, Pic2, Src: Integer);
end;

var
Form1: TForm1;

implementation

procedure TForm1.FadeIn(Steps: Integer; Src, Dest: Integer);
var
i, x, y: Integer;
SrcScan, DestScan: PByteArray;
StartTime: TDateTime;
begin
Form1.Caption := ('Fading in');
StartTime := Now;
for i := 0 to Steps do
begin
for y := 0 to Images.Items[Src].Picture.Bitmap.Height-1 do
begin
// Get the scanlines.
DestScan := Images.Items[Dest].Picture.Bitmap.ScanLine[y];
SrcScan := Images.Items[Src].Picture.Bitmap.ScanLine[y];
// height * 3 for 24 bpp.
for x := 0 to (Images.Items[Src].Picture.Bitmap.Width) * 3 do
DestScan[x] := (SrcScan[x] * i) div Steps;
end;
// Restore the bitmap and draw it.
Images.Items[Dest].Restore;
Images.Items[Dest].Draw(DXDraw.Surface,0,0,0);
DXDraw.Flip;
end;
// Show the FPS
Form1.Caption := 'Fading out done, ' +
IntToStr(Round(Steps / ((Now-StartTime)*86401))) + ' FPS';
end;// FadeIn

procedure TForm1.FadeOut(Steps: Integer; Src, Dest: Integer);
var
i, x, y: Integer;
SrcScan, DestScan: PByteArray;
StartTime: TDateTime;
begin
Form1.Caption := ('Fading out');
StartTime := Now;
for i := Steps downto 0 do
begin
for y := 0 to Images.Items[Src].Picture.Bitmap.Height-1 do
begin
DestScan := Images.Items[Dest].Picture.Bitmap.ScanLine[y];
SrcScan := Images.Items[Src].Picture.Bitmap.ScanLine[y];
for x := 0 to (Images.Items[Src].Picture.Bitmap.Width) * 3 do
// DestScan[x] := (SrcScan[x] * i) div Steps;
// This shows the speed difference between div's
// and shr's. (Doubled)
DestScan[x] := (SrcScan[x] * i) shr 7;
end;
Images.Items[Dest].Restore;
Images.Items[Dest].Draw(DXDraw.Surface,0,0,0);
DXDraw.Flip;
end;
Form1.Caption := 'Fading out done, ' +
IntToStr(Round(Steps / ((Now-StartTime)*86401))) + ' FPS';
end;// FadeOut

procedure TForm1.Mix(Pic1, Pic2, Src: Integer);
var
i, x, y: Integer;
Src1Scan, Src2Scan, DestScan: PByteArray;
StartTime: TDateTime;
begin
Form1.Caption := ('Mixing');
StartTime := Now;
for i := 0 to 128 do
begin
for y := 0 to Images.Items[0].Picture.Bitmap.Height-1 do
begin
Src1Scan := Images.Items[Pic1].Picture.Bitmap.ScanLine[y];
Src2Scan := Images.Items[Pic2].Picture.Bitmap.ScanLine[y];
DestScan := Images.Items[Src].Picture.Bitmap.ScanLine[y];
for x := 0 to (Images.Items[0].Picture.Bitmap.Width) * 3 do
// This adds the pictures together and
// fades from one to other.
DestScan[x] := ((Src1Scan[x] * i) shr 7) +
((Src2Scan[x] * (128-i))shr 7);
end;
Images.Items[Src].Restore;
Images.Items[Src].Draw(DXDraw.Surface,0,0,0);
DXDraw.Flip;
end;
Form1.Caption := 'Mixing done, ' +
IntToStr(Round(128 / ((Now-StartTime)*86401))) + ' FPS';
end;// Mix

{$R *.DFM}

procedure TForm1.DXDrawMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
If not (ssShift in Shift) then
begin
If Button = mbleft then
fadein(128,0,1)
else
// If you change this, remember to change the
// fadeout function back.
fadeout(128,0,1);
end else
begin
If Button = mbleft then
Mix(0,2,1)
else
Mix(2,0,1);
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
// If I put the images straight to ImageList,
// The bitmap property doesn't seem to be set.
// I don't know how to get the pixels otherwise.
Images.Items[0].Picture.Bitmap.LoadFromFile('back.bmp');
Images.Items[1].Picture.Bitmap.LoadFromFile('delta.bmp');
Images.Items[2].Picture.Bitmap.LoadFromFile('delta.bmp');
// Set the form and DXDraw size.
DXDraw.Height := Images.Items[0].Picture.Bitmap.Height;
DXDraw.Width := Images.Items[0].Picture.Bitmap.Width;
Form1.ClientHeight := DXDraw.Height;
Form1.ClientWidth := DXDraw.Width;

Form1.Caption := 'Click the canvas!';
end;

end.

cairnswm
08-10-2003, 08:23 AM
Great!

I'll give it a try!

Avatar
09-10-2003, 04:16 AM
I remember you can modify the palette in order to have a fade(just increase the colors values each refresh) ... But it was when I worked on 8 bits palette ... I don't know if 32 bits is that easy to modify ^^

Bye
Avatar

Alimonster
10-10-2003, 09:29 PM
I remember you can modify the palette in order to have a fade(just increase the colors values each refresh) ... But it was when I worked on 8 bits palette ... I don't know if 32 bits is that easy to modify ^^

Bye
Avatar
Nope, unfortunately it's not as simple as that in 32 bit colour :(. More's the pity!

Now, about the previous code: you probably want to remove the property access in the inner loop. A quick, unrelated example to show what I mean:

var
i: Integer;
begin
for i := 0 to class1.property1.property2.property3 - 1 do
Whatever;
end;

changes into...

var
i: Integer;
BlahCount: Integer;
begin
BlahCount := class1.property1.property2.property3;
for i := 0 to BlahCount - 1 do
Whatever;
end;

Next up: you don't want to be touching the .scanline in the inner loop either. The bitmap's memory itself will not change location, which means that you can calculate the offset from one row to the next. Here's a quote from Danny Thorpe from an article I'll ]

On the question of performance impact of calling Scanlines[] a lot: Yes, there is a potential performance hit. The DIBSection's memory buffer is not guaranteed to be coherent (reflect the most recent GDI operations) unless you call GDIFlush before accessing the pointer. The ScanLines[] property has to call GDIFlush to ensure the buffer contents are in sync. If the bitmap has been modified by GDI calls, then the call to GDIFlush could block waiting for the GDI pipeline to empty. If no modifications are still pending, GDIFlush should return immediately, but there will always be the overhead of making an additional call.

Additionally, TBitmap.GetScanlines() calls TBitmap.Changing, which may have to create a completely new, unshared copy of the image if the bitmap is shared. That will completely blow out performance on the first touch of Scanlines, and it chips away a little even when the bitmap isn't shared.

The technique shown in Listing 13 for minimizing time lost to ScanLines[] was developed by me while writing the TJPEGImage class. There is more at work here than meets the eye, though, which should be mentioned with that listing. The easiest way to eliminate calls to ScanLines[] is to simply do your own pointer arithmetic to advance the start-of-scanline buffer pointer to the next scanline. There are two hazards in doing that: 1) correctly dealing with the DWORD alignment of scan lines, and 2) the physical layout of scanlines in the memory buffer. DIBs can be oriented as "top-down", where the first row of pixels in the bitmap reside in the first bytes of memory in the buffer, or as "bottom-up", where the first row of pixels reside in the last bytes of memory and grow upward in memory. Oddly enough, "bottom-up" is the more common DIB orientation, possibly due to BMP's OS/2 origins.

The technique of subtracting the address of Scanline[0] from the address of Scanline[1] solves both problems very nicely. It gives you the distance between scanlines including DWORD padding, if any, and the sign of the delta implicitly indicates the DIB memory orientation. This eliminates the need for performance-robbing conditional statements in the critical pointer advancement loop. Just increment the pointer by that delta, and it will put you on the next scanline, be it above or below the current address.[/quote]
The article is at EFG's site: http://www.efg2.com/Lab/ImageProcessing/Scanline.htm

Also, note that EFG has an example about fading to/from black, which you can use to compare and constrast with the code above. The report: http://www.efg2.com/Lab/ImageProcessing/fade.htm

I'd offer to optimise the code myself but lately I've found myself breaking promises like that, unfortunately. However, the above should give some ideas.