I have this procedure that lets a user select an image file. Once selected, it should be displayed in a TImage component.
Why is it not showing (even when the file path is shown in a label)?
procedure TForm1.btn1_select_imgClick(Sender: TObject);
var
OpenDialog: TOpenDialog;
begin
OpenDialog := TOpenDialog.Create(nil);
OpenDialog.Filter := 'All Files|*.*';
OpenDialog.Options := [ofPathMustExist, ofFileMustExist];
try
if OpenDialog.Execute then
begin
lbl_selected_file.Caption := OpenDialog.FileName;
img1.Picture.LoadFromFile(OpenDialog.FileName);
OriginalImage := TBitmap.Create;
OriginalImage.Assign(img1.Picture.Bitmap);
end;
finally
OpenDialog.Free;
end;
end;
Expected result:
Full code:
unit image_blurrr_unit;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Imaging.Jpeg;
var
OriginalImage: TBitmap;
PixellatedImage: TBitmap;
type
TForm1 = class(TForm)
btn1_select_img: TButton;
btn2_select_output_path: TButton;
lbl_selected_file: TLabel;
lbl_output_path: TLabel;
img1: TImage;
pnl_img: TPanel;
pnl_btns: TPanel;
procedure btn1_select_imgClick(Sender: TObject);
procedure btn2_select_output_pathClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btn1_select_imgClick(Sender: TObject);
var
OpenDialog: TOpenDialog;
begin
OpenDialog := TOpenDialog.Create(nil);
OpenDialog.Filter := 'All Files|*.*';
OpenDialog.Options := [ofPathMustExist, ofFileMustExist];
try
if OpenDialog.Execute then
begin
lbl_selected_file.Caption := OpenDialog.FileName;
img1.Picture.LoadFromFile(OpenDialog.FileName);
OriginalImage := TBitmap.Create;
OriginalImage.Assign(img1.Picture.Bitmap);
end;
finally
OpenDialog.Free;
end;
end;
procedure TForm1.btn2_select_output_pathClick(Sender: TObject);
var
FileOpenDialog: TFileOpenDialog;
OutputImage: TBitmap;
begin
FileOpenDialog := TFileOpenDialog.Create(nil);
try
FileOpenDialog.Options := [fdoPickFolders];
FileOpenDialog.Title := 'Select Output Path';
if FileOpenDialog.Execute then
begin
OutputImage := TBitmap.Create;
try
// Create a copy of the original image
OutputImage.Assign(OriginalImage);
// Apply the blur effect to the output image here...
// Save the output image to the selected folder
OutputImage.SaveToFile(FileOpenDialog.FileName + '\output.jpg');
finally
OutputImage.Free;
end;
end;
finally
FileOpenDialog.Free;
end;
end;
end.
The answer is in the TPicture.Bitmap documentation:
If Bitmap is referenced when the picture contains a Metafile or Icon graphic 1, the graphic won't be converted (Types of Graphic Objects). Instead, the original contents of the picture are discarded and Bitmap returns a new, blank bitmap.
1: just replace "a Metafile or Icon graphic" with "any non-BMP graphic".
So, if the user selects a non-.bmp file (which is likely the case, since .bmp files are rarely used nowadays, in favor of other file formats, like PNG), then accessing img1.Picture.Bitmap will wipe out the current image that has been loaded into img1.Picture, replaced with a blank TBitmap object (which you then assign to your OriginalImage object). That is why you don't see anything being displayed in the TImage.
The solution is to access the original loaded image via the TPicture.Graphic property, instead of the TPicture.Bitmap property, eg:
OriginalImage.Assign(img1.Picture.Graphic);
That being said, there is another problem with your code:
OutputImage.SaveToFile(FileOpenDialog.FileName + '\output.jpg');
OutputImage is a TBitmap, so its SaveToFile() method can only create a BMP-encoded file. So, you are creating a BMP file with a .jpg file extension, which does not make it a valid JPG image. To do that, you would need to use TJPEGImage instead of TBitmap, eg:
uses
..., Vcl.Imaging.jpeg;
procedure TForm1.btn2_select_output_pathClick(Sender: TObject);
var
FileOpenDialog: TFileOpenDialog;
BlurredImage: TBitmap;
OutputImage: TJPEGImage;
begin
FileOpenDialog := TFileOpenDialog.Create(nil);
try
FileOpenDialog.Options := [fdoPickFolders];
FileOpenDialog.Title := 'Select Output Path';
if FileOpenDialog.Execute then
begin
BlurredImage := TBitmap.Create;
try
// Create a copy of the original image
BlurredImage.Assign(OriginalImage);
// Apply the blur effect to the output image here...
// Save the output image to the selected folder
OutputImage := TJPEGImage.Create;
try
OutputImage.Assign(BlurredImage);
OutputImage.SaveToFile(FileOpenDialog.FileName + '\output.jpg');
finally
OutputImage.Free;
end;
finally
BlurredImage.Free;
end;
end;
finally
FileOpenDialog.Free;
end;
end;
UPDATE:
That said, if you want the output file to be in the same format as the input file, then you can do this instead:
var
OriginalImage: TGraphic;
OriginalExt: String;
...
procedure TForm1.btn1_select_imgClick(Sender: TObject);
var
OpenDialog: TOpenDialog;
begin
OpenDialog := TOpenDialog.Create(nil);
OpenDialog.Filter := 'All Files|*.*';
OpenDialog.Options := [ofPathMustExist, ofFileMustExist];
try
if OpenDialog.Execute then
begin
lbl_selected_file.Caption := OpenDialog.FileName;
img1.Picture.LoadFromFile(OpenDialog.FileName);
OriginalImage := TGraphicClass(img1.Picture.Graphic.ClassType).Create;
OriginalImage.Assign(img1.Picture.Graphic);
OriginalExt := ExtractFileExt(OpenDialog.FileName);
end;
finally
OpenDialog.Free;
end;
end;
procedure TForm1.btn2_select_output_pathClick(Sender: TObject);
var
FileOpenDialog: TFileOpenDialog;
BlurredImage: TBitmap;
OutputImage: TGraphic;
begin
FileOpenDialog := TFileOpenDialog.Create(nil);
try
FileOpenDialog.Options := [fdoPickFolders];
FileOpenDialog.Title := 'Select Output Path';
if FileOpenDialog.Execute then
begin
BlurredImage := TBitmap.Create;
try
// Create a copy of the original image
BlurredImage.Assign(OriginalImage);
// Apply the blur effect to the output image here...
// Save the output image to the selected folder
OutputImage := TGraphicClass(OriginalImage.ClassType).Create;
try
OutputImage.Assign(BlurredImage);
OutputImage.SaveToFile(FileOpenDialog.FileName + '\output' + OriginalExt);
// alternatively:
// OutputImage.SaveToFile(FileOpenDialog.FileName + '\output.' + GraphicExtension(TGraphicClass(OutputImage.ClassType)));
finally
OutputImage.Free;
end;
finally
BlurredImage.Free;
end;
end;
finally
FileOpenDialog.Free;
end;
end;
Do you know that you can use FMX.TBitmap class in VCL application?
Why would anyone even consider using FMX.TBitmap in VCL application?
For one FMX.TBitmap.LoadFromFile class supports loading and saving of many image types with ease.
Another thing is that you can easily apply any number FMX.Filter.Effects to the said FMX Bitmap.
The only problem is that there is no easy way of of loading FMX Bitmap to or from VCL components.
Here is a simple code example for adding Pixelate effect for chosen file that is then saved into another file
uses FMX.Graphics, FMX.Filter.Effects;
procedure TForm1.BtnOpenImageClick(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
Image1.Picture.LoadFromFile(OPenDialog1.FileName);
OriginalFileName := OpenDialog1.FileName;
end;
end;
procedure TForm1.BtnSaveProcessedImageClick(Sender: TObject);
var FMXOriginalBitmap: FMX.Graphics.TBitmap;
FMXProcessedBitmap: FMX.Graphics.TBitmap;
FMXPixelateFilter: FMX.Filter.Effects.TFilterPixelate;
begin
FMXOriginalBitmap := FMX.Graphics.TBitmap.Create;
FMXOriginalBitmap.LoadFromFile(OriginalFileName);
FMXPixelateFilter := FMX.Filter.Effects.TFilterPixelate.Create(nil);
FMXPixelateFilter.Input := FMXOriginalBitmap;
FMXPixelateFilter.BlockCount := 100;
FMXProcessedBitmap := FMXPixelateFilter.Output;
FMXProcessedBitmap.SaveToFile('D:\Proba.jpg');
Image2.Picture.LoadFromFile('D:\Proba.jpg');
end;
Related
I'm writing a program that:
1.- Ask the user to select a file, Any kind of image (JPG, PNG, etc)
2.- Let's user pixellate the image and shows the new pixellated image.
Since my test image is a JPG, I'm getting error: Incompatible types: 'TPersistent' and 'TFileName'
Before trying to convert the JPG to Bitmap, I was getting:
Bitmap image is not valid
Code:
unit demo_2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Imaging.Jpeg;
var
OpenDialog: TOpenDialog;
OpenFolder: TFileOpenDialog;
OriginalImage: TBitmap;
PixellatedImage: TBitmap;
type
TForm1 = class(TForm)
btn1_select_img: TButton;
btn2_select_output_path: TButton;
lbl_selected_file: TLabel;
lbl_output_path: TLabel;
img1: TImage;
pnl_img: TPanel;
pnl_btns: TPanel;
procedure btn1_select_imgClick(Sender: TObject);
procedure btn2_select_output_pathClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btn1_select_imgClick(Sender: TObject);
begin
OpenDialog := TOpenDialog.Create(nil);
OpenDialog.Filter := 'All Files|*.*';
OpenDialog.Options := [ofPathMustExist, ofFileMustExist];
try
if OpenDialog.Execute then
begin
// Print the selected file's path to the console
//WriteLn(OpenDialog.FileName);
lbl_selected_file.Caption := OpenDialog.FileName;
img1.Picture.LoadFromFile(OpenDialog.FileName);
end;
finally
//OpenDialog.Free;
end;
end;
procedure TForm1.btn2_select_output_pathClick(Sender: TObject);
begin
//OpenFolder := TFileOpenDialog.Create(nil); for folder selection
//OpenFolder.Options := [fdoPickFolders]; for folder selection
try
//if OpenFolder.Execute then
begin
PixellatedImage := TBitmap.Create;
//PixellatedImage.LoadFromFile(OpenDialog.FileName);
PixellatedImage.Assign(OpenDialog.FileName);
// Pixellate the image by setting the Width and Height to a small value
PixellatedImage.Width := 10;
PixellatedImage.Height := 10;
img1.Picture.Bitmap := PixellatedImage;
//lbl_output_path.Caption := OpenFolder.FileName; for folder selection
end;
finally
//OpenFolder.Free;
//OpenDialog.Free;
PixellatedImage.Free;
end;
end;
end.
Since my test image is a JPG, I'm getting error: Incompatible types: 'TPersistent' and 'TFileName'
Actually, that's wrong in the sense that the error has nothing to do with the image being JPG. So while both "my test image is a JPG" and "I'm getting error ..." are correct, the implication ("since") is not.
The TPersistent.Assign method used in
PixellatedImage.Assign(OpenDialog.FileName);
requires a TPersistent object. In this case, when you are dealing with graphics, you typically need a TGraphic instance. Hence, you cannot pass a string, even if that string happens to be a file name of an image file.
So if you want to use the Assign method on a graphics object, you need to pass it another graphics object -- one you may have loaded from file using its own LoadFromFile:
procedure TForm1.FormCreate(Sender: TObject);
begin
var JpegImage := TJPEGImage.Create;
try
var OpenDlg := TFileOpenDialog.Create(Self);
try
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'JPEG images';
FileMask := '*.jpg';
end;
if OpenDlg.Execute then
JpegImage.LoadFromFile(OpenDlg.FileName)
else
Exit;
finally
OpenDlg.Free;
end;
var BmpImage := TBitmap.Create;
try
BmpImage.Assign(JpegImage);
// For example: BmpImage.SaveToFile('K:\bitmap.bmp');
finally
BmpImage.Free;
end;
finally
JpegImage.Free;
end;
end;
Also, please note that you must use the idiom
LFrog := TFrog.Create;
try
// use LFrog
finally
LFrog.Free;
end
and never
try
LFrog := TFrog.Create;
// use LFrog
finally // WRONG!
LFrog.Free;
end
assuming LFrog is a local variable. If LFrog isn't a local variable, it probably should be made local! Otherwise, it is important to do FreeAndNil on it and not only Free.
Update. The Q was changed so it no longer is about JPG -> BMP, but "any" image file to BMP. Then perhaps the best way is to use the Windows Imaging Component:
procedure TForm1.FormCreate(Sender: TObject);
begin
var WicImage := TWICImage.Create;
try
var OpenDlg := TFileOpenDialog.Create(Self);
try
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'All image files';
FileMask := '*.jpg;*.tiff;*.tif;*.png;*.gif;*.bmp';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'JPEG images';
FileMask := '*.jpg';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'TIFF images';
FileMask := '*.tiff;*.tif';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'PNG images';
FileMask := '*.png';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'GIF images';
FileMask := '*.gif';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'Bitmap images';
FileMask := '*.bmp';
end;
// etc.
if OpenDlg.Execute then
WicImage.LoadFromFile(OpenDlg.FileName);
finally
OpenDlg.Free;
end;
var BmpImage := TBitmap.Create;
try
BmpImage.Assign(WicImage);
// For example: BmpImage.SaveToFile('K:\bitmap.bmp');
finally
BmpImage.Free;
end;
finally
WicImage.Free;
end;
end;
Finally, I note that you write
// Pixellate the image by setting the Width and Height to a small value
PixellatedImage.Width := 10;
PixellatedImage.Height := 10;
Although not relevant to your main question about TGraphic.Assign, I should note that setting a TBitmap's Width and Height very much doesn't pixelate the image in the usual sense of the word. (Algorithmically, pixelation should be done like in this Pixelate procedure.)
I'm trying to show an image taken with a camera on a multi-device form with paintbox after processing it with opencv. However, cvImage2Bitmap returns VCL.Graphics.TBitmap. So I need to convert this to FMX.Graphics.TBitmap.
unit xml_cam2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, VCL.Graphics, FMX.Dialogs, FMX.ScrollBox,
FMX.Memo, FMX.Objects, FMX.Controls.Presentation, FMX.StdCtrls,
ocv.highgui_c,
ocv.core_c,
ocv.core.types_c,
ocv.imgproc_c,
ocv.imgproc.types_c,
ocv.utils;
type
TForm1 = class(TForm)
Button1: TButton;
OpenDialog1: TOpenDialog;
PaintBox1: TPaintBox;
Memo1: TMemo;
procedure PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
procedure FormCreate(Sender: TObject);
private
capture: pCvCapture;
frame: pIplImage;
procedure OnIdle(Sender: TObject; var Done: Boolean);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
var
Bitmap, PaintBoxBitmap: FMX.Graphics.TBitmap;
{$R *.fmx}
procedure TForm1.FormCreate(Sender: TObject);
begin
capture := cvCreateCameraCapture(CV_CAP_ANY);
if Assigned(capture) then
Application.OnIdle := OnIdle;
end;
procedure TForm1.OnIdle(Sender: TObject; var Done: Boolean);
begin
if Assigned(capture) then
begin
frame := cvQueryFrame(capture);
if Assigned(frame) then
begin
Bitmap := cvImage2Bitmap(frame);
//cvImage2Bitmap returns VCL.Graphics.TBitmap
end;
end;
Memo1.Lines.Add(IntToStr(Bitmap.Width));
Memo1.Lines.Add(IntToStr(Bitmap.Height));
if (PaintBoxBitmap = nil) then
PaintBoxBitmap := FMX.Graphics.TBitmap.Create;
PaintBoxBitmap.Assign(Bitmap);
Invalidate;
Bitmap.Free;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
begin
if Assigned(PaintBoxBitmap) then
PaintBox1.Canvas.DrawBitmap(PaintBoxBitmap, PaintBox1.ClipRect, PaintBox1.ClipRect, 1);
Memo1.Lines.Add('b');
end;
end.
If you know any other efficient way to show iplimage to paintbox, please let us know.
It would be nice to move image data directly from one bitmap to the other, but doesn't seems to be an easy task.
Instead, the simplest way ( as it was answered here VCL.Bitmap To FMX.Bitmap ) seems to be to save image to a memory stream and load again in FMX bitmap object.
This simple code just works. You pay the penalty for moving image data to memory to load again in the new bitmap, but seems fair in exchange for simplicity.
procedure TForm1.FormCreate(Sender: TObject);
var
ms : TMemoryStream;
begin
ms := TMemoryStream.Create;
try
VCL_bmp := VCL.Graphics.TBitmap.Create;
try
VCL_bmp.LoadFromFile('file.bmp');
VCL_bmp.SaveToStream(ms);
ms.Seek(0, soFromBeginning);
finally
FreeAndNil(VCL_bmp);
end;
FMX_bmp := FMX.Graphics.TBitmap.Create();
try
FMX_bmp.LoadFromStream(ms);
... do something with the image ...
finally
FreeAndNil(FMX_bmp);
end;
finally
FreeAndNil(ms);
end;
end;
There are a couple of ways to do this. One way is to write the VCL bitmap to a TStream and then read it into the FMX bitmap. However, you can't convert the other way like that and it may be quite slow. I prefer to use Scanlines to convert between one and the other. In my code below I'm using 24 bit VCL bitmaps because I've found that the Windows API prefers these (AVIFile32 for example). Both bitmaps need to be created before calling the procedures. Of course you need to be creating an FMX application for Windows and include VCL.Graphics in your uses. Any transparency in the FMX bitmap will be lost when converting to a 24 bit VCL bitmap.
Convert 24 bit VCL bitmap to FMX bitmap
procedure VCLtoFMX_Bitmap(const VCLBmp : VCL.Graphics.TBitmap ; out FMXBmp : FMX.Graphics.TBitmap);
var
bData : TBitmapData;
x, y : Integer;
pfmxbyte, pvclbyte : PByte;
begin
VCLBmp.PixelFormat := pf24bit;
FMXBmp.SetSize(VCLBmp.Width, VCLBmp.Height);
FMXBmp.Map(TMapAccess.ReadWrite, bdata);
try
for y := 0 to FMXBmp.Height - 1 do begin
pfmxbyte := bdata.GetScanline(y);
pvclbyte := VCLBmp.Scanline[y];
for x := 0 to FMXBmp.Width - 1 do begin
pfmxbyte^ := pvclbyte^; Inc(pvclbyte); Inc(pfmxbyte);
pfmxbyte^ := pvclbyte^; Inc(pvclbyte); Inc(pfmxbyte);
pfmxbyte^ := pvclbyte^; Inc(pvclbyte); Inc(pfmxbyte);
pfmxbyte^ := $FF; Inc(pfmxbyte); // Full opacity
end;
end;
finally
FMXBmp.Unmap(bdata);
end;
end;
Convert FMX bitmap to 24 bit VCL bitmap
procedure FMXtoVCL_Bitmap(const FMXBmp : FMX.Graphics.TBitmap ; out VCLBmp : VCL.Graphics.TBitmap);
var
bData : TBitmapData;
x, y : Integer;
pfmxbyte, pvclbyte : PByte;
begin
VCLBmp.PixelFormat := pf24bit;
VCLBmp.SetSize(FMXBmp.Width, FMXBmp.Height);
FMXBmp.Map(TMapAccess.Read, bdata);
try
for y := 0 to FMXBmp.Height - 1 do begin
pfmxbyte := bdata.GetScanline(y);
pvclbyte := VCLBmp.Scanline[y];
for x := 0 to FMXBmp.Width - 1 do begin
pvclbyte^ := pfmxbyte^; Inc(pvclbyte); Inc(pfmxbyte);
pvclbyte^ := pfmxbyte^; Inc(pvclbyte); Inc(pfmxbyte);
pvclbyte^ := pfmxbyte^; Inc(pvclbyte); Inc(pfmxbyte, 2);
end;
end;
finally
FMXBmp.Unmap(bdata);
end;
end;
I will assign this procedure into OnMouseEnter. I have some TImage that will change it's picture OnMouseEnter. It is easier to make each procedure of it on event handler. But i don't like to repeat the same code.
var
i: Integer;
CoName: TComponent;
png: TPngImage;
s: string;
begin
s := '';
for i := 1 to 16 do
begin
CoName := Form1.Components[i];
if CoName is TImage then
begin
s := CoName.Name;
Break;
end;
end;
if Trim(s) <> '' then
begin
png := TPngImage.Create;
try
png.LoadFromResourceName(hInstance, 'ResImgA');
// s.picture.Assign(png); > i can not do this
finally
FreeAndNil(png);
end;
end;
end;
How can i allow s into TImage.Name ?
Set the OnMouseEnter event of all the TImage objects to point to the same event handler, and use its Sender parameter to identify which TImage is calling the handler:
procedure TForm38.ImageMouseEnter(Sender: TObject);
var
ResName: string;
im: TImage;
png: TPngImage;
begin
im := Sender as TImage;
// if your image resources are named as 'Res' + name of TImage (eg. 'ImgA')
// you can combine these as
ResName := 'Res' + im.Name;
png := TPngImage.Create;
try
png.LoadFromResourceName(hInstance, ResName);
im.picture.Assign(png);
finally
png.Free;
end;
end;
I do this and it's work fine, you don't need String variable or loops:
procedure TForm1.Image1MouseEnter(Sender: TObject);
Var PngImg : TPngImage;
// Image : TImage; < -- If you need to handle error
begin
//Image := Sender as TImage; and remove IF
if Sender is TImage then
begin
PngImg := TPngImage.Create;
try
PngImg.LoadFromResourceName(HInstance , 'PngImage_1');
TImage(Sender).Picture.Assign(PngImg);
finally
PngImg.Free;
end ;
end;
end;
For all the other Timage (15) , you can set the event without repeat the code from the object inspector as:
I am using TGifImage that is included with Delphi XE.
What I am trying to do is load a Gif from a File and and extract all the frames to a Bitmap.
This is what I did so far:
procedure ExtractGifFrames(FileName: string);
var
Gif: TGifImage;
Bmp: TBitmap;
i: Integer;
begin
Gif := TGifImage.Create;
try
Gif.LoadFromFile(FileName);
Bmp := TBitmap.Create;
try
Bmp.SetSize(Gif.Width, Gif.Height);
for i := 0 to Gif.Images.Count - 1 do
begin
if not Gif.Images[i].Empty then
begin
Bmp.Assign(Gif.Images[i]);
Bmp.SaveToFile('C:\test\bitmap' + IntToStr(i) + '.bmp');
end;
end;
finally
Bmp.Free;
end;
finally
Gif.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenPictureDialog1.Execute then
begin
ExtractGifFrames(OpenPictureDialog1.FileName);
end;
end;
The problem I am facing is with some transparency issue with a lot of different Gifs, and also size problems.
Here are some example bitmaps that were saved using my code above:
As you can see the results are not great, they have size and transparency issues.
I know the Gif Files themselves are not corrupt, because I can load them through my web browser and they display correctly without fault.
How can I load a Gif from File, assign each frame to Bitmap without losing any quality?
For older Delphi Versions (Pre 2009): Take a look at the code of GIFImage unit, you might want to check how TGIFPainter renders the images based on each Frame's Disposal method.
I have wrote a small code utilizing TGIFPainter.OnAfterPaint event handler to save the active frame to BMP, and do all the "hard work".
Note: GIFImage unit version 2.2 Release: 5 (23-MAY-1999)
type
TForm1 = class(TForm)
Button1: TButton;
ProgressBar1: TProgressBar;
procedure Button1Click(Sender: TObject);
public
FBitmap: TBitmap;
procedure AfterPaintGIF(Sender: TObject);
end;
...
procedure TForm1.Button1Click(Sender: TObject);
var
GIF: TGIFImage;
begin
GIF := TGIFImage.Create;
FBitmap := TBitmap.Create;
Button1.Enabled := False;
try
GIF.LoadFromFile('c:\test\test.gif');
GIF.DrawOptions := GIF.DrawOptions - [goLoop, goLoopContinously, goAsync];
GIF.AnimationSpeed := 1000; // Max - no delay
FBitmap.Width := GIF.Width;
FBitmap.Height := GIF.Height;
GIF.OnAfterPaint := AfterPaintGIF;
ProgressBar1.Max := Gif.Images.Count;
ProgressBar1.Position := 0;
ProgressBar1.Smooth := True;
ProgressBar1.Step := 1;
// Paint the GIF onto FBitmap, Let TGIFPainter do the painting logic
// AfterPaintGIF will fire for each Frame
GIF.Paint(FBitmap.Canvas, FBitmap.Canvas.ClipRect, GIF.DrawOptions);
ShowMessage('Done!');
finally
FBitmap.Free;
GIF.Free;
Button1.Enabled := True;
end;
end;
procedure TForm1.AfterPaintGIF(Sender: TObject);
begin
if not (Sender is TGIFPainter) then Exit;
if not Assigned(FBitmap) then Exit;
// The event will ignore Empty frames
FBitmap.Canvas.Lock;
try
FBitmap.SaveToFile(Format('%.2d.bmp', [TGIFPainter(Sender).ActiveImage]));
finally
FBitmap.Canvas.Unlock;
end;
ProgressBar1.StepIt;
end;
Note: No error handling to simplify the code.
For newer Delphi Versions (2009+): With build-in GIFImg unit, you can do this quit easy with the use of TGIFRenderer (which completely replaced old TGIFPainter) e.g.:
procedure TForm1.Button1Click(Sender: TObject);
var
GIF: TGIFImage;
Bitmap: TBitmap;
I: Integer;
GR: TGIFRenderer;
begin
GIF := TGIFImage.Create;
Bitmap := TBitmap.Create;
try
GIF.LoadFromFile('c:\test\test.gif');
Bitmap.SetSize(GIF.Width, GIF.Height);
GR := TGIFRenderer.Create(GIF);
try
for I := 0 to GIF.Images.Count - 1 do
begin
if GIF.Images[I].Empty then Break;
GR.Draw(Bitmap.Canvas, Bitmap.Canvas.ClipRect);
GR.NextFrame;
Bitmap.SaveToFile(Format('%.2d.bmp', [I]));
end;
finally
GR.Free;
end;
finally
GIF.Free;
Bitmap.Free;
end;
end;
Using GDI+:
uses ..., GDIPAPI, GDIPOBJ, GDIPUTIL;
procedure ExtractGifFrames(const FileName: string);
var
GPImage: TGPImage;
encoderClsid: TGUID;
BmpFrame: TBitmap;
MemStream: TMemoryStream;
FrameCount, FrameIndex: Integer;
begin
GPImage := TGPImage.Create(FileName);
try
if GPImage.GetLastStatus = Ok then
begin
GetEncoderClsid('image/bmp', encoderClsid);
FrameCount := GPImage.GetFrameCount(GDIPAPI.FrameDimensionTime);
for FrameIndex := 0 to FrameCount - 1 do
begin
GPImage.SelectActiveFrame(GDIPAPI.FrameDimensionTime, FrameIndex);
MemStream := TMemoryStream.Create;
try
if GPImage.Save(TStreamAdapter.Create(MemStream), encoderClsid) = Ok then
begin
MemStream.Position := 0;
BmpFrame := TBitmap.Create;
try
BmpFrame.LoadFromStream(MemStream);
BmpFrame.SaveToFile(Format('%.2d.bmp', [FrameIndex]));
finally
BmpFrame.Free;
end;
end;
finally
MemStream.Free;
end;
end;
end;
finally
GPImage.Free;
end;
end;
The frames of an animated GIF file often only contain the differences from the previous frame (an optimisation technique to reduce file size). So in order to produce a snapshot of the GIF at a particular point, you'll have to paste all the frames up to that point, one after the other.
We can achieve this by using Draw() with its 'draw transparently' option set:
procedure ExtractGifFrames(FileName: string);
var
Gif: TGifImage;
Bmp: TBitmap;
i: Integer;
Bounds: TRect;
begin
Gif := TGifImage.Create;
try
Gif.LoadFromFile(FileName);
Bounds := Rect(0, 0, Gif.Width-1, Gif.Height-1);
Bmp := TBitmap.Create;
try
Bmp.SetSize(Gif.Width, Gif.Height);
Bmp.PixelFormat := pf32bit;
for i := 0 to Gif.Images.Count - 1 do
begin
if not Gif.Images[i].Empty then
begin
Gif.Images[i].Draw(Bmp.Canvas, Bounds, True, True);
Bmp.SaveToFile(IntToStr(i) + '.bmp');
end;
end;
finally
Bmp.Free;
end;
finally
Gif.Free;
end;
end;
NB: There are other elements to the animated GIF format, which specify the amount of times frames are to be repeated etc. but they may not concern you.
Here is small snippet of the code (ready to paste and run) which run one thread.
This thread get a list of the jpg files on disc, then do certain operations on it.
Normally it works okay. If I start moving the cursor on the form I get this error everytime :)
Any Idea?
Thanks!
unit uTest;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, jpeg, gr32;
type
TThreadSafeJpegImage = class(TJPEGImage)
protected
procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
end;
TForm1 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
public
end;
TWatek = class(TThread)
public
procedure Execute;override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btn1Click(Sender: TObject);
var
thr: TWatek;
begin
thr := TWatek.Create(true);
thr.FreeOnTerminate := true;
thr.Resume;
end;
{ TWatek }
procedure TWatek.Execute;
var
sciezka: string;
Rec : TSearchRec;
Path : string;
I: Integer;
function TestFile(path: string): WideString;
var
stream: TMemoryStream;
jpg: TThreadSafeJpegImage;
bmp32: TBitmap32;
strStr: TStringStream;
err: String;
begin
try
stream := TMemoryStream.Create;
jpg := TThreadSafeJpegImage.Create;
try
stream.LoadFromFile(path);
jpg.LoadFromStream(stream);
finally
FreeAndNil(stream);
end;
bmp32 := TBitmap32.Create;
try
bmp32.Assign(jpg);
strStr := TStringStream.Create('');
bmp32.SaveToStream(strStr);
strStr.Seek(0,soFromBeginning);
finally
FreeAndNil(jpg);
FreeAndNil(bmp32);
end;
result := strStr.DataString;
FreeAndNil(strStr);
except
on e: exception do
begin
err := e.Message;
showmessage (err);
end;
end;
end;
begin
sciezka := 'd:\pictures\';
for I := 1 to 100 do
begin
Path := IncludeTrailingPathDelimiter(sciezka) ;
if FindFirst (Path + '*.jpg', faAnyFile - faDirectory, Rec) = 0 then
begin
try
repeat
TestFile (Path + Rec.Name);
until FindNext(Rec) <> 0;
finally
FindClose(Rec) ;
end;
end;
end;
end;
{ TThreadSafeJpegImage }
procedure TThreadSafeJpegImage.Draw(ACanvas: TCanvas; const Rect: TRect);
begin
Bitmap.Canvas.Lock;
try
inherited Draw(ACanvas, Rect);
finally
Bitmap.Canvas.Unlock;
end;
end;
end.
A guy from Graphics32 group found a solution for me. We have to amend some fixes to gr32 unit, as follows:
1) In TBitmap32.AssignTo() replace
DrawTo(Bmp.Canvas.Handle, 0, 0);
with
Bmp.Canvas.Lock;
try
DrawTo(Bmp.Canvas.Handle, 0, 0);
finally
Bmp.Canvas.UnLock;
end;
2) In TBitmap32.Assign() replace
TGraphicAccess(Source).Draw(Canvas, MakeRect(0, 0, Width, Height));
with
Canvas.Lock;
try
TGraphicAccess(Source).Draw(Canvas, MakeRect(0, 0, Width, Height));
finally
Canvas.UnLock;
end;
Now it works!
not 100% sure, please vote me down if I'm delusional.
TThreadSafeJpegImage.Draw locks the Canvas.
When moving the mouse across the form you force a redraw, which the form cannot do (because you've already locked the canvas previously) and this causes the error to be returned.
Change the Draw code like so:
procedure TThreadSafeJpegImage.Draw(ACanvas: TCanvas; const Rect: TRect);
var
OKToDraw: boolean;
begin
OKToDraw:= Bitmap.Canvas.TryLock;
if OKTODraw then try
inherited Draw(ACanvas, Rect);
finally
Bitmap.Canvas.Unlock;
end; {if try}
end;