Question

Work on a project and I want to save the images I downloadin a blob field. I use idHTTP with success but UI freezes.

I create a procedure to download images from URL using ImageLoader method (Image Loader page). Everything is OK with the images but I cannot save them in the specific field in DB.

How can I do it?

Here is my code :

procedure TForm1.LoadImages;
var
  i: integer;
  IMLO: TImageLoader;
  IM: TImage;
  Stream: TMemoryStream;
  imgAddress: string;

begin
  Table1.First;
   for i := 0 to Table1.RecordCount-1 do
     begin
       imgAddress := Table1.FieldByName('URL').AsString;
       Stream := TMemoryStream.Create;
       IM:= TImage.Create(self);
       IM.Name := 'IM'+inttostr(i);
       IM.Parent := HorzScrollBox1;
       IM.Position.X := i * 320;
       IM.Align:= TAlignLayout.alLeft;
       IM.Width := 300;
       IMLO.LoadImage(IM, imgAddress);
 {      Table1.Edit;
       TBlobField(Table1.FieldByName('image')).LoadFromStream(Stream);
       Table1.Post; 
       Stream.Free;  }
       Table1.Next;
     end;
end;

The code works perfectly if I want just to show images. How can I use stream to save them?

Was it helpful?

Solution

Problem
The problem is that the TImageLoader.LoadImage loads the data from the net asynchronously.
Because of that the data is not yet available at the time when you're loading the data.

Simple solution
In order to stop TImageLoader from doing that you can add an extra method to it and call that one instead.
The other problem is that you are loading the Blob with the Stream data, but you never load the stream with anything.
Luckily you don't need the stream.

function TImageLoader.LoadImageNow(const AImage: TImage; const AImageURL: string): boolean;
const
  success = true;
  failure = false;
var
  MS : TMemoryStream;
  Image: TImage;
begin
  Result:= success;
  MS := TMemoryStream.Create;
  Image:= TImage.Create;
  try
    IdHTTP1.get(AImageURL,MS);
    Ms.Seek(0,soFromBeginning);       
    Image.LoadFromStream(MS);
    AImage.Picture.Assign(Image);   
  except
    Result:= failure;
  end;
  FreeAndNil(Image);
  FreeAndNil(MS);
end;

Now the loader will only return when the image is loaded. This also explains why you get an exception upon freeing IM.
You're freeing IM, but the loader is still using it in the background.
That's the problem with using multi-threaded applications, you need to keep track of what the other threads are doing.

The code for your loop will now look like this:

procedure TForm1.LoadImages;
var
  i: integer;
  IMLO: TImageLoader;
  IM: TImage;
  imgAddress: string;
  FieldImage: TGraphicField;
  FieldUrl: TField;
begin
  FieldImage:= TGraphicField(Table1.FieldByName('image'));
  FieldUrl:= Table1.FieldByName('URL');

  Table1.First;
  for i := 0 to Table1.RecordCount-1 do try
    imgAddress := FieldUrl.AsString;
    IM:= TImage.Create(self);
    IM.Name := 'IM'+inttostr(i);
    IM.Parent := HorzScrollBox1;
    IM.Position.X := i * 320;
    IM.Align:= TAlignLayout.alLeft;
    IM.Width := 300;
    if IMLO.LoadImageNow(IM, imgAddress) then begin
      Table1.Edit;
      FieldImage.Assign(IM.Picture);
      Table1.Post; 
    end; {if}
  finally  
    IM.Free;
    Stream.Free;  
    Table1.Next;
  end; {for .. try}
end;

Because FieldByName is slow it is best to pull it out of a loop (it probably will not matter because of the slowness of LoadImageNow).

See: http://docwiki.embarcadero.com/Libraries/XE5/en/Data.DB.TBlobField.Assign
And: http://docwiki.embarcadero.com/CodeExamples/XE3/en/HTTP_Get_%28Delphi%29

Other option
Add an event to the thread that you can hook into every time a image finishes loading.
In the event handler you can then display the image and put it into the database.
In fact the async loader may already have an option for this, but I do not know that codebase well enough to answer that question.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top