How to get volume level in current sample? Delphi 7 - delphi

On Delphi 7 I am running this code with NewAC Audio library. I am having short wav file, 44.100 kHz, mono, 16 bit.
unit Main;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ACS_Classes, ACS_DXAudio, ACS_Wave, ACS_Misc, ACS_Types, StdCtrls;
type
TForm1 = class(TForm)
AudioProcessor1: TAudioProcessor;
WaveIn1: TWaveIn;
DXAudioOut1: TDXAudioOut;
OpenDialog1: TOpenDialog;
Button1: TButton;
Button2: TButton;
procedure AudioProcessor1GetData(
Sender: TComponent;
var Buffer: Pointer;
var NBlockBytes: Cardinal);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure DXAudioOut1Done(Sender: TComponent);
procedure AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
procedure AudioProcessor1Flush(Sender: TComponent);
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.AudioProcessor1GetData(Sender: TComponent;
var Buffer: Pointer; var NBlockBytes: Cardinal);
var Tmp : Integer;
i : Integer;
list1: TStringList;
list2: TStringList;
b1, b2, b3, b4:byte;
si1, si2, si3, si4: ShortInt;
mono: Boolean;
values: array of word;
begin
list1 := TStringList.Create;
list2 := TStringList.Create;
AudioProcessor1.Input.GetData(Buffer, NBlockBytes);
if Buffer = nil then
Exit;
mono := false;
case AudioProcessor1.Input.BitsPerSample of
16 :
begin
B16 := Buffer;
setlength(values, NBlockBytes div 2);
for i := 0 to (NBlockBytes div 4) - 1 do
begin
Tmp := B16[i*2];
move(B16[i*2], b1, 1); // copy left channel
move(B16[i*2+1], b2, 1); // copy right channel
move(B16[i*2+2], b3, 1); // copy left channel
move(B16[i*2+3], b4, 1); // copy right channel
si1 := b1;
si2 := b2;
si3 := b3;
si4 := b4;
list1.add(''+inttostr(si1));
list2.add(''+inttostr(si2));
list1.add(''+inttostr(si3));
list2.add(''+inttostr(si4));
B16[i*2] := B16[i*2 + 1];
B16[i*2 + 1] := Tmp;
end;
end;
end;
list1.free;
list2.free;
end;
procedure TForm1.AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
begin
TAudioProcessor(Sender).Input.Init;
TotalSize := TAudioProcessor(Sender).Input.Size
end;
procedure TForm1.AudioProcessor1Flush(Sender: TComponent);
begin
TAudioProcessor(Sender).Input.Flush;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
Button1.Enabled := False;
WaveIn1.FileName := OpenDialog1.FileName;
DXAudioOut1.Run;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DXAudioOut1.Stop;
end;
procedure TForm1.DXAudioOut1Done(Sender: TComponent);
begin
Button1.Enabled := True;
end;
end.
When I open the file in editing software I can see the amplitude of the sound and I see that the beginning values are 0. But when I run this program and I add the si1, si2, si3 and si4 to watch (in this order are the variables in watch), so I have these values in first iteration:
80,124,104,32.
I expected that these values should be 0 because there is silence on the begin.
First, may you explain why these are not zero?
Second, I am not sure what these values really represent. I know that si1 and si2 are first sample. But is it really level of the volume? How to correct the program to recognize the silence in the begin?
Tested file -> the section which should be passed to the function as first.
This part is not proccessed (because I processed only few cicles of the first loop):
I did some tests with file "silence plus", amplifications and see the first 8 cicles values.
Another test with word instead byte:
B16 := Buffer;
...
move(B16[i*2], w1, 2);
move(B16[i*2+1], w2, 2);
It really looks like the bits need to swap. I thought that in Windows XP I have little endian bit order. So I will write a swapper.

