mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 04:13:40 +01:00
fixese
This commit is contained in:
parent
5367d288d6
commit
0aa75306d4
8 changed files with 301 additions and 95 deletions
|
|
@ -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<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var tm = await Service<TextureManager>.GetAsync();
|
||||
return tm.NoThrottleCreateFromImage(await File.ReadAllBytesAsync(this.path, cancellationToken));
|
||||
return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,6 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
|
|||
var tm = await Service<TextureManager>.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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
|
||||
return (specs, bytes);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
internal sealed partial class TextureManager
|
||||
{
|
||||
private ComPtr<IWICImagingFactory> factory;
|
||||
private ComPtr<IWICImagingFactory> wicFactory;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[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
|
|||
}
|
||||
|
||||
/// <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(
|
||||
"StyleCop.CSharp.LayoutRules",
|
||||
"SA1519:Braces should not be omitted from multi-line child statement",
|
||||
Justification = "Multiple fixed blocks")]
|
||||
private unsafe Dictionary<Guid, string[]> GetSupportedContainerFormats()
|
||||
private unsafe Dictionary<Guid, string[]> GetSupportedContainerFormats(WICComponentType componentType)
|
||||
{
|
||||
var result = new Dictionary<Guid, string[]>();
|
||||
using var enumUnknown = default(ComPtr<IEnumUnknown>);
|
||||
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<IWICBitmap>);
|
||||
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<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>);
|
||||
WICConvertBitmapSource(
|
||||
&guidPixelFormat,
|
||||
(IWICBitmapSource*)tempBitmap.Get(),
|
||||
tempBitmap2.GetAddressOf()).ThrowOnError();
|
||||
using var tempBitmap2 = default(ComPtr<IWICBitmapSource>);
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITextureProvider>]
|
||||
[ResolveVia<ITextureSubstitutionProvider>]
|
||||
|
|
@ -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);
|
||||
|
||||
/// <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 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
|
|||
}
|
||||
}
|
||||
|
||||
/// <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"/>
|
||||
internal IDalamudTextureWrap NoThrottleCreateFromRaw(
|
||||
RawImageSpecification specs,
|
||||
|
|
|
|||
|
|
@ -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<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(
|
||||
rented,
|
||||
texture,
|
||||
Path.GetExtension(path),
|
||||
File.Create(path),
|
||||
props: new Dictionary<string, object>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
|||
/// <summary>
|
||||
/// Factory for the implementation of <see cref="IFontAtlas"/>.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed partial class FontAtlasFactory
|
||||
: IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -180,10 +180,15 @@ public partial interface ITextureProvider
|
|||
int dxgiFormat = 0,
|
||||
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
|
||||
/// 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>
|
||||
/// <param name="wrap">The texture wrap to save.</param>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue