プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

WPFでアプリ画面をキャプチャーしてJpegで保存する(exif付き)

なにでググったらよかったのか分からなかったので自分で書きます(主にexifのところが)

ソース

github.com

とりあえず動くものを置きました。自由に使ってください

以下はその説明みたいな感じ

UIElementを画像にする

WPFなのでSystem.Drawingは使いません(たまに使うけど)

var width = (int)CaptureTarget.ActualWidth;
var height = (int)CaptureTarget.ActualHeight;

// UIElement to RenderTargetBitmap
var renderTargetBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
var visual = new DrawingVisual();
using (var context = visual.RenderOpen())
{
    var brush = new VisualBrush(CaptureTarget);
    context.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
renderTargetBitmap.Render(visual);

RenderTargetBitmapDrawingVisualでうまいこと作りましょう

exifを付ける

BitmapMetadataってのを作ってその中にいろいろ入れちゃいます

var encoder = new JpegBitmapEncoder();

// Add exif data
var metadata = new BitmapMetadata("jpg");
metadata.ApplicationName = Assembly.GetExecutingAssembly().GetName().Name;
metadata.Comment = Assembly.GetExecutingAssembly().GetName().Version.ToString();
metadata.DateTaken = DateTime.UtcNow.ToString(DateTimeFormatInfo.InvariantInfo);
//Custom metadata
//metadata.SetQuery("", "");

encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap, null, metadata, null));

一般的なのはプロパティとしてすでに生えているのでそこに代入

今回はアプリ名・コメント(バージョン)・作成日時を入れてみました

プロパティが無い奴はSetQueryメソッドで追加できるっぽいです

あとは保存して完成

f:id:puni-o:20171215112818j:plain

できました

ファイルの変更をRxで監視する

前人の知恵がググると簡単にでてきます

qiita.com

まぁこれでホボ完成なんですが私はコンソールアプリで読み込むだけだったので、スケジューラー指定してとかそういうところを割愛

するとファイルの変更イベントが2回上がるようになりました

ほぼ同時なので2回目のイベントでのファイル読み込みは1回目の読み込みの影響でだいたい失敗します

これまたググる

stackoverflow.com

メモ帳の場合ファイルの保存で1回、属性の変更で1回ファイルを更新するよ、そしてこの動作は他のアプリでも同じだよ、的なことが書いてあるような気がします

今回は変更があったらファイルを読むとかその程度の動きができればいいのでThrottleで2回連続を1回にまとめました

private FileSystemWatcher _watcher;
private IDisposable _disposable;

public FileWatcher(string fileName)
{
    var file = new FileInfo(fileName);
    _watcher = new FileSystemWatcher();
    _watcher.Path = file.Directory?.FullName;
    _watcher.Filter = file.Name;
    _watcher.NotifyFilter = NotifyFilters.LastWrite;
    _disposable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
        h => this._watcher.Changed += h,
        h => this._watcher.Changed -= h)
        .Throttle(TimeSpan.FromSeconds(.1))
        .Subscribe(e => this.ReadFile(e.EventArgs.FullPath));
    _watcher.EnableRaisingEvents = true;
}

public void Stop()
{
    _disposable?.Dispose();
    _watcher?.Dispose();
}

private void ReadFile(string file)
{
    try
    {
        using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
        {
        }
    }
    catch (Exception exp)
    {
        System.Diagnostics.Debug.WriteLine("read error.");
    }
}

まぁ100msも待てば十分でしょう

Microsoft.Maps.MapControl.WPF の残念なうごき

Microsoft.Maps.MapControl.WPFはNuGetで入れることができるWPF用の地図コントロールです

Bing Maps WPF Control

ベースとなる地図がBing MapsになっているだけのよくあるTMS(?)のコントロールで最新版は1.0.0.3(2015/02/19)となっています

このコントロールMapCore.BoundingRectangleプロパティを参照することで現在表示している領域の東西南北が取得できます

ということで地図が移動した時の西と東の度を画面上に表示するだけのアプリを作ってみました(わかると思いますが西を左側、東を右側にだしてます)

f:id:puni-o:20171109142740g:plain

東が180°を超えた瞬間に西と東が入れ替わります

BoundingRectangleプロパティが返すLocationRectクラスがWestとEastをイイ感じ(?)に入れ替えてくれるのが原因なんですが、どう見てもバグです、すごく困ります

LocationRectクラスはどうやら

  • West < East
  • 緯度は±180の中にある

を保証しようとしているためにこんなことになっているようです (これだと経度180度線が入った領域は表現できませんよね)

Microsoft.Maps.MapControl.WPFの中ではLocationRectを使う箇所がいくつかあるんですが、上の値チェックはクラスに側が保証していることを前提にできていたりするので、ちょっと修正とかできないんだろうなーとは思うんですがどうなんでしょう

そもそもバグ報告をどこにしていいのかも分からないし、とっくにサポートおわってんじゃないかとか思っていたり

ちなみにUWP版にはこのプロパティはありません