Browse Source

Merge pull request #427 from ionite34/fast-png

Fast png
pull/361/head v2.7.5
JT 11 months ago committed by GitHub
parent
commit
70e9b9ae43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 7
      StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs
  3. 68
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  4. 48
      StabilityMatrix.Core/Helper/ImageMetadata.cs
  5. 159
      StabilityMatrix.Core/Services/MetadataImportService.cs

2
CHANGELOG.md

@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
## v2.7.5 ## v2.7.5
### Fixed ### Fixed
- Fixed Python Packages manager crash when pip list returns warnings in json - Fixed Python Packages manager crash when pip list returns warnings in json
- Fixed slowdown when loading PNGs with large amounts of metadata
- Fixed crash when scanning directories for missing metadata
## v2.7.4 ## v2.7.4
### Changed ### Changed

7
StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs

@ -7,6 +7,7 @@ using AsyncAwaitBestPractices;
using AsyncImageLoader.Loaders; using AsyncImageLoader.Loaders;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
namespace StabilityMatrix.Avalonia; namespace StabilityMatrix.Avalonia;
@ -42,7 +43,11 @@ public class FallbackRamCachedWebImageLoader : RamCachedWebImageLoader
{ {
try try
{ {
return new Bitmap(url); if (!url.EndsWith("png", StringComparison.OrdinalIgnoreCase))
return new Bitmap(url);
using var stream = ImageMetadata.BuildImageWithoutMetadata(url);
return stream == null ? new Bitmap(url) : new Bitmap(stream);
} }
catch (Exception e) catch (Exception e)
{ {

68
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -54,11 +54,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
private readonly ILogger<OutputsPageViewModel> logger; private readonly ILogger<OutputsPageViewModel> logger;
public override string Title => Resources.Label_OutputsPageTitle; public override string Title => Resources.Label_OutputsPageTitle;
public override IconSource IconSource => public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
public SourceCache<LocalImageFile, string> OutputsCache { get; } = public SourceCache<LocalImageFile, string> OutputsCache { get; } = new(file => file.AbsolutePath);
new(file => file.AbsolutePath);
public IObservableCollection<OutputImageViewModel> Outputs { get; set; } = public IObservableCollection<OutputImageViewModel> Outputs { get; set; } =
new ObservableCollectionExtended<OutputImageViewModel>(); new ObservableCollectionExtended<OutputImageViewModel>();
@ -88,8 +86,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty] [ObservableProperty]
private bool isConsolidating; private bool isConsolidating;
public bool CanShowOutputTypes => public bool CanShowOutputTypes => SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
public string NumImagesSelected => public string NumImagesSelected =>
NumItemsSelected == 1 NumItemsSelected == 1
@ -138,7 +135,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
delay: TimeSpan.FromMilliseconds(250) delay: TimeSpan.FromMilliseconds(250)
); );
RefreshCategories(); RefreshCategories(false);
} }
public override void OnLoaded() public override void OnLoaded()
@ -163,10 +160,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
GetOutputs(path); GetOutputs(path);
} }
partial void OnSelectedCategoryChanged( partial void OnSelectedCategoryChanged(PackageOutputCategory? oldValue, PackageOutputCategory? newValue)
PackageOutputCategory? oldValue,
PackageOutputCategory? newValue
)
{ {
if (oldValue == newValue || newValue == null) if (oldValue == newValue || newValue == null)
return; return;
@ -223,8 +217,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
) )
.Subscribe(ctx => .Subscribe(ctx =>
{ {
Dispatcher.UIThread Dispatcher
.InvokeAsync(async () => .UIThread.InvokeAsync(async () =>
{ {
var sender = (ImageViewerViewModel)ctx.Sender!; var sender = (ImageViewerViewModel)ctx.Sender!;
var newIndex = currentIndex + (ctx.EventArgs.IsNext ? 1 : -1); var newIndex = currentIndex + (ctx.EventArgs.IsNext ? 1 : -1);
@ -261,7 +255,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
public void Refresh() public void Refresh()
{ {
Dispatcher.UIThread.Post(RefreshCategories); Dispatcher.UIThread.Post(() => RefreshCategories());
Dispatcher.UIThread.Post(OnLoaded); Dispatcher.UIThread.Post(OnLoaded);
} }
@ -430,9 +424,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
Directory.CreateDirectory(settingsManager.ConsolidatedImagesDirectory); Directory.CreateDirectory(settingsManager.ConsolidatedImagesDirectory);
foreach ( foreach (var category in stackPanel.Children.OfType<CheckBox>().Where(c => c.IsChecked == true))
var category in stackPanel.Children.OfType<CheckBox>().Where(c => c.IsChecked == true)
)
{ {
if ( if (
string.IsNullOrWhiteSpace(category.Tag?.ToString()) string.IsNullOrWhiteSpace(category.Tag?.ToString())
@ -442,13 +434,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
var directory = category.Tag.ToString(); var directory = category.Tag.ToString();
foreach ( foreach (var path in Directory.EnumerateFiles(directory, "*.png", SearchOption.AllDirectories))
var path in Directory.EnumerateFiles(
directory,
"*.png",
SearchOption.AllDirectories
)
)
{ {
try try
{ {
@ -524,7 +510,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
} }
} }
private void RefreshCategories() private void RefreshCategories(bool updateProperty = true)
{ {
if (Design.IsDesignMode) if (Design.IsDesignMode)
return; return;
@ -534,15 +520,11 @@ public partial class OutputsPageViewModel : PageViewModelBase
var previouslySelectedCategory = SelectedCategory; var previouslySelectedCategory = SelectedCategory;
var packageCategories = settingsManager.Settings.InstalledPackages var packageCategories = settingsManager
.Where(x => !x.UseSharedOutputFolder) .Settings.InstalledPackages.Where(x => !x.UseSharedOutputFolder)
.Select(packageFactory.GetPackagePair) .Select(packageFactory.GetPackagePair)
.WhereNotNull() .WhereNotNull()
.Where( .Where(p => p.BasePackage.SharedOutputFolders != null && p.BasePackage.SharedOutputFolders.Any())
p =>
p.BasePackage.SharedOutputFolders != null
&& p.BasePackage.SharedOutputFolders.Any()
)
.Select( .Select(
pair => pair =>
new PackageOutputCategory new PackageOutputCategory
@ -567,16 +549,22 @@ public partial class OutputsPageViewModel : PageViewModelBase
packageCategories.Insert( packageCategories.Insert(
1, 1,
new PackageOutputCategory new PackageOutputCategory { Path = settingsManager.ImagesInferenceDirectory, Name = "Inference" }
{
Path = settingsManager.ImagesInferenceDirectory,
Name = "Inference"
}
); );
Categories = new ObservableCollection<PackageOutputCategory>(packageCategories); Categories = new ObservableCollection<PackageOutputCategory>(packageCategories);
SelectedCategory =
Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name) if (updateProperty)
?? Categories.First(); {
SelectedCategory =
Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name)
?? Categories.First();
}
else
{
selectedCategory =
Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name)
?? Categories.First();
}
} }
} }

48
StabilityMatrix.Core/Helper/ImageMetadata.cs

@ -179,4 +179,52 @@ public class ImageMetadata
return string.Empty; return string.Empty;
} }
public static MemoryStream? BuildImageWithoutMetadata(FilePath imagePath)
{
using var byteStream = new BinaryReader(File.OpenRead(imagePath));
byteStream.BaseStream.Position = 0;
if (!byteStream.ReadBytes(8).SequenceEqual(PngHeader))
{
return null;
}
var memoryStream = new MemoryStream();
memoryStream.Write(PngHeader);
// add the IHDR chunk
var ihdrStuff = byteStream.ReadBytes(25);
memoryStream.Write(ihdrStuff);
// find IDATs
while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)
{
var chunkSizeBytes = byteStream.ReadBytes(4);
var chunkSize = BitConverter.ToInt32(chunkSizeBytes.Reverse().ToArray());
var chunkTypeBytes = byteStream.ReadBytes(4);
var chunkType = Encoding.UTF8.GetString(chunkTypeBytes);
if (chunkType != Encoding.UTF8.GetString(Idat))
{
// skip chunk data
byteStream.BaseStream.Position += chunkSize;
// skip crc
byteStream.BaseStream.Position += 4;
continue;
}
memoryStream.Write(chunkSizeBytes);
memoryStream.Write(chunkTypeBytes);
var idatBytes = byteStream.ReadBytes(chunkSize);
memoryStream.Write(idatBytes);
var crcBytes = byteStream.ReadBytes(4);
memoryStream.Write(crcBytes);
}
// Add IEND chunk
memoryStream.Write([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]);
memoryStream.Position = 0;
return memoryStream;
}
} }

159
StabilityMatrix.Core/Services/MetadataImportService.cs

@ -1,5 +1,4 @@
using System.Diagnostics; using System.Text.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper;
@ -18,7 +17,10 @@ public class MetadataImportService(
ModelFinder modelFinder ModelFinder modelFinder
) : IMetadataImportService ) : IMetadataImportService
{ {
public async Task ScanDirectoryForMissingInfo(DirectoryPath directory, IProgress<ProgressReport>? progress = null) public async Task ScanDirectoryForMissingInfo(
DirectoryPath directory,
IProgress<ProgressReport>? progress = null
)
{ {
progress?.Report(new ProgressReport(-1f, "Scanning directory...", isIndeterminate: true)); progress?.Report(new ProgressReport(-1f, "Scanning directory...", isIndeterminate: true));
@ -54,7 +56,9 @@ public class MetadataImportService(
} }
var fileNameWithoutExtension = checkpointFilePath.NameWithoutExtension; var fileNameWithoutExtension = checkpointFilePath.NameWithoutExtension;
var cmInfoPath = checkpointFilePath.Directory?.JoinFile($"{fileNameWithoutExtension}.cm-info.json"); var cmInfoPath = checkpointFilePath.Directory?.JoinFile(
$"{fileNameWithoutExtension}.cm-info.json"
);
var cmInfoExists = File.Exists(cmInfoPath); var cmInfoExists = File.Exists(cmInfoPath);
if (cmInfoExists) if (cmInfoExists)
continue; continue;
@ -70,43 +74,57 @@ public class MetadataImportService(
); );
}); });
var blake3 = await GetBlake3Hash(cmInfoPath, checkpointFilePath, hashProgress).ConfigureAwait(false); try
if (string.IsNullOrWhiteSpace(blake3))
{ {
logger.LogWarning($"Blake3 hash was null for {checkpointFilePath}"); var blake3 = await GetBlake3Hash(cmInfoPath, checkpointFilePath, hashProgress)
scanned++; .ConfigureAwait(false);
continue; if (string.IsNullOrWhiteSpace(blake3))
} {
logger.LogWarning($"Blake3 hash was null for {checkpointFilePath}");
var modelInfo = await modelFinder.RemoteFindModel(blake3).ConfigureAwait(false); scanned++;
if (modelInfo == null) continue;
{ }
logger.LogWarning($"Could not find model for {blake3}");
scanned++; var modelInfo = await modelFinder.RemoteFindModel(blake3).ConfigureAwait(false);
continue; if (modelInfo == null)
} {
logger.LogWarning($"Could not find model for {blake3}");
scanned++;
continue;
}
var (model, modelVersion, modelFile) = modelInfo.Value;
var updatedCmInfo = new ConnectedModelInfo(
model,
modelVersion,
modelFile,
DateTimeOffset.UtcNow
);
await updatedCmInfo
.SaveJsonToDirectory(checkpointFilePath.Directory, fileNameWithoutExtension)
.ConfigureAwait(false);
var (model, modelVersion, modelFile) = modelInfo.Value; var image = modelVersion.Images?.FirstOrDefault(
img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
);
if (image == null)
{
scanned++;
success++;
continue;
}
var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow); await DownloadImage(image, checkpointFilePath, progress).ConfigureAwait(false);
await updatedCmInfo
.SaveJsonToDirectory(checkpointFilePath.Directory, fileNameWithoutExtension)
.ConfigureAwait(false);
var image = modelVersion
.Images
?.FirstOrDefault(img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url)));
if (image == null)
{
scanned++; scanned++;
success++; success++;
continue;
} }
catch (Exception e)
await DownloadImage(image, checkpointFilePath, progress).ConfigureAwait(false); {
logger.LogError(e, "Error while scanning {checkpointFilePath}", checkpointFilePath);
scanned++; scanned++;
success++; }
} }
progress?.Report( progress?.Report(
@ -124,7 +142,10 @@ public class MetadataImportService(
&& !File.Exists(file.Directory?.JoinFile($"{file.NameWithoutExtension}.cm-info.json")); && !File.Exists(file.Directory?.JoinFile($"{file.NameWithoutExtension}.cm-info.json"));
} }
public async Task UpdateExistingMetadata(DirectoryPath directory, IProgress<ProgressReport>? progress = null) public async Task UpdateExistingMetadata(
DirectoryPath directory,
IProgress<ProgressReport>? progress = null
)
{ {
progress?.Report(new ProgressReport(-1f, "Scanning directory...", isIndeterminate: true)); progress?.Report(new ProgressReport(-1f, "Scanning directory...", isIndeterminate: true));
@ -151,33 +172,47 @@ public class MetadataImportService(
) )
); );
var hash = cmInfoValue.Hashes.BLAKE3; try
if (string.IsNullOrWhiteSpace(hash))
continue;
var modelInfo = await modelFinder.RemoteFindModel(hash).ConfigureAwait(false);
if (modelInfo == null)
{ {
logger.LogWarning($"Could not find model for {hash}"); var hash = cmInfoValue.Hashes.BLAKE3;
continue; if (string.IsNullOrWhiteSpace(hash))
} continue;
var (model, modelVersion, modelFile) = modelInfo.Value; var modelInfo = await modelFinder.RemoteFindModel(hash).ConfigureAwait(false);
if (modelInfo == null)
var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow); {
logger.LogWarning($"Could not find model for {hash}");
continue;
}
var (model, modelVersion, modelFile) = modelInfo.Value;
var updatedCmInfo = new ConnectedModelInfo(
model,
modelVersion,
modelFile,
DateTimeOffset.UtcNow
);
var nameWithoutCmInfo = filePath.NameWithoutExtension.Replace(".cm-info", string.Empty); var nameWithoutCmInfo = filePath.NameWithoutExtension.Replace(".cm-info", string.Empty);
await updatedCmInfo.SaveJsonToDirectory(filePath.Directory, nameWithoutCmInfo).ConfigureAwait(false); await updatedCmInfo
.SaveJsonToDirectory(filePath.Directory, nameWithoutCmInfo)
.ConfigureAwait(false);
var image = modelVersion var image = modelVersion.Images?.FirstOrDefault(
.Images img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
?.FirstOrDefault(img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))); );
if (image == null) if (image == null)
continue; continue;
await DownloadImage(image, filePath, progress).ConfigureAwait(false); await DownloadImage(image, filePath, progress).ConfigureAwait(false);
success++; success++;
}
catch (Exception e)
{
logger.LogError(e, "Error while updating {filePath}", filePath);
}
} }
} }
@ -223,11 +258,13 @@ public class MetadataImportService(
var (model, modelVersion, modelFile) = modelInfo.Value; var (model, modelVersion, modelFile) = modelInfo.Value;
var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow); var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow);
await updatedCmInfo.SaveJsonToDirectory(filePath.Directory, fileNameWithoutExtension).ConfigureAwait(false); await updatedCmInfo
.SaveJsonToDirectory(filePath.Directory, fileNameWithoutExtension)
.ConfigureAwait(false);
var image = modelVersion var image = modelVersion.Images?.FirstOrDefault(
.Images img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))
?.FirstOrDefault(img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))); );
if (image == null) if (image == null)
return updatedCmInfo; return updatedCmInfo;

Loading…
Cancel
Save