読者です 読者をやめる 読者になる 読者になる

[WPF] Imageがぼやける問題

Image を使って画像を表示させていると等倍で表示しているはずなのにぼやけてしまいました。
SnapsToDevicePixels や UseLayoutRounding では直らなかったので途方に暮れていたらいいものがありました。

blogs.msdn.microsoft.com


専用のコントロールを作っているので、Imageコントロールに移植します。
※2016/06/03 LayoutUpdatedイベントの処理を追記

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public class Image : System.Windows.Controls.Image {

    public Image() {
        this.LayoutUpdated += Image_LayoutUpdated;
    }

    private Point _pixelOffset;

    void Image_LayoutUpdated( object sender, EventArgs e ) {
        Point pixelOffset = GetPixelOffset();
        if( !AreClose( pixelOffset, _pixelOffset ) ) {
            InvalidateVisual();
        }
    }

    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 ) );
    }

    protected override Size MeasureOverride( Size constraint ) {
        // ちょっとだけ修正 BitmapSourceの時だけ処理する
        var bitmapSource = Source as BitmapSource;
        if( bitmapSource != null ) {
            Size measureSize = new Size();

            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;
        }

        return base.MeasureOverride( constraint );
    }

    protected override void OnRender( System.Windows.Media.DrawingContext dc ) {
        _pixelOffset = GetPixelOffset();
        dc.DrawImage( this.Source, new Rect( _pixelOffset, DesiredSize ) );
    }

    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 );
            // エラーが出るので nullの時の処理を追記
            if( rootVisual.TransformToDescendant( this ) != null ) {
                pixelOffset = rootVisual.TransformToDescendant( this ).Transform( pixelOffset );
            }
        }

        return pixelOffset;
    }

    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 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;
    }
  }

論理ピクセルとデバイピクセルのズレはもういや><