20

I have to load an image from an XML file. There is no information in the XML file about whether the image is JPG/GIF/BMP. After loading the image, I need to convert it to Bitmap.

Does anyone have any clue how to convert images to Bitmap without knowing the actual file format? I'm using Delphi 2007/2009

Thank you.

M.M
  • 138,810
  • 21
  • 208
  • 365
J K Kunil
  • 551
  • 1
  • 5
  • 13
  • also see: https://stackoverflow.com/questions/53382075/jpeg-to-bmp-conversion-takes-unreasonable-amount-of-time/53382894#53382894 – Gabriel Mar 17 '19 at 12:23

4 Answers4

41

Delphi 2009 comes with built in support for JPEG, BMP, GIF and PNG.

For earlier versions of Delphi you may need to find third party implementations for PNG and GIF, but in Delphi 2009 you simply add the Jpeg, pngimage and GIFImg units to your uses clause.

If the file has an extension you can use the following code, as noted by others the TPicture.LoadFromFile looks at the extensions registered by the inherited classes to determine which image to load.

uses
  Graphics, Jpeg, pngimage, GIFImg;

procedure TForm1.Button1Click(Sender: TObject);
var
  Picture: TPicture;
  Bitmap: TBitmap;
begin
  Picture := TPicture.Create;
  try
    Picture.LoadFromFile('C:\imagedata.dat');
    Bitmap := TBitmap.Create;
    try
      Bitmap.Width := Picture.Width;
      Bitmap.Height := Picture.Height;
      Bitmap.Canvas.Draw(0, 0, Picture.Graphic);
      Bitmap.SaveToFile('C:\test.bmp');
    finally
      Bitmap.Free;
    end;
  finally
    Picture.Free;
  end;
end;

If the file extension is not known one method is to look at the first few bytes to determine the image type.

procedure DetectImage(const InputFileName: string; BM: TBitmap);
var
  FS: TFileStream;
  FirstBytes: AnsiString;
  Graphic: TGraphic;
begin
  Graphic := nil;
  FS := TFileStream.Create(InputFileName, fmOpenRead);
  try
    SetLength(FirstBytes, 8);
    FS.Read(FirstBytes[1], 8);
    if Copy(FirstBytes, 1, 2) = 'BM' then
    begin
      Graphic := TBitmap.Create;
    end else
    if FirstBytes = #137'PNG'#13#10#26#10 then
    begin
      Graphic := TPngImage.Create;
    end else
    if Copy(FirstBytes, 1, 3) =  'GIF' then
    begin
      Graphic := TGIFImage.Create;
    end else
    if Copy(FirstBytes, 1, 2) = #$FF#$D8 then
    begin
      Graphic := TJPEGImage.Create;
    end;
    if Assigned(Graphic) then
    begin
      try
        FS.Seek(0, soFromBeginning);
        Graphic.LoadFromStream(FS);
        BM.Assign(Graphic);
      except
      end;
      Graphic.Free;
    end;
  finally
    FS.Free;
  end;
end;
Kevin Newman
  • 2,437
  • 1
  • 15
  • 12
  • Is that so easy? Thank you so much! Yes, the image data is in CDATA section in the xml file. BTW, doesn't Delphi 2007 support Gif? I don't need png suppor. – J K Kunil Jun 06 '09 at 07:54
  • 1
    Jpeg has been around a while. Not sure about GIF. I always used Jedi in the past for GIF support. – Jim McKeeth Jun 06 '09 at 22:43
  • use: fmOpenRead OR fmShareDenyNone { This prevents fail if the file is locked } – Gabriel Feb 13 '19 at 11:27
13

I've found a simpler way! It loads JPG/GIF/BMP etc. files automatically without even knowing/checking the file format, and convert that accordingly. It worked for me perfectly.

Sharing it here :)

Uses
Classes, ExtCtrls, Graphics, axCtrls;

Procedure TForm1.Button1Click(Sender: TObject);
Var
     OleGraphic               : TOleGraphic;
     fs                       : TFileStream;
     Source                   : TImage;
     BMP                      : TBitmap;
Begin
     Try
          OleGraphic := TOleGraphic.Create; {The magic class!}

          fs := TFileStream.Create('c:\testjpg.dat', fmOpenRead Or fmSharedenyNone);
          OleGraphic.LoadFromStream(fs);

          Source := Timage.Create(Nil);
          Source.Picture.Assign(OleGraphic);

          BMP := TBitmap.Create; {Converting to Bitmap}
          bmp.Width := Source.Picture.Width;
          bmp.Height := source.Picture.Height;
          bmp.Canvas.Draw(0, 0, source.Picture.Graphic);

          image1.Picture.Bitmap := bmp; {Show the bitmap on form}
     Finally
          fs.Free;
          OleGraphic.Free;
          Source.Free;
          bmp.Free;
     End;
