WPFクリップボード経由で画像を白黒化(2値化)する。

瀧の川の圖 コンピュータ
出典:国立国会図書館「NDLイメージバンク」 (https://rnavi.ndl.go.jp/imagebank/)
GIMPで選択コピーした画像をクリップボード経由でOpenCvSharpのフィルターをかけて、その結果をクリップボード経由でGIMPに貼り付けて戻します。
GIMPの外部フィルターのような使い方が出来ないかと試作してみました。

プロジェクトの作成

dotnet new wpf -n ClipThreshold
cd ClipThreshold
dotnet add package OpenCvSharp4.Windows
dotnet add package OpenCvSharp4.runtime.win
dotnet add package OpenCvSharp4.WpfExtensions
dotnet add package System.Drawing.Common
code .

ソースコード

ファイル名:MainWindow.xaml

<Window x:Class="ClipThreshold.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:ClipThreshold"
        mc:Ignorable="d"
        FontSize="12pt"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="5" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>
        <StackPanel
            Margin="10"
            Grid.Column="0">
            <Label>白黒化(2値化)</Label>
            <WrapPanel Margin="10">
                <Label>閾値</Label>
                <Label Content="{Binding ElementName=Slider1, Path=(Slider.Value)}" />
                <Slider
                    x:Name="Slider1"
                    Minimum="0"
                    Maximum="255"
                    IsSnapToTickEnabled="True"
                    TickFrequency="1"
                    SmallChange="1"
                    LargeChange="1"
                    Value="127"
                    Width="200" />
            </WrapPanel>
        </StackPanel>
        <GridSplitter
            Grid.Column="1"
            HorizontalAlignment="Stretch"
            Background="Gray" />
        <StackPanel
            Margin="10"
            Grid.Column="2">
            <Canvas
                Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel}}"
                Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel}}">          
                <Image
                    x:Name="ViewImage"
                    Stretch="None">
                </Image>
            </Canvas>
        </StackPanel>
    </Grid>
</Window>

ファイル名:MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using OpenCvSharp;
using OpenCvSharp.WpfExtensions;

namespace ClipThreshold
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        /********************************************************************
        * フィルター・白黒化(2値化)
        *********************************************************************/
        private BitmapSource filter(BitmapSource inBitmap, int threshold = 32)
        {
            using var mat = BitmapSourceConverter.ToMat(inBitmap);
            Cv2.CvtColor(mat, mat, ColorConversionCodes.BGRA2GRAY);
            Cv2.Threshold(mat, mat, threshold, 255, ThresholdTypes.Binary);
            Cv2.CvtColor(mat, mat, ColorConversionCodes.GRAY2BGRA);
            var outBitmap = BitmapSourceConverter.ToBitmapSource(mat);
            outBitmap.Freeze();

            return outBitmap;
        }

        public MainWindow()
        {
            InitializeComponent();
            
            
            var ms = (System.IO.MemoryStream)Clipboard.GetData("PNG");
            if (ms is null)
            {
                System.Windows.Application.Current.Shutdown();
                MessageBox.Show("クリップボードに画像が存在しない。");
                return;
            }

            var bi = new BitmapImage();
            bi.BeginInit();
            bi.CacheOption = BitmapCacheOption.OnLoad;
            bi.StreamSource = ms;
            bi.EndInit();
            bi.Freeze();

            ViewImage.Source = filter(bi, (int)Slider1.Value);
            
            Slider1.ValueChanged += async (s, e) =>
            {
                int t = (int)Slider1.Value;
                var b = await Task.Run(() => filter(bi, t));
                ViewImage.Source = b;
            };

            this.Closing += (s, e) =>
            {
                if (ViewImage.Source is null) { return; }
                BitmapSource bi =  (BitmapSource)ViewImage.Source;

                var pngEnc = new PngBitmapEncoder();
                pngEnc.Frames.Add(BitmapFrame.Create(bi));
                using var mem = new System.IO.MemoryStream();
                pngEnc.Save(mem);

                Clipboard.SetData("PNG", mem);
            };
        }
    }
}

実行

GIMPで画像をコピーしクリップボードに画像がセットされている状態で起動。

スライダーを動かすと閾値が変化する。
閉じるとフィルター後の画像がクリップボードにコピーされているので、GIMPで貼り付ける。
OpenCvSharpで2値化してみる。
OpenCvSharpでグレースケールで読み込んだ画像を2値化(白黒)にしてみました。 ソース // // 二値化 // using System; using System.Windows.Forms; using System.Draw...

コメント