This commit is contained in:
Soreepeong 2024-03-02 07:50:37 +09:00
parent 5367d288d6
commit 0aa75306d4
8 changed files with 301 additions and 95 deletions

View file

@ -1,4 +1,3 @@
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -44,6 +43,6 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken) private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
{ {
var tm = await Service<TextureManager>.GetAsync(); var tm = await Service<TextureManager>.GetAsync();
return tm.NoThrottleCreateFromImage(await File.ReadAllBytesAsync(this.path, cancellationToken)); return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);
} }
} }

View file

@ -59,6 +59,6 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
var tm = await Service<TextureManager>.GetAsync(); var tm = await Service<TextureManager>.GetAsync();
var ms = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0); var ms = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0);
await stream.CopyToAsync(ms, cancellationToken); 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);
} }
} }

View file

@ -219,7 +219,11 @@ internal sealed partial class TextureManager
0, 0,
&mapped).ThrowOnError(); &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<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray(); var bytes = new Span<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
return (specs, bytes); return (specs, bytes);
} }

View file

@ -3,11 +3,17 @@ using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Interface.Internal.SharedImmediateTextures;
using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using Lumina.Data;
using Lumina.Data.Files;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
@ -18,7 +24,7 @@ namespace Dalamud.Interface.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary> /// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
internal sealed partial class TextureManager internal sealed partial class TextureManager
{ {
private ComPtr<IWICImagingFactory> factory; private ComPtr<IWICImagingFactory> wicFactory;
/// <inheritdoc/> /// <inheritdoc/>
[SuppressMessage( [SuppressMessage(
@ -34,7 +40,7 @@ internal sealed partial class TextureManager
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var container = GUID.GUID_ContainerFormatPng; 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)) if (v.Contains(extension, StringComparer.InvariantCultureIgnoreCase))
container = k; container = k;
@ -133,18 +139,231 @@ internal sealed partial class TextureManager
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<string[]> GetSupportedImageExtensions() => this.GetSupportedContainerFormats().Values; public IEnumerable<string[]> GetLoadSupportedImageExtensions() =>
this.GetSupportedContainerFormats(WICComponentType.WICDecoder).Values;
/// <inheritdoc/>
public IEnumerable<string[]> GetSaveSupportedImageExtensions() =>
this.GetSupportedContainerFormats(WICComponentType.WICEncoder).Values;
/// <summary>Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
/// <param name="bytes">The data.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The loaded texture.</returns>
internal unsafe IDalamudTextureWrap NoThrottleCreateFromImage(
ReadOnlyMemory<byte> 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<IWICStream>);
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
wicStream.Get()->InitializeFromMemory(p, checked((uint)bytes.Length)).ThrowOnError();
return this.NoThrottleCreateFromWicStream((IStream*)wicStream.Get(), cancellationToken);
}
}
/// <summary>Creates a texture from the given path to an image file. Skips the load throttler; intended to be used
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
/// <param name="path">The path of the file..</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The loaded texture.</returns>
internal async Task<IDalamudTextureWrap> NoThrottleCreateFromFileAsync(
string path,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(this.disposing, this);
cancellationToken.ThrowIfCancellationRequested();
try
{
unsafe
{
fixed (char* pPath = path)
{
using var wicStream = default(ComPtr<IWICStream>);
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<TexFile.TexHeader>())
{
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;
}
}
/// <summary>
/// Gets the corresponding <see cref="DXGI_FORMAT"/> from a <see cref="Guid"/> containing a WIC pixel format.
/// </summary>
/// <param name="fmt">The WIC pixel format.</param>
/// <returns>The corresponding <see cref="DXGI_FORMAT"/>, or <see cref="DXGI_FORMAT.DXGI_FORMAT_UNKNOWN"/> if
/// unavailable.</returns>
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<IWICBitmapDecoder>);
this.wicFactory.Get()->CreateDecoderFromStream(
wicStream,
null,
WICDecodeOptions.WICDecodeMetadataCacheOnDemand,
decoder.GetAddressOf()).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
using var frame = default(ComPtr<IWICBitmapFrameDecode>);
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<IWICBitmapSource>);
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<IWICBitmap>);
using var bitmapLock = default(ComPtr<IWICBitmapLock>);
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( [SuppressMessage(
"StyleCop.CSharp.LayoutRules", "StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement", "SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed blocks")] Justification = "Multiple fixed blocks")]
private unsafe Dictionary<Guid, string[]> GetSupportedContainerFormats() private unsafe Dictionary<Guid, string[]> GetSupportedContainerFormats(WICComponentType componentType)
{ {
var result = new Dictionary<Guid, string[]>(); var result = new Dictionary<Guid, string[]>();
using var enumUnknown = default(ComPtr<IEnumUnknown>); using var enumUnknown = default(ComPtr<IEnumUnknown>);
this.factory.Get()->CreateComponentEnumerator( this.wicFactory.Get()->CreateComponentEnumerator(
(uint)WICComponentType.WICEncoder, (uint)componentType,
(uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault, (uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault,
enumUnknown.GetAddressOf()).ThrowOnError(); enumUnknown.GetAddressOf()).ThrowOnError();
@ -207,7 +426,7 @@ internal sealed partial class TextureManager
var guidPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA; var guidPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
unsafe unsafe
{ {
this.factory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError(); this.wicFactory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
encoder.Get()->Initialize(wrappedStream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache) encoder.Get()->Initialize(wrappedStream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
@ -225,42 +444,28 @@ internal sealed partial class TextureManager
encoderFrame.Get()->SetPixelFormat(&guidPixelFormat).ThrowOnError(); encoderFrame.Get()->SetPixelFormat(&guidPixelFormat).ThrowOnError();
encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError(); encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
if (guidPixelFormat == GUID.GUID_WICPixelFormat32bppBGRA) using var tempBitmap = default(ComPtr<IWICBitmap>);
fixed (Guid* pGuid = &GUID.GUID_WICPixelFormat32bppBGRA)
fixed (byte* pBytes = bytes)
{ {
fixed (byte* pByte = bytes) this.wicFactory.Get()->CreateBitmapFromMemory(
{ (uint)specs.Width,
encoderFrame.Get()->WritePixels( (uint)specs.Height,
(uint)specs.Height, pGuid,
(uint)specs.Pitch, (uint)specs.Pitch,
checked((uint)bytes.Length), checked((uint)bytes.Length),
pByte).ThrowOnError(); pBytes,
} tempBitmap.GetAddressOf()).ThrowOnError();
} }
else
{
using var tempBitmap = default(ComPtr<IWICBitmap>);
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<IWICBitmapSource>); using var tempBitmap2 = default(ComPtr<IWICBitmapSource>);
WICConvertBitmapSource( WICConvertBitmapSource(
&guidPixelFormat, &guidPixelFormat,
(IWICBitmapSource*)tempBitmap.Get(), (IWICBitmapSource*)tempBitmap.Get(),
tempBitmap2.GetAddressOf()).ThrowOnError(); tempBitmap2.GetAddressOf()).ThrowOnError();
encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError(); encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
encoderFrame.Get()->WriteSource(tempBitmap2.Get(), null).ThrowOnError(); encoderFrame.Get()->WriteSource(tempBitmap2.Get(), null).ThrowOnError();
}
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();

View file

@ -18,7 +18,6 @@ using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using Lumina.Data;
using Lumina.Data.Files; using Lumina.Data.Files;
using SharpDX; using SharpDX;
@ -36,7 +35,7 @@ namespace Dalamud.Interface.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary> /// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ITextureProvider>] [ResolveVia<ITextureProvider>]
[ResolveVia<ITextureSubstitutionProvider>] [ResolveVia<ITextureSubstitutionProvider>]
@ -96,7 +95,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
null, null,
(uint)CLSCTX.CLSCTX_INPROC_SERVER, (uint)CLSCTX.CLSCTX_INPROC_SERVER,
piidWicImagingFactory, 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?.Dispose();
this.drawsOneSquare = null; this.drawsOneSquare = null;
this.factory.Reset(); this.wicFactory.Reset();
return; return;
@ -254,7 +253,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.LoadTextureAsync( this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(), new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray()), ct), ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
cancellationToken); cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
@ -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 using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
await stream.CopyToAsync(ms, ct).ConfigureAwait(false); await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
return this.NoThrottleCreateFromImage(ms.GetBuffer()); return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
}, },
cancellationToken) cancellationToken)
.ContinueWith( .ContinueWith(
@ -464,47 +463,6 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
} }
} }
/// <summary>Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
/// <param name="bytes">The data.</param>
/// <returns>The loaded texture.</returns>
internal IDalamudTextureWrap NoThrottleCreateFromImage(ReadOnlyMemory<byte> bytes)
{
ObjectDisposedException.ThrowIf(this.disposing, this);
if (this.interfaceManager.Scene is not { } scene)
{
_ = Service<InterfaceManager.InterfaceManagerWithScene>.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."));
}
/// <inheritdoc cref="ITextureProvider.CreateFromRaw"/> /// <inheritdoc cref="ITextureProvider.CreateFromRaw"/>
internal IDalamudTextureWrap NoThrottleCreateFromRaw( internal IDalamudTextureWrap NoThrottleCreateFromRaw(
RawImageSpecification specs, RawImageSpecification specs,

View file

@ -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(); ImGui.SameLine();
if (ImGui.Button("Copy Reference")) if (ImGui.Button("Copy Reference"))
runLater = () => this.addedTextures.Add(t.CreateFromSharedLowLevelResource(this.textureManager)); runLater = () => this.addedTextures.Add(t.CreateFromSharedLowLevelResource(this.textureManager));
@ -329,7 +348,7 @@ internal class TexWidget : IDataWindowWidget
string.Join( string.Join(
',', ',',
this.textureManager this.textureManager
.GetSupportedImageExtensions() .GetSaveSupportedImageExtensions()
.Select(x => $"{string.Join(" | ", x)}{{{string.Join(',', x)}}}")), .Select(x => $"{string.Join(" | ", x)}{{{string.Join(',', x)}}}")),
Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), ".png"), Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), ".png"),
".png", ".png",
@ -380,8 +399,24 @@ internal class TexWidget : IDataWindowWidget
try try
{ {
using var rented = await texture.RentAsync(); using var rented = await texture.RentAsync();
this.SaveTextureWrap(rented, path);
}
catch (Exception e)
{
Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveImmediateTexture)}");
Service<NotificationManager>.Get().AddNotification(
$"Failed to save file: {e}",
this.DisplayName,
NotificationType.Error);
}
}
private async void SaveTextureWrap(IDalamudTextureWrap texture, string path)
{
try
{
await this.textureManager.SaveAsImageFormatToStreamAsync( await this.textureManager.SaveAsImageFormatToStreamAsync(
rented, texture,
Path.GetExtension(path), Path.GetExtension(path),
File.Create(path), File.Create(path),
props: new Dictionary<string, object> props: new Dictionary<string, object>

View file

@ -31,7 +31,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
/// <summary> /// <summary>
/// Factory for the implementation of <see cref="IFontAtlas"/>. /// Factory for the implementation of <see cref="IFontAtlas"/>.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed partial class FontAtlasFactory internal sealed partial class FontAtlasFactory
: IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable : IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable
{ {

View file

@ -180,10 +180,15 @@ public partial interface ITextureProvider
int dxgiFormat = 0, int dxgiFormat = 0,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary>Gets the supported image file extensions.</summary> /// <summary>Gets the supported image file extensions available for loading.</summary>
/// <returns>The supported extensions. Each <c>string[]</c> entry indicates that there can be multiple extensions /// <returns>The supported extensions. Each <c>string[]</c> entry indicates that there can be multiple extensions
/// that correspond to one container format.</returns> /// that correspond to one container format.</returns>
IEnumerable<string[]> GetSupportedImageExtensions(); IEnumerable<string[]> GetLoadSupportedImageExtensions();
/// <summary>Gets the supported image file extensions available for saving.</summary>
/// <returns>The supported extensions. Each <c>string[]</c> entry indicates that there can be multiple extensions
/// that correspond to one container format.</returns>
IEnumerable<string[]> GetSaveSupportedImageExtensions();
/// <summary>Saves a texture wrap to a stream in an image file format.</summary> /// <summary>Saves a texture wrap to a stream in an image file format.</summary>
/// <param name="wrap">The texture wrap to save.</param> /// <param name="wrap">The texture wrap to save.</param>