The main problems of my code were:
1) Reading 1 byte of sample instead 2 bytes of sample.
2) The sample is signed, not unsigned. So when I tried to read two bytes of word, I get wrong numbers (see the last table in question).
3) I also tried to use two bytes of SmallInt swapped, but that resulted to crazy numbers like -25345, -1281, 26624, -19968 ... This is because on my system I use Little endian (Windows XP). There is not need to swap it on Windows.
So the solution was to copy 16 bits to SmallInt, no swap.
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ACS_Classes, ACS_DXAudio, ACS_Wave, ACS_Misc, ACS_Types, StdCtrls;
type
TForm1 = class(TForm)
AudioProcessor1: TAudioProcessor;
WaveIn1: TWaveIn;
DXAudioOut1: TDXAudioOut;
OpenDialog1: TOpenDialog;
Button1: TButton;
Button2: TButton;
procedure AudioProcessor1GetData(
Sender: TComponent;
var Buffer: Pointer;
var NBlockBytes: Cardinal);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure DXAudioOut1Done(Sender: TComponent);
procedure AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
procedure AudioProcessor1Flush(Sender: TComponent);
private
{ Private declarations }
public
{ Public declarations }
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.AudioProcessor1GetData(Sender: TComponent;
var Buffer: Pointer; var NBlockBytes: Cardinal);
var
B16 : PBuffer16;
i, end_ : Integer;
si1, si2: SmallInt;
begin
AudioProcessor1.Input.GetData(Buffer, NBlockBytes);
if Buffer = nil then
Exit;
case AudioProcessor1.Input.BitsPerSample of
16 :
begin
B16 := Buffer;
end_ := (NBlockBytes div 2) - 1;
for i := 0 to end_ do
begin
move(B16[i*2], si1, 2);
move(B16[i*2+1], si2, 2);
end;
end;
end;
end;
procedure TForm1.AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
begin
TAudioProcessor(Sender).Input.Init;
TotalSize := TAudioProcessor(Sender).Input.Size
end;
procedure TForm1.AudioProcessor1Flush(Sender: TComponent);
begin
TAudioProcessor(Sender).Input.Flush;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
Button1.Enabled := False;
WaveIn1.FileName := OpenDialog1.FileName;
DXAudioOut1.Run;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DXAudioOut1.Stop;
end;
procedure TForm1.DXAudioOut1Done(Sender: TComponent);
begin
Button1.Enabled := True;
end;
end.
Here are the values:

Related

How can I do PING threads, reading OnReply event in Delphi 6?

