improve:截图
This commit is contained in:
@@ -0,0 +1,261 @@
|
|||||||
|
using AForge.Video;
|
||||||
|
using AForge.Video.DirectShow;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
public class CameraService : IDisposable
|
||||||
|
{
|
||||||
|
private VideoCaptureDevice _videoSource;
|
||||||
|
private bool _isCapturing;
|
||||||
|
private Bitmap _currentFrame;
|
||||||
|
private readonly object _frameLock = new object();
|
||||||
|
private Dispatcher _dispatcher;
|
||||||
|
|
||||||
|
public event EventHandler<Bitmap> FrameReceived;
|
||||||
|
public event EventHandler<string> ErrorOccurred;
|
||||||
|
|
||||||
|
public bool IsCapturing => _isCapturing;
|
||||||
|
public List<FilterInfo> AvailableCameras { get; private set; }
|
||||||
|
public FilterInfo CurrentCamera { get; private set; }
|
||||||
|
|
||||||
|
public CameraService()
|
||||||
|
{
|
||||||
|
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||||
|
AvailableCameras = new List<FilterInfo>();
|
||||||
|
RefreshCameraList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 刷新可用摄像头列表
|
||||||
|
/// </summary>
|
||||||
|
public void RefreshCameraList()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AvailableCameras.Clear();
|
||||||
|
var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
|
||||||
|
|
||||||
|
foreach (FilterInfo device in videoDevices)
|
||||||
|
{
|
||||||
|
AvailableCameras.Add(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.WriteLogToFile($"发现 {AvailableCameras.Count} 个摄像头设备");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
ErrorOccurred?.Invoke(this, $"刷新摄像头列表失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始摄像头预览
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cameraIndex">摄像头索引</param>
|
||||||
|
public bool StartPreview(int cameraIndex = 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (AvailableCameras.Count == 0)
|
||||||
|
{
|
||||||
|
RefreshCameraList();
|
||||||
|
if (AvailableCameras.Count == 0)
|
||||||
|
{
|
||||||
|
ErrorOccurred?.Invoke(this, "未找到可用的摄像头设备");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
|
||||||
|
{
|
||||||
|
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止当前预览
|
||||||
|
StopPreview();
|
||||||
|
|
||||||
|
CurrentCamera = AvailableCameras[cameraIndex];
|
||||||
|
_videoSource = new VideoCaptureDevice(CurrentCamera.MonikerString);
|
||||||
|
|
||||||
|
// 设置视频源事件处理
|
||||||
|
_videoSource.NewFrame += VideoSource_NewFrame;
|
||||||
|
|
||||||
|
// 启动视频源
|
||||||
|
_videoSource.Start();
|
||||||
|
|
||||||
|
_isCapturing = true;
|
||||||
|
LogHelper.WriteLogToFile($"开始摄像头预览: {CurrentCamera.Name}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"启动摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
ErrorOccurred?.Invoke(this, $"启动摄像头预览失败: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 停止摄像头预览
|
||||||
|
/// </summary>
|
||||||
|
public void StopPreview()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_videoSource != null && _videoSource.IsRunning)
|
||||||
|
{
|
||||||
|
_videoSource.SignalToStop();
|
||||||
|
_videoSource.WaitForStop();
|
||||||
|
_videoSource.NewFrame -= VideoSource_NewFrame;
|
||||||
|
_videoSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isCapturing = false;
|
||||||
|
LogHelper.WriteLogToFile("摄像头预览已停止");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"停止摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换到指定摄像头
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cameraIndex">摄像头索引</param>
|
||||||
|
public bool SwitchCamera(int cameraIndex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
|
||||||
|
{
|
||||||
|
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StartPreview(cameraIndex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
ErrorOccurred?.Invoke(this, $"切换摄像头失败: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前帧的Bitmap
|
||||||
|
/// </summary>
|
||||||
|
public Bitmap GetCurrentFrame()
|
||||||
|
{
|
||||||
|
lock (_frameLock)
|
||||||
|
{
|
||||||
|
return _currentFrame?.Clone() as Bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前帧的BitmapSource(WPF格式)
|
||||||
|
/// </summary>
|
||||||
|
public BitmapSource GetCurrentFrameAsBitmapSource()
|
||||||
|
{
|
||||||
|
lock (_frameLock)
|
||||||
|
{
|
||||||
|
if (_currentFrame == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var memory = new MemoryStream())
|
||||||
|
{
|
||||||
|
_currentFrame.Save(memory, ImageFormat.Png);
|
||||||
|
memory.Position = 0;
|
||||||
|
|
||||||
|
var bitmapImage = new BitmapImage();
|
||||||
|
bitmapImage.BeginInit();
|
||||||
|
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmapImage.StreamSource = memory;
|
||||||
|
bitmapImage.EndInit();
|
||||||
|
bitmapImage.Freeze();
|
||||||
|
|
||||||
|
return bitmapImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"转换帧为BitmapSource失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 视频源新帧事件处理
|
||||||
|
/// </summary>
|
||||||
|
private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_frameLock)
|
||||||
|
{
|
||||||
|
// 释放之前的帧
|
||||||
|
_currentFrame?.Dispose();
|
||||||
|
|
||||||
|
// 克隆新帧
|
||||||
|
_currentFrame = eventArgs.Frame.Clone() as Bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在UI线程中触发事件
|
||||||
|
_dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
FrameReceived?.Invoke(this, _currentFrame);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"处理新帧失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
ErrorOccurred?.Invoke(this, $"处理新帧失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取摄像头名称列表
|
||||||
|
/// </summary>
|
||||||
|
public List<string> GetCameraNames()
|
||||||
|
{
|
||||||
|
return AvailableCameras.Select(camera => camera.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查是否有可用摄像头
|
||||||
|
/// </summary>
|
||||||
|
public bool HasAvailableCameras()
|
||||||
|
{
|
||||||
|
if (AvailableCameras.Count == 0)
|
||||||
|
{
|
||||||
|
RefreshCameraList();
|
||||||
|
}
|
||||||
|
return AvailableCameras.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
StopPreview();
|
||||||
|
|
||||||
|
lock (_frameLock)
|
||||||
|
{
|
||||||
|
_currentFrame?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -158,6 +158,8 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
||||||
<PackageReference Include="OSVersionExt" Version="3.0.0" />
|
<PackageReference Include="OSVersionExt" Version="3.0.0" />
|
||||||
|
<PackageReference Include="AForge.Video" Version="2.2.5" />
|
||||||
|
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<COMReference Include="IWshRuntimeLibrary">
|
<COMReference Include="IWshRuntimeLibrary">
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
public Rectangle Area;
|
public Rectangle Area;
|
||||||
public List<Point> Path;
|
public List<Point> Path;
|
||||||
|
public Bitmap CameraImage;
|
||||||
|
|
||||||
public ScreenshotResult(Rectangle area, List<Point> path = null)
|
public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null)
|
||||||
{
|
{
|
||||||
Area = area;
|
Area = area;
|
||||||
Path = path;
|
Path = path;
|
||||||
|
CameraImage = cameraImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,34 +57,43 @@ namespace Ink_Canvas
|
|||||||
// 恢复窗口显示
|
// 恢复窗口显示
|
||||||
Visibility = originalVisibility;
|
Visibility = originalVisibility;
|
||||||
|
|
||||||
if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
|
if (screenshotResult.HasValue)
|
||||||
{
|
{
|
||||||
// 截取选定区域
|
// 检查是否是摄像头截图
|
||||||
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
if (screenshotResult.Value.CameraImage != null)
|
||||||
{
|
{
|
||||||
if (originalBitmap != null)
|
// 摄像头截图
|
||||||
|
await InsertScreenshotToCanvas(screenshotResult.Value.CameraImage);
|
||||||
|
}
|
||||||
|
else if (screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
|
||||||
|
{
|
||||||
|
// 屏幕截图
|
||||||
|
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
||||||
{
|
{
|
||||||
Bitmap finalBitmap = originalBitmap;
|
if (originalBitmap != null)
|
||||||
bool needDisposeFinalBitmap = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// 如果有路径信息,应用形状遮罩
|
Bitmap finalBitmap = originalBitmap;
|
||||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
bool needDisposeFinalBitmap = false;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
// 如果有路径信息,应用形状遮罩
|
||||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||||
|
{
|
||||||
|
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||||
|
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将截图转换为WPF Image并插入到画布
|
||||||
|
await InsertScreenshotToCanvas(finalBitmap);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
// 将截图转换为WPF Image并插入到画布
|
|
||||||
await InsertScreenshotToCanvas(finalBitmap);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// 如果创建了新的位图,需要释放它
|
|
||||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
|
||||||
{
|
{
|
||||||
finalBitmap.Dispose();
|
// 如果创建了新的位图,需要释放它
|
||||||
|
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||||
|
{
|
||||||
|
finalBitmap.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,10 +163,22 @@ namespace Ink_Canvas
|
|||||||
var selectorWindow = new ScreenshotSelectorWindow();
|
var selectorWindow = new ScreenshotSelectorWindow();
|
||||||
if (selectorWindow.ShowDialog() == true)
|
if (selectorWindow.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
result = new ScreenshotResult(
|
// 检查是否是摄像头截图
|
||||||
selectorWindow.SelectedArea.Value,
|
if (selectorWindow.CameraImage != null)
|
||||||
selectorWindow.SelectedPath
|
{
|
||||||
);
|
result = new ScreenshotResult(
|
||||||
|
Rectangle.Empty, // 摄像头截图不需要区域
|
||||||
|
null, // 摄像头截图不需要路径
|
||||||
|
selectorWindow.CameraImage // 摄像头图像
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = new ScreenshotResult(
|
||||||
|
selectorWindow.SelectedArea.Value,
|
||||||
|
selectorWindow.SelectedPath
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,15 @@
|
|||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
FontWeight="Medium"
|
FontWeight="Medium"
|
||||||
Click="FullScreenButton_Click" />
|
Click="FullScreenButton_Click" />
|
||||||
|
<Button Name="CameraModeButton"
|
||||||
|
Content="摄像头截图"
|
||||||
|
Margin="4,0"
|
||||||
|
Padding="12,6"
|
||||||
|
Background="#6b7280"
|
||||||
|
Foreground="White"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Click="CameraModeButton_Click" />
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
<!-- 分隔线 -->
|
||||||
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"
|
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"
|
||||||
@@ -152,6 +161,68 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<!-- 摄像头预览区域 -->
|
||||||
|
<Border Name="CameraPreviewBorder"
|
||||||
|
Background="#1a1a1a"
|
||||||
|
Opacity="0.95"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="8"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Width="640"
|
||||||
|
Height="480"
|
||||||
|
Visibility="Collapsed"
|
||||||
|
Panel.ZIndex="1000">
|
||||||
|
<Grid>
|
||||||
|
<!-- 摄像头预览画面 -->
|
||||||
|
<Image Name="CameraPreviewImage"
|
||||||
|
Stretch="Uniform"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
|
||||||
|
<!-- 摄像头控制面板 -->
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Margin="0,0,0,8">
|
||||||
|
<!-- 摄像头选择下拉框 -->
|
||||||
|
<ComboBox Name="CameraSelectionComboBox"
|
||||||
|
Width="200"
|
||||||
|
Margin="4,0"
|
||||||
|
Background="#2d2d2d"
|
||||||
|
Foreground="White"
|
||||||
|
BorderBrush="#404040"
|
||||||
|
SelectionChanged="CameraSelectionComboBox_SelectionChanged" />
|
||||||
|
|
||||||
|
<!-- 切换摄像头按钮 -->
|
||||||
|
<Button Name="SwitchCameraButton"
|
||||||
|
Content="切换摄像头"
|
||||||
|
Margin="4,0"
|
||||||
|
Padding="8,4"
|
||||||
|
Background="#3b82f6"
|
||||||
|
Foreground="White"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Click="SwitchCameraButton_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 摄像头状态指示 -->
|
||||||
|
<Border Background="#1a1a1a"
|
||||||
|
Opacity="0.9"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="8,4"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="8">
|
||||||
|
<TextBlock Name="CameraStatusText"
|
||||||
|
Foreground="White"
|
||||||
|
FontSize="12"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Text="摄像头未连接" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- 统一提示文字区域 -->
|
<!-- 统一提示文字区域 -->
|
||||||
<Border Name="HintTextBorder"
|
<Border Name="HintTextBorder"
|
||||||
Background="#1a1a1a"
|
Background="#1a1a1a"
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using System.Drawing;
|
||||||
|
using Ink_Canvas.Helpers;
|
||||||
using Brushes = System.Windows.Media.Brushes;
|
using Brushes = System.Windows.Media.Brushes;
|
||||||
using Color = System.Windows.Media.Color;
|
using Color = System.Windows.Media.Color;
|
||||||
using DrawingRectangle = System.Drawing.Rectangle;
|
using DrawingRectangle = System.Drawing.Rectangle;
|
||||||
|
using DrawingPoint = System.Drawing.Point;
|
||||||
|
using WpfPoint = System.Windows.Point;
|
||||||
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
|
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
|
||||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||||
using WpfCanvas = System.Windows.Controls.Canvas;
|
using WpfCanvas = System.Windows.Controls.Canvas;
|
||||||
@@ -22,13 +26,16 @@ namespace Ink_Canvas
|
|||||||
private bool _isFreehandMode;
|
private bool _isFreehandMode;
|
||||||
private bool _isAdjusting;
|
private bool _isAdjusting;
|
||||||
private bool _isMoving;
|
private bool _isMoving;
|
||||||
private Point _startPoint;
|
private bool _isCameraMode;
|
||||||
private Point _currentPoint;
|
private WpfPoint _startPoint;
|
||||||
private Point _lastMousePosition;
|
private WpfPoint _currentPoint;
|
||||||
private List<Point> _freehandPoints;
|
private WpfPoint _lastMousePosition;
|
||||||
|
private List<WpfPoint> _freehandPoints;
|
||||||
private Polyline _freehandPolyline;
|
private Polyline _freehandPolyline;
|
||||||
private Rect _currentSelection;
|
private Rect _currentSelection;
|
||||||
private ControlPointType _activeControlPoint = ControlPointType.None;
|
private ControlPointType _activeControlPoint = ControlPointType.None;
|
||||||
|
private CameraService _cameraService;
|
||||||
|
private Bitmap _capturedCameraImage;
|
||||||
|
|
||||||
// 控制点类型枚举
|
// 控制点类型枚举
|
||||||
private enum ControlPointType
|
private enum ControlPointType
|
||||||
@@ -40,7 +47,8 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DrawingRectangle? SelectedArea { get; private set; }
|
public DrawingRectangle? SelectedArea { get; private set; }
|
||||||
public List<Point> SelectedPath { get; private set; }
|
public List<WpfPoint> SelectedPath { get; private set; }
|
||||||
|
public Bitmap CameraImage { get; private set; }
|
||||||
|
|
||||||
public ScreenshotSelectorWindow()
|
public ScreenshotSelectorWindow()
|
||||||
{
|
{
|
||||||
@@ -58,6 +66,9 @@ namespace Ink_Canvas
|
|||||||
// 初始化按钮状态
|
// 初始化按钮状态
|
||||||
InitializeButtonStates();
|
InitializeButtonStates();
|
||||||
|
|
||||||
|
// 初始化摄像头服务
|
||||||
|
InitializeCameraService();
|
||||||
|
|
||||||
// 隐藏提示文字的定时器
|
// 隐藏提示文字的定时器
|
||||||
var timer = new DispatcherTimer();
|
var timer = new DispatcherTimer();
|
||||||
timer.Interval = TimeSpan.FromSeconds(5);
|
timer.Interval = TimeSpan.FromSeconds(5);
|
||||||
@@ -71,7 +82,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
private void InitializeFreehandMode()
|
private void InitializeFreehandMode()
|
||||||
{
|
{
|
||||||
_freehandPoints = new List<Point>();
|
_freehandPoints = new List<WpfPoint>();
|
||||||
_freehandPolyline = new Polyline
|
_freehandPolyline = new Polyline
|
||||||
{
|
{
|
||||||
Stroke = Brushes.White,
|
Stroke = Brushes.White,
|
||||||
@@ -86,6 +97,126 @@ namespace Ink_Canvas
|
|||||||
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeCameraService()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cameraService = new CameraService();
|
||||||
|
_cameraService.FrameReceived += CameraService_FrameReceived;
|
||||||
|
_cameraService.ErrorOccurred += CameraService_ErrorOccurred;
|
||||||
|
|
||||||
|
// 初始化摄像头选择下拉框
|
||||||
|
RefreshCameraComboBox();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"初始化摄像头服务失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshCameraComboBox()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CameraSelectionComboBox.Items.Clear();
|
||||||
|
|
||||||
|
if (_cameraService.HasAvailableCameras())
|
||||||
|
{
|
||||||
|
var cameraNames = _cameraService.GetCameraNames();
|
||||||
|
foreach (var name in cameraNames)
|
||||||
|
{
|
||||||
|
CameraSelectionComboBox.Items.Add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cameraNames.Count > 0)
|
||||||
|
{
|
||||||
|
CameraSelectionComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CameraSelectionComboBox.Items.Add("未找到摄像头设备");
|
||||||
|
CameraSelectionComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CameraService_FrameReceived(object sender, Bitmap frame)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
if (_isCameraMode && CameraPreviewImage != null && frame != null)
|
||||||
|
{
|
||||||
|
// 克隆帧以避免在转换过程中被释放
|
||||||
|
using (var clonedFrame = frame.Clone() as Bitmap)
|
||||||
|
{
|
||||||
|
if (clonedFrame != null)
|
||||||
|
{
|
||||||
|
var bitmapSource = ConvertBitmapToBitmapSource(clonedFrame);
|
||||||
|
if (bitmapSource != null)
|
||||||
|
{
|
||||||
|
CameraPreviewImage.Source = bitmapSource;
|
||||||
|
CameraStatusText.Text = "摄像头已连接";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"处理摄像头帧失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CameraService_ErrorOccurred(object sender, string error)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
CameraStatusText.Text = $"摄像头错误: {error}";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"处理摄像头错误失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Windows.Media.Imaging.BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var memory = new System.IO.MemoryStream())
|
||||||
|
{
|
||||||
|
bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
|
||||||
|
memory.Position = 0;
|
||||||
|
|
||||||
|
var bitmapImage = new System.Windows.Media.Imaging.BitmapImage();
|
||||||
|
bitmapImage.BeginInit();
|
||||||
|
bitmapImage.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad;
|
||||||
|
bitmapImage.StreamSource = memory;
|
||||||
|
bitmapImage.EndInit();
|
||||||
|
bitmapImage.Freeze();
|
||||||
|
|
||||||
|
return bitmapImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"转换位图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BindControlPointEvents()
|
private void BindControlPointEvents()
|
||||||
@@ -181,14 +312,100 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 设置全屏截图模式
|
// 设置全屏截图模式
|
||||||
_isFreehandMode = false;
|
_isFreehandMode = false;
|
||||||
|
_isCameraMode = false;
|
||||||
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
|
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
|
||||||
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
|
||||||
|
// 隐藏摄像头预览
|
||||||
|
CameraPreviewBorder.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
// 直接执行全屏截图
|
// 直接执行全屏截图
|
||||||
PerformFullScreenCapture();
|
PerformFullScreenCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CameraModeButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 重置所有选择状态
|
||||||
|
ResetSelectionState();
|
||||||
|
|
||||||
|
// 设置摄像头模式
|
||||||
|
_isFreehandMode = false;
|
||||||
|
_isCameraMode = true;
|
||||||
|
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
|
||||||
|
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
|
||||||
|
// 显示摄像头预览
|
||||||
|
CameraPreviewBorder.Visibility = Visibility.Visible;
|
||||||
|
HintText.Text = "摄像头预览模式,点击确认截图按钮进行截图";
|
||||||
|
HintTextBorder.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
// 启动摄像头预览
|
||||||
|
if (_cameraService != null && _cameraService.HasAvailableCameras())
|
||||||
|
{
|
||||||
|
var selectedIndex = CameraSelectionComboBox.SelectedIndex;
|
||||||
|
if (selectedIndex >= 0 && selectedIndex < _cameraService.GetCameraNames().Count)
|
||||||
|
{
|
||||||
|
_cameraService.StartPreview(selectedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CameraStatusText.Text = "未找到摄像头设备";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"启动摄像头模式失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
CameraStatusText.Text = $"启动摄像头失败: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CameraSelectionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_isCameraMode && _cameraService != null)
|
||||||
|
{
|
||||||
|
var selectedIndex = CameraSelectionComboBox.SelectedIndex;
|
||||||
|
if (selectedIndex >= 0 && selectedIndex < _cameraService.GetCameraNames().Count)
|
||||||
|
{
|
||||||
|
_cameraService.SwitchCamera(selectedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SwitchCameraButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_cameraService != null && _cameraService.HasAvailableCameras())
|
||||||
|
{
|
||||||
|
var cameraNames = _cameraService.GetCameraNames();
|
||||||
|
if (cameraNames.Count > 1)
|
||||||
|
{
|
||||||
|
var currentIndex = CameraSelectionComboBox.SelectedIndex;
|
||||||
|
var nextIndex = (currentIndex + 1) % cameraNames.Count;
|
||||||
|
CameraSelectionComboBox.SelectedIndex = nextIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
// 在自由绘制模式下,确认按钮不执行任何操作
|
// 在自由绘制模式下,确认按钮不执行任何操作
|
||||||
@@ -197,9 +414,53 @@ namespace Ink_Canvas
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在摄像头模式下,执行摄像头截图
|
||||||
|
if (_isCameraMode)
|
||||||
|
{
|
||||||
|
ConfirmCameraCapture();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ConfirmSelection();
|
ConfirmSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ConfirmCameraCapture()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_cameraService != null && _cameraService.IsCapturing)
|
||||||
|
{
|
||||||
|
// 获取当前帧
|
||||||
|
var currentFrame = _cameraService.GetCurrentFrame();
|
||||||
|
if (currentFrame != null)
|
||||||
|
{
|
||||||
|
// 保存摄像头图像
|
||||||
|
CameraImage = currentFrame.Clone() as Bitmap;
|
||||||
|
|
||||||
|
// 停止摄像头预览
|
||||||
|
_cameraService.StopPreview();
|
||||||
|
|
||||||
|
// 设置结果并关闭窗口
|
||||||
|
DialogResult = true;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CameraStatusText.Text = "无法获取摄像头画面";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CameraStatusText.Text = "摄像头未启动";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"摄像头截图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
CameraStatusText.Text = $"摄像头截图失败: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
CancelSelection();
|
CancelSelection();
|
||||||
@@ -317,7 +578,7 @@ namespace Ink_Canvas
|
|||||||
if (_freehandPoints.Count > 1) // 只要有点就可以截图
|
if (_freehandPoints.Count > 1) // 只要有点就可以截图
|
||||||
{
|
{
|
||||||
// 创建路径的副本,避免修改原始列表
|
// 创建路径的副本,避免修改原始列表
|
||||||
var pathPoints = new List<Point>(_freehandPoints);
|
var pathPoints = new List<WpfPoint>(_freehandPoints);
|
||||||
|
|
||||||
// 简化路径处理,不强制闭合
|
// 简化路径处理,不强制闭合
|
||||||
// 如果路径没有闭合,自动添加起始点
|
// 如果路径没有闭合,自动添加起始点
|
||||||
@@ -661,13 +922,20 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
private void CancelSelection()
|
private void CancelSelection()
|
||||||
{
|
{
|
||||||
|
// 停止摄像头预览
|
||||||
|
if (_cameraService != null)
|
||||||
|
{
|
||||||
|
_cameraService.StopPreview();
|
||||||
|
}
|
||||||
|
|
||||||
SelectedArea = null;
|
SelectedArea = null;
|
||||||
SelectedPath = null;
|
SelectedPath = null;
|
||||||
|
CameraImage = null;
|
||||||
DialogResult = false;
|
DialogResult = false;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Rect CalculatePathBounds(List<Point> points)
|
private Rect CalculatePathBounds(List<WpfPoint> points)
|
||||||
{
|
{
|
||||||
if (points == null || points.Count == 0)
|
if (points == null || points.Count == 0)
|
||||||
return new Rect();
|
return new Rect();
|
||||||
@@ -688,12 +956,12 @@ namespace Ink_Canvas
|
|||||||
return new Rect(minX, minY, maxX - minX, maxY - minY);
|
return new Rect(minX, minY, maxX - minX, maxY - minY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Point> OptimizePath(List<Point> points)
|
private List<WpfPoint> OptimizePath(List<WpfPoint> points)
|
||||||
{
|
{
|
||||||
if (points == null || points.Count < 3)
|
if (points == null || points.Count < 3)
|
||||||
return points;
|
return points;
|
||||||
|
|
||||||
var optimized = new List<Point>();
|
var optimized = new List<WpfPoint>();
|
||||||
optimized.Add(points[0]);
|
optimized.Add(points[0]);
|
||||||
|
|
||||||
for (int i = 1; i < points.Count - 1; i++)
|
for (int i = 1; i < points.Count - 1; i++)
|
||||||
@@ -716,7 +984,7 @@ namespace Ink_Canvas
|
|||||||
return optimized;
|
return optimized;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double DistanceToLine(Point point, Point lineStart, Point lineEnd)
|
private double DistanceToLine(WpfPoint point, WpfPoint lineStart, WpfPoint lineEnd)
|
||||||
{
|
{
|
||||||
var A = point.X - lineStart.X;
|
var A = point.X - lineStart.X;
|
||||||
var B = point.Y - lineStart.Y;
|
var B = point.Y - lineStart.Y;
|
||||||
@@ -844,8 +1112,15 @@ namespace Ink_Canvas
|
|||||||
_isSelecting = false;
|
_isSelecting = false;
|
||||||
_isAdjusting = false;
|
_isAdjusting = false;
|
||||||
_isMoving = false;
|
_isMoving = false;
|
||||||
|
_isCameraMode = false;
|
||||||
_activeControlPoint = ControlPointType.None;
|
_activeControlPoint = ControlPointType.None;
|
||||||
|
|
||||||
|
// 停止摄像头预览
|
||||||
|
if (_cameraService != null)
|
||||||
|
{
|
||||||
|
_cameraService.StopPreview();
|
||||||
|
}
|
||||||
|
|
||||||
// 清除自由绘制的内容
|
// 清除自由绘制的内容
|
||||||
_freehandPoints.Clear();
|
_freehandPoints.Clear();
|
||||||
_freehandPolyline.Points.Clear();
|
_freehandPolyline.Points.Clear();
|
||||||
@@ -859,6 +1134,9 @@ namespace Ink_Canvas
|
|||||||
SelectionPath.Visibility = Visibility.Collapsed;
|
SelectionPath.Visibility = Visibility.Collapsed;
|
||||||
HintTextBorder.Visibility = Visibility.Collapsed;
|
HintTextBorder.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
// 隐藏摄像头预览
|
||||||
|
CameraPreviewBorder.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
// 重置遮罩
|
// 重置遮罩
|
||||||
TransparentSelectionMask.Visibility = Visibility.Collapsed;
|
TransparentSelectionMask.Visibility = Visibility.Collapsed;
|
||||||
OverlayRectangle.Visibility = Visibility.Visible;
|
OverlayRectangle.Visibility = Visibility.Visible;
|
||||||
@@ -879,10 +1157,38 @@ namespace Ink_Canvas
|
|||||||
_currentSelection = new Rect();
|
_currentSelection = new Rect();
|
||||||
SelectedArea = null;
|
SelectedArea = null;
|
||||||
SelectedPath = null;
|
SelectedPath = null;
|
||||||
|
CameraImage = null;
|
||||||
|
|
||||||
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosed(EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 清理摄像头资源
|
||||||
|
if (_cameraService != null)
|
||||||
|
{
|
||||||
|
_cameraService.StopPreview();
|
||||||
|
_cameraService.Dispose();
|
||||||
|
_cameraService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理摄像头图像
|
||||||
|
_capturedCameraImage?.Dispose();
|
||||||
|
CameraImage?.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"清理资源失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
base.OnClosed(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user