End;
J K Kunil
  • 551
  • 1
  • 5
  • 13
  • Yes. Sad. No PNG = no fun! – Gabriel Feb 13 '19 at 11:25
  • Please note that this answer has severe memory-management bugs. If the `TOleGraphic` constructor fails, you will attemp `fs.Free`, `OleGraphic.Free`, `Source.Free`, and `bmp.Free` on random pointers, which is very bad! Similarly, if the `TFileStream` constructor fails, you will attempt `fs.Free`, `Source.Free`, and `bmp.Free` on random pointers. And so on, until `bmp.Width := ...`. This line is the first correct line. Always use the idiom `X := TX.Create; try {use X} finally X.Free; end`, or at the very least initialize your pointers to `nil` before `try`. I'll do -1 until this issue is fixed. – Andreas Rejbrand Jun 08 '20 at 16:15
9

You can't use TPicture.LoadFromFile if you don't know what format the graphic has, as this method uses the file extension to determine which of the registered graphic formats needs to be loaded. There's a reason that there is no matching TPicture.LoadFromStream method.

An external library which can examine data and determine the graphic format at runtime would be the best solution. You could use the efg page as a starting point of your research.

A quick and dirty solution is to try the few formats you need to handle until one succeeds:

function TryLoadPicture(const AFileName: string; APicture: TPicture): boolean;
const
  GraphicClasses: array[0..3] of TGraphicClass = (
    TBitmap, TJPEGImage, TGIFImage, TPngImage);
var
  FileStr, MemStr: TStream;
  ClassIndex: integer;
  Graphic: TGraphic;
begin
  Assert(APicture <> nil);
  FileStr := TFileStream.Create('D:\Temp\img.dat', fmOpenRead);
  try
    MemStr := TMemoryStream.Create;
    try
      MemStr.CopyFrom(FileStr, FileStr.Size);
      // try various
      for ClassIndex := Low(GraphicClasses) to High(GraphicClasses) do begin
        Graphic := GraphicClasses[ClassIndex].Create;
        try
          try
            MemStr.Seek(0, soFromBeginning);
            Graphic.LoadFromStream(MemStr);
            APicture.Assign(Graphic);
            Result := TRUE;
            exit;
          except
          end;
        finally
          Graphic.Free;
        end;
      end;
    finally
      MemStr.Free;
    end;
  finally
    FileStr.Free;
  end;
  Result := FALSE;
end;

Edit:

The GraphicEx library has an example convert that uses

GraphicClass := FileFormatList.GraphicFromContent(...);

to determine the graphic format. This seems very similar to the VB6 way of doing this that you mention. Maybe you can use this library for your purpose.

mghie
  • 32,028
  • 6
  • 87
  • 129
  • Thank you, both for your solution and clarifying the above response. Your code looks interesting, but as you have mentioned, it is actually a quick-and-dirty approach. I'm looking for something like VB 6 LoadPicture function which works for supported image irrespective of file name extension. – J K Kunil Jun 06 '09 at 08:39
  • Quote from GraphicEx web site: "Last change: 03. Feb 2007" – Gabriel Feb 13 '19 at 11:36
7

I don't have Delphi 2007 or 2009 to see if this will work in either of those versions. However, in XE2, there is another class in Vcl.Graphics called TWICImage that handles images supported by the Microsoft Imaging Component, including BMP, GIF, ICO, JPEG, PNG, TIF and Windows Media Photo. It is capable of detecting the image type from a stream. Assuming you have a TImage on the form called Image1:

procedure LoadImageFromStream(Stream: TStream; Image: TImage);
var
  wic: TWICImage;
begin
  Stream.Position := 0;
  wic := TWICImage.Create;
  try
    wic.LoadFromStream(Stream);
    Image.Picture.Assign(wic);
  finally
    wic.Free;
  end;
end;

procedure RenderImage(const Filename: string);
var
  fs: TFileStream;
begin
  fs := TFileStream.Create(Filename, fmOpenRead);
  try
    LoadImageFromStream(fs, Image1);
  finally
    fs.Free;
  end;
end;

It works without adding PNGImage, GIFImg, or JPEG to your uses statement.

The other answers show how to convert the TImage to a BMP, so I'm not including that here. I'm just showing another way to load various graphic types into a TImage without knowing beforehand the image type, or the file extension...

James L.
  • 9,384
  • 5
  • 38
  • 77
  • 1
    Works perfectly in C++Builder XE5 – M.M Dec 06 '14 at 13:03
  • does `TPicture::Assign` copy the image data into the Picture? (I'm wary of a dangling pointer after `wic.Free`) – M.M Dec 06 '14 at 13:03
  • 1
    @MattMcNabb - Yes, it copies it (http://docwiki.embarcadero.com/Libraries/XE5/en/Vcl.Graphics.TPicture.Assign), which is verified by tracing through the VCL code. – James L. Dec 08 '14 at 19:28
  • Loading via WIC is much faster than loading via TImage load from file. – Gabriel Feb 13 '19 at 11:38