I have a problem with Delphi 6 and Indy's TIdIcmpClient component.
I get this message when compiling the following code, in the marked line (51):
FPing.OnReply := OnPingReply;
[Error] fire.pas(51): Incompatible types: 'TComponent' and 'TIdIcmpClient'
How should I fix it?
unit fire;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
IdIcmpClient;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TMyThread = class(TThread)
private
FIndex: Integer;
FPing: TIdIcmpClient;
FIP: string;
protected
procedure Execute; override;
procedure OnPingReply(ASender: TIdIcmpClient; AReplyStatus: TReplyStatus);
public
constructor Create(AIndex: Integer);
destructor Destroy; override;
end;
constructor TMyThread.Create(AIndex: Integer);
begin
inherited Create(False);
FIndex := AIndex;
FIP := '192.168.1.' + IntToStr(FIndex + 1);
FPing := TIdIcmpClient.Create(nil);
FPing.Host:=FIP;
FPing.ReceiveTimeout:=1500;
FPing.OnReply := OnPingReply;
end;
destructor TMyThread.Destroy;
begin
FPing.Free;
inherited;
end;
//var// icmp:array[0..10] of TIdIcmpClient;
// ip:string;
procedure TMyThread.Execute; // aici e ce face thread-ul
var
i: Integer;
begin
FPing.Ping;
// ICMP.Ping('a',1000);
// Sleep(1300);
// form1.memo1.lines.add(IntToStr(findex)+' '+ICMP.ReplyStatus.fromipaddress);
for i := 1 to 1 do
begin
// 'findex' este indexul thread-ului din matrice
form1.memo1.lines.add(inttostr(findex)+' Thread running...');
application.ProcessMessages;
Sleep(1000);
end;
end;
procedure TMyThread.OnPingReply(ASender: TIdIcmpClient; AReplyStatus: TReplyStatus);
begin
if AReplyStatus.BytesReceived > 0 then
form1.memo1.Lines.add(FIP+ ' is reachable')
else
form1.memo1.Lines.add(FIP+ ' is not reachable: ');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyThreads: array[0..10] of TMyThread;
// icmp:array[0..10] of TIdIcmpClient;
i: Integer;
begin
{ for i := 0 to 10 do //10 fire
begin
icmp[i]:=tidicmpclient.create(nil);
icmp[i].ReceiveTimeout:=1200;
ip:=Format('%s.%d', ['192.168.1', i]);
ICMP[i].Host :=ip;
end; }
for i := 0 to 10 do //10 fire
begin
MyThreads[i] := TMyThread.Create(i);
MyThreads[i].Resume;
application.ProcessMessages;
end;
// Readln;
for i := 0 to 10 do
begin
MyThreads[i].Free;
// icmp[i].Free;
end;
end;
end.
I expected it to be compilable, but I don't see the reason why it is not.
Your event handler is declared wrong. The ASender parameter needs to be TComponent rather than TIdIcmpClient, and the AReplyStatus parameter needs to be const:
procedure OnPingReply(ASender: TComponent; const AReplyStatus: TReplyStatus);
That being said, you don't need to use the OnReply event at all in this situation. TIdIcmpClient operates synchronously, so you can simply use the TIdIcmpClient.ReplyStatus property after the TIdIcmpClient.Ping() method exits:
procedure TMyThread.Execute; // aici e ce face thread-ul
var
...
begin
FPing.Ping;
if FPing.ReplyStatus.BytesReceived > 0 then
...
else
...
...
end;
Also, you must synchronize with the main UI thread when accessing UI controls in a worker thread. You can use TThread.Synchronize() method for that.
And, you do not need to call Application.ProcessMessages() in a worker thread. Doing so will have no effect on the main UI thread.
With all of that said, try something more like this:
unit fire;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
IdIcmpClient;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure AddText(const AText: String);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TMyThread = class(TThread)
private
FIndex: Integer;
FPing: TIdIcmpClient;
FIP: string;
FText: String;
procedure AddTextToUI(const AText: String);
procedure DoSyncText;
protected
procedure Execute; override;
public
constructor Create(AIndex: Integer);
destructor Destroy; override;
end;
constructor TMyThread.Create(AIndex: Integer);
begin
inherited Create(False);
FIndex := AIndex;
FIP := '192.168.1.' + IntToStr(FIndex + 1);
FPing := TIdIcmpClient.Create(nil);
FPing.Host := FIP;
FPing.ReceiveTimeout := 1500;
end;
destructor TMyThread.Destroy;
begin
FPing.Free;
inherited;
end;
procedure TMyThread.AddTextToUI(const AText: String);
begin
FText := AText;
Synchronize(DoSyncText);
end;
procedure TMyThread.DoSyncText;
begin
Form1.AddText(FText);
end;
procedure TMyThread.Execute; // aici e ce face thread-ul
begin
AddTextToUI(IntToStr(FIndex) + ' Thread running...');
try
FPing.Ping;
except
AddTextToUI('Error pinging ' + FIP);
Exit;
end;
if FPing.ReplyStatus.BytesReceived > 0 then
AddTextToUI(FIP + ' is reachable')
else
AddTextToUI(FIP + ' is not reachable');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyThreads: array[0..10] of TMyThread;
I: Integer;
begin
for I := Low(MyThreads) to High(MyThreads) do //10 fire
begin
MyThreads[I] := TMyThread.Create(I);
end;
for I := Low(MyThreads) to High(MyThreads) do
begin
MyThreads[i].WaitFor;
MyThreads[i].Free;
end;
end;
procedure TForm1.AddText(const AText: String);
begin
Memo1.Lines.Add(AText);
end;
end.

how to avoid Memory leaks when using Generics.add<TClass.Create(AParams)>

