Dalamud/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
2024-03-03 20:08:52 +09:00

655 lines
29 KiB
C#

using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Dalamud.Utility.TerraFxCom;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
namespace Dalamud.Interface.Textures.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed blocks")]
internal sealed partial class TextureManager
{
/// <inheritdoc/>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed blocks")]
public async Task SaveToStreamAsync(
IDalamudTextureWrap wrap,
Guid containerGuid,
Stream stream,
IReadOnlyDictionary<string, object>? props = null,
bool leaveWrapOpen = false,
bool leaveStreamOpen = false,
CancellationToken cancellationToken = default)
{
using var wrapDispose = leaveWrapOpen ? null : wrap;
var texDesc = default(D3D11_TEXTURE2D_DESC);
unsafe
{
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>);
using var context = default(ComPtr<ID3D11DeviceContext>);
using var tex2D = default(ComPtr<ID3D11Texture2D>);
fixed (Guid* piid = &IID.IID_ID3D11ShaderResourceView)
((IUnknown*)wrap.ImGuiHandle)->QueryInterface(piid, (void**)texSrv.GetAddressOf()).ThrowOnError();
this.Device.Get()->GetImmediateContext(context.GetAddressOf());
using (var texRes = default(ComPtr<ID3D11Resource>))
{
texSrv.Get()->GetResource(texRes.GetAddressOf());
using var tex2DTemp = default(ComPtr<ID3D11Texture2D>);
texRes.As(&tex2DTemp).ThrowOnError();
tex2D.Swap(&tex2DTemp);
}
tex2D.Get()->GetDesc(&texDesc);
}
var dxgiFormat = texDesc.Format;
if (!WicManager.GetCorrespondingWicPixelFormat(dxgiFormat, out _, out _))
dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
using var istream = ManagedIStream.Create(stream, leaveStreamOpen);
var (specs, bytes) = await this.GetRawDataFromExistingTextureAsync(
wrap,
new()
{
Format = dxgiFormat,
},
true,
cancellationToken).ConfigureAwait(false);
this.Wic.SaveToStreamUsingWic(
specs,
bytes,
containerGuid,
istream,
props,
cancellationToken);
}
/// <inheritdoc/>
public async Task SaveToFileAsync(
IDalamudTextureWrap wrap,
Guid containerGuid,
string path,
IReadOnlyDictionary<string, object>? props = null,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default)
{
using var wrapDispose = leaveWrapOpen ? null : wrap;
var pathTemp = $"{path}.{GetCurrentThreadId():X08}{Environment.TickCount64:X16}.tmp";
try
{
await this.SaveToStreamAsync(
wrap,
containerGuid,
File.Create(pathTemp),
props,
leaveWrapOpen: true,
leaveStreamOpen: false,
cancellationToken: cancellationToken);
}
catch (Exception e)
{
try
{
if (File.Exists(pathTemp))
File.Delete(pathTemp);
}
catch (Exception e2)
{
throw new AggregateException(
"Failed to save the file, and failed to remove the temporary file.",
e,
e2);
}
throw;
}
try
{
try
{
File.Replace(pathTemp, path, null, true);
}
catch
{
File.Move(pathTemp, path, true);
}
}
catch (Exception e)
{
try
{
if (File.Exists(pathTemp))
File.Delete(pathTemp);
}
catch (Exception e2)
{
throw new AggregateException(
"Failed to move the temporary file to the target path, and failed to remove the temporary file.",
e,
e2);
}
throw;
}
}
/// <inheritdoc/>
IEnumerable<IBitmapCodecInfo> ITextureProvider.GetSupportedImageDecoderInfos() =>
this.Wic.GetSupportedDecoderInfos();
/// <inheritdoc/>
IEnumerable<IBitmapCodecInfo> ITextureProvider.GetSupportedImageEncoderInfos() =>
this.Wic.GetSupportedEncoderInfos();
/// <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 IDalamudTextureWrap NoThrottleCreateFromImage(
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(this.disposing, this);
cancellationToken.ThrowIfCancellationRequested();
try
{
using var handle = bytes.Pin();
using var stream = this.Wic.CreateIStreamFromMemory(handle, bytes.Length);
return this.Wic.NoThrottleCreateFromWicStream(stream, cancellationToken);
}
catch (Exception e1)
{
try
{
return this.NoThrottleCreateFromTexFile(bytes.Span);
}
catch (Exception e2)
{
throw new AggregateException(e1, e2);
}
}
}
/// <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
{
using var stream = this.Wic.CreateIStreamFromFile(path);
return this.Wic.NoThrottleCreateFromWicStream(stream, cancellationToken);
}
catch (Exception e1)
{
try
{
return this.NoThrottleCreateFromTexFile(await File.ReadAllBytesAsync(path, cancellationToken));
}
catch (Exception e2)
{
throw new AggregateException(e1, e2);
}
}
}
/// <summary>A part of texture manager that uses Windows Imaging Component under the hood.</summary>
internal sealed class WicManager : IDisposable
{
private readonly TextureManager textureManager;
private ComPtr<IWICImagingFactory> wicFactory;
private ComPtr<IWICImagingFactory2> wicFactory2;
/// <summary>Initializes a new instance of the <see cref="WicManager"/> class.</summary>
/// <param name="textureManager">An instance of <see cref="Interface.Textures.Internal.TextureManager"/>.</param>
public WicManager(TextureManager textureManager)
{
this.textureManager = textureManager;
unsafe
{
fixed (Guid* pclsidWicImagingFactory = &CLSID.CLSID_WICImagingFactory)
fixed (Guid* piidWicImagingFactory = &IID.IID_IWICImagingFactory)
fixed (Guid* pclsidWicImagingFactory2 = &CLSID.CLSID_WICImagingFactory2)
fixed (Guid* piidWicImagingFactory2 = &IID.IID_IWICImagingFactory2)
{
if (CoCreateInstance(
pclsidWicImagingFactory2,
null,
(uint)CLSCTX.CLSCTX_INPROC_SERVER,
piidWicImagingFactory2,
(void**)this.wicFactory2.GetAddressOf()).SUCCEEDED)
{
this.wicFactory2.As(ref this.wicFactory).ThrowOnError();
}
else
{
CoCreateInstance(
pclsidWicImagingFactory,
null,
(uint)CLSCTX.CLSCTX_INPROC_SERVER,
piidWicImagingFactory,
(void**)this.wicFactory.GetAddressOf()).ThrowOnError();
}
}
}
}
/// <summary>
/// Finalizes an instance of the <see cref="WicManager"/> class.
/// </summary>
~WicManager() => this.ReleaseUnmanagedResource();
/// <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>
public 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,
};
/// <summary>
/// Gets the corresponding <see cref="Guid"/> containing a WIC pixel format from a <see cref="DXGI_FORMAT"/>.
/// </summary>
/// <param name="dxgiPixelFormat">The DXGI pixel format.</param>
/// <param name="wicPixelFormat">The corresponding <see cref="Guid"/>.</param>
/// <param name="srgb">Whether the image is in SRGB.</param>
/// <returns><c>true</c> if a corresponding pixel format exists.</returns>
public static bool GetCorrespondingWicPixelFormat(
DXGI_FORMAT dxgiPixelFormat,
out Guid wicPixelFormat,
out bool srgb)
{
wicPixelFormat = dxgiPixelFormat switch
{
// See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat64bppRGBAHalf,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppRGBA,
DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => GUID.GUID_WICPixelFormat32bppRGBA1010102XR,
DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM => GUID.GUID_WICPixelFormat32bppRGBA1010102,
DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => GUID.GUID_WICPixelFormat16bppBGRA5551,
DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM => GUID.GUID_WICPixelFormat16bppBGR565,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => GUID.GUID_WICPixelFormat32bppGrayFloat,
DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT => GUID.GUID_WICPixelFormat16bppGrayHalf,
DXGI_FORMAT.DXGI_FORMAT_R16_UNORM => GUID.GUID_WICPixelFormat16bppGray,
DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => GUID.GUID_WICPixelFormat8bppAlpha,
DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM => GUID.GUID_WICPixelFormat32bppRGBA,
DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => GUID.GUID_WICPixelFormat32bppRGBA,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => GUID.GUID_WICPixelFormat32bppBGRA,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => GUID.GUID_WICPixelFormat32bppBGRA,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => GUID.GUID_WICPixelFormat32bppBGR,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB => GUID.GUID_WICPixelFormat32bppBGR,
_ => Guid.Empty,
};
srgb = dxgiPixelFormat
is DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
or DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB
or DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
return wicPixelFormat != Guid.Empty;
}
/// <inheritdoc/>
public void Dispose()
{
this.ReleaseUnmanagedResource();
GC.SuppressFinalize(this);
}
/// <summary>Creates a new instance of <see cref="IStream"/> from a <see cref="MemoryHandle"/>.</summary>
/// <param name="handle">An instance of <see cref="MemoryHandle"/>.</param>
/// <param name="length">The number of bytes in the memory.</param>
/// <returns>The new instance of <see cref="IStream"/>.</returns>
public unsafe ComPtr<IStream> CreateIStreamFromMemory(MemoryHandle handle, int length)
{
using var wicStream = default(ComPtr<IWICStream>);
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
wicStream.Get()->InitializeFromMemory((byte*)handle.Pointer, checked((uint)length)).ThrowOnError();
var res = default(ComPtr<IStream>);
wicStream.As(ref res).ThrowOnError();
return res;
}
/// <summary>Creates a new instance of <see cref="IStream"/> from a file path.</summary>
/// <param name="path">The file path.</param>
/// <returns>The new instance of <see cref="IStream"/>.</returns>
public unsafe ComPtr<IStream> CreateIStreamFromFile(string path)
{
using var wicStream = default(ComPtr<IWICStream>);
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
fixed (char* pPath = path)
wicStream.Get()->InitializeFromFilename((ushort*)pPath, GENERIC_READ).ThrowOnError();
var res = default(ComPtr<IStream>);
wicStream.As(ref res).ThrowOnError();
return res;
}
/// <summary>Creates a new instance of <see cref="IDalamudTextureWrap"/> from a <see cref="IStream"/>.</summary>
/// <param name="stream">The stream that will NOT be closed after.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The newly loaded texture.</returns>
public unsafe IDalamudTextureWrap NoThrottleCreateFromWicStream(
ComPtr<IStream> stream,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
using var decoder = default(ComPtr<IWICBitmapDecoder>);
this.wicFactory.Get()->CreateDecoderFromStream(
stream,
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.textureManager.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.textureManager.NoThrottleCreateFromRaw(
new(rcLock.Width, rcLock.Height, (int)dxgiFormat, (int)stride),
new(pbData, (int)cbBufferSize));
}
/// <summary>Gets the supported bitmap codecs.</summary>
/// <returns>The supported encoders.</returns>
public IEnumerable<BitmapCodecInfo> GetSupportedEncoderInfos()
{
foreach (var ptr in new ComponentEnumerable<IWICBitmapCodecInfo>(
this.wicFactory,
WICComponentType.WICEncoder))
yield return new(ptr);
}
/// <summary>Gets the supported bitmap codecs.</summary>
/// <returns>The supported decoders.</returns>
public IEnumerable<BitmapCodecInfo> GetSupportedDecoderInfos()
{
foreach (var ptr in new ComponentEnumerable<IWICBitmapCodecInfo>(
this.wicFactory,
WICComponentType.WICDecoder))
yield return new(ptr);
}
/// <summary>Saves the given raw bitmap to a stream.</summary>
/// <param name="specs">The raw bitmap specifications.</param>
/// <param name="bytes">The raw bitmap bytes.</param>
/// <param name="containerFormat">The container format from <see cref="GetSupportedEncoderInfos"/>.</param>
/// <param name="stream">The stream to write to. The ownership is not transferred.</param>
/// <param name="props">The encoder properties.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public unsafe void SaveToStreamUsingWic(
RawImageSpecification specs,
ReadOnlySpan<byte> bytes,
Guid containerFormat,
ComPtr<IStream> stream,
IReadOnlyDictionary<string, object>? props = null,
CancellationToken cancellationToken = default)
{
if (!GetCorrespondingWicPixelFormat((DXGI_FORMAT)specs.DxgiFormat, out var inPixelFormat, out var srgb))
throw new NotSupportedException("DXGI_FORMAT from specs is not supported by WIC.");
using var encoder = default(ComPtr<IWICBitmapEncoder>);
using var encoderFrame = default(ComPtr<IWICBitmapFrameEncode>);
this.wicFactory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
// See: DirectXTK/Src/ScreenGrab.cpp
var outPixelFormat = (DXGI_FORMAT)specs.DxgiFormat switch
{
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT when !this.wicFactory2.IsEmpty() =>
GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat32bppBGRA,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppBGRA,
DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => GUID.GUID_WICPixelFormat16bppBGRA5551,
DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM => GUID.GUID_WICPixelFormat16bppBGR565,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_R16_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
_ => GUID.GUID_WICPixelFormat24bppBGR,
};
encoder.Get()->Initialize(stream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
.ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
using var propertyBag = default(ComPtr<IPropertyBag2>);
encoder.Get()->CreateNewFrame(encoderFrame.GetAddressOf(), propertyBag.GetAddressOf()).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
// Opt-in to the WIC2 support for writing 32-bit Windows BMP files with an alpha channel
if (containerFormat == GUID.GUID_ContainerFormatBmp && !this.wicFactory2.IsEmpty())
propertyBag.Get()->Write("EnableV5Header32bppBGRA", true).ThrowOnError();
if (props is not null)
{
foreach (var (name, untypedValue) in props)
propertyBag.Get()->Write(name, untypedValue).ThrowOnError();
}
encoderFrame.Get()->Initialize(propertyBag).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
encoderFrame.Get()->SetPixelFormat(&outPixelFormat).ThrowOnError();
encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
using (var metaWriter = default(ComPtr<IWICMetadataQueryWriter>))
{
if (encoderFrame.Get()->GetMetadataQueryWriter(metaWriter.GetAddressOf()).SUCCEEDED)
{
if (containerFormat == GUID.GUID_ContainerFormatPng)
{
// Set sRGB chunk
if (srgb)
{
_ = metaWriter.Get()->SetMetadataByName("/sRGB/RenderingIntent", (byte)0);
}
else
{
// add gAMA chunk with gamma 1.0
// gama value * 100,000 -- i.e. gamma 1.0
_ = metaWriter.Get()->SetMetadataByName("/sRGB/RenderingIntent", 100000U);
// remove sRGB chunk which is added by default.
_ = metaWriter.Get()->RemoveMetadataByName("/sRGB/RenderingIntent");
}
}
else
{
// Set EXIF Colorspace of sRGB
_ = metaWriter.Get()->SetMetadataByName("System.Image.ColorSpace", (ushort)0);
}
}
}
using var tempBitmap = default(ComPtr<IWICBitmap>);
fixed (byte* pBytes = bytes)
{
this.wicFactory.Get()->CreateBitmapFromMemory(
(uint)specs.Width,
(uint)specs.Height,
&inPixelFormat,
(uint)specs.Pitch,
checked((uint)bytes.Length),
pBytes,
tempBitmap.GetAddressOf()).ThrowOnError();
}
using var outBitmapSource = default(ComPtr<IWICBitmapSource>);
if (inPixelFormat != outPixelFormat)
{
WICConvertBitmapSource(
&outPixelFormat,
(IWICBitmapSource*)tempBitmap.Get(),
outBitmapSource.GetAddressOf()).ThrowOnError();
}
else
{
tempBitmap.As(&outBitmapSource);
}
encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
encoderFrame.Get()->WriteSource(outBitmapSource.Get(), null).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
encoderFrame.Get()->Commit().ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
encoder.Get()->Commit().ThrowOnError();
}
private void ReleaseUnmanagedResource()
{
this.wicFactory.Reset();
this.wicFactory2.Reset();
}
private readonly struct ComponentEnumerable<T> : IEnumerable<ComPtr<T>>
where T : unmanaged, IWICComponentInfo.Interface
{
private readonly ComPtr<IWICImagingFactory> factory;
private readonly WICComponentType componentType;
/// <summary>Initializes a new instance of the <see cref="ComponentEnumerable{T}"/> struct.</summary>
/// <param name="factory">The WIC factory. Ownership is not transferred.
/// </param>
/// <param name="componentType">The component type to enumerate.</param>
public ComponentEnumerable(ComPtr<IWICImagingFactory> factory, WICComponentType componentType)
{
this.factory = factory;
this.componentType = componentType;
}
public unsafe ManagedIEnumUnknownEnumerator<T> GetEnumerator()
{
var enumUnknown = default(ComPtr<IEnumUnknown>);
this.factory.Get()->CreateComponentEnumerator(
(uint)this.componentType,
(uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault,
enumUnknown.GetAddressOf()).ThrowOnError();
return new(enumUnknown);
}
IEnumerator<ComPtr<T>> IEnumerable<ComPtr<T>>.GetEnumerator() => this.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}
}