Author |
Topic |
PeterPanino
935 Posts |
Posted - Feb 14 2024 : 09:44:38
|
I capture a window with CaptureFromScreen multiple times, appending each time the images to a TIEMultiBitmap, and then at the end, save the MultiBitmap as a GIF file:
FMultiBitmap.Write(ThisTempFile);
The saving of the GIF file takes more than 10 seconds (!) with the MultiBitMap containing appr. 100 images. Is there a way to make this more efficient? For example by:
1. CaptureFromScreen to a more efficient image format?
2. Converting the MultiBitmap images to a more efficient image format before saving?
3. Is it possible to handle a progress bar while saving the MultiBitmap to show the saving progress? |
|
PeterPanino
935 Posts |
Posted - Feb 14 2024 : 10:31:14
|
I have now set the MultiBitmap Params each time when appending an image:
// append the screenshot bitmap to the gif multi bitmap:
var idx := FMultiBitmapGIF.AppendImage(FIOGIFScreenshot.IEBitmap);
FMultiBitmapGIF.Params[idx].BitsPerSample := 8;
FMultiBitmapGIF.Params[idx].SamplesPerPixel := 1;
FMultiBitmapGIF.Params[idx].GIF_DelayTime := 10;
Unfortunately, this did not optimize the efficiency regarding file size and saving time. |
|
|
PeterPanino
935 Posts |
Posted - Feb 14 2024 : 13:29:01
|
I have now implemented the progress bar:
FMultiBitmap := TIEMultiBitmap.Create; // for storing the animated GIF
FMultiBitmap.OnProgress := MultiBitmapProgress;
procedure TForm1.MultiBitmapProgress(Sender: TObject; per: integer);
begin
ProgressBarSave.Position := per;
end;
And it works:
However, saving to the GIF file when the MultiBitmap contains a few hundred images takes a VERY LONG time! Isn't there a way to make this more efficient? |
|
|
xequte
38690 Posts |
Posted - Feb 14 2024 : 14:20:13
|
Hi Peter
I think the main issue will be the size of the images you are saving. I presume the resulting GIF file is huge (many MB).
What is the width and height of the GIF frames?
Nigel Xequte Software www.imageen.com
|
|
|
PeterPanino
935 Posts |
Posted - Feb 14 2024 : 14:58:24
|
Hi Nigel,
The image sizes can vary depending on the window from which the user chooses to make a GIF video. Is there a way to optimize the single images before saving them to the GIF file?
The documentation describes a feature IEOptimizeGIF that optimizes an EXISTING animated GIF file.
Is there such a similar feature for a GIF file already loaded in the TIEMultiBitmap in memory? That would be optimal for reducing the time to save the TIEMultiBitmap to a GIF file. |
|
|
PeterPanino
935 Posts |
Posted - Feb 14 2024 : 16:38:19
|
Improved the UI:
|
|
|
xequte
38690 Posts |
Posted - Feb 14 2024 : 22:28:19
|
Hi Peter
IEOptimizeGIF() is a file only method at this time. It should not greatly improve save speed to optimize the GIF before the save because usually the optimization is not significant. That is true for standard animated GIFs, but in this case you may get a better result. Have you compared the file size before and after calling IEOptimizeGIF()?
Have you considered using a true video format such as AVI?
Nigel Xequte Software www.imageen.com
|
|
|
PeterPanino
935 Posts |
Posted - Feb 15 2024 : 02:02:19
|
To implement a procedure for optimizing GIFs within an ImageEn.TIEMultiBitmap object, where consecutive, largely identical images are combined into a single image, one can follow these steps. This approach leverages the TIOParams.GIF_DelayTime property to extend the display time of the first image in a series of identical or nearly identical images and then delete the subsequent similar images. This method can significantly reduce the size of the ImageEn.TIEMultiBitmap object without a noticeable loss in quality or functionality.
Steps for GIF Optimization:
1. Comparing Images: Determine whether two consecutive images are identical or nearly identical. This could be done through a direct pixel comparison or through a less computationally intensive method, such as comparing hash values of the images.
2. Combining Images: If two consecutive images are identified as identical, adjust the TIOParams.GIF_DelayTime of the first image by summing the delay times of the identical images.
3. Removing Subsequent Identical Images: Delete the subsequent images that have been combined with the first identical image.
Here's a pseudocode example illustrating these steps:
procedure OptimizeGIFImages(MultiBitmap: ImageEn.TIEMultiBitmap);
var
i: Integer;
Identical: Boolean;
begin
i := 0;
while i < MultiBitmap.Count - 1 do
begin
Identical := CompareImages(MultiBitmap.Bitmap[i], MultiBitmap.Bitmap[i + 1]);
if Identical then
begin
// Increase the GIF_DelayTime of the current image
MultiBitmap.Params[i].GIF_DelayTime := MultiBitmap.Params[i].GIF_DelayTime + MultiBitmap.Params[i + 1].GIF_DelayTime;
// Delete the subsequent identical image
MultiBitmap.Delete(i + 1);
end
else
Inc(i);
end;
end;
function CompareImages(Image1, Image2: TIEBitmap): Boolean;
begin
// Implement a function to compare two images
// Return True if the images are identical or nearly identical, otherwise False
end; |
|
|
xequte
38690 Posts |
Posted - Feb 15 2024 : 15:10:06
|
Sorry Peter, I assumed the images in your TIEMultiBitmap were already unique.
Rather than filling the TIEMultiBitmap and then de-duping, you would be better to not append an image if it identical to the last one added.
i.e.
// Only add image if not identical to the last image
if ( mbmp.Count > 0 ) and ( newBitmap.GetHash = mbmp.ImageHash[ mbmp.Count - 1 ]) then
begin
// Image is same as last one, increase display time
mbmp.Params[ mbmp.Count - 1 ].GIF_DelayTime := mbmp.Params[ mbmp.Count - 1 ].GIF_DelayTime + newInterval;
end
else
begin
// New image
mbmp.AppendImage( newBitmap );
mbmp.Params[ mbmp.Count - 1 ].GIF_DelayTime := newInterval;
end;
This method uses an MD5 hash as a quick way to compare images, but you might prefer to exclude images that are "mostly" identical. Take a look at the options for CompareWith:
http://www.imageen.com/help/TImageEnProc.CompareWith.html
Nigel Xequte Software www.imageen.com
|
|
|
PeterPanino
935 Posts |
Posted - Feb 15 2024 : 16:53:18
|
Hi Nigel,
Let's discuss this later, as there is another problem:
When I save the TIEMultiBitmap to a GIF file:
FMultiBitmapGIF.Write(ThisTempFile); ...and then play it in an external viewer such as IrfanView, then it plays at its regular size and at the original speed. Everything OK!
But when I assign the TIEMultiBitmap to a TImageEnMView on another form (by copying the Params):
formPreview.ImageEnMView1.AssignEx(FMultiBitmapGIF, True);
formPreview.ImageEnMView1.Playing := True; ...then the GIF plays way too fast, and the displayed size of the GIF is too small!
I have not found any settings to make the GIF play 1. at its original size and 2. at the original speed set by Params.
The documentation says: "The default playback speed is set for each frame by its ImageDelayTime time." But this is not the case in this case. |
|
|
xequte
38690 Posts |
|
PeterPanino
935 Posts |
Posted - Feb 16 2024 : 01:44:08
|
Hi Nigel,
Please read my posting - as I wrote, I DO pass TRUE when assigning:
formPreview.ImageEnMView1.AssignEx(FMultiBitmapGIF, True);
formPreview.ImageEnMView1.Playing := True;
Regarding the size: FMultiBitmapGIF is not a control but a TIEMultiBitmap Object. And the formPreview.ImageEnMView1.Zoom = 100. |
|
|
PeterPanino
935 Posts |
Posted - Feb 16 2024 : 03:03:16
|
I have now tested it:
1. Saved the GIF from the TIEMultiBitmap object:
FMultiBitmapGIF.Write(ThisTempFile);
...and then loaded it in the \Demos\ImageEditing\AnimatedGIF demo: It plays at normal speed.
But when ASSIGNING the TIEMultiBitmap (by passing TRUE) to the TImageEnMView, it plays way too fast. This proves that ASSIGNING does in fact not pass the Params although I did pass True:
formPreview.ImageEnMView1.AssignEx(FMultiBitmapGIF, True);
formPreview.ImageEnMView1.Playing := True;
|
|
|
PeterPanino
935 Posts |
Posted - Feb 16 2024 : 04:00:12
|
I have now empirically found the bug - it is in TImageEnMView.AssignEx:
I have measured ImageEnMView1.ImageDelayTime[0] right after assigning:
formPreview.ImageEnMView1.AssignEx(FMultiBitmapGIF, TRUE);
CodeSite.Send('formPreview.ImageEnMView1.ImageDelayTime[0]', formPreview.ImageEnMView1.ImageDelayTime[0]);
... which gives this result:
TformCapture.SpeedButtonStopClick: ImageDelayTime[0] = 0.00
This proves that AssignEx, in fact, does not copy the Params, although CopyParams is passed as TRUE! TImageEnMView sets ImageDelayTime[n] correctly after loading a GIF from a file, but NOT when assigning with AssignEx!
Can you confirm this?
|
|
|
PeterPanino
935 Posts |
Posted - Feb 16 2024 : 06:08:37
|
I have now solved the SIZE problem:
As TImageEnMView during the animation incorrectly sets the animated image size to the TImageEnMView THUMBNAIL size (and not to each frame image's original size as it correctly should be!), I have to correct this manually:
formPreview.ImageEnMView1.AssignEx(FMultiBitmapGIF, True);
formPreview.ImageEnMView1.ThumbWidth := FMultiBitmapGIF.ImageWidth[0];
formPreview.ImageEnMView1.ThumbHeight := FMultiBitmapGIF.ImageHeight[0];
Now, the animated image size has the exact original size.
BTW, there should be a separate TImageEnAnimationPlayer component that fixes all these issues and only plays animated GIFs, etc. (and can load them by both file loading and assigning). |
|
|
PeterPanino
935 Posts |
Posted - Feb 16 2024 : 09:25:01
|
The animation speed issue is still not solved:
I capture the window in a Timer event (Interval = 100) from a TImageEnIO object:
procedure TformCapture.TimerCaptureTimer(Sender: TObject);
begin
// create the screenshot from the target window:
var h := FTargetHandle;
if FIOGIFScreenshot.CaptureFromScreen(iecsSpecifiedWindow, FIncludeMouseCursor, h) = False then
FIOGIFScreenshot.CaptureFromScreen(iecsSpecifiedWindow2, FIncludeMouseCursor, h);
// append the screenshot bitmap to the gif multibitmap:
var idx := FMultiBitmapGIF.AppendImage(FIOGIFScreenshot.IEBitmap);
FMultiBitmapGIF.Params[idx].BitsPerSample := 8;
FMultiBitmapGIF.Params[idx].SamplesPerPixel := 1;
FMultiBitmapGIF.Params[idx].GIF_DelayTime := 10; // Display each frame for 1/10th of a second = 100 milliseconds
end;
After the Timer has been disabled, I assign the MultiBitmap to ImageEnMView1:
// Show Preview:
if CheckBoxPreview.Checked then
begin
formPreview.ImageEnMView1.Clear;
formPreview.ImageEnMView1.AssignEx(FMultiBitmapGIF, True); // CopyParams does not work!
formPreview.ImageEnMView1.ThumbWidth := FMultiBitmapGIF.ImageWidth[0];
formPreview.ImageEnMView1.ThumbHeight := FMultiBitmapGIF.ImageHeight[0];
for var i := 0 to formPreview.ImageEnMView1.ImageCount - 1 do
formPreview.ImageEnMView1.ImageDelayTime[i] := FMultiBitmapGIF.Params[i].GIF_DelayTime / 10; // delay ratio
formPreview.Show;
SetForegroundWindow(formPreview.Handle);
formPreview.ImageEnMView1.Playing := True;
formPreview.ImageEnMView1.Zoom := 100;
end;
However, the animation is still running too fast (the last two digits are SECONDS!):
What is wrong here? |
|
|
PeterPanino
935 Posts |
Posted - Feb 16 2024 : 12:14:24
|
I found the error - the delay ratio was wrong. It should be:
for var i := 0 to formPreview.ImageEnMView1.ImageCount - 1 do
formPreview.ImageEnMView1.ImageDelayTime[i] := FMultiBitmapGIF.Params[i].GIF_DelayTime * 10; // delay ratio
Now, it works perfectly:
|
|
|
PeterPanino
935 Posts |
Posted - Feb 16 2024 : 16:45:13
|
As for the optimizing, I decided to do the optimizing AFTER the capture because: With large images, the process of getting the hash could potentially overlap two timer event cycles and thus lead to errors.
So, I implemented the optimization:
procedure OptimizeMultiBitmap(AMultiBitmap: TIEMultiBitmap);
var
i: Integer;
begin
if not Assigned(AMultiBitmap) then EXIT;
if AMultiBitmap.Count < 2 then EXIT;
// Iterate through all images of the AMultiBitmap from High to Low, excluding the first one
// because there's no previous image to compare with for the first image:
for i := AMultiBitmap.Count - 1 downto 1 do
begin
// Compare the current image hash with the previous image hash:
if AMultiBitmap.ImageHash[i] = AMultiBitmap.ImageHash[i - 1] then
begin
// If the hashes are the same, sum the current image's GIF_DelayTime
// to the previous image's GIF_DelayTime:
AMultiBitmap.Params[i - 1].GIF_DelayTime := AMultiBitmap.Params[i - 1].GIF_DelayTime + AMultiBitmap.Params[i].GIF_DelayTime;
// Delete the current image since it's a duplicate:
AMultiBitmap.DeleteImage(i);
end;
end;
end;
It works perfectly: When capturing the clock test video, I could reduce the number of GIF frames from over 300 to 26 while the animation remains the same! |
|
|
xequte
38690 Posts |
Posted - Feb 16 2024 : 18:02:33
|
Hi Peter
I'm sorry, i cannot reproduce that. I tested as follows:
var
mbmp: TIEMultiBitmap;
begin
mbmp := TIEMultiBitmap.Create();
mbmp.Read( 'D:\Testing_Multimedia\GIF_Animated\ABUELAT.GIF' );
ImageEnMView2.Clear;
ImageEnMView2.AssignEx( mbmp, True );
lblDebug.Caption := IntToStr( mbmp.Params[0].GIF_DelayTime ) + '/' + IntToStr( ImageEnMView2.MIO.Params[0].GIF_DelayTime );
mbmp.Free;
end;
lblDebug.Caption was "25/25" as expected.
Nigel Xequte Software www.imageen.com
|
|
|
PeterPanino
935 Posts |
Posted - Feb 17 2024 : 04:10:42
|
Hi Nigel
The difference may be that you are READING the GIF from a FILE. I will try to make a test project and send it to you. |
|
|
PeterPanino
935 Posts |
Posted - Feb 17 2024 : 04:17:43
|
BTW, I need an "AutoShrink" property for the TImageEnMView "Playing" mode. Is there a workaround for that? |
|
|
Topic |
|