i have this example here:
uses
System.Generics.Collections;
type
TMouseDrag = class
FX, FY: Integer;
constructor Create(AX, AY: Integer);
end;
TFrmMain = class(TForm)
Btn_FillList: TButton;
LV_MouseDrag: TListView;
procedure Btn_FillListClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FrmMain: TFrmMain;
List_MouseDrag: TList<TMouseDrag>;
AItems: TListItem;
FMouseMove: Integer;
implementation
{$R *.dfm}
{ TMouseDrag }
constructor TMouseDrag.Create(AX, AY: Integer);
begin
FX := AX; FY := AY;
// Free;
end;
{ TFrmMain }
procedure TFrmMain.Btn_FillListClick(Sender: TObject);
begin
List_MouseDrag.Add(TMouseDrag.Create(Random(100), Random(100)));
LV_MouseDrag.Items.BeginUpdate;
AItems := LV_MouseDrag.Items.Add;
AItems.Caption := List_MouseDrag.Items[FMouseMove].FX.ToString;
AItems.SubItems.Add(List_MouseDrag.Items[FMouseMove].FY.ToString);
inc(FMouseMove);
LV_MouseDrag.Items.EndUpdate;
end;
initialization
FMouseMove := 0;
List_MouseDrag := TList<TMouseDrag>.Create;
finalization
List_MouseDrag.Free;
for ex: when i click button three times i got a memory leaks says[13-20 bytes: TMouseDrag x3]
and when i activate the free methode inside TMouseDrag Create bloc is ok but this will clear List_MouseDrag Values ...
Do I missing something?
why List_MouseDrag can't free Also TMouseDrag Items when its Free?

Interfacing Octave and Lazarus/FreePascal with TProcess

I have also asked this question # the Lazarus forums, here
I am trying to communicate with Octave via a TProcess, but I don't seem to be able to read any bytes from it. Attached is the main form's unit; a full demo application is available as a zip from the forum under my post.
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Process;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
POctave: TProcess;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
if (not POctave.Running) then
begin
POctave.Executable := 'C:\Octave\Octave-4.2.0\bin\octave-cli.exe';
POctave.Parameters.Add('--no-gui');
POctave.Options := [poUsePipes];
WriteLn('-- Executing octave --');
POctave.Execute;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
command: string;
Buffer: string;
BytesAvailable: DWord;
BytesRead: longint;
NoMoreOutput: boolean;
begin
command := 'pwd' + #10;
if (POctave.Running) then
POctave.Input.Write(command, Length(command));
if (POctave.Running) then
begin
BytesAvailable := POctave.Output.NumBytesAvailable;
BytesRead := 0;
while BytesAvailable > 0 do
begin
SetLength(Buffer, BytesAvailable);
BytesRead := POctave.OutPut.Read(Buffer[1], BytesAvailable);
WriteLn(Buffer);
BytesAvailable := POctave.Output.NumBytesAvailable;
NoMoreOutput := False;
end;
end;
end;
initialization
POctave := TProcess.Create(nil);
finalization
POctave.Free;
end.
I've added sleep routines and changed the 'pwd' command's return to #1310, both without success.
procedure TForm1.Button2Click(Sender: TObject);
var
command: ansistring;
Buffer: string;
BytesAvailable: DWord;
BytesRead: longint;
NoMoreOutput: boolean;
begin
command := 'pwd'#13#10;
if (POctave.Running) then
POctave.Input.Write(command, Length(command));
Sleep(100);
if (POctave.Running) then
begin
Sleep(100);
BytesAvailable := POctave.Output.NumBytesAvailable;
BytesRead := 0;
while BytesAvailable > 0 do
begin
Sleep(100);
SetLength(Buffer, BytesAvailable);
BytesRead := POctave.OutPut.Read(Buffer[1], BytesAvailable);
WriteLn(Buffer);
BytesAvailable := POctave.Output.NumBytesAvailable;
NoMoreOutput := False;
end;
end;
end;
The problem was that I was calling this line:
POctave.Input.Write(command, Length(command));
instead of this:
POctave.Input.Write(command[1], Length(command));
After changing this (AND ADDING THE DELAY! It was absolutely critical to wait for the result, but my mistake was more fundamental.)
Remember: Pascal strings aren't C strings. Whoops...
It worked! Now I can send commands to Octave and retrieve the results via pipes. :)

