using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media.Media3D;
using System.Xml.Serialization;
using TIS.Imaging;
namespace TIASshot {
public partial class Form1 : Form {
private CameraBase _camera;
public static string APP_NAME = "TIAS Shot";
private LightSource _light = new LightSource();
private bool _isLightOn = true;
private PreviewMonitor _previewMonitor;
private Bitmap _dispBuf = null;
private bool _isUpdateBuf = false;
private readonly object _dispBufLock = new object();
// 本モニタ(picDisplay)の自ペース再描画用タイマー。
// 以前はコールバックスレッドから毎フレーム Invalidate() していたが,Form1 の WM_PAINT が
// 常時保留状態になり,同一 UI スレッドを共有する副モニタの WM_TIMER が
// (WM_PAINT > WM_TIMER の優先度により)飢餓状態になっていた。
// 本モニタも副モニタと同じタイマー駆動に統一し,コールバックスレッドからは
// Invalidate を一切呼ばないことで WM_PAINT の常時保留(洪水源)を消す。
private const int MainRefreshIntervalMs = 33; // ≒30fps(PreviewMonitor と同じ値)
private readonly System.Windows.Forms.Timer _mainRefreshTimer = new System.Windows.Forms.Timer();
/// <summary>
/// コンストラクタ
/// </summary>
public Form1() {
InitializeComponent();
#if WHITEBOARD
InitializeWhiteBoardButton();
#endif
#if MEMMONITOR
this.Shown += (s, e) => ShowMemoryMonitor();
this.KeyPreview = true;
this.KeyDown += Form1_MemMonitor_KeyDown;
#endif
var version = Assembly.GetExecutingAssembly().GetName().Version;
Text = $"{APP_NAME} ver.{version.Major}.{version.Minor}";
#if DEBUG
Text += " [DEBUG]";
#endif
}
/// <summary>
/// フォームロード
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e) {
Config.Load();
_camera = new Lucam(this, picPreview);
if (!_camera.Connect()) {
Debug.WriteLine(_camera.ErrorMsg);
Debug.WriteLine("Lucamの接続に失敗しました");
_camera = null;
}
if (_camera == null) {
var cameraList = icImagingControl1.Devices.Select(d => d.Name.Replace(" ", ""));
foreach (var cam in cameraList) {
_camera = new IScam(this, icImagingControl1, cam);
if (_camera.Connect()) break;
Debug.WriteLine(_camera.ErrorMsg);
Debug.WriteLine($"{cam}の接続に失敗しました");
_camera = null;
}
}
if (_camera == null) {
MessageBox.Show("カメラが見つかりません.終了します.", APP_NAME,
MessageBoxButtons.OK, MessageBoxIcon.Warning);
Close();
return;
}
txtDeviceName.Text = _camera.DeviceName;
txtSerialNo.Text = _camera.SerialNumber;
txtNumMultiShots.Text = Config.GetString("Shot/MultiShotCount");
txtMultiShotsInterval.Text = Config.GetString("Shot/MultiShotInterval");
txtDataName.Text = Config.GetString("Shot/SubjectName");
txtSaveFolder.Text = Config.GetString("File/SaveFolder");
txtConvElemNum.Text = Config.GetString("Calib/ConversionChannels");
EnableShots(false);
//_lucam.StartStopPreview();
if (_light.Open()) {
_light.SetLight();
} else {
Debug.WriteLine("光源の接続に失敗しました");
btnLightSW.Enabled = false;
}
_previewMonitor = new PreviewMonitor();
if (_previewMonitor.IsShown) _previewMonitor.Show();
_mainRefreshTimer.Interval = MainRefreshIntervalMs;
_mainRefreshTimer.Tick += MainRefreshTimer_Tick;
_mainRefreshTimer.Start();
}
/// <summary>
/// フォームクローズ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
_mainRefreshTimer.Stop();
if (_camera == null) return;
_camera.Disconnect();
}
/// <summary>
/// 画像撮影1枚
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnShot1_Click(object sender, EventArgs e) {
EnableShots(false);
_camera.ShotOne();
}
/// <summary>
/// 複数画像撮影
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnShotMulti_Click(object sender, EventArgs e) {
EnableShots(false);
_camera.ShotMulti();
}
/// <summary>
/// 画像表示
/// ワーカースレッドから呼び出される
/// </summary>
/// <param name="bmp"></param>
public void ShowImage(Bitmap bmp) {
#if MEMMONITOR
PreviewProfiler.IncrementCallbackFrames();
PreviewProfiler.SetLastImageSize(bmp.Width, bmp.Height);
long mainCopyStart = System.Diagnostics.Stopwatch.GetTimestamp();
#endif
// Lucam 経路は二重バッファ(_bmps[0]/_bmps[1])をコールバックスレッド上で Dispose するため,
// 入力 bmp をそのまま picDisplay.Image に載せると,UIスレッドの OnPaint が Width 等を
// 読んでいる最中にコールバックスレッドが Dispose して別スレッド同時アクセスでクラッシュしうる。
// そのため ShowImage は入力 bmp を「読むだけ」とし,表示用には呼び出しスレッド上で
// Form1 所有のコピーを生成する(bmp の所有権は呼び出し側に残す)。
// BitmapUtil.FastClone は LockBits による生画素コピーで,GDI+ の DrawImage 経路を使う
// new Bitmap(bmp)(~25ms)より高速(数 ms)。生成されるコピーは入力と独立した画素バッファを
// 持つため,所有権・スレッド安全性のモデルは new Bitmap(bmp) と完全に同一。
var dispCopy = BitmapUtil.FastClone(bmp);
#if MEMMONITOR
PreviewProfiler.AddMainCopyTicks(System.Diagnostics.Stopwatch.GetTimestamp() - mainCopyStart);
#endif
// 原因2: タイマー Tick に反映されなかった取りこぼしフレームを Dispose する
// _dispBufLock で ShowImage(コールバックスレッド)と MainRefreshTimer_Tick(UIスレッド)の
// 同時アクセスを防ぎ、二重 Dispose・使用中バッファの誤 Dispose を回避する
Bitmap toDispose = null;
lock (_dispBufLock) {
if (_isUpdateBuf && _dispBuf != null) {
toDispose = _dispBuf; // タイマー未反映の旧バッファを破棄予約
}
_dispBuf = dispCopy;
_isUpdateBuf = true;
}
toDispose?.Dispose(); // ロック外で GDI 解放(ロック保持時間を最小化)
// Invalidate() は呼ばない。毎フレーム呼ぶと Form1 の WM_PAINT が常時保留状態になり,
// 同一 UI スレッドを共有する副モニタの WM_TIMER(WM_PAINT より優先度が低い)が
// 飢餓状態になるため(本モニタも MainRefreshTimer_Tick による自ペース再描画に統一した)。
// 原因1: 副モニタが表示されているときだけフルコピーを生成する
// (非表示時に毎フレーム bmp2 を生成しリークしていた問題を修正)
bool feedSub = _previewMonitor != null && _previewMonitor.IsShown;
#if MEMMONITOR
// F9 トグル(ToggleSubMonitorProfiling)による計測用の一時抑制。
// 抑制中は副モニタ用コピー生成も UpdateImage 呼び出しも走らせず,
// コールバックスレッドのコピー回数を本モニタ用の 1 回だけに戻す。
if (_subMonitorSuppressed) feedSub = false;
#endif
if (feedSub) {
#if MEMMONITOR
PreviewProfiler.IsSubShown = true;
long subCopyStart = System.Diagnostics.Stopwatch.GetTimestamp();
var subCopy = BitmapUtil.FastClone(bmp);
PreviewProfiler.AddSubCopyTicks(System.Diagnostics.Stopwatch.GetTimestamp() - subCopyStart);
_previewMonitor.UpdateImage(subCopy);
#else
_previewMonitor.UpdateImage(BitmapUtil.FastClone(bmp));
#endif
}
#if MEMMONITOR
else {
PreviewProfiler.IsSubShown = false;
}
#endif
}
/// <summary>
/// 本モニタ(picDisplay)の自ペース再描画(約30fps)。
/// 新フレームがあるときだけ picDisplay.Image を差し替える(静止フレームは無駄に再描画しない)。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainRefreshTimer_Tick(object sender, EventArgs e) {
// 原因2: _dispBufLock で ShowImage との競合を防ぎながらバッファを取り出す
// _dispBuf を null に戻すことで ShowImage 側が「表示中バッファ」を誤って Dispose しないようにする
Bitmap toDisplay;
lock (_dispBufLock) {
if (!_isUpdateBuf || _dispBuf == null) return;
toDisplay = _dispBuf;
_dispBuf = null; // 参照を手放し、次の ShowImage で二重 Dispose されないようにする
_isUpdateBuf = false;
}
// picDisplay.Image の Dispose・差し替えは UI スレッド(このタイマー Tick)でのみ行う。
picDisplay.Image?.Dispose(); // 直前に表示していたバッファを解放
picDisplay.Image = toDisplay;
#if MEMMONITOR
PreviewProfiler.IncrementMainPaints();
#endif
}
/// <summary>
/// メッセージ表示
/// </summary>
/// <param name="msg"></param>
public void ShowMessage(string msg) {
if (InvokeRequired) {
Invoke((MethodInvoker)delegate { ShowMessage(msg); });
return;
}
txtMessage.Text = msg;
txtMessage.Update();
}
/// <summary>
/// データ名取得
/// </summary>
/// <returns></returns>
public string GetDataName() {
if (InvokeRequired) {
return (string)Invoke((MethodInvoker)delegate { GetDataName(); });
}
return txtDataName.Text;
}
/// <summary>
/// マルチショット撮影の枚数取得
/// </summary>
/// <returns></returns>
public int GetNumMultiShots() {
if (InvokeRequired) {
return (int)Invoke((MethodInvoker)delegate { GetNumMultiShots(); });
}
if (int.TryParse(txtNumMultiShots.Text, out int result)) return result;
return 1;
}
/// <summary>
/// マルチショット撮影の間隔取得
/// </summary>
/// <returns></returns>
public int GetMultiShotsInterval() {
if (InvokeRequired) {
return (int)Invoke((MethodInvoker)delegate { GetMultiShotsInterval(); });
}
if (int.TryParse(txtMultiShotsInterval.Text, out int result)) return result;
return 1000;
}
/// <summary>
/// 撮影ボタン有効化
/// </summary>
/// <param name="enable"></param>
public void EnableShots(bool enable = true) {
if (InvokeRequired) {
Invoke((MethodInvoker)delegate { EnableShots(enable); });
return;
}
btnShotOne.Enabled = enable;
btnShotMulti.Enabled = enable;
}
/// <summary>
/// データフォルダを開く
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnOpenDataFolder_Click(object sender, EventArgs e) {
System.Diagnostics.Process.Start(txtSaveFolder.Text);
}
/// <summary>
/// 光源のON/OFF切り替え
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLightSW_Click(object sender, EventArgs e) {
if (!_light.IsOpen || _camera.IsCalibrating) return;
if (_isLightOn) {
_light.TurnOff();
btnLightSW.Text = "照明 ON";
_isLightOn = false;
} else {
_light.SetLight();
btnLightSW.Text = "照明 OFF";
_isLightOn = true;
}
}
}
}