8

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

那么,我该怎么做呢?

这是指JPEGBMPPNGGIF类型的图像文件。

4

6 回答 6

17

如果“图像文件”是指 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;

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

于 2013-03-04T19:12:14.610 回答
13

你可以试试这个页面。我没有测试过它,但它似乎很合理。

此外,不同的文件类型有不同的获取宽度和高度的方法。

其中一页回答:

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;
于 2013-03-04T19:15:35.803 回答
7

作为对Rafael 回答的补充,我相信这个更短的过程可以检测 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;
于 2013-03-04T19:29:59.253 回答
2

如果有人对在不加载图形的情况下检索 TIFF 图像尺寸感兴趣,那么有一种经过验证的方法可以在所有环境中完美地为我工作。我还为此找到了另一种解决方案,但它从 Illustrator 生成的 TIFF 中返回了错误的值。但是有一个很棒的图形库,由 Mike Lischke(TVirtualStringTree 的非常有才华的开发人员)称为 GraphicEx。有许多流行的图像格式的实现,它们都来自基类 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;

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

于 2015-10-08T10:34:17.020 回答
1

我不太喜欢Rafael 的JPEG 文件解决方案,因为他的算法会解析每个字节,直到达到 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;
于 2020-07-20T15:01:35.593 回答
0

由于GetGIFSizeRafael 的回答中,答案是破碎的并且非常复杂,这是我个人的版本:

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找到了它。

于 2020-06-22T03:42:46.570 回答