Tip: detecting graphic formats

If you’re storing an image in a blob field or the like, I guess it’s not uncommon to have another field that specifies the graphic type. Really though, there’s no need, since the common graphic formats (‘common’ in a Delphi context at least) all have distinct signatures that allow for their easy identification –

  • Windows Bitmap (BMP): the ASCII characters ‘BM’.
  • GIF: the three ASCII characters ‘GIF’.
  • JPEG: a ‘new marker’ indicator ($FF) followed by a ‘start of image’ marker ($D8).
  • PNG: in order, the following eight ASCII characters – #137, ‘P’, ‘N’, ‘G’, #13, #10, #26 and #10.
  • TIFF: ‘II’ or ‘MM’ followed by $002A or $2A00 respectively.
  • Windows icon (ICO): the second word has the value $0001.
  • Windows Metafile (WMF): the first DWORD has the value $9AC6CDD7.
  • Windows Enhanced Metafile (EMF): the first DWORD must equal $00000001, followed by $464D4520 (i.e., ‘EMF’ when read back to front) in the eleventh.

Of this ‘standard’ set, the ICO format is obviously a bit indeterminate when it comes to making a quick check, but the rest are unique enough. In practice, the lack of a proper signature for ICO just means you should test for it last.

Anyhow, putting this knowledge together, you can write something like this:

uses SysUtils, Classes, Graphics, GIFImg, JPEG, PngImage;

const
  MinGraphicSize = 44; //we may test up to & including the 11th longword

function FindGraphicClass(const Buffer; const BufferSize: Int64;
  out GraphicClass: TGraphicClass): Boolean; overload;
var
  LongWords: array[Byte] of LongWord absolute Buffer;
  Words: array[Byte] of Word absolute Buffer;
begin
  GraphicClass := nil;
  Result := False;
  if BufferSize < MinGraphicSize then Exit;
  case Words[0] of
    $4D42: GraphicClass := TBitmap;
    $D8FF: GraphicClass := TJPEGImage;
    $4949: if Words[1] = $002A then GraphicClass := TWicImage; //i.e., TIFF
    $4D4D: if Words[1] = $2A00 then GraphicClass := TWicImage; //i.e., TIFF
  else
    if Int64(Buffer) = $A1A0A0D474E5089 then
      GraphicClass := TPNGImage
    else if LongWords[0] = $9AC6CDD7 then
      GraphicClass := TMetafile
    else if (LongWords[0] = 1) and (LongWords[10] = $464D4520) then
      GraphicClass := TMetafile
    else if StrLComp(PAnsiChar(@Buffer), 'GIF', 3) = 0 then
      GraphicClass := TGIFImage
    else if Words[1] = 1 then
      GraphicClass := TIcon;
  end;
  Result := (GraphicClass <> nil);
end;

function FindGraphicClass(Stream: TStream;
  out GraphicClass: TGraphicClass): Boolean; overload;
var
  Buffer: PByte;
  CurPos: Int64;
  BytesRead: Integer;
begin
  if Stream is TCustomMemoryStream then
  begin
    Buffer := TCustomMemoryStream(Stream).Memory;
    CurPos := Stream.Position;
    Inc(Buffer, CurPos);
    Result := FindGraphicClass(Buffer^, Stream.Size - CurPos, GraphicClass);
    Exit;
  end;
  GetMem(Buffer, MinGraphicSize);
  try
    BytesRead := Stream.Read(Buffer^, MinGraphicSize);
    Stream.Seek(-BytesRead, soCurrent);
    Result := FindGraphicClass(Buffer^, BytesRead, GraphicClass);
  finally
    FreeMem(Buffer);
  end;
end;

The graphic classes returned here are the ‘in the box’ set for recent Delphi versions; depending on which particular Delphi version and third party libraries you use, the ones that are most appropriate to yourself may differ of course.

With the FindGraphicClass function at hand, we can then write a further utility routine that loads a TPicture instance from a TBlobField like thus:

uses Consts, DB;

procedure LoadPictureFromBlobField(Field: TBlobField; Dest: TPicture);
var
  Graphic: TGraphic;
  GraphicClass: TGraphicClass;
  Stream: TMemoryStream;
begin
  Graphic := nil;
  Stream := TMemoryStream.Create;
  try
    Field.SaveToStream(Stream);
    if Stream.Size = 0 then
    begin
      Dest.Assign(nil);
      Exit;
    end;
    if not FindGraphicClass(Stream.Memory^, Stream.Size, GraphicClass) then
      raise EInvalidGraphic.Create(SInvalidImage);
    Graphic := GraphicClass.Create;
    Stream.Position := 0;
    Graphic.LoadFromStream(Stream);
    Dest.Assign(Graphic);
  finally
    Stream.Free;
    Graphic.Free;
  end;
end;

This last routine may then be used like this (where ‘cdsPicture’ and ‘Image1’ are a TBlobField and TImage respectively that are created at design time):

procedure TForm1.btnTestLoadPictureClick(Sender: TObject);
begin
  LoadPictureFromBlobField(cdsPicture, Image1.Picture);
end;
Advertisements

6 thoughts on “Tip: detecting graphic formats

  1. I needed this last week! OK, I STILL need it. Thanks. I’m writing a “merge” program that gives the ability to drop images into an ODT at the point of a placeholder. I got it working with JPEGs…now this will give me the rest.

    • Hey, quick commenting! Just to say, between the time I saw my post up on DelphiFeeds.com and then noticing your comment, I fixed a silly typo (the MinGraphicSize constant should be 44 to cover the EMF check, not 8).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s