You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Direct2D中无质量损失加载192DPI PNG图像的问题求助

解决WICImage转ID2D1Bitmap时DPI与尺寸冲突的问题

你遇到的这个问题其实是D2D的核心设计逻辑导致的——D2D会根据渲染目标的DPI和图像的DPI差异自动调整位图的像素尺寸,来保证图像的物理显示大小一致。咱们一步步拆解问题和解决方案:

问题根源

  • 当你传nil给CreateBitmapFromWicBitmap的bitmapProperties参数时,D2D会默认使用渲染目标的DPI(通常是系统默认的96)来创建位图。这时候图像的像素尺寸和WIC里的原始尺寸一致,但DPI被强制改成96,导致实际渲染时图像会被拉伸(因为192DPI的图像在96DPI的画布上,物理大小会翻倍)。
  • 当你传入带192DPI的bitmapProperties时,D2D发现渲染目标的DPI(96)和图像DPI(192)不匹配,就会自动把像素尺寸减半(192/96=2),这样图像在画布上的物理大小才会和原始设计一致,但这直接压缩了像素数,损失了图像质量。

解决方案:让渲染目标DPI与图像DPI对齐,或手动创建位图

要同时保留原始像素尺寸和原始DPI,有两种靠谱的方法:

方法1:调整渲染目标的DPI匹配图像

如果你的应用可以针对单张图像调整渲染目标的DPI(比如专门用于显示高DPI图像的控件),可以先获取图像的原始DPI,再初始化/重置渲染目标的DPI:

首先,修改渲染目标的初始化逻辑,支持传入自定义DPI:

procedure TYourClass.InitializeRenderTarget(ADpiX, ADpiY: Double);
var
  RenderTargetProps: D2D1_HWND_RENDER_TARGET_PROPERTIES;
  D2DFactory: ID2D1Factory;
begin
  // 初始化D2D单线程工厂
  D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, D2DFactory);
  
  // 配置渲染目标属性,指定目标DPI
  RenderTargetProps := D2D1_HWND_RENDER_TARGET_PROPERTIES(
    D2D1_RENDER_TARGET_PROPERTIES_DEFAULT,
    D2D1_SIZE_U(ClientWidth, ClientHeight),
    ADpiX,
    ADpiY
  );
  
  // 创建适配指定DPI的渲染目标
  D2DFactory.CreateHwndRenderTarget(RenderTargetProps, FRenderTarget);
end;

然后修正你的LoadBitmapDX方法:

procedure TPrimitiveBitmap.LoadBitmapDX(AStream: TStream);
var
  TmpWicFactory: IWICImagingFactory;
  TmpWicImage: TWICImage;
  TmpConverter: IWICFormatConverter;
  TmpProperties: TD2D1BitmapProperties;
  TmpDpiX, TmpDpiY: Double;
  TmpPixelWidth, TmpPixelHeight: UINT;
begin
  TmpWicImage := TWICImage.Create();
  try
    TmpWicImage.LoadFromStream(AStream);
    TmpWicFactory := TmpWicImage.ImagingFactory;
    
    // 获取图像的原始像素尺寸(注意是像素数,不是物理尺寸)
    TmpWicImage.Handle.GetSize(TmpPixelWidth, TmpPixelHeight);
    // 获取图像的原始DPI
    TmpWicImage.Handle.GetResolution(TmpDpiX, TmpDpiY);
    
    // 如果当前渲染目标的DPI和图像不一致,重置渲染目标
    if (FRenderTarget.GetDpi.x <> TmpDpiX) or (FRenderTarget.GetDpi.y <> TmpDpiY) then
    begin
      FRenderTarget := nil;
      InitializeRenderTarget(TmpDpiX, TmpDpiY);
    end;
    
    // 初始化格式转换器,转成D2D支持的PBGRA格式
    TmpWicFactory.CreateFormatConverter(TmpConverter);
    TmpConverter.Initialize(
      TmpWicImage.Handle,
      GUID_WICPixelFormat32bppPBGRA,
      WICBitmapDitherTypeNone,
      nil,
      0,
      WICBitmapPaletteTypeMedianCut
    );
    
    // 设置位图属性,严格匹配图像的DPI和像素格式
    TmpProperties.pixelFormat := D2D1PixelFormat(D2D1_ALPHA_MODE_PREMULTIPLIED, DXGI_FORMAT_B8G8R8A8_UNORM);
    TmpProperties.dpiX := TmpDpiX;
    TmpProperties.dpiY := TmpDpiY;
    
    // 此时渲染目标DPI和图像DPI一致,D2D不会自动缩放像素尺寸
    FRenderTarget.CreateBitmapFromWicBitmap(TmpConverter, @TmpProperties, FBitmapD2D);
    
    // 现在FBitmapD2D的像素尺寸和原始图像一致,DPI也保留了192
    FSize := TSizeF.Create(TmpPixelWidth, TmpPixelHeight);
    FResolution := TPointF.Create(TmpDpiX, TmpDpiY);
  finally
    TmpConverter := nil;
    TmpWicFactory := nil;
    FreeAndNil(TmpWICImage);
  end;
