如何在Delphi中获取图像文件的尺寸?

时间:2013-03-04 19:04:16

标签: image delphi dimensions image-size

我想在打开该文件之前知道图像文件的宽度和高度。

那么,该怎么做?

编辑: 这是指jpg,bmp,png和gif类型的图像文件。

7 个答案:

答案 0 :(得分:12)

如果通过'图像文件'表示VCL图形系统识别的光栅图像文件,并且'打开之前'表示'在用户可能注意到文件已打开之前',则可以执行此操作很容易:

var
  pict: TPicture;
begin
  with TOpenDialog.Create(nil) do
    try
      if Execute then
      begin
        pict := TPicture.Create;          
        try
          pict.LoadFromFile(FileName);
          Caption := Format('%d×%d', [pict.Width, pict.Height])
        finally
          pict.Free;
        end;
      end;
    finally
      Free;
    end;

当然,文件是打开的,如果图像很大,这需要大量内存。但是,如果您需要在不加载文件的情况下获取元数据(如维度),我相信您需要一个更“复杂”的解决方案。

答案 1 :(得分:12)

您可以尝试This页面。我没有对它进行过测试,但看起来非常合理。

此外,不同的文件类型有不同的获取宽度和高度的方法。尝试更具体地解决您的问题。

其中一页anwser:

unit ImgSize;

interface

uses Classes;

procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);

implementation

uses SysUtils;

function ReadMWord(f: TFileStream): word;

type
  TMotorolaWord = record
  case byte of
  0: (Value: word);
  1: (Byte1, Byte2: byte);
end;

var
  MW: TMotorolaWord;
begin
  // It would probably be better to just read these two bytes in normally and
  // then do a small ASM routine to swap them. But we aren't talking about
  // reading entire files, so I doubt the performance gain would be worth the trouble.      
  f.Read(MW.Byte2, SizeOf(Byte));
  f.Read(MW.Byte1, SizeOf(Byte));
  Result := MW.Value;
end;

procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
const
  ValidSig : array[0..1] of byte = ($FF, $D8);
  Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
var
  Sig: array[0..1] of byte;
  f: TFileStream;
  x: integer;
  Seg: byte;
  Dummy: array[0..15] of byte;
  Len: word;
  ReadLen: LongInt;
