旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【WPF】 WindowStyle=NoneでWindowState=Maximizedにするとタスクバーが隠れる問題の対処法

タイトルの通りなんですが、WindowStyle=NoneでWindowsState=Maximizedにすると完全なフルスクリーン状態となりタスクバーも隠れてしまいます。アプリケーションによってはこれで問題無いのですが、自分の場合はタスクバーは表示させたいと思い、解決方法を探すと以下に書かれた方法でいけそうでした。

detail.chiebukuro.yahoo.co.jp

public MainWindow()
{
    InitializeComponent();

    Width = SystemParameters.WorkArea.Width;
    Height = SystemParameters.WorkArea.Height;
    Left = 0;
    Top = 0;
    WindowState = WindowState.Normal;
    ResizeMode = ResizeMode.NoResize;
}

【WPF】 拡大した画像上でクリックした座標の取得

今回やりたいことは文章だと説明が難しいのですが、下図の青丸部分をクリックするとクリックした箇所の座標は(x, y)=(75, 60)になります。 f:id:ni4muraano:20171021172729p:plain 次に上図のオレンジで囲った箇所を以前ブログに書いた方法で拡大表示して下図が得られたとします(【WPF】 Imageを拡大/平行移動させる - 旅行好きなソフトエンジニアの備忘録)。 f:id:ni4muraano:20171021172407p:plain
この時上図の青丸をクリックすると(x, y)=(50, 40)が得られます。ただ、拡大表示する前の座標は(75, 60)なので、拡大表示した状態で青丸をクリックしても(75, 60)という座標が欲しい、というのが今回やりたいことになります。

幸い以前ブログに書いた方法で拡大表示していたとすると、拡大表示するために利用した行列を参照できるので、この行列を利用すればやりたいことは実現できます。画像を表示するために使ったImageの名前をImage1にしたとすると、行列は以下の手順で取得できます。

// 拡大表示のために利用した行列を取得
var matrix = Image1.RenderTransform.Value;

この行列にはプロパティとしてM11, M12, M21, M22, OffsetX, OffsetYがありますが、これらは以下に対応しています。 \begin{pmatrix} M11 & M12 & OffsetX \\ M21 & M22 & OffsetY \\ 0 & 0 & 1 \end{pmatrix} 実際にはM12, M21には0が入るので、上記は更に簡単になります。 \begin{pmatrix} M11 & 0 & OffsetX \\ 0 & M22 & OffsetY \\ 0 & 0 & 1 \end{pmatrix} そして、この行列の逆行列を求めて拡大表示状態でクリックして得た座標に対して適用すると、拡大前の座標に戻すことができます。上記行列の逆行列は公式(2x2行列と3x3行列と4x4行列の逆行列の公式)から簡単に求めることができます。逆行列を以下のように定義すると、求める座標は以下から計算できます。

\begin{pmatrix} A11 & A12 & A13 \\ A21 & A22 & A23 \\ A31 & A32 & A33 \end{pmatrix}

DET=M11×M22
A11=M22÷DET
A12=0
A13=OffsetX×M22×(-1)÷DET
A21=0
A22=M11÷DET
A23=M11×OffsetY×(-1)÷DET

x=A11×x^{'} + A13
y=A22×y^{'} + A23

---2017年10月25日追記---
逆行列は自分で計算しなくてもInvertメソッドで取得可能でした。

// 拡大表示のために利用した行列を取得
var matrix = Image1.RenderTransform.Value;
// 逆行列の計算
var inverse = matrix;
inverse.Invert();

【WPF】 Imageを拡大/平行移動させる

表示している画像をマウスのホイールを使って拡大したり、ドラッグして平行移動させたいと思ったところ、以下の回答が評価が高かったです。

stackoverflow.com

ただ、リンクの通りに実装するとTransformGroupへキャストする時にキャストエラーが出て動作させることが出来ませんでした。でもリンク先を少し変更すると動作したのでメモしておきます。

まず、xaml側はこんな感じです。

<Border Name="Border1" ClipToBounds="True">
    <Image Name="Image1" MouseWheel="Image1_MouseWheel" MouseLeftButtonDown="Image1_MouseLeftButtonDown" MouseMove="Image1_MouseMove" MouseLeftButtonUp="Image1_MouseLeftButtonUp"/>
</Border>


次に拡大するためのロジックは以下のようになります。マウスホイールを回すことで、今マウスのある位置で拡大することができます。

private void Image1_MouseWheel(object sender, MouseWheelEventArgs e)
{
    // スケールの値を変えることでホイールを動かした時の拡大率を制御できます
    const double scale = 1.2;

    var matrix = Image1.RenderTransform.Value;
    if (e.Delta > 0)
    {
        // 拡大処理
        matrix.ScaleAt(scale, scale, e.GetPosition(this).X, e.GetPosition(this).Y);
    }
    else
    {
        // 縮小処理
        matrix.ScaleAt(1.0/scale, 1.0/scale, e.GetPosition(this).X, e.GetPosition(this).Y);
    }

    Image1.RenderTransform = new MatrixTransform(matrix);
}

f:id:ni4muraano:20171021134748p:plain:w250f:id:ni4muraano:20171021134758p:plain:w250
次にドラッグして平行移動させるロジックは以下になります。これにより、左クリックを押したままドラッグすることで画像を平行移動できます。

private Point _start;

private void Image1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Image1.CaptureMouse();
    _start = e.GetPosition(Border1);
}

private void Image1_MouseMove(object sender, MouseEventArgs e)
{
    if (Image1.IsMouseCaptured)
    {
        var matrix = Image1.RenderTransform.Value;

        Vector v = _start - e.GetPosition(Border1);
        matrix.Translate(-v.X, -v.Y);
        Image1.RenderTransform = new MatrixTransform(matrix);
        _start = e.GetPosition(Border1);
    }
}

private void Image1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    Image1.ReleaseMouseCapture();
}

