diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs index e49fe303..51ea578b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs @@ -409,13 +409,20 @@ public abstract partial class InferenceGenerationViewModelBase : InferenceTabVie ); // convert to gif - await GifConverter.ConvertWebpToGif(webpFilePath); + var inputStream = File.OpenRead(webpFilePath); var gifFilePath = webpFilePath.ToString().Replace(".webp", ".gif"); - if (File.Exists(gifFilePath)) - { - // delete webp - File.Delete(webpFilePath); - } + var outputStream = File.OpenWrite(gifFilePath); + + await GifConverter.ConvertAnimatedWebpToGifAsync(inputStream, outputStream); + await inputStream.DisposeAsync(); + await outputStream.FlushAsync(); + await outputStream.DisposeAsync(); + + // if (File.Exists(gifFilePath)) + // { + // // delete webp + // File.Delete(webpFilePath); + // } outputImages.Add(new ImageSource(gifFilePath)); EventManager.Instance.OnImageFileAdded(gifFilePath); diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs index 13183691..b1e6d838 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs @@ -65,6 +65,10 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod [JsonIgnore] private string outputUri; + [ObservableProperty] + [JsonIgnore] + private bool isGenerating; + public InferenceImageToVideoViewModel( INotificationService notificationService, IInferenceClientManager inferenceClientManager, @@ -94,6 +98,7 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod samplerCard.SelectedSampler = ComfySampler.Euler; samplerCard.SelectedScheduler = ComfyScheduler.Karras; samplerCard.IsDenoiseStrengthEnabled = true; + samplerCard.DenoiseStrength = 1.0f; }); BatchSizeCardViewModel = vmFactory.Get(); @@ -118,6 +123,11 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod EventManager.Instance.ImageFileAdded += OnImageFileAdded; } + public override void OnUnloaded() + { + EventManager.Instance.ImageFileAdded -= OnImageFileAdded; + } + private void OnImageFileAdded(object? sender, FilePath e) { if (!e.Extension.Contains("gif")) @@ -219,11 +229,15 @@ public partial class InferenceImageToVideoViewModel : InferenceGenerationViewMod batchArgs.Add(generationArgs); } + IsGenerating = true; + // Run batches foreach (var args in batchArgs) { await RunGeneration(args, cancellationToken); } + + IsGenerating = false; } /// diff --git a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml index 92a5866c..c3497945 100644 --- a/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml +++ b/StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml @@ -94,10 +94,17 @@ - - + IsVisible="{Binding ElementName=Dock, Path=DataContext.IsGenerating}" + DataContext="{Binding ImageGalleryCardViewModel}"/> + + + + EnumerateAnimatedWebp(Stream webpSource) { - using var webp = new MagickImageCollection(filePath, MagickFormat.WebP); - var path = filePath.ToString().Replace(".webp", ".gif"); - await webp.WriteAsync(path, MagickFormat.Gif).ConfigureAwait(false); + using var webp = new SKManagedStream(webpSource); + using var codec = SKCodec.Create(webp); + + var info = new SKImageInfo(codec.Info.Width, codec.Info.Height); + + for (var i = 0; i < codec.FrameCount; i++) + { + using var tempSurface = new SKBitmap(info); + + codec.GetFrameInfo(i, out var frameInfo); + + var decodeInfo = info.WithAlphaType(frameInfo.AlphaType); + + tempSurface.TryAllocPixels(decodeInfo); + + var result = codec.GetPixels(decodeInfo, tempSurface.GetPixels(), new SKCodecOptions(i)); + + if (result != SKCodecResult.Success) + throw new InvalidDataException($"Could not decode frame {i} of {codec.FrameCount}."); + + using var peekPixels = tempSurface.PeekPixels(); + + yield return peekPixels.GetReadableBitmapData(WorkingColorSpace.Default); + } + } + + public static Task ConvertAnimatedWebpToGifAsync(Stream webpSource, Stream gifOutput) + { + var gifBitmaps = EnumerateAnimatedWebp(webpSource); + + return GifEncoder.EncodeAnimationAsync( + new AnimatedGifConfiguration(gifBitmaps, TimeSpan.FromMilliseconds(150)) + { + Quantizer = OptimizedPaletteQuantizer.Wu(alphaThreshold: 0), + AllowDeltaFrames = true + }, + gifOutput + ); } } diff --git a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs index 1abde8dd..3f327f28 100644 --- a/StabilityMatrix.Core/Models/Database/LocalImageFile.cs +++ b/StabilityMatrix.Core/Models/Database/LocalImageFile.cs @@ -48,19 +48,9 @@ public record LocalImageFile /// public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(AbsolutePath); - public ( - string? Parameters, - string? ParametersJson, - string? SMProject, - string? ComfyNodes - ) ReadMetadata() + public (string? Parameters, string? ParametersJson, string? SMProject, string? ComfyNodes) ReadMetadata() { - using var stream = new FileStream( - AbsolutePath, - FileMode.Open, - FileAccess.Read, - FileShare.Read - ); + using var stream = new FileStream(AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.Read); using var reader = new BinaryReader(stream); var parameters = ImageMetadata.ReadTextChunk(reader, "parameters"); @@ -79,8 +69,7 @@ public record LocalImageFile public static LocalImageFile FromPath(FilePath filePath) { // TODO: Support other types - const LocalImageFileType imageType = - LocalImageFileType.Inference | LocalImageFileType.TextToImage; + const LocalImageFileType imageType = LocalImageFileType.Inference | LocalImageFileType.TextToImage; // Get metadata using var stream = filePath.Info.OpenRead(); @@ -115,6 +104,5 @@ public record LocalImageFile }; } - public static readonly HashSet SupportedImageExtensions = - new() { ".png", ".jpg", ".jpeg", ".webp" }; + public static readonly HashSet SupportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif"]; } diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj index ab540ac6..d64996a3 100644 --- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj +++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj @@ -29,6 +29,8 @@ + + @@ -51,6 +53,7 @@ +