WPF:在Image中获得1:1像素渲染,其大小使用LayoutTransform进行修改

时间:2013-07-31 22:51:07

标签: wpf image xaml bitmap layouttransform

首先我要说的是,我已经对此进行了广泛的搜索并找到了部分答案,但没有任何方法可以解决问题。

我需要在我的WPF应用程序中显示未缩放的位图图像。我想将位图的1个像素映射到显示器的1个像素。我打算通过发送我的位图的多个版本来支持多种分辨率。但我想知道,当选择了一个特定的位图时,它将在设计时完全呈现。

我克服WPF中发生的自动缩放的策略是查看自动应用的内容(通过OS DPI设置),然后将一个相反的LayoutTransform应用到我窗口的最外层容器中

这确保了无论用户的DPI设置是什么,app都会将窗口内容呈现为WPF像素与硬件像素的1:1比例。到目前为止,非常好。

该代码看起来像这样。 (假设这是用1.0的参数调用的。)

    private void SetScale(double factor)
    {
        // First note the current window transform factor.
        // This is the factor being applied to the entire window due to OS DPI settings.
        Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
        double currentWindowTransformFactorX = m.M11;
        double currentWindowTransformFactorY = m.M22;

        // Now calculate the inverse.
        double currentWindowTransformInverseX = (1 / m.M11);
        double currentWindowTransformInverseY = (1 / m.M22);

        // This factor will put us "back to 1.0" in terms of a device-independent-pixel to physical pixel mapping.
        // On top of this, we can apply our caller-specified factor.
        double transformFactorX = currentWindowTransformInverseX * factor;
        double transformFactorY = currentWindowTransformInverseY * factor;

        // Apply the transform to the registered target container
        ScaleTransform dpiTransform = new ScaleTransform(transformFactorX, transformFactorY);
        if (dpiTransform.CanFreeze)
            dpiTransform.Freeze();

        this.pnlOutermost.LayoutTransform = dpiTransform;
    }

到目前为止,一切都很好。无论我将Windows DPI设置为什么,该主容器的内容总是完全相同,并且位图精确呈现。

现在来了有趣的部分。我希望通过提供特定于分辨率的图稿来支持不同的屏幕分辨率,并根据需要缩放整个UI。

事实证明,LayoutTransform对此非常有效。因此,如果我使用1.25或1.5或其他任何方式调用上述方法,整个UI都会缩放,一切看起来都很完美......除了我的图像,即使我将源更改为图像也是如此正确的尺寸适合新的缩放尺寸。

例如,假设我在XAML中有一个100x100的图像。我的作品有三种版本:100x100,125x125和150x150。当我缩放容纳图像的容器时,我也将该图像的来源更改为适当的容器。

有趣的是,如果图像对象位于一个位置,当按因子缩放时,会产生积分结果,那么缩放后的图像看起来很好。也就是说,假设图像具有以下属性:

Canvas.Left = 12
Canvas.Top = 100

当我们应用因子1.25时,得到15和125,图像看起来很棒。但如果图像移动一个像素,则说:

Canvas.Left = 13
Canvas.Top = 100

现在当我们应用因子1.25时,得到15.25和125,结果看起来很糟糕。

显然,这看起来像某种舍入问题或类似问题。所以我试过了:

UseLayoutRounding="True"
SnapsToDevicePixels="True"
RenderOptions.EdgeMode="Aliased" 
RenderOptions.BitmapScalingMode="NearestNeighbor"

我在窗口,缩放容器和图像对象中尝试过这些。什么都行不通。并且BitmapScalingMode无论如何都没有意义,因为图像根本不应该被缩放。

永远感谢任何能够阐明这一点的人。

1 个答案:

答案 0 :(得分:0)

我遇到了完全相同的问题,因此看来该框架截至2019年尚未解决。

我设法通过三步法解决了这个问题。

  1. 在我的顶级UI元素上启用布局四舍五入

    <UserControl ... UseLayoutRounding="True">
    
  2. 将反LayoutTransform应用于我的Image对象(LayoutTransform应用于父ListBox)。

    <Image ... LayoutTransform="{Binding Path=LayoutTransform.Inverse,
                                 Mode=OneTime,
                                 RelativeSource={RelativeSource FindAncestor,
                                 AncestorType={x:Type ListBox}}}">
    
  3. Image子类,并为OnRender添加自定义替代。

        internal class CustomImage: Image {
            private PresentationSource presentationSource;
    
            public CustomImage() => Loaded += OnLoaded;
    
            protected override void OnRender(DrawingContext dc) {
                if (this.Source == null) {
                    return;
                }
                var offset = GetOffset();
                dc.DrawImage(this.Source, new Rect(offset, this.RenderSize));
            }
    
            private Point GetOffset() {
                var offset = new Point(0, 0);
    
                var root = this.presentationSource?.RootVisual;
                var compositionTarget = this.presentationSource?.CompositionTarget;
                if (root == null || compositionTarget == null) {
                    return offset;
                }
    
                // Transform origin to device (pixel) coordinates.
                offset = TransformToAncestor(root).Transform(offset);
                offset = compositionTarget.TransformToDevice.Transform(offset);
    
                // Round to nearest integer value.
                offset.X = Math.Round(offset.X);
                offset.Y = Math.Round(offset.Y);
    
                // Transform back to local coordinate system.
                offset = compositionTarget.TransformFromDevice.Transform(offset);
                offset = root.TransformToDescendant(this).Transform(offset);
    
                return offset;
            }
    
            private void OnLoaded(object sender, RoutedEventArgs e) {
                this.presentationSource = PresentationSource.FromVisual(this);
                InvalidateVisual();
            }
        }
    }
    

第3步中的代码基于此blogpost

通过在XAML中使用CustomImage类而不是Image并绑定到一个BitmapSource,它将根据当前比例因子返回适当大小的图像,我设法实现了看起来没有任何不必要的缩放比例的图像。

请注意,当需要重新渲染图像时,可能需要在图像上调用InvalidateVisual