From 0aa75306d4bada6de5da501a3c7ec0aef3c94a4c Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sat, 2 Mar 2024 07:50:37 +0900 Subject: [PATCH] fixese --- .../FileSystemSharedImmediateTexture.cs | 3 +- .../ManifestResourceSharedImmediateTexture.cs | 2 +- .../Internal/TextureManager.FormatConvert.cs | 6 +- .../Interface/Internal/TextureManager.Wic.cs | 283 +++++++++++++++--- Dalamud/Interface/Internal/TextureManager.cs | 52 +--- .../Windows/Data/Widgets/TexWidget.cs | 39 ++- .../Internals/FontAtlasFactory.cs | 2 +- Dalamud/Plugin/Services/ITextureProvider.cs | 9 +- 8 files changed, 301 insertions(+), 95 deletions(-) diff --git a/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs b/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs index 702a83f52..06c366601 100644 --- a/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs +++ b/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs @@ -1,4 +1,3 @@ -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -44,6 +43,6 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture private async Task CreateTextureAsync(CancellationToken cancellationToken) { var tm = await Service.GetAsync(); - return tm.NoThrottleCreateFromImage(await File.ReadAllBytesAsync(this.path, cancellationToken)); + return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken); } } diff --git a/Dalamud/Interface/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs b/Dalamud/Interface/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs index a75a7cb68..c017a0764 100644 --- a/Dalamud/Interface/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs +++ b/Dalamud/Interface/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs @@ -59,6 +59,6 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe var tm = await Service.GetAsync(); var ms = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0); await stream.CopyToAsync(ms, cancellationToken); - return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, (int)ms.Length)); + return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, (int)ms.Length), cancellationToken); } } diff --git a/Dalamud/Interface/Internal/TextureManager.FormatConvert.cs b/Dalamud/Interface/Internal/TextureManager.FormatConvert.cs index 900eb5627..99474dca2 100644 --- a/Dalamud/Interface/Internal/TextureManager.FormatConvert.cs +++ b/Dalamud/Interface/Internal/TextureManager.FormatConvert.cs @@ -219,7 +219,11 @@ internal sealed partial class TextureManager 0, &mapped).ThrowOnError(); - var specs = RawImageSpecification.From((int)desc.Width, (int)desc.Height, (int)desc.Format); + var specs = new RawImageSpecification( + (int)desc.Width, + (int)desc.Height, + (int)mapped.RowPitch, + (int)desc.Format); var bytes = new Span(mapped.pData, checked((int)mapped.DepthPitch)).ToArray(); return (specs, bytes); } diff --git a/Dalamud/Interface/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Internal/TextureManager.Wic.cs index 1adeccede..66be9ca58 100644 --- a/Dalamud/Interface/Internal/TextureManager.Wic.cs +++ b/Dalamud/Interface/Internal/TextureManager.Wic.cs @@ -3,11 +3,17 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Dalamud.Interface.Internal.SharedImmediateTextures; +using Dalamud.Plugin.Services; using Dalamud.Utility; +using Lumina.Data; +using Lumina.Data.Files; + using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -18,7 +24,7 @@ namespace Dalamud.Interface.Internal; /// Service responsible for loading and disposing ImGui texture wraps. internal sealed partial class TextureManager { - private ComPtr factory; + private ComPtr wicFactory; /// [SuppressMessage( @@ -34,7 +40,7 @@ internal sealed partial class TextureManager CancellationToken cancellationToken = default) { var container = GUID.GUID_ContainerFormatPng; - foreach (var (k, v) in this.GetSupportedContainerFormats()) + foreach (var (k, v) in this.GetSupportedContainerFormats(WICComponentType.WICEncoder)) { if (v.Contains(extension, StringComparer.InvariantCultureIgnoreCase)) container = k; @@ -133,18 +139,231 @@ internal sealed partial class TextureManager } /// - public IEnumerable GetSupportedImageExtensions() => this.GetSupportedContainerFormats().Values; + public IEnumerable GetLoadSupportedImageExtensions() => + this.GetSupportedContainerFormats(WICComponentType.WICDecoder).Values; + + /// + public IEnumerable GetSaveSupportedImageExtensions() => + this.GetSupportedContainerFormats(WICComponentType.WICEncoder).Values; + + /// Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used + /// from implementation of s. + /// The data. + /// The cancellation token. + /// The loaded texture. + internal unsafe IDalamudTextureWrap NoThrottleCreateFromImage( + ReadOnlyMemory bytes, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(this.disposing, this); + + cancellationToken.ThrowIfCancellationRequested(); + + if (TexFileExtensions.IsPossiblyTexFile2D(bytes.Span)) + { + var bytesArray = bytes.ToArray(); + var tf = new TexFile(); + typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke( + tf, + new object?[] { bytesArray }); + typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke( + tf, + new object?[] { new LuminaBinaryReader(bytesArray) }); + // Note: FileInfo and FilePath are not used from TexFile; skip it. + try + { + return this.NoThrottleCreateFromTexFile(tf); + } + catch (Exception) + { + // ignore + } + } + + fixed (byte* p = bytes.Span) + { + using var wicStream = default(ComPtr); + this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError(); + wicStream.Get()->InitializeFromMemory(p, checked((uint)bytes.Length)).ThrowOnError(); + return this.NoThrottleCreateFromWicStream((IStream*)wicStream.Get(), cancellationToken); + } + } + + /// Creates a texture from the given path to an image file. Skips the load throttler; intended to be used + /// from implementation of s. + /// The path of the file.. + /// The cancellation token. + /// The loaded texture. + internal async Task NoThrottleCreateFromFileAsync( + string path, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(this.disposing, this); + + cancellationToken.ThrowIfCancellationRequested(); + + try + { + unsafe + { + fixed (char* pPath = path) + { + using var wicStream = default(ComPtr); + this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError(); + wicStream.Get()->InitializeFromFilename((ushort*)pPath, GENERIC_READ).ThrowOnError(); + return this.NoThrottleCreateFromWicStream((IStream*)wicStream.Get(), cancellationToken); + } + } + } + catch + { + try + { + await using var fp = File.OpenRead(path); + if (fp.Length >= Unsafe.SizeOf()) + { + var bytesArray = new byte[fp.Length]; + await fp.ReadExactlyAsync(bytesArray, cancellationToken); + if (TexFileExtensions.IsPossiblyTexFile2D(bytesArray)) + { + var tf = new TexFile(); + typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke( + tf, + new object?[] { bytesArray }); + typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke( + tf, + new object?[] { new LuminaBinaryReader(bytesArray) }); + // Note: FileInfo and FilePath are not used from TexFile; skip it. + return this.NoThrottleCreateFromTexFile(tf); + } + } + } + catch (Exception) + { + // ignore + } + + throw; + } + } + + /// + /// Gets the corresponding from a containing a WIC pixel format. + /// + /// The WIC pixel format. + /// The corresponding , or if + /// unavailable. + private static DXGI_FORMAT GetCorrespondingDxgiFormat(Guid fmt) => 0 switch + { + // See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile + _ when fmt == GUID.GUID_WICPixelFormat128bppRGBAFloat => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT, + _ when fmt == GUID.GUID_WICPixelFormat64bppRGBAHalf => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT, + _ when fmt == GUID.GUID_WICPixelFormat64bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102XR => DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102 => DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat16bppBGRA5551 => DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat16bppBGR565 => DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat32bppGrayFloat => DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT, + _ when fmt == GUID.GUID_WICPixelFormat16bppGrayHalf => DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT, + _ when fmt == GUID.GUID_WICPixelFormat16bppGray => DXGI_FORMAT.DXGI_FORMAT_R16_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat8bppGray => DXGI_FORMAT.DXGI_FORMAT_R8_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat8bppAlpha => DXGI_FORMAT.DXGI_FORMAT_A8_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat32bppBGRA => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, + _ when fmt == GUID.GUID_WICPixelFormat32bppBGR => DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM, + _ => DXGI_FORMAT.DXGI_FORMAT_UNKNOWN, + }; + + private unsafe IDalamudTextureWrap NoThrottleCreateFromWicStream( + IStream* wicStream, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + using var decoder = default(ComPtr); + this.wicFactory.Get()->CreateDecoderFromStream( + wicStream, + null, + WICDecodeOptions.WICDecodeMetadataCacheOnDemand, + decoder.GetAddressOf()).ThrowOnError(); + + cancellationToken.ThrowIfCancellationRequested(); + + using var frame = default(ComPtr); + decoder.Get()->GetFrame(0, frame.GetAddressOf()).ThrowOnError(); + var pixelFormat = default(Guid); + frame.Get()->GetPixelFormat(&pixelFormat).ThrowOnError(); + var dxgiFormat = GetCorrespondingDxgiFormat(pixelFormat); + + cancellationToken.ThrowIfCancellationRequested(); + + using var bitmapSource = default(ComPtr); + if (dxgiFormat == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN || !this.IsDxgiFormatSupported(dxgiFormat)) + { + dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM; + pixelFormat = GUID.GUID_WICPixelFormat32bppBGRA; + WICConvertBitmapSource(&pixelFormat, (IWICBitmapSource*)frame.Get(), bitmapSource.GetAddressOf()) + .ThrowOnError(); + } + else + { + frame.As(&bitmapSource); + } + + cancellationToken.ThrowIfCancellationRequested(); + + using var bitmap = default(ComPtr); + using var bitmapLock = default(ComPtr); + WICRect rcLock; + uint stride; + uint cbBufferSize; + byte* pbData; + if (bitmapSource.As(&bitmap).FAILED) + { + bitmapSource.Get()->GetSize((uint*)&rcLock.Width, (uint*)&rcLock.Height).ThrowOnError(); + this.wicFactory.Get()->CreateBitmap( + (uint)rcLock.Width, + (uint)rcLock.Height, + &pixelFormat, + WICBitmapCreateCacheOption.WICBitmapCacheOnDemand, + bitmap.GetAddressOf()).ThrowOnError(); + + bitmap.Get()->Lock( + &rcLock, + (uint)WICBitmapLockFlags.WICBitmapLockWrite, + bitmapLock.ReleaseAndGetAddressOf()) + .ThrowOnError(); + bitmapLock.Get()->GetStride(&stride).ThrowOnError(); + bitmapLock.Get()->GetDataPointer(&cbBufferSize, &pbData).ThrowOnError(); + bitmapSource.Get()->CopyPixels(null, stride, cbBufferSize, pbData).ThrowOnError(); + } + + cancellationToken.ThrowIfCancellationRequested(); + + bitmap.Get()->Lock( + &rcLock, + (uint)WICBitmapLockFlags.WICBitmapLockRead, + bitmapLock.ReleaseAndGetAddressOf()) + .ThrowOnError(); + bitmapSource.Get()->GetSize((uint*)&rcLock.Width, (uint*)&rcLock.Height).ThrowOnError(); + bitmapLock.Get()->GetStride(&stride).ThrowOnError(); + bitmapLock.Get()->GetDataPointer(&cbBufferSize, &pbData).ThrowOnError(); + bitmapSource.Get()->CopyPixels(null, stride, cbBufferSize, pbData).ThrowOnError(); + return this.NoThrottleCreateFromRaw( + new RawImageSpecification(rcLock.Width, rcLock.Height, (int)stride, (int)dxgiFormat), + new(pbData, (int)cbBufferSize)); + } [SuppressMessage( "StyleCop.CSharp.LayoutRules", "SA1519:Braces should not be omitted from multi-line child statement", Justification = "Multiple fixed blocks")] - private unsafe Dictionary GetSupportedContainerFormats() + private unsafe Dictionary GetSupportedContainerFormats(WICComponentType componentType) { var result = new Dictionary(); using var enumUnknown = default(ComPtr); - this.factory.Get()->CreateComponentEnumerator( - (uint)WICComponentType.WICEncoder, + this.wicFactory.Get()->CreateComponentEnumerator( + (uint)componentType, (uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault, enumUnknown.GetAddressOf()).ThrowOnError(); @@ -207,7 +426,7 @@ internal sealed partial class TextureManager var guidPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA; unsafe { - this.factory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError(); + this.wicFactory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError(); cancellationToken.ThrowIfCancellationRequested(); encoder.Get()->Initialize(wrappedStream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache) @@ -225,42 +444,28 @@ internal sealed partial class TextureManager encoderFrame.Get()->SetPixelFormat(&guidPixelFormat).ThrowOnError(); encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError(); - if (guidPixelFormat == GUID.GUID_WICPixelFormat32bppBGRA) + using var tempBitmap = default(ComPtr); + fixed (Guid* pGuid = &GUID.GUID_WICPixelFormat32bppBGRA) + fixed (byte* pBytes = bytes) { - fixed (byte* pByte = bytes) - { - encoderFrame.Get()->WritePixels( - (uint)specs.Height, - (uint)specs.Pitch, - checked((uint)bytes.Length), - pByte).ThrowOnError(); - } + this.wicFactory.Get()->CreateBitmapFromMemory( + (uint)specs.Width, + (uint)specs.Height, + pGuid, + (uint)specs.Pitch, + checked((uint)bytes.Length), + pBytes, + tempBitmap.GetAddressOf()).ThrowOnError(); } - else - { - using var tempBitmap = default(ComPtr); - fixed (Guid* pGuid = &GUID.GUID_WICPixelFormat32bppBGRA) - fixed (byte* pBytes = bytes) - { - this.factory.Get()->CreateBitmapFromMemory( - (uint)specs.Width, - (uint)specs.Height, - pGuid, - (uint)specs.Pitch, - checked((uint)bytes.Length), - pBytes, - tempBitmap.GetAddressOf()).ThrowOnError(); - } - using var tempBitmap2 = default(ComPtr); - WICConvertBitmapSource( - &guidPixelFormat, - (IWICBitmapSource*)tempBitmap.Get(), - tempBitmap2.GetAddressOf()).ThrowOnError(); + using var tempBitmap2 = default(ComPtr); + WICConvertBitmapSource( + &guidPixelFormat, + (IWICBitmapSource*)tempBitmap.Get(), + tempBitmap2.GetAddressOf()).ThrowOnError(); - encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError(); - encoderFrame.Get()->WriteSource(tempBitmap2.Get(), null).ThrowOnError(); - } + encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError(); + encoderFrame.Get()->WriteSource(tempBitmap2.Get(), null).ThrowOnError(); cancellationToken.ThrowIfCancellationRequested(); diff --git a/Dalamud/Interface/Internal/TextureManager.cs b/Dalamud/Interface/Internal/TextureManager.cs index 8e61e42b0..ce9f2a22d 100644 --- a/Dalamud/Interface/Internal/TextureManager.cs +++ b/Dalamud/Interface/Internal/TextureManager.cs @@ -18,7 +18,6 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; -using Lumina.Data; using Lumina.Data.Files; using SharpDX; @@ -36,7 +35,7 @@ namespace Dalamud.Interface.Internal; /// Service responsible for loading and disposing ImGui texture wraps. [PluginInterface] [InterfaceVersion("1.0")] -[ServiceManager.BlockingEarlyLoadedService] +[ServiceManager.EarlyLoadedService] #pragma warning disable SA1015 [ResolveVia] [ResolveVia] @@ -96,7 +95,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu null, (uint)CLSCTX.CLSCTX_INPROC_SERVER, piidWicImagingFactory, - (void**)this.factory.GetAddressOf()).ThrowOnError(); + (void**)this.wicFactory.GetAddressOf()).ThrowOnError(); } } } @@ -140,7 +139,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu this.drawsOneSquare?.Dispose(); this.drawsOneSquare = null; - this.factory.Reset(); + this.wicFactory.Reset(); return; @@ -254,7 +253,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu CancellationToken cancellationToken = default) => this.textureLoadThrottler.LoadTextureAsync( new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(), - ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray()), ct), + ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct), cancellationToken); /// @@ -268,7 +267,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu { await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new(); await stream.CopyToAsync(ms, ct).ConfigureAwait(false); - return this.NoThrottleCreateFromImage(ms.GetBuffer()); + return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct); }, cancellationToken) .ContinueWith( @@ -464,47 +463,6 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu } } - /// Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used - /// from implementation of s. - /// The data. - /// The loaded texture. - internal IDalamudTextureWrap NoThrottleCreateFromImage(ReadOnlyMemory bytes) - { - ObjectDisposedException.ThrowIf(this.disposing, this); - - if (this.interfaceManager.Scene is not { } scene) - { - _ = Service.Get(); - scene = this.interfaceManager.Scene ?? throw new InvalidOperationException(); - } - - var bytesArray = bytes.ToArray(); - var texFileAttemptException = default(Exception); - if (TexFileExtensions.IsPossiblyTexFile2D(bytesArray)) - { - var tf = new TexFile(); - typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke( - tf, - new object?[] { bytesArray }); - typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke( - tf, - new object?[] { new LuminaBinaryReader(bytesArray) }); - // Note: FileInfo and FilePath are not used from TexFile; skip it. - try - { - return this.NoThrottleCreateFromTexFile(tf); - } - catch (Exception e) - { - texFileAttemptException = e; - } - } - - return new DalamudTextureWrap( - scene.LoadImage(bytesArray) - ?? throw texFileAttemptException ?? new("Failed to load image because of an unknown reason.")); - } - /// internal IDalamudTextureWrap NoThrottleCreateFromRaw( RawImageSpecification specs, diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs index a39a48f66..45a3a5331 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs @@ -189,6 +189,25 @@ internal class TexWidget : IDataWindowWidget }; } + ImGui.SameLine(); + if (ImGui.Button("Save")) + { + this.fileDialogManager.SaveFileDialog( + "Save texture...", + string.Join( + ',', + this.textureManager + .GetSaveSupportedImageExtensions() + .Select(x => $"{string.Join(" | ", x)}{{{string.Join(',', x)}}}")), + $"Texture {t.Id}.png", + ".png", + (ok, path) => + { + if (ok && t.GetTexture(this.textureManager) is { } source) + Task.Run(() => this.SaveTextureWrap(source, path)); + }); + } + ImGui.SameLine(); if (ImGui.Button("Copy Reference")) runLater = () => this.addedTextures.Add(t.CreateFromSharedLowLevelResource(this.textureManager)); @@ -329,7 +348,7 @@ internal class TexWidget : IDataWindowWidget string.Join( ',', this.textureManager - .GetSupportedImageExtensions() + .GetSaveSupportedImageExtensions() .Select(x => $"{string.Join(" | ", x)}{{{string.Join(',', x)}}}")), Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), ".png"), ".png", @@ -380,8 +399,24 @@ internal class TexWidget : IDataWindowWidget try { using var rented = await texture.RentAsync(); + this.SaveTextureWrap(rented, path); + } + catch (Exception e) + { + Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveImmediateTexture)}"); + Service.Get().AddNotification( + $"Failed to save file: {e}", + this.DisplayName, + NotificationType.Error); + } + } + + private async void SaveTextureWrap(IDalamudTextureWrap texture, string path) + { + try + { await this.textureManager.SaveAsImageFormatToStreamAsync( - rented, + texture, Path.GetExtension(path), File.Create(path), props: new Dictionary diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs index a632f14e4..6f2a56394 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs @@ -31,7 +31,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals; /// /// Factory for the implementation of . /// -[ServiceManager.BlockingEarlyLoadedService] +[ServiceManager.EarlyLoadedService] internal sealed partial class FontAtlasFactory : IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable { diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs index 723801c9d..ac6ab8baf 100644 --- a/Dalamud/Plugin/Services/ITextureProvider.cs +++ b/Dalamud/Plugin/Services/ITextureProvider.cs @@ -180,10 +180,15 @@ public partial interface ITextureProvider int dxgiFormat = 0, CancellationToken cancellationToken = default); - /// Gets the supported image file extensions. + /// Gets the supported image file extensions available for loading. /// The supported extensions. Each string[] entry indicates that there can be multiple extensions /// that correspond to one container format. - IEnumerable GetSupportedImageExtensions(); + IEnumerable GetLoadSupportedImageExtensions(); + + /// Gets the supported image file extensions available for saving. + /// The supported extensions. Each string[] entry indicates that there can be multiple extensions + /// that correspond to one container format. + IEnumerable GetSaveSupportedImageExtensions(); /// Saves a texture wrap to a stream in an image file format. /// The texture wrap to save.