在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
相关产品推荐
相关产品推荐

