Skip to content

Commit

Permalink
Merge pull request #2000 from erri120/feat/1864-9
Browse files Browse the repository at this point in the history
Image store
  • Loading branch information
erri120 authored Sep 11, 2024
2 parents 33bc84f + 7c36b0d commit 7552bfb
Show file tree
Hide file tree
Showing 14 changed files with 522 additions and 7 deletions.
7 changes: 4 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
<PackageVersion Include="Avalonia.Labs.Panels" Version="11.1.0" />
<PackageVersion Include="Avalonia.Skia" Version="11.1.0" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.1.0" />
<PackageVersion Include="FlatSharp.Compiler" Version="7.6.0" />
<PackageVersion Include="FlatSharp.Runtime" Version="7.6.0" />
Expand All @@ -10,8 +11,8 @@
<PackageVersion Include="Nerdbank.FullDuplexStream" Version="1.1.12" />
<PackageVersion Include="Nerdbank.Streams" Version="2.11.74" />
<PackageVersion Include="NexusMods.Paths" Version="0.10.0" />
<PackageVersion Include="NexusMods.MnemonicDB.Abstractions" Version="0.9.80" />
<PackageVersion Include="NexusMods.MnemonicDB" Version="0.9.80" />
<PackageVersion Include="NexusMods.MnemonicDB.Abstractions" Version="0.9.81" />
<PackageVersion Include="NexusMods.MnemonicDB" Version="0.9.81" />
<PackageVersion Include="NexusMods.Hashing.xxHash64" Version="2.0.1" />
<PackageVersion Include="NexusMods.Paths.Extensions.Nx" Version="0.10.0" />
<PackageVersion Include="NexusMods.Paths.TestingHelpers" Version="0.9.5" />
Expand Down Expand Up @@ -121,7 +122,7 @@
<PackageVersion Include="Humanizer" Version="2.14.1" />
<PackageVersion Include="ini-parser-netstandard" Version="2.5.2" />
<PackageVersion Include="Mutagen.Bethesda.Skyrim" Version="0.44.0" />
<PackageVersion Include="NexusMods.MnemonicDB.SourceGenerator" Version="0.9.80" />
<PackageVersion Include="NexusMods.MnemonicDB.SourceGenerator" Version="0.9.81" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.11" />
<PackageVersion Include="OneOf" Version="3.0.271" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
Expand Down
7 changes: 7 additions & 0 deletions NexusMods.App.sln
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.GC",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.FileStore.Nx", "src\Abstractions\NexusMods.Abstractions.FileStore.Nx\NexusMods.Abstractions.FileStore.Nx.csproj", "{5CB12332-A2E9-4A6A-993E-718490C61A9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Media", "src\Abstractions\NexusMods.Abstractions.Media\NexusMods.Abstractions.Media.csproj", "{5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -638,6 +640,10 @@ Global
{5CB12332-A2E9-4A6A-993E-718490C61A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CB12332-A2E9-4A6A-993E-718490C61A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5CB12332-A2E9-4A6A-993E-718490C61A9B}.Release|Any CPU.Build.0 = Release|Any CPU
{5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -752,6 +758,7 @@ Global
{B917FBF8-46F8-4776-8BAE-A54BED756E7E} = {D49730E2-2EED-4E72-88CA-35462CC8D9A6}
{A1FCEB06-7599-44AD-90CA-2C88C32B959C} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{5CB12332-A2E9-4A6A-993E-718490C61A9B} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F9F8352-34DD-42C0-8564-EE9AF34A3501}
Expand Down
20 changes: 20 additions & 0 deletions src/Abstractions/NexusMods.Abstractions.Media/IImageStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Avalonia.Media.Imaging;
using BitFaster.Caching;
using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions;
using OneOf;

namespace NexusMods.Abstractions.Media;

