トップ回答者
マルチタッチを用いての複数線の同時描画とパフォーマンスについて

質問
-
お世話になります。
マルチタッチで同時に複数の線を描画可能とする簡易お絵かきアプリを下記サイトを参考にして作成しております。
https://code.msdn.microsoft.com/CVBXAML-WPF-4-TouchDown-b1018a60/
https://code.msdn.microsoft.com/windowsdesktop/CVBXAML-WPF-Windows-WPF-0738a600
XAMLコード:
<Window x:Name="window" x:Class="multiTouchApp.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:multiTouchApp" mc:Ignorable="d" Title="MainWindow" Height="700" Width="1050"> <Grid> <StackPanel x:Name="stackPanel" HorizontalAlignment="Center" VerticalAlignment="Center"> <Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="{Binding Width, ElementName=image1}" Height="{Binding Height, ElementName=image1}" > <Image x:Name="image1" Stretch="Fill" TouchDown="image1_TouchDown" TouchMove="image1_TouchMove" TouchUp="image1_TouchUp" Width="1000" Height="600" /> </Border> </StackPanel> </Grid> </Window>
C#コード:
namespace multiTouchApp { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { // タッチ情報 public class TouchData { public Point lastTouchPoint; public Brush brush; public double press; } RenderTargetBitmap bitmap; public MainWindow() { InitializeComponent(); bitmap = new RenderTargetBitmap((int)image1.Width, (int)image1.Height, 96, 96, PixelFormats.Default); image1.Source = bitmap; } private DrawingVisual drawVisual = new DrawingVisual(); private DrawingContext drawContext; private Random rand = new Random(); private Dictionary<TouchDevice, TouchData> map = new Dictionary<TouchDevice, TouchData>(); private void image1_TouchDown(object sender, TouchEventArgs e) { var pt = e.GetTouchPoint(image1).Position; TouchData td = new TouchData(); td.lastTouchPoint = pt; // 擬似的に筆圧を設定 td.press = 12; // 線の色をタッチデバイス毎にランダムで決定する td.brush = new SolidColorBrush(Color.FromRgb((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256))); // キーコレクションに現在のタッチ情報を登録 map[e.TouchDevice] = td; image1.CaptureTouch(e.TouchDevice); // 描画処理開始 drawContext = drawVisual.RenderOpen(); drawContext.Close(); bitmap.Render(drawVisual); } private void image1_TouchMove(object sender, TouchEventArgs e) { if (e.TouchDevice.Captured == image1) { var pt = e.GetTouchPoint(image1).Position; var tm = map[e.TouchDevice]; drawContext = drawVisual.RenderOpen(); Pen penDraw = new Pen(tm.brush, tm.press); penDraw.StartLineCap = PenLineCap.Round; penDraw.EndLineCap = PenLineCap.Round; // 直線を描画 drawContext.DrawLine(penDraw, tm.lastTouchPoint, pt); drawContext.Close(); bitmap.Render(drawVisual); tm.lastTouchPoint = pt; } } private void image1_TouchUp(object sender, TouchEventArgs e) { if (e.TouchDevice.Captured == image1) { // タッチを離したデバイスの情報を解放 image1.ReleaseTouchCapture(e.TouchDevice); map.Remove(e.TouchDevice); } } } }
しかし、上記のコードでは以下のような問題が発生しており、解決できず困っております。
1)同時に複数ポイントをタッチして線を引こうとした場合、描画処理が著しく遅くなる
2)描画時に200MB~500MBと大量のメモリを使用しており、描画完了後も200MB程度が開放されずに使用中となっている
これらの解決方法、または他にパフォーマンスの良い描画方法があればご教授願えないでしょうか。
現状、描く線はラスタ形式ですが、ベクタ形式となっても問題ありません。
(補足情報)
■実行環境
ノートPC(10タッチポイントでのWindowsタッチのフルサポート)
OS: Win8.1 64bit
CPU: i7-4500U 1.80GHz
GPU: Intel HD Graphics 4400
MEM: 8GB
※MSペイントにてマルチタッチによる線描画が可能であることを確認しております。
以上よろしくお願いいたします。
回答
-
TouchMoveイベントが大量に発生するのにたいして、毎回bitmap.Render(drawVisual)を行っているために負荷がかかっています。
描画する回数を減らしてやれば負荷も減ります。<Grid> <StackPanel x:Name="stackPanel" HorizontalAlignment="Center" VerticalAlignment="Center"> <Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="{Binding Width, ElementName=image1}" Height="{Binding Height, ElementName=image1}" > <Grid> <Image x:Name="image1" Stretch="Fill" TouchDown="image1_TouchDown" TouchMove="image1_TouchMove" TouchUp="image1_TouchUp" Width="1000" Height="600" /> <!-- 一時描画用のキャンパスをかぶせる --> <Canvas IsHitTestVisible="False" x:Name="tempCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Polyline/> </Canvas> </Grid> </Border> </StackPanel> </Grid>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace WpfApplication1 { public partial class MainWindow : Window { // タッチ情報 public class TouchData { public Point lastTouchPoint; public Brush brush; public double press; public System.Windows.Shapes.Polyline Line { get; private set; } /// <summary>一時描画用のPolylineを用意</summary> /// <param name="cvs">一時描画先のCanvas</param> public void CreatePolyline(Canvas cvs) { if (Line != null) { throw new InvalidOperationException(); } Line = new Polyline(); Line.Stroke = brush; Line.StrokeThickness = press; cvs.Children.Add(Line); } /// <summary> /// 一時的な描画に使ったPolylineを除去 /// </summary> public void RemovePolyline() { if (Line != null) { ((Canvas)VisualTreeHelper.GetParent(Line)).Children.Remove(Line); Line = null; } } /// <summary>Polylineの軌跡でdrawingContextに線を描きなおす</summary> public void DrawLineTo(DrawingContext dc) { if (Line != null && Line.Points.Count > 0) { Pen penDraw = new Pen(brush, press); penDraw.StartLineCap = PenLineCap.Round; penDraw.EndLineCap = PenLineCap.Round; Point p0; p0 = Line.Points.First(); foreach (Point p in Line.Points.Skip(1)) { dc.DrawLine(penDraw, p0, p); p0 = p; } } } } public MainWindow() { InitializeComponent(); bitmap = new RenderTargetBitmap((int)image1.Width, (int)image1.Height, 96, 96, PixelFormats.Default); image1.Source = bitmap; } private RenderTargetBitmap bitmap; private DrawingVisual drawVisual = new DrawingVisual(); private DrawingContext drawContext; private Random rand = new Random(); private Dictionary<TouchDevice, TouchData> map = new Dictionary<TouchDevice, TouchData>(); private void image1_TouchDown(object sender, TouchEventArgs e) { var pt = e.GetTouchPoint(image1).Position; TouchData td = new TouchData(); td.lastTouchPoint = pt; td.press = 12; td.brush = new SolidColorBrush(Color.FromRgb((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256))); map[e.TouchDevice] = td; image1.CaptureTouch(e.TouchDevice); //いきなりDrawingContextに描画をすると重いので、まずはPolylineで描くようにする td.CreatePolyline(this.tempCanvas); td.Line.Points.Add(pt); } private void image1_TouchMove(object sender, TouchEventArgs e) { if (e.TouchDevice.Captured == image1) { var pt = e.GetTouchPoint(image1).Position; //Polylineの描画点を追加して自動で描かせる map[e.TouchDevice].Line.Points.Add(pt); } } private void image1_TouchUp(object sender, TouchEventArgs e) { if (e.TouchDevice.Captured == image1) { // タッチを離したデバイスの情報を解放 image1.ReleaseTouchCapture(e.TouchDevice); var td = map[e.TouchDevice]; map.Remove(e.TouchDevice); //軌跡からビットマップに描画しなおす drawContext = drawVisual.RenderOpen(); td.DrawLineTo(drawContext); drawContext.Close(); bitmap.Render(drawVisual); td.RemovePolyline();//一時描画の線を除去 } } } }
なお、このサンプルでは適当なPolylineなので描画途中にヒゲが生えることがありますw
上記のサンプルではタッチを離した時にそれまでの軌跡でbitmapに描画を行うため書き換え回数は少なくなります。
ただし、別の色の軌跡が交差した場合は、途中に関係なく後に書き終えた色で上書きになります。もう少し負荷がかかってもきれいにしたい場合は、10回のMoveに対して10回分をまとめて描画とかすればいいです。
あと、移動していなくてもイベントが発生し続けるので、座標が移動していなければ描画しないとか。#インフルエンザ出社禁止中orz
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク der_y 2016年2月5日 6:52
すべての返信
-
TouchMoveイベントが大量に発生するのにたいして、毎回bitmap.Render(drawVisual)を行っているために負荷がかかっています。
描画する回数を減らしてやれば負荷も減ります。<Grid> <StackPanel x:Name="stackPanel" HorizontalAlignment="Center" VerticalAlignment="Center"> <Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="{Binding Width, ElementName=image1}" Height="{Binding Height, ElementName=image1}" > <Grid> <Image x:Name="image1" Stretch="Fill" TouchDown="image1_TouchDown" TouchMove="image1_TouchMove" TouchUp="image1_TouchUp" Width="1000" Height="600" /> <!-- 一時描画用のキャンパスをかぶせる --> <Canvas IsHitTestVisible="False" x:Name="tempCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Polyline/> </Canvas> </Grid> </Border> </StackPanel> </Grid>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace WpfApplication1 { public partial class MainWindow : Window { // タッチ情報 public class TouchData { public Point lastTouchPoint; public Brush brush; public double press; public System.Windows.Shapes.Polyline Line { get; private set; } /// <summary>一時描画用のPolylineを用意</summary> /// <param name="cvs">一時描画先のCanvas</param> public void CreatePolyline(Canvas cvs) { if (Line != null) { throw new InvalidOperationException(); } Line = new Polyline(); Line.Stroke = brush; Line.StrokeThickness = press; cvs.Children.Add(Line); } /// <summary> /// 一時的な描画に使ったPolylineを除去 /// </summary> public void RemovePolyline() { if (Line != null) { ((Canvas)VisualTreeHelper.GetParent(Line)).Children.Remove(Line); Line = null; } } /// <summary>Polylineの軌跡でdrawingContextに線を描きなおす</summary> public void DrawLineTo(DrawingContext dc) { if (Line != null && Line.Points.Count > 0) { Pen penDraw = new Pen(brush, press); penDraw.StartLineCap = PenLineCap.Round; penDraw.EndLineCap = PenLineCap.Round; Point p0; p0 = Line.Points.First(); foreach (Point p in Line.Points.Skip(1)) { dc.DrawLine(penDraw, p0, p); p0 = p; } } } } public MainWindow() { InitializeComponent(); bitmap = new RenderTargetBitmap((int)image1.Width, (int)image1.Height, 96, 96, PixelFormats.Default); image1.Source = bitmap; } private RenderTargetBitmap bitmap; private DrawingVisual drawVisual = new DrawingVisual(); private DrawingContext drawContext; private Random rand = new Random(); private Dictionary<TouchDevice, TouchData> map = new Dictionary<TouchDevice, TouchData>(); private void image1_TouchDown(object sender, TouchEventArgs e) { var pt = e.GetTouchPoint(image1).Position; TouchData td = new TouchData(); td.lastTouchPoint = pt; td.press = 12; td.brush = new SolidColorBrush(Color.FromRgb((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256))); map[e.TouchDevice] = td; image1.CaptureTouch(e.TouchDevice); //いきなりDrawingContextに描画をすると重いので、まずはPolylineで描くようにする td.CreatePolyline(this.tempCanvas); td.Line.Points.Add(pt); } private void image1_TouchMove(object sender, TouchEventArgs e) { if (e.TouchDevice.Captured == image1) { var pt = e.GetTouchPoint(image1).Position; //Polylineの描画点を追加して自動で描かせる map[e.TouchDevice].Line.Points.Add(pt); } } private void image1_TouchUp(object sender, TouchEventArgs e) { if (e.TouchDevice.Captured == image1) { // タッチを離したデバイスの情報を解放 image1.ReleaseTouchCapture(e.TouchDevice); var td = map[e.TouchDevice]; map.Remove(e.TouchDevice); //軌跡からビットマップに描画しなおす drawContext = drawVisual.RenderOpen(); td.DrawLineTo(drawContext); drawContext.Close(); bitmap.Render(drawVisual); td.RemovePolyline();//一時描画の線を除去 } } } }
なお、このサンプルでは適当なPolylineなので描画途中にヒゲが生えることがありますw
上記のサンプルではタッチを離した時にそれまでの軌跡でbitmapに描画を行うため書き換え回数は少なくなります。
ただし、別の色の軌跡が交差した場合は、途中に関係なく後に書き終えた色で上書きになります。もう少し負荷がかかってもきれいにしたい場合は、10回のMoveに対して10回分をまとめて描画とかすればいいです。
あと、移動していなくてもイベントが発生し続けるので、座標が移動していなければ描画しないとか。#インフルエンザ出社禁止中orz
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク der_y 2016年2月5日 6:52