From 7dbd32ddcf6611dec88ef56336b364110b019571 Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Tue, 26 Mar 2019 12:54:09 +0200 Subject: A lot of work !!!! --- .../Tango.UnitTesting/TCC/TCC Resources/bitmap.bmp | Bin 0 -> 140174 bytes .../Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs | 81 +++++++++++++++++++++ .../Tango.UnitTesting/Tango.UnitTesting.csproj | 15 +++- 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap.bmp create mode 100644 Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs (limited to 'Software/Visual_Studio/Tango.UnitTesting') diff --git a/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap.bmp b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap.bmp new file mode 100644 index 000000000..1d48c010b Binary files /dev/null and b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap.bmp differ diff --git a/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs new file mode 100644 index 000000000..a96328e8b --- /dev/null +++ b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs @@ -0,0 +1,81 @@ +using Google.Protobuf; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Tango.Core.IO; +using Tango.PMR.TCC; +using Tango.TCC.BL; + +namespace Tango.UnitTesting.TCC +{ + [TestClass] + [TestCategory("TCC")] + public class TCC_TST + { + [TestMethod] + public void Detect() + { + var original_bitmap = Directory.GetCurrentDirectory() + "\\TCC\\TCC Resources\\bitmap.bmp"; + + using (ColorDetector detector = new ColorDetector()) + { + DetectionInput input = new DetectionInput() + { + Number = 10, + Columns = 10, + Rows = 11, + TargetIndex = 99, + Bitmap = ByteString.CopyFrom(File.ReadAllBytes(original_bitmap)), + DebugMode = true, + }; + + var output = detector.Detect(input); + + using (MemoryStream ms = new MemoryStream(output.DebugBitmap.ToArray())) + { + var outputBitmap = TemporaryManager.Default.CreateFile(".bmp"); + outputBitmap.Persist = true; + ms.Position = 0; + Bitmap bmp = new Bitmap(ms); + bmp.Save(outputBitmap); + Process.Start(outputBitmap); + } + + using (Bitmap colorMatrixBmp = ColorDetector.DetectionOutputToImage(input, output, 300, 320)) + { + var outputBitmap = TemporaryManager.Default.CreateFile(".bmp"); + outputBitmap.Persist = true; + colorMatrixBmp.Save(outputBitmap); + Process.Start(outputBitmap); + } + + Assert.IsTrue(output.Number == 20); + } + } + + private byte[] GetBitmapData(byte[] bitmap) + { + using (MemoryStream ms = new MemoryStream(bitmap)) + { + ms.Position = 0; + Bitmap image = new Bitmap(ms); + Rectangle area = new Rectangle(0, 0, image.Width, image.Height); + BitmapData bitmapData = image.LockBits(area, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); + int stride = bitmapData.Stride; + IntPtr ptr = bitmapData.Scan0; + int numBytes = bitmapData.Stride * image.Height; + byte[] rgbValues = new byte[numBytes]; + Marshal.Copy(ptr, rgbValues, 0, numBytes); + return rgbValues; + } + } + } +} diff --git a/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj b/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj index d1bba995e..b515ac420 100644 --- a/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj +++ b/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj @@ -102,6 +102,7 @@ ..\packages\System.Data.SQLite.Linq.1.0.108.0\lib\net46\System.Data.SQLite.Linq.dll True + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -126,6 +127,7 @@ + @@ -138,6 +140,9 @@ Always + + PreserveNewest + @@ -230,6 +235,14 @@ {5001990f-977b-48ff-b217-0236a5022ad8} Tango.Web + + {f209fae8-73f9-441b-97f4-0844a0279390} + Tango.TCC.BL + + + {5d0d4053-cab3-4a4a-929e-37a76483bc22} + Tango.TCC.OpenCV.DLL + {ebb7cb9f-6af2-456b-a5dd-1b136b605d90} Tango.DBObservablesGenerator.CLI @@ -264,7 +277,7 @@ - + \ No newline at end of file -- cgit v1.3.1 From a3bcf6afe7959686a84050caa2064852ae223667 Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Wed, 27 Mar 2019 17:49:22 +0200 Subject: Added timestamps to external bridge udp packet. Fixed detection of long gone external bridge ppc's. --- .../ExternalBridgeUdpDiscoveryPacket.proto | 1 + .../Build/Shortcuts/Machine Emulator.lnk | Bin 1293 -> 1445 bytes .../ExternalBridge/ExternalBridgeScanner.cs | 45 +++++++++++++++------ .../ExternalBridge/ExternalBridgeService.cs | 8 ++++ .../ExternalBridgeUdpDiscoveryPacket.cs | 36 +++++++++++++++-- .../Tango.Transport/Discovery/IDiscoveryService.cs | 5 +++ .../Discovery/UdpDiscoveryService.cs | 7 ++++ .../Tango.Transport/Tango.Transport.csproj | 2 +- .../TCC/TCC Resources/bitmap_ok.bmp | Bin 0 -> 1351734 bytes .../Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs | 20 +++++++-- .../Tango.UnitTesting/Tango.UnitTesting.csproj | 9 ++++- 11 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap_ok.bmp (limited to 'Software/Visual_Studio/Tango.UnitTesting') diff --git a/Software/PMR/Messages/Integration/ExternalBridgeUdpDiscoveryPacket.proto b/Software/PMR/Messages/Integration/ExternalBridgeUdpDiscoveryPacket.proto index 33c565bae..d797e6de3 100644 --- a/Software/PMR/Messages/Integration/ExternalBridgeUdpDiscoveryPacket.proto +++ b/Software/PMR/Messages/Integration/ExternalBridgeUdpDiscoveryPacket.proto @@ -5,5 +5,6 @@ option java_package = "com.twine.tango.pmr.integration"; message ExternalBridgeUdpDiscoveryPacket { + string Time = 1; string SerialNumber = 2; } \ No newline at end of file diff --git a/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk b/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk index ef854af85..cd539235a 100644 Binary files a/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk and b/Software/Visual_Studio/Build/Shortcuts/Machine Emulator.lnk differ diff --git a/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeScanner.cs b/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeScanner.cs index 25ecd7d09..15ccbf0f7 100644 --- a/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeScanner.cs +++ b/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeScanner.cs @@ -133,25 +133,44 @@ namespace Tango.Integration.ExternalBridge { while (IsStarted) { - var ClientEp = new IPEndPoint(IPAddress.Any, 0); - _server.EnableBroadcast = true; - var ClientRequestData = _server.Receive(ref ClientEp); - - ExternalBridgeUdpDiscoveryPacket packet = ExternalBridgeUdpDiscoveryPacket.Parser.ParseFrom(ClientRequestData); - - if (!AvailableMachines.OfType().ToList().Exists(x => x.SerialNumber == packet.SerialNumber)) + try { - ExternalBridgeTcpClient newMachine = new ExternalBridgeTcpClient(packet.SerialNumber, ClientEp.Address.ToString()); + var ClientEp = new IPEndPoint(IPAddress.Any, 0); + _server.EnableBroadcast = true; + var ClientRequestData = _server.Receive(ref ClientEp); - LogManager.Log("Found a new machine via TCP " + newMachine.SerialNumber); + ExternalBridgeUdpDiscoveryPacket packet = ExternalBridgeUdpDiscoveryPacket.Parser.ParseFrom(ClientRequestData); - ThreadsHelper.InvokeUINow(() => + if (packet.Time == String.Empty || (DateTime.Now - DateTime.Parse(packet.Time)).TotalSeconds < 6) { - AvailableMachines.Insert(1, newMachine); - }); + if (!AvailableMachines.OfType().ToList().Exists(x => x.SerialNumber == packet.SerialNumber && x.IPAddress == ClientEp.Address.ToString())) + { + ExternalBridgeTcpClient newMachine = new ExternalBridgeTcpClient(packet.SerialNumber, ClientEp.Address.ToString()); + + LogManager.Log("Found a new machine via TCP " + newMachine.SerialNumber); + + ThreadsHelper.InvokeUINow(() => + { + AvailableMachines.Insert(1, newMachine); + }); + } + } + //else if (packet.Time != String.Empty && (DateTime.Now - DateTime.Parse(packet.Time)).TotalSeconds > 5) + //{ + // var machine = AvailableMachines.OfType().ToList().FirstOrDefault(x => x.SerialNumber == packet.SerialNumber && x.IPAddress == ClientEp.Address.ToString()); + + // if (machine != null) + // { + // AvailableMachines.Remove(machine); + // } + //} + } + catch (Exception ex) + { + LogManager.Log(ex); } - Thread.Sleep(1000); + //Thread.Sleep(1000); } } diff --git a/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeService.cs b/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeService.cs index 4cdd372f5..faa538e93 100644 --- a/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeService.cs +++ b/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeService.cs @@ -156,6 +156,14 @@ namespace Tango.Integration.ExternalBridge { SerialNumber = Machine.SerialNumber, }); + + _discoverySevice.BeforeBroadcasting -= _discoverySevice_BeforeBroadcasting; + _discoverySevice.BeforeBroadcasting += _discoverySevice_BeforeBroadcasting; + } + + private void _discoverySevice_BeforeBroadcasting(object sender, ExternalBridgeUdpDiscoveryPacket e) + { + e.Time = DateTime.Now.ToString(); } /// diff --git a/Software/Visual_Studio/Tango.PMR/Integration/ExternalBridgeUdpDiscoveryPacket.cs b/Software/Visual_Studio/Tango.PMR/Integration/ExternalBridgeUdpDiscoveryPacket.cs index 163a753b7..df584d5c6 100644 --- a/Software/Visual_Studio/Tango.PMR/Integration/ExternalBridgeUdpDiscoveryPacket.cs +++ b/Software/Visual_Studio/Tango.PMR/Integration/ExternalBridgeUdpDiscoveryPacket.cs @@ -23,13 +23,13 @@ namespace Tango.PMR.Integration { byte[] descriptorData = global::System.Convert.FromBase64String( string.Concat( "CiZFeHRlcm5hbEJyaWRnZVVkcERpc2NvdmVyeVBhY2tldC5wcm90bxIVVGFu", - "Z28uUE1SLkludGVncmF0aW9uIjgKIEV4dGVybmFsQnJpZGdlVWRwRGlzY292", - "ZXJ5UGFja2V0EhQKDFNlcmlhbE51bWJlchgCIAEoCUIhCh9jb20udHdpbmUu", - "dGFuZ28ucG1yLmludGVncmF0aW9uYgZwcm90bzM=")); + "Z28uUE1SLkludGVncmF0aW9uIkYKIEV4dGVybmFsQnJpZGdlVWRwRGlzY292", + "ZXJ5UGFja2V0EgwKBFRpbWUYASABKAkSFAoMU2VyaWFsTnVtYmVyGAIgASgJ", + "QiEKH2NvbS50d2luZS50YW5nby5wbXIuaW50ZWdyYXRpb25iBnByb3RvMw==")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { }, new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { - new pbr::GeneratedClrTypeInfo(typeof(global::Tango.PMR.Integration.ExternalBridgeUdpDiscoveryPacket), global::Tango.PMR.Integration.ExternalBridgeUdpDiscoveryPacket.Parser, new[]{ "SerialNumber" }, null, null, null) + new pbr::GeneratedClrTypeInfo(typeof(global::Tango.PMR.Integration.ExternalBridgeUdpDiscoveryPacket), global::Tango.PMR.Integration.ExternalBridgeUdpDiscoveryPacket.Parser, new[]{ "Time", "SerialNumber" }, null, null, null) })); } #endregion @@ -60,6 +60,7 @@ namespace Tango.PMR.Integration { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public ExternalBridgeUdpDiscoveryPacket(ExternalBridgeUdpDiscoveryPacket other) : this() { + time_ = other.time_; serialNumber_ = other.serialNumber_; } @@ -68,6 +69,17 @@ namespace Tango.PMR.Integration { return new ExternalBridgeUdpDiscoveryPacket(this); } + /// Field number for the "Time" field. + public const int TimeFieldNumber = 1; + private string time_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Time { + get { return time_; } + set { + time_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + /// Field number for the "SerialNumber" field. public const int SerialNumberFieldNumber = 2; private string serialNumber_ = ""; @@ -92,6 +104,7 @@ namespace Tango.PMR.Integration { if (ReferenceEquals(other, this)) { return true; } + if (Time != other.Time) return false; if (SerialNumber != other.SerialNumber) return false; return true; } @@ -99,6 +112,7 @@ namespace Tango.PMR.Integration { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override int GetHashCode() { int hash = 1; + if (Time.Length != 0) hash ^= Time.GetHashCode(); if (SerialNumber.Length != 0) hash ^= SerialNumber.GetHashCode(); return hash; } @@ -110,6 +124,10 @@ namespace Tango.PMR.Integration { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { + if (Time.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Time); + } if (SerialNumber.Length != 0) { output.WriteRawTag(18); output.WriteString(SerialNumber); @@ -119,6 +137,9 @@ namespace Tango.PMR.Integration { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { int size = 0; + if (Time.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Time); + } if (SerialNumber.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(SerialNumber); } @@ -130,6 +151,9 @@ namespace Tango.PMR.Integration { if (other == null) { return; } + if (other.Time.Length != 0) { + Time = other.Time; + } if (other.SerialNumber.Length != 0) { SerialNumber = other.SerialNumber; } @@ -143,6 +167,10 @@ namespace Tango.PMR.Integration { default: input.SkipLastField(); break; + case 10: { + Time = input.ReadString(); + break; + } case 18: { SerialNumber = input.ReadString(); break; diff --git a/Software/Visual_Studio/Tango.Transport/Discovery/IDiscoveryService.cs b/Software/Visual_Studio/Tango.Transport/Discovery/IDiscoveryService.cs index 9ec48ba47..f650c77f7 100644 --- a/Software/Visual_Studio/Tango.Transport/Discovery/IDiscoveryService.cs +++ b/Software/Visual_Studio/Tango.Transport/Discovery/IDiscoveryService.cs @@ -13,6 +13,11 @@ namespace Tango.Transport.Discovery /// The type of the discovery message. public interface IDiscoveryService : IDiscoveryComponent where DiscoveryMessage : IMessage { + /// + /// Occurs before broadcasting the discovery message and gives a chance to modify the packet message. + /// + event EventHandler BeforeBroadcasting; + /// /// Gets or sets the current discovery message. /// diff --git a/Software/Visual_Studio/Tango.Transport/Discovery/UdpDiscoveryService.cs b/Software/Visual_Studio/Tango.Transport/Discovery/UdpDiscoveryService.cs index 97736e7fb..1fa486e68 100644 --- a/Software/Visual_Studio/Tango.Transport/Discovery/UdpDiscoveryService.cs +++ b/Software/Visual_Studio/Tango.Transport/Discovery/UdpDiscoveryService.cs @@ -19,6 +19,11 @@ namespace Tango.Transport.Discovery { private Timer _timer; + /// + /// Occurs before broadcasting the discovery message and gives a chance to modify the packet message. + /// + public event EventHandler BeforeBroadcasting; + /// /// Gets or sets the current discovery message. /// @@ -107,6 +112,8 @@ namespace Tango.Transport.Discovery IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, Port); + BeforeBroadcasting?.Invoke(this, CurrentDiscoveryMessage); + byte[] bytes = CurrentDiscoveryMessage.ToByteArray(); client.EnableBroadcast = true; diff --git a/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj b/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj index 2fe525153..87af709e0 100644 --- a/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj +++ b/Software/Visual_Studio/Tango.Transport/Tango.Transport.csproj @@ -148,7 +148,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap_ok.bmp b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap_ok.bmp new file mode 100644 index 000000000..0135298a5 Binary files /dev/null and b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC Resources/bitmap_ok.bmp differ diff --git a/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs index a96328e8b..475bc68fd 100644 --- a/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs +++ b/Software/Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs @@ -11,6 +11,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Tango.Core.IO; +using Tango.CSV; using Tango.PMR.TCC; using Tango.TCC.BL; @@ -23,13 +24,13 @@ namespace Tango.UnitTesting.TCC [TestMethod] public void Detect() { - var original_bitmap = Directory.GetCurrentDirectory() + "\\TCC\\TCC Resources\\bitmap.bmp"; + var original_bitmap = Directory.GetCurrentDirectory() + "\\TCC\\TCC Resources\\bitmap_ok.bmp"; using (ColorDetector detector = new ColorDetector()) { DetectionInput input = new DetectionInput() { - Number = 10, + Number = 5, Columns = 10, Rows = 11, TargetIndex = 99, @@ -57,7 +58,20 @@ namespace Tango.UnitTesting.TCC Process.Start(outputBitmap); } - Assert.IsTrue(output.Number == 20); + var benchmarksCsvFile = TemporaryManager.Default.CreateFile(".csv"); + benchmarksCsvFile.Persist = true; + + using (CsvFile benchmarksRGB = new CsvFile(new CsvDestination(benchmarksCsvFile))) + { + foreach (var color in output.ColorMatrix) + { + benchmarksRGB.Append(color); + } + } + + Helper.ShowInExplorer(benchmarksCsvFile); + + Assert.IsTrue(output.Number == 15); } } diff --git a/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj b/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj index b515ac420..3e5434c98 100644 --- a/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj +++ b/Software/Visual_Studio/Tango.UnitTesting/Tango.UnitTesting.csproj @@ -143,6 +143,9 @@ PreserveNewest + + PreserveNewest + @@ -179,6 +182,10 @@ {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} Tango.Core + + {58e8825f-0c96-449c-b320-1e82b0aa876b} + Tango.CSV + {0e0eef3e-8f4e-4f23-9d19-479fd8d76c12} Tango.DAL.Local @@ -277,7 +284,7 @@ - + \ No newline at end of file -- cgit v1.3.1 From e07ebe48156313899ab6f4a06928aac6ef818f0d Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Thu, 28 Mar 2019 19:05:42 +0200 Subject: Working on real-time card detection... --- Software/PMR/Messages/TCC/DetectionInput.proto | 5 +- .../Controls/ColorMatrixControl.cs | 55 + .../Controls/IndexedUniformGrid.cs | 47 + .../Graph/WpfGraphController.cs | 36 + .../Images/bottomLeft.bmp | Bin 0 -> 3678 bytes .../Images/bottomRight.bmp | Bin 0 -> 3678 bytes .../Images/topRight.bmp | Bin 0 -> 3678 bytes .../Images/topleft.bmp | Bin 0 -> 3678 bytes .../Tango.MachineStudio.ColorCapture.csproj | 25 + .../Themes/Generic.xaml | 40 +- .../ViewModels/MainViewVM.cs | 113 +- .../Views/MainView.xaml | 236 +- .../Views/MainView.xaml.cs | 8 + .../RealTimeGraphX.WPF/Surfaces/WpfGraphSurface.cs | 2 + .../SideChains/RealTimeGraphX/GraphPainterBase.cs | 4 +- .../TCC/Tango.TCC.BL/CardDetectionResult.cs | 17 + .../Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs | 74 + .../TCC/Tango.TCC.BL/Tango.TCC.BL.csproj | 11 + .../TCC/Tango.TCC.CardDetector/ArucoUtils.cpp | 89 + .../TCC/Tango.TCC.CardDetector/ArucoUtils.h | 25 + .../TCC/Tango.TCC.CardDetector/AssemblyInfo.cpp | Bin 0 -> 1512 bytes .../TCC/Tango.TCC.CardDetector/CardDetection.cpp | Bin 0 -> 2730 bytes .../TCC/Tango.TCC.CardDetector/CardDetection.h | Bin 0 -> 776 bytes .../Tango.TCC.CardDetector/CardDetectionConfig.cpp | 7 + .../Tango.TCC.CardDetector/CardDetectionConfig.h | 18 + .../Tango.TCC.CardDetector/CardDetectionResult.cpp | 7 + .../Tango.TCC.CardDetector/CardDetectionResult.h | 21 + .../Tango.TCC.CardDetector.vcxproj | 158 + .../Tango.TCC.CardDetector.vcxproj.filters | 47 + .../TCC/Tango.TCC.CardDetector/app.ico | Bin 0 -> 41395 bytes .../TCC/Tango.TCC.CardDetector/app.rc | Bin 0 -> 2558 bytes .../TCC/Tango.TCC.CardDetector/bitmap_image.hpp | 5105 ++++++++++++++++++++ .../TCC/Tango.TCC.ColorDetector/ColorDetection.cpp | 18 +- .../PMR/TCC/DetectionInput.pb-c.c | 35 +- .../PMR/TCC/DetectionInput.pb-c.h | 8 +- .../TCC/Tango.TCC.OpenCV/CardDetector.cpp | 1062 ---- .../TCC/Tango.TCC.OpenCV/CardDetector.h | 57 - .../TCC/Tango.TCC.OpenCV/CardProcessor.cpp | 1062 ++++ .../TCC/Tango.TCC.OpenCV/CardProcessor.h | 57 + .../TCC/Tango.TCC.OpenCV/Tango.TCC.OpenCV.vcxproj | 4 +- .../Tango.TCC.OpenCV.vcxproj.filters | 8 +- .../Visual_Studio/Tango.PMR/TCC/DetectionInput.cs | 76 +- .../Visual_Studio/Tango.UnitTesting/TCC/TCC_TST.cs | 55 +- Software/Visual_Studio/Tango.sln | 49 +- 44 files changed, 7357 insertions(+), 1284 deletions(-) create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/ColorMatrixControl.cs create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/IndexedUniformGrid.cs create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomLeft.bmp create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomRight.bmp create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topRight.bmp create mode 100644 Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topleft.bmp create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.h create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/AssemblyInfo.cpp create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.cpp create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.cpp create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj.filters create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.ico create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.rc create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.CardDetector/bitmap_image.hpp delete mode 100644 Software/Visual_Studio/TCC/Tango.TCC.OpenCV/CardDetector.cpp delete mode 100644 Software/Visual_Studio/TCC/Tango.TCC.OpenCV/CardDetector.h create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.OpenCV/CardProcessor.cpp create mode 100644 Software/Visual_Studio/TCC/Tango.TCC.OpenCV/CardProcessor.h (limited to 'Software/Visual_Studio/Tango.UnitTesting') diff --git a/Software/PMR/Messages/TCC/DetectionInput.proto b/Software/PMR/Messages/TCC/DetectionInput.proto index 30dbacab4..d9332bbd4 100644 --- a/Software/PMR/Messages/TCC/DetectionInput.proto +++ b/Software/PMR/Messages/TCC/DetectionInput.proto @@ -11,7 +11,8 @@ message DetectionInput int32 Columns = 2; int32 Rows = 3; int32 TargetIndex = 4; - bool DebugMode = 5; - repeated DetectionBenchmark Benchmarks = 6; + bool RequestDebugImage = 5; + bool RequestColorMatrix = 6; + repeated DetectionBenchmark Benchmarks = 7; double number = 10; } \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/ColorMatrixControl.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/ColorMatrixControl.cs new file mode 100644 index 000000000..7ea8681ab --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/ColorMatrixControl.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +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; + +namespace Tango.MachineStudio.ColorCapture.Controls +{ + public class ColorMatrixControl : Control + { + public ObservableCollection Colors + { + get { return (ObservableCollection)GetValue(ColorsProperty); } + set { SetValue(ColorsProperty, value); } + } + public static readonly DependencyProperty ColorsProperty = + DependencyProperty.Register("Colors", typeof(ObservableCollection), typeof(ColorMatrixControl), new PropertyMetadata(null)); + + public int Columns + { + get { return (int)GetValue(ColumnsProperty); } + set { SetValue(ColumnsProperty, value); } + } + public static readonly DependencyProperty ColumnsProperty = + DependencyProperty.Register("Columns", typeof(int), typeof(ColorMatrixControl), new PropertyMetadata(10)); + + public int Rows + { + get { return (int)GetValue(RowsProperty); } + set { SetValue(RowsProperty, value); } + } + public static readonly DependencyProperty RowsProperty = + DependencyProperty.Register("Rows", typeof(int), typeof(ColorMatrixControl), new PropertyMetadata(11)); + + public ColorMatrixControl() + { + Colors = new ObservableCollection(); + } + + static ColorMatrixControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorMatrixControl), new FrameworkPropertyMetadata(typeof(ColorMatrixControl))); + } + } +} diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/IndexedUniformGrid.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/IndexedUniformGrid.cs new file mode 100644 index 000000000..af34e038d --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Controls/IndexedUniformGrid.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace Tango.MachineStudio.ColorCapture.Controls +{ + public class IndexedUniformGrid : Grid + { + public int Columns + { + get { return (int)GetValue(ColumnsProperty); } + set { SetValue(ColumnsProperty, value); } + } + public static readonly DependencyProperty ColumnsProperty = + DependencyProperty.Register("Columns", typeof(int), typeof(IndexedUniformGrid), new PropertyMetadata(0)); + + public int Rows + { + get { return (int)GetValue(RowsProperty); } + set { SetValue(RowsProperty, value); } + } + public static readonly DependencyProperty RowsProperty = + DependencyProperty.Register("Rows", typeof(int), typeof(IndexedUniformGrid), new PropertyMetadata(0)); + + public IndexedUniformGrid() + { + Loaded += IndexedUniformGrid_Loaded; + } + + private void IndexedUniformGrid_Loaded(object sender, RoutedEventArgs e) + { + for (int i = 0; i < Columns; i++) + { + ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); + } + + for (int i = 0; i < Rows; i++) + { + RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); + } + } + } +} diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs new file mode 100644 index 000000000..dd32cde93 --- /dev/null +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Graph/WpfGraphController.cs @@ -0,0 +1,36 @@ +using RealTimeGraphX; +using RealTimeGraphX.DataPoints; +using RealTimeGraphX.Renderers; +using RealTimeGraphX.WPF.DataSeries; +using RealTimeGraphX.WPF.Painters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace Tango.MachineStudio.ColorCapture.Graph +{ + public class WpfGraphController : GraphControllerBase + { + public WpfGraphController(int refreshRate = 50) + { + AddDataSeries(new WpfDataSeries() + { + StrokeThickness = 1, + Stroke = Colors.Black, + }); + + var renderer = new GraphScrollingRenderer() + { + RefreshRate = TimeSpan.FromMilliseconds(refreshRate) + }; + + var painter = new WpfScrollingGraphPainter(); + + ConnectOutput(renderer); + renderer.ConnectOutput(painter); + } + } +} diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomLeft.bmp b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomLeft.bmp new file mode 100644 index 000000000..13b33704b Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomLeft.bmp differ diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomRight.bmp b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomRight.bmp new file mode 100644 index 000000000..d2116fad2 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/bottomRight.bmp differ diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topRight.bmp b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topRight.bmp new file mode 100644 index 000000000..f008d454d Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topRight.bmp differ diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topleft.bmp b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topleft.bmp new file mode 100644 index 000000000..8115cf359 Binary files /dev/null and b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Images/topleft.bmp differ diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj index 2422cec2a..4d0c0fae5 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Tango.MachineStudio.ColorCapture.csproj @@ -88,6 +88,9 @@ GlobalVersionInfo.cs + + + Code @@ -117,6 +120,18 @@ + + {37e4ceab-b54b-451f-b535-04cf7da9c459} + ColorMine + + + {99d233c5-fee7-418e-9c25-d4584cb52e28} + RealTimeGraphX.WPF + + + {6d55a3b8-46d3-493a-a143-aebd2b98d683} + RealTimeGraphX + {f441feee-322a-4943-b566-110e12fd3b72} Tango.BL @@ -145,6 +160,10 @@ {f209fae8-73f9-441b-97f4-0844a0279390} Tango.TCC.BL + + {5d0d4053-cab3-4a4a-929e-37a76483bc22} + Tango.TCC.OpenCV.DLL + {cb0b0aa2-bb24-4bca-a720-45e397684e12} Tango.MachineStudio.Common @@ -156,5 +175,11 @@ + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Themes/Generic.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Themes/Generic.xaml index 968c541b3..0a2ebbca8 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Themes/Generic.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Themes/Generic.xaml @@ -1,6 +1,44 @@ - + + diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs index 298b862fb..f21c403de 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/ViewModels/MainViewVM.cs @@ -1,14 +1,20 @@ -using System; +using ColorMine.ColorSpaces; +using ColorMine.ColorSpaces.Comparisons; +using System; using System.Collections.Generic; -using System.Drawing; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Media; +using System.Windows.Media.Imaging; using Tango.Core.Commands; +using Tango.MachineStudio.ColorCapture.Graph; using Tango.MachineStudio.Common; using Tango.MachineStudio.Common.Notifications; using Tango.MachineStudio.Common.Video; +using Tango.TCC.BL; using Tango.Video.DirectCapture; namespace Tango.MachineStudio.ColorCapture.ViewModels @@ -16,6 +22,8 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels public class MainViewVM : StudioViewModel { private INotificationProvider _notification; + private CardDetector _cardDetector; + private int _sampleCounter; public IVideoCaptureProvider VideoProvider { get; set; } @@ -32,14 +40,74 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels } } + private BitmapSource _detectedSource; + public BitmapSource DetectedSource + { + get { return _detectedSource; } + set { _detectedSource = value; RaisePropertyChangedAuto(); } + } + + private ObservableCollection _colors; + public ObservableCollection Colors + { + get { return _colors; } + set { _colors = value; RaisePropertyChangedAuto(); RaisePropertyChangedAuto(); } + } + + private Color _processedColor; + public Color ProcessedColor + { + get { return _processedColor; } + set { _processedColor = value; RaisePropertyChangedAuto(); } + } + + private Color _capturedColor; + public Color CapturedColor + { + get { return _capturedColor; } + set { _capturedColor = value; RaisePropertyChangedAuto(); } + } + + private double _measureL; + public double MeasureL + { + get { return _measureL; } + set { _measureL = value; RaisePropertyChangedAuto(); } + } + + private double _measureA; + public double MeasureA + { + get { return _measureA; } + set { _measureA = value; RaisePropertyChangedAuto(); } + } + + private double _measureB; + public double MeasureB + { + get { return _measureB; } + set { _measureB = value; RaisePropertyChangedAuto(); } + } + + + public WpfGraphController CaptureDeltaEController { get; set; } + public RelayCommand ToggleCameraCommand { get; set; } - public MainViewVM(IVideoCaptureProvider videoProvider, INotificationProvider notificationProvider) + public MainViewVM() + { + _cardDetector = new CardDetector(); + ToggleCameraCommand = new RelayCommand(ToggleCamera); + CaptureDeltaEController = new WpfGraphController(); + CaptureDeltaEController.Range.AutoY = true; + CaptureDeltaEController.Range.MaximumX = 1000; + } + + public MainViewVM(IVideoCaptureProvider videoProvider, INotificationProvider notificationProvider) : this() { _notification = notificationProvider; VideoProvider = videoProvider; SelectedVideoDevice = videoProvider.AvailableCaptureDevices.FirstOrDefault(); - ToggleCameraCommand = new RelayCommand(ToggleCamera); } private void ToggleCamera() @@ -52,6 +120,7 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels } else { + SelectedVideoDevice.Resolution = new Resolution(1280, 720); SelectedVideoDevice.Start(); } } @@ -71,11 +140,41 @@ namespace Tango.MachineStudio.ColorCapture.ViewModels } } - private void OnVideoFrameReceived(object sender, Video.DirectShow.EventArguments.FrameReceivedEventArgs args) + double deltaE = 0; + private async void OnVideoFrameReceived(object sender, Video.DirectShow.EventArguments.FrameReceivedEventArgs args) { - Bitmap bmp = args.BitmapSource.ConvertFormat(PixelFormats.Rgb24).ToGDIBitmap(); + if (_cardDetector.CanDetect) + { + var result = await _cardDetector.Detect(args.BitmapSource); + + if (result.IsDetected) + { + DetectedSource = result.DetectedBitmap; + + if (Colors == null) + { + Colors = new ObservableCollection(Enumerable.Range(0, result.ColorDetectionOutput.ColorMatrix.Count).Select(x => new Color())); + } + + InvokeUI(() => + { + for (int i = 0; i < result.ColorDetectionOutput.ColorMatrix.Count; i++) + { + var detectedColor = result.ColorDetectionOutput.ColorMatrix[i]; + Colors[i] = Color.FromArgb(255, (byte)detectedColor.R, (byte)detectedColor.G, (byte)detectedColor.B); + } + + CapturedColor = Color.FromArgb(255, (byte)result.ColorDetectionOutput.RawColor.R, (byte)result.ColorDetectionOutput.RawColor.G, (byte)result.ColorDetectionOutput.RawColor.B); + ProcessedColor = Color.FromArgb(255, (byte)result.ColorDetectionOutput.ProcessedColor.R, (byte)result.ColorDetectionOutput.ProcessedColor.G, (byte)result.ColorDetectionOutput.ProcessedColor.B); + }); + + //calculate delta E. + Lab measureLab = new Lab(MeasureL, MeasureA, MeasureB); + deltaE = measureLab.Compare(new Rgb(ProcessedColor.R, ProcessedColor.G, ProcessedColor.B), new CieDe2000Comparison()); + } + } - bmp.Dispose(); + CaptureDeltaEController.PushData(_sampleCounter++, deltaE); } public override void OnApplicationReady() diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml index cd619d0a0..259e3160e 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml @@ -6,11 +6,16 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:converters="clr-namespace:Tango.SharedUI.Converters;assembly=Tango.SharedUI" xmlns:vm="clr-namespace:Tango.MachineStudio.ColorCapture.ViewModels" + xmlns:mahapps="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:graphX="clr-namespace:RealTimeGraphX.WPF.Surfaces;assembly=RealTimeGraphX.WPF" + xmlns:componentsX="clr-namespace:RealTimeGraphX.WPF.Components;assembly=RealTimeGraphX.WPF" + xmlns:controls="clr-namespace:Tango.MachineStudio.ColorCapture.Controls" + xmlns:realtimeGraphX="clr-namespace:RealTimeGraphX.WPF.Surfaces;assembly=RealTimeGraphX.WPF" xmlns:global="clr-namespace:Tango.MachineStudio.ColorCapture" xmlns:local="clr-namespace:Tango.MachineStudio.ColorCapture.Views" mc:Ignorable="d" d:DesignHeight="1080" d:DesignWidth="1920" Background="Transparent" d:DataContext="{d:DesignInstance Type=vm:MainViewVM, IsDesignTimeCreatable=False}" DataContext="{x:Static global:ViewModelLocator.MainViewVM}"> - + @@ -25,83 +30,170 @@ - Selected Capture Device - + Capture Device + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Captured Color + + + + + + + + + + + + + + + + + + Processed Color + + + + + + + + + + + + + + + + + + + + + + + + + + HISTORY + + + + + + + + + + + + + Delta E Reference Point + + L: + + + A: + + + B: + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs index 4f890bfe1..05b5daf7e 100644 --- a/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs +++ b/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.ColorCapture/Views/MainView.xaml.cs @@ -12,6 +12,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using Tango.MachineStudio.ColorCapture.ViewModels; namespace Tango.MachineStudio.ColorCapture.Views { @@ -20,9 +21,16 @@ namespace Tango.MachineStudio.ColorCapture.Views /// public partial class MainView : UserControl { + private MainViewVM _vm; + public MainView() { InitializeComponent(); + Loaded += (_, __) => + { + _vm = DataContext as MainViewVM; + _vm.CaptureDeltaEController.Output.Output.ConnectOutput(Graph); + }; } } } diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/Surfaces/WpfGraphSurface.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/Surfaces/WpfGraphSurface.cs index b9e425282..5057949f0 100644 --- a/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/Surfaces/WpfGraphSurface.cs +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX.WPF/Surfaces/WpfGraphSurface.cs @@ -398,6 +398,8 @@ namespace RealTimeGraphX.WPF.Surfaces { Input.ConnectOutput(this, true); } + + OnSurfaceSizeChanged(_grid.ActualWidth, _grid.ActualHeight); } /// diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphPainterBase.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphPainterBase.cs index c9af5858d..12f6143fc 100644 --- a/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphPainterBase.cs +++ b/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphPainterBase.cs @@ -396,13 +396,13 @@ namespace RealTimeGraphX Output = surface; + Output.SurfaceSizeChanged += _surface_SurfaceSizeChanged; + if (!fromOutput) { Output.ConnectInput(this, true); } - Output.SurfaceSizeChanged += _surface_SurfaceSizeChanged; - _size_changed = true; } diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs new file mode 100644 index 000000000..821d19330 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetectionResult.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Tango.PMR.TCC; + +namespace Tango.TCC.BL +{ + public class CardDetectionResult + { + public bool IsDetected { get; set; } + public BitmapSource DetectedBitmap { get; set; } + public DetectionOutput ColorDetectionOutput { get; set; } + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs new file mode 100644 index 000000000..d10e99b81 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/CardDetector.cs @@ -0,0 +1,74 @@ +using Google.Protobuf; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Tango.PMR.TCC; + +namespace Tango.TCC.BL +{ + public class CardDetector + { + private ColorDetector _colorDetector; + + private bool _canDetect; + public bool CanDetect + { + get { return _canDetect; } + private set { _canDetect = value; } + } + + public CardDetector() + { + CanDetect = true; + _colorDetector = new ColorDetector(); + } + + public Task Detect(BitmapSource source) + { + if (!CanDetect) + { + throw new InvalidOperationException("Cannot detect the card at this moment. Please wait for CanDetect to be true."); + } + + CanDetect = false; + + var cloned = source.Clone(); + cloned.Freeze(); + + return Task.Factory.StartNew(() => + { + CardDetectionResult detectionResult = new CardDetectionResult(); + + Tango.TCC.CardDetector.CardDetection detector = new TCC.CardDetector.CardDetection(); + var result = detector.Detect(cloned.ToBytes(PixelFormats.Rgb24), new TCC.CardDetector.CardDetectionConfig() + { + DesiredBitmapWidth = 300, + DesiredBitmapHeight = 310, + }); + + if (result.IsDetected) + { + detectionResult.IsDetected = true; + detectionResult.DetectedBitmap = result.DetectedBitmap.ToBitmapSource(); + + detectionResult.ColorDetectionOutput = _colorDetector.Detect(new DetectionInput() + { + Bitmap = ByteString.CopyFrom(detectionResult.DetectedBitmap.ToBmpBytes()), + Columns = 10, + Rows = 11, + TargetIndex = 89, + RequestColorMatrix = true, + }); + } + + CanDetect = true; + + return detectionResult; + }); + } + } +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj b/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj index 39aa7e8b2..bb82e8c5d 100644 --- a/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj +++ b/Software/Visual_Studio/TCC/Tango.TCC.BL/Tango.TCC.BL.csproj @@ -34,18 +34,25 @@ ..\..\packages\Google.Protobuf.3.4.1\lib\net45\Google.Protobuf.dll + + + + + + + @@ -71,6 +78,10 @@ {5001990f-977b-48ff-b217-0236a5022ad8} Tango.Web + + {bb268536-9e03-46a4-9b11-6025211d87f0} + Tango.TCC.CardDetector + {5d0d4053-cab3-4a4a-929e-37a76483bc22} Tango.TCC.OpenCV.DLL diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp new file mode 100644 index 000000000..76923a5c6 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.cpp @@ -0,0 +1,89 @@ +#include "ArucoUtils.h" + +using namespace Tango::TCC::CardDetector; + +Ptr dictionary; + +ArucoUtils::ArucoUtils() +{ + dictionary = getPredefinedDictionary(aruco::DICT_4X4_50); +} + + +ArucoUtils::~ArucoUtils() +{ +} + +vector ArucoUtils::getArcusVertices(Mat image) +{ + vector vertices; + + vector ids; + vector> corners; + + aruco::DetectorParameters* params = new aruco::DetectorParameters(); + params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + params->perspectiveRemovePixelPerCell = 50; + + + aruco::detectMarkers(image, dictionary, corners, ids, &(*params)); + if (corners.size() == 4) + { + //aruco::drawDetectedMarkers(image, corners, ids); + + vertices = + { + Point(0,0), + Point(0,0), + Point(0,0), + Point(0,0) + }; + + InputArrayOfArrays _corners = corners; + InputArray _ids = ids; + + int nMarkers = (int)_corners.total(); + + for (int i = 0; i < nMarkers; i++) { + + Mat currentMarker = _corners.getMat(i); + int id = _ids.getMat().ptr< int >(0)[i] - 1; + + switch (id) + { + case 0: + vertices[0] = currentMarker.ptr< Point2f >(0)[0]; + break; + case 1: + vertices[1] = currentMarker.ptr< Point2f >(0)[1]; + break; + case 2: + vertices[2] = currentMarker.ptr< Point2f >(0)[2]; + break; + case 3: + vertices[3] = currentMarker.ptr< Point2f >(0)[3]; + break; + } + } + } + + return vertices; +} + +Mat ArucoUtils::applyHomography(Mat image, vector vertices, Size destination_size) +{ + Mat im_dst = Mat::zeros(destination_size, CV_8UC3); + + vector pts_dst; + + pts_dst.push_back(Point2f(0, 0)); + pts_dst.push_back(Point2f(destination_size.width - 1, 0)); + pts_dst.push_back(Point2f(destination_size.width - 1, destination_size.height - 1)); + pts_dst.push_back(Point2f(0, destination_size.height - 1)); + + Mat im_temp = image.clone(); + + Mat tform = findHomography(vertices, pts_dst); + warpPerspective(image, im_dst, tform, destination_size); + return im_dst; +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.h new file mode 100644 index 000000000..382adf635 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/ArucoUtils.h @@ -0,0 +1,25 @@ +#include +#include + +using namespace cv; +using namespace std; +using namespace aruco; + +#pragma once +namespace Tango +{ + namespace TCC + { + namespace CardDetector { + class ArucoUtils + { + public: + ArucoUtils(); + ~ArucoUtils(); + vector getArcusVertices(Mat image); + Mat applyHomography(Mat image, vector vertices, cv::Size destination_size); + }; + } + } +} + diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/AssemblyInfo.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/AssemblyInfo.cpp new file mode 100644 index 000000000..6cc770937 Binary files /dev/null and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/AssemblyInfo.cpp differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp new file mode 100644 index 000000000..546c228db Binary files /dev/null and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.cpp differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h new file mode 100644 index 000000000..db8f51406 Binary files /dev/null and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetection.h differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.cpp new file mode 100644 index 000000000..12aed5597 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.cpp @@ -0,0 +1,7 @@ +#include "CardDetectionConfig.h" + +using namespace Tango::TCC::CardDetector; + +CardDetectionConfig::CardDetectionConfig() +{ +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h new file mode 100644 index 000000000..f014c2159 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionConfig.h @@ -0,0 +1,18 @@ +#pragma once + +namespace Tango +{ + namespace TCC + { + namespace CardDetector { + public ref class CardDetectionConfig + { + public: + CardDetectionConfig(); + property double DesiredBitmapWidth; + property double DesiredBitmapHeight; + }; + } + } +} + diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.cpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.cpp new file mode 100644 index 000000000..4d0596f0e --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.cpp @@ -0,0 +1,7 @@ +#include "CardDetectionResult.h" + +using namespace Tango::TCC::CardDetector; + +CardDetectionResult::CardDetectionResult() +{ +} diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h new file mode 100644 index 000000000..68507e1f4 --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/CardDetectionResult.h @@ -0,0 +1,21 @@ +#pragma once + +using namespace System; +using namespace System::Drawing; + +namespace Tango +{ + namespace TCC + { + namespace CardDetector { + public ref class CardDetectionResult + { + public: + CardDetectionResult(); + property bool IsDetected; + property cli::array^ DetectedBitmap; + }; + } + } +} + diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj new file mode 100644 index 000000000..2ee6e7a2d --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {BB268536-9E03-46A4-9B11-6025211D87F0} + v4.6.1 + ManagedCProj + TangoTCCCardDetector + 10.0.17134.0 + + + + DynamicLibrary + true + v141 + true + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + DynamicLibrary + true + v141 + true + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)Build\TCC\Debug + $(ProjectDir);$(IncludePath) + + + true + + + false + $(SolutionDir)Build\TCC\Release + + + false + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\Tango.TCC.OpenCV;$(SolutionDir)..\External_Repositories\OpenCV;$(SolutionDir)..\External_Repositories\OpenCV\opencv_contrib-master\modules\aruco\include;%(AdditionalIncludeDirectories) + + + opencv_calib3d330d.lib;opencv_core330d.lib;opencv_features2d330d.lib;opencv_flann330d.lib;opencv_highgui330d.lib;opencv_imgproc330d.lib;opencv_imgcodecs330d.lib;opencv_ml330d.lib;opencv_objdetect330d.lib;opencv_photo330d.lib;opencv_stitching330d.lib;opencv_superres330d.lib;opencv_video330d.lib;opencv_videostab330d.lib;opencv_videoio330d.lib;opencv_aruco330d.lib;%(AdditionalDependencies) + $(SolutionDir)..\External_Repositories\OpenCV\bin;%(AdditionalLibraryDirectories) + + + + + Use + Level3 + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + + + + + + Use + Level3 + WIN32;NDEBUG;%(PreprocessorDefinitions) + + + + + + + + Use + Level3 + NDEBUG;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj.filters b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj.filters new file mode 100644 index 000000000..4e7e00d7b --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/Tango.TCC.CardDetector.vcxproj.filters @@ -0,0 +1,47 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.ico b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.ico new file mode 100644 index 000000000..789d7ccbb Binary files /dev/null and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.ico differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.rc b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.rc new file mode 100644 index 000000000..eab43064f Binary files /dev/null and b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/app.rc differ diff --git a/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/bitmap_image.hpp b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/bitmap_image.hpp new file mode 100644 index 000000000..cd4b450ee --- /dev/null +++ b/Software/Visual_Studio/TCC/Tango.TCC.CardDetector/bitmap_image.hpp @@ -0,0 +1,5105 @@ +/* + ***************************************************************************** + * * + * Platform Independent * + * Bitmap Image Reader Writer Library * + * * + * Author: Arash Partow - 2002 * + * URL: http://partow.net/programming/bitmap/index.html * + * * + * Note: This library only supports 24-bits per pixel bitmap format files. * + * * + * Copyright notice: * + * Free use of the Platform Independent Bitmap Image Reader Writer Library * + * is permitted under the guidelines and in accordance with the most current * + * version of the MIT License. * + * http://www.opensource.org/licenses/MIT * + * * + ***************************************************************************** +*/ + + +#ifndef INCLUDE_BITMAP_IMAGE_HPP +#define INCLUDE_BITMAP_IMAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class bitmap_image +{ +public: + + enum channel_mode { + rgb_mode = 0, + bgr_mode = 1 + }; + + enum color_plane { + blue_plane = 0, + green_plane = 1, + red_plane = 2 + }; + + struct rgb_t + { + unsigned char red; + unsigned char green; + unsigned char blue; + }; + + bitmap_image() + : file_name_(""), + width_(0), + height_(0), + row_increment_(0), + bytes_per_pixel_(3), + channel_mode_(bgr_mode) + {} + + bitmap_image(const std::string& filename) + : file_name_(filename), + width_(0), + height_(0), + row_increment_(0), + bytes_per_pixel_(0), + channel_mode_(bgr_mode) + { + load_bitmap(); + } + + bitmap_image(unsigned char* data, size_t length) + : file_name_(""), + width_(0), + height_(0), + row_increment_(0), + bytes_per_pixel_(0), + channel_mode_(bgr_mode) + { + load_bitmap_from_memory(data, length); + } + + bitmap_image(const unsigned int width, const unsigned int height) + : file_name_(""), + width_(width), + height_(height), + row_increment_(0), + bytes_per_pixel_(3), + channel_mode_(bgr_mode) + { + create_bitmap(); + } + + bitmap_image(const bitmap_image& image) + : file_name_(image.file_name_), + width_(image.width_), + height_(image.height_), + row_increment_(0), + bytes_per_pixel_(3), + channel_mode_(bgr_mode) + { + create_bitmap(); + data_ = image.data_; + } + + bitmap_image& operator=(const bitmap_image& image) + { + if (this != &image) + { + file_name_ = image.file_name_; + bytes_per_pixel_ = image.bytes_per_pixel_; + width_ = image.width_; + height_ = image.height_; + row_increment_ = 0; + channel_mode_ = image.channel_mode_; + create_bitmap(); + data_ = image.data_; + } + + return *this; + } + + inline bool operator!() + { + return (data_.size() == 0) || + (width_ == 0) || + (height_ == 0) || + (row_increment_ == 0); + } + + inline void clear(const unsigned char v = 0x00) + { + std::fill(data_.begin(), data_.end(), v); + } + + inline unsigned char red_channel(const unsigned int x, const unsigned int y) const + { + return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 2)]; + } + + inline unsigned char green_channel(const unsigned int x, const unsigned int y) const + { + return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 1)]; + } + + inline unsigned char blue_channel(const unsigned int x, const unsigned int y) const + { + return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 0)]; + } + + inline void red_channel(const unsigned int x, const unsigned int y, const unsigned char value) + { + data_[(y * row_increment_) + (x * bytes_per_pixel_ + 2)] = value; + } + + inline void green_channel(const unsigned int x, const unsigned int y, const unsigned char value) + { + data_[(y * row_increment_) + (x * bytes_per_pixel_ + 1)] = value; + } + + inline void blue_channel(const unsigned int x, const unsigned int y, const unsigned char value) + { + data_[(y * row_increment_) + (x * bytes_per_pixel_ + 0)] = value; + } + + inline unsigned char* row(unsigned int row_index) const + { + return const_cast(&data_[(row_index * row_increment_)]); + } + + inline void get_pixel(const unsigned int x, const unsigned int y, + unsigned char& red, + unsigned char& green, + unsigned char& blue) const + { + const unsigned int y_offset = y * row_increment_; + const unsigned int x_offset = x * bytes_per_pixel_; + const unsigned int offset = y_offset + x_offset; + + blue = data_[offset + 0]; + green = data_[offset + 1]; + red = data_[offset + 2]; + } + + template + inline void get_pixel(const unsigned int x, const unsigned int y, RGB& colour) const + { + get_pixel(x, y, colour.red, colour.green, colour.blue); + } + + inline rgb_t get_pixel(const unsigned int x, const unsigned int y) const + { + rgb_t colour; + get_pixel(x, y, colour.red, colour.green, colour.blue); + return colour; + } + + inline void set_pixel(const unsigned int x, const unsigned int y, + const unsigned char red, + const unsigned char green, + const unsigned char blue) + { + const unsigned int y_offset = y * row_increment_; + const unsigned int x_offset = x * bytes_per_pixel_; + const unsigned int offset = y_offset + x_offset; + + data_[offset + 0] = blue; + data_[offset + 1] = green; + data_[offset + 2] = red; + } + + template + inline void set_pixel(const unsigned int x, const unsigned int y, const RGB& colour) + { + set_pixel(x, y, colour.red, colour.green, colour.blue); + } + + inline bool copy_from(const bitmap_image& image) + { + if ( + (image.height_ != height_) || + (image.width_ != width_) + ) + { + return false; + } + + data_ = image.data_; + + return true; + } + + inline bool copy_from(const bitmap_image& source_image, + const unsigned int& x_offset, + const unsigned int& y_offset) + { + if ((x_offset + source_image.width_) > width_) { return false; } + if ((y_offset + source_image.height_) > height_) { return false; } + + for (unsigned int y = 0; y < source_image.height_; ++y) + { + unsigned char* itr1 = row(y + y_offset) + x_offset * bytes_per_pixel_; + const unsigned char* itr2 = source_image.row(y); + const unsigned char* itr2_end = itr2 + source_image.width_ * bytes_per_pixel_; + + std::copy(itr2, itr2_end, itr1); + } + + return true; + } + + inline bool region(const unsigned int& x, + const unsigned int& y, + const unsigned int& width, + const unsigned int& height, + bitmap_image& dest_image) const + { + if ((x + width) > width_) { return false; } + if ((y + height) > height_) { return false; } + + if ( + (dest_image.width_ < width_) || + (dest_image.height_ < height_) + ) + { + dest_image.setwidth_height(width, height); + } + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr1 = row(r + y) + x * bytes_per_pixel_; + unsigned char* itr1_end = itr1 + (width * bytes_per_pixel_); + unsigned char* itr2 = dest_image.row(r); + + std::copy(itr1, itr1_end, itr2); + } + + return true; + } + + inline bool roi_from_center(const unsigned int& cx, + const unsigned int& cy, + const unsigned int& width, + const unsigned int& height, + bitmap_image& dest_image) const + { + return region(cx - (width / 2), cy - (height / 2), + width, height, + dest_image); + } + + inline bool set_region(const unsigned int& x, + const unsigned int& y, + const unsigned int& width, + const unsigned int& height, + const unsigned char& value) + { + if ((x + width) > width_) { return false; } + if ((y + height) > height_) { return false; } + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr = row(r + y) + x * bytes_per_pixel_; + unsigned char* itr_end = itr + (width * bytes_per_pixel_); + + std::fill(itr, itr_end, value); + } + + return true; + } + + inline bool set_region(const unsigned int& x, + const unsigned int& y, + const unsigned int& width, + const unsigned int& height, + const color_plane color, + const unsigned char& value) + { + if ((x + width) > width_) { return false; } + if ((y + height) > height_) { return false; } + + const unsigned int color_plane_offset = offset(color); + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr = row(r + y) + x * bytes_per_pixel_ + color_plane_offset; + unsigned char* itr_end = itr + (width * bytes_per_pixel_); + + while (itr != itr_end) + { + *itr = value; + itr += bytes_per_pixel_; + } + } + + return true; + } + + inline bool set_region(const unsigned int& x, + const unsigned int& y, + const unsigned int& width, + const unsigned int& height, + const unsigned char& red, + const unsigned char& green, + const unsigned char& blue) + { + if ((x + width) > width_) { return false; } + if ((y + height) > height_) { return false; } + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr = row(r + y) + x * bytes_per_pixel_; + unsigned char* itr_end = itr + (width * bytes_per_pixel_); + + while (itr != itr_end) + { + *(itr++) = blue; + *(itr++) = green; + *(itr++) = red; + } + } + + return true; + } + + void reflective_image(bitmap_image& image, const bool include_diagnols = false) + { + image.setwidth_height(3 * width_, 3 * height_, true); + + image.copy_from(*this, width_, height_); + + vertical_flip(); + + image.copy_from(*this, width_, 0); + image.copy_from(*this, width_, 2 * height_); + + vertical_flip(); + horizontal_flip(); + + image.copy_from(*this, 0, height_); + image.copy_from(*this, 2 * width_, height_); + + horizontal_flip(); + + if (include_diagnols) + { + bitmap_image tile = *this; + + tile.vertical_flip(); + tile.horizontal_flip(); + + image.copy_from(tile, 0, 0); + image.copy_from(tile, 2 * width_, 0); + image.copy_from(tile, 2 * width_, 2 * height_); + image.copy_from(tile, 0, 2 * height_); + } + } + + inline unsigned int width() const + { + return width_; + } + + inline unsigned int height() const + { + return height_; + } + + inline unsigned int bytes_per_pixel() const + { + return bytes_per_pixel_; + } + + inline unsigned int pixel_count() const + { + return width_ * height_; + } + + inline void setwidth_height(const unsigned int width, + const unsigned int height, + const bool clear = false) + { + data_.clear(); + width_ = width; + height_ = height; + + create_bitmap(); + + if (clear) + { + std::fill(data_.begin(), data_.end(), static_cast(0x00)); + } + } + + void save_image(const std::string& file_name) const + { + std::ofstream stream(file_name.c_str(), std::ios::binary); + + if (!stream) + { + std::cerr << "bitmap_image::save_image(): Error - Could not open file " << file_name << " for writing!" << std::endl; + return; + } + + bitmap_information_header bih; + + bih.width = width_; + bih.height = height_; + bih.bit_count = static_cast(bytes_per_pixel_ << 3); + bih.clr_important = 0; + bih.clr_used = 0; + bih.compression = 0; + bih.planes = 1; + bih.size = bih.struct_size(); + bih.x_pels_per_meter = 0; + bih.y_pels_per_meter = 0; + bih.size_image = (((bih.width * bytes_per_pixel_) + 3) & 0x0000FFFC) * bih.height; + + bitmap_file_header bfh; + + bfh.type = 19778; + bfh.size = bfh.struct_size() + bih.struct_size() + bih.size_image; + bfh.reserved1 = 0; + bfh.reserved2 = 0; + bfh.off_bits = bih.struct_size() + bfh.struct_size(); + + write_bfh(stream, bfh); + write_bih(stream, bih); + + unsigned int padding = (4 - ((3 * width_) % 4)) % 4; + char padding_data[4] = { 0x00, 0x00, 0x00, 0x00 }; + + for (unsigned int i = 0; i < height_; ++i) + { + const unsigned char* data_ptr = &data_[(row_increment_ * (height_ - i - 1))]; + + stream.write(reinterpret_cast(data_ptr), sizeof(unsigned char) * bytes_per_pixel_ * width_); + stream.write(padding_data, padding); + } + + stream.close(); + } + + inline void set_all_ith_bits_low(const unsigned int bitr_index) + { + unsigned char mask = static_cast(~(1 << bitr_index)); + + for (unsigned char* itr = data(); itr != end(); ++itr) + { + *itr &= mask; + } + } + + inline void set_all_ith_bits_high(const unsigned int bitr_index) + { + unsigned char mask = static_cast(1 << bitr_index); + + for (unsigned char* itr = data(); itr != end(); ++itr) + { + *itr |= mask; + } + } + + inline void set_all_ith_channels(const unsigned int& channel, const unsigned char& value) + { + for (unsigned char* itr = (data() + channel); itr < end(); itr += bytes_per_pixel_) + { + *itr = value; + } + } + + inline void set_channel(const color_plane color, const unsigned char& value) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + *itr = value; + } + } + + inline void ror_channel(const color_plane color, const unsigned int& ror) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + *itr = static_cast(((*itr) >> ror) | ((*itr) << (8 - ror))); + } + } + + inline void set_all_channels(const unsigned char& value) + { + for (unsigned char* itr = data(); itr < end(); ) + { + *(itr++) = value; + } + } + + inline void set_all_channels(const unsigned char& r_value, + const unsigned char& g_value, + const unsigned char& b_value) + { + for (unsigned char* itr = (data() + 0); itr < end(); itr += bytes_per_pixel_) + { + *(itr + 0) = b_value; + *(itr + 1) = g_value; + *(itr + 2) = r_value; + } + } + + inline void invert_color_planes() + { + for (unsigned char* itr = data(); itr < end(); *itr = ~(*itr), ++itr); + } + + inline void add_to_color_plane(const color_plane color, const unsigned char& value) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + (*itr) += value; + } + } + + inline void convert_to_grayscale() + { + double r_scaler = 0.299; + double g_scaler = 0.587; + double b_scaler = 0.114; + + if (rgb_mode == channel_mode_) + { + std::swap(r_scaler, b_scaler); + } + + for (unsigned char* itr = data(); itr < end(); ) + { + unsigned char gray_value = static_cast + ( + (r_scaler * (*(itr + 2))) + + (g_scaler * (*(itr + 1))) + + (b_scaler * (*(itr + 0))) + ); + + *(itr++) = gray_value; + *(itr++) = gray_value; + *(itr++) = gray_value; + } + } + + inline const unsigned char* data() const + { + return data_.data(); + } + + inline unsigned char* data() + { + return const_cast(data_.data()); + } + + inline void bgr_to_rgb() + { + if ((bgr_mode == channel_mode_) && (3 == bytes_per_pixel_)) + { + reverse_channels(); + channel_mode_ = rgb_mode; + } + } + + inline void rgb_to_bgr() + { + if ((rgb_mode == channel_mode_) && (3 == bytes_per_pixel_)) + { + reverse_channels(); + channel_mode_ = bgr_mode; + } + } + + inline void reverse() + { + unsigned char* itr1 = data(); + unsigned char* itr2 = end() - bytes_per_pixel_; + + while (itr1 < itr2) + { + for (std::size_t i = 0; i < bytes_per_pixel_; ++i) + { + unsigned char* citr1 = itr1 + i; + unsigned char* citr2 = itr2 + i; + + std::swap(*citr1, *citr2); + } + + itr1 += bytes_per_pixel_; + itr2 -= bytes_per_pixel_; + } + } + + inline void horizontal_flip() + { + for (unsigned int y = 0; y < height_; ++y) + { + unsigned char* itr1 = row(y); + unsigned char* itr2 = itr1 + row_increment_ - bytes_per_pixel_; + + while (itr1 < itr2) + { + for (unsigned int i = 0; i < bytes_per_pixel_; ++i) + { + unsigned char* p1 = (itr1 + i); + unsigned char* p2 = (itr2 + i); + + std::swap(*p1, *p2); + } + + itr1 += bytes_per_pixel_; + itr2 -= bytes_per_pixel_; + } + } + } + + inline void vertical_flip() + { + for (unsigned int y = 0; y < (height_ / 2); ++y) + { + unsigned char* itr1 = row(y); + unsigned char* itr2 = row(height_ - y - 1); + + for (std::size_t x = 0; x < row_increment_; ++x) + { + std::swap(*(itr1 + x), *(itr2 + x)); + } + } + } + + inline void export_color_plane(const color_plane color, unsigned char* image) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); ++image, itr += bytes_per_pixel_) + { + (*image) = (*itr); + } + } + + inline void export_color_plane(const color_plane color, bitmap_image& image) + { + if ( + (width_ != image.width_) || + (height_ != image.height_) + ) + { + image.setwidth_height(width_, height_); + } + + image.clear(); + + unsigned char* itr1 = (data() + offset(color)); + unsigned char* itr1_end = end(); + unsigned char* itr2 = (image.data() + offset(color)); + + while (itr1 < itr1_end) + { + (*itr2) = (*itr1); + + itr1 += bytes_per_pixel_; + itr2 += bytes_per_pixel_; + } + } + + inline void export_response_image(const color_plane color, double* response_image) + { + double* resp_itr = response_image; + + for (unsigned char* itr = (data() + offset(color)); itr < end(); ++response_image, itr += bytes_per_pixel_) + { + *(resp_itr++) = (1.0 * (*itr)) / 256.0; + } + } + + inline void export_gray_scale_response_image(double* response_image) const + { + double* resp_itr = response_image; + + for (const unsigned char* itr = data(); itr < end(); itr += bytes_per_pixel_) + { + unsigned char gray_value = static_cast + ( + (0.299 * (*(itr + 2))) + + (0.587 * (*(itr + 1))) + + (0.114 * (*(itr + 0))) + ); + + *(resp_itr++) = (1.0 * gray_value) / 256.0; + } + } + + inline void export_rgb(double* red, double* green, double* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue) = (1.0 * (*(itr++))) / 256.0; + (*green) = (1.0 * (*(itr++))) / 256.0; + (*red) = (1.0 * (*(itr++))) / 256.0; + } + } + + inline void export_rgb(float* red, float* green, float* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue) = (1.0f * (*(itr++))) / 256.0f; + (*green) = (1.0f * (*(itr++))) / 256.0f; + (*red) = (1.0f * (*(itr++))) / 256.0f; + } + } + + inline void export_rgb(unsigned char* red, unsigned char* green, unsigned char* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue) = *(itr++); + (*green) = *(itr++); + (*red) = *(itr++); + } + } + + inline void export_ycbcr(double* y, double* cb, double* cr) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++y, ++cb, ++cr) + { + const double blue = (1.0 * (*(itr++))); + const double green = (1.0 * (*(itr++))); + const double red = (1.0 * (*(itr++))); + + (*y) = clamp(16.0 + (1.0 / 256.0) * (65.738 * red + 129.057 * green + 25.064 * blue), 1.0, 254); + (*cb) = clamp(128.0 + (1.0 / 256.0) * (-37.945 * red - 74.494 * green + 112.439 * blue), 1.0, 254); + (*cr) = clamp(128.0 + (1.0 / 256.0) * (112.439 * red - 94.154 * green - 18.285 * blue), 1.0, 254); + } + } + + inline void export_rgb_normal(double* red, double* green, double* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue) = (1.0 * (*(itr++))); + (*green) = (1.0 * (*(itr++))); + (*red) = (1.0 * (*(itr++))); + } + } + + inline void export_rgb_normal(float* red, float* green, float* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue) = (1.0f * (*(itr++))); + (*green) = (1.0f * (*(itr++))); + (*red) = (1.0f * (*(itr++))); + } + } + + inline void import_rgb(double* red, double* green, double* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(256.0 * (*blue)); + *(itr++) = static_cast(256.0 * (*green)); + *(itr++) = static_cast(256.0 * (*red)); + } + } + + inline void import_rgb(float* red, float* green, float* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(256.0f * (*blue)); + *(itr++) = static_cast(256.0f * (*green)); + *(itr++) = static_cast(256.0f * (*red)); + } + } + + inline void import_rgb(unsigned char* red, unsigned char* green, unsigned char* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = (*blue); + *(itr++) = (*green); + *(itr++) = (*red); + } + } + + inline void import_ycbcr(double* y, double* cb, double* cr) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++y, ++cb, ++cr) + { + double y_ = (*y); + double cb_ = (*cb); + double cr_ = (*cr); + + *(itr++) = static_cast(clamp((298.082 * y_ + 516.412 * cb_) / 256.0 - 276.836, 0.0, 255.0)); + *(itr++) = static_cast(clamp((298.082 * y_ - 100.291 * cb_ - 208.120 * cr_) / 256.0 + 135.576, 0.0, 255.0)); + *(itr++) = static_cast(clamp((298.082 * y_ + 408.583 * cr_) / 256.0 - 222.921, 0.0, 255.0)); + } + } + + inline void import_gray_scale_clamped(double* gray) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++gray) + { + unsigned char c = static_cast(clamp(256.0 * (*gray), 0.0, 255.0)); + + *(itr + 0) = c; + *(itr + 1) = c; + *(itr + 2) = c; + + itr += 3; + } + } + + inline void import_rgb_clamped(double* red, double* green, double* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(clamp(256.0 * (*blue), 0.0, 255.0)); + *(itr++) = static_cast(clamp(256.0 * (*green), 0.0, 255.0)); + *(itr++) = static_cast(clamp(256.0 * (*red), 0.0, 255.0)); + } + } + + inline void import_rgb_clamped(float* red, float* green, float* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(clamp(256.0f * (*blue), 0.0, 255.0)); + *(itr++) = static_cast(clamp(256.0f * (*green), 0.0, 255.0)); + *(itr++) = static_cast(clamp(256.0f * (*red), 0.0, 255.0)); + } + } + + inline void import_rgb_normal(double* red, double* green, double* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(*blue); + *(itr++) = static_cast(*green); + *(itr++) = static_cast(*red); + } + } + + inline void import_rgb_normal(float* red, float* green, float* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(*blue); + *(itr++) = static_cast(*green); + *(itr++) = static_cast(*red); + } + } + + inline void subsample(bitmap_image& dest) const + { + /* + Half sub-sample of original image. + */ + unsigned int w = 0; + unsigned int h = 0; + + bool odd_width = false; + bool odd_height = false; + + if (0 == (width_ % 2)) + w = width_ / 2; + else + { + w = 1 + (width_ / 2); + odd_width = true; + } + + if (0 == (height_ % 2)) + h = height_ / 2; + else + { + h = 1 + (height_ / 2); + odd_height = true; + } + + unsigned int horizontal_upper = (odd_width) ? (w - 1) : w; + unsigned int vertical_upper = (odd_height) ? (h - 1) : h; + + dest.setwidth_height(w, h); + dest.clear(); + + unsigned char* s_itr[3]; + const unsigned char* itr1[3]; + const unsigned char* itr2[3]; + + s_itr[0] = dest.data() + 0; + s_itr[1] = dest.data() + 1; + s_itr[2] = dest.data() + 2; + + itr1[0] = data() + 0; + itr1[1] = data() + 1; + itr1[2] = data() + 2; + + itr2[0] = data() + row_increment_ + 0; + itr2[1] = data() + row_increment_ + 1; + itr2[2] = data() + row_increment_ + 2; + + unsigned int total = 0; + + for (unsigned int j = 0; j < vertical_upper; ++j) + { + for (unsigned int i = 0; i < horizontal_upper; ++i) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + total = 0; + total += *(itr1[k]); + total += *(itr1[k]); + total += *(itr2[k]); + total += *(itr2[k]); + + itr1[k] += bytes_per_pixel_; + itr1[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + + *(s_itr[k]) = static_cast(total >> 2); + } + } + + if (odd_width) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + total = 0; + total += *(itr1[k]); + total += *(itr2[k]); + + itr1[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + + *(s_itr[k]) = static_cast(total >> 1); + } + } + + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + itr1[k] += row_increment_; + } + + if (j != (vertical_upper - 1)) + { + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + itr2[k] += row_increment_; + } + } + } + + if (odd_height) + { + for (unsigned int i = 0; i < horizontal_upper; ++i) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + total = 0; + total += *(itr1[k]); + total += *(itr2[k]); + + itr1[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + + *(s_itr[k]) = static_cast(total >> 1); + } + } + + if (odd_width) + { + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + (*(s_itr[k])) = *(itr1[k]); + } + } + } + } + + inline void upsample(bitmap_image& dest) const + { + /* + 2x up-sample of original image. + */ + + dest.setwidth_height(2 * width_, 2 * height_); + dest.clear(); + + const unsigned char* s_itr[3]; + unsigned char* itr1[3]; + unsigned char* itr2[3]; + + s_itr[0] = data() + 0; + s_itr[1] = data() + 1; + s_itr[2] = data() + 2; + + itr1[0] = dest.data() + 0; + itr1[1] = dest.data() + 1; + itr1[2] = dest.data() + 2; + + itr2[0] = dest.data() + dest.row_increment_ + 0; + itr2[1] = dest.data() + dest.row_increment_ + 1; + itr2[2] = dest.data() + dest.row_increment_ + 2; + + for (unsigned int j = 0; j < height_; ++j) + { + for (unsigned int i = 0; i < width_; ++i) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + *(itr1[k]) = *(s_itr[k]); itr1[k] += bytes_per_pixel_; + *(itr1[k]) = *(s_itr[k]); itr1[k] += bytes_per_pixel_; + + *(itr2[k]) = *(s_itr[k]); itr2[k] += bytes_per_pixel_; + *(itr2[k]) = *(s_itr[k]); itr2[k] += bytes_per_pixel_; + } + } + + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + itr1[k] += dest.row_increment_; + itr2[k] += dest.row_increment_; + } + } + } + + inline void alpha_blend(const double& alpha, const bitmap_image& image) + { + if ( + (image.width_ != width_) || + (image.height_ != height_) + ) + { + return; + } + + if ((alpha < 0.0) || (alpha > 1.0)) + { + return; + } + + unsigned char* itr1 = data(); + const unsigned char* itr1_end = end(); + const unsigned char* itr2 = image.data(); + + double alpha_compliment = 1.0 - alpha; + + while (itr1 != itr1_end) + { + *(itr1) = static_cast((alpha * (*itr2)) + (alpha_compliment * (*itr1))); + ++itr1; + ++itr2; + } + } + + inline double psnr(const bitmap_image& image) + { + if ( + (image.width_ != width_) || + (image.height_ != height_) + ) + { + return 0.0; + } + + const unsigned char* itr1 = data(); + const unsigned char* itr2 = image.data(); + + double mse = 0.0; + + while (itr1 != end()) + { + const double v = (static_cast(*itr1) - static_cast(*itr2)); + + mse += v * v; + ++itr1; + ++itr2; + } + + if (mse <= 0.0000001) + { + return 1000000.0; + } + else + { + mse /= (3.0 * width_ * height_); + + return 20.0 * std::log10(255.0 / std::sqrt(mse)); + } + } + + inline double psnr(const unsigned int& x, + const unsigned int& y, + const bitmap_image& image) + { + if ((x + image.width()) > width_) { return 0.0; } + if ((y + image.height()) > height_) { return 0.0; } + + double mse = 0.0; + + const unsigned int height = image.height(); + const unsigned int width = image.width(); + + for (unsigned int r = 0; r < height; ++r) + { + const unsigned char* itr1 = row(r + y) + x * bytes_per_pixel_; + const unsigned char* itr1_end = itr1 + (width * bytes_per_pixel_); + const unsigned char* itr2 = image.row(r); + + while (itr1 != itr1_end) + { + double v = (static_cast(*itr1) - static_cast(*itr2)); + mse += v * v; + ++itr1; + ++itr2; + } + } + + if (mse <= 0.0000001) + { + return 1000000.0; + } + else + { + mse /= (3.0 * image.width() * image.height()); + return 20.0 * std::log10(255.0 / std::sqrt(mse)); + } + } + + inline void histogram(const color_plane color, double hist[256]) const + { + std::fill(hist, hist + 256, 0.0); + + for (const unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + ++hist[(*itr)]; + } + } + + inline void histogram_normalized(const color_plane color, double hist[256]) const + { + histogram(color, hist); + + double* h_itr = hist; + const double* h_end = hist + 256; + const double pixel_count = static_cast(width_ * height_); + + while (h_end != h_itr) + { + *(h_itr++) /= pixel_count; + } + } + + inline unsigned int offset(const color_plane color) const + { + switch (channel_mode_) + { + case rgb_mode: { + switch (color) + { + case red_plane: return 0; + case green_plane: return 1; + case blue_plane: return 2; + default: return std::numeric_limits::max(); + } + } + + case bgr_mode: { + switch (color) + { + case red_plane: return 2; + case green_plane: return 1; + case blue_plane: return 0; + default: return std::numeric_limits::max(); + } + } + + default: return std::numeric_limits::max(); + } + } + + inline void incremental() + { + unsigned char current_color = 0; + + for (unsigned char* itr = data(); itr < end();) + { + (*itr++) = (current_color); + (*itr++) = (current_color); + (*itr++) = (current_color); + + ++current_color; + } + } + + inline void reverse_channels() + { + if (3 != bytes_per_pixel_) + return; + + for (unsigned char* itr = data(); itr < end(); itr += bytes_per_pixel_) + { + std::swap(*(itr + 0), *(itr + 2)); + } + } + +private: + + inline const unsigned char* end() const + { + return data_.data() + data_.size(); + } + + inline unsigned char* end() + { + return const_cast(data() + data_.size()); + } + + struct bitmap_file_header + { + unsigned short type; + unsigned int size; + unsigned short reserved1; + unsigned short reserved2; + unsigned int off_bits; + + unsigned int struct_size() const + { + return sizeof(type) + + sizeof(size) + + sizeof(reserved1) + + sizeof(reserved2) + + sizeof(off_bits); + } + + void clear() + { + std::memset(this, 0x00, sizeof(bitmap_file_header)); + } + }; + + struct bitmap_information_header + { + unsigned int size; + unsigned int width; + unsigned int height; + unsigned short planes; + unsigned short bit_count; + unsigned int compression; + unsigned int size_image; + unsigned int x_pels_per_meter; + unsigned int y_pels_per_meter; + unsigned int clr_used; + unsigned int clr_important; + + unsigned int struct_size() const + { + return sizeof(size) + + sizeof(width) + + sizeof(height) + + sizeof(planes) + + sizeof(bit_count) + + sizeof(compression) + + sizeof(size_image) + + sizeof(x_pels_per_meter) + + sizeof(y_pels_per_meter) + + sizeof(clr_used) + + sizeof(clr_important); + } + + void clear() + { + std::memset(this, 0x00, sizeof(bitmap_information_header)); + } + }; + + inline bool big_endian() const + { + unsigned int v = 0x01; + + return (1 != reinterpret_cast(&v)[0]); + } + + inline unsigned short flip(const unsigned short& v) const + { + return ((v >> 8) | (v << 8)); + } + + inline unsigned int flip(const unsigned int& v) const + { + return ( + ((v & 0xFF000000) >> 0x18) | + ((v & 0x000000FF) << 0x18) | + ((v & 0x00FF0000) >> 0x08) | + ((v & 0x0000FF00) << 0x08) + ); + } + + template + inline void read_from_stream(std::istream& stream, T& t) + { + stream.read(reinterpret_cast(&t), sizeof(T)); + } + + template + inline void write_to_stream(std::ofstream& stream, const T& t) const + { + stream.write(reinterpret_cast(&t), sizeof(T)); + } + + inline void read_bfh(std::istream& stream, bitmap_file_header& bfh) + { + read_from_stream(stream, bfh.type); + read_from_stream(stream, bfh.size); + read_from_stream(stream, bfh.reserved1); + read_from_stream(stream, bfh.reserved2); + read_from_stream(stream, bfh.off_bits); + + if (big_endian()) + { + bfh.type = flip(bfh.type); + bfh.size = flip(bfh.size); + bfh.reserved1 = flip(bfh.reserved1); + bfh.reserved2 = flip(bfh.reserved2); + bfh.off_bits = flip(bfh.off_bits); + } + } + + inline void write_bfh(std::ofstream& stream, const bitmap_file_header& bfh) const + { + if (big_endian()) + { + write_to_stream(stream, flip(bfh.type)); + write_to_stream(stream, flip(bfh.size)); + write_to_stream(stream, flip(bfh.reserved1)); + write_to_stream(stream, flip(bfh.reserved2)); + write_to_stream(stream, flip(bfh.off_bits)); + } + else + { + write_to_stream(stream, bfh.type); + write_to_stream(stream, bfh.size); + write_to_stream(stream, bfh.reserved1); + write_to_stream(stream, bfh.reserved2); + write_to_stream(stream, bfh.off_bits); + } + } + + inline void read_bih(std::istream& stream, bitmap_information_header& bih) + { + read_from_stream(stream, bih.size); + read_from_stream(stream, bih.width); + read_from_stream(stream, bih.height); + read_from_stream(stream, bih.planes); + read_from_stream(stream, bih.bit_count); + read_from_stream(stream, bih.compression); + read_from_stream(stream, bih.size_image); + read_from_stream(stream, bih.x_pels_per_meter); + read_from_stream(stream, bih.y_pels_per_meter); + read_from_stream(stream, bih.clr_used); + read_from_stream(stream, bih.clr_important); + + if (big_endian()) + { + bih.size = flip(bih.size); + bih.width = flip(bih.width); + bih.height = flip(bih.height); + bih.planes = flip(bih.planes); + bih.bit_count = flip(bih.bit_count); + bih.compression = flip(bih.compression); + bih.size_image = flip(bih.size_image); + bih.x_pels_per_meter = flip(bih.x_pels_per_meter); + bih.y_pels_per_meter = flip(bih.y_pels_per_meter); + bih.clr_used = flip(bih.clr_used); + bih.clr_important = flip(bih.clr_important); + } + } + + inline void write_bih(std::ofstream& stream, const bitmap_information_header& bih) const + { + if (big_endian()) + { + write_to_stream(stream, flip(bih.size)); + write_to_stream(stream, flip(bih.width)); + write_to_stream(stream, flip(bih.height)); + write_to_stream(stream, flip(bih.planes)); + write_to_stream(stream, flip(bih.bit_count)); + write_to_stream(stream, flip(bih.compression)); + write_to_stream(stream, flip(bih.size_image)); + write_to_stream(stream, flip(bih.x_pels_per_meter)); + write_to_stream(stream, flip(bih.y_pels_per_meter)); + write_to_stream(stream, flip(bih.clr_used)); + write_to_stream(stream, flip(bih.clr_important)); + } + else + { + write_to_stream(stream, bih.size); + write_to_stream(stream, bih.width); + write_to_stream(stream, bih.height); + write_to_stream(stream, bih.planes); + write_to_stream(stream, bih.bit_count); + write_to_stream(stream, bih.compression); + write_to_stream(stream, bih.size_image); + write_to_stream(stream, bih.x_pels_per_meter); + write_to_stream(stream, bih.y_pels_per_meter); + write_to_stream(stream, bih.clr_used); + write_to_stream(stream, bih.clr_important); + } + } + + inline std::size_t file_size(const std::string& file_name) const + { + std::ifstream file(file_name.c_str(), std::ios::in | std::ios::binary); + if (!file) return 0; + file.seekg(0, std::ios::end); + return static_cast(file.tellg()); + } + + void create_bitmap() + { + row_increment_ = width_ * bytes_per_pixel_; + data_.resize(height_ * row_increment_); + } + + void load_bitmap() + { + std::ifstream stream(file_name_.c_str(), std::ios::binary); + + if (!stream) + { + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - file " << file_name_ << " not found!" << std::endl; + return; + } + + width_ = 0; + height_ = 0; + + bitmap_file_header bfh; + bitmap_information_header bih; + + bfh.clear(); + bih.clear(); + + read_bfh(stream, bfh); + read_bih(stream, bih); + + if (bfh.type != 19778) + { + bfh.clear(); + bih.clear(); + + stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Invalid type value " << bfh.type << " expected 19778." << std::endl; + return; + } + + if (bih.bit_count != 24) + { + bfh.clear(); + bih.clear(); + + stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Invalid bit depth " << bih.bit_count << " expected 24." << std::endl; + + return; + } + + if (bih.size != bih.struct_size()) + { + bfh.clear(); + bih.clear(); + + stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Invalid BIH size " << bih.size << " expected " << bih.struct_size() << std::endl; + + return; + } + + width_ = bih.width; + height_ = bih.height; + + bytes_per_pixel_ = bih.bit_count >> 3; + + unsigned int padding = (4 - ((3 * width_) % 4)) % 4; + char padding_data[4] = { 0,0,0,0 }; + + std::size_t bitmap_file_size = file_size(file_name_); + + std::size_t bitmap_logical_size = (height_ * width_ * bytes_per_pixel_) + + (height_ * padding) + + bih.struct_size() + + bfh.struct_size(); + + if (bitmap_file_size != bitmap_logical_size) + { + bfh.clear(); + bih.clear(); + + stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Mismatch between logical and physical sizes of bitmap. " << + "Logical: " << bitmap_logical_size << " " << + "Physical: " << bitmap_file_size << std::endl; + + return; + } + + create_bitmap(); + + for (unsigned int i = 0; i < height_; ++i) + { + unsigned char* data_ptr = row(height_ - i - 1); // read in inverted row order + + stream.read(reinterpret_cast(data_ptr), sizeof(char) * bytes_per_pixel_ * width_); + stream.read(padding_data, padding); + } + } + + void load_bitmap_from_memory(unsigned char* data, size_t length) + { + std::string str((char*)data, length); + std::stringstream s(str); + + std::istream& stream = s; + + if (!stream) + { + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - file " << file_name_ << " not found!" << std::endl; + return; + } + + width_ = 0; + height_ = 0; + + bitmap_file_header bfh; + bitmap_information_header bih; + + bfh.clear(); + bih.clear(); + + read_bfh(stream, bfh); + read_bih(stream, bih); + + if (bfh.type != 19778) + { + bfh.clear(); + bih.clear(); + + //stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Invalid type value " << bfh.type << " expected 19778." << std::endl; + return; + } + + if (bih.bit_count != 24) + { + bfh.clear(); + bih.clear(); + + //stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Invalid bit depth " << bih.bit_count << " expected 24." << std::endl; + + return; + } + + if (bih.size != bih.struct_size()) + { + bfh.clear(); + bih.clear(); + + //stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Invalid BIH size " << bih.size << " expected " << bih.struct_size() << std::endl; + + return; + } + + width_ = bih.width; + height_ = bih.height; + + bytes_per_pixel_ = bih.bit_count >> 3; + + unsigned int padding = (4 - ((3 * width_) % 4)) % 4; + char padding_data[4] = { 0,0,0,0 }; + + std::size_t bitmap_file_size = length; + + std::size_t bitmap_logical_size = (height_ * width_ * bytes_per_pixel_) + + (height_ * padding) + + bih.struct_size() + + bfh.struct_size(); + + if (bitmap_file_size != bitmap_logical_size) + { + bfh.clear(); + bih.clear(); + + //stream.close(); + + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - Mismatch between logical and physical sizes of bitmap. " << + "Logical: " << bitmap_logical_size << " " << + "Physical: " << bitmap_file_size << std::endl; + + return; + } + + create_bitmap(); + + for (unsigned int i = 0; i < height_; ++i) + { + unsigned char* data_ptr = row(height_ - i - 1); // read in inverted row order + + stream.read(reinterpret_cast(data_ptr), sizeof(char) * bytes_per_pixel_ * width_); + stream.read(padding_data, padding); + } + } + + template + inline T clamp(const T& v, const T& lower_range, const T& upper_range) const + { + if (v < lower_range) + return lower_range; + else if (v > upper_range) + return upper_range; + else + return v; + } + + std::string file_name_; + unsigned int width_; + unsigned int height_; + unsigned int row_increment_; + unsigned int bytes_per_pixel_; + channel_mode channel_mode_; + std::vector data_; +}; + +typedef bitmap_image::rgb_t rgb_t; + +inline bool operator==(const rgb_t& c0, const rgb_t& c1) +{ + return (c0.red == c1.red) && + (c0.green == c1.green) && + (c0.blue == c1.blue); +} + +inline bool operator!=(const rgb_t& c0, const rgb_t& c1) +{ + return (c0.red != c1.red) || + (c0.green != c1.green) || + (c0.blue != c1.blue); +} + +inline std::size_t hamming_distance(const rgb_t& c0, const rgb_t& c1) +{ + std::size_t result = 0; + + if (c0.red != c1.red) ++result; + if (c0.green != c1.green) ++result; + if (c0.blue != c1.blue) ++result; + + return result; +} + +inline rgb_t make_colour(const unsigned int& red, const unsigned int& green, const unsigned int& blue) +{ + rgb_t result; + + result.red = static_cast(red); + result.green = static_cast(green); + result.blue = static_cast(blue); + + return result; +} + +template +inline void generate_colours(const std::size_t& steps, const rgb_t c0, const rgb_t& c1, OutputIterator out) +{ + double dr = ((double)c1.red - (double)c0.red) / steps; + double dg = ((double)c1.green - (double)c0.green) / steps; + double db = ((double)c1.blue - (double)c0.blue) / steps; + + for (std::size_t i = 0; i < steps; ++i) + { + rgb_t c; + + c.red = static_cast(c0.red + (i * dr)); + c.green = static_cast(c0.green + (i * dg)); + c.blue = static_cast(c0.blue + (i * db)); + + *(out++) = c; + } +} + +template +inline std::size_t convert_rsp_to_image(const ResponseImage& resp_image, const Palette& palette, bitmap_image& image) +{ + if ( + (resp_image.width() > image.width()) || + (resp_image.height() > image.height()) + ) + return 0; + + for (std::size_t y = 0; y < resp_image.height(); ++y) + { + for (std::size_t x = 0; x < resp_image.width(); ++x) + { + const double v = resp_image(x, y); + + unsigned int index = static_cast((v < 0) ? 0 : v > (palette.size()) ? (palette.size() - 1) : v); + + image.set_pixel(x, y, palette[index]); + } + } + + return (resp_image.width() * resp_image.height()); +} + +inline void rgb_to_ycbcr(const unsigned int& length, double* red, double* green, double* blue, + double* y, double* cb, double* cr) +{ + unsigned int i = 0; + + while (i < length) + { + (*y) = 16.0 + (65.481 * (*red) + 128.553 * (*green) + 24.966 * (*blue)); + (*cb) = 128.0 + (-37.797 * (*red) + -74.203 * (*green) + 112.000 * (*blue)); + (*cr) = 128.0 + (112.000 * (*red) + -93.786 * (*green) - 18.214 * (*blue)); + + ++i; + ++red; ++green; ++blue; + ++y; ++cb; ++cr; + } +} + +inline void ycbcr_to_rgb(const unsigned int& length, double* y, double* cb, double* cr, + double* red, double* green, double* blue) +{ + unsigned int i = 0; + + while (i < length) + { + double y_ = (*y) - 16.0; + double cb_ = (*cb) - 128.0; + double cr_ = (*cr) - 128.0; + + (*red) = 0.000456621 * y_ + 0.00625893 * cr_; + (*green) = 0.000456621 * y_ - 0.00153632 * cb_ - 0.00318811 * cr_; + (*blue) = 0.000456621 * y_ + 0.00791071 * cb_; + + ++i; + ++red; ++green; ++blue; + ++y; ++cb; ++cr; + } +} + +inline void subsample(const unsigned int& width, + const unsigned int& height, + const double* source, + unsigned int& w, + unsigned int& h, + double*& dest) +{ + /* Single channel. */ + + w = 0; + h = 0; + + bool odd_width = false; + bool odd_height = false; + + if (0 == (width % 2)) + w = width / 2; + else + { + w = 1 + (width / 2); + odd_width = true; + } + + if (0 == (height % 2)) + h = height / 2; + else + { + h = 1 + (height / 2); + odd_height = true; + } + + unsigned int horizontal_upper = (odd_width) ? w - 1 : w; + unsigned int vertical_upper = (odd_height) ? h - 1 : h; + + dest = new double[w * h]; + + double* s_itr = dest; + const double* itr1 = source; + const double* itr2 = source + width; + + for (unsigned int j = 0; j < vertical_upper; ++j) + { + for (unsigned int i = 0; i < horizontal_upper; ++i, ++s_itr) + { + (*s_itr) = *(itr1++); + (*s_itr) += *(itr1++); + (*s_itr) += *(itr2++); + (*s_itr) += *(itr2++); + (*s_itr) /= 4.0; + } + + if (odd_width) + { + (*(s_itr++)) = ((*itr1++) + (*itr2++)) / 2.0; + } + + itr1 += width; + + if (j != (vertical_upper - 1)) + { + itr2 += width; + } + } + + if (odd_height) + { + for (unsigned int i = 0; i < horizontal_upper; ++i, ++s_itr) + { + (*s_itr) += (*(itr1++)); + (*s_itr) += (*(itr1++)); + (*s_itr) /= 2.0; + } + + if (odd_width) + { + (*(s_itr++)) = (*itr1); + } + } +} + +inline void upsample(const unsigned int& width, + const unsigned int& height, + const double* source, + unsigned int& w, + unsigned int& h, + double*& dest) +{ + /* Single channel. */ + + w = 2 * width; + h = 2 * height; + + dest = new double[w * h]; + + const double* s_itr = source; + double* itr1 = dest; + double* itr2 = dest + w; + + for (unsigned int j = 0; j < height; ++j) + { + for (unsigned int i = 0; i < width; ++i, ++s_itr) + { + *(itr1++) = (*s_itr); + *(itr1++) = (*s_itr); + *(itr2++) = (*s_itr); + *(itr2++) = (*s_itr); + } + + itr1 += w; + itr2 += w; + } +} + +inline void checkered_pattern(const unsigned int x_width, + const unsigned int y_width, + const unsigned char value, + const bitmap_image::color_plane color, + bitmap_image& image) +{ + if ( + (x_width >= image.width()) || + (y_width >= image.height()) + ) + { + return; + } + + bool setter_x = false; + bool setter_y = true; + + const unsigned int color_plane_offset = image.offset(color); + const unsigned int height = image.height(); + const unsigned int width = image.width(); + + for (unsigned int y = 0; y < height; ++y) + { + if (0 == (y % y_width)) + { + setter_y = !setter_y; + } + + unsigned char* row = image.row(y) + color_plane_offset; + + for (unsigned int x = 0; x < width; ++x, row += image.bytes_per_pixel()) + { + if (0 == (x % x_width)) + { + setter_x = !setter_x; + } + + if (setter_x ^ setter_y) + { + *row = value; + } + } + } +} + +inline void checkered_pattern(const unsigned int x_width, + const unsigned int y_width, + const unsigned char red, + const unsigned char green, + const unsigned char blue, + bitmap_image& image) +{ + if ( + (x_width >= image.width()) || + (y_width >= image.height()) + ) + { + return; + } + + bool setter_x = false; + bool setter_y = true; + + const unsigned int height = image.height(); + const unsigned int width = image.width(); + + for (unsigned int y = 0; y < height; ++y) + { + if (0 == (y % y_width)) + { + setter_y = !setter_y; + } + + unsigned char* row = image.row(y); + + for (unsigned int x = 0; x < width; ++x, row += image.bytes_per_pixel()) + { + if (0 == (x % x_width)) + { + setter_x = !setter_x; + } + + if (setter_x ^ setter_y) + { + *(row + 0) = blue; + *(row + 1) = green; + *(row + 2) = red; + } + } + } +} + +inline void plasma(bitmap_image& image, + const double& x, const double& y, + const double& width, const double& height, + const double& c1, const double& c2, + const double& c3, const double& c4, + const double& roughness = 3.0, + const rgb_t colormap[] = 0) +{ + // Note: c1,c2,c3,c4 -> [0.0,1.0] + + const double half_width = (width / 2.0); + const double half_height = (height / 2.0); + + if ((width >= 1.0) || (height >= 1.0)) + { + const double corner1 = (c1 + c2) / 2.0; + const double corner2 = (c2 + c3) / 2.0; + const double corner3 = (c3 + c4) / 2.0; + const double corner4 = (c4 + c1) / 2.0; + double center = (c1 + c2 + c3 + c4) / 4.0 + + ((1.0 * ::rand() / (1.0 * RAND_MAX)) - 0.5) * // should use a better rng + ((1.0 * half_width + half_height) / (image.width() + image.height()) * roughness); + + center = std::min(std::max(0.0, center), 1.0); + + plasma(image, x, y, half_width, half_height, c1, corner1, center, corner4, roughness, colormap); + plasma(image, x + half_width, y, half_width, half_height, corner1, c2, corner2, center, roughness, colormap); + plasma(image, x + half_width, y + half_height, half_width, half_height, center, corner2, c3, corner3, roughness, colormap); + plasma(image, x, y + half_height, half_width, half_height, corner4, center, corner3, c4, roughness, colormap); + } + else + { + rgb_t color = colormap[static_cast(1000.0 * ((c1 + c2 + c3 + c4) / 4.0)) % 1000]; + + image.set_pixel(static_cast(x), static_cast(y), color); + } +} + +inline void plasma(bitmap_image& image, + const double& c1, const double& c2, + const double& c3, const double& c4, + const double& roughness = 3.0, + const rgb_t colormap[] = 0) +{ + plasma + ( + image, 0, 0, image.width(), image.height(), + c1, c2, c3, c4, + roughness, colormap + ); +} + +inline double psnr_region(const unsigned int& x, const unsigned int& y, + const unsigned int& width, const unsigned int& height, + const bitmap_image& image1, const bitmap_image& image2) +{ + if ( + (image1.width() != image2.width()) || + (image1.height() != image2.height()) + ) + { + return 0.0; + } + + if ((x + width) > image1.width()) { return 0.0; } + if ((y + height) > image1.height()) { return 0.0; } + + double mse = 0.0; + + for (unsigned int r = 0; r < height; ++r) + { + const unsigned char* itr1 = image1.row(r + y) + x * image1.bytes_per_pixel(); + const unsigned char* itr1_end = itr1 + (width * image1.bytes_per_pixel()); + const unsigned char* itr2 = image2.row(r + y) + x * image2.bytes_per_pixel(); + + while (itr1 != itr1_end) + { + double v = (static_cast(*itr1) - static_cast(*itr2)); + mse += v * v; + ++itr1; + ++itr2; + } + } + + if (mse <= 0.0000001) + { + return 1000000.0; + } + else + { + mse /= (3.0 * width * height); + return 20.0 * std::log10(255.0 / std::sqrt(mse)); + } +} + +inline void hierarchical_psnr_r(const double& x, const double& y, + const double& width, const double& height, + const bitmap_image& image1, + bitmap_image& image2, + const double& threshold, + const rgb_t colormap[]) +{ + if ((width <= 4.0) || (height <= 4.0)) + { + const double psnr = psnr_region + ( + static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height), + image1, image2 + ); + + if (psnr < threshold) + { + rgb_t c = colormap[static_cast(1000.0 * (1.0 - (psnr / threshold)))]; + + image2.set_region + ( + static_cast(x), + static_cast(y), + static_cast(width + 1), + static_cast(height + 1), + c.red, c.green, c.blue + ); + } + } + else + { + const double half_width = (width / 2.0); + const double half_height = (height / 2.0); + + hierarchical_psnr_r(x, y, half_width, half_height, image1, image2, threshold, colormap); + hierarchical_psnr_r(x + half_width, y, half_width, half_height, image1, image2, threshold, colormap); + hierarchical_psnr_r(x + half_width, y + half_height, half_width, half_height, image1, image2, threshold, colormap); + hierarchical_psnr_r(x, y + half_height, half_width, half_height, image1, image2, threshold, colormap); + } +} + +inline void hierarchical_psnr(bitmap_image& image1, bitmap_image& image2, const double threshold, const rgb_t colormap[]) +{ + if ( + (image1.width() != image2.width()) || + (image1.height() != image2.height()) + ) + { + return; + } + + const double psnr = psnr_region + ( + 0, 0, image1.width(), image1.height(), + image1, image2 + ); + + if (psnr < threshold) + { + hierarchical_psnr_r + ( + 0, 0, image1.width(), image1.height(), + image1, image2, + threshold, + colormap + ); + } +} + +class image_drawer +{ +public: + + image_drawer(bitmap_image& image) + : image_(image), + pen_width_(1), + pen_color_red_(0), + pen_color_green_(0), + pen_color_blue_(0) + {} + + void rectangle(int x1, int y1, int x2, int y2) + { + line_segment(x1, y1, x2, y1); + line_segment(x2, y1, x2, y2); + line_segment(x2, y2, x1, y2); + line_segment(x1, y2, x1, y1); + } + + void triangle(int x1, int y1, int x2, int y2, int x3, int y3) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x1, y1); + } + + void quadix(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x4, y4); + line_segment(x4, y4, x1, y1); + } + + void line_segment(int x1, int y1, int x2, int y2) + { + int steep = 0; + int sx = ((x2 - x1) > 0) ? 1 : -1; + int sy = ((y2 - y1) > 0) ? 1 : -1; + int dx = abs(x2 - x1); + int dy = abs(y2 - y1); + + if (dy > dx) + { + std::swap(x1, y1); + std::swap(dx, dy); + std::swap(sx, sy); + + steep = 1; + } + + int e = 2 * dy - dx; + + for (int i = 0; i < dx; ++i) + { + if (steep) + plot_pen_pixel(y1, x1); + else + plot_pen_pixel(x1, y1); + + while (e >= 0) + { + y1 += sy; + e -= (dx << 1); + } + + x1 += sx; + e += (dy << 1); + } + + plot_pen_pixel(x2, y2); + } + + void horiztonal_line_segment(int x1, int x2, int y) + { + if (x1 > x2) + { + std::swap(x1, x2); + } + + for (int i = 0; i < (x2 - x1); ++i) + { + plot_pen_pixel(x1 + i, y); + } + } + + void vertical_line_segment(int y1, int y2, int x) + { + if (y1 > y2) + { + std::swap(y1, y2); + } + + for (int i = 0; i < (y2 - y1); ++i) + { + plot_pen_pixel(x, y1 + i); + } + } + + void ellipse(int centerx, int centery, int a, int b) + { + int t1 = a * a; + int t2 = t1 << 1; + int t3 = t2 << 1; + int t4 = b * b; + int t5 = t4 << 1; + int t6 = t5 << 1; + int t7 = a * t5; + int t8 = t7 << 1; + int t9 = 0; + + int d1 = t2 - t7 + (t4 >> 1); + int d2 = (t1 >> 1) - t8 + t5; + int x = a; + int y = 0; + + int negative_tx = centerx - x; + int positive_tx = centerx + x; + int negative_ty = centery - y; + int positive_ty = centery + y; + + while (d2 < 0) + { + plot_pen_pixel(positive_tx, positive_ty); + plot_pen_pixel(positive_tx, negative_ty); + plot_pen_pixel(negative_tx, positive_ty); + plot_pen_pixel(negative_tx, negative_ty); + + ++y; + + t9 = t9 + t3; + + if (d1 < 0) + { + d1 = d1 + t9 + t2; + d2 = d2 + t9; + } + else + { + x--; + t8 = t8 - t6; + d1 = d1 + (t9 + t2 - t8); + d2 = d2 + (t9 + t5 - t8); + negative_tx = centerx - x; + positive_tx = centerx + x; + } + + negative_ty = centery - y; + positive_ty = centery + y; + } + + do + { + plot_pen_pixel(positive_tx, positive_ty); + plot_pen_pixel(positive_tx, negative_ty); + plot_pen_pixel(negative_tx, positive_ty); + plot_pen_pixel(negative_tx, negative_ty); + + x--; + t8 = t8 - t6; + + if (d2 < 0) + { + ++y; + t9 = t9 + t3; + d2 = d2 + (t9 + t5 - t8); + negative_ty = centery - y; + positive_ty = centery + y; + } + else + d2 = d2 + (t5 - t8); + + negative_tx = centerx - x; + positive_tx = centerx + x; + } while (x >= 0); + } + + void circle(int centerx, int centery, int radius) + { + int x = 0; + int d = (1 - radius) << 1; + + while (radius >= 0) + { + plot_pen_pixel(centerx + x, centery + radius); + plot_pen_pixel(centerx + x, centery - radius); + plot_pen_pixel(centerx - x, centery + radius); + plot_pen_pixel(centerx - x, centery - radius); + + if ((d + radius) > 0) + d -= ((--radius) << 1) - 1; + if (x > d) + d += ((++x) << 1) + 1; + } + } + + void plot_pen_pixel(int x, int y) + { + switch (pen_width_) + { + case 1: plot_pixel(x, y); + break; + + case 2: { + plot_pixel(x, y); + plot_pixel(x + 1, y); + plot_pixel(x + 1, y + 1); + plot_pixel(x, y + 1); + } + break; + + case 3: { + plot_pixel(x, y - 1); + plot_pixel(x - 1, y - 1); + plot_pixel(x + 1, y - 1); + + plot_pixel(x, y); + plot_pixel(x - 1, y); + plot_pixel(x + 1, y); + + plot_pixel(x, y + 1); + plot_pixel(x - 1, y + 1); + plot_pixel(x + 1, y + 1); + } + break; + + default: plot_pixel(x, y); + break; + } + } + + void plot_pixel(int x, int y) + { + if ( + (x < 0) || + (y < 0) || + (x >= static_cast(image_.width())) || + (y >= static_cast(image_.height())) + ) + return; + + image_.set_pixel(x, y, pen_color_red_, pen_color_green_, pen_color_blue_); + } + + void pen_width(const unsigned int& width) + { + if ((width > 0) && (width < 4)) + { + pen_width_ = width; + } + } + + void pen_color(const unsigned char& red, + const unsigned char& green, + const unsigned char& blue) + { + pen_color_red_ = red; + pen_color_green_ = green; + pen_color_blue_ = blue; + } + + template + void pen_color(const RGB colour) + { + pen_color_red_ = colour.red; + pen_color_green_ = colour.green; + pen_color_blue_ = colour.blue; + } + +private: + + image_drawer(const image_drawer& id); + image_drawer& operator =(const image_drawer& id); + + bitmap_image& image_; + unsigned int pen_width_; + unsigned char pen_color_red_; + unsigned char pen_color_green_; + unsigned char pen_color_blue_; +}; + +class cartesian_canvas +{ +public: + + cartesian_canvas(const double x_length, const double y_length) + : width_div2_(0.0), + height_div2_(0.0), + min_x_(0.0), + min_y_(0.0), + max_x_(0.0), + max_y_(0.0), + draw_(image_) + { + setup_canvas(x_length, y_length); + } + + inline bool operator!() + { + return !image_; + } + + void rectangle(double x1, double y1, double x2, double y2) + { + line_segment(x1, y1, x2, y1); + line_segment(x2, y1, x2, y2); + line_segment(x2, y2, x1, y2); + line_segment(x1, y2, x1, y1); + } + + void triangle(double x1, double y1, double x2, double y2, double x3, double y3) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x1, y1); + } + + void quadix(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x4, y4); + line_segment(x4, y4, x1, y1); + } + + void line_segment(double x1, double y1, double x2, double y2) + { + if (clip(x1, y1, x2, y2)) + { + const int sc_x1 = static_cast(cart_to_screen_x(x1)); + const int sc_x2 = static_cast(cart_to_screen_x(x2)); + const int sc_y1 = static_cast(cart_to_screen_y(y1)); + const int sc_y2 = static_cast(cart_to_screen_y(y2)); + + draw_.line_segment(sc_x1, sc_y1, sc_x2, sc_y2); + } + } + + void horiztonal_line_segment(double x1, double x2, double y) + { + x1 = clamp_x(x1); + x2 = clamp_x(x2); + y = clamp_y(y); + + const int sc_x1 = static_cast(cart_to_screen_x(x1)); + const int sc_x2 = static_cast(cart_to_screen_x(x2)); + const int sc_y = static_cast(cart_to_screen_y(y)); + + draw_.horiztonal_line_segment(sc_x1, sc_x2, sc_y); + } + + void vertical_line_segment(double y1, double y2, double x) + { + y1 = clamp_y(y1); + y2 = clamp_y(y2); + x = clamp_x(x); + + const int sc_y1 = static_cast(cart_to_screen_y(y1)); + const int sc_y2 = static_cast(cart_to_screen_y(y2)); + const int sc_x = static_cast(cart_to_screen_x(x)); + + draw_.vertical_line_segment(sc_y1, sc_y2, sc_x); + } + + void ellipse(double centerx, double centery, double a, double b) + { + + const int sc_cx = static_cast(cart_to_screen_x(centerx)); + const int sc_cy = static_cast(cart_to_screen_y(centery)); + + draw_.ellipse(sc_cx, sc_cy, static_cast(a), static_cast(b)); + } + + void circle(double centerx, double centery, double radius) + { + const int sc_cx = static_cast(cart_to_screen_x(centerx)); + const int sc_cy = static_cast(cart_to_screen_y(centery)); + + draw_.circle(sc_cx, sc_cy, static_cast(radius)); + } + + void fill_rectangle(double x1, double y1, double x2, double y2) + { + if (y1 > y2) + std::swap(y1, y2); + + for (double y = y1; y <= y2; y += 0.5) + { + line_segment(x1, y, x2, y); + } + } + + void fill_triangle(double x1, double y1, double x2, double y2, double x3, double y3) + { + typedef std::pair point_t; + + std::vector p; + + p.push_back(std::make_pair(x1, y1)); + p.push_back(std::make_pair(x2, y2)); + p.push_back(std::make_pair(x3, y3)); + + if (p[0].second > p[1].second) + std::swap(p[0], p[1]); + if (p[0].second > p[2].second) + std::swap(p[0], p[2]); + if (p[1].second > p[2].second) + std::swap(p[1], p[2]); + + class draw_modes + { + private: + + cartesian_canvas& canvas; + + // Needed for incompetent and broken msvc compiler versions +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4822) +#endif + draw_modes& operator=(const draw_modes&); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + public: + + draw_modes(cartesian_canvas& c) + : canvas(c) + {} + + void bottom(const point_t& p0, const point_t& p1, const point_t& p2) + { + const double m0 = (p1.first - p0.first) / (2.0 * (p1.second - p0.second)); + const double m1 = (p2.first - p0.first) / (2.0 * (p2.second - p0.second)); + + double x0 = p0.first; + double x1 = p0.first; + + for (double y = p0.second; y <= p1.second; y += 0.5) + { + canvas.horiztonal_line_segment(x0, x1, y); + + x0 += m0; + x1 += m1; + } + } + + void top(const point_t& p0, const point_t& p1, const point_t& p2) + { + const double m0 = (p2.first - p0.first) / (2.0 * (p2.second - p0.second)); + const double m1 = (p2.first - p1.first) / (2.0 * (p2.second - p1.second)); + + double x0 = p2.first; + double x1 = p2.first; + + for (double y = p2.second; y >= p0.second; y -= 0.5) + { + canvas.horiztonal_line_segment(x0, x1, y); + + x0 -= m0; + x1 -= m1; + } + } + }; + + draw_modes dm(*this); + + const double eps = 0.00001; + + if (std::abs(p[1].second - p[2].second) < eps) + dm.bottom(p[0], p[1], p[2]); + else if (std::abs(p[0].second - p[1].second) < eps) + dm.top(p[0], p[1], p[2]); + else + { + point_t p3; + + p3.first = (p[0].first + ((p[1].second - p[0].second) / (p[2].second - p[0].second)) * (p[2].first - p[0].first)); + p3.second = p[1].second; + + dm.bottom(p[0], p[1], p3); + dm.top(p[1], p3, p[2]); + } + } + + void fill_quadix(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) + { + fill_triangle(x1, y1, x2, y2, x3, y3); + fill_triangle(x1, y1, x3, y3, x4, y4); + } + + void fill_circle(double cx, double cy, double radius) + { + const double delta = 1.0; + double x = radius; + double y = 0.0; + double dx = delta - (2.0 * delta * radius); + double dy = 0.0; + double dr = 0.0; + + while (x >= y) + { + for (double i = cx - x; i <= cx + x; i += delta) + { + horiztonal_line_segment(cx - x, cx + x, cy + y); + horiztonal_line_segment(cx - x, cx + x, cy - y); + } + + for (double i = cx - y; i <= cx + y; i += delta) + { + horiztonal_line_segment(cx - y, cx + y, cy + x); + horiztonal_line_segment(cx - y, cx + y, cy - x); + } + + y += delta; + + dr += dy; + dy += 2.0 * delta; + + if ((2.0 * delta * dr + dx) > 0) + { + x -= delta; + dr += dx; + dx += 2.0 * delta; + } + } + } + + void plot_pen_pixel(double x, double y) + { + if ((x < min_x_) || (x > max_x_)) return; + if ((y < min_y_) || (y > max_y_)) return; + + const int sc_x = static_cast(cart_to_screen_x(x)); + const int sc_y = static_cast(cart_to_screen_y(y)); + + draw_.plot_pen_pixel(sc_x, sc_y); + } + + void plot_pixel(double x, double y) + { + if ((x < min_x_) || (x > max_x_)) return; + if ((y < min_y_) || (y > max_y_)) return; + + const int sc_x = static_cast(cart_to_screen_x(x)); + const int sc_y = static_cast(cart_to_screen_y(y)); + + draw_.plot_pixel(sc_x, sc_y); + } + + void pen_width(const unsigned int& width) + { + draw_.pen_width(width); + } + + void pen_color(const unsigned char& red, + const unsigned char& green, + const unsigned char& blue) + { + draw_.pen_color(red, green, blue); + } + + template + void pen_color(const RGB colour) + { + draw_.pen_color(colour); + } + + const bitmap_image& image() const + { + return image_; + } + + bitmap_image& image() + { + return image_; + } + + void set_widthheight(const double x_length, const double y_length) + { + setup_canvas(x_length, y_length); + } + + double min_x() const { return min_x_; } + double min_y() const { return min_y_; } + double max_x() const { return max_x_; } + double max_y() const { return max_y_; } + +private: + + void setup_canvas(const double x_length, const double y_length) + { + if ((x_length < 2.0) || (y_length < 2.0)) + return; + + width_div2_ = x_length / 2.0; + height_div2_ = y_length / 2.0; + + min_x_ = -width_div2_; + min_y_ = -height_div2_; + max_x_ = width_div2_; + max_y_ = height_div2_; + + image_.setwidth_height(static_cast(x_length) + 1, static_cast(y_length) + 1); + + image_.clear(0xFF); + } + + double clamp_x(const double& x) + { + if (x < min_x_) return min_x_; + else if (x > max_x_) return max_x_; + else return x; + } + + double clamp_y(const double& y) + { + if (y < min_y_) return min_y_; + else if (y > max_y_) return max_y_; + else return y; + } + + double cart_to_screen_x(const double& x) + { + return x + width_div2_; + } + + double cart_to_screen_y(const double& y) + { + return height_div2_ - y; + } + + enum clip_code + { + e_clip_bottom = 1, + e_clip_top = 2, + e_clip_left = 4, + e_clip_right = 8 + }; + + int out_code( + const double& x, const double& y, + const double& x1, const double& y1, + const double& x2, const double& y2 + ) + { + int result = 0; + if (y < y1) result |= e_clip_bottom; + else if (y > y2) result |= e_clip_top; + + if (x < x1) result |= e_clip_left; + else if (x > x2) result |= e_clip_right; + + return result; + } + + bool clip(double& x1, double& y1, double& x2, double& y2) + { + bool result = false; + double x = 0.0; + double y = 0.0; + + int outcode0 = out_code(x1, y1, min_x_, min_y_, max_x_, max_y_); + int outcode1 = out_code(x2, y2, min_x_, min_y_, max_x_, max_y_); + int outcodeout = 0; + + while ((outcode0 != 0) || (outcode1 != 0)) + { + if ((outcode0 & outcode1) != 0) + return result; + else + { + if (outcode0 != 0) + outcodeout = outcode0; + else + outcodeout = outcode1; + + double dx = (x2 - x1); + double dy = (y2 - y1); + + if ((outcodeout & e_clip_bottom) == e_clip_bottom) + { + x = x1 + dx * (min_y_ - y1) / dy; + y = min_y_; + } + else if ((outcodeout & e_clip_top) == e_clip_top) + { + x = x1 + dx * (max_y_ - y1) / dy; + y = max_y_; + } + else if ((outcodeout & e_clip_right) == e_clip_right) + { + y = y1 + dy * (max_x_ - x1) / dx; + x = max_x_; + } + else if ((outcodeout & e_clip_left) == e_clip_left) + { + y = y1 + dy * (min_x_ - x1) / dx; + x = min_x_; + } + + if (outcodeout == outcode0) + { + x1 = x; + y1 = y; + outcode0 = out_code(x1, y1, min_x_, min_y_, max_x_, max_y_); + } + else + { + x2 = x; + y2 = y; + outcode1 = out_code(x2, y2, min_x_, min_y_, max_x_, max_y_); + } + } + } + + return true; + } + + cartesian_canvas(const cartesian_canvas&); + cartesian_canvas operator=(const cartesian_canvas&); + + double width_div2_; + double height_div2_; + double min_x_; + double min_y_; + double max_x_; + double max_y_; + bitmap_image image_; + image_drawer draw_; +}; + +inline rgb_t convert_wave_length_nm_to_rgb(const double wave_length_nm) +{ + // Credits: Dan Bruton http://www.physics.sfasu.edu/astro/color.html + double red = 0.0; + double green = 0.0; + double blue = 0.0; + + if ((380.0 <= wave_length_nm) && (wave_length_nm <= 439.0)) + { + red = -(wave_length_nm - 440.0) / (440.0 - 380.0); + green = 0.0; + blue = 1.0; + } + else if ((440.0 <= wave_length_nm) && (wave_length_nm <= 489.0)) + { + red = 0.0; + green = (wave_length_nm - 440.0) / (490.0 - 440.0); + blue = 1.0; + } + else if ((490.0 <= wave_length_nm) && (wave_length_nm <= 509.0)) + { + red = 0.0; + green = 1.0; + blue = -(wave_length_nm - 510.0) / (510.0 - 490.0); + } + else if ((510.0 <= wave_length_nm) && (wave_length_nm <= 579.0)) + { + red = (wave_length_nm - 510.0) / (580.0 - 510.0); + green = 1.0; + blue = 0.0; + } + else if ((580.0 <= wave_length_nm) && (wave_length_nm <= 644.0)) + { + red = 1.0; + green = -(wave_length_nm - 645.0) / (645.0 - 580.0); + blue = 0.0; + } + else if ((645.0 <= wave_length_nm) && (wave_length_nm <= 780.0)) + { + red = 1.0; + green = 0.0; + blue = 0.0; + } + + double factor = 0.0; + + if ((380.0 <= wave_length_nm) && (wave_length_nm <= 419.0)) + factor = 0.3 + 0.7 * (wave_length_nm - 380.0) / (420.0 - 380.0); + else if ((420.0 <= wave_length_nm) && (wave_length_nm <= 700.0)) + factor = 1.0; + else if ((701.0 <= wave_length_nm) && (wave_length_nm <= 780.0)) + factor = 0.3 + 0.7 * (780.0 - wave_length_nm) / (780.0 - 700.0); + else + factor = 0.0; + + rgb_t result; + + const double gamma = 0.8; + const double intensity_max = 255.0; + +#define round(d) std::floor(d + 0.5) + + result.red = static_cast((red == 0.0) ? red : round(intensity_max * std::pow(red * factor, gamma))); + result.green = static_cast((green == 0.0) ? green : round(intensity_max * std::pow(green * factor, gamma))); + result.blue = static_cast((blue == 0.0) ? blue : round(intensity_max * std::pow(blue * factor, gamma))); + +#undef round + + return result; +} + +inline double weighted_distance(const unsigned char r0, const unsigned char g0, const unsigned char b0, + const unsigned char r1, const unsigned char g1, const unsigned char b1) +{ + const double diff_r = /*0.30 */ (r0 - r1); + const double diff_g = /*0.59 */ (g0 - g1); + const double diff_b = /*0.11 */ (b0 - b1); + + return std::sqrt((diff_r * diff_r) + (diff_g * diff_g) + (diff_b * diff_b)); +} + +inline double weighted_distance(const rgb_t c0, const rgb_t c1) +{ + return weighted_distance(c0.red, c0.green, c0.blue, + c1.red, c1.green, c1.blue); +} + +template +inline rgb_t find_nearest_color(const rgb_t& c, const Iterator begin, const Iterator end) +{ + if (0 == std::distance(begin, end)) + return c; + + double min_d = std::numeric_limits::max(); + rgb_t result = *begin; + + for (Iterator itr = begin; itr != end; ++itr) + { + if (c == (*itr)) + { + return (*itr); + } + + double curr_d = weighted_distance(c, *itr); + + if (curr_d < min_d) + { + min_d = curr_d; + result = *itr; + } + } + + return result; +} + +template