Inno setup command line progress

How can I get progress when I'm executing inno script from a command line compiler (iscc.exe)?
I can pipeline the output but I want to get % completed as well.
Use ISCmplr library instead. For an inspiration, a very basic Delphi InnoSetup compiler might look like this (of course without hardcoded paths). It uses the original CompInt.pas unit from InnoSetup source pack:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, CompInt;
const
CompLib = ISCmplrDLL;
CompPath = 'c:\Program Files (x86)\Inno Setup 5\';
CompScriptProc = {$IFNDEF UNICODE}'ISDllCompileScript'{$ELSE}'ISDllCompileScriptW'{$ENDIF};
type
TCompScriptProc = function(const Params: TCompileScriptParamsEx): Integer; stdcall;
PAppData = ^TAppData;
TAppData = record
Lines: TStringList;
LineNumber: Integer;
StatusLabel: TLabel;
ProgressBar: TProgressBar;
end;
type
TForm1 = class(TForm)
Label1: TLabel;
Button1: TButton;
ProgressBar1: TProgressBar;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
FCompLibHandle: HMODULE;
FCompScriptProc: TCompScriptProc;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FCompLibHandle := SafeLoadLibrary(CompPath + CompLib);
if FCompLibHandle <> 0 then
FCompScriptProc := GetProcAddress(FCompLibHandle, CompScriptProc);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FCompLibHandle <> 0 then
FreeLibrary(FCompLibHandle);
end;
function CompilerCallbackProc(Code: Integer; var Data: TCompilerCallbackData;
AppData: Longint): Integer; stdcall;
begin
// in every stage you can cancel the compilation if you pass e.g. a Boolean
// field through the AppData by using the following line:
// Result := iscrRequestAbort;
Result := iscrSuccess;
case Code of
iscbReadScript:
with PAppData(AppData)^ do
begin
if Data.Reset then
LineNumber := 0;
if LineNumber < Lines.Count then
begin
Data.LineRead := PChar(Lines[LineNumber]);
Inc(LineNumber);
end;
end;
iscbNotifyStatus:
Form1.Label1.Caption := Data.StatusMsg;
iscbNotifyIdle:
begin
with PAppData(AppData)^ do
begin
ProgressBar.Max := Data.CompressProgressMax;
ProgressBar.Position := Data.CompressProgress;
StatusLabel.Caption := Format('Bitrate: %d B/s; Remaining time: %d s',
[Data.BytesCompressedPerSecond, Data.SecondsRemaining]);
Application.ProcessMessages;
end;
end;
iscbNotifySuccess:
ShowMessageFmt('Yipee! Compilation succeeded; Output: %s', [Data.OutputExeFilename]);
iscbNotifyError:
ShowMessageFmt('An error occured! File: %s; Line: %d; Message: %s', [Data.ErrorFilename,
Data.ErrorLine, Data.ErrorMsg]);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
CustData: TAppData;
CompParams: TCompileScriptParamsEx;
begin
if Assigned(FCompScriptProc) then
begin
CustData.Lines := TStringList.Create;
try
CustData.Lines.LoadFromFile('c:\Program Files (x86)\Inno Setup 5\Examples\Example1.iss');
CustData.LineNumber := 0;
CustData.StatusLabel := Label1;
CustData.ProgressBar := ProgressBar1;
CompParams.Size := SizeOf(CompParams);
CompParams.CompilerPath := CompPath; // path to the folder containing *.e32 files (InnoSetup install folder)
CompParams.SourcePath := 'c:\Program Files (x86)\Inno Setup 5\Examples\'; // path to the script file to be compiled
CompParams.CallbackProc := CompilerCallbackProc; // callback procedure which the compiler calls to read the script and for status notifications
Pointer(CompParams.AppData) := #CustData; // custom data passed to the callback procedure
CompParams.Options := ''; // additional options; see CompInt.pas for description
if FCompScriptProc(CompParams) <> isceNoError then
ShowMessage('Compiler Error');
finally
CustData.Lines.Free;
end;
end;
end;
end.

