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:
- Make sure that JPEG is in the uses list as that registers itself in TPicture
- Use TPicture.LoadFromFile and TPicture.SaveToFile to load from or save to JPEG images
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
- PNGImage
- JPEG
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!