WPFで画像のピクセルの情報調べるツール「GrayscalePixelAnalyzer」を作る。

コンピュータ

PNG画像をウィンドウにドロップすると、画像がそのまま表示されます。
シンプルなビューアのように見えますが、このツールの本質はそこではありません。

画像上をクリックすると、クリックした座標のピクセル情報を取得し、数値として確認することができます。


表示される情報は以下の通りです。

  • クリック位置の座標(x, y)
  • そのピクセルの輝度値(グレースケール)
  • 左右8ピクセル分の値
  • クリック位置を基準とした差分(前後差)

例えば、次のような情報が表示されます。

(120,50) = 127

[Prev]
Val :
120 122 125 126 127 127 126 125
Diff:
 -7  -5  -2  -1   0   0  -1  -2

[Next]
Val :
127 128 130 200 200 130 128 127
Diff:
  0   1   3  73  73   3   1   0

このように、単なるピクセル値だけでなく「周辺との変化」を見ることができます。


このツールで分かること

数値と差分を観察することで、画像の構造が見えてきます。

なだらかな変化 → グラデーション
大きな差分 → エッジ(輪郭)
一定のパターン → トーン(網点)

特に差分を見ることで、「どこで変化が起きているのか」が直感的に把握できます。


なぜグレースケールなのか

今回はグレースケール画像に変換して扱っています。

カラーのままだとRGB3チャンネル分の情報があり、解析が分かりにくくなるためです。
グレースケールにすることで、1ピクセル = 1値となり、変化の傾向がシンプルに見えるようになります。

ソースファイル

MainWindow.xaml

<Window x:Class="GrayscalePixelAnalyzer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GrayscalePixelAnalyzer"
        xmlns:h="clr-namespace:Maywork.WPF.Helpers"
        mc:Ignorable="d"
        Title="GrayscalePixelAnalyzer" Height="450" Width="800">

<Grid
    x:Name="DropBase"
    h:FileDropHelper.Enable="True">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TextBlock
        Grid.Row="0"
        FontSize="16"
        Text="ここにファイルをドロップ"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"/>
    
	<ScrollViewer
        Grid.Row="0"
        h:ImageScaleHelper.Enable="True">
		<Canvas>
			<Image
                x:Name="Image1"
                MouseLeftButtonDown="OnClick"/>
		</Canvas>
	</ScrollViewer>

    <!-- 解析表示 -->
    <StackPanel Grid.Row="1">
        <TextBox x:Name="CurrentBox" FontFamily="Consolas"/>
        <TextBox x:Name="PrevBox" FontFamily="Consolas"/>
        <TextBox x:Name="NextBox" FontFamily="Consolas"/>
        <Canvas x:Name="GraphCanvas" Height="100" Background="Black"/>
    </StackPanel>
</Grid>

</Window>

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Input;

using Maywork.WPF.Helpers;
using ImageBuffer = Maywork.WPF.Helpers.ImageBufferHelper.ImageBuffer;
using System.Text;
using System.Windows.Shapes;
using System.Windows.Media;

namespace GrayscalePixelAnalyzer;

public partial class MainWindow : Window
{
    ICommand DropCommand;
    ImageBuffer _img;

