如何调整图片大小?

问题描述 投票:0回答:7

我有图像 (500x500),但我需要将其大小调整为 200x200 并将其绘制在 TImage 上。如何达到这样的效果?

注意
我知道 TImage 中的

Stretch
属性,但我想以编程方式调整图像大小。

image delphi resize delphi-7
7个回答
23
投票

如果你知道新的尺寸不大于原始尺寸,你可以简单地这样做

procedure ShrinkBitmap(Bitmap: TBitmap; const NewWidth, NewHeight: integer);
begin
  Bitmap.Canvas.StretchDraw(
    Rect(0, 0, NewWidth, NewHeight),
    Bitmap);
  Bitmap.SetSize(NewWidth, NewHeight);
end;

如果您知道新尺寸不比原始尺寸,我将其作为编写相应代码的练习。

如果你想要通用功能,你可以这样做

procedure ResizeBitmap(Bitmap: TBitmap; const NewWidth, NewHeight: integer);
var
  buffer: TBitmap;
begin
  buffer := TBitmap.Create;
  try
    buffer.SetSize(NewWidth, NewHeight);
    buffer.Canvas.StretchDraw(Rect(0, 0, NewWidth, NewHeight), Bitmap);
    Bitmap.SetSize(NewWidth, NewHeight);
    Bitmap.Canvas.Draw(0, 0, buffer);
  finally
    buffer.Free;
  end;
end;

这种方法的缺点是需要执行two像素复制操作。我至少可以想到两个解决该问题的方法。 (哪个?)


14
投票

出色的可用性和图像质量提供了以下装置

ResizeImage
的功能1)。该代码取决于 Graphics32GIFImage 2)PNGImage 2)

该函数采用两个文件名或两个流。输入(自动检测为)BMP、PNG、GIF 或 JPG,输出始终为 JPG。

unit AwResizeImage;

interface

uses
  Windows, SysUtils, Classes, Graphics, Math, JPEG, GR32, GIFImage, PNGImage,
  GR32_Resamplers;

type
  TImageType = (itUnknown, itBMP, itGIF, itJPG, itPNG);
  TImageInfo = record
    ImgType: TImageType;
    Width: Cardinal;
    Height: Cardinal;
  end;

  function GetImageInfo(const AFilename: String): TImageInfo; overload;
  function GetImageInfo(const AStream: TStream): TImageInfo; overload;

  function ResizeImage(const ASource, ADest: String; const AWidth,
    AHeight: Integer; const ABackColor: TColor;
    const AType: TImageType = itUnknown): Boolean; overload;
  function ResizeImage(const ASource, ADest: TStream; const AWidth,
    AHeight: Integer; const ABackColor: TColor;
    const AType: TImageType = itUnknown): Boolean; overload;

implementation

type
  TGetDimensions = procedure(const ASource: TStream;
    var AImageInfo: TImageInfo);

  TCardinal = record
    case Byte of
      0: (Value: Cardinal);
      1: (Byte1, Byte2, Byte3, Byte4: Byte);
  end;

  TWord = record
    case Byte of
      0: (Value: Word);
      1: (Byte1, Byte2: Byte);
  end;

  TPNGIHDRChunk = packed record
    Width: Cardinal;
    Height: Cardinal;
    Bitdepth: Byte;
    Colortype: Byte;
    Compression: Byte;
    Filter: Byte;
    Interlace: Byte;
  end;

  TGIFHeader = packed record
    Signature: array[0..2] of Char;
    Version: array[0..2] of Char;
    Width: Word;
    Height: Word;
  end;

  TJPGChunk = record
    ID: Word;
    Length: Word;
  end;

  TJPGHeader = packed record
    Reserved: Byte;
    Height: Word;
    Width: Word;
  end;

