【问题标题】:WPF: Get 1:1 pixel rendering in Image whose size is modified with a LayoutTransformWPF:在使用 LayoutTransform 修改大小的图像中获取 1:1 像素渲染
【发布时间】:2013-07-31 22:51:07
【问题描述】:

首先让我说我已经对此进行了广泛的搜索,并找到了部分答案,但没有任何解决办法。

我需要在我的 WPF 应用程序中显示未缩放的位图图像。我想将位图的 1 个像素映射到显示器的 1 个像素。我确实打算通过发送多个版本的位图来支持多种分辨率。 But I want to know that, when a particular bitmap has been chosen, it will be rendered EXACTLY as it has been designed.

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

这可确保无论用户的 DPI 设置是什么,应用都会以 1:1 的 WPF 像素与硬件像素的比例呈现窗口内容。到目前为止,一切顺利。

该代码如下所示。 (假设这是使用 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 并没有什么意义,因为图像根本不应该被缩放。

永远感谢任何能对此有所了解的人。

【问题讨论】:

  • 只是出于好奇,因为我不理解 factor 参数。打电话给pnlOutermost.LayoutTransform = new MatrixTransform(PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice) 还不够吗?注意 TransformFromDevice 中的 From。当我将该变换应用到 WPF 应用程序的顶部网格时,它会“缩小”到标准的 96 dpi 或 100%。当然,这并不能解决您的对齐问题。
  • @Clemens,在你回到 1.0 之后,factor 参数可以支持额外的规模。因此,如果您发送 1.0,无论 DPI 如何,您都将返回 1-1 映射。如果您通过 1.25,您将获得该比例,而不必担心操作系统缩放的影响。

标签: wpf image xaml bitmap layouttransform


【解决方案1】:

我遇到了完全相同的问题,因此截至 2019 年,该问题似乎尚未在框架中得到修复。

我设法使用三步方法解决了这个问题。

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

    <UserControl ... UseLayoutRounding="True">
    
  2. 将逆 LayoutTransform 应用于我的 Imageobjects(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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-11
    • 1970-01-01
    • 2014-10-14
    • 1970-01-01
    • 1970-01-01
    • 2015-09-02
    • 2011-09-06
    相关资源
    最近更新 更多