begin
  FillChar(Sig, SizeOf(Sig), #0);
  f := TFileStream.Create(sFile, fmOpenRead);
  try
    ReadLen := f.Read(Sig[0], SizeOf(Sig));
    for x := Low(Sig) to High(Sig) do
      if Sig[x] <> ValidSig[x] then
        ReadLen := 0;
      if ReadLen > 0 then
      begin
        ReadLen := f.Read(Seg, 1);
        while (Seg = $FF) and (ReadLen > 0) do
        begin
          ReadLen := f.Read(Seg, 1);
          if Seg <> $FF then
          begin
            if (Seg = $C0) or (Seg = $C1) then
            begin
              ReadLen := f.Read(Dummy[0], 3);  // don't need these bytes 
              wHeight := ReadMWord(f);
              wWidth := ReadMWord(f);
            end
            else
            begin
              if not (Seg in Parameterless) then
              begin
                Len := ReadMWord(f);
                f.Seek(Len - 2, 1);
                f.Read(Seg, 1);
              end
              else
                Seg := $FF;  // Fake it to keep looping. 
            end;
          end;
        end;
      end;
    finally
    f.Free;
  end;
end;

procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
type
  TPNGSig = array[0..7] of byte;
const
  ValidSig: TPNGSig = (137, 80, 78, 71, 13, 10, 26, 10);
var
  Sig: TPNGSig;
  f: tFileStream;
  x: integer;
begin
  FillChar(Sig, SizeOf(Sig), #0);
  f := TFileStream.Create(sFile, fmOpenRead);
  try
    f.Read(Sig[0], SizeOf(Sig));
    for x := Low(Sig) to High(Sig) do
      if Sig[x] <> ValidSig[x] then
        exit;
      f.Seek(18, 0);
      wWidth := ReadMWord(f);
      f.Seek(22, 0);
      wHeight := ReadMWord(f);
  finally
    f.Free;
  end;
end;

procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);
type
  TGIFHeader = record
  Sig: array[0..5] of char;
  ScreenWidth, ScreenHeight: word;
  Flags, Background, Aspect: byte;
end;
  TGIFImageBlock = record
  Left, Top, Width, Height: word;
  Flags: byte;
end;
var
  f: file;
  Header: TGifHeader;
  ImageBlock: TGifImageBlock;
  nResult: integer;
  x: integer;
  c: char;
  DimensionsFound: boolean;
begin
  wWidth  := 0;
  wHeight := 0;
  if sGifFile = '' then
    exit;

  {$I-}

  FileMode := 0;  // read-only 
  AssignFile(f, sGifFile);
  reset(f, 1);
  if IOResult <> 0 then
    // Could not open file
  exit;
  // Read header and ensure valid file
  BlockRead(f, Header, SizeOf(TGifHeader), nResult);
  if (nResult <> SizeOf(TGifHeader)) or (IOResult <> 0) 
    or (StrLComp('GIF', Header.Sig, 3) <> 0) then
  begin
    // Image file invalid
    close(f);
    exit;
  end;
  // Skip color map, if there is one
  if (Header.Flags and $80) > 0 then
  begin
    x := 3 * (1 SHL ((Header.Flags and 7) + 1));
    Seek(f, x);
    if IOResult <> 0 then
    begin
      // Color map thrashed
      close(f);
      exit;
    end;
  end;
  DimensionsFound := False;
  FillChar(ImageBlock, SizeOf(TGIFImageBlock), #0);
  // Step through blocks 
  BlockRead(f, c, 1, nResult);
  while (not EOF(f)) and (not DimensionsFound) do
  begin
    case c of
    ',':  // Found image 
    begin
      BlockRead(f, ImageBlock, SizeOf(TGIFImageBlock), nResult);
      if nResult <> SizeOf(TGIFImageBlock) then
      begin
        // Invalid image block encountered 
        close(f);
        exit;
      end;
      wWidth := ImageBlock.Width;
      wHeight := ImageBlock.Height;
      DimensionsFound := True;
    end;
    ',' :  // Skip 
    begin
      // NOP 
    end;
    // nothing else, just ignore 
  end;
  BlockRead(f, c, 1, nResult);
end;
close(f);

{$I+}

end;

end.

对于BMP(也在我提到的页面上找到):

function FetchBitmapHeader(PictFileName: String; Var wd, ht: Word): Boolean;
// similar routine is in "BitmapRegion" routine
label ErrExit;
const
  ValidSig: array[0..1] of byte = ($FF, $D8);
  Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
  BmpSig = $4d42;
var
  // Err : Boolean;
  fh: HFile;
  // tof : TOFSTRUCT;
  bf: TBITMAPFILEHEADER;
  bh: TBITMAPINFOHEADER;
  // JpgImg  : TJPEGImage;
  Itype: Smallint;
  Sig: array[0..1] of byte;
  x: integer;
  Seg: byte;
  Dummy: array[0..15] of byte;
  skipLen: word;
  OkBmp, Readgood: Boolean;
begin
  // Open the file and get a handle to it's BITMAPINFO
  OkBmp := False;
  Itype := ImageType(PictFileName);
  fh := CreateFile(PChar(PictFileName), GENERIC_READ, FILE_SHARE_READ, Nil,
           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if (fh = INVALID_HANDLE_VALUE) then
    goto ErrExit;
  if Itype = 1 then
  begin
    // read the BITMAPFILEHEADER
    if not GoodFileRead(fh, @bf, sizeof(bf)) then
      goto ErrExit;
    if (bf.bfType <> BmpSig) then  // 'BM'
      goto ErrExit;
    if not GoodFileRead(fh, @bh, sizeof(bh)) then
      goto ErrExit;
    // for now, don't even deal with CORE headers
    if (bh.biSize = sizeof(TBITMAPCOREHEADER)) then
      goto ErrExit;
    wd := bh.biWidth;
    ht := bh.biheight;
    OkBmp := True;
  end
  else
  if (Itype = 2) then
  begin
    FillChar(Sig, SizeOf(Sig), #0);
    if not GoodFileRead(fh, @Sig[0], sizeof(Sig)) then
      goto ErrExit;
    for x := Low(Sig) to High(Sig) do
      if Sig[x] <> ValidSig[x] then
        goto ErrExit;
      Readgood := GoodFileRead(fh, @Seg, sizeof(Seg));
      while (Seg = $FF) and Readgood do
      begin
        Readgood := GoodFileRead(fh, @Seg, sizeof(Seg));
        if Seg <> $FF then
        begin
          if (Seg = $C0) or (Seg = $C1) or (Seg = $C2) then
          begin
            Readgood := GoodFileRead(fh, @Dummy[0],3);  // don't need these bytes
            if ReadMWord(fh, ht) and ReadMWord(fh, wd) then
              OkBmp := True;
          end
          else
          begin
            if not (Seg in Parameterless) then
            begin
              ReadMWord(fh,skipLen);
              SetFilePointer(fh, skipLen - 2, nil, FILE_CURRENT);
              GoodFileRead(fh, @Seg, sizeof(Seg));
            end
            else
              Seg := $FF;  // Fake it to keep looping
          end;
        end;
      end;
  end;
  ErrExit: CloseHandle(fh);
  Result := OkBmp;
end;

答案 2 :(得分:6)

作为Rafael's answer的补充,我相信这个更短的程序可以检测BMP维度:

function GetBitmapDimensions(const FileName: string; out Width,
  Height: integer): boolean;
const
  BMP_MAGIC_WORD = ord('M') shl 8 or ord('B');
var
  f: TFileStream;
  header: TBitmapFileHeader;
  info: TBitmapInfoHeader;
begin
  result := false;
  f := TFileStream.Create(FileName, fmOpenRead);
  try
    if f.Read(header, sizeof(header)) <> sizeof(header) then Exit;
    if header.bfType <> BMP_MAGIC_WORD then Exit;
    if f.Read(info, sizeof(info)) <> sizeof(info) then Exit;
    Width := info.biWidth;
    Height := abs(info.biHeight);
    result := true;
  finally
    f.Free;
  end;
end;

答案 3 :(得分:0)

如果有人对在不加载图形的情况下检索TIFF图像尺寸感兴趣,那么有一种经过验证的方法可以在所有环境中完美地适用于我。我也找到了another solution,但它从Illustrator生成的TIFF中返回了错误的值。但是有一个很棒的图形库,名为GraphicEx by Mike Lischke(TVirtualStringTree,非常有才华的开发人员)。有许多流行的图像格式的实现,它们都来自基类TGraphicExGraphic,它实现了ReadImageProperties虚方法。它是基于流的,只在所有实现中读取文件头。所以它闪电般快......: - )

所以,这是一个示例代码,它检索TIFF的维度(所有图形实现的方法都相同,PNG,PCD,TGA,GIF,PCX等):

Uses ..., GraphicEx,...,...;

Procedure ReadTifSize (FN:String; Var iWidth,iHeight:Integer);
Var FS:TFileStream;
    TIFF:TTIFFGraphic;
Begin
  iWidth:=0;iHeight:=0;
  TIFF:=TTIFFGraphic.Create;
  FS:=TFileStream.Create(FN,OF_READ);

  Try
    TIFF.ReadImageProperties(FS,0);
    iWidth:=TIFF.ImageProperties.Width;
    iHeight:=TIFF.ImageProperties.Height;
  Finally
    TIFF.Destroy;
    FS.Free;
  End;
End;

全部... :-)这对于该单元中的所有图形实现都是一样的。

答案 4 :(得分:0)

查看exiftool.exe。免费。这是这种事情的标准,但你不得不掏空。

答案 5 :(得分:0)

由于拉斐尔(Rafael)的答案GetGIFSize被打破并且完全复杂,这是我的个人说法:

function GetGifSize(var Stream: TMemoryStream; var Width: Word; var Height: Word): Boolean;
var
    HeaderStr: AnsiString;

begin
    Result := False;
    Width := 0;
    Height := 0;

    //GIF header is 13 bytes in length
    if Stream.Size > 13 then
    begin
        SetString(HeaderStr, PAnsiChar(Stream.Memory), 6);
        if (HeaderStr = 'GIF89a') or (HeaderStr = 'GIF87a') then
        begin
            Stream.Seek(6, soFromBeginning);
            Stream.Read(Width, 2);  //Width is located at bytes 7-8
            Stream.Read(Height, 2); //Height is located at bytes 9-10

            Result := True;
        end;
    end;
end;

通过阅读RFC找到了它。

答案 6 :(得分:0)

我不太喜欢Rafael对于JPG的解决方案,因为他的算法会解析每个字节,直到达到FFC0。它没有利用几乎所有标记(FFD8,FFD9和FFFE除外)后跟两个长度字节的事实,从而允许在标记之间跳转。因此,我建议执行以下过程(通过填充检查标记并将值检索到同一函数中,我得到了更多压缩):

procedure GetJPGSize(const Filename: string; var ImgWidth, ImgHeight: word);
const
  SigJPG : TBytes = [$FF, $D8];
  SigC01 : TBytes = [$FF, $C0];
  SigC02 : TBytes = [$FF, $C1];
var
  FStream: TFileStream;
  Buf: array[0..1] of Byte;
  Offset,CheckMarker : Word;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
  function  SameValue(Sig:TBytes):Boolean;
  begin
     Result := CompareMem(@Sig[0], @Buf[0], Length(Sig));
  end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
  function  CheckMarkerOrVal(var Value:Word):Boolean;
  begin
    FStream.ReadData(Buf, Length(Buf));
    Value := Swap(PWord(@Buf[0])^);
    Result := (Buf[0] = $FF);
  end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
begin
  FStream := TFileStream.Create(Filename, fmOpenRead);
  Try
    // First two bytes in a JPG file MUST be $FFD8, followed by the next marker
    If not (CheckMarkerOrVal(CheckMarker) and SameValue(SigJPG))
      then exit;
    Repeat
      If not CheckMarkerOrVal(CheckMarker)
        then exit;
      If SameValue(SigC01) or SameValue(SigC02) then begin
        FStream.Position := FStream.Position + 3;
        CheckMarkerOrVal(ImgHeight);
        CheckMarkerOrVal(ImgWidth);
        exit;
      end;
      CheckMarkerOrVal(Offset);
      FStream.Position := FStream.Position + Offset - 2;
    until FStream.Position > FStream.Size div 2;
  Finally
    FStream.Free;
  end;
end;