end;

方法2:手动读取像素数据创建位图(不修改渲染目标DPI)

如果你的应用需要固定渲染目标的DPI(比如适配系统全局DPI),可以直接读取WIC图像的原始像素数据,绕开D2D的自动缩放逻辑:

procedure TPrimitiveBitmap.LoadBitmapDX(AStream: TStream);
var
  TmpWicImage: TWICImage;
  TmpBitmap: IWICBitmap;
  TmpLock: IWICBitmapLock;
  TmpData: WICRect;
  TmpBuffer: PByte;
  TmpStride: UINT;
  TmpPixelWidth, TmpPixelHeight: UINT;
  TmpDpiX, TmpDpiY: Double;
  TmpProperties: TD2D1BitmapProperties;
begin
  TmpWicImage := TWICImage.Create();
  try
    TmpWicImage.LoadFromStream(AStream);
    TmpBitmap := TmpWicImage.Handle as IWICBitmap;
    
    // 获取原始像素尺寸和DPI
    TmpBitmap.GetSize(TmpPixelWidth, TmpPixelHeight);
    TmpBitmap.GetResolution(TmpDpiX, TmpDpiY);
    
    // 锁定WIC位图的像素缓冲区,准备读取原始数据
    TmpData.X := 0;
    TmpData.Y := 0;
    TmpData.Width := TmpPixelWidth;
    TmpData.Height := TmpPixelHeight;
    TmpBitmap.Lock(&TmpData, WICBitmapLockRead, TmpLock);
    
    try
      TmpLock.GetDataPointer(TmpBuffer);
      TmpLock.GetStride(TmpStride);
      
      // 设置位图属性,指定原始DPI和像素格式
      TmpProperties.pixelFormat := D2D1PixelFormat(D2D1_ALPHA_MODE_PREMULTIPLIED, DXGI_FORMAT_B8G8R8A8_UNORM);
      TmpProperties.dpiX := TmpDpiX;
      TmpProperties.dpiY := TmpDpiY;
      
      // 直接从原始像素数据创建D2D位图,完全保留像素尺寸和DPI
      FRenderTarget.CreateBitmap(
        D2D1_SIZE_U(TmpPixelWidth, TmpPixelHeight),
        TmpBuffer,
        TmpStride,
        TmpProperties,
        FBitmapD2D
      );
    finally
      TmpLock := nil;
    end;
    
    FSize := TSizeF.Create(TmpPixelWidth, TmpPixelHeight);
    FResolution := TPointF.Create(TmpDpiX, TmpDpiY);
  finally
    TmpBitmap := nil;
    FreeAndNil(TmpWICImage);
  end;
end;

为什么这两种方法有效

  • 方法1通过让渲染目标的DPI和图像完全对齐,消除了D2D的自动缩放触发条件——D2D认为图像的物理大小和渲染目标的坐标系匹配,不需要调整像素尺寸。
  • 方法2直接绕过了WIC到D2D的转换逻辑,手动传递原始像素数据,强制D2D保留原始像素数和DPI,完全掌控位图的创建过程。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 00:17:44