增加背景图案选项

This commit is contained in:
ww-rm
2025-09-22 23:23:01 +08:00
parent 3e88e65bbd
commit 798883d4e0
8 changed files with 145 additions and 4 deletions

View File

@@ -33,6 +33,8 @@ namespace SpineViewer.Models
public float Speed { get; set; } = 1f; public float Speed { get; set; } = 1f;
public bool ShowAxis { get; set; } = true; public bool ShowAxis { get; set; } = true;
public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105); public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105);
public string BackgroundImagePath { get; set; }
public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform;
#endregion #endregion

View File

@@ -121,7 +121,8 @@
<s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String> <s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String>
<s:String x:Key="Str_ShowAxis">Show Axis</s:String> <s:String x:Key="Str_ShowAxis">Show Axis</s:String>
<s:String x:Key="Str_BackgroundColor">Background Color</s:String> <s:String x:Key="Str_BackgroundColor">Background Color</s:String>
<s:String x:Key="Str_BackgroundImage">Background Image</s:String> <s:String x:Key="Str_BackgroundImagePath">Background Image Path</s:String>
<s:String x:Key="Str_BackgroundImageMode">Background Image Mode</s:String>
<!-- 渲染画面按钮组 --> <!-- 渲染画面按钮组 -->
<s:String x:Key="Str_StopTooltip">Stop</s:String> <s:String x:Key="Str_StopTooltip">Stop</s:String>

View File

@@ -121,7 +121,8 @@
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String> <s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
<s:String x:Key="Str_ShowAxis">座標軸を表示</s:String> <s:String x:Key="Str_ShowAxis">座標軸を表示</s:String>
<s:String x:Key="Str_BackgroundColor">背景色</s:String> <s:String x:Key="Str_BackgroundColor">背景色</s:String>
<s:String x:Key="Str_BackgroundImage">背景画像</s:String> <s:String x:Key="Str_BackgroundImagePath">背景画像のパス</s:String>
<s:String x:Key="Str_BackgroundImageMode">背景画像のモード</s:String>
<!-- 渲染画面按钮组 --> <!-- 渲染画面按钮组 -->
<s:String x:Key="Str_StopTooltip">停止</s:String> <s:String x:Key="Str_StopTooltip">停止</s:String>

View File

@@ -121,7 +121,8 @@
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String> <s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
<s:String x:Key="Str_ShowAxis">显示坐标轴</s:String> <s:String x:Key="Str_ShowAxis">显示坐标轴</s:String>
<s:String x:Key="Str_BackgroundColor">背景颜色</s:String> <s:String x:Key="Str_BackgroundColor">背景颜色</s:String>
<s:String x:Key="Str_BackgroundImage">背景图片</s:String> <s:String x:Key="Str_BackgroundImagePath">背景图片路径</s:String>
<s:String x:Key="Str_BackgroundImageMode">背景图片模式</s:String>
<!-- 渲染画面按钮组 --> <!-- 渲染画面按钮组 -->
<s:String x:Key="Str_StopTooltip">停止</s:String> <s:String x:Key="Str_StopTooltip">停止</s:String>

View File

@@ -90,6 +90,22 @@ namespace SpineViewer.Services
return false; return false;
} }
public static bool ShowOpenSFMLImageDialog(out string? fileName, string initialDirectory = "")
{
var dialog = new OpenFileDialog()
{
InitialDirectory = initialDirectory,
Filter = "SFML Image|*.png;*.jpg;*.jpeg;*.bmp;*.tga|All|*.*"
};
if (dialog.ShowDialog() is true)
{
fileName = dialog.FileName;
return true;
}
fileName = null;
return false;
}
public static bool ShowOpenJsonDialog(out string? fileName, string initialDirectory = "") public static bool ShowOpenJsonDialog(out string? fileName, string initialDirectory = "")
{ {
var dialog = new OpenFileDialog() var dialog = new OpenFileDialog()

View File

@@ -10,8 +10,10 @@ using SpineViewer.Services;
using SpineViewer.Utils; using SpineViewer.Utils;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -23,6 +25,8 @@ namespace SpineViewer.ViewModels.MainWindow
{ {
public class SFMLRendererViewModel : ObservableObject public class SFMLRendererViewModel : ObservableObject
{ {
public ImmutableArray<Stretch> StretchOptions { get; } = Enum.GetValues<Stretch>().ToImmutableArray();
/// <summary> /// <summary>
/// 日志器 /// 日志器
/// </summary> /// </summary>
@@ -69,6 +73,13 @@ namespace SpineViewer.ViewModels.MainWindow
private float _forwardDelta = 0; private float _forwardDelta = 0;
private readonly object _forwardDeltaLock = new(); private readonly object _forwardDeltaLock = new();
/// <summary>
/// 背景图片
/// </summary>
private SFML.Graphics.Sprite? _backgroundImageSprite; // XXX: 暂时未使用 Dispose 释放
private SFML.Graphics.Texture? _backgroundImageTexture; // XXX: 暂时未使用 Dispose 释放
private readonly object _bgLock = new();
/// <summary> /// <summary>
/// 临时变量, 记录拖放世界源点 /// 临时变量, 记录拖放世界源点
/// </summary> /// </summary>
@@ -169,6 +180,62 @@ namespace SpineViewer.ViewModels.MainWindow
} }
private SFML.Graphics.Color _backgroundColor = new(105, 105, 105); private SFML.Graphics.Color _backgroundColor = new(105, 105, 105);
public string BackgroundImagePath
{
get => _backgroundImagePath;
set => SetProperty(_backgroundImagePath, value, v =>
{
if (string.IsNullOrWhiteSpace(v))
{
lock (_bgLock)
{
_backgroundImageSprite?.Dispose();
_backgroundImageTexture?.Dispose();
_backgroundImageTexture = null;
_backgroundImageSprite = null;
}
_backgroundImagePath = v;
}
else
{
if (!File.Exists(v))
{
_logger.Warn("Omit non-existed background image path, {0}", v);
return;
}
SFML.Graphics.Texture tex = null;
SFML.Graphics.Sprite sprite = null;
try
{
tex = new(v);
sprite = new(tex) { Origin = new(tex.Size.X / 2f, tex.Size.Y / 2f) };
lock (_bgLock)
{
_backgroundImageSprite?.Dispose();
_backgroundImageTexture?.Dispose();
_backgroundImageTexture = tex;
_backgroundImageSprite = sprite;
}
_backgroundImagePath = v;
}
catch (Exception ex)
{
sprite?.Dispose();
tex?.Dispose();
_logger.Error("Failed to load background image from path: {0}, {1}", v, ex.Message);
}
}
});
}
private string _backgroundImagePath;
public Stretch BackgroundImageMode
{
get => _backgroundImageMode;
set => SetProperty(ref _backgroundImageMode, value);
}
private Stretch _backgroundImageMode = Stretch.Uniform;
public bool RenderSelectedOnly public bool RenderSelectedOnly
{ {
get => _renderSelectedOnly; get => _renderSelectedOnly;
@@ -189,6 +256,14 @@ namespace SpineViewer.ViewModels.MainWindow
} }
private bool _isUpdating = true; private bool _isUpdating = true;
public RelayCommand Cmd_SelectBackgroundImage => _cmd_SelectBackgroundImage ??= new(() =>
{
if (!DialogService.ShowOpenSFMLImageDialog(out var fileName))
return;
BackgroundImagePath = fileName;
});
private RelayCommand? _cmd_SelectBackgroundImage;
public RelayCommand Cmd_Stop => _cmd_Stop ??= new(() => public RelayCommand Cmd_Stop => _cmd_Stop ??= new(() =>
{ {
IsUpdating = false; IsUpdating = false;
@@ -386,6 +461,38 @@ namespace SpineViewer.ViewModels.MainWindow
_renderer.Clear(_backgroundColor); _renderer.Clear(_backgroundColor);
// 渲染背景
lock (_bgLock)
{
if (_backgroundImageSprite is not null)
{
using var view = _renderer.GetView();
var bg = _backgroundImageSprite;
var viewSize = view.Size;
var bgSize = bg.Texture.Size;
var scaleX = Math.Abs(viewSize.X / bgSize.X);
var scaleY = Math.Abs(viewSize.Y / bgSize.Y);
var signX = Math.Sign(viewSize.X);
var signY = Math.Sign(viewSize.Y);
if (_backgroundImageMode == Stretch.None)
{
scaleX = scaleY = 1f / _renderer.Zoom;
}
else if (_backgroundImageMode == Stretch.Uniform)
{
scaleX = scaleY = Math.Min(scaleX, scaleY);
}
else if (_backgroundImageMode == Stretch.UniformToFill)
{
scaleX = scaleY = Math.Max(scaleX, scaleY);
}
bg.Scale = new(signX * scaleX, signY * scaleY);
bg.Position = view.Center;
bg.Rotation = view.Rotation;
_renderer.Draw(bg);
}
}
if (_showAxis) if (_showAxis)
{ {
// 画一个很长的坐标轴, 用 1e9 比较合适 // 画一个很长的坐标轴, 用 1e9 比较合适

View File

@@ -727,6 +727,8 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- 水平分辨率 --> <!-- 水平分辨率 -->
@@ -780,7 +782,15 @@
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/> <TextBox Grid.Row="12" Grid.Column="1" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
<!-- 背景图案 --> <!-- 背景图案 -->
<Label Grid.Row="13" Grid.Column="0" Content="{DynamicResource Str_BackgroundImagePath}"/>
<DockPanel Grid.Row="13" Grid.Column="1" >
<Button DockPanel.Dock="Right" Content="..." Command="{Binding Cmd_SelectBackgroundImage}"/>
<TextBox Text="{Binding BackgroundImagePath}"/>
</DockPanel>
<!-- 背景图案模式 --> <!-- 背景图案模式 -->
<Label Grid.Row="14" Grid.Column="0" Content="{DynamicResource Str_BackgroundImageMode}"/>
<ComboBox Grid.Row="14" Grid.Column="1" SelectedValue="{Binding BackgroundImageMode}" ItemsSource="{Binding StretchOptions}"/>
</Grid> </Grid>
</TabItem> </TabItem>
</TabControl> </TabControl>

View File

@@ -157,6 +157,8 @@ public partial class MainWindow : Window
_vm.SFMLRendererViewModel.Speed = m.Speed; _vm.SFMLRendererViewModel.Speed = m.Speed;
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis; _vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor; _vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
_vm.SFMLRendererViewModel.BackgroundImagePath = m.BackgroundImagePath;
_vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode;
} }
} }
@@ -181,6 +183,8 @@ public partial class MainWindow : Window
Speed = _vm.SFMLRendererViewModel.Speed, Speed = _vm.SFMLRendererViewModel.Speed,
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis, ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor, BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
BackgroundImagePath = _vm.SFMLRendererViewModel.BackgroundImagePath,
BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode,
}; };
JsonHelper.Serialize(m, LastStateFilePath); JsonHelper.Serialize(m, LastStateFilePath);
@@ -596,5 +600,4 @@ public partial class MainWindow : Window
} }
#endregion #endregion
} }