const
  SIG_BMP: array[0..1] of Char = ('B', 'M');
  SIG_GIF: array[0..2] of Char = ('G', 'I', 'F');
  SIG_JPG: array[0..2] of Char = (#255, #216, #255);
  SIG_PNG: array[0..7] of Char = (#137, #80, #78, #71, #13, #10, #26, #10);

function SwapBytes(const ASource: Cardinal): Cardinal; overload;
var
  mwSource: TCardinal;
  mwDest: TCardinal;
begin
  mwSource.Value := ASource;
  mwDest.Byte1 := mwSource.Byte4;
  mwDest.Byte2 := mwSource.Byte3;
  mwDest.Byte3 := mwSource.Byte2;
  mwDest.Byte4 := mwSource.Byte1;
  Result := mwDest.Value;
end;

function SwapBytes(const ASource: Word): Word; overload;
var
  mwSource: TWord;
  mwDest: TWord;
begin
  mwSource.Value  := ASource;
  mwDest.Byte1 := mwSource.Byte2;
  mwDest.Byte2 := mwSource.Byte1;
  Result := mwDest.Value;
end;

procedure GetBMPDimensions(const ASource: TStream; var AImageInfo: TImageInfo);
var
  bmpFileHeader: TBitmapFileHeader;
  bmpInfoHeader: TBitmapInfoHeader;
begin
  FillChar(bmpFileHeader, SizeOf(TBitmapFileHeader), #0);
  FillChar(bmpInfoHeader, SizeOf(TBitmapInfoHeader), #0);
  ASource.Read(bmpFileHeader, SizeOf(TBitmapFileHeader));
  ASource.Read(bmpInfoHeader, SizeOf(TBitmapInfoHeader));
  AImageInfo.Width := bmpInfoHeader.biWidth;
  AImageInfo.Height := bmpInfoHeader.biHeight;
end;

procedure GetGIFDimensions(const ASource: TStream; var AImageInfo: TImageInfo);
var
  gifHeader: TGIFHeader;
begin
  FillChar(gifHeader, SizeOf(TGIFHeader), #0);
  ASource.Read(gifHeader, SizeOf(TGIFHeader));
  AImageInfo.Width := gifHeader.Width;
  AImageInfo.Height := gifHeader.Height;
end;

procedure GetJPGDimensions(const ASource: TStream; var AImageInfo: TImageInfo);
var
  cSig: array[0..1] of Char;
  jpgChunk: TJPGChunk;
  jpgHeader: TJPGHeader;
  iSize: Integer;
  iRead: Integer;
begin
  FillChar(cSig, SizeOf(cSig), #0);
  ASource.Read(cSig, SizeOf(cSig));
  iSize := SizeOf(TJPGChunk);
  repeat
    FillChar(jpgChunk, iSize, #0);
    iRead := ASource.Read(jpgChunk, iSize);
    if iRead <> iSize then
      Break;
    if jpgChunk.ID = $C0FF then
    begin
      ASource.Read(jpgHeader, SizeOf(TJPGHeader));
      AImageInfo.Width := SwapBytes(jpgHeader.Width);
      AImageInfo.Height := SwapBytes(jpgHeader.Height);
      Break;
    end
    else
      ASource.Position := ASource.Position + (SwapBytes(jpgChunk.Length) - 2);
  until False;
end;

procedure GetPNGDimensions(const ASource: TStream; var AImageInfo: TImageInfo);
var
  cSig: array[0..7] of Char;
  cChunkLen: Cardinal;
  cChunkType: array[0..3] of Char;
  ihdrData: TPNGIHDRChunk;
begin
  FillChar(cSig, SizeOf(cSig), #0);
  FillChar(cChunkType, SizeOf(cChunkType), #0);
  ASource.Read(cSig, SizeOf(cSig));
  cChunkLen := 0;
  ASource.Read(cChunkLen, SizeOf(Cardinal));
  cChunkLen := SwapBytes(cChunkLen);
  if cChunkLen = SizeOf(TPNGIHDRChunk) then
  begin
    ASource.Read(cChunkType, SizeOf(cChunkType));
    if AnsiUpperCase(cChunkType) = 'IHDR' then
    begin
      FillChar(ihdrData, SizeOf(TPNGIHDRChunk), #0);
      ASource.Read(ihdrData, SizeOf(TPNGIHDRChunk));
      AImageInfo.Width := SwapBytes(ihdrData.Width);
      AImageInfo.Height := SwapBytes(ihdrData.Height);
    end;
  end;
end;

function GetImageInfo(const AFilename: String): TImageInfo;
var
  fsImage: TFileStream;
begin
  fsImage := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyWrite);
  try
    Result := GetImageInfo(fsImage);
  finally
    FreeAndNil(fsImage);
  end;
end;

function GetImageInfo(const AStream: TStream): TImageInfo;
var
  iPos: Integer;
  cBuffer: array[0..2] of Char;
  cPNGBuffer: array[0..4] of Char;
  GetDimensions: TGetDimensions;
begin
  GetDimensions := nil;
  Result.ImgType := itUnknown;
  Result.Width := 0;
  Result.Height := 0;
  FillChar(cBuffer, SizeOf(cBuffer), #0);
  FillChar(cPNGBuffer, SizeOf(cPNGBuffer), #0);
  iPos := AStream.Position;
  AStream.Read(cBuffer, SizeOf(cBuffer));
  if cBuffer = SIG_GIF then
  begin
    Result.ImgType := itGIF;
    GetDimensions := GetGIFDimensions;
  end
  else if cBuffer = SIG_JPG then
  begin
    Result.ImgType := itJPG;
    GetDimensions := GetJPGDimensions;
  end
  else if cBuffer = Copy(SIG_PNG, 1, 3) then
  begin
    AStream.Read(cPNGBuffer, SizeOf(cPNGBuffer));
    if cPNGBuffer = Copy(SIG_PNG, 4, 5) then
    begin
      Result.ImgType := itPNG;
      GetDimensions := GetPNGDimensions;
    end;
  end
  else if Copy(cBuffer, 1, 2) = SIG_BMP then
  begin
    Result.ImgType := itBMP;
    GetDimensions := GetBMPDimensions;
  end;
  AStream.Position := iPos;
  if Assigned(GetDimensions) then
  begin
    GetDimensions(AStream, Result);
    AStream.Position := iPos;
  end;
end;

procedure GIFToBMP(const ASource: TStream; const ADest: TBitmap);
var
  imgSource: TGIFImage;
begin
  imgSource := TGIFImage.Create();
  try
    imgSource.LoadFromStream(ASource);
    ADest.Assign(imgSource);
  finally
    FreeAndNil(imgSource);
  end;
end;

procedure JPGToBMP(const ASource: TStream; const ADest: TBitmap);
var
  imgSource: TJPEGImage;
begin
  imgSource := TJPEGImage.Create();
  try
    imgSource.LoadFromStream(ASource);
    ADest.Assign(imgSource);
  finally
    FreeAndNil(imgSource);
  end;
end;

procedure PNGToBMP(const ASource: TStream; const ADest: TBitmap);
var
  imgSource: TPNGImage;
begin
  imgSource := TPNGImage.Create();
  try
    imgSource.LoadFromStream(ASource);
    ADest.Assign(imgSource);
  finally
    FreeAndNil(imgSource);
  end;
end;

function ResizeImage(const ASource, ADest: String; const AWidth,
  AHeight: Integer; const ABackColor: TColor;
  const AType: TImageType = itUnknown): Boolean;
var
  fsSource: TFileStream;
  fsDest: TFileStream;
begin
  Result := False;
  fsSource := TFileStream.Create(ASource, fmOpenRead or fmShareDenyWrite);
  try
    fsDest := TFileStream.Create(ADest, fmCreate or fmShareExclusive);
    try
      Result := not Result; //hide compiler hint
      Result := ResizeImage(fsSource, fsDest, AWidth, AHeight, ABackColor, AType);
    finally
      FreeAndNil(fsDest);
    end;
  finally
    FreeAndNil(fsSource);
  end;
end;

function ResizeImage(const ASource, ADest: TStream; const AWidth,
  AHeight: Integer; const ABackColor: TColor;
  const AType: TImageType = itUnknown): Boolean;
var
  itImage: TImageType;
  ifImage: TImageInfo;
  bmpTemp: TBitmap;
  bmpSource: TBitmap32;
  bmpResized: TBitmap32;
  cBackColor: TColor32;
  rSource: TRect;
  rDest: TRect;
  dWFactor: Double;
  dHFactor: Double;
  dFactor: Double;
  iSrcWidth: Integer;
  iSrcHeight: Integer;
  iWidth: Integer;
  iHeight: Integer;
  jpgTemp: TJPEGImage;
begin
  Result := False;
  itImage := AType;
  if itImage = itUnknown then
  begin
    ifImage := GetImageInfo(ASource);
    itImage := ifImage.ImgType;
    if itImage = itUnknown then
      Exit;
  end;
  bmpTemp := TBitmap.Create();
  try
    case itImage of
      itBMP: bmpTemp.LoadFromStream(ASource);
      itGIF: GIFToBMP(ASource, bmpTemp);
      itJPG: JPGToBMP(ASource, bmpTemp);
      itPNG: PNGToBMP(ASource, bmpTemp);
    end;
    bmpSource := TBitmap32.Create();
    bmpResized := TBitmap32.Create();
    try
      cBackColor  := Color32(ABackColor);
      bmpSource.Assign(bmpTemp);
      bmpResized.Width := AWidth;
      bmpResized.Height := AHeight;
      bmpResized.Clear(cBackColor);
      iSrcWidth := bmpSource.Width;
      iSrcHeight := bmpSource.Height;
      iWidth := iSrcWidth;
      iHeight := iSrcHeight;
      with rSource do
      begin
        Left := 0;
        Top := 0;
        Right := iSrcWidth;
        Bottom := iSrcHeight;
      end;
      if (iWidth > AWidth) or (iHeight > AHeight) then
      begin
        dWFactor := AWidth / iWidth;
        dHFactor := AHeight / iHeight;
        if (dWFactor > dHFactor) then
          dFactor := dHFactor
        else
          dFactor := dWFactor;
        iWidth := Floor(iWidth * dFactor);
        iHeight := Floor(iHeight * dFactor);
      end;
      with rDest do
      begin
        Left := Floor((AWidth - iWidth) / 2);
        Top := Floor((AHeight - iHeight) / 2);
        Right := Left + iWidth;
        Bottom := Top + iHeight;
      end;
      bmpSource.Resampler := TKernelResampler.Create;
      TKernelResampler(bmpSource.Resampler).Kernel := TLanczosKernel.Create;
      bmpSource.DrawMode := dmOpaque;
      bmpResized.Draw(rDest, rSource, bmpSource);
      bmpTemp.Assign(bmpResized);
      jpgTemp := TJPEGImage.Create();
      jpgTemp.CompressionQuality := 80;
      try
        jpgTemp.Assign(bmpTemp);
        jpgTemp.SaveToStream(ADest);
        Result := True;
      finally
        FreeAndNil(jpgTemp);
      end;
    finally
      FreeAndNil(bmpResized);
      FreeAndNil(bmpSource);
    end;
  finally
    FreeAndNil(bmpTemp);
  end;
end;

end.

备注:

  • 1) 我当然没有自己编写这个代码,但我不知道我从哪里得到它。
  • 2) 包含在最近的 Delphi 版本中。
  • 如果使用更新版本的 RAD Studio/Delphi XE 进行编译,请记住将所有 char 变量类型的 char 替换为 ansichar,否则 GetImageInfo 将不起作用,并且不会调整图像大小。 这是必需的,因为默认的 char 类型是两个字节,并且该函数期望它是单字节。

8
投票

我经常使用此页面中的

SmoothResize
程序:http://www.swissdelphicenter.ch/torry/printcode.php?id=1896

缩放比StretchDraw功能好很多。

不要让标题欺骗了你。该页面演示了调整 JPG 的大小,但 SmoothResize 过程本身使用位图来调整大小。调整 PNG 的大小可以通过类似的方式完成,但如果使用此过程,您将失去透明度。


7
投票

请参阅这个简单示例,了解如何使用两个 TBitmap32 对象调整图像大小。 TBitmap32 在速度/图像质量比方面是最好的。

它需要 https://github.com/graphics32 库。

uses 
  GR32, GR32_Resamplers;

procedure Resize(InputPicture: TBitmap; OutputImage: TImage; const DstWidth, DstHeigth: Integer);
var
  Src, Dst: TBitmap32;
begin
  Dst := nil;
  try
    Src := TBitmap32.Create;
    try
      Src.Assign(InputPicture);
      SetHighQualityStretchFilter(Src);
      Dst := TBitmap32.Create;
      Dst.SetSize(DstWidth, DstHeigth);
      Src.DrawTo(Dst, Rect(0, 0, DstWidth, DstHeigth), Rect(0, 0, Src.Width, Src.Height));
    finally
      FreeAndNil(Src);
    end;
    OutputImage.Assign(Dst);
  finally
    FreeAndNil(Dst);
  end;
end;

// If you need to set a highest quality resampler, use this helper routine to configure it
procedure SetHighQualityStretchFilter(B: TBitmap32);
var
  KR: TKernelResampler;
begin
  if not (B.Resampler is TKernelResampler) then
  begin
    KR := TKernelResampler.Create(B);
    KR.Kernel := TLanczosKernel.Create;
  end
  else
  begin
    KR := B.Resampler as TKernelResampler;
    if not (KR.Kernel is TLanczosKernel) then
    begin
      KR.Kernel.Free;
      KR.Kernel := TLanczosKernel.Create;
    end;
  end;
end;

1
投票

我在这个方向上做了相当多的广泛测试(10 个算法/库)。我只提前三个。
如果您懒惰阅读,请跳至我的结论 :)


JanFX 库 现在并入 fat Jedi 发行版中。幸运的是,您可以从 Jedi 中提取此文件,而无需将整个猛犸象拖到您的项目中。
它提供了非常好的平滑效果(不如 Graphics32,但足够好),但速度快得多。

注意:Jedi 中的 JanFX.pas 存在缺陷:开启范围检查时不起作用。您需要在代码之前定义{$R-}。就是这样。 Jedi 的人输入了这个错误,因为他们总是在编译时关闭范围检查。

JanFx.SmoothResize(SrcBMP, DstBMP); 

Graphics32 库
超级好的输出质量。
但如果您需要的只是一个重采样器,那么使用整个 Graphics32 库可能就有点过分了。 JanFx 更小、更便携。 Graphics32 将为您提供稍微更好的结果,但处理时间大约高 10 倍!


拉伸Blt
如果您不想涉及外部库,请查看 StretchBlt。 这不会给您提供与 Graphics32 一样的最佳结果,但与 Graphics32 相比,它的速度要快得多。
(见下面的代码)


结论:

StretchBlt 是我的程序的最终选择,它是输出质量和速度之间的最佳折衷方案。它不仅在下采样方面做得很好,而且在上采样方面也做得很好。

   {-------------------------------------------------------------------------------------------------------------
   Uses MS Windows StretchBlt
   BEST (see tester)

   Zoom: In/Out
   Keep aspect ration: No
   Stretch provided in: pixels

   Resize down: VERY smooth. Better than JanFX.SmoothResize.
   Resize up: better (sharper) than JanFX.SmoothResize
   Time: similar to JanFx

   BitBlt only does copy. NO STRETCH

   https://msdn.microsoft.com/en-us/library/windows/desktop/dd162950(v=vs.85).aspx
-------------------------------------------------------------------------------------------------------------}
function StretchF(BMP: TBitmap; OutWidth, OutHeight: Integer): TBitmap;
begin
 if (BMP.Width < 12) OR (BMP.Height< 12) then
  begin
   ShowMessage('Cannot stretch images under 12 pixels!');   { 'WinStretchBltF' will crash if the image size is too small (below 10 pixels)}
   EXIT(NIL);
  end;

 Result:= TBitmap.Create;
 TRY
  Result.PixelFormat:= BMP.PixelFormat; { Make sure we use the same pixel format as the original image }
  SetLargeSize(Result, OutWidth, OutHeight);
  SetStretchBltMode(Result.Canvas.Handle, HALFTONE);
  SetBrushOrgEx    (Result.Canvas.Handle, 0,0, NIL);
  StretchBlt(Result.Canvas.Handle, 0, 0, Result.Width, Result.Height, BMP.Canvas.Handle, 0, 0, BMP.Width, BMP.Height, SRCCOPY);
 FINALLY
  FreeAndNil(Result);
  RAISE;
 END;
end;

0
投票

对于任何类型的图像,您可以使用此:

img := TIMage.create(nil);
img.picture.loadfromfile('any_file_type');
Result:= TBitmap.Create;
result.Width := newWidth;
result.Height := newHeight;
Result.Canvas.Draw(0,0,img.Picture.Graphic);

0
投票

最新 RAD Studio (Delphi) 提供的 11 和 12 中的图像重采样非常简单

Bitmap.Resize(Width, Height)

它比以前简单得多,并且还正确保持了宽高比。

在编码位图的函数中使用它的示例(来自 Tina4Delphi 库)

function BitmapToBase64EncodedString(Bitmap: FMX.Graphics.TBitmap; Resize: Boolean = True; Width: Integer = 256; Height: Integer = 256): String;
var
  ByteStream: TBytesStream;
  Base64EncodedStream: TStringStream;
begin
  ByteStream := TBytesStream.Create();
  if Resize then
  begin
    Bitmap.Resize(Width, Height);
  end;
  Bitmap.SaveToStream(ByteStream);
  Base64EncodedStream := TStringStream.Create();
  try
    ByteStream.Position := 0;
    TNetEncoding.Base64.Encode(ByteStream, Base64EncodedStream);
    Result := Base64EncodedStream.DataString;
  finally
    Base64EncodedStream.Free;
    ByteStream.Free;
  end;
end;
© www.soinside.com 2019 - 2024. All rights reserved.