From Percentage in Non Decimal Numbers - delphi

I have this code to record a video in my program:
pbVideoProgress.Percent := Round((vidrec / MAX_REC) * 100);
Now my problem is I want to put a label on my program that will display a timer before the next video will be shown, I tried this:
Label1.Caption :=inttostr(pbVideoProgress.Percent) ;
But it will display the percentage from 100% until it reaches 0% (so next video will be shown).
How can I chang that percentage from % in a real number such as from 10, 9... until 0?

The code line you showed is not valid Delphi syntax. You have to use separate statements:
pbVideoProgress.Percent := Round((vidrec / MAX_REC) * 100);
...
Label1.Caption := IntToStr(100 - pbVideoProgress.Percent);

Related

How can I encode a real-time video from dynamically spaced frames?

I'm trying to create a video from a series of screenshots. The screenshots are in a database and have dynamic FPS (1-3 FPS). How can I create a video file with constant FPS?
Before performing av_packet_rescale_ts I tried to change the st^.codec.time_base.den value on the fly between 1 and 3.
This is the basic cycle of encoding of one picture:
repeat
fillchar(pkt, sizeof(TAVPacket), #0);
av_init_packet(#pkt);
(* encode the image *)
ret := avcodec_encode_video2(st^.codec, #pkt, frame, got_packet);
if (ret < 0) then
begin
writeln(format('Error encoding video frame: %s', [av_err2str(ret)]));
exit;
end;
if (got_packet > 0) then
begin
(* rescale output packet timestamp values from codec to stream timebase *)
av_packet_rescale_ts(#pkt, st^.codec.time_base, st^.time_base);
pkt.stream_index := st^.index;
log_packet(oc, #pkt);
(* Write the compressed frame to the media file. *)
av_interleaved_write_frame(oc, #pkt);
end;
inc(frame.pts);
until (av_compare_ts(frame.pts, st^.codec^.time_base, 1, av_make_q(1, 1)) >= 0);
Changing the FPS on the fly causes the video output to fail. If I don't change the st^.codec.time_base.den value the video speeds up and slows down.
There is no notion in ffmpeg of a dynamic timebase, so changing it during encoding is forbidden. But you are free to set the PTS of your frames before encoding to anything monotonically increasing.
You are not showing how you set the PTS in your example code. If you want a constant framerate, just ignore the timestamps from your database, count the frames and calculate a PTS according to the frame number (probably this is what ffmpeg is doing when you don't give it any PTS).
If your frames were recorded with a varying framerate, but you didn't record any timestamps for them, you cannot get a smooth looking video anymore.

Delphi printing with Canvas, align text to the right

I'm printing using TPrinter.Canvas and am trying to align the text to the right due to money displaying numbers.
The text is printed with this code:
Printer.Canvas.Textout(800, 100, 250.00); //charge1
Printer.Canvas.Textout(800, 200, 10.00); //charge2
Printer.Canvas.Textout(800, 300, 260.00); //Total Amount
How can I align the lines to display correctly?
Instead of using VCL's TextOut, you should use the Winapi DrawText and choose to align to the right using that within a given rectangle, specifically using DT_RIGHT. For example...
DrawText(Canvas.Handle, PChar(T), Length(T), R, DT_SINGLELINE or DT_RIGHT);
You could use the lower level device context Api's to more accurately control the text output, such as DrawText() as suggested by Jerry. However, doing this will require that you pre-calculate/set the bounding rectangle for your text output so that Windows knows the area which any alignment is relative to.
There are two potentially simpler alternatives:
Option 1 - For Fixed Width Fonts ONLY
This may be appropriate for you since you appear to be using a fixed-width font.
All we do here is introduce padding to your strings prior to output. This may be simpler than the calculations required for the output rectangles etc.
To do this, decide on the longest string you will accept for output and then add whatever spaces you need to the left of each string to ensure they are all the same length. With a fixed width font, a space takes up the same horizontal space as any other character so by making all strings the same length you will achieve "right alignment":
procedure PrintRightJustified(const aX, aY, aMaxLen: Integer; aValue: String);
var
s: String;
begin
s := aValue;
// Pad the string with spaces to the maximum length
if Length(s) < aMaxLen then
s := StringOfChar(' ', aMaxLen - Length(s)) + s;
Printer.Canvas.Textout(aX, aY, s);
end;
Then call this method to output your strings right-justified to a consistent maximum length, e.g. if 6 is your maximum length (999.99):
PrintRightJustified(800, 100, 6, '250.00'); //charge1
PrintRightJustified(800, 200, 6, '10.00'); //charge2
PrintRightJustified(800, 300, 6, '260.00'); //Total Amount
How to handle scenarios where the specified string is longer than the maximum width is left as an exercise.
Option 2 - For Any Font
Another way, which would work for fixed width or variable width fonts (e.g. Arial) would be to choose the position of the rightmost edge of the strings and calculate the X position relative to this based on the device width calculated for each string you are outputting:
procedure PrintRightJustified(const aRightX, aY; aValue: String);
var
leftX: Integer;
begin
leftX := aRightX - Printer.Canvas.TextWidth(aValue);
Printer.Canvas.Textout(leftX, aY, aValue);
end;
You don't need to specify a maximum width for this version, it works regardless of font being used:
PrintRightJustified(900, 100, '250.00'); //charge1
PrintRightJustified(900, 200, '10.00'); //charge2
PrintRightJustified(900, 300, '260.00'); //Total Amount
This does mean that text may potentially "bleed" too far to the left without some mechanism to detect and handle this. Again, If that is a concern then either move the right-edge further across or you will have to come up with a way of handling it (left as an exercise for you since we don't know what your precise requirements are).
To the precedent answer!
With the above given code (Answer 1) I couldn'get my number aligned with one space.
After some thinking, I check on Word with my font, Times New Roman, how many space I need to get the number aligned, (the decimal dot have to be vertically aligned).
I noticed that two spaces were necessary not only one.
I juste wrote the command two times:
s:= StringOfChar(' ', aMaxLen - Length(s)) + s;
s:= StringOfChar(' ', aMaxLen - Length(s)) + s;
Man could do
s:= StringOfChar(' ', aMaxLen - Length(s))+ StringOfChar(' ', aMaxLen -
Length(s)) + s;
Excuse my english but it is not my mother language.
I hope it is a bit more understandable.
To Option 1 - For Fixed Width Fonts ONLY
First on word you check how many space you need to align your number. For Times I did need 2 spaces so you just double the procedure line:
s := StringOfChar(' ', aMaxLen - Length(s)) + s;
s := StringOfChar(' ', aMaxLen - Length(s)) + s;

Delphi Printer.Canvas.TextWidth property

I'm trying to set the column width for printing with my Delphi application. Whatever I type for the string doesn't make the width fewer. Actually I don't understand why the property returns a string, it should return width in pixels.
My code is
Printer.Canvas.TextWidth('M');
Edit: i understood it doesn't return a string but what does 'M' mean? what i m trying to do is making a column narrower. my code is located at sudrap.org/paste/text/19688
Edit: i m afraid i couldn t explain the problem clearly, i m sorry. i want it to print like this:
not like this:
Try to check TextRect function. Using this function you can specify the target rectangle where the text should be printed, so you can narrow your column.
uses Graphics;
var
Text: string;
TargetRect: TRect;
begin
Printer.BeginDoc;
Text := 'This is a very long text';
// now I'll specify the rectangle where the text will be printed
// it respects the rectangle, so the text cannot exceed these coordinates
// with the following values you will get the column width set to 50 px
TargetRect := Rect(Margin, Y, Margin + 50, Y + LineHeight);
Printer.Canvas.Font.Size := 11;
Printer.Canvas.Font.Name := 'Arial';
Printer.Canvas.Font.Style := [fsBold];
Printer.Canvas.TextRect(TargetRect, Text);
Printer.EndDoc;
end;
Except this you can get with the TextRect function set of the formatting flags which can help you to specify e.g. text alignment, word wrap etc. For instance if you would like to center the text horizontally in the specified rectangle [100;100], [250;117] you can use the following.
Text := 'Centered text';
TargetRect := Rect(100, 100, 250, 117);
Printer.Canvas.TextRect(TargetRect, Text, [tfCenter]);
Or in your case might be more useful word wrap. Here's an example with rectangle [100;100], [200;134] where the text is automatically wrapped by the TextRect function.
Text := 'This is a very long text';
TargetRect := Rect(100, 100, 200, 134);
Printer.Canvas.TextRect(TargetRect, Text, [tfWordBreak]);
If you use a fixed width font on the canvas, you should get the same result for all single-character strings. If you use a variable width font, each character will return a different width.
Printer.Canvas.Font.Name = 'Courier New';
Printer.Canvas.Font.Size = 13;
ColumnWidth := Printer.Canvas.TextWidth('M');
For different fonts or different font sizes, you will get different results.
I don't see how you're saying it returns text. If it were returning text your code wouldn't even compile, you would be getting errors when you tried to multiply a number by text. You even convert it to a string for display purposes.
Are you being mislead by the fact that with a variable-width font that you'll get different answers for different strings? You can even get different answers for the same letters in a different order. For some fonts "WAM" will produce a different answer than "WMA" because of how the W and A fit together.
Also, you're simply assuming that your labels are narrower than 15 M's. While this is generally the case it's not good programming practice. Instead, you should be asking for the width of each label and using something a bit above the biggest answer.
Finally, your handling of LineHeight is atrocious. Simply add 300 to y if that's what you really want although it should be some multiple of your line height, not a fixed value. You'll get VERY different results from your code off printers with different DPI settings.
Have you even tried stepping through this code with the debugger to see what's going on internally? Your output of the position to the printout suggests you aren't using the debugger.

Why the form moving effect is not happening the first time?

I'm using this code to show a form with a messenger-like popup:
TimeSleep := 5;
for i := 1 to FormHeight do
begin
Form.Top := Form.Top - 1;
Sleep(TimeSleep);
end
It's working smoothly but only from the second time. I mean I have a button on my form that invokes the loop and the first time I click the form shows suddenly without the sliding effect. If I click again, the effect works.
How can I have it working from the first time?
Thanks.
You are not posting enough code to be sure about it, but I assume that your form isn't visible or doesn't even have a window handle before you click the button the first time, so the moving of the form isn't visible. If you insert
Form.Show;
Form.Update;
before the loop things should work the first time too. Note the call to Update(), it is needed to really show the form that has been made visible in the previous line.
Similarly you should insert a call to Update() after the change to the Top property inside your loop - it does what the call to Application.ProcessMessages() does too, without being such a sledgehammer. Try to make do without Application.ProcessMessages() whenever there is a better way, search Stack Overflow for discussions regarding this.
Two more pieces of advice regarding your loop:
Movement will not be smooth if anything causes your Sleep() to be longer than the 5 milliseconds you request - it's much better to calculate the amount to decrement the top coordinate of the form from the elapsed time since the last movement.
There is no way anybody will see your form move in 1 pixel increments - the human eye and brain aren't fast enough to gather and process information at that speed, and even cats and insects in your room will only see the 60 or so updates your monitor does in each second. So a parameter to Sleep() of 20 or even 50 is much more sensible than 5. Adjust the form movement accordingly.
Edit:
Here is some sample code that creates constant movement of the form even with differing delays:
uses
MMSystem;
procedure TForm1.Button1Click(Sender: TObject);
var
Frm: TForm;
TicksStart, TicksNow: Longword;
Progress: Single;
begin
Frm := TForm.CreateNew(Self);
try
// slide a form of 600 by 500 pixel into view over a period of 100 millis
Frm.SetBounds((Screen.Width - 600) div 2, -500, 600, 500);
Frm.Show;
TicksStart := timeGetTime;
while True do begin
Sleep(15);
TicksNow := timeGetTime;
if TicksNow - TicksStart >= 1000 then
break;
Progress := (TicksNow - TicksStart) / 1000;
Frm.Top := - Round((1.0 - Progress) * 500);
end;
Frm.Top := 0;
Sleep(500);
finally
Frm.Free;
end;
end;
Try adding "Application.ProcessMessages;" before sleep.

Delphi: Autoscale TEdit based on text length does not work when removing chars

I have an input edit field where the user can enter data. I want the box width to be at least 191px (min) and maximum 450px (max).
procedure THauptform.edtEingabeChange(Sender: TObject);
begin
// Scale
if Length(edtEingabe.Text) > 8 then
begin
if Hauptform.Width <= 450 then
begin
verschiebung := verschiebung + 9;
// The initial values like 'oldedtEingabeWidth' are global vars.
edtEingabe.Width := oldedtEingabeWidth + verschiebung;
buDo.Left := oldbuDoLeft + verschiebung;
Hauptform.Width := oldHauptformWidth + verschiebung;
end;
end;
end;
This works for ENTERING text. But when I delete one char, it does not scale back accordingly.
In your code, nothing will happen when your text is less than 8 characters long.
Also, I don't see any condition under which your width becomes smaller. It only becomes larger (by 9) with each iteration.
By the way, you appear to be multiplying by 9 as an average character width. You can use Canvas.TextWidth to determine the actual width required by the text without estimating.
If you want to use "9" anyway, you should name it as a constant to make clear what it is.
Quick and dirty using TextWidth:
const
MAX_EINGABE_WIDTH = 450;
MIN_EINGABE_WIDTH = 191;
procedure THauptform.edtEingabeChange(Sender: TObject);
var Width: Integer;
begin
// Scale
Width := edtEingabe.Canvas.TextWidth(edtEingabe.Text);
if Width > MAX_EINGABE_WIDTH then
Width := MAX_EINGABE_WIDTH
else if Width < MIN_EINGABE_WIDTH then
Width := MIN_EINGABE_WIDTH
edtEingabe.Width := Width;
end;
You're just adding 9 everytime the text changes and the length is grater than 8 - regardless of the change. You need to make it a function based on the length instead.
Something like this would do the trick:
procedure THauptform.edtEingabeChange(Sender: TObject);
var
len: integer;
additionalWidth: integer;
begin
len := Length(edtEingabe.Text);
if len <=8 then
additionalWidth:=0
else
additionalWidth:=(len-8)*9; //Assuming we need an extra 9 pixels per character after the 8th one
if additionalWidth > 259 then additionalWidth := 259; // maximum - minimum
edtEingabe.Width := 191 + additionalWidth;
Width := OriginalFormWidth + additionalWidth; // You'll need to know what the minimum width of your form is
end;
This isn't really a very pretty solution, though - changing all of those properties in the same way is ugly. Instead, since it appears you're also resizing the form, you can change the Anchors property of your edit box to make it maintain its margin to the right side as well, and only resize your form.
However, you probably want to consider if this is really a good idea. Why not let the input field just have a single size? In general, it looks better if windows don't resize on their own.
Do something like this:
procedure THauptform.edtEingabeChange(Sender: TObject);
var
Edit:TEdit;
begin
Edit := TEdit(Sender);
Edit.Width := Canvas.TextWidth(Edit.Text+' |')+
Edit.Padding.Left+
Edit.Padding.Right;
end;
Note 1: Don't manually try to limit the size. Instead, set Constraints.MinWidth and Constraints.MaxWidth via the property editor. That leaves your code clean and useless GUI stuff like this in the .dfm.
Note 2: TEdit doesn't have any public canvas property that you can use to get the text width.
Note 3: I don't like this kind of interface with growing and shrinking inputs, but it's probably just a matter of personal taste.

Resources