/// <summary>
/// Optimized storage for images.
/// </summary>
[PublicAPI]
public interface IImageStore
{
ValueTask<StoredImage.ReadOnly> PutAsync(Bitmap bitmap);

[MustDisposeResource] Lifetime<Bitmap>? Get(OneOf<StoredImageId, StoredImage.ReadOnly> input);

StoredImage.New CreateStoredImage(ITransaction transaction, Bitmap bitmap);
}
37 changes: 37 additions & 0 deletions src/Abstractions/NexusMods.Abstractions.Media/ImageData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace NexusMods.Abstractions.Media;

/// <summary>
/// Image data.
/// </summary>
public readonly struct ImageData
{
/// <summary>
/// Compression type.
/// </summary>
public readonly ImageDataCompression Compression;

/// <summary>
/// Binary data.
/// </summary>
public readonly byte[] Data;

/// <summary>
/// Constructor.
/// </summary>
public ImageData(ImageDataCompression compression, byte[] data)
{
Compression = compression;
Data = data;
}
}

/// <summary>
/// Compression types.
/// </summary>
public enum ImageDataCompression : byte
{
/// <summary>
/// No compression.
/// </summary>
None = 0,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Diagnostics;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.ElementComparers;

namespace NexusMods.Abstractions.Media;

/// <summary>
/// Binary blob containing image data.
/// </summary>
public class ImageDataAttribute(string ns, string name) : BlobAttribute<ImageData>(ns, name)
{
/// <inheritdoc/>
protected override ImageData FromLowLevel(ReadOnlySpan<byte> value, ValueTags tags, RegistryId registryId)
{
Debug.Assert(sizeof(ImageDataCompression) == 1);
var compression = (ImageDataCompression)value[0];

var data = Decompress(compression, value[1..]);
return new ImageData(compression, data);
}

/// <inheritdoc/>
protected override void WriteValue<TWriter>(ImageData value, TWriter writer)
{
Debug.Assert(sizeof(ImageDataCompression) == 1);
var count = value.Data.Length + sizeof(ImageDataCompression);

var span = writer.GetSpan(sizeHint: count);
span[0] = (byte)value.Compression;

var bytesWritten = Compress(value.Compression, value.Data, span[1..]);
writer.Advance(bytesWritten + sizeof(ImageDataCompression));
}

private static int Compress(ImageDataCompression compression, byte[] data, Span<byte> outputSpan)
{
switch (compression)
{
case ImageDataCompression.None:
data.CopyTo(outputSpan);
return data.Length;
default:
throw new ArgumentOutOfRangeException();
}
}

private static byte[] Decompress(ImageDataCompression compression, ReadOnlySpan<byte> data)
{
switch (compression)
{
case ImageDataCompression.None:
return data.ToArray();
default:
throw new ArgumentOutOfRangeException();
}
}
}
107 changes: 107 additions & 0 deletions src/Abstractions/NexusMods.Abstractions.Media/ImageMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Skia;
using SkiaSharp;

namespace NexusMods.Abstractions.Media;

/// <summary>
/// Metadata of an image.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct ImageMetadata
{
/// <summary>
/// Width.
/// </summary>
public readonly uint ImageWidth;

/// <summary>
/// Height.
/// </summary>
public readonly uint ImageHeight;

/// <summary>
/// Color type.
/// </summary>
public readonly SKColorType SkColorType;

/// <summary>
/// Alpha format.
/// </summary>
public readonly AlphaFormat AlphaFormat;

/// <summary>
/// DPI.
/// </summary>
public readonly uint Dpi;

/// <summary>
/// Pixel size.
/// </summary>
public PixelSize PixelSize => new((int)ImageWidth, (int)ImageHeight);

/// <summary>
/// Pixel format.
/// </summary>
public PixelFormat PixelFormat => SkColorType.ToPixelFormat();

// NOTE(erri120): Going from bits to bytes requires dividing by 8, aka bit shift by 3

/// <summary>
/// Stride is the number of bytes from one row pixels in memory to the next row.
/// </summary>
public int Stride => ((int)ImageWidth * PixelFormat.BitsPerPixel) >> 3;

/// <summary>
/// Total length of the raw data.
/// </summary>
public ulong DataLength => (ImageWidth * ImageHeight * (uint)PixelFormat.BitsPerPixel) >> 3;

/// <summary>
/// Constructor.
/// </summary>
public ImageMetadata(uint imageWidth, uint imageHeight, SKColorType skColorType, AlphaFormat alphaFormat, uint dpi)
{
ImageWidth = imageWidth;
ImageHeight = imageHeight;
SkColorType = skColorType;
AlphaFormat = alphaFormat;
Dpi = dpi;
}

/// <summary>
/// Reads the binary data as metadata.
/// </summary>
public static ImageMetadata Read(ReadOnlySpan<byte> bytes)
{
Debug.Assert(bytes.Length == Marshal.SizeOf<ImageMetadata>());

unsafe
{
fixed (byte* b = bytes)
{
return Unsafe.Read<ImageMetadata>(b);
}
}
}

/// <summary>
/// Writes the metadata as binary data.
/// </summary>
public void Write(Span<byte> bytes)
{
Debug.Assert(bytes.Length == Marshal.SizeOf<ImageMetadata>());

unsafe
{
fixed (void* b = bytes)
{
Unsafe.Write(b, this);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Runtime.InteropServices;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.ElementComparers;

namespace NexusMods.Abstractions.Media;

/// <summary>
/// Binary blob representation of <see cref="ImageMetadata"/>.
/// </summary>
public class ImageMetadataAttribute(string ns, string name) : BlobAttribute<ImageMetadata>(ns, name)
{
private static readonly int ImageMetadataSize = Marshal.SizeOf<ImageMetadata>();

/// <inheritdoc/>
protected override ImageMetadata FromLowLevel(ReadOnlySpan<byte> value, ValueTags tags, RegistryId registryId)
{
return ImageMetadata.Read(value);
}

/// <inheritdoc/>
protected override void WriteValue<TWriter>(ImageMetadata value, TWriter writer)
{
var span = writer.GetSpan(sizeHint: ImageMetadataSize);
value.Write(span);
writer.Advance(ImageMetadataSize);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- NuGet Package Shared Details -->
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<ItemGroup>
<PackageReference Include="Avalonia.Skia" />
<PackageReference Include="OneOf"/>
<PackageReference Include="NexusMods.MnemonicDB.Abstractions"/>
<PackageReference Include="NexusMods.MnemonicDB.SourceGenerator" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<PackageReference Include="BitFaster.Caching"/>
</ItemGroup>

</Project>
17 changes: 17 additions & 0 deletions src/Abstractions/NexusMods.Abstractions.Media/ServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Extensions.DependencyInjection;

namespace NexusMods.Abstractions.Media;

/// <summary>
/// Extension methods.
/// </summary>
public static class ServiceExtensions
{
/// <summary>
/// Adds media.
/// </summary>
public static IServiceCollection AddMedia(this IServiceCollection serviceCollection)
{
return serviceCollection.AddStoredImageModel();
}
}
23 changes: 23 additions & 0 deletions src/Abstractions/NexusMods.Abstractions.Media/StoredImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.Abstractions.Media;

/// <summary>
/// Represent an image.
/// </summary>
[UsedImplicitly]
public partial class StoredImage : IModelDefinition
{
private const string Namespace = "NexusMods.ImageStore.StoredImage";

/// <summary>
/// Image data.
/// </summary>
public static readonly ImageDataAttribute ImageData = new(Namespace, nameof(ImageData)) { NoHistory = true };

/// <summary>
/// Image metadata.
/// </summary>
public static readonly ImageMetadataAttribute Metadata = new(Namespace, nameof(Metadata));
}
Loading

0 comments on commit 7552bfb

Please sign in to comment.