You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Android/iOS上将FireMonkey TBitmap转换为Windows Bitmap(.bmp)?

当然没问题!在Delphi Tokyo开发Android应用时,要把TBitmapTBitmapSurface转换成标准Windows Bitmap(BMP)格式并存入流,其实有几种靠谱的方案,甚至不需要自定义TBmpBitmapCodec(不过真要定制的话也能做到)。下面我给你详细说说:

方案一:用TBitmapCodecManager快速实现(推荐)

Delphi的TBitmapCodecManager已经内置了对BMP格式的支持,哪怕是在Android平台上也能直接用。它会帮你处理BMP的文件头、信息头和像素编码,生成标准的Windows Bitmap格式,省心又可靠。

步骤与代码示例:

  1. 先把TBitmap转换为TBitmapSurface(如果源是TBitmapSurface可以跳过这步);
  2. 确保像素格式为24位RGB(Windows BMP最常用的无压缩格式);
  3. TBitmapCodecManager.SaveToStream将表面数据写入流。
uses
  System.SysUtils, System.Classes, FMX.Graphics, FMX.Surfaces, FMX.Imaging;

function ConvertToWindowsBMP(const ASource: TBitmap; out AStream: TMemoryStream): Boolean;
var
  LSurface: TBitmapSurface;
begin
  Result := False;
  AStream := nil;
  if not Assigned(ASource) then Exit;

  LSurface := TBitmapSurface.Create;
  try
    // 将TBitmap内容复制到BitmapSurface
    LSurface.Assign(ASource);
    
    // 转换为24位RGB格式(Windows BMP标准格式)
    if LSurface.Format <> TPixelFormat.RGB then
      LSurface.Convert(TPixelFormat.RGB);

    AStream := TMemoryStream.Create;
    // 保存为BMP格式到流
    if TBitmapCodecManager.SaveToStream(AStream, LSurface, TBMPImageFormat) then
    begin
      AStream.Position := 0; // 重置流指针到开头,方便后续发送
      Result := True;
    end
    else
    begin
      FreeAndNil(AStream);
      Exit;
    end;
  finally
    LSurface.Free;
  end;
end;

调用这个函数后,AStream里就是标准的Windows BMP数据,可以直接通过DataSnap发送给服务器。

方案二:手动构建BMP文件结构(适合定制需求)

如果你需要对BMP的细节完全控制(比如调整压缩方式、自定义像素格式),可以手动构建BMP的文件头、信息头和像素数据。不过这个方式需要注意BMP的存储规则:比如像素行是从下到上存储,24位BMP的像素顺序是BGR而非RGB,且每行字节数要对齐到4字节。

代码示例:

uses
  System.SysUtils, System.Classes, FMX.Graphics, FMX.Surfaces, Winapi.Windows;

function ManualConvertToBMP(const ASource: TBitmap; out AStream: TMemoryStream): Boolean;
var
  LSurface: TBitmapSurface;
  BmpFileHeader: TBitmapFileHeader;
  BmpInfoHeader: TBitmapInfoHeader;
  RowSize: Integer;
  I, J: Integer;
  SrcPixel: PRGBQuad;
  DestBytes: PByte;
begin
  Result := False;
  AStream := nil;
  if not Assigned(ASource) then Exit;

  LSurface := TBitmapSurface.Create;
  try
    LSurface.Assign(ASource);
    // 转换为24位RGB格式
    if LSurface.Format <> TPixelFormat.RGB then
      LSurface.Convert(TPixelFormat.RGB);

    AStream := TMemoryStream.Create;
    try
      // 填充BMP文件头
      FillChar(BmpFileHeader, SizeOf(BmpFileHeader), 0);
      BmpFileHeader.bfType := $4D42; // 标识为BMP文件('BM')
      BmpFileHeader.bfOffBits := SizeOf(BmpFileHeader) + SizeOf(BmpInfoHeader);

      // 填充BMP信息头
      FillChar(BmpInfoHeader, SizeOf(BmpInfoHeader), 0);
      BmpInfoHeader.biSize := SizeOf(BmpInfoHeader);
      BmpInfoHeader.biWidth := LSurface.Width;
      BmpInfoHeader.biHeight := LSurface.Height;
      BmpInfoHeader.biPlanes := 1;
      BmpInfoHeader.biBitCount := 24; // 24位无压缩
      BmpInfoHeader.biCompression := BI_RGB;
      // 计算每行字节数(对齐到4字节)
      RowSize := ((LSurface.Width * 3 + 3) div 4) * 4;
      BmpInfoHeader.biSizeImage := RowSize * LSurface.Height;
      BmpFileHeader.bfSize := BmpFileHeader.bfOffBits + BmpInfoHeader.biSizeImage;

      // 写入文件头和信息头到流
      AStream.WriteBuffer(BmpFileHeader, SizeOf(BmpFileHeader));
      AStream.WriteBuffer(BmpInfoHeader, SizeOf(BmpInfoHeader));

      // 处理像素数据:从底部行开始写入,转换RGB为BGR顺序
      GetMem(DestBytes, RowSize);
      try
        for I := LSurface.Height - 1 downto 0 do
        begin
          FillChar(DestBytes^, RowSize, 0);
          SrcPixel := LSurface.ScanLine[I];
          for J := 0 to LSurface.Width - 1 do
          begin
            // BMP是BGR顺序,所以交换R和B
            PByte(Cardinal(DestBytes) + J*3)^ := SrcPixel^.gbBlue;
            PByte(Cardinal(DestBytes) + J*3 + 1)^ := SrcPixel^.gbGreen;
            PByte(Cardinal(DestBytes) + J*3 + 2)^ := SrcPixel^.gbRed;
            Inc(SrcPixel);
          end;
          AStream.WriteBuffer(DestBytes^, RowSize);
        end;
      finally
        FreeMem(DestBytes);
      end;

      AStream.Position := 0;
      Result := True;
    except
      FreeAndNil(AStream);
      raise;
    end;
  finally
    LSurface.Free;
  end;
end;

这个方案灵活性高,但需要熟悉BMP的文件结构,容易出错,所以除非有特殊需求,优先用方案一。

关于自定义TBmpBitmapCodec的说明

其实Delphi已经在FMX.Imaging.BMP单元中内置了TBMPBitmapCodec,它就是用来处理BMP格式编码解码的,默认的TBitmapCodecManager已经会调用它。所以你完全不需要自己自定义Codec,除非你需要支持一些非标准的BMP变种(比如带Alpha通道的32位BMP,或者自定义压缩方式)。如果真要扩展,你可以继承TBitmapCodec类实现自己的编码逻辑,但一般来说自带的Codec足够满足你的需求。

缩略图生成的补充

你需要生成128×128的缩略图,在转换为BMP之前,可以先对原图进行缩放:

var
  OriginalBitmap: TBitmap;
  ThumbBitmap: TBitmap;
  BmpStream: TMemoryStream;
begin
  // 假设OriginalBitmap是你从手机获取的原图
  ThumbBitmap := TBitmap.Create(128, 128);
  try
    ThumbBitmap.Assign(OriginalBitmap);
    // 缩放到128×128
    ThumbBitmap.Resize(128, 128);
    // 转换为BMP流
    if ConvertToWindowsBMP(ThumbBitmap, BmpStream) then
    begin
      // 这里可以把BmpStream通过DataSnap发送给服务器
      // ...
      BmpStream.Free;
    end;
  finally
    ThumbBitmap.Free;
  end;
end;

TBitmap.Resize方法简单快捷,也可以用TBitmapSurface.Resize来获得更高的性能。


内容的提问来源于stack exchange,提问作者Erwin Sienknecht

火山引擎 最新活动