using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using PsdParser; using Vortice.Direct2D1; using Vortice.Direct2D1.Effects; using YukkuriMovieMaker.Commons; using YukkuriMovieMaker.Player.Video; using YukkuriMovieMaker.Plugin.FileSource.Psd; using YukkuriMovieMaker.Plugin.Voice; namespace YukkuriMovieMaker.Plugin.Tachie.Psd; public class PsdTachieSource : ITachieSource2, ITachieSource, IDisposable { private readonly IGraphicsDevicesAndContext devices; private readonly DisposeCollector disposer = new DisposeCollector(); private PsdFile? psdFile; private PsdFolder? psdRoot; private PsdFileSettings? psdFileSettings; private ID2D1Bitmap bitmap; private readonly AffineTransform2D centeringEffect; private bool isCleared = true; private bool isFirst = true; private string? filePath; private ImmutableList defaultEnableLayers = ImmutableList.Create(default(ReadOnlySpan)); private ImmutableList? activeLayers; private ImmutableList? lastEnableLayersInput; private ImmutableList? lastEnableLayerPathsInput; private ImmutableList resolvedEnableLayers = ImmutableList.Create(default(ReadOnlySpan)); private PsdFileSettings? normalizedPsdFileSettings; private ImmutableList? cachedEyeAnimations; private ImmutableList? cachedMouthAnimations; private ImmutableList? cachedVowelAnimations; private bool disposedValue; public ID2D1Image Output { get; } public PsdTachieSource(IGraphicsDevicesAndContext devices) { this.devices = devices; bitmap = devices.DeviceContext.CreateEmptyBitmap(); disposer.Collect(bitmap); centeringEffect = new AffineTransform2D(devices.DeviceContext); disposer.Collect(centeringEffect); centeringEffect.SetInput(0, bitmap, true); Output = centeringEffect.Output; disposer.Collect(Output); } public void Update(TachieSourceDescription desc) { TimeSpan time = desc.ItemPosition.Time; _ = desc.ItemDuration.Time; TachieFaceDescription? tachieFaceDescription = desc.Tachie.Faces.FirstOrDefault(); if ((object)tachieFaceDescription == null) { _ = TimeSpan.Zero; } else { _ = tachieFaceDescription.ItemPosition.Time; } if ((object)tachieFaceDescription == null) { _ = TimeSpan.Zero; } else { _ = tachieFaceDescription.ItemDuration.Time; } ITachieCharacterParameter characterParameter = desc.Tachie.CharacterParameter; ITachieItemParameter itemParameter = desc.Tachie.ItemParameter; ITachieFaceParameter tachieFaceParameter = tachieFaceDescription?.FaceParameter; double voiceVolume = desc.VoiceVolume; MouthShape mouthShape = desc.MouthShape; if (!(characterParameter is PsdTachieCharacterParameter psdTachieCharacterParameter) || !(itemParameter is PsdTachieItemParameter psdTachieItemParameter) || (psdTachieItemParameter.IsHiddenWhenNoSpeech && voiceVolume == -1.0)) { Clear(); return; } PsdTachieFaceParameter psdTachieFaceParameter = tachieFaceParameter as PsdTachieFaceParameter; string text = psdTachieCharacterParameter.FilePath; ImmutableList immutableList = ((psdTachieFaceParameter != null && text == psdTachieFaceParameter.FilePath && psdTachieFaceParameter.IsEnabled) ? psdTachieFaceParameter.EnableLayers : ((psdTachieFaceParameter != null && psdTachieFaceParameter.IsEnabled) ? null : ((text == psdTachieItemParameter.FilePath) ? psdTachieItemParameter.EnableLayers : null))); ImmutableList immutableList2 = ((psdTachieFaceParameter != null && text == psdTachieFaceParameter.FilePath && psdTachieFaceParameter.IsEnabled) ? psdTachieFaceParameter.EnableLayerPaths : ((psdTachieFaceParameter != null && psdTachieFaceParameter.IsEnabled) ? null : ((text == psdTachieItemParameter.FilePath) ? psdTachieItemParameter.EnableLayerPaths : null))); if (isFirst || text != filePath) { if (psdFile != null) { disposer.RemoveAndDispose(ref psdFile); } psdRoot = null; psdFileSettings = null; lastEnableLayersInput = null; lastEnableLayerPathsInput = null; normalizedPsdFileSettings = null; try { if (File.Exists(text)) { psdFile = new PsdFile(text); disposer.Collect(psdFile); psdFileSettings = PsdFileSettings.LoadFromPsdFilePath(text); psdRoot = PsdFolder.Parse(psdFile); defaultEnableLayers = ImmutableList.Create(new ReadOnlySpan(psdRoot.GetEnableItems().ToArray())); } } catch { if (psdFile != null) { disposer.RemoveAndDispose(ref psdFile); } psdRoot = null; psdFileSettings = null; } } if (psdFile == null || psdRoot == null) { Clear(); isFirst = false; filePath = text; activeLayers = null; return; } ImmutableList enableItems; if (immutableList == null) { enableItems = defaultEnableLayers; } else { if (lastEnableLayersInput != immutableList || lastEnableLayerPathsInput != immutableList2) { resolvedEnableLayers = psdRoot.ResolveLayers(immutableList, immutableList2 ?? ImmutableList.Create(default(ReadOnlySpan))); lastEnableLayersInput = immutableList; lastEnableLayerPathsInput = immutableList2; } enableItems = resolvedEnableLayers; } psdRoot.SetEnableItems(enableItems); ImmutableList immutableList3 = psdRoot.GetActiveLayers().ToImmutableList(); if (psdFileSettings == null) { normalizedPsdFileSettings = null; } else if (normalizedPsdFileSettings == null || cachedEyeAnimations != psdFileSettings.EyeAnimations || cachedMouthAnimations != psdFileSettings.MouthAnimations || cachedVowelAnimations != psdFileSettings.MouthVowelAnimations) { normalizedPsdFileSettings = psdFileSettings.ResolveAgainst(psdRoot); cachedEyeAnimations = psdFileSettings.EyeAnimations; cachedMouthAnimations = psdFileSettings.MouthAnimations; cachedVowelAnimations = psdFileSettings.MouthVowelAnimations; } immutableList3 = ApplyAnimation(text, immutableList3, normalizedPsdFileSettings, time, voiceVolume, psdTachieCharacterParameter.MouthSensitivity, psdTachieFaceParameter?.EyeAnimation ?? AnimationMode.Default, psdTachieFaceParameter?.MouthAnimation ?? MouthAnimationMode.VowelLipSyncPriority, mouthShape); if (isFirst || filePath != text || (activeLayers == null && !immutableList3.IsEmpty) || (activeLayers != null && !immutableList3.SequenceEqual(activeLayers))) { bitmap.Dispose(); disposer.Remove(bitmap); psdRoot.SetActiveLayers(immutableList3); bitmap = PsdFileSourcePlugin.CreateBitmap(devices, psdRoot, psdFile.Header.Width, psdFile.Header.Height) ?? devices.DeviceContext.CreateEmptyBitmap(); disposer.Collect(bitmap); centeringEffect.SetInput(0, bitmap, true); centeringEffect.TransformMatrix = Matrix3x2.CreateTranslation((0f - bitmap.Size.Width) / 2f, (0f - bitmap.Size.Height) / 2f); } isCleared = false; isFirst = false; filePath = text; activeLayers = immutableList3; } private static ImmutableList ApplyAnimation(string? psdFilePath, ImmutableList activeLayers, PsdFileSettings? psdFileSettings, TimeSpan tachieTime, double kuchipaku, double mouthSensitivity, AnimationMode eyeAnimationMode, MouthAnimationMode mouthAnimationMode, MouthShape mouthShape) { if (psdFileSettings == null) { return activeLayers; } IEnumerable activeMouthAnimations = psdFileSettings.GetActiveMouthAnimations(activeLayers); IEnumerable activeEyeAnimations = psdFileSettings.GetActiveEyeAnimations(activeLayers); IEnumerable activeVowelMouthAnimations = psdFileSettings.GetActiveVowelMouthAnimations(activeLayers); ImmutableList.Builder builder = activeLayers.ToBuilder(); IEnumerable enumerable = new IEnumerable[2] { activeVowelMouthAnimations.Select((PsdVowelMouthAnimation x) => x.Key), activeMouthAnimations.Select((PsdMouthAnimation x) => x.Layers.First()) }.SelectMany((IEnumerable x) => x).Distinct(); foreach (string item3 in new IEnumerable[2] { activeEyeAnimations.Select((PsdEyeAnimation x) => x.Layers.First()), enumerable }.SelectMany((IEnumerable x) => x).Distinct()) { builder.Remove(item3); } double mouthRate = mouthAnimationMode switch { MouthAnimationMode.AlwaysClose => 0.0, MouthAnimationMode.AlwaysOpen => 1.0, _ => (kuchipaku == -1.0) ? 0.0 : Math.Clamp(kuchipaku * 10.0 * mouthSensitivity / 100.0, 0.0, 1.0), }; foreach (string key in enumerable) { PsdVowelMouthAnimation psdVowelMouthAnimation = activeVowelMouthAnimations.FirstOrDefault((PsdVowelMouthAnimation x) => x.Key == key); PsdMouthAnimation psdMouthAnimation = activeMouthAnimations.FirstOrDefault((PsdMouthAnimation x) => x.Layers.First() == key); switch (mouthAnimationMode) { case MouthAnimationMode.VowelLipSyncPriority: if (psdVowelMouthAnimation != null) { ApplyVowelMouthAnimation(psdVowelMouthAnimation); } else if (psdMouthAnimation != null) { ApplyVolumeMouthAnimation(psdMouthAnimation); } else if (!builder.Contains(key)) { builder.Add(key); } continue; case MouthAnimationMode.VolumeLipSyncPriority: if (psdMouthAnimation != null) { ApplyVolumeMouthAnimation(psdMouthAnimation); } else if (psdVowelMouthAnimation != null) { ApplyVowelMouthAnimation(psdVowelMouthAnimation); } else if (!builder.Contains(key)) { builder.Add(key); } continue; } if (psdMouthAnimation != null) { ApplyVolumeMouthAnimation(psdMouthAnimation); } else if (psdVowelMouthAnimation != null) { switch (mouthAnimationMode) { case MouthAnimationMode.AlwaysOpen: { string key2 = psdVowelMouthAnimation.Key; if (!builder.Contains(key2)) { builder.Add(key2); } break; } case MouthAnimationMode.AlwaysClose: { string mouthLayer = psdVowelMouthAnimation.GetMouthLayer(MouthShape.Silent); if (!builder.Contains(mouthLayer)) { builder.Add(mouthLayer); } break; } } } else if (!builder.Contains(key)) { builder.Add(key); } } foreach (PsdEyeAnimation item4 in activeEyeAnimations) { int num = psdFilePath?.GetHashCode() ?? 0; double num2; if (item4.Offset != 0.0) { num2 = item4.Offset; } else { InlineArray2 buffer = default(InlineArray2); buffer[0] = num; buffer[1] = 0L; num2 = 5.0 + StatelessRandom.GetDouble(buffer) * 5.0; } double offset = num2; double num3; if (item4.Interval != 0.0) { num3 = item4.Interval; } else { InlineArray2 buffer2 = default(InlineArray2); buffer2[0] = num; buffer2[1] = 1L; num3 = 5.0 + StatelessRandom.GetDouble(buffer2) * 5.0; } double interval = num3; double num4 = eyeAnimationMode switch { AnimationMode.AlwaysClose => 0.0, AnimationMode.AlwaysOpen => 1.0, _ => GetMabataki(tachieTime, offset, interval), }; int count = item4.Layers.Count; int index = Math.Clamp((int)((double)count * (1.0 - num4)), 0, count - 1); string item = item4.Layers[index]; if (!builder.Contains(item)) { builder.Add(item); } } return builder.ToImmutable(); void ApplyVolumeMouthAnimation(PsdMouthAnimation animation) { int count2 = animation.Layers.Count; int index2 = Math.Clamp((int)((double)count2 * (1.0 - mouthRate)), 0, count2 - 1); string item2 = animation.Layers[index2]; if (!builder.Contains(item2)) { builder.Add(item2); } } void ApplyVowelMouthAnimation(PsdVowelMouthAnimation animation) { string mouthLayer2 = animation.GetMouthLayer(mouthShape); if (!builder.Contains(mouthLayer2)) { builder.Add(mouthLayer2); } } } private static double GetMabataki(TimeSpan tachieTime, double offset, double interval) { double num = 0.3; double num2 = (tachieTime.TotalSeconds - offset) % (num + interval) / num; if (!(num2 >= 0.0) || !(num2 <= 0.5)) { if (!(num2 >= 0.5) || !(num2 <= 1.0)) { return 1.0; } return (num2 - 0.5) * 2.0; } return 1.0 - num2 * 2.0; } private void Clear() { if (!isCleared) { isCleared = true; if (psdFile != null) { disposer.RemoveAndDispose(ref psdFile); } psdRoot = null; psdFileSettings = null; filePath = string.Empty; activeLayers = ImmutableList.Create(default(ReadOnlySpan)); bitmap.Dispose(); disposer.Remove(bitmap); bitmap = devices.DeviceContext.CreateEmptyBitmap(); disposer.Collect(bitmap); centeringEffect.SetInput(0, bitmap, true); } } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { centeringEffect.SetInput(0, null, true); disposer.Dispose(); } disposedValue = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } }