From 798883d4e0df1d161920e6c6c3912a525e464820 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Mon, 22 Sep 2025 23:23:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=83=8C=E6=99=AF=E5=9B=BE?= =?UTF-8?q?=E6=A1=88=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpineViewer/Models/LastStateModel.cs | 2 + SpineViewer/Resources/Strings/en.xaml | 3 +- SpineViewer/Resources/Strings/ja.xaml | 3 +- SpineViewer/Resources/Strings/zh.xaml | 3 +- SpineViewer/Services/DialogService.cs | 16 +++ .../MainWindow/SFMLRendererViewModel.cs | 107 ++++++++++++++++++ SpineViewer/Views/MainWindow.xaml | 10 ++ SpineViewer/Views/MainWindow.xaml.cs | 5 +- 8 files changed, 145 insertions(+), 4 deletions(-) diff --git a/SpineViewer/Models/LastStateModel.cs b/SpineViewer/Models/LastStateModel.cs index c61455f..639132a 100644 --- a/SpineViewer/Models/LastStateModel.cs +++ b/SpineViewer/Models/LastStateModel.cs @@ -33,6 +33,8 @@ namespace SpineViewer.Models public float Speed { get; set; } = 1f; public bool ShowAxis { get; set; } = true; public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105); + public string BackgroundImagePath { get; set; } + public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform; #endregion diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml index f77be99..7cdda02 100644 --- a/SpineViewer/Resources/Strings/en.xaml +++ b/SpineViewer/Resources/Strings/en.xaml @@ -121,7 +121,8 @@ Render Selected Only Show Axis Background Color - Background Image + Background Image Path + Background Image Mode Stop diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml index 6a84772..5f4ef84 100644 --- a/SpineViewer/Resources/Strings/ja.xaml +++ b/SpineViewer/Resources/Strings/ja.xaml @@ -121,7 +121,8 @@ 選択のみレンダリング 座標軸を表示 背景色 - 背景画像 + 背景画像のパス + 背景画像のモード 停止 diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml index f138b4c..793fd7e 100644 --- a/SpineViewer/Resources/Strings/zh.xaml +++ b/SpineViewer/Resources/Strings/zh.xaml @@ -121,7 +121,8 @@ 仅渲染选中 显示坐标轴 背景颜色 - 背景图片 + 背景图片路径 + 背景图片模式 停止 diff --git a/SpineViewer/Services/DialogService.cs b/SpineViewer/Services/DialogService.cs index 6e11135..7587af2 100644 --- a/SpineViewer/Services/DialogService.cs +++ b/SpineViewer/Services/DialogService.cs @@ -90,6 +90,22 @@ namespace SpineViewer.Services 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 = "") { var dialog = new OpenFileDialog() diff --git a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs index 348c80e..7dc9751 100644 --- a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs @@ -10,8 +10,10 @@ using SpineViewer.Services; using SpineViewer.Utils; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.Specialized; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -23,6 +25,8 @@ namespace SpineViewer.ViewModels.MainWindow { public class SFMLRendererViewModel : ObservableObject { + public ImmutableArray StretchOptions { get; } = Enum.GetValues().ToImmutableArray(); + /// /// 日志器 /// @@ -69,6 +73,13 @@ namespace SpineViewer.ViewModels.MainWindow private float _forwardDelta = 0; private readonly object _forwardDeltaLock = new(); + /// + /// 背景图片 + /// + private SFML.Graphics.Sprite? _backgroundImageSprite; // XXX: 暂时未使用 Dispose 释放 + private SFML.Graphics.Texture? _backgroundImageTexture; // XXX: 暂时未使用 Dispose 释放 + private readonly object _bgLock = new(); + /// /// 临时变量, 记录拖放世界源点 /// @@ -169,6 +180,62 @@ namespace SpineViewer.ViewModels.MainWindow } 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 { get => _renderSelectedOnly; @@ -189,6 +256,14 @@ namespace SpineViewer.ViewModels.MainWindow } 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(() => { IsUpdating = false; @@ -386,6 +461,38 @@ namespace SpineViewer.ViewModels.MainWindow _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) { // 画一个很长的坐标轴, 用 1e9 比较合适 diff --git a/SpineViewer/Views/MainWindow.xaml b/SpineViewer/Views/MainWindow.xaml index 1089639..965473a 100644 --- a/SpineViewer/Views/MainWindow.xaml +++ b/SpineViewer/Views/MainWindow.xaml @@ -727,6 +727,8 @@ + + @@ -780,7 +782,15 @@ +