From 54f9fe590a5e502847d6150fe9f55ba474631eea Mon Sep 17 00:00:00 2001 From: sup39 Date: Sat, 25 Sep 2021 02:48:44 +0800 Subject: [PATCH] v0.1.0 --- LICENSE | 22 + README.md | 21 + supAutoSplit.sln | 25 + supAutoSplit/Properties/AssemblyInfo.cs | 39 ++ ...upAutoSplit.UI.TemplateSettings.datasource | 10 + supAutoSplit/Properties/Resources.Designer.cs | 63 +++ supAutoSplit/Properties/Resources.resx | 120 +++++ supAutoSplit/SupAutoSplit/Component.cs | 168 +++++++ supAutoSplit/SupAutoSplit/Factory.cs | 27 ++ supAutoSplit/SupAutoSplit/MatchTemplate.cs | 124 +++++ .../SupAutoSplit/UI/Settings.Designer.cs | 145 ++++++ supAutoSplit/SupAutoSplit/UI/Settings.cs | 92 ++++ supAutoSplit/SupAutoSplit/UI/Settings.resx | 120 +++++ .../UI/TemplateSettings.Designer.cs | 439 ++++++++++++++++++ .../SupAutoSplit/UI/TemplateSettings.cs | 182 ++++++++ .../SupAutoSplit/UI/TemplateSettings.resx | 123 +++++ supAutoSplit/SupExtension.cs | 44 ++ supAutoSplit/app.config | 11 + supAutoSplit/packages.config | 13 + supAutoSplit/supAutoSplit.csproj | 133 ++++++ 20 files changed, 1921 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 supAutoSplit.sln create mode 100644 supAutoSplit/Properties/AssemblyInfo.cs create mode 100644 supAutoSplit/Properties/DataSources/LiveSplit.SupAutoSplit.UI.TemplateSettings.datasource create mode 100644 supAutoSplit/Properties/Resources.Designer.cs create mode 100644 supAutoSplit/Properties/Resources.resx create mode 100644 supAutoSplit/SupAutoSplit/Component.cs create mode 100644 supAutoSplit/SupAutoSplit/Factory.cs create mode 100644 supAutoSplit/SupAutoSplit/MatchTemplate.cs create mode 100644 supAutoSplit/SupAutoSplit/UI/Settings.Designer.cs create mode 100644 supAutoSplit/SupAutoSplit/UI/Settings.cs create mode 100644 supAutoSplit/SupAutoSplit/UI/Settings.resx create mode 100644 supAutoSplit/SupAutoSplit/UI/TemplateSettings.Designer.cs create mode 100644 supAutoSplit/SupAutoSplit/UI/TemplateSettings.cs create mode 100644 supAutoSplit/SupAutoSplit/UI/TemplateSettings.resx create mode 100644 supAutoSplit/SupExtension.cs create mode 100644 supAutoSplit/app.config create mode 100644 supAutoSplit/packages.config create mode 100644 supAutoSplit/supAutoSplit.csproj diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3000b11 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2021 sup39[サポミク] + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2dc9cf9 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# supAutoSplit +An auto splitter for LiveSplit using OpenCV and template matching. + +## Installation +### Step 1. Install supAutoSplit +Download `LiveSplit.supAutoSplit.dll` from the [release page](https://github.com/sup39/supAutoSplit/releases), and put it into `(LiveSplit)\Components` +### Step 2. Install [opencvsharp](https://github.com/shimat/opencvsharp) +Download `OpenCvSharp-*.zip` (e.g. `OpenCvSharp-4.5.3-20210821.zip`) from the [release page](https://github.com/shimat/opencvsharp/releases), extract and copy: +- `(Zip)\ManagedLib\net461\OpenCvSharp.dll` to `(LiveSplit)\Components\` +- `(Zip)\NativeLib\win` (whole directory) to `(LiveSplit)\Components\`, and rename the directory to `dll` +### After the installation +There should be the following files in the `(LiveSplit)\Components` directory: +- `LiveSplit.supAutoSplit.dll` +- `OpenCvSharp.dll` +- `dll\x86\OpenCvSharpExtern.dll` +- `dll\x64\OpenCvSharpExtern.dll` + +## Usage +- Right click on LiveSplit, and select `Edit Layout...` +- In Layout Editor, click the (+) button, in `Control` click `supAutoSplit` +- After adding `supAutoSplit` to the list, click the `Layout Settings` button, navigate to the `supAutoSplit` tab, add template and set the parameters diff --git a/supAutoSplit.sln b/supAutoSplit.sln new file mode 100644 index 0000000..b4814b2 --- /dev/null +++ b/supAutoSplit.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1622 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "supAutoSplit", "supAutoSplit\supAutoSplit.csproj", "{C1B6A79E-088A-411C-B546-4D72447B4DAA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C1B6A79E-088A-411C-B546-4D72447B4DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1B6A79E-088A-411C-B546-4D72447B4DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1B6A79E-088A-411C-B546-4D72447B4DAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1B6A79E-088A-411C-B546-4D72447B4DAA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7FDD6D01-2B0C-45F7-A245-9FEE2E65724F} + EndGlobalSection +EndGlobal diff --git a/supAutoSplit/Properties/AssemblyInfo.cs b/supAutoSplit/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f1d53c9 --- /dev/null +++ b/supAutoSplit/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +using LiveSplit.UI.Components; +using LiveSplit.SupAutoSplit; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: ComponentFactory(typeof(Factory))] + +// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 +// アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("supAutoSplit")] +[assembly: AssemblyDescription("An auto splitter for LiveSplit using OpenCV and template matching")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("supAutoSplit")] +[assembly: AssemblyCopyright("Copyright © 2021 sup39[サポミク]")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("c1b6a79e-088a-411c-b546-4d72447b4daa")] + +// アセンブリのバージョン情報は次の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// Revision +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 以下のように '*' を使用します: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.1.0.0")] +// [assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/supAutoSplit/Properties/DataSources/LiveSplit.SupAutoSplit.UI.TemplateSettings.datasource b/supAutoSplit/Properties/DataSources/LiveSplit.SupAutoSplit.UI.TemplateSettings.datasource new file mode 100644 index 0000000..fe5317d --- /dev/null +++ b/supAutoSplit/Properties/DataSources/LiveSplit.SupAutoSplit.UI.TemplateSettings.datasource @@ -0,0 +1,10 @@ + + + + LiveSplit.SupAutoSplit.UI.TemplateSettings, Livesplit.SupAutoSplit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + \ No newline at end of file diff --git a/supAutoSplit/Properties/Resources.Designer.cs b/supAutoSplit/Properties/Resources.Designer.cs new file mode 100644 index 0000000..b974922 --- /dev/null +++ b/supAutoSplit/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:4.0.30319.42000 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします。 +// +//------------------------------------------------------------------------------ + +namespace LiveSplit.Properties { + using System; + + + /// + /// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。 + /// + // このクラスは StronglyTypedResourceBuilder クラスが ResGen + // または Visual Studio のようなツールを使用して自動生成されました。 + // メンバーを追加または削除するには、.ResX ファイルを編集して、/str オプションと共に + // ResGen を実行し直すか、または VS プロジェクトをビルドし直します。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// このクラスで使用されているキャッシュされた ResourceManager インスタンスを返します。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LiveSplit.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// すべてについて、現在のスレッドの CurrentUICulture プロパティをオーバーライドします + /// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/supAutoSplit/Properties/Resources.resx b/supAutoSplit/Properties/Resources.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/supAutoSplit/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/supAutoSplit/SupAutoSplit/Component.cs b/supAutoSplit/SupAutoSplit/Component.cs new file mode 100644 index 0000000..93e7cd6 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/Component.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Windows.Forms; +using System.Xml; +using LiveSplit.Model; +using LiveSplit.UI; +using LiveSplit.UI.Components; +using OpenCvSharp; + +namespace LiveSplit.SupAutoSplit { + public sealed class Component : LogicComponent { + public override string ComponentName => "supAutoSplit"; + private UI.Settings Settings { get; set; } + private LiveSplitState State { get; set; } + private TimerModel Model { get; } + + public Component(LiveSplitState state) { + Settings = new UI.Settings(); + State = state; + Model = new TimerModel { + CurrentState = state + }; + + ContextMenuControls = new Dictionary { + { "Start supAutoSplit", Start } + }; + } + + private Thread captureThread; + private List handlersAll; + private List handlers; + private volatile bool handlerReady = false; + private void UpdateHandlers(object sender, EventArgs e) { + handlers = handlersAll.Where(h => h.Reset(State)).ToList(); + handlerReady = true; + } + private void UpdateHandlers_Reset(object sender, TimerPhase e) => UpdateHandlers(null, null); + private void Start() { + ContextMenuControls.Clear(); + ContextMenuControls.Add("Reload supAutoSplit", Reload); + ContextMenuControls.Add("Stop supAutoSplit", Stop); + + Reload(); + State.OnStart += UpdateHandlers; + State.OnSplit += UpdateHandlers; + State.OnSkipSplit += UpdateHandlers; + State.OnUndoSplit += UpdateHandlers; + State.OnPause += UpdateHandlers; + State.OnResume += UpdateHandlers; + State.OnReset += UpdateHandlers_Reset; + + captureThread = new Thread(() => { + using (var capture = new VideoCapture(Settings.CaptureDevice)) + using (var window = new Window(Settings.WindowName)) + using (Mat frame = new Mat()) { + try { + // Stopwatch sw = new Stopwatch(); + // Stopwatch sw1 = new Stopwatch(); + // Stopwatch sw2 = new Stopwatch(); + // sw.Start(); + while (capture.Read(frame)) { + // sw1.Restart(); + if (handlerReady) { + foreach (var handler in handlers) { + if (handler.Match(frame)) { + handlerReady = false; + handler.Action(Model); + break; + } + } + } + // sw1.Stop(); + // sw2.Restart(); + window.ShowImage(frame); + // sw2.Stop(); + // sw.Stop(); + // Debug.WriteLine($"{sw1.ElapsedMilliseconds:#0} ({sw1.ElapsedTicks}) {sw2.ElapsedMilliseconds:#0} ({sw2.ElapsedTicks}) {sw.ElapsedMilliseconds:#0}"); + Cv2.WaitKey(1); + // sw.Restart(); + } + } catch (ThreadInterruptedException) {} + } + }); + captureThread.Start(); + } + private void Reload() { + handlersAll = Settings.TemplateSettings.Select(o => new MatchHandler(o)).ToList(); + UpdateHandlers(null, null); + } + private void Stop() { + captureThread?.Abort(); + captureThread = null; + + State.OnStart -= UpdateHandlers; + State.OnSplit -= UpdateHandlers; + State.OnSkipSplit -= UpdateHandlers; + State.OnUndoSplit -= UpdateHandlers; + State.OnPause -= UpdateHandlers; + State.OnResume -= UpdateHandlers; + State.OnReset -= UpdateHandlers_Reset; + + ContextMenuControls.Clear(); + ContextMenuControls.Add("Start supAutoSplit", Start); + } + + public override void Dispose() { + captureThread?.Abort(); + } + + public override XmlNode GetSettings(XmlDocument document) => Settings.GetSettings(document); + public override Control GetSettingsControl(LayoutMode mode) => Settings; + public int GetSettingsHashCode() => Settings.GetSettingsHashCode(); + public override void SetSettings(XmlNode settings) => Settings.SetSettings(settings); + public override void Update(IInvalidator invalidator, LiveSplitState state, float width, float height, LayoutMode mode) {} + } +} + +namespace LiveSplit.SupAutoSplit { + class MatchHandler { + private readonly Predicate fEnabled; + private readonly Rect imageRange; + private readonly Func fSim; + private readonly Predicate fMatch; + private readonly Predicate fMatchNeg; + public Action Action { get; } + private bool ready = false; + + public MatchHandler(UI.TemplateSettings settings) { + fEnabled = settings.EnableIf; + using (var timg = Cv2.ImRead(settings.ImagePath, ImreadModes.Unchanged)) { + imageRange = new Rect(settings.ImageOffset, timg.Size()); + fSim = settings.MatchMethodFactory(timg); + } + var threshold = settings.MatchThreshold; + var thresholdNeg = settings.MatchThresholdNeg; + if (thresholdNeg <= 0) thresholdNeg = threshold; + if (settings.IsActionOnPosedge) { + fMatch = sim => sim <= threshold; + fMatchNeg = sim => sim >= thresholdNeg; + } else { + fMatch = sim => sim >= threshold; + fMatchNeg = sim => sim <= thresholdNeg; + } + Action = settings.MatchAction; + } + + public bool Reset(LiveSplitState state) { + ready = false; + return fEnabled(state); + } + public bool Match(Mat frame) { + // Stopwatch sw = new Stopwatch(); + var fimg = frame[imageRange]; + var sim = fSim(fimg); + // sw.Stop(); + // Debug.WriteLine($"#sim {sim} | {sw.ElapsedMilliseconds} ({sw.ElapsedTicks})"); + if (ready) { + return fMatch(sim); + } else if (fMatchNeg(sim)) { + ready = true; + } + return false; + } + } +} diff --git a/supAutoSplit/SupAutoSplit/Factory.cs b/supAutoSplit/SupAutoSplit/Factory.cs new file mode 100644 index 0000000..a95d230 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/Factory.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using LiveSplit.Model; +using LiveSplit.UI.Components; + +namespace LiveSplit.SupAutoSplit { + public class Factory : IComponentFactory { + public string ComponentName => "supAutoSplit"; + + public string Description => "An auto splitter using template matching"; + + public ComponentCategory Category => ComponentCategory.Control; + + public string UpdateName => ComponentName; + + public string XMLURL => "https://301.sup39.ml/LiveSplit/supAutoSplit.xml"; // TODO + + public string UpdateURL => "https://301.sup39.ml/LiveSplit/supAutoSplit/update"; // TODO + + public Version Version => Version.Parse("0.1.0"); + + public IComponent Create(LiveSplitState state) => new Component(state); + } +} diff --git a/supAutoSplit/SupAutoSplit/MatchTemplate.cs b/supAutoSplit/SupAutoSplit/MatchTemplate.cs new file mode 100644 index 0000000..7d80ebb --- /dev/null +++ b/supAutoSplit/SupAutoSplit/MatchTemplate.cs @@ -0,0 +1,124 @@ +using LiveSplit.Model; +using OpenCvSharp; +using SupExtension; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace LiveSplit.SupAutoSplit { + using MatchAction = Action; + using EnableIfFactory = Func>; + using MatchMethodFactory = Func>; + + class MatchTemplate { + static public readonly ListItem[] EnableIfFactories = { + ("Index", "Split Index in range", arg => { + var testers = arg.Split(',').Select(s => { + string[] ns = arg.Split(':'); + if (ns.Length == 0 || ns.Length > 3) return null; + int start = 0, end = int.MaxValue, step = 1; + if (ns[0] != "" && !int.TryParse(ns[0], out start)) return null; + if (ns.Length == 1) return start < 0 ? + (Func)((idx, len) => idx == len + start) : + ((idx, len) => idx==start); + if (ns[1] != "" && !int.TryParse(ns[1], out end)) return null; + if (ns.Length == 3 && ns[2] != "" && (!int.TryParse(ns[2], out step) || step==0)) return null; + return (idx, len) => { + int start1 = start<0 ? start+len : start; + int end1 = end<0 ? end+len : end; + return start1 <= idx && idx < end1 && (idx-start1)%step == 0; + }; + }).Where(s => s!=null).ToList(); + return state => testers.Any(f => f(state.CurrentSplitIndex, state.Run.Count)); + }), + ("Name", "Split Name matches", arg => { + try { + var re = new Regex(arg); + return state => state.CurrentSplit == null ? false : re.IsMatch(state.CurrentSplit.Name); + } catch { + // not regex + return state => state.CurrentSplit == null ? false : state.CurrentSplit.Name == arg; + } + }), + }; + static public readonly string[] EnableIfItems = EnableIfFactories.Select(e => e.text).ToArray(); + static public readonly ListItem[] MatchActions = { + ("Start", "Start", model => model.Start()), + ("Split", "Split", model => model.Split()), + ("Skip", "Skip Split", model => model.SkipSplit()), + ("Undo", "Undo Split", model => model.UndoSplit()), + ("Reset", "Reset", model => model.Reset()), + ("Pause", "Pause", model => model.Pause()), + }; + static public readonly string[] MatchActionItems = MatchActions.Select(e => e.text).ToArray(); + + static public readonly ListItem[] ActionTimings = { + ("Posedge", "Posedge: First Match after unmatch", true), + ("Negedge", "Negedge: First Unmatch after match", false), + }; + static public readonly string[] ActionTimingItems = ActionTimings.Select(e => e.text).ToArray(); + + static public readonly ListItem[] MatchMethodFactories = { + ("COSINE", "1 - Cosine Similarity", timg => { + Mat[] bchs = timg.Split(); + Mat mask = bchs[3]; + Array.Resize(ref bchs, 3); + Mat[] bimg = bchs.Select(m => (Mat)m.BitwiseAnd(mask)).ToArray(); + var b1 = Math.Sqrt(bimg.Select(m => m.Norm(NormTypes.L2SQR, mask)).Sum()); + return frame => { + Mat[] fimg = frame.Split(); + var f1 = Math.Sqrt(fimg.Select(m => m.Norm(NormTypes.L2SQR, mask)).Sum()); + var fb = fimg.Zip(bimg, (mf, mb) => mf.Dot(mb)).Sum(); + fimg.ForEach(m => m.Release()); + return 1-fb/f1/b1; + }; + }), + ("SQDIFF_NORM", "Normalized Squared Difference", timg => { + Mat[] bimg = timg.Split(); + Mat mask = bimg[3]; + Array.Resize(ref bimg, 3); + var b1 = Math.Sqrt(bimg.Select(m => m.Norm(NormTypes.L2SQR, mask)).Sum()); + return frame => { + Mat[] fimg = frame.Split(); + var f1 = Math.Sqrt(fimg.Select(m => m.Norm(NormTypes.L2SQR, mask)).Sum()); + var d2 = fimg.Zip(bimg, (mf, mb) => Cv2.Norm(mf, mb, NormTypes.L2SQR, mask)).Sum(); + fimg.ForEach(m => m.Release()); + return d2/f1/b1; + }; + }), + ("V128_BINARY", "V128 Binary Classification: MAX(R,G,B)<128 or not", timg => { + Mat bimg; + { + Mat[] chs = timg.Split(); + if (chs.Length == 3) { + using (Mat v = RGB2V(chs)) + bimg = v.LessThan(128); + } else { + bimg = chs[0].LessThan(128); + } + chs.ForEach(m => m.Release()); + } + return frame => { + Mat[] chs = frame.Split(); + using (Mat v = RGB2V(chs)) { + chs.ForEach(ch => ch.Release()); + using (Mat fimg = v.LessThan(128)) + return Cv2.Mean(fimg.NotEquals(bimg)).ToDouble()/255.0; + } + }; + }), + }; + static public readonly string[] MatchMethodItems = MatchMethodFactories.Select(e => e.text).ToArray(); + + static public Mat RGB2V(Mat[] chs) { + Mat m = new Mat(); + Cv2.Max(chs[0], chs[1], m); + Cv2.Max(m, chs[2], m); + return m; + } + } +} diff --git a/supAutoSplit/SupAutoSplit/UI/Settings.Designer.cs b/supAutoSplit/SupAutoSplit/UI/Settings.Designer.cs new file mode 100644 index 0000000..4ffb658 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/UI/Settings.Designer.cs @@ -0,0 +1,145 @@ +namespace LiveSplit.SupAutoSplit.UI { + partial class Settings { + /// + /// 必要なデザイナー変数です。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 使用中のリソースをすべてクリーンアップします。 + /// + /// マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。 + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region コンポーネント デザイナーで生成されたコード + + /// + /// デザイナー サポートに必要なメソッドです。このメソッドの内容を + /// コード エディターで変更しないでください。 + /// + private void InitializeComponent() { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.label3 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.tlpTemplates = new System.Windows.Forms.TableLayoutPanel(); + this.btnAddTemplate = new System.Windows.Forms.Button(); + this.tableLayoutPanel1.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.tlpTemplates.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.AutoSize = true; + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.label3, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.groupBox1, 0, 1); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(450, 79); + this.tableLayoutPanel1.TabIndex = 0; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(54, 3); + this.label3.Margin = new System.Windows.Forms.Padding(3); + this.label3.Name = "label3"; + this.label3.Padding = new System.Windows.Forms.Padding(3); + this.label3.Size = new System.Drawing.Size(65, 18); + this.label3.TabIndex = 2; + this.label3.Text = "SMS Any%"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 3); + this.label1.Margin = new System.Windows.Forms.Padding(3); + this.label1.Name = "label1"; + this.label1.Padding = new System.Windows.Forms.Padding(3); + this.label1.Size = new System.Drawing.Size(45, 18); + this.label1.TabIndex = 0; + this.label1.Text = "Profile:"; + // + // groupBox1 + // + this.groupBox1.AutoSize = true; + this.tableLayoutPanel1.SetColumnSpan(this.groupBox1, 2); + this.groupBox1.Controls.Add(this.tlpTemplates); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox1.Location = new System.Drawing.Point(3, 27); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(444, 49); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Templates"; + // + // tlpTemplates + // + this.tlpTemplates.AutoSize = true; + this.tlpTemplates.ColumnCount = 1; + this.tlpTemplates.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpTemplates.Controls.Add(this.btnAddTemplate, 0, 0); + this.tlpTemplates.Dock = System.Windows.Forms.DockStyle.Fill; + this.tlpTemplates.Location = new System.Drawing.Point(3, 18); + this.tlpTemplates.Name = "tlpTemplates"; + this.tlpTemplates.RowCount = 1; + this.tlpTemplates.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpTemplates.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpTemplates.Size = new System.Drawing.Size(438, 28); + this.tlpTemplates.TabIndex = 0; + // + // btnAddTemplate + // + this.btnAddTemplate.AutoSize = true; + this.btnAddTemplate.Location = new System.Drawing.Point(3, 3); + this.btnAddTemplate.Name = "btnAddTemplate"; + this.btnAddTemplate.Size = new System.Drawing.Size(81, 22); + this.btnAddTemplate.TabIndex = 0; + this.btnAddTemplate.Text = "Add Template"; + this.btnAddTemplate.UseVisualStyleBackColor = true; + this.btnAddTemplate.Click += new System.EventHandler(this.BtnAddTemplate_Click); + // + // Settings + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoSize = true; + this.Controls.Add(this.tableLayoutPanel1); + this.Name = "Settings"; + this.Padding = new System.Windows.Forms.Padding(3); + this.Size = new System.Drawing.Size(456, 85); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.tlpTemplates.ResumeLayout(false); + this.tlpTemplates.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TableLayoutPanel tlpTemplates; + private System.Windows.Forms.Button btnAddTemplate; + } +} diff --git a/supAutoSplit/SupAutoSplit/UI/Settings.cs b/supAutoSplit/SupAutoSplit/UI/Settings.cs new file mode 100644 index 0000000..4c5df40 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/UI/Settings.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml; +using LiveSplit.UI.Components; +using LiveSplit.UI; +using SupExtension; + +namespace LiveSplit.SupAutoSplit.UI { + public partial class Settings : UserControl { + public int CaptureDevice = 0; // TODO + public string WindowName = "supAutoSplit"; // TODO + public List TemplateSettings { get; set; } = new List(); + + public Settings() { + InitializeComponent(); + } + public XmlNode GetSettings(XmlDocument document) { + var parent = document.CreateElement("Settings"); + CreateSettingsNode(document, parent); + return parent; + } + public int GetSettingsHashCode() { + return CreateSettingsNode(null, null); + } + public int CreateSettingsNode(XmlDocument document, XmlElement parent) { + var hashCode = + SettingsHelper.CreateSetting(document, parent, "Version", "1.0.0"); + // profile TODO + var profileRoot = document?.CreateElement("Profile", parent); + SettingsHelper.CreateSetting(document, profileRoot, "Name", "SMS Any%"); + // templates + var templatesRoot = document?.CreateElement("Templates", profileRoot); + foreach (var ts in TemplateSettings) { + XmlElement templateParent = document?.CreateElement("Template", templatesRoot); + hashCode ^= ts.CreateSettingsNode(document, templateParent); + } + // return + return hashCode; + } + + public void SetSettings(XmlNode settings) { + if (settings == null) return; + // profile + var profileRoot = settings["Profile"]; + if (profileRoot == null) return; + // template + var templatesRoot = profileRoot["Templates"]; + if (templatesRoot == null) return; + + // reset RowStyles + var rs = tlpTemplates.RowStyles[0]; + tlpTemplates.RowStyles.Clear(); + tlpTemplates.RowStyles.Add(rs); + // remove UI + foreach (var ts in TemplateSettings) { + tlpTemplates.Controls.Remove(ts); + } + tlpTemplates.RowCount = 1; + // reinit TemplateSettings[] + TemplateSettings.Clear(); + foreach (var templateParent in templatesRoot.ChildNodes) { + AddTemplateSettings((XmlElement)templateParent); + } + } + + private void BtnAddTemplate_Click(object sender, EventArgs e) { + AddTemplateSettings(null); + } + private void AddTemplateSettings(XmlElement settings) { + var irow = tlpTemplates.RowCount; + tlpTemplates.RowCount = irow + 1; + tlpTemplates.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + var v = new TemplateSettings(settings, o => { + tlpTemplates.Controls.Remove(o); + tlpTemplates.RowCount = irow; + tlpTemplates.RowStyles.RemoveAt(irow); + TemplateSettings.Remove(o); + }); + // tlpTemplates.RowStyles.Add(new RowStyle(SizeType.Absolute, v.Height)); + // v.Width = tlpTemplates.Width; + tlpTemplates.Controls.Add(v, 0, irow); + TemplateSettings.Add(v); + } + } +} diff --git a/supAutoSplit/SupAutoSplit/UI/Settings.resx b/supAutoSplit/SupAutoSplit/UI/Settings.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/UI/Settings.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/supAutoSplit/SupAutoSplit/UI/TemplateSettings.Designer.cs b/supAutoSplit/SupAutoSplit/UI/TemplateSettings.Designer.cs new file mode 100644 index 0000000..d3f6c31 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/UI/TemplateSettings.Designer.cs @@ -0,0 +1,439 @@ +namespace LiveSplit.SupAutoSplit.UI { + partial class TemplateSettings { + /// + /// 必要なデザイナー変数です。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 使用中のリソースをすべてクリーンアップします。 + /// + /// マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。 + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region コンポーネント デザイナーで生成されたコード + + /// + /// デザイナー サポートに必要なメソッドです。このメソッドの内容を + /// コード エディターで変更しないでください。 + /// + private void InitializeComponent() { + this.ofdTemplate = new System.Windows.Forms.OpenFileDialog(); + this.gpbRoot = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.ttbThresholdNeg = new System.Windows.Forms.TextBox(); + this.cbbEnableIf = new System.Windows.Forms.ComboBox(); + this.cbbActionTiming = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.ttbThreshold = new System.Windows.Forms.TextBox(); + this.label10 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.cbbAction = new System.Windows.Forms.ComboBox(); + this.ttbName = new System.Windows.Forms.TextBox(); + this.label9 = new System.Windows.Forms.Label(); + this.cbbMethod = new System.Windows.Forms.ComboBox(); + this.flowLayoutPanel3 = new System.Windows.Forms.FlowLayoutPanel(); + this.label12 = new System.Windows.Forms.Label(); + this.ttbOffsetX = new System.Windows.Forms.TextBox(); + this.label11 = new System.Windows.Forms.Label(); + this.ttbOffsetY = new System.Windows.Forms.TextBox(); + this.label13 = new System.Windows.Forms.Label(); + this.btnImage = new System.Windows.Forms.Button(); + this.ttbEnableIfArg = new System.Windows.Forms.TextBox(); + this.btnRemove = new System.Windows.Forms.Button(); + this.label3 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.gpbRoot.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + this.flowLayoutPanel3.SuspendLayout(); + this.SuspendLayout(); + // + // ofdTemplate + // + this.ofdTemplate.Filter = "Image Files|*.bmp;*.pbm;*.pgm;*.ppm;*.sr;*.ras;*.jpeg;*.jpg;*.jpe;*.jp2;*.tiff;*." + + "tif;*.png"; + // + // gpbRoot + // + this.gpbRoot.AutoSize = true; + this.gpbRoot.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.gpbRoot.Controls.Add(this.tableLayoutPanel3); + this.gpbRoot.DataBindings.Add(new System.Windows.Forms.Binding("Text", this, "TemplateTitle", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged)); + this.gpbRoot.Dock = System.Windows.Forms.DockStyle.Fill; + this.gpbRoot.Location = new System.Drawing.Point(0, 0); + this.gpbRoot.Name = "gpbRoot"; + this.gpbRoot.Size = new System.Drawing.Size(434, 314); + this.gpbRoot.TabIndex = 16; + this.gpbRoot.TabStop = false; + this.gpbRoot.Text = "Template: "; + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.ColumnCount = 3; + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 126F)); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel3.Controls.Add(this.ttbThresholdNeg, 1, 8); + this.tableLayoutPanel3.Controls.Add(this.cbbEnableIf, 1, 1); + this.tableLayoutPanel3.Controls.Add(this.cbbActionTiming, 1, 3); + this.tableLayoutPanel3.Controls.Add(this.label1, 0, 2); + this.tableLayoutPanel3.Controls.Add(this.ttbThreshold, 1, 7); + this.tableLayoutPanel3.Controls.Add(this.label10, 0, 7); + this.tableLayoutPanel3.Controls.Add(this.label7, 0, 0); + this.tableLayoutPanel3.Controls.Add(this.label5, 0, 5); + this.tableLayoutPanel3.Controls.Add(this.label4, 0, 4); + this.tableLayoutPanel3.Controls.Add(this.label2, 0, 1); + this.tableLayoutPanel3.Controls.Add(this.cbbAction, 1, 2); + this.tableLayoutPanel3.Controls.Add(this.ttbName, 1, 0); + this.tableLayoutPanel3.Controls.Add(this.label9, 0, 6); + this.tableLayoutPanel3.Controls.Add(this.cbbMethod, 1, 6); + this.tableLayoutPanel3.Controls.Add(this.flowLayoutPanel3, 1, 5); + this.tableLayoutPanel3.Controls.Add(this.btnImage, 1, 4); + this.tableLayoutPanel3.Controls.Add(this.ttbEnableIfArg, 2, 1); + this.tableLayoutPanel3.Controls.Add(this.btnRemove, 2, 9); + this.tableLayoutPanel3.Controls.Add(this.label3, 0, 3); + this.tableLayoutPanel3.Controls.Add(this.label6, 0, 8); + this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel3.Location = new System.Drawing.Point(3, 18); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 10; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.Size = new System.Drawing.Size(428, 293); + this.tableLayoutPanel3.TabIndex = 2; + // + // ttbThresholdNeg + // + this.tableLayoutPanel3.SetColumnSpan(this.ttbThresholdNeg, 2); + this.ttbThresholdNeg.DataBindings.Add(new System.Windows.Forms.Binding("Text", this, "MatchThresholdNegStr", true)); + this.ttbThresholdNeg.Dock = System.Windows.Forms.DockStyle.Fill; + this.ttbThresholdNeg.Location = new System.Drawing.Point(97, 239); + this.ttbThresholdNeg.Name = "ttbThresholdNeg"; + this.ttbThresholdNeg.Size = new System.Drawing.Size(328, 22); + this.ttbThresholdNeg.TabIndex = 28; + // + // cbbEnableIf + // + this.cbbEnableIf.Dock = System.Windows.Forms.DockStyle.Fill; + this.cbbEnableIf.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbbEnableIf.FormattingEnabled = true; + this.cbbEnableIf.Location = new System.Drawing.Point(97, 31); + this.cbbEnableIf.Name = "cbbEnableIf"; + this.cbbEnableIf.Size = new System.Drawing.Size(120, 20); + this.cbbEnableIf.TabIndex = 26; + // + // cbbActionTiming + // + this.tableLayoutPanel3.SetColumnSpan(this.cbbActionTiming, 2); + this.cbbActionTiming.Dock = System.Windows.Forms.DockStyle.Fill; + this.cbbActionTiming.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbbActionTiming.FormattingEnabled = true; + this.cbbActionTiming.Location = new System.Drawing.Point(97, 85); + this.cbbActionTiming.Name = "cbbActionTiming"; + this.cbbActionTiming.Size = new System.Drawing.Size(328, 20); + this.cbbActionTiming.TabIndex = 25; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 59); + this.label1.Margin = new System.Windows.Forms.Padding(3); + this.label1.Name = "label1"; + this.label1.Padding = new System.Windows.Forms.Padding(3); + this.label1.Size = new System.Drawing.Size(45, 18); + this.label1.TabIndex = 22; + this.label1.Text = "Action:"; + // + // ttbThreshold + // + this.tableLayoutPanel3.SetColumnSpan(this.ttbThreshold, 2); + this.ttbThreshold.DataBindings.Add(new System.Windows.Forms.Binding("Text", this, "MatchThresholdStr", true)); + this.ttbThreshold.Dock = System.Windows.Forms.DockStyle.Fill; + this.ttbThreshold.Location = new System.Drawing.Point(97, 211); + this.ttbThreshold.Name = "ttbThreshold"; + this.ttbThreshold.Size = new System.Drawing.Size(328, 22); + this.ttbThreshold.TabIndex = 8; + // + // label10 + // + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(3, 211); + this.label10.Margin = new System.Windows.Forms.Padding(3); + this.label10.Name = "label10"; + this.label10.Padding = new System.Windows.Forms.Padding(3); + this.label10.Size = new System.Drawing.Size(87, 18); + this.label10.TabIndex = 17; + this.label10.Text = "Max Difference:"; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(3, 3); + this.label7.Margin = new System.Windows.Forms.Padding(3); + this.label7.Name = "label7"; + this.label7.Padding = new System.Windows.Forms.Padding(3); + this.label7.Size = new System.Drawing.Size(41, 18); + this.label7.TabIndex = 14; + this.label7.Text = "Name:"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(3, 157); + this.label5.Margin = new System.Windows.Forms.Padding(3); + this.label5.Name = "label5"; + this.label5.Padding = new System.Windows.Forms.Padding(3); + this.label5.Size = new System.Drawing.Size(88, 18); + this.label5.TabIndex = 6; + this.label5.Text = "Template Offset:"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(3, 111); + this.label4.Margin = new System.Windows.Forms.Padding(3); + this.label4.Name = "label4"; + this.label4.Padding = new System.Windows.Forms.Padding(3); + this.label4.Size = new System.Drawing.Size(57, 18); + this.label4.TabIndex = 5; + this.label4.Text = "Template:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 31); + this.label2.Margin = new System.Windows.Forms.Padding(3); + this.label2.Name = "label2"; + this.label2.Padding = new System.Windows.Forms.Padding(3); + this.label2.Size = new System.Drawing.Size(57, 18); + this.label2.TabIndex = 4; + this.label2.Text = "Enable If:"; + // + // cbbAction + // + this.tableLayoutPanel3.SetColumnSpan(this.cbbAction, 2); + this.cbbAction.Dock = System.Windows.Forms.DockStyle.Fill; + this.cbbAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbbAction.FormattingEnabled = true; + this.cbbAction.Location = new System.Drawing.Point(97, 59); + this.cbbAction.Name = "cbbAction"; + this.cbbAction.Size = new System.Drawing.Size(328, 20); + this.cbbAction.TabIndex = 3; + // + // ttbName + // + this.tableLayoutPanel3.SetColumnSpan(this.ttbName, 2); + this.ttbName.DataBindings.Add(new System.Windows.Forms.Binding("Text", this, "TemplateName", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged)); + this.ttbName.Dock = System.Windows.Forms.DockStyle.Fill; + this.ttbName.Location = new System.Drawing.Point(97, 3); + this.ttbName.Name = "ttbName"; + this.ttbName.Size = new System.Drawing.Size(328, 22); + this.ttbName.TabIndex = 1; + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(3, 185); + this.label9.Margin = new System.Windows.Forms.Padding(3); + this.label9.Name = "label9"; + this.label9.Padding = new System.Windows.Forms.Padding(3); + this.label9.Size = new System.Drawing.Size(82, 18); + this.label9.TabIndex = 16; + this.label9.Text = "Match Method:"; + // + // cbbMethod + // + this.tableLayoutPanel3.SetColumnSpan(this.cbbMethod, 2); + this.cbbMethod.Dock = System.Windows.Forms.DockStyle.Fill; + this.cbbMethod.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbbMethod.FormattingEnabled = true; + this.cbbMethod.Location = new System.Drawing.Point(97, 185); + this.cbbMethod.Name = "cbbMethod"; + this.cbbMethod.Size = new System.Drawing.Size(328, 20); + this.cbbMethod.TabIndex = 7; + // + // flowLayoutPanel3 + // + this.flowLayoutPanel3.AutoSize = true; + this.tableLayoutPanel3.SetColumnSpan(this.flowLayoutPanel3, 2); + this.flowLayoutPanel3.Controls.Add(this.label12); + this.flowLayoutPanel3.Controls.Add(this.ttbOffsetX); + this.flowLayoutPanel3.Controls.Add(this.label11); + this.flowLayoutPanel3.Controls.Add(this.ttbOffsetY); + this.flowLayoutPanel3.Controls.Add(this.label13); + this.flowLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel3.Location = new System.Drawing.Point(94, 154); + this.flowLayoutPanel3.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel3.Name = "flowLayoutPanel3"; + this.flowLayoutPanel3.Size = new System.Drawing.Size(334, 28); + this.flowLayoutPanel3.TabIndex = 18; + // + // label12 + // + this.label12.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label12.AutoSize = true; + this.label12.Location = new System.Drawing.Point(3, 8); + this.label12.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(9, 12); + this.label12.TabIndex = 19; + this.label12.Text = "("; + // + // ttbOffsetX + // + this.ttbOffsetX.DataBindings.Add(new System.Windows.Forms.Binding("Text", this, "ImageOffsetXStr", true)); + this.ttbOffsetX.Location = new System.Drawing.Point(15, 3); + this.ttbOffsetX.Name = "ttbOffsetX"; + this.ttbOffsetX.Size = new System.Drawing.Size(48, 22); + this.ttbOffsetX.TabIndex = 5; + // + // label11 + // + this.label11.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(66, 13); + this.label11.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(11, 12); + this.label11.TabIndex = 17; + this.label11.Text = ", "; + // + // ttbOffsetY + // + this.ttbOffsetY.DataBindings.Add(new System.Windows.Forms.Binding("Text", this, "ImageOffsetYStr", true)); + this.ttbOffsetY.Location = new System.Drawing.Point(80, 3); + this.ttbOffsetY.Name = "ttbOffsetY"; + this.ttbOffsetY.Size = new System.Drawing.Size(48, 22); + this.ttbOffsetY.TabIndex = 6; + // + // label13 + // + this.label13.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label13.AutoSize = true; + this.label13.Location = new System.Drawing.Point(131, 8); + this.label13.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); + this.label13.Name = "label13"; + this.label13.Size = new System.Drawing.Size(9, 12); + this.label13.TabIndex = 20; + this.label13.Text = ")"; + // + // btnImage + // + this.btnImage.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; + this.tableLayoutPanel3.SetColumnSpan(this.btnImage, 2); + this.btnImage.Location = new System.Drawing.Point(97, 111); + this.btnImage.Name = "btnImage"; + this.btnImage.Size = new System.Drawing.Size(40, 40); + this.btnImage.TabIndex = 4; + this.btnImage.UseVisualStyleBackColor = true; + this.btnImage.Click += new System.EventHandler(this.BtnTemplate_Click); + // + // ttbEnableIfArg + // + this.ttbEnableIfArg.DataBindings.Add(new System.Windows.Forms.Binding("Text", this, "EnableIfArg", true)); + this.ttbEnableIfArg.Dock = System.Windows.Forms.DockStyle.Fill; + this.ttbEnableIfArg.Location = new System.Drawing.Point(223, 31); + this.ttbEnableIfArg.Name = "ttbEnableIfArg"; + this.ttbEnableIfArg.Size = new System.Drawing.Size(202, 22); + this.ttbEnableIfArg.TabIndex = 2; + // + // btnRemove + // + this.btnRemove.AutoSize = true; + this.btnRemove.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.btnRemove.Dock = System.Windows.Forms.DockStyle.Right; + this.btnRemove.Location = new System.Drawing.Point(325, 267); + this.btnRemove.Name = "btnRemove"; + this.btnRemove.Size = new System.Drawing.Size(100, 23); + this.btnRemove.TabIndex = 23; + this.btnRemove.TabStop = false; + this.btnRemove.Text = "Remove Template"; + this.btnRemove.UseVisualStyleBackColor = true; + this.btnRemove.Click += new System.EventHandler(this.BtnRemove_Click); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(3, 85); + this.label3.Margin = new System.Windows.Forms.Padding(3); + this.label3.Name = "label3"; + this.label3.Padding = new System.Windows.Forms.Padding(3); + this.label3.Size = new System.Drawing.Size(82, 18); + this.label3.TabIndex = 24; + this.label3.Text = "Action Timing:"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(3, 239); + this.label6.Margin = new System.Windows.Forms.Padding(3); + this.label6.Name = "label6"; + this.label6.Padding = new System.Windows.Forms.Padding(3); + this.label6.Size = new System.Drawing.Size(88, 18); + this.label6.TabIndex = 27; + this.label6.Text = "Ready Min Diff:"; + // + // TemplateSettings + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.gpbRoot); + this.Name = "TemplateSettings"; + this.Size = new System.Drawing.Size(434, 314); + this.gpbRoot.ResumeLayout(false); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this.flowLayoutPanel3.ResumeLayout(false); + this.flowLayoutPanel3.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.OpenFileDialog ofdTemplate; + private System.Windows.Forms.GroupBox gpbRoot; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.TextBox ttbEnableIfArg; + private System.Windows.Forms.TextBox ttbThreshold; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox ttbName; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.ComboBox cbbMethod; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel3; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.TextBox ttbOffsetX; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.TextBox ttbOffsetY; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.Button btnImage; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ComboBox cbbAction; + private System.Windows.Forms.Button btnRemove; + private System.Windows.Forms.ComboBox cbbActionTiming; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.ComboBox cbbEnableIf; + private System.Windows.Forms.TextBox ttbThresholdNeg; + private System.Windows.Forms.Label label6; + } +} diff --git a/supAutoSplit/SupAutoSplit/UI/TemplateSettings.cs b/supAutoSplit/SupAutoSplit/UI/TemplateSettings.cs new file mode 100644 index 0000000..87be0a1 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/UI/TemplateSettings.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using LiveSplit.UI.Components; +using SupExtension; +using LiveSplit.Model; +using LiveSplit.UI; +using System.Xml; +using OpenCvSharp; + +namespace LiveSplit.SupAutoSplit.UI { + using MatchAction = Action; + using EnableIfFactory = Func>; + using MatchMethodFactory = Func>; + + public partial class TemplateSettings : UserControl { + internal Action RemoveHandler; + + #region Parameters + public string TemplateName { get; set; } + public string TemplateTitle => $"Template: {TemplateName}"; + + public int EnableIfISel { get; set; } = 0; + public string EnableIfArg { get; set; } + private ListItem EnableIfSel => MatchTemplate.EnableIfFactories[EnableIfISel]; + private bool IsActionStart => matchActionISel == 0; // start + public Predicate EnableIf => IsActionStart ? (state => state.CurrentSplitIndex == -1): EnableIfSel.value(EnableIfArg); + + private int matchActionISel; + public int MatchActionISel { + get => matchActionISel; + set { + matchActionISel = value; + // Start => Disable Arg + cbbEnableIf.Enabled = ttbEnableIfArg.Enabled = !IsActionStart; + } + } + private ListItem MatchActionSel => MatchTemplate.MatchActions[MatchActionISel]; + public MatchAction MatchAction => MatchActionSel.value; + + public int ActionTimingISel { get; set; } = 1; + private ListItem ActionTimingSel => MatchTemplate.ActionTimings[ActionTimingISel]; + public bool IsActionOnPosedge => ActionTimingSel.value; + + private string _imagePath; + public string ImagePath { + get => _imagePath; + set { + try { + if (value != "") { + var img = Image.FromFile(value); + var h0 = btnImage.Height; + var h = Math.Max(11, Math.Min(66, img.Height)); + btnImage.Height = h; + btnImage.Width = h * img.Width / img.Height; + btnImage.BackgroundImage = img; + // adjust this.Height + Height += h - h0; + } + _imagePath = value; + } catch { + _imagePath = ""; + } + } + } + + private int ImageOffsetX; + private int ImageOffsetY; + public string ImageOffsetXStr { + get => ImageOffsetX.ToString(); + set => int.TryParse(value, out ImageOffsetX); + } + public string ImageOffsetYStr { + get => ImageOffsetY.ToString(); + set => int.TryParse(value, out ImageOffsetY); + } + public OpenCvSharp.Point ImageOffset => new OpenCvSharp.Point(ImageOffsetX, ImageOffsetY); + + public int MatchMethodISel { get; set; } + private ListItem MatchMethodSel => MatchTemplate.MatchMethodFactories[MatchMethodISel]; + public MatchMethodFactory MatchMethodFactory => MatchMethodSel.value; + + private double matchThreshold; + public double MatchThreshold => matchThreshold; + public string MatchThresholdStr { + get => MatchThreshold.ToString(); + set => double.TryParse(value, out matchThreshold); + } + + private double matchThresholdNeg; + public double MatchThresholdNeg => matchThresholdNeg; + public string MatchThresholdNegStr { + get => MatchThresholdNeg.ToString(); + set => double.TryParse(value, out matchThresholdNeg); + } + #endregion + + public TemplateSettings(XmlElement settings, Action removeHandler) { + RemoveHandler = removeHandler; + + InitializeComponent(); + SetSettings(settings); + + cbbEnableIf.DataSource = MatchTemplate.EnableIfItems; + cbbEnableIf.DataBindings.Add("SelectedIndex", this, "EnableIfISel", false, DataSourceUpdateMode.OnPropertyChanged); + cbbAction.DataSource = MatchTemplate.MatchActionItems; + cbbAction.DataBindings.Add("SelectedIndex", this, "MatchActionISel", false, DataSourceUpdateMode.OnPropertyChanged); + cbbActionTiming.DataSource = MatchTemplate.ActionTimingItems; + cbbActionTiming.DataBindings.Add("SelectedIndex", this, "ActionTimingISel", false, DataSourceUpdateMode.OnPropertyChanged); + cbbMethod.DataSource = MatchTemplate.MatchMethodItems; + cbbMethod.DataBindings.Add("SelectedIndex", this, "MatchMethodISel", false, DataSourceUpdateMode.OnPropertyChanged); + } + + private void BtnTemplate_Click(object sender, EventArgs e) { + if (ofdTemplate.ShowDialog() == DialogResult.OK) + ImagePath = ofdTemplate.FileName; + } + private void BtnRemove_Click(object sender, EventArgs e) { + RemoveHandler(this); + } + + internal int CreateSettingsNode(XmlDocument document, XmlElement parent) { + var hashCode = + SettingsHelper.CreateSetting(document, parent, "Name", TemplateName) ^ + SettingsHelper.CreateSetting(document, parent, "EnableIf", EnableIfSel.key) ^ + SettingsHelper.CreateSetting(document, parent, "EnableIfArg", EnableIfArg) ^ + SettingsHelper.CreateSetting(document, parent, "Action", MatchActionSel.key) ^ + SettingsHelper.CreateSetting(document, parent, "ActionOn", ActionTimingSel.key) ^ + SettingsHelper.CreateSetting(document, parent, "Image", ImagePath) ^ + SettingsHelper.CreateSetting(document, parent, "OffsetX", ImageOffsetX) ^ + SettingsHelper.CreateSetting(document, parent, "OffsetY", ImageOffsetY) ^ + SettingsHelper.CreateSetting(document, parent, "Method", MatchMethodSel.key) ^ + SettingsHelper.CreateSetting(document, parent, "Threshold", MatchThreshold); + SettingsHelper.CreateSetting(document, parent, "ThresholdNeg", MatchThresholdNeg); + return hashCode; + } + private void SetSettings(XmlElement settings) { + TemplateName = SettingsHelper.ParseString(settings?["Name"], ""); + EnableIfISel = SupSettingsHelper.ParseListISel(settings?["EnableIf"], MatchTemplate.EnableIfFactories, 0); + EnableIfArg = SettingsHelper.ParseString(settings?["EnableIfArg"], ""); + MatchActionISel = SupSettingsHelper.ParseListISel(settings?["Action"], MatchTemplate.MatchActions, 1); + ActionTimingISel = SupSettingsHelper.ParseListISel(settings?["ActionOn"], MatchTemplate.ActionTimings, 0); + ImagePath = SettingsHelper.ParseString(settings?["Image"], ""); + ImageOffsetX = SettingsHelper.ParseInt(settings?["OffsetX"], 0); + ImageOffsetY = SettingsHelper.ParseInt(settings?["OffsetY"], 0); + MatchMethodISel = SupSettingsHelper.ParseListISel(settings?["Method"], MatchTemplate.MatchMethodFactories, 0); + matchThreshold = SettingsHelper.ParseDouble(settings?["Threshold"], 0); + matchThresholdNeg = SettingsHelper.ParseDouble(settings?["ThresholdNeg"], 0); + } + } +} + +namespace SupExtension { + public struct ListItem { + public string key; + public string text; + public V value; + + public static implicit operator ListItem((string, V) arg) => + new ListItem() { key = arg.Item1, text = arg.Item1, value = arg.Item2 }; + public static implicit operator ListItem((string, string, V) arg) => + new ListItem() { key = arg.Item1, text = arg.Item2, value = arg.Item3 }; + } + public static class SupArray { + public static int FindIndex(this T[] self, Predicate match) => + Array.FindIndex(self, match); + } + static class SupSettingsHelper { + internal static int ParseListISel(XmlElement element, ListItem[] items, int defaultValue=-1) { + var key = SettingsHelper.ParseString(element); + if (key == null) return defaultValue; + var index = Array.FindIndex(items, o => o.key == key); + return index == -1 ? defaultValue : index; + } + } +} diff --git a/supAutoSplit/SupAutoSplit/UI/TemplateSettings.resx b/supAutoSplit/SupAutoSplit/UI/TemplateSettings.resx new file mode 100644 index 0000000..ebe09a9 --- /dev/null +++ b/supAutoSplit/SupAutoSplit/UI/TemplateSettings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/supAutoSplit/SupExtension.cs b/supAutoSplit/SupExtension.cs new file mode 100644 index 0000000..d395b5f --- /dev/null +++ b/supAutoSplit/SupExtension.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace SupExtension { + public static class SupEnum { + public static E[] Enumerate() => (E[])Enum.GetValues(typeof(E)); + public static IEnumerable EnumerateDescription() => + Enumerate().Select(e => e.ToDescription()); + public static string[] DescriptionList() => + Enumerate().Select(e => e.ToDescription()).ToArray(); + public static string ToDescription(this E val) { + DescriptionAttribute[] attributes = (DescriptionAttribute[])val + .GetType() + .GetField(val.ToString()) + .GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : string.Empty; + } + } + + public static class SupEnumerable { + public static void ForEach(this IEnumerable itr, Action action) { + foreach (T item in itr) { + action(item); + } + } + } + + public static class SupXml { + public static XmlElement CreateElement(this XmlDocument document, string name, XmlElement parent) { + var elm = document.CreateElement(name); + parent?.AppendChild(elm); + return elm; + } + } + + public static class SupParse { + public static int? Int(string s) => int.TryParse(s, out var result) ? (int?)result : null; + } +} diff --git a/supAutoSplit/app.config b/supAutoSplit/app.config new file mode 100644 index 0000000..f10b4ac --- /dev/null +++ b/supAutoSplit/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/supAutoSplit/packages.config b/supAutoSplit/packages.config new file mode 100644 index 0000000..0c259ac --- /dev/null +++ b/supAutoSplit/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/supAutoSplit/supAutoSplit.csproj b/supAutoSplit/supAutoSplit.csproj new file mode 100644 index 0000000..3de1879 --- /dev/null +++ b/supAutoSplit/supAutoSplit.csproj @@ -0,0 +1,133 @@ + + + + + + Debug + AnyCPU + {C1B6A79E-088A-411C-B546-4D72447B4DAA} + Library + Properties + LiveSplit + Livesplit.supAutoSplit + v4.6.1 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\..\..\..\Software\LiveSplit\LiveSplit.Core.dll + + + ..\packages\OpenCvSharp4.4.5.3.20210817\lib\net461\OpenCvSharp.dll + + + ..\packages\OpenCvSharp4.4.5.3.20210817\lib\net461\OpenCvSharp.Extensions.dll + + + ..\packages\OpenCvSharp4.WpfExtensions.4.5.3.20210817\lib\net461\OpenCvSharp.WpfExtensions.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + + ..\packages\System.Drawing.Common.5.0.2\lib\net461\System.Drawing.Common.dll + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + + + + + + + + + + ..\..\..\..\..\..\Software\LiveSplit\UpdateManager.dll + + + + + True + True + Resources.resx + + + + + + + + UserControl + + + Settings.cs + + + UserControl + + + TemplateSettings.cs + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + Settings.cs + + + TemplateSettings.cs + + + + + copy $(TargetDir)$(TargetFileName) C:\Software\LiveSplit\Components\ + + + + このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。 + + + + \ No newline at end of file