- FlixEngineering LLC - https://flixengineering.com -

Download an image from the web and assign a stream to TPicture (for any image format)

I struggled with this myself when implementing the Pexels component. The problem is rather simple: You download an image from the web, end up with data in a stream and want to use a bitmap to use the image in your application. The tasks presents itself as rather complex due to the flexibility that Delphi offers in this regard. In defense of any other modern object-oriented programming language, they all pick a rather similar approach.

Repeating some Delphi basics. A bitmap is nothing more than a collection of pixels that has a width and a height. We can make changes to any pixel. The problem is that images from the web are not bitmaps. The images follow an encoding that is referred to as image format. It is possible to convert any image file into a TBitmap. We just need a TGraphic class that registers itself in TPicture. The whole deal is explained rather well in the comments in Vcl.Graphics.pas:

 { TPicture }
  { TPicture is a TGraphic container.  It is used in place of a TGraphic if the
    graphic can be of any TGraphic class.  LoadFromFile and SaveToFile are
    polymorphic. For example, if the TPicture is holding an Icon, you can
    LoadFromFile a bitmap file, where if the class was TIcon you could only read
    .ICO files.
      LoadFromFile - Reads a picture from disk.  The TGraphic class created
        determined by the file extension of the file.  If the file extension is
        not recognized an exception is generated.
      SaveToFile - Writes the picture to disk.
 [...]
      Assign - Copys the contents of the given TPicture.  Used most often in
        the implementation of TPicture properties.
      RegisterFileFormat - Register a new TGraphic class for use in
        LoadFromFile.
 [...]
      UnRegisterGraphicClass - Removes all references to the specified TGraphic
        class and all its descendents from the file format and clipboard format
        internal lists.
      Height - The native, unstretched, height of the picture.
      Width - The native, unstretched, width of the picture.
      Graphic - The TGraphic object contained by the TPicture
      Bitmap - Returns a bitmap.  If the contents is not already a bitmap, the
        contents are thrown away and a blank bitmap is returned.
      Icon - Returns an icon.  If the contents is not already an icon, the
        contents are thrown away and a blank icon is returned.
      Metafile - Returns a metafile.  If the contents is not already a metafile,
        the contents are thrown away and a blank metafile is returned. }

Let’s say you want to use a JPEG image in your Delphi project:

The common problem asked on the web is: Where is TPicture.LoadFromStream  or TPicture.SaveToStream  ?

As explained in the comments from TPicture the whole system is based on file extensions. That means, if you have a data stream, you have to tell Delphi what kind of file it is supposed to load. Thus, you are forced back to use the TGraphic  class directly. The TPicture  loading mechanism cannot be used.

Getting back to the example loading an image from the web, we can use any of the many web components that Delphi offers. The result is stored in a TMemoryStream, for example. Of course, you can circumvent the whole problem storing the data you downloaded in a temporary file with the correct extension and you can then use the loading mechanism. But temporary files are such a hassle to create and clean up and you will see, they are not necessary.

The response stream is stored in the local variable lResponse . The URL of the web request is available as well and will be the deciding factor, which TGraphic  class to use to load the image. FPicture  is a field variable of type TPicture  that is to contain the loaded image.

var
 lResponse : TMemoryStream;
 lUrl : String;
 lJPEG : TJPEGImage;
 lPNG: TPNGImage;

begin
  // ...

  if lUrl.EndsWith('.png') then
  begin
    lPNG := TPNGImage.Create;
    lPNG.LoadFromStream( lResponse );
    FPicture.Assign( lPNG );
    lPNG.Free;
  end;

  if lURL.EndsWith('.jpg') or lURL.EndsWith( '.jpeg' ) then
  begin
    lJPEG := TJPEGImage.Create;
    lJPEG.LoadFromStream(lResponse);
    FPicture.Assign(lJPEG);
    lJPEG.Free;
  end;

If the URL ends with ‘.png’  we use TPNGImage to load the image from the stream, for ‘.jpg’ and ‘.jpeg’ we use TJPEGImage instead. For any more graphic extensions, add more classes. The amount of extensions is limited by the API you use. Considering the Pexels API it should all be JPEG images, but I already found a few PNG images and had to implement this mechanism.

Remember to add

to your uses clause.

Some hints on memory management:  We assign the loaded image to FPicture . This means a copy is created in the process. Writing FPicture := lJPEG;  would be hazardous as we would have to handle the memory management of the image that was assigned before to TPicture  (before that assignment) and we would also never know when the lJPEG -reference needs to be freed. lJPEG  runs out of scope after our method is executed and this will most definitely lead to an unexpected access violation. Thus, using Assign  is the only solution. FPicture  has its ‘own’ image and we have to get rid of our local reference right away.

After this explanation, we will have to amend our code snippet as follows:

if lUrl.EndsWith('.png') then
  begin
    lPNG := TPNGImage.Create;
    try
      lPNG.LoadFromStream( lResponse );
      FPicture.Assign( lPNG );
    finally
      lPNG.Free;
    end;
  end;

  if lURL.EndsWith('.jpg') or lURL.EndsWith( '.jpeg' ) then
  begin
    lJPEG := TJPEGImage.Create;
    try
      lJPEG.LoadFromStream(lResponse);
      FPicture.Assign(lJPEG);
    finally
      lJPEG.Free;
    end;
  end;

This code will make sure that our local references are freed properly. Unnecessary precaution? I will have to disagree. What if there is not enough memory for the image to be loaded from the stream? JPEG images can be huge when decompressed. What if the URL has been misnamed and the loading of the image fails with an exception? For these and many more reasons we need to wrap using try…finally!