#0016 优化集成图像库

This commit is contained in:
zhengxuan.zhang
2026-03-14 20:39:34 +08:00
parent 47e163f774
commit 1214da13d2
17 changed files with 293 additions and 67 deletions
+1
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.IO;
using System.Windows; using System.Windows;
using XplorePlane.Views; using XplorePlane.Views;
using XplorePlane.ViewModels; using XplorePlane.ViewModels;
@@ -11,6 +11,7 @@ namespace XplorePlane.Services
{ {
IReadOnlyList<string> GetAvailableProcessors(); IReadOnlyList<string> GetAvailableProcessors();
IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName); IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName);
ImageProcessorBase GetProcessor(string processorName);
void RegisterProcessor(string name, ImageProcessorBase processor); void RegisterProcessor(string name, ImageProcessorBase processor);
Task<BitmapSource> ProcessImageAsync( Task<BitmapSource> ProcessImageAsync(
@@ -71,6 +71,13 @@ namespace XplorePlane.Services
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName)); throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
} }
public ImageProcessorBase GetProcessor(string processorName)
{
if (_processorRegistry.TryGetValue(processorName, out var processor))
return processor;
throw new ArgumentException($"Processor not registered or is 16-bit only: {processorName}", nameof(processorName));
}
public async Task<BitmapSource> ProcessImageAsync( public async Task<BitmapSource> ProcessImageAsync(
BitmapSource source, BitmapSource source,
string processorName, string processorName,
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Microsoft.Win32;
using Prism.Commands; using Prism.Commands;
using Prism.Mvvm; using Prism.Mvvm;
using Serilog; using Serilog;
@@ -36,6 +39,8 @@ namespace XplorePlane.ViewModels
SelectProcessorCommand = new DelegateCommand<string>(OnSelectProcessor); SelectProcessorCommand = new DelegateCommand<string>(OnSelectProcessor);
ApplyProcessingCommand = new DelegateCommand(OnApplyProcessing); ApplyProcessingCommand = new DelegateCommand(OnApplyProcessing);
ResetImageCommand = new DelegateCommand(OnResetImage); ResetImageCommand = new DelegateCommand(OnResetImage);
LoadImageCommand = new DelegateCommand(OnLoadImage);
SaveResultCommand = new DelegateCommand(OnSaveResult, () => CurrentImage != null);
} }
public ObservableCollection<string> AvailableProcessors { get; } public ObservableCollection<string> AvailableProcessors { get; }
@@ -50,7 +55,11 @@ namespace XplorePlane.ViewModels
public BitmapSource CurrentImage public BitmapSource CurrentImage
{ {
get => _currentImage; get => _currentImage;
set => SetProperty(ref _currentImage, value); set
{
SetProperty(ref _currentImage, value);
SaveResultCommand?.RaiseCanExecuteChanged();
}
} }
public BitmapSource OriginalImage public BitmapSource OriginalImage
@@ -80,6 +89,8 @@ namespace XplorePlane.ViewModels
public DelegateCommand<string> SelectProcessorCommand { get; } public DelegateCommand<string> SelectProcessorCommand { get; }
public DelegateCommand ApplyProcessingCommand { get; } public DelegateCommand ApplyProcessingCommand { get; }
public DelegateCommand ResetImageCommand { get; } public DelegateCommand ResetImageCommand { get; }
public DelegateCommand LoadImageCommand { get; }
public DelegateCommand SaveResultCommand { get; }
private void OnSelectProcessor(string processorName) private void OnSelectProcessor(string processorName)
{ {
@@ -153,5 +164,67 @@ namespace XplorePlane.ViewModels
StatusMessage = "Image reset to original"; StatusMessage = "Image reset to original";
ProcessingProgress = 0; ProcessingProgress = 0;
} }
private void OnLoadImage()
{
var dlg = new OpenFileDialog
{
Title = "加载图像",
Filter = "图像文件|*.bmp;*.png;*.jpg;*.jpeg;*.tif;*.tiff|所有文件|*.*"
};
if (dlg.ShowDialog() != true) return;
try
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(dlg.FileName);
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
OriginalImage = bitmap;
CurrentImage = bitmap;
StatusMessage = $"已加载:{Path.GetFileName(dlg.FileName)}";
ProcessingProgress = 0;
}
catch (Exception ex)
{
StatusMessage = $"加载失败:{ex.Message}";
_logger.Error(ex, "Failed to load image: {Path}", dlg.FileName);
}
}
private void OnSaveResult()
{
if (CurrentImage == null) return;
var dlg = new SaveFileDialog
{
Title = "保存结果",
Filter = "PNG 图像|*.png|BMP 图像|*.bmp|JPEG 图像|*.jpg",
DefaultExt = ".png"
};
if (dlg.ShowDialog() != true) return;
try
{
BitmapEncoder encoder = Path.GetExtension(dlg.FileName).ToLower() switch
{
".bmp" => new BmpBitmapEncoder(),
".jpg" or ".jpeg" => new JpegBitmapEncoder(),
_ => new PngBitmapEncoder()
};
encoder.Frames.Add(BitmapFrame.Create(CurrentImage));
using var stream = File.OpenWrite(dlg.FileName);
encoder.Save(stream);
StatusMessage = $"已保存:{Path.GetFileName(dlg.FileName)}";
}
catch (Exception ex)
{
StatusMessage = $"保存失败:{ex.Message}";
_logger.Error(ex, "Failed to save image: {Path}", dlg.FileName);
}
}
} }
} }
+7
View File
@@ -24,6 +24,7 @@ namespace XplorePlane.ViewModels
public DelegateCommand ExportCommand { get; set; } public DelegateCommand ExportCommand { get; set; }
public DelegateCommand ClearCommand { get; set; } public DelegateCommand ClearCommand { get; set; }
public DelegateCommand EditPropertiesCommand { get; set; } public DelegateCommand EditPropertiesCommand { get; set; }
public DelegateCommand OpenImageProcessingCommand { get; set; }
public MainViewModel(ILogger logger) public MainViewModel(ILogger logger)
{ {
@@ -37,6 +38,12 @@ namespace XplorePlane.ViewModels
ExportCommand = new DelegateCommand(OnExport); ExportCommand = new DelegateCommand(OnExport);
ClearCommand = new DelegateCommand(OnClear); ClearCommand = new DelegateCommand(OnClear);
EditPropertiesCommand = new DelegateCommand(OnEditProperties); EditPropertiesCommand = new DelegateCommand(OnEditProperties);
OpenImageProcessingCommand = new DelegateCommand(() =>
{
var window = new Views.ImageProcessingWindow();
window.Show();
_logger.Information("图像处理窗口已打开");
});
_logger.Information("MainViewModel 已初始化"); _logger.Information("MainViewModel 已初始化");
} }
@@ -11,7 +11,7 @@ namespace XplorePlane.ViewModels
{ {
Name = parameter.Name; Name = parameter.Name;
DisplayName = parameter.DisplayName; DisplayName = parameter.DisplayName;
_value = parameter.DefaultValue; _value = parameter.Value;
MinValue = parameter.MinValue; MinValue = parameter.MinValue;
MaxValue = parameter.MaxValue; MaxValue = parameter.MaxValue;
ParameterType = parameter.ValueType?.Name?.ToLower() switch ParameterType = parameter.ValueType?.Name?.ToLower() switch
+120 -58
View File
@@ -3,73 +3,135 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:ImageProcessing.Controls;assembly=ImageProcessing.Controls"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800"> d:DesignHeight="700" d:DesignWidth="1000">
<Grid> <Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<!-- 主内容区:三栏布局 -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/> <ColumnDefinition Width="160" MinWidth="140" MaxWidth="200" />
<ColumnDefinition Width="*"/> <ColumnDefinition Width="5" />
<ColumnDefinition Width="*" MinWidth="400" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="300" MinWidth="260" MaxWidth="380" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Left panel: processor selection + parameters --> <!-- 左侧:操作按钮 -->
<Grid Grid.Column="0" Margin="5"> <Border Grid.Column="0"
<Grid.RowDefinitions> Background="#FAFAFA"
<RowDefinition Height="Auto"/> BorderBrush="#DDDDDD"
<RowDefinition Height="200"/> BorderThickness="0,0,1,0">
<RowDefinition Height="Auto"/> <ScrollViewer VerticalScrollBarVisibility="Auto">
<RowDefinition Height="*"/> <StackPanel>
<RowDefinition Height="Auto"/> <Button Content="加载图像"
<RowDefinition Height="Auto"/> Height="60" Margin="8,8,8,4"
</Grid.RowDefinitions> Command="{Binding LoadImageCommand}" />
<Button Content="处理图像"
<TextBlock Grid.Row="0" Text="Processors" FontWeight="Bold" Margin="0,0,0,5"/> Height="60" Margin="8,4,8,4"
Command="{Binding ApplyProcessingCommand}" />
<ListBox Grid.Row="1" <Button Content="重置图像"
ItemsSource="{Binding AvailableProcessors}" Height="60" Margin="8,4,8,4"
SelectedItem="{Binding SelectedProcessor}"> Command="{Binding ResetImageCommand}" />
<ListBox.ItemContainerStyle> <Button Content="保存结果"
<Style TargetType="ListBoxItem"> Height="60" Margin="8,4,8,8"
<EventSetter Event="Selected" Command="{Binding SaveResultCommand}" />
Handler="OnProcessorSelected"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<TextBlock Grid.Row="2" Text="Parameters" FontWeight="Bold" Margin="0,10,0,5"/>
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding CurrentParameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,2">
<TextBlock Text="{Binding DisplayName}"/>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel> </StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer> </ScrollViewer>
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="0,10,0,5">
<Button Content="Apply" Command="{Binding ApplyProcessingCommand}" Margin="0,0,5,0" Width="80"/>
<Button Content="Reset" Command="{Binding ResetImageCommand}" Width="80"/>
</StackPanel>
<ProgressBar Grid.Row="5" Value="{Binding ProcessingProgress}" Minimum="0" Maximum="1" Height="10"/>
</Grid>
<!-- Right panel: image preview + status -->
<Grid Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1">
<Image Source="{Binding CurrentImage}" Stretch="Uniform"/>
</Border> </Border>
<TextBlock Grid.Row="1" Text="{Binding StatusMessage}" Margin="0,5,0,0" TextWrapping="Wrap"/> <!-- GridSplitter 1 -->
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Background="#E0E0E0" />
<!-- 中间:原始图像 + 处理结果 -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GroupBox Header="原始图像" Grid.Row="0" Margin="5,5,5,3">
<Border BorderBrush="#CCCCCC" BorderThickness="1" Background="Black">
<Image Source="{Binding OriginalImage}"
Stretch="Uniform"
RenderOptions.BitmapScalingMode="HighQuality" />
</Border>
</GroupBox>
<GroupBox Header="处理结果" Grid.Row="1" Margin="5,3,5,5">
<Border BorderBrush="#CCCCCC" BorderThickness="1" Background="Black">
<Image Source="{Binding CurrentImage}"
Stretch="Uniform"
RenderOptions.BitmapScalingMode="HighQuality" />
</Border>
</GroupBox>
</Grid> </Grid>
<!-- GridSplitter 2 -->
<GridSplitter Grid.Column="3" Width="5" HorizontalAlignment="Stretch" Background="#E0E0E0" />
<!-- 右侧:算子选择 + 参数配置 -->
<Border Grid.Column="4"
Background="#FAFAFA"
BorderBrush="#DDDDDD"
BorderThickness="1,0,0,0">
<ScrollViewer VerticalScrollBarVisibility="Auto" VerticalAlignment="Top">
<StackPanel Margin="10">
<!-- 算子选择 -->
<GroupBox Header="选择算子" Margin="0,0,0,10">
<ComboBox x:Name="cmbProcessors"
Height="32" Margin="5"
VerticalContentAlignment="Center"
ItemsSource="{Binding AvailableProcessors}"
SelectedItem="{Binding SelectedProcessor}"
SelectionChanged="OnProcessorComboChanged" />
</GroupBox>
<!-- 参数配置 (复用 DLL 里的 ProcessorParameterControl) -->
<GroupBox Header="参数配置" Margin="0,0,0,10">
<controls:ProcessorParameterControl
x:Name="parameterControl"
Height="350"
Margin="5,0,5,0"
ParameterChanged="OnParameterChanged" />
</GroupBox>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
<!-- 状态栏 -->
<Border Grid.Row="1"
Background="#F5F5F5"
BorderBrush="#CCCCCC"
BorderThickness="0,1,0,0"
Padding="10,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="状态:" FontWeight="Bold" />
<TextBlock Text="{Binding StatusMessage}" Margin="4,0,0,0" />
</StackPanel>
<ProgressBar Grid.Column="2"
Value="{Binding ProcessingProgress}"
Minimum="0" Maximum="1"
Height="12"
VerticalAlignment="Center"
Margin="0,0,10,0" />
</Grid>
</Border>
</Grid> </Grid>
</UserControl> </UserControl>
@@ -1,20 +1,52 @@
using System.Windows.Controls; using System;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using Prism.Ioc;
using XplorePlane.Services;
using XplorePlane.ViewModels; using XplorePlane.ViewModels;
namespace XplorePlane.Views namespace XplorePlane.Views
{ {
public partial class ImageProcessingPanelView : UserControl public partial class ImageProcessingPanelView : UserControl
{ {
private IImageProcessingService _imageProcessingService;
public ImageProcessingPanelView() public ImageProcessingPanelView()
{ {
InitializeComponent(); InitializeComponent();
Loaded += OnLoaded;
} }
private void OnProcessorSelected(object sender, RoutedEventArgs e) private void OnLoaded(object sender, RoutedEventArgs e)
{ {
if (DataContext is ImageProcessingViewModel vm && sender is ListBoxItem item) if (DataContext == null)
vm.SelectProcessorCommand.Execute(item.Content as string); DataContext = ContainerLocator.Container.Resolve<ImageProcessingViewModel>();
_imageProcessingService = ContainerLocator.Container.Resolve<IImageProcessingService>();
}
private void OnProcessorComboChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ComboBox cmb && cmb.SelectedItem is string processorName
&& _imageProcessingService != null)
{
try
{
var processor = _imageProcessingService.GetProcessor(processorName);
parameterControl.LoadProcessor(processor);
}
catch (ArgumentException)
{
// 16-bit processor — no UI parameters supported via ProcessorParameterControl
parameterControl.Clear();
}
}
}
private void OnParameterChanged(object sender, EventArgs e)
{
// Parameters are applied directly to the processor instance inside ProcessorParameterControl.
// Nothing extra needed here — ApplyProcessingCommand will pick them up via the service.
} }
} }
} }
@@ -0,0 +1,11 @@
<Window x:Class="XplorePlane.Views.ImageProcessingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:XplorePlane.Views"
Title="图像处理"
Width="1000" Height="700"
MinWidth="800" MinHeight="500"
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False">
<views:ImageProcessingPanelView />
</Window>
@@ -0,0 +1,12 @@
using System.Windows;
namespace XplorePlane.Views
{
public partial class ImageProcessingWindow : Window
{
public ImageProcessingWindow()
{
InitializeComponent();
}
}
}
+13
View File
@@ -23,6 +23,7 @@
LightBasePath="/Telerik.Windows.Controls.Spreadsheet;component/Images/Light/" /> LightBasePath="/Telerik.Windows.Controls.Spreadsheet;component/Images/Light/" />
<spreadsheetControls:BoolToVisibilityValueConverter x:Key="BoolToVisibilityValueConverter" /> <spreadsheetControls:BoolToVisibilityValueConverter x:Key="BoolToVisibilityValueConverter" />
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Window.Resources> </Window.Resources>
<Grid x:Name="LayoutRoot"> <Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -1704,6 +1705,18 @@
</telerik:RadRibbonGroup> </telerik:RadRibbonGroup>
</telerik:RadRibbonTab> </telerik:RadRibbonTab>
<telerik:RadRibbonTab Header="工具">
<telerik:RadRibbonGroup Header="图像处理">
<telerik:RadRibbonGroup.Variants>
<telerik:GroupVariant Priority="0" Variant="Large" />
</telerik:RadRibbonGroup.Variants>
<telerik:RadRibbonButton
Command="{Binding OpenImageProcessingCommand}"
Size="Large"
Text="图像处理" />
</telerik:RadRibbonGroup>
</telerik:RadRibbonTab>
<telerik:RadRibbonView.ContextualGroups> <telerik:RadRibbonView.ContextualGroups>
<telerik:RadRibbonContextualGroup <telerik:RadRibbonContextualGroup
x:Name="PictureTools" x:Name="PictureTools"
+7
View File
@@ -27,6 +27,9 @@
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" /> <PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageReference Include="System.Data.SQLite" Version="1.0.118" /> <PackageReference Include="System.Data.SQLite" Version="1.0.118" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
<PackageReference Include="Emgu.CV" Version="4.10.0.5680" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.10.0.5680" />
<PackageReference Include="Emgu.CV.Bitmap" Version="4.10.0.5680" />
</ItemGroup> </ItemGroup>
<!-- DLL 引用 --> <!-- DLL 引用 -->
@@ -57,6 +60,10 @@
<HintPath>Libs\ImageProcessing\ImageProcessing.Processors.dll</HintPath> <HintPath>Libs\ImageProcessing\ImageProcessing.Processors.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="ImageProcessing.Controls">
<HintPath>Libs\ImageProcessing\ImageProcessing.Controls.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Telerik.Windows.Controls"> <Reference Include="Telerik.Windows.Controls">