Why doesn't this D2006 code to fade a PNG Image work?

This question springs from an earlier one. Most of the code is from suggested answers that probably worked in later versions of Delphi. In D2006 I don't get the full range of opacity, and the transparent part of the image shows as white.
Image is from http://upload.wikimedia.org/wikipedia/commons/6/61/Icon_attention_s.png.
It is loaded from the PNGImageCollection into the TImage at run-time because I have found you have to do this as the image doesn't remain intact after the DFM is saved. For the purposes of demonstrating the behaviour you probably don't need the PNGImageCollection and can just load the PNG image into the TImage at design time and then run it from the IDE.
There are four buttons on the form - each one sets a different value of opacity. Opacity=0 works fine (paintbox image is not visible, opacity=16 looks OK except for the white background, opacity=64, 255 are similar - the opacity seems to saturate at around 10%.
Any ideas as to what's up?
unit Unit18;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, pngimage, StdCtrls, Spin, PngImageList;
type
TAlphaBlendForm = class(TForm)
PaintBox1: TPaintBox;
Image1: TImage;
PngImageCollection1: TPngImageCollection;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure PaintBox1Paint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
FOpacity : Integer ;
FBitmap : TBitmap ;
{ Private declarations }
public
{ Public declarations }
end;
var
AlphaBlendForm: TAlphaBlendForm;
implementation
{$R *.dfm}
procedure TAlphaBlendForm.Button1Click(Sender: TObject);
begin
FOpacity:= 0 ;
PaintBox1.Invalidate;
end;
procedure TAlphaBlendForm.Button2Click(Sender: TObject);
begin
FOpacity:= 16 ;
PaintBox1.Invalidate;
end;
procedure TAlphaBlendForm.Button3Click(Sender: TObject);
begin
FOpacity:= 64 ;
PaintBox1.Invalidate;
end;
procedure TAlphaBlendForm.Button4Click(Sender: TObject);
begin
FOpacity:= 255 ;
PaintBox1.Invalidate;
end;
procedure TAlphaBlendForm.FormCreate(Sender: TObject);
begin
Image1.Picture.Assign (PngImageCollection1.Items [0].PNGImage) ;
FBitmap := TBitmap.Create;
FBitmap.Assign(Image1.Picture.Graphic);//Image1 contains a transparent PNG
FBitmap.PixelFormat := pf32bit ;
PaintBox1.Width := FBitmap.Width;
PaintBox1.Height := FBitmap.Height;
end;
procedure TAlphaBlendForm.PaintBox1Paint(Sender: TObject);
var
fn: TBlendFunction;
begin
fn.BlendOp := AC_SRC_OVER;
fn.BlendFlags := 0;
fn.SourceConstantAlpha := FOpacity;
fn.AlphaFormat := AC_SRC_ALPHA;
Windows.AlphaBlend(
PaintBox1.Canvas.Handle,
0,
0,
PaintBox1.Width,
PaintBox1.Height,
FBitmap.Canvas.Handle,
0,
0,
FBitmap.Width,
FBitmap.Height,
fn
);
end;
end.
** This code (using graphics32 TImage32) almost works **
unit Unit18;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, pngimage, StdCtrls, Spin, PngImageList, GR32_Image;
type
TAlphaBlendForm = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Image321: TImage32;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
AlphaBlendForm: TAlphaBlendForm;
implementation
{$R *.dfm}
procedure TAlphaBlendForm.Button1Click(Sender: TObject);
begin
Image321.Bitmap.MasterAlpha := 0 ;
end;
procedure TAlphaBlendForm.Button2Click(Sender: TObject);
begin
Image321.Bitmap.MasterAlpha := 16 ;
end;
procedure TAlphaBlendForm.Button3Click(Sender: TObject);
begin
Image321.Bitmap.MasterAlpha := 64 ;
end;
procedure TAlphaBlendForm.Button4Click(Sender: TObject);
begin
Image321.Bitmap.MasterAlpha := 255 ;
end;
end.
** (UPDATE) This code (using graphics32 TImage32) DOES work **
The following code is successful in assigning a PNG image to the Graphics32.TImage32 at run-time. The PNG image with alpha channel is loaded into a TPNGImageCollection (really useful component as it allows mixtures of images of arbitrary size) at design time. On form creation it is written to a stream, then read from the stream into the Image32 using LoadPNGintoBitmap32. Once this is done I can control the opacity by assigning to TImage32.Bitmap.MasterAlpha. No bothering with OnPaint handlers.
procedure TAlphaBlendForm.FormCreate(Sender: TObject);
var
FStream : TMemoryStream ;
AlphaChannelUsed : boolean ;
begin
FStream := TMemoryStream.Create ;
try
PngImageCollection1.Items [0].PngImage.SaveToStream (FStream) ;
FStream.Position := 0 ;
LoadPNGintoBitmap32 (Image321.Bitmap, FStream, AlphaChannelUsed) ;
finally
FStream.Free ;
end;
end ;
As David commented to the question, the alpha channel information is lost when you assign the graphic to the bitmap. As such there's no point in setting the pixel format to pf32bit after the assignment, apart from preventing AlphaBlend call to fail, there's no per-pixel alpha in the bitmap anyway.
But the png object knows how to draw on a canvas taking into consideration the transparency information. So the solution would involve drawing on the bitmap canvas instead of assigning the graphic, and then, since there's no Alpha channel, remove the AC_SRC_ALPHA flag from the BLENDFUNCTION.
Below is working code here on D2007:
procedure TAlphaBlendForm.FormCreate(Sender: TObject);
begin
Image1.Picture.LoadFromFile(
ExtractFilePath(Application.ExeName) + 'Icon_attention_s.png');
FBitmap := TBitmap.Create;
FBitmap.Width := Image1.Picture.Graphic.Width;
FBitmap.Height := Image1.Picture.Graphic.Height;
FBitmap.Canvas.Brush.Color := Color; // background color for the image
FBitmap.Canvas.FillRect(FBitmap.Canvas.ClipRect);
FBitmap.Canvas.Draw(0, 0, Image1.Picture.Graphic);
PaintBox1.Width := FBitmap.Width;
PaintBox1.Height := FBitmap.Height;
end;
procedure TAlphaBlendForm.PaintBox1Paint(Sender: TObject);
var
fn: TBlendFunction;
begin
fn.BlendOp := AC_SRC_OVER;
fn.BlendFlags := 0;
fn.SourceConstantAlpha := FOpacity;
fn.AlphaFormat := 0;
Windows.AlphaBlend(
PaintBox1.Canvas.Handle,
0,
0,
PaintBox1.Width,
PaintBox1.Height,
FBitmap.Canvas.Handle,
0,
0,
FBitmap.Width,
FBitmap.Height,
fn
);
end;
or without using a intermediate TImage:
procedure TAlphaBlendForm.FormCreate(Sender: TObject);
var
PNG: TPNGObject;
begin
PNG := TPNGObject.Create;
try
PNG.LoadFromFile(ExtractFilePath(Application.ExeName) + 'Icon_attention_s.png');
FBitmap := TBitmap.Create;
FBitmap.Width := PNG.Width;
FBitmap.Height := PNG.Height;
FBitmap.Canvas.Brush.Color := Color;
FBitmap.Canvas.FillRect(FBitmap.Canvas.ClipRect);
PNG.Draw(FBitmap.Canvas, FBitmap.Canvas.ClipRect);
PaintBox1.Width := FBitmap.Width;
PaintBox1.Height := FBitmap.Height;
finally
PNG.Free;
end;
end;

Resources