using LensAdjustmentTool.Camera;
using LensAdjustmentTool.Configuration;
using LensAdjustmentTool.Infrastructure;
using LensAdjustmentTool.Models;
using LensAdjustmentTool.Processing;
using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
namespace LensAdjustmentTool
{
public partial class MainForm : Form
{
private readonly AppLogger _logger;
private readonly LatestResultMailbox _mailbox;
private readonly LensAdjustmentProcessor _processor;
private readonly IcImagingCameraService _camera;
private readonly ApertureRoiSettingsStore _apertureRoiSettingsStore;
private readonly Timer _uiTimer;
private TabControl _tabs;
private TabPage _apertureTabPage;
private TabPage _focusTabPage;
private PictureBox _focusPicture;
private Label _focusCurrentValue;
private Label _focusMaxValue;
private Button _focusResetButton;
private SplitContainer _focusSplit;
private SplitContainer _apertureSplit;
private PictureBox _aperturePicture;
private Label _apertureCurrentValue;
private Label _apertureDifferenceValue;
private Label _apertureInstructionLabel;
private ToolStripStatusLabel _cameraStatusLabel;
private ToolStripStatusLabel _cameraInfoLabel;
private ToolStripStatusLabel _fpsLabel;
private int _displayedFrameCount;
private DateTime _fpsWindowStart = DateTime.Now;
private bool _closing;
public MainForm()
{
InitializeComponent();
BuildUserInterface();
_logger = new AppLogger();
_mailbox = new LatestResultMailbox();
_processor = new LensAdjustmentProcessor();
_apertureRoiSettingsStore = new ApertureRoiSettingsStore();
_camera = new IcImagingCameraService(icImagingControl1, _processor, _mailbox, _logger);
_uiTimer = new Timer { Interval = AppSettings.UiRefreshIntervalMs };
_uiTimer.Tick += UiTimer_Tick;
Shown += MainForm_Shown;
FormClosing += MainForm_FormClosing;
_tabs.SelectedIndexChanged += Tabs_SelectedIndexChanged;
_focusResetButton.Click += FocusResetButton_Click;
var version = Assembly.GetExecutingAssembly().GetName().Version;
Text = $"DFK33UX178 Lens Adjustment Tool ver.{version.Major}.{version.Minor}";
#if DEBUG
Text += " [DEBUG]";
#endif
}
private void BuildUserInterface()
{
WindowState = FormWindowState.Maximized;
MinimumSize = new Size(1100, 700);
_tabs = new TabControl
{
Dock = DockStyle.Fill,
Font = new Font("Yu Gothic UI", 16F),
ItemSize = new Size(220, 48),
SizeMode = TabSizeMode.Fixed,
DrawMode = TabDrawMode.OwnerDrawFixed
};
_tabs.DrawItem += Tabs_DrawItem;
_focusTabPage = new TabPage("フォーカス調整");
_apertureTabPage = new TabPage("開口調整");
BuildFocusPage(_focusTabPage);
BuildAperturePage(_apertureTabPage);
_tabs.TabPages.Add(_apertureTabPage);
_tabs.TabPages.Add(_focusTabPage);
_tabs.SelectedIndex = 0;
var statusStrip = new StatusStrip();
_cameraStatusLabel = new ToolStripStatusLabel("Camera: 未初期化");
_cameraInfoLabel = new ToolStripStatusLabel { Spring = true, TextAlign = ContentAlignment.MiddleLeft };
_fpsLabel = new ToolStripStatusLabel("UI: 0.0 fps");
statusStrip.Items.AddRange(new ToolStripItem[]
{
_cameraStatusLabel,
new ToolStripStatusLabel(" | "),
_cameraInfoLabel,
_fpsLabel
});
Controls.Add(_tabs);
Controls.Add(statusStrip);
_tabs.BringToFront();
statusStrip.BringToFront();
}
private void BuildFocusPage(TabPage page)
{
_focusSplit = new SplitContainer
{
Dock = DockStyle.Fill,
FixedPanel = FixedPanel.Panel2
};
var split = _focusSplit;
_focusPicture = CreatePictureBox();
split.Panel1.Controls.Add(_focusPicture);
var panel = CreateSidePanel();
split.Panel2.Controls.Add(panel);
AddHeader(panel, "フォーカス評価値");
AddCaption(panel, "現在値");
_focusCurrentValue = AddValueLabel(panel, "--");
AddCaption(panel, "最大値");
_focusMaxValue = AddValueLabel(panel, "--");
_focusResetButton = new Button
{
Text = "最大値をリセット",
Width = 310,
Height = 48,
Font = new Font("Yu Gothic UI", 12F),
Margin = new Padding(3, 10, 3, 10)
};
panel.Controls.Add(_focusResetButton);
AddInstruction(
panel,
"最初にフォーカスを広くゆっくり回してください。\r\n\r\n" +
"その後、現在値が最大値に近くなるように\r\nレンズのフォーカスを調整してください。\r\n\r\n" +
"ROI: (2135, 800), 400×600\r\n表示: 右90°回転 → 600×400");
AddInstructionImage(panel, "LensFocus.jpg");
page.Controls.Add(split);
split.Panel2MinSize = 340;
_focusSplit.Layout += SplitContainer_Layout;
}
private void BuildAperturePage(TabPage page)
{
_apertureSplit = new SplitContainer
{
Dock = DockStyle.Fill,
FixedPanel = FixedPanel.Panel2
};
var split = _apertureSplit;
_aperturePicture = CreatePictureBox();
split.Panel1.Controls.Add(_aperturePicture);
var panel = CreateSidePanel();
split.Panel2.Controls.Add(panel);
AddHeader(panel, "白色票 平均画素値");
AddCaption(panel, "現在値");
_apertureCurrentValue = AddValueLabel(panel, "--");
AddCaption(panel, "目標値");
AddValueLabel(panel, AppSettings.ApertureTarget.ToString("0.0"));
AddCaption(panel, "差(現在値 − 目標値)");
_apertureDifferenceValue = AddValueLabel(panel, "--");
//AddCaption(panel, "ROI位置微調整");
_apertureInstructionLabel = AddInstruction(
panel,
BuildApertureInstructionText(AppSettings.ApertureCalculationRoi));
panel.Controls.Add(CreateApertureRoiMovePanel());
AddInstructionImage(panel, "LensApature.jpg");
page.Controls.Add(split);
split.Panel2MinSize = 340;
_apertureSplit.Layout += SplitContainer_Layout;
}
private void SetSplitterDistance(SplitContainer split)
{
int available = split.Width;
if (available <= split.Panel1MinSize + split.Panel2MinSize + split.SplitterWidth)
return;
int distance = Math.Max(
available - split.Panel2MinSize - split.SplitterWidth,
split.Panel1MinSize);
split.SplitterDistance = distance;
}
private static PictureBox CreatePictureBox()
{
return new PictureBox
{
Dock = DockStyle.Fill,
BackColor = Color.Black,
SizeMode = PictureBoxSizeMode.Zoom,
BorderStyle = BorderStyle.FixedSingle
};
}
private static FlowLayoutPanel CreateSidePanel()
{
return new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.TopDown,
WrapContents = false,
AutoScroll = true,
Padding = new Padding(18),
BackColor = SystemColors.Control
};
}
private static void AddHeader(Control parent, string text)
{
parent.Controls.Add(new Label
{
Text = text,
AutoSize = false,
Width = 310,
Height = 48,
Font = new Font("Yu Gothic UI", 20F, FontStyle.Bold),
TextAlign = ContentAlignment.MiddleLeft
});
}
private TableLayoutPanel CreateApertureRoiMovePanel()
{
var panel = new TableLayoutPanel
{
ColumnCount = 3,
RowCount = 3,
Width = 150,
Height = 110,
Margin = new Padding(3, 4, 3, 12)
};
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 46F));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 46F));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 46F));
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 32F));
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 32F));
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 32F));
panel.Controls.Add(CreateApertureRoiMoveButton("↑", 0, -AppSettings.ApertureRoiMoveStep), 1, 0);
panel.Controls.Add(CreateApertureRoiMoveButton("←", -AppSettings.ApertureRoiMoveStep, 0), 0, 1);
panel.Controls.Add(CreateApertureRoiMoveButton("→", AppSettings.ApertureRoiMoveStep, 0), 2, 1);
panel.Controls.Add(CreateApertureRoiMoveButton("↓", 0, AppSettings.ApertureRoiMoveStep), 1, 2);
return panel;
}
private Button CreateApertureRoiMoveButton(string text, int deltaX, int deltaY)
{
var button = new Button
{
Text = text,
Width = 40,
Height = 28,
Font = new Font("Yu Gothic UI", 10F),
Margin = new Padding(3),
Tag = new Point(deltaX, deltaY)
};
button.Click += ApertureRoiMoveButton_Click;
return button;
}
private static void AddCaption(Control parent, string text)
{
parent.Controls.Add(new Label
{
Text = text,
AutoSize = false,
Width = 310,
Height = 32,
Font = new Font("Yu Gothic UI", 11F),
TextAlign = ContentAlignment.BottomLeft
});
}
private static Label AddValueLabel(Control parent, string text)
{
var label = new Label
{
Text = text,
AutoSize = false,
Width = 310,
Height = 76,
Font = new Font("Consolas", 32F, FontStyle.Bold),
TextAlign = ContentAlignment.MiddleRight,
BorderStyle = BorderStyle.FixedSingle,
BackColor = Color.White
};
parent.Controls.Add(label);
return label;
}
private static Label AddInstruction(Control parent, string text)
{
var label = new Label
{
Text = text,
AutoSize = false,
Width = 310,
Height = 230,
Font = new Font("Yu Gothic UI", 12F),
TextAlign = ContentAlignment.MiddleLeft,
Padding = new Padding(8)
};
parent.Controls.Add(label);
return label;
}
private static string BuildApertureInstructionText(Rectangle calculationRoi)
{
return "現在値が目標値(±1.0)となるように\r\nレンズの開口を調整してください。\r\n\r\n" +
"ROI位置微調整\r\n" +
$"赤枠: ROI ({calculationRoi.X}, {calculationRoi.Y}), {calculationRoi.Width}×{calculationRoi.Height}\r\n" +
//$"表示ROI: ({AppSettings.ApertureDisplayRoi.X}, {AppSettings.ApertureDisplayRoi.Y}), {AppSettings.ApertureDisplayRoi.Width}×{AppSettings.ApertureDisplayRoi.Height}\r\n" +
$"ROI移動: {AppSettings.ApertureRoiMoveStep}px単位";
}
private void UpdateApertureInstructionText(Rectangle calculationRoi)
{
if (_apertureInstructionLabel != null)
{
_apertureInstructionLabel.Text = BuildApertureInstructionText(calculationRoi);
}
}
private static void AddInstructionImage(Control parent, string fileName)
{
string path = FindMediaFilePath(fileName);
if (path == null)
return;
Image image;
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var source = Image.FromStream(stream))
{
image = new Bitmap(source);
}
var pictureBox = new PictureBox
{
Width = 310,
Height = 464,
SizeMode = PictureBoxSizeMode.Zoom,
BorderStyle = BorderStyle.FixedSingle,
BackColor = Color.White,
Margin = new Padding(3, 8, 3, 8),
Image = image
};
pictureBox.Disposed += (sender, e) => pictureBox.Image?.Dispose();
parent.Controls.Add(pictureBox);
}
private static string FindMediaFilePath(string fileName)
{
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
string[] candidates =
{
Path.Combine(baseDirectory, "Media", fileName),
//Path.Combine(baseDirectory, fileName),
//Path.GetFullPath(Path.Combine(baseDirectory, @"..\Media", fileName)),
//Path.GetFullPath(Path.Combine(baseDirectory, @"..\..\Media", fileName)),
//Path.GetFullPath(Path.Combine(baseDirectory, @"..\..\..\Media", fileName))
};
foreach (string candidate in candidates)
{
if (File.Exists(candidate))
return candidate;
}
return null;
}
private void Tabs_DrawItem(object sender, DrawItemEventArgs e)
{
TabPage page = _tabs.TabPages[e.Index];
bool selected = e.Index == _tabs.SelectedIndex;
Color backColor = selected ? Color.FromArgb(0, 120, 215) : Color.FromArgb(240, 240, 240);
Color foreColor = selected ? Color.White : Color.FromArgb(60, 60, 60);
FontStyle style = selected ? FontStyle.Bold : FontStyle.Regular;
using (Brush back = new SolidBrush(backColor))
e.Graphics.FillRectangle(back, e.Bounds);
using (Font font = new Font("Yu Gothic UI", 13F, style))
using (Brush fore = new SolidBrush(foreColor))
{
StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
e.Graphics.DrawString(page.Text, font, fore, e.Bounds, sf);
}
}
private void SplitContainer_Layout(object sender, LayoutEventArgs e)
{
var split = (SplitContainer)sender;
SetSplitterDistance(split);
}
private void MainForm_Shown(object sender, EventArgs e)
{
try
{
SetSplitterDistance(_focusSplit);
SetSplitterDistance(_apertureSplit);
//Text = "フォーカス調整 \u2014 Lens Adjustment Tool";
_logger.Info("Application start.");
RestoreApertureRoi();
ApplySelectedMode(clearMailbox: false);
_camera.InitializeAndStart(AppDomain.CurrentDomain.BaseDirectory);
_cameraInfoLabel.Text = _camera.CameraInfo?.ToString() ?? string.Empty;
_uiTimer.Start();
}
catch (Exception ex)
{
_logger.Error("Camera initialization failed.", ex);
_cameraStatusLabel.Text = "Camera: ERROR";
MessageBox.Show(
this,
"カメラの初期化に失敗しました。\r\n\r\n" + ex.Message +
"\r\n\r\nログ:\r\n" + _logger.LogFilePath,
"Camera initialization error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private void Tabs_SelectedIndexChanged(object sender, EventArgs e)
{
ApplySelectedMode(clearMailbox: true);
}
private void ApplySelectedMode(bool clearMailbox)
{
AdjustmentMode mode = GetSelectedMode();
_processor.SetMode(mode, resetFocusMaximum: mode == AdjustmentMode.Focus);
if (clearMailbox)
{
_mailbox.Clear();
}
if (mode == AdjustmentMode.Focus)
{
_focusCurrentValue.Text = "--";
_focusMaxValue.Text = "--";
}
_logger.Info("Mode changed: " + mode);
}
private AdjustmentMode GetSelectedMode()
{
return _tabs.SelectedTab == _focusTabPage
? AdjustmentMode.Focus
: AdjustmentMode.Aperture;
}
private void FocusResetButton_Click(object sender, EventArgs e)
{
_processor.ResetFocusMaximum();
_focusMaxValue.Text = "--";
_logger.Info("Focus maximum reset.");
}
private void ApertureRoiMoveButton_Click(object sender, EventArgs e)
{
var button = (Button)sender;
var delta = (Point)button.Tag;
Rectangle roi = _processor.MoveApertureCalculationRoi(delta.X, delta.Y);
UpdateApertureInstructionText(roi);
SaveApertureRoi(roi);
_logger.Info($"Aperture ROI moved to ({roi.X}, {roi.Y}).");
}
private void RestoreApertureRoi()
{
Rectangle? roi = _apertureRoiSettingsStore.Load();
if (!roi.HasValue)
return;
Rectangle applied = _processor.SetApertureCalculationRoi(roi.Value);
UpdateApertureInstructionText(applied);
_logger.Info($"Aperture ROI restored to ({applied.X}, {applied.Y}).");
}
private void SaveApertureRoi(Rectangle roi)
{
try
{
_apertureRoiSettingsStore.Save(roi);
}
catch (Exception ex)
{
_logger.Warn("Failed to save aperture ROI." + Environment.NewLine + ex.Message);
}
}
private void UiTimer_Tick(object sender, EventArgs e)
{
_cameraStatusLabel.Text = "Camera: " + _camera.StatusText;
using (FrameAnalysisResult result = _mailbox.TakeLatest())
{
if (result != null)
{
UpdateFromResult(result);
_displayedFrameCount++;
}
}
double elapsed = (DateTime.Now - _fpsWindowStart).TotalSeconds;
if (elapsed >= 1.0)
{
double fps = _displayedFrameCount / elapsed;
_fpsLabel.Text = $"UI: {fps:0.0} fps";
_displayedFrameCount = 0;
_fpsWindowStart = DateTime.Now;
}
}
private void UpdateFromResult(FrameAnalysisResult result)
{
if (result.Mode == AdjustmentMode.Focus)
{
ReplacePicture(_focusPicture, new Bitmap(result.DisplayImage));
_focusCurrentValue.Text = result.CurrentValue.ToString("0.00");
_focusMaxValue.Text = result.AuxiliaryValue.ToString("0.00");
}
else
{
ReplacePicture(_aperturePicture, new Bitmap(result.DisplayImage));
_apertureCurrentValue.Text = result.CurrentValue.ToString("0.0");
_apertureDifferenceValue.Text = result.AuxiliaryValue.ToString("+0.0;-0.0;0.0");
}
}
private static void ReplacePicture(PictureBox box, Bitmap next)
{
Image old = box.Image;
box.Image = next;
old?.Dispose();
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (_closing) return;
_closing = true;
_uiTimer.Stop();
try
{
_camera.Dispose();
}
catch (Exception ex)
{
_logger.Error("Error while stopping camera.", ex);
}
SaveApertureRoi(_processor.GetApertureCalculationRoi());
_mailbox.Clear();
ReplacePicture(_focusPicture, null);
ReplacePicture(_aperturePicture, null);
_logger.Info("Application end.");
_logger.Dispose();
}
}
}