diff --git a/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs b/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs index 38c1ac4..0a22267 100644 --- a/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs +++ b/Meddle/Meddle.Plugin/Models/Composer/CharacterComposer.cs @@ -18,12 +18,65 @@ public class CharacterComposer { private readonly DataProvider dataProvider; private readonly Action? progress; - private static TexFile? CubeMapTex; - private static PbdFile? PbdFile; private static readonly object StaticFileLock = new(); + private static PbdFile? DefaultPbdFile; private readonly SkeletonUtils.PoseMode poseMode; private readonly bool includePose; private readonly TextureMode textureMode; + private bool arrayTexturesSaved; + + private void SaveArrayTextures() + { + if (arrayTexturesSaved) return; + arrayTexturesSaved = true; + lock (StaticFileLock) + { + try + { + var outDir = Path.Combine(this.dataProvider.GetCacheDir(), "array_textures"); + + var catchlight = this.dataProvider.LookupData("chara/common/texture/sphere_d_array.tex"); + if (catchlight == null) throw new Exception("Failed to load catchlight texture"); + var catchLightTex = new TexFile(catchlight); + var catchlightOutDir = Path.Combine(outDir, "chara/common/texture/sphere_d_array"); + Directory.CreateDirectory(catchlightOutDir); + for (int i = 0; i < catchLightTex.Header.CalculatedArraySize; i++) + { + var img = ImageUtils.GetTexData(catchLightTex, i, 0, 0); + var texture = img.ImageAsPng(); + File.WriteAllBytes(Path.Combine(catchlightOutDir, $"sphere_d_array.{i}.png"), texture.ToArray()); + } + + var tileNorm = this.dataProvider.LookupData("chara/common/texture/tile_norm_array.tex"); + if (tileNorm == null) throw new Exception("Failed to load tile norm texture"); + var tileNormTex = new TexFile(tileNorm); + var tileNormOutDir = Path.Combine(outDir, "chara/common/texture/tile_norm_array"); + Directory.CreateDirectory(tileNormOutDir); + for (int i = 0; i < tileNormTex.Header.CalculatedArraySize; i++) + { + var img = ImageUtils.GetTexData(tileNormTex, i, 0, 0); + var texture = img.ImageAsPng(); + File.WriteAllBytes(Path.Combine(tileNormOutDir, $"tile_norm_array.{i}.png"), texture.ToArray()); + } + + var tileOrb = this.dataProvider.LookupData("chara/common/texture/tile_orb_array.tex"); + if (tileOrb == null) throw new Exception("Failed to load tile orb texture"); + var tileOrbTex = new TexFile(tileOrb); + var tileOrbOutDir = Path.Combine(outDir, "chara/common/texture/tile_orb_array"); + Directory.CreateDirectory(tileOrbOutDir); + for (int i = 0; i < tileOrbTex.Header.CalculatedArraySize; i++) + { + var img = ImageUtils.GetTexData(tileOrbTex, i, 0, 0); + var texture = img.ImageAsPng(); + File.WriteAllBytes(Path.Combine(tileOrbOutDir, $"tile_orb_array.{i}.png"), texture.ToArray()); + } + } + catch (Exception e) + { + Plugin.Logger?.LogError(e, "Failed to save array textures"); + } + } + } public CharacterComposer(DataProvider dataProvider, Configuration config, Action? progress = null) { @@ -32,21 +85,14 @@ public CharacterComposer(DataProvider dataProvider, Configuration config, Action includePose = config.IncludePose; poseMode = config.PoseMode; textureMode = config.TextureMode; - + lock (StaticFileLock) { - if (CubeMapTex == null) + if (DefaultPbdFile == null) { - var catchlight = this.dataProvider.LookupData("chara/common/texture/sphere_d_array.tex"); - if (catchlight == null) throw new Exception("Failed to load catchlight texture"); - CubeMapTex = new TexFile(catchlight); - } - - if (PbdFile == null) - { - var pbdData = this.dataProvider.LookupData("chara/xls/boneDeformer/human.pbd"); - if (pbdData == null) throw new Exception("Failed to load human.pbd"); - PbdFile = new PbdFile(pbdData); + var pbdData = dataProvider.LookupData("chara/xls/boneDeformer/human.pbd"); + if (pbdData == null) throw new InvalidOperationException("Failed to load default pbd file"); + DefaultPbdFile = new PbdFile(pbdData); } } } @@ -56,6 +102,8 @@ private void HandleModel(GenderRace genderRace, CustomizeParameter customizePara BoneNodeBuilder? rootBone, Matrix4x4 transform) { + SaveArrayTextures(); + if (modelInfo.Path.GamePath.Contains("b0003_top")) { Plugin.Logger?.LogDebug("Skipping model {ModelPath}", modelInfo.Path.GamePath); @@ -140,7 +188,7 @@ private void HandleModel(GenderRace genderRace, CustomizeParameter customizePara var parsed = RaceDeformer.ParseRaceCode(modelInfo.Path.GamePath); if (Enum.IsDefined(parsed)) { - deform = (parsed, genderRace, new RaceDeformer(PbdFile!, bones)); + deform = (parsed, genderRace, new RaceDeformer(DefaultPbdFile!, bones)); } else { diff --git a/Meddle/Meddle.Plugin/Models/Composer/DataProvider.cs b/Meddle/Meddle.Plugin/Models/Composer/DataProvider.cs index 21bc3a9..34ed774 100644 --- a/Meddle/Meddle.Plugin/Models/Composer/DataProvider.cs +++ b/Meddle/Meddle.Plugin/Models/Composer/DataProvider.cs @@ -21,6 +21,7 @@ public class DataProvider private readonly ConcurrentDictionary> mtrlCache = new(); private readonly ConcurrentDictionary> mtrlFileCache = new(); private readonly ConcurrentDictionary> shpkFileCache = new(); + public string GetCacheDir() => cacheDir; public DataProvider(string cacheDir, SqPack dataManager, ILogger logger, CancellationToken cancellationToken) { diff --git a/Meddle/Meddle.Plugin/Models/Composer/InstanceComposer.cs b/Meddle/Meddle.Plugin/Models/Composer/InstanceComposer.cs index a6d3d88..1dbc459 100644 --- a/Meddle/Meddle.Plugin/Models/Composer/InstanceComposer.cs +++ b/Meddle/Meddle.Plugin/Models/Composer/InstanceComposer.cs @@ -24,6 +24,63 @@ public class InstanceComposer : IDisposable private readonly Action? progress; private readonly DataProvider dataProvider; private int countProgress; + private bool arrayTexturesSaved; + private static readonly object StaticFileLock = new(); + private void SaveArrayTextures() + { + if (arrayTexturesSaved) return; + arrayTexturesSaved = true; + lock (StaticFileLock) + { + try + { + var outDir= Path.Combine(this.dataProvider.GetCacheDir(), "array_textures"); + + var catchlight = this.dataProvider.LookupData("bgcommon/texture/sphere_d_array.tex"); + if (catchlight == null) throw new Exception("Failed to load catchlight texture"); + var catchLightTex = new TexFile(catchlight); + var catchlightOutDir = Path.Combine(outDir, "bgcommon/texture/sphere_d_array"); + Directory.CreateDirectory(catchlightOutDir); + + for (int i = 0; i < catchLightTex.Header.CalculatedArraySize; i++) + { + var img = ImageUtils.GetTexData(catchLightTex, i, 0, 0); + var texture = img.ImageAsPng(); + File.WriteAllBytes(Path.Combine(catchlightOutDir, $"sphere_d_array.{i}.png"), texture.ToArray()); + } + + var detailD = this.dataProvider.LookupData("bgcommon/nature/detail/texture/detail_d_array.tex"); + if (detailD == null) throw new Exception("Failed to load detail diffuse texture"); + var detailDTex = new TexFile(detailD); + var detailDOutDir = Path.Combine(outDir, "bgcommon/nature/detail/texture/detail_d_array"); + Directory.CreateDirectory(detailDOutDir); + + for (int i = 0; i < detailDTex.Header.CalculatedArraySize; i++) + { + var img = ImageUtils.GetTexData(detailDTex, i, 0, 0); + var texture = img.ImageAsPng(); + File.WriteAllBytes(Path.Combine(detailDOutDir, $"detail_d_array.{i}.png"), texture.ToArray()); + } + + var detailN = this.dataProvider.LookupData("bgcommon/nature/detail/texture/detail_n_array.tex"); + if (detailN == null) throw new Exception("Failed to load tile orb texture"); + var detailNTex = new TexFile(detailN); + var detailNOutDir = Path.Combine(outDir, "bgcommon/nature/detail/texture/detail_n_array"); + Directory.CreateDirectory(detailNOutDir); + + for (int i = 0; i < detailNTex.Header.CalculatedArraySize; i++) + { + var img = ImageUtils.GetTexData(detailNTex, i, 0, 0); + var texture = img.ImageAsPng(); + File.WriteAllBytes(Path.Combine(detailNOutDir, $"detail_n_array.{i}.png"), texture.ToArray()); + } + } + catch (Exception e) + { + Plugin.Logger?.LogError(e, "Failed to save array textures"); + } + } + } public InstanceComposer( Configuration config, @@ -93,6 +150,7 @@ public void Compose(SceneBuilder scene) public NodeBuilder? ComposeInstance(SceneBuilder scene, ParsedInstance parsedInstance) { if (cancellationToken.IsCancellationRequested) return null; + SaveArrayTextures(); var root = new NodeBuilder(); var transform = parsedInstance.Transform; if (parsedInstance is IPathInstance pathInstance) diff --git a/Meddle/Meddle.Plugin/Utils/UIUtil.cs b/Meddle/Meddle.Plugin/Utils/UIUtil.cs index f2d6691..ab60762 100644 --- a/Meddle/Meddle.Plugin/Utils/UIUtil.cs +++ b/Meddle/Meddle.Plugin/Utils/UIUtil.cs @@ -84,17 +84,27 @@ public static void DrawColorTable(ColorTable table, ColorDyeTable? dyeTable = nu public static void DrawColorTable(ReadOnlySpan tableRows, ColorDyeTable? dyeTable = null) { - if (ImGui.BeginTable("ColorTable", 9, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable)) + if (ImGui.BeginTable("ColorTable", 16, ImGuiTableFlags.Borders | + ImGuiTableFlags.Resizable | + ImGuiTableFlags.SizingFixedFit | + ImGuiTableFlags.Hideable)) { ImGui.TableSetupColumn("Row", ImGuiTableColumnFlags.WidthFixed, 50); ImGui.TableSetupColumn("Diffuse", ImGuiTableColumnFlags.WidthFixed, 100); ImGui.TableSetupColumn("Specular", ImGuiTableColumnFlags.WidthFixed, 100); ImGui.TableSetupColumn("Emissive", ImGuiTableColumnFlags.WidthFixed, 100); - ImGui.TableSetupColumn("Material Repeat", ImGuiTableColumnFlags.WidthFixed, 100); - ImGui.TableSetupColumn("Material Skew", ImGuiTableColumnFlags.WidthFixed, 100); - ImGui.TableSetupColumn("Specular Strength", ImGuiTableColumnFlags.WidthFixed, 100); - ImGui.TableSetupColumn("Gloss", ImGuiTableColumnFlags.WidthFixed, 100); - ImGui.TableSetupColumn("Tile Set", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Sheen Rate", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Sheen Tint", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Sheen Apt.", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Roughness", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Metalness", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Anisotropy", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Tile Matrix", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Sphere Mask", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Sphere Idx", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Shader Idx", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Tile Index", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Tile Alpha", ImGuiTableColumnFlags.WidthFixed, 50); ImGui.TableHeadersRow(); for (var i = 0; i < tableRows.Length; i++) @@ -141,29 +151,40 @@ private static void DrawRow(int i, ColorTableRow row, ColorDyeTable? dyeTable) } ImGui.TableSetColumnIndex(4); - ImGui.Text($"{row.MaterialRepeat}"); + ImGui.Text($"{row.SheenRate}"); + ImGui.TableSetColumnIndex(5); - ImGui.Text($"{row.MaterialSkew}"); + ImGui.Text($"{row.SheenTint}"); + ImGui.TableSetColumnIndex(6); - if (dyeTable != null) - { - var specStrength = dyeTable.Value.Rows[i].SpecularStrength; - ImGui.Checkbox("##rowspecstrcheck", ref specStrength); - ImGui.SameLine(); - } - - ImGui.Text($"{row.SpecularStrength}"); + ImGui.Text($"{row.SheenAptitude}"); + ImGui.TableSetColumnIndex(7); - if (dyeTable != null) - { - var gloss = dyeTable.Value.Rows[i].Gloss; - ImGui.Checkbox("##rowglosscheck", ref gloss); - ImGui.SameLine(); - } - - ImGui.Text($"{row.GlossStrength}"); + ImGui.Text($"{row.Roughness}"); + ImGui.TableSetColumnIndex(8); + ImGui.Text($"{row.Metalness}"); + + ImGui.TableSetColumnIndex(9); + ImGui.Text($"{row.Anisotropy}"); + + ImGui.TableSetColumnIndex(10); + ImGui.Text($"UU: {row.TileMatrix.UU}, UV: {row.TileMatrix.UV}, VU: {row.TileMatrix.VU}, VV: {row.TileMatrix.VV}"); + + ImGui.TableSetColumnIndex(11); + ImGui.Text($"{row.SphereMask}"); + + ImGui.TableSetColumnIndex(12); + ImGui.Text($"{row.SphereIndex}"); + + ImGui.TableSetColumnIndex(13); + ImGui.Text($"{row.ShaderId}"); + + ImGui.TableSetColumnIndex(14); ImGui.Text($"{row.TileIndex}"); + + ImGui.TableSetColumnIndex(15); + ImGui.Text($"{row.TileAlpha}"); } public static unsafe void DrawCharacterAttaches(Pointer characterPointer) diff --git a/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableRow.cs b/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableRow.cs index 7769112..d5323d6 100644 --- a/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableRow.cs +++ b/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableRow.cs @@ -8,14 +8,14 @@ namespace Meddle.Utils.Files.Structs.Material; /// /// # | X (+0) | | Y (+1) | | Z (+2) | | W (+3) | /// -------------------------------------------------------------------------------------- -/// 0 (+ 0) | Diffuse.R | 0 | Diffuse.G | 0 | Diffuse.B | 0 | | +/// 0 (+ 0) | Diffuse.R | 0 | Diffuse.G | 0 | Diffuse.B | 0 | Unk | /// 1 (+ 4) | Specular.R | 1 | Specular.G | 1 | Specular.B | 1 | Unk | /// 2 (+ 8) | Emissive.R | 2 | Emissive.G | 2 | Emissive.B | 2 | Unk | 3 -/// 3 (+12) | Unk | 6 | Unk | 7 | Unk | 8 | | -/// 4 (+16) | Unk | 5 | | | Unk | 4 | Unk | 9 -/// 5 (+20) | | | Unk | 11 | | | | -/// 6 (+24) | Shader Idx | | Tile Index | | Unk | | Unk | 10 -/// 7 (+28) | Tile Count.X | | Tile Count.Y | | Tile Skew.X | | Tile Skew.Y | +/// 3 (+12) | Sheen Rate | 6 | Sheen Tint | 7 | Sheen Apt. | 8 | Unk | +/// 4 (+16) | Rougnhess? | 5 | | | Metalness? | 4 | Anisotropy | 9 +/// 5 (+20) | Unk | | Sphere Mask | 11 | Unk | | Unk | +/// 6 (+24) | Shader Idx | | Tile Index | | Tile Alpha | | Sphere Idx | 10 +/// 7 (+28) | Tile XF UU | | Tile XF UV | | Tile XF VU | | Tile XF VV | /// /// [StructLayout(LayoutKind.Explicit, Size = 0x40)] @@ -33,8 +33,8 @@ public struct ShortVec4 [FieldOffset(0x0)] public ShortVec4 _diffuse; [FieldOffset(0x8)] public ShortVec4 _specular; [FieldOffset(0x10)] public ShortVec4 _emissive; - [FieldOffset(0x18)] public ShortVec4 _unk0; - [FieldOffset(0x20)] public ShortVec4 _unk1; + [FieldOffset(0x18)] public ShortVec4 _sheen; + [FieldOffset(0x20)] public ShortVec4 _r_u_m_a; [FieldOffset(0x28)] public ShortVec4 _unk2; [FieldOffset(0x30)] public ShortVec4 _idxData; [FieldOffset(0x38)] public ShortVec4 _tile; @@ -78,48 +78,109 @@ public Vector3 Emissive } } - public float SpecularStrength + public float SheenRate { - readonly get => ToFloat(_specular.W); - set => _specular.W = FromFloat(value); + readonly get => ToFloat(_sheen.X); + set => _sheen.X = FromFloat(value); } - public float GlossStrength + public float SheenTint { - readonly get => ToFloat(_emissive.W); - set => _emissive.W = FromFloat(value); + readonly get => ToFloat(_sheen.Y); + set => _sheen.Y = FromFloat(value); } - public Vector2 MaterialRepeat + public float SheenAptitude { - readonly get => new Vector2(ToFloat(_tile.X), ToFloat(_tile.Y)); - set - { - _tile.X = FromFloat(value.X); - _tile.Y = FromFloat(value.Y); - } + readonly get => ToFloat(_sheen.Z); + set => _sheen.Z = FromFloat(value); } - public Vector2 MaterialSkew + public float Roughness { - readonly get => new Vector2(ToFloat(_tile.Z), ToFloat(_tile.W)); + readonly get => ToFloat(_r_u_m_a.X); + set => _r_u_m_a.X = FromFloat(value); + } + + public float Metalness + { + readonly get => ToFloat(_r_u_m_a.Z); + set => _r_u_m_a.W = FromFloat(value); + } + + public float Anisotropy + { + readonly get => ToFloat(_r_u_m_a.W); + set => _r_u_m_a.W = FromFloat(value); + } + + // public float SpecularStrength + // { + // readonly get => ToFloat(_specular.W); + // set => _specular.W = FromFloat(value); + // } + // + // public float GlossStrength + // { + // readonly get => ToFloat(_emissive.W); + // set => _emissive.W = FromFloat(value); + // } + + public struct Matrix2x2 + { + public float UU; + public float UV; + public float VU; + public float VV; + } + + public Matrix2x2 TileMatrix + { + readonly get => new() + { + UU = ToFloat(_tile.X), + UV = ToFloat(_tile.Y), + VU = ToFloat(_tile.Z), + VV = ToFloat(_tile.W) + }; set { - _tile.Z = FromFloat(value.X); - _tile.W = FromFloat(value.Y); + _tile.X = FromFloat(value.UU); + _tile.Y = FromFloat(value.UV); + _tile.Z = FromFloat(value.VU); + _tile.W = FromFloat(value.VV); } } + public float SphereMask + { + readonly get => ToFloat(_unk2.Y); + set => _unk2.Y = FromFloat(value); + } + public ushort ShaderId { readonly get => _idxData.X; set => _idxData.X = value; } - public ushort TileIndex + public byte TileIndex { - readonly get => _idxData.Y; - set => _idxData.Y = value; + readonly get => (byte)(ToFloat(_idxData.Y) * 64f); + set => _idxData.Y = FromFloat((value + 0.5f) / 64f); + } + + + public float TileAlpha + { + readonly get => ToFloat(_idxData.Z); + set => _idxData.Z = FromFloat(value); + } + + public ushort SphereIndex + { + readonly get => _idxData.W; + set => _idxData.W = value; } } @@ -226,21 +287,4 @@ public ushort TileIndex readonly get => _emissive.Y; set => _emissive.Y = value; } - - public ColorTableRow ToNew() - { - var newRow = new ColorTableRow - { - Diffuse = Diffuse, - Specular = Specular, - Emissive = Emissive, - SpecularStrength = SpecularStrength, - GlossStrength = GlossStrength, - MaterialRepeat = MaterialRepeat, - MaterialSkew = MaterialSkew, - ShaderId = 0, - TileIndex = TileIndex - }; - return newRow; - } } diff --git a/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableSet.cs b/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableSet.cs index f158555..9853955 100644 --- a/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableSet.cs +++ b/Meddle/Meddle.Utils/Files/Structs/Material/ColorTableSet.cs @@ -199,15 +199,21 @@ public object ToObject() { Rows = Rows.ToArray().Select(r => new { - Diffuse = r.Diffuse, - Specular = r.Specular, - SpecularStrength = r.SpecularStrength, - Emissive = r.Emissive, - GlossStrength = r.GlossStrength, - MaterialRepeat = r.MaterialRepeat, - MaterialSkew = r.MaterialSkew, - TileIndex = r.TileIndex, - ShaderId = r.ShaderId + r.Diffuse, + r.Specular, + r.Emissive, + r.SheenRate, + r.SheenTint, + r.SheenAptitude, + r.Roughness, + r.Metalness, + r.Anisotropy, + r.SphereMask, + r.ShaderId, + r.TileIndex, + r.TileAlpha, + r.SphereIndex, + r.TileMatrix, }).ToArray() }; } @@ -223,10 +229,14 @@ public ColorTableRow GetBlendedPair(int weight, int blend) Diffuse = Vector3.Clamp(Vector3.Lerp(row1.Diffuse, row0.Diffuse, blendAmount), Vector3.Zero, Vector3.One), Specular = Vector3.Clamp(Vector3.Lerp(row1.Specular, row0.Specular, blendAmount), Vector3.Zero, Vector3.One), Emissive = Vector3.Clamp(Vector3.Lerp(row1.Emissive, row0.Emissive, blendAmount), Vector3.Zero, Vector3.One), - SpecularStrength = float.Lerp(row1.SpecularStrength, row0.SpecularStrength, blendAmount), - GlossStrength = float.Lerp(row1.GlossStrength, row0.GlossStrength, blendAmount), - MaterialRepeat = prioRow.MaterialRepeat, - MaterialSkew = prioRow.MaterialSkew, + SheenRate = float.Lerp(row1.SheenRate, row0.SheenRate, blendAmount), + SheenTint = float.Lerp(row1.SheenTint, row0.SheenTint, blendAmount), + SheenAptitude = float.Lerp(row1.SheenAptitude, row0.SheenAptitude, blendAmount), + Roughness = float.Lerp(row1.Roughness, row0.Roughness, blendAmount), + Metalness = float.Lerp(row1.Metalness, row0.Metalness, blendAmount), + Anisotropy = float.Lerp(row1.Anisotropy, row0.Anisotropy, blendAmount), + SphereMask = float.Lerp(row1.SphereMask, row0.SphereMask, blendAmount), + TileIndex = prioRow.TileIndex }; diff --git a/Meddle/Meddle.Utils/Helpers/ImageUtils.cs b/Meddle/Meddle.Utils/Helpers/ImageUtils.cs index acef75e..944a408 100644 --- a/Meddle/Meddle.Utils/Helpers/ImageUtils.cs +++ b/Meddle/Meddle.Utils/Helpers/ImageUtils.cs @@ -39,7 +39,7 @@ public static TextureResource ToResource(this TexFile file) file.TextureBuffer); } - public static ReadOnlySpan ImageAsPng(Image image) + public static ReadOnlySpan ImageAsPng(this Image image) { unsafe {