mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 20:33: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;
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue