在WPF应用程序中以像素精度显示图像



我想在WPF应用程序中显示一些图像,但是微软"聪明地"将所有内容缩放到不正确的比例!我想要的是显示像素精度的图像。例如,如果图像是50x50,我想在屏幕上显示50像素和50像素,无论图像/屏幕的DPI设置是什么。

我尝试了以下操作,但不成功:

  1. 设置。png图像dpi为96。
  2. 在AssemblyInfo.cs
  3. 中添加[assembly: System.Windows.Media.DisableDpiAwareness]

问题是,我坐得离屏幕很远,所以在控制面板中我将文本大小设置得更大。但是我可以看出图片的大小不正确,因为当我在Photoshop中并排打开同一张图片时,它变得更大而且模糊了。

Dwayne Need创建了一篇关于这个问题的优秀博客文章,可以在这里找到。该代码在同一位置也可用。引用他的话:

下面包含的代码引入了一个名为Bitmap的新类。位图是Image的替代品,但不是显示任何图像源,它只显示位图源。这让我可以访问PixelWidth和PixelHeight属性来确定合适的大小。这个类的重要方面是:

  • 从UIElement而不是FrameworkElement派生,因为我不想要像MinWidth, MaxWidth,甚至Width这样的东西。
  • 位图。Source可以设置为任意BitmapSource。
  • 当测量时,它将返回适当的测量单位来显示位图的PixelWidth和PixelHeight
  • 当渲染时,它将偏移它所绘制的图像以与像素网格对齐
  • 每当布局更新时,它检查是否需要重新渲染以再次对齐像素网格。

这是一个非常直接的类,详细信息请查看源代码。

我希望他不介意我在这里分享代码:

示例窗口(一个更好的示例可以在原始代码中找到…):

<Window x:Class="ImageSnappingToPixels.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ImageSnappingToPixels"
    Height="300" Width="600">
  <Grid x:Name="root">
    <StackPanel Orientation="Horizontal">
      <local:Bitmap Source="picture1.png" />
      <local:Bitmap Source="picture2.png" />
    </StackPanel>
  </Grid>
</Window>