    public MainWindow()
    {
        InitializeComponent();

        _img = ImageBufferHelper.Create(1, 1, 1);
    
        DropCommand = new RelayCommand<string[]>(
            files => DropFile(files));
        
        FileDropHelper.SetDropCommand(DropBase, DropCommand);
    }
    // ファイルドロップ
    void DropFile(string[] files)
    {
        var file = files.Where(
            x => System.IO.Path.GetExtension(x)
            .Equals(".png", StringComparison.CurrentCultureIgnoreCase))
            .FirstOrDefault();
        if (file is null) return;

        var buff = SimplePngDecoder.Load(file)
            .ToGray();
        
        _img = buff;

        Image1.Source = buff.ToBitmapSource();
    }
    // クリック処理
    private void OnClick(object sender, MouseButtonEventArgs e)
    {
        var pos = e.GetPosition(Image1);

        int x = (int)(pos.X * _img.Width / Image1.ActualWidth);
        int y = (int)(pos.Y * _img.Height / Image1.ActualHeight);

        if (x < 0 || y < 0 || x >= _img.Width || y >= _img.Height)
            return;

        DumpAround(x, y);
        DrawGraph(x, y);
        DrawDiffGraph(x, y);
    }
    // ダンプ処理
    private void DumpAround(int x, int y)
    {
        int w = _img.Width;
        byte center = _img.GetPixelSpan(x, y)[0];

        // =========================
        // Current
        // =========================
        CurrentBox.Text = $"({x},{y}) = {center}";

        // =========================
        // Prev(左)
        // =========================
        var prevVal = new StringBuilder();
        var prevDiff = new StringBuilder();

        for (int i = 8; i >= 1; i--)
        {
            int px = x - i;

            if (px < 0)
            {
                prevVal.Append(" ----");
                prevDiff.Append(" ----");
                continue;
            }

            byte v = _img.GetPixelSpan(px, y)[0];
            int d = v - center;

            prevVal.Append($"{v,4}");
            prevDiff.Append($"{d,4}");
        }

        PrevBox.Text = $"[Prev]\nVal : {prevVal}\nDiff: {prevDiff}";

        // =========================
        // Next(右)
        // =========================
        var nextVal = new StringBuilder();
        var nextDiff = new StringBuilder();

        for (int i = 1; i <= 8; i++)
        {
            int px = x + i;

            if (px >= w)
            {
                nextVal.Append(" ----");
                nextDiff.Append(" ----");
                continue;
            }

            byte v = _img.GetPixelSpan(px, y)[0];
            int d = v - center;

            nextVal.Append($"{v,4}");
            nextDiff.Append($"{d,4}");
        }

        NextBox.Text = $"[Next]\nVal : {nextVal}\nDiff: {nextDiff}";
    }

    private void DrawGraph(int x, int y)
    {
        GraphCanvas.Children.Clear();

        int w = _img.Width;
        var p = _img.Pixels;

        int baseIndex = y * w;

        double scaleX = GraphCanvas.ActualWidth / 16.0;
        double scaleY = GraphCanvas.ActualHeight / 255.0;

        var line = new Polyline
        {
            Stroke = Brushes.Lime,
            StrokeThickness = 1
        };

        // 前後8ピクセル
        for (int i = -8; i <= 8; i++)
        {
            int px = x + i;

            if (px < 0 || px >= w) continue;

            byte v = p[baseIndex + px];

            double gx = (i + 8) * scaleX;
            double gy = GraphCanvas.ActualHeight - v * scaleY;

            line.Points.Add(new Point(gx, gy));
        }

        GraphCanvas.Children.Add(line);
    }
    private void DrawDiffGraph(int x, int y)
    {
        //GraphCanvas.Children.Clear();

        int w = _img.Width;
        var p = _img.Pixels;

        int baseIndex = y * w;

        double scaleX = GraphCanvas.ActualWidth / 16.0;
        double scaleY = GraphCanvas.ActualHeight / 255.0;

        byte center = p[baseIndex + x];

        var line = new Polyline
        {
            Stroke = Brushes.Red,
            StrokeThickness = 1
        };

        for (int i = -8; i <= 8; i++)
        {
            int px = x + i;

            if (px < 0 || px >= w) continue;

            int diff = p[baseIndex + px] - center;

            // -128〜+128 → 0〜255に変換
            int v = diff + 128;

            double gx = (i + 8) * scaleX;
            double gy = GraphCanvas.ActualHeight - v * scaleY;

            line.Points.Add(new Point(gx, gy));
        }

        GraphCanvas.Children.Add(line);
    }
}

コメント