diff --git a/src/Beutl.Engine/Rendering/Renderer.cs b/src/Beutl.Engine/Rendering/Renderer.cs index 19b539838..40d9d4dd8 100644 --- a/src/Beutl.Engine/Rendering/Renderer.cs +++ b/src/Beutl.Engine/Rendering/Renderer.cs @@ -80,16 +80,22 @@ public IRenderer.RenderResult RenderGraphics(TimeSpan timeSpan) RenderThread.Dispatcher.VerifyAccess(); if (!IsGraphicsRendering) { - IsGraphicsRendering = true; - _instanceClock.CurrentTime = timeSpan; - RenderScene.Clear(); - using (_fpsText.StartRender(_immediateCanvas)) + try { - RenderGraphicsCore(); + IsGraphicsRendering = true; + _instanceClock.CurrentTime = timeSpan; + RenderScene.Clear(); + using (_fpsText.StartRender(_immediateCanvas)) + { + RenderGraphicsCore(); + } + + return new IRenderer.RenderResult(_immediateCanvas.GetBitmap()); + } + finally + { + IsGraphicsRendering = false; } - - IsGraphicsRendering = false; - return new IRenderer.RenderResult(_immediateCanvas.GetBitmap()); } else { @@ -111,12 +117,18 @@ public IRenderer.RenderResult RenderAudio(TimeSpan timeSpan) { if (!IsAudioRendering) { - IsAudioRendering = true; - _instanceClock.AudioStartTime = timeSpan; - RenderAudioCore(); + try + { + IsAudioRendering = true; + _instanceClock.AudioStartTime = timeSpan; + RenderAudioCore(); - IsAudioRendering = false; - return new IRenderer.RenderResult(Audio: _audio.GetPcm()); + return new IRenderer.RenderResult(Audio: _audio.GetPcm()); + } + finally + { + IsAudioRendering = false; + } } else { @@ -129,20 +141,26 @@ public IRenderer.RenderResult Render(TimeSpan timeSpan) RenderThread.Dispatcher.VerifyAccess(); if (!IsGraphicsRendering && !IsAudioRendering) { - IsGraphicsRendering = true; - IsAudioRendering = true; - _instanceClock.CurrentTime = timeSpan; - _instanceClock.AudioStartTime = timeSpan; - RenderScene.Clear(); - using (_fpsText.StartRender(_immediateCanvas)) + try { - RenderGraphicsCore(); - RenderAudioCore(); + IsGraphicsRendering = true; + IsAudioRendering = true; + _instanceClock.CurrentTime = timeSpan; + _instanceClock.AudioStartTime = timeSpan; + RenderScene.Clear(); + using (_fpsText.StartRender(_immediateCanvas)) + { + RenderGraphicsCore(); + RenderAudioCore(); + } + + return new IRenderer.RenderResult(_immediateCanvas.GetBitmap(), _audio.GetPcm()); + } + finally + { + IsGraphicsRendering = false; + IsAudioRendering = false; } - - IsGraphicsRendering = false; - IsAudioRendering = false; - return new IRenderer.RenderResult(_immediateCanvas.GetBitmap(), _audio.GetPcm()); } else { diff --git a/src/Beutl.Utilities/MathUtilities.cs b/src/Beutl.Utilities/MathUtilities.cs index b0520f560..a4c00e7ef 100644 --- a/src/Beutl.Utilities/MathUtilities.cs +++ b/src/Beutl.Utilities/MathUtilities.cs @@ -5,7 +5,7 @@ public static class MathUtilities // smallest such that 1.0+DoubleEpsilon != 1.0 internal static readonly double s_doubleEpsilon = 2.2204460492503131e-016; - private const float FloatEpsilon = 1.192092896e-07F; + public const float FloatEpsilon = 1.192092896e-07F; public static float ToRadians(float degrees) { diff --git a/src/Beutl/ViewModels/Dialogs/CreateNewProjectViewModel.cs b/src/Beutl/ViewModels/Dialogs/CreateNewProjectViewModel.cs index 9ba924751..5a4fc576c 100644 --- a/src/Beutl/ViewModels/Dialogs/CreateNewProjectViewModel.cs +++ b/src/Beutl/ViewModels/Dialogs/CreateNewProjectViewModel.cs @@ -15,7 +15,7 @@ public CreateNewProjectViewModel() Name.SetValidateNotifyError(n => { - if(n == string.Empty || n == null) + if(n == string.Empty || n == null || n.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) { return Message.InvalidString; } diff --git a/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs b/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs index 0da82eb33..a6e46243b 100644 --- a/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs +++ b/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs @@ -23,7 +23,11 @@ public CreateNewSceneViewModel() Name.SetValidateNotifyError(n => { - if (Directory.Exists(Path.Combine(Location.Value, n))) + if (n == string.Empty || n == null || n.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) + { + return Message.InvalidString; + } + else if (Directory.Exists(Path.Combine(Location.Value, n))) { return Message.ItAlreadyExists; } @@ -51,9 +55,14 @@ public CreateNewSceneViewModel() string location = t.Second; PixelSize size = t.Third; - return !Directory.Exists(Path.Combine(location, name)) && + if (location != null && name != null) + { + return !Directory.Exists(Path.Combine(location, name)) && size.Width > 0 && size.Height > 0; + } + else return false; + }).ToReadOnlyReactivePropertySlim(); Create = new ReactiveCommand(CanCreate); Create.Subscribe(() => diff --git a/src/Beutl/ViewModels/ElementViewModel.cs b/src/Beutl/ViewModels/ElementViewModel.cs index 45a480032..52370b68e 100644 --- a/src/Beutl/ViewModels/ElementViewModel.cs +++ b/src/Beutl/ViewModels/ElementViewModel.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Nodes; +using System.Numerics; +using System.Text.Json.Nodes; using Avalonia; using Avalonia.Input; @@ -7,20 +8,17 @@ using Beutl.Commands; using Beutl.Models; using Beutl.ProjectSystem; +using Beutl.Utilities; using Reactive.Bindings; using Reactive.Bindings.Extensions; +using HslColor = Avalonia.Media.HslColor; + namespace Beutl.ViewModels; public sealed class ElementViewModel : IDisposable { - private static readonly Graphics.ColorMatrix s_grayscale = new( - 0.21f, 0.72f, 0.07f, 0, 0, - 0.21f, 0.72f, 0.07f, 0, 0, - 0.21f, 0.72f, 0.07f, 0, 0, - 0.000f, 0.000f, 0.000f, 1, 0); - private static readonly Graphics.ColorMatrix s_contrast100 = Graphics.ColorMatrix.CreateContrast(100); private readonly CompositeDisposable _disposables = new(); private IClipboard? _clipboard; @@ -67,14 +65,7 @@ public ElementViewModel(Element element, TimelineViewModel timeline) .ToReactiveProperty() .AddTo(_disposables); - TextColor = Color.Select(c => - { - //filter: invert(100 %) grayscale(100 %) contrast(100); - var cc = new Media.Color(c.A, (byte)(255 - c.R), (byte)(255 - c.G), (byte)(255 - c.B)); - cc = s_grayscale * cc; - cc = s_contrast100 * cc; - return cc.ToAvalonia(); - }) + TextColor = Color.Select(GetTextColor) .ToReadOnlyReactivePropertySlim() .AddTo(_disposables); @@ -370,6 +361,54 @@ private List CreateKeyBinding() return list; } + // https://github.com/google/skia/blob/0d39172f35d259b6ab888974177bc4e6d839d44c/src/effects/SkHighContrastFilter.cpp + private Avalonia.Media.Color GetTextColor(Avalonia.Media.Color color) + { + static Vector3 Mix(Vector3 x, Vector3 y, float a) + { + return (x * (1 - a)) + (y * a); + } + + static Vector3 Saturate(Vector3 a) + { + return Vector3.Clamp(a, new(0), new(1)); + } + + static Avalonia.Media.Color ToColor(Vector3 vector) + { + return new(255, (byte)(vector.X * 255), (byte)(vector.Y * 255), (byte)(vector.Z * 255)); + } + + static Vector3 ToVector3(Avalonia.Media.Color color) + { + return new Vector3(color.R / 255f, color.G / 255f, color.B / 255f); + } + + // 計算機イプシロン + // 'float.Epsilon'は使わないで + const float Epsilon = MathUtilities.FloatEpsilon; + float contrast = 1.0f; + contrast = Math.Max(-1.0f + Epsilon, Math.Min(contrast, +1.0f - Epsilon)); + + contrast = (1.0f + contrast) / (1.0f - contrast); + + Vector3 c = ToVector3(color); + float grayscale = Vector3.Dot(new(0.2126f, 0.7152f, 0.0722f), c); + c = new Vector3(grayscale); + + // brightness + //c = Vector3.One - c; + + //lightness + HslColor hsl = ToColor(c).ToHsl(); + c = ToVector3(HslColor.ToRgb(hsl.H, hsl.S, 1 - hsl.L, hsl.A)); + + c = Mix(new Vector3(0.5f), c, contrast); + c = Saturate(c); + + return ToColor(c); + } + public record struct PrepareAnimationContext( Thickness Margin, Thickness BorderMargin,