diff --git a/SpineViewer/Exporter/ExportArgs.cs b/SpineViewer/Exporter/ExportArgs.cs index 308e2f3..52315f0 100644 --- a/SpineViewer/Exporter/ExportArgs.cs +++ b/SpineViewer/Exporter/ExportArgs.cs @@ -67,10 +67,12 @@ namespace SpineViewer.Exporter public bool RenderSelectedOnly { get; } /// - /// 背景颜色 TODO: 提供颜色编辑 + /// 背景颜色 /// + [Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))] + [TypeConverter(typeof(SFMLColorConverter))] [Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")] - public SFML.Graphics.Color BackgroundColor { get; set; } = SFML.Graphics.Color.Transparent; + public SFML.Graphics.Color BackgroundColor { get; set; } = new(0, 255, 0, 0); /// /// 检查参数是否合法并规范化参数值, 否则返回用户错误原因 diff --git a/SpineViewer/Exporter/TypeConverter.cs b/SpineViewer/Exporter/TypeConverter.cs index aa47c67..7fe0bb5 100644 --- a/SpineViewer/Exporter/TypeConverter.cs +++ b/SpineViewer/Exporter/TypeConverter.cs @@ -23,4 +23,81 @@ namespace SpineViewer.Exporter return new StandardValuesCollection(supportedFileSuffix); } } + + public class SFMLColorConverter : ExpandableObjectConverter + { + private class SFMLColorPropertyDescriptor : SimplePropertyDescriptor + { + public SFMLColorPropertyDescriptor(Type componentType, string name, Type propertyType) : base(componentType, name, propertyType) { } + + public override object? GetValue(object? component) + { + return component?.GetType().GetField(Name)?.GetValue(component) ?? default; + } + + public override void SetValue(object? component, object? value) + { + component?.GetType().GetField(Name)?.SetValue(component, value); + } + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is string s) + { + s = s.Trim(); + if (s.StartsWith("#") && s.Length == 9) + { + try + { + // 解析 R, G, B, A 分量,注意16进制解析 + byte r = byte.Parse(s.Substring(1, 2), NumberStyles.HexNumber); + byte g = byte.Parse(s.Substring(3, 2), NumberStyles.HexNumber); + byte b = byte.Parse(s.Substring(5, 2), NumberStyles.HexNumber); + byte a = byte.Parse(s.Substring(7, 2), NumberStyles.HexNumber); + return new SFML.Graphics.Color(r, g, b, a); + } + catch (Exception ex) + { + throw new FormatException("无法解析颜色,确保格式为 #RRGGBBAA", ex); + } + } + throw new FormatException("格式错误,正确格式为 #RRGGBBAA"); + } + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + { + return destinationType == typeof(string) || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (destinationType == typeof(string) && value is SFML.Graphics.Color color) + return $"#{color.R:X2}{color.G:X2}{color.B:X2}{color.A:X2}"; + return base.ConvertTo(context, culture, value, destinationType); + } + + public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes) + { + // 自定义属性集合 + var properties = new List + { + // 定义 R, G, B, A 四个字段的描述器 + new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "R", typeof(byte)), + new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "G", typeof(byte)), + new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "B", typeof(byte)), + new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "A", typeof(byte)) + }; + + // 返回自定义属性集合 + return new PropertyDescriptorCollection(properties.ToArray()); + } + } } diff --git a/SpineViewer/Exporter/UITypeEditor.cs b/SpineViewer/Exporter/UITypeEditor.cs new file mode 100644 index 0000000..e6e58b3 --- /dev/null +++ b/SpineViewer/Exporter/UITypeEditor.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing.Design; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SpineViewer.Exporter +{ + class SFMLColorEditor : UITypeEditor + { + public override bool GetPaintValueSupported(ITypeDescriptorContext? context) => true; + + public override void PaintValue(PaintValueEventArgs e) + { + if (e.Value is SFML.Graphics.Color color) + { + // 定义颜色和透明度的绘制区域 + var colorBox = new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width / 2, e.Bounds.Height); + var alphaBox = new Rectangle(e.Bounds.X + e.Bounds.Width / 2, e.Bounds.Y, e.Bounds.Width / 2, e.Bounds.Height); + + // 转换为 System.Drawing.Color + var drawColor = Color.FromArgb(color.A, color.R, color.G, color.B); + + // 绘制纯颜色(RGB 部分) + using (var brush = new SolidBrush(Color.FromArgb(color.R, color.G, color.B))) + { + e.Graphics.FillRectangle(brush, colorBox); + e.Graphics.DrawRectangle(Pens.Black, colorBox); + } + + // 绘制带透明度效果的颜色 + using (var checkerBrush = CreateTransparencyBrush()) + { + e.Graphics.FillRectangle(checkerBrush, alphaBox); // 背景棋盘格 + } + using (var brush = new SolidBrush(drawColor)) + { + e.Graphics.FillRectangle(brush, alphaBox); // 叠加透明颜色 + e.Graphics.DrawRectangle(Pens.Black, alphaBox); + } + } + else + { + base.PaintValue(e); + } + } + + // 创建一个透明背景的棋盘格图案画刷 + private static TextureBrush CreateTransparencyBrush() + { + var bitmap = new Bitmap(8, 8); + using (var g = Graphics.FromImage(bitmap)) + { + g.Clear(Color.White); + using (var grayBrush = new SolidBrush(Color.LightGray)) + { + g.FillRectangle(grayBrush, 0, 0, 4, 4); + g.FillRectangle(grayBrush, 4, 4, 4, 4); + } + } + return new TextureBrush(bitmap); + } + } +}