f:id:ni4muraano:20171021135234p:plain:w250f:id:ni4muraano:20171021135239p:plain:w250
最後に以下を加えるとEscキーを押した時に拡大/縮小、平行移動した画像を元に戻せます。

private void Window_KeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Escape)
    {
        var matrix = Image1.RenderTransform.Value;
        matrix.M11 = 1.0;
        matrix.M12 = 0.0;
        matrix.M21 = 0.0;
        matrix.M22 = 1.0;
        matrix.OffsetX = 0.0;
        matrix.OffsetY = 0.0;
        Image1.RenderTransform = new MatrixTransform(matrix);
    }
}

【OpenCVSharp】 ヒートマップを作成する

OpenCVSharpを使ってヒートマップを描くプログラムのメモです。ヒートマップは赤⇒緑⇒青の順で遷移するため、HSV空間を使い、その後RGB空間に戻すと簡単に作成できます(OpenCVでは0が赤、60が緑、120が青に相当します)。

// 画像サイズ
const int size = 101;
// ガウス関数のシグマ
const double sigma = 20.0;
// HSVを記録する行列
var hsvMap = new Mat(new Size(size, size), MatType.CV_8UC3);
for (int i = -50; i <= 50; ++i)
{
    for (int j = -50; j <= 50; ++j)
    {
        // ガウス関数の値
        double g = Math.Exp(-(Math.Pow(i, 2) + Math.Pow(j, 2))/(2.0*Math.Pow(sigma, 2)));
        // 色相の計算(OpenCVでは0で赤、120で青)
        byte hue = (byte)(120*(1.0 - g));
        hsvMap.Set(i+50, j+50, new Vec3b(hue, 255, 255));
    }
}
// HSVをBGRに変換する
var bgrMap = new Mat(new Size(size, size), MatType.CV_8UC3);
Cv2.CvtColor(hsvMap, bgrMap, ColorConversionCodes.HSV2BGR);
// ヒートマップの保存
bgrMap.ImWrite("heatmap.bmp");
hsvMap.Dispose();
bgrMap.Dispose();

上記で出来たヒートマップが以下の写真になります。
f:id:ni4muraano:20171018215712j:plain

【WPF】 ボタンのサイズをリソースで指定する

作っているアプリケーションにボタンが沢山あるのですが、それらの幅、高さは全て共通なのでハードコーディングではなく一括管理したいと考えました。そのやり方ですが、以下の2ステップになります。

① App.xamlに以下を追加(xmlns:sysと<sys:Double ...>の三行を追加してます)

<Application x:Class="ResourcePractice.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:ResourcePractice"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <sys:Double x:Key="ButtonWidth">100</sys:Double>
        <sys:Double x:Key="ButtonHeight">100</sys:Double>
    </Application.Resources>
</Application>


② Buttonから先程のリソースを参照する

<Button Width="{StaticResource ButtonWidth}" Height="{StaticResource ButtonHeight}"/>


ちなみにこのやり方は以下のリンクを参考にしました。

stackoverflow.com

【WPF】 続・スクリーンショットを撮り続けて動画に保存する

以前スクリーンショットを撮り続けて動画に保存する記事を書いたのですが、実はMicrosoft Expression Encoderを使えばやりたいことは出来たようです。 ni4muraano.hatenablog.com

Microsoft Expression Encoderはここからダウンロードできます。NuGetからもダウンロードできるのですが、動作させようとすると良く分からないエラーが出て結局動かすことができませんでした。結局自分は下記手順で動作させることができました。

  1. リンクからMicrosoft Expression Encoderをダウンロード・インストールして
  2. NuGetからMicrosoft Expression Encoderをインストールする

プログラムについてですが、まずスクリーンショットを撮り続けて動画に保存する部分は以下になります(System.Drawing.Rectangleを使うためにSystem.Drawingを参照に追加してください)。

// usingに追加
using Microsoft.Expression.Encoder;
using Microsoft.Expression.Encoder.ScreenCapture;

// フィールドでキャプチャーを宣言
private readonly ScreenCaptureJob _capture = new ScreenCaptureJob();

// 設定、キャプチャー開始命令
_capture.CaptureRectangle = new System.Drawing.Rectangle(0, 0, 1200, 940);
_capture.ScreenCaptureVideoProfile.FrameRate = 30;
_capture.OutputScreenCaptureFileName = @"C:\Users\MyFolder\Videos\video.xesc";
_capture.Start();

そして、以下を実行することで保存した動画をWMVにエンコーディングできます。

// Stop, Disposeしないと動画が作成されない
_capture.Stop();
_capture.Dispose();
// 作成した動画をエンコーディングする
var media = new MediaItem(@"C:\Users\MyFolder\Videos\video.xesc"); ;
var job = new Job();
job.MediaItems.Add(media);
job.OutputDirectory = @"C:\Users\MyFolder\Videos";
job.Encode();

【WPF】 BitmapSourceクラスをbitmapとして保存する

BitmapSourceクラスをbitmapとして保存する方法を探していたのですが、以下に書かれていたのでメモします。

stackoverflow.com

public void SaveBitmapSourceToFile(BitmapSource bitmapSource, string filePath)
{
    using (var fileStream = new FileStream(filePath, FileMode.Create))
    {
        BitmapEncoder encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
        encoder.Save(fileStream);
    }
}