这是他完整的和未删节的Bitmap类:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace ImageSnappingToPixels
{
    class Bitmap : UIElement
    {
        public Bitmap()
        {
            _sourceDownloaded = new EventHandler(OnSourceDownloaded);
            _sourceFailed = new EventHandler<ExceptionEventArgs>(OnSourceFailed);
            LayoutUpdated += new EventHandler(OnLayoutUpdated);
        }
        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
            "Source",
            typeof(BitmapSource),
            typeof(Bitmap),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.AffectsRender |
                FrameworkPropertyMetadataOptions.AffectsMeasure,
                new PropertyChangedCallback(Bitmap.OnSourceChanged)));
        public BitmapSource Source
        {
            get
            {
                return (BitmapSource) GetValue(SourceProperty);
            }
            set
            {
                SetValue(SourceProperty, value);
            }
        }
        public event EventHandler<ExceptionEventArgs> BitmapFailed;
        // Return our measure size to be the size needed to display the bitmap pixels.
        protected override Size MeasureCore(Size availableSize)
        {
            Size measureSize = new Size();
            BitmapSource bitmapSource = Source;
            if(bitmapSource != null)
            {
                PresentationSource ps = PresentationSource.FromVisual(this);
                if (ps != null)
                {
                    Matrix fromDevice = ps.CompositionTarget.TransformFromDevice;
                    Vector pixelSize = new Vector(bitmapSource.PixelWidth, bitmapSource.PixelHeight);
                    Vector measureSizeV = fromDevice.Transform(pixelSize);
                    measureSize = new Size(measureSizeV.X, measureSizeV.Y);
                }
            }
            return measureSize;
        }
        protected override void OnRender(DrawingContext dc)
        {
            BitmapSource bitmapSource = this.Source;
            if (bitmapSource != null)
            {
                _pixelOffset = GetPixelOffset();
                // Render the bitmap offset by the needed amount to align to pixels.
                dc.DrawImage(bitmapSource, new Rect(_pixelOffset, DesiredSize));
            }
        }
        private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Bitmap bitmap = (Bitmap)d;
            BitmapSource oldValue = (BitmapSource)e.OldValue;
            BitmapSource newValue = (BitmapSource)e.NewValue;
            if (((oldValue != null) && (bitmap._sourceDownloaded != null)) && (!oldValue.IsFrozen && (oldValue is BitmapSource)))
            {
                ((BitmapSource)oldValue).DownloadCompleted -= bitmap._sourceDownloaded;
                ((BitmapSource)oldValue).DownloadFailed -= bitmap._sourceFailed;
                // ((BitmapSource)newValue).DecodeFailed -= bitmap._sourceFailed; // 3.5
            }
            if (((newValue != null) && (newValue is BitmapSource)) && !newValue.IsFrozen)
            {
                ((BitmapSource)newValue).DownloadCompleted += bitmap._sourceDownloaded;
                ((BitmapSource)newValue).DownloadFailed += bitmap._sourceFailed;
                // ((BitmapSource)newValue).DecodeFailed += bitmap._sourceFailed; // 3.5
            }
        }
        private void OnSourceDownloaded(object sender, EventArgs e)
        {
            InvalidateMeasure();
            InvalidateVisual();
        }
        private void OnSourceFailed(object sender, ExceptionEventArgs e)
        {
            Source = null; // setting a local value seems scetchy...
            BitmapFailed(this, e);
        }
        private void OnLayoutUpdated(object sender, EventArgs e)
        {
            // This event just means that layout happened somewhere.  However, this is
            // what we need since layout anywhere could affect our pixel positioning.
            Point pixelOffset = GetPixelOffset();
            if (!AreClose(pixelOffset, _pixelOffset))
            {
                InvalidateVisual();
            }
        }
        // Gets the matrix that will convert a point from "above" the
        // coordinate space of a visual into the the coordinate space
        // "below" the visual.
        private Matrix GetVisualTransform(Visual v)
        {
            if (v != null)
            {
                Matrix m = Matrix.Identity;
                Transform transform = VisualTreeHelper.GetTransform(v);
                if (transform != null)
                {
                    Matrix cm = transform.Value;
                    m = Matrix.Multiply(m, cm);
                }
                Vector offset = VisualTreeHelper.GetOffset(v);
                m.Translate(offset.X, offset.Y);
                return m;
            }
            return Matrix.Identity;
        }
        private Point TryApplyVisualTransform(Point point, Visual v, bool inverse, bool throwOnError, out bool success)
        {
            success = true;
            if (v != null)
            {
                Matrix visualTransform = GetVisualTransform(v);
                if (inverse)
                {
                    if (!throwOnError && !visualTransform.HasInverse)
                    {
                        success = false;
                        return new Point(0, 0);
                    }
                    visualTransform.Invert();
                }
                point = visualTransform.Transform(point);
            }
            return point;
        }
        private Point ApplyVisualTransform(Point point, Visual v, bool inverse)
        {
            bool success = true;
            return TryApplyVisualTransform(point, v, inverse, true, out success);
        }
        private Point GetPixelOffset()
        {
            Point pixelOffset = new Point();
            PresentationSource ps = PresentationSource.FromVisual(this);
            if (ps != null)
            {
                Visual rootVisual = ps.RootVisual;
                // Transform (0,0) from this element up to pixels.
                pixelOffset = this.TransformToAncestor(rootVisual).Transform(pixelOffset);
                pixelOffset = ApplyVisualTransform(pixelOffset, rootVisual, false);
                pixelOffset = ps.CompositionTarget.TransformToDevice.Transform(pixelOffset);
                // Round the origin to the nearest whole pixel.
                pixelOffset.X = Math.Round(pixelOffset.X);
                pixelOffset.Y = Math.Round(pixelOffset.Y);
                // Transform the whole-pixel back to this element.
                pixelOffset = ps.CompositionTarget.TransformFromDevice.Transform(pixelOffset);
                pixelOffset = ApplyVisualTransform(pixelOffset, rootVisual, true);
                pixelOffset = rootVisual.TransformToDescendant(this).Transform(pixelOffset);
            }
            return pixelOffset;
        }
        private bool AreClose(Point point1, Point point2)
        {
            return AreClose(point1.X, point2.X) && AreClose(point1.Y, point2.Y);
        }
        private bool AreClose(double value1, double value2)
        {
            if (value1 == value2)
            {
                return true;
            }
            double delta = value1 - value2;
            return ((delta < 1.53E-06) && (delta > -1.53E-06));
        }
        private EventHandler _sourceDownloaded;
        private EventHandler<ExceptionEventArgs> _sourceFailed;
        private Point _pixelOffset;
    }
}

最新更新