diff --git a/LooseTextureCompilerCore b/LooseTextureCompilerCore index b3f03b400..448865e97 160000 --- a/LooseTextureCompilerCore +++ b/LooseTextureCompilerCore @@ -1 +1 @@ -Subproject commit b3f03b400080859b5036c285c927d82c296a18d3 +Subproject commit 448865e97206c6b1d7070559a80ff42cdc166e0c diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index f17fdaa27..82e13eb9c 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -53,7 +53,7 @@ private static MaterialBuilder BuildCharacter(Material material, string name) var normal = material.Textures[TextureUsage.SamplerNormal]; var operation = new ProcessCharacterNormalOperation(normal, table); - ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, normal.Bounds(), in operation); + ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, normal.Bounds, in operation); // Check if full textures are provided, and merge in if available. var baseColor = operation.BaseColor; @@ -199,7 +199,7 @@ public static void Execute(Image target, Image context.Resize(large.Width, large.Height)); var operation = new MultiplyOperation(target, multiplier); - ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, target.Bounds(), in operation); + ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, target.Bounds, in operation); } } diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index e81bae01e..874a9f387 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -50,10 +50,6 @@ $(DalamudLibPath)Lumina.dll False - - $(DalamudLibPath)Lumina.Excel.dll - False - $(DalamudLibPath)FFXIVClientStructs.dll False @@ -69,8 +65,9 @@ + - + diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 32be8caea..6b27b5e5f 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -1,367 +1,367 @@ -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using OtterTex; -using Penumbra.Import.Textures; -using Penumbra.Mods; -using Penumbra.UI.Classes; - -namespace Penumbra.UI.AdvancedWindow; - -public partial class ModEditWindow -{ - private readonly TextureManager _textures; - - private readonly Texture _left = new(); - private readonly Texture _right = new(); - private readonly CombinedTexture _center; - private readonly TextureDrawer.PathSelectCombo _textureSelectCombo; - - private bool _overlayCollapsed = true; - private bool _addMipMaps = true; - private int _currentSaveAs; - - private static readonly (string, string)[] SaveAsStrings = - { - ("As Is", "Save the current texture with its own format without additional conversion or compression, if possible."), - ("RGBA (Uncompressed)", - "Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality."), - ("BC3 (Simple Compression)", - "Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality."), - ("BC7 (Complex Compression)", - "Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while."), - }; - - private void DrawInputChild(string label, Texture tex, Vector2 size, Vector2 imageSize) - { - using (var child = ImRaii.Child(label, size, true)) - { - if (!child) - return; - - using var id = ImRaii.PushId(label); - ImGuiUtil.DrawTextButton(label, new Vector2(-1, 0), ImGui.GetColorU32(ImGuiCol.FrameBg)); - ImGui.NewLine(); - - using (var disabled = ImRaii.Disabled(!_center.SaveTask.IsCompleted)) - { - TextureDrawer.PathInputBox(_textures, tex, ref tex.TmpPath, "##input", "Import Image...", - "Can import game paths as well as your own files.", Mod!.ModPath.FullName, _fileDialog, _config.DefaultModImportPath); - if (_textureSelectCombo.Draw("##combo", - "Select the textures included in this mod on your drive or the ones they replace from the game files.", tex.Path, - Mod.ModPath.FullName.Length + 1, out var newPath) - && newPath != tex.Path) - tex.Load(_textures, newPath); - - if (tex == _left) - _center.DrawMatrixInputLeft(size.X); - else - _center.DrawMatrixInputRight(size.X); - } - - ImGui.NewLine(); - using var child2 = ImRaii.Child("image"); - if (child2) - TextureDrawer.Draw(tex, imageSize); - } - - if (_dragDropManager.CreateImGuiTarget("TextureDragDrop", out var files, out _) && GetFirstTexture(files, out var file)) - tex.Load(_textures, file); - } - - private void SaveAsCombo() - { - var (text, desc) = SaveAsStrings[_currentSaveAs]; - ImGui.SetNextItemWidth(-ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X); - using var combo = ImRaii.Combo("##format", text); - ImGuiUtil.HoverTooltip(desc); - if (!combo) - return; - - foreach (var ((newText, newDesc), idx) in SaveAsStrings.WithIndex()) - { - if (ImGui.Selectable(newText, idx == _currentSaveAs)) - _currentSaveAs = idx; - - ImGuiUtil.SelectableHelpMarker(newDesc); - } +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterTex; +using Penumbra.Import.Textures; +using Penumbra.Mods; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.AdvancedWindow; + +public partial class ModEditWindow +{ + private readonly TextureManager _textures; + + private readonly Texture _left = new(); + private readonly Texture _right = new(); + private readonly CombinedTexture _center; + private readonly TextureDrawer.PathSelectCombo _textureSelectCombo; + + private bool _overlayCollapsed = true; + private bool _addMipMaps = true; + private int _currentSaveAs; + + private static readonly (string, string)[] SaveAsStrings = + { + ("As Is", "Save the current texture with its own format without additional conversion or compression, if possible."), + ("RGBA (Uncompressed)", + "Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality."), + ("BC3 (Simple Compression)", + "Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality."), + ("BC7 (Complex Compression)", + "Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while."), + }; + + private void DrawInputChild(string label, Texture tex, Vector2 size, Vector2 imageSize) + { + using (var child = ImRaii.Child(label, size, true)) + { + if (!child) + return; + + using var id = ImRaii.PushId(label); + ImGuiUtil.DrawTextButton(label, new Vector2(-1, 0), ImGui.GetColorU32(ImGuiCol.FrameBg)); + ImGui.NewLine(); + + using (var disabled = ImRaii.Disabled(!_center.SaveTask.IsCompleted)) + { + TextureDrawer.PathInputBox(_textures, tex, ref tex.TmpPath, "##input", "Import Image...", + "Can import game paths as well as your own files.", Mod!.ModPath.FullName, _fileDialog, _config.DefaultModImportPath); + if (_textureSelectCombo.Draw("##combo", + "Select the textures included in this mod on your drive or the ones they replace from the game files.", tex.Path, + Mod.ModPath.FullName.Length + 1, out var newPath) + && newPath != tex.Path) + tex.Load(_textures, newPath); + + if (tex == _left) + _center.DrawMatrixInputLeft(size.X); + else + _center.DrawMatrixInputRight(size.X); + } + + ImGui.NewLine(); + using var child2 = ImRaii.Child("image"); + if (child2) + TextureDrawer.Draw(tex, imageSize); + } + + if (_dragDropManager.CreateImGuiTarget("TextureDragDrop", out var files, out _) && GetFirstTexture(files, out var file)) + tex.Load(_textures, file); } - - private void RedrawOnSaveBox() - { - var redraw = _config.Ephemeral.ForceRedrawOnFileChange; - if (ImGui.Checkbox("Redraw on Save", ref redraw)) - { - _config.Ephemeral.ForceRedrawOnFileChange = redraw; - _config.Ephemeral.Save(); - } - - ImGuiUtil.HoverTooltip("Force a redraw of your player character whenever you save a file here."); - } - - private void MipMapInput() - { - ImGui.Checkbox("##mipMaps", ref _addMipMaps); - ImGuiUtil.HoverTooltip( - "Add the appropriate number of MipMaps to the file."); - } - - private bool _forceTextureStartPath = true; - - private void DrawOutputChild(Vector2 size, Vector2 imageSize) - { - using var child = ImRaii.Child("Output", size, true); - if (!child) - return; - - if (_center.IsLoaded) - { - RedrawOnSaveBox(); - ImGui.SameLine(); - SaveAsCombo(); - ImGui.SameLine(); - MipMapInput(); - - var canSaveInPlace = Path.IsPathRooted(_left.Path) && _left.Type is TextureType.Tex or TextureType.Dds or TextureType.Png; - var isActive = _config.DeleteModModifier.IsActive(); - var tt = isActive - ? "This saves the texture in place. This is not revertible." - : $"This saves the texture in place. This is not revertible. Hold {_config.DeleteModModifier} to save."; - - var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); - if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2, - tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) - { - _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); - InvokeChange(Mod, _left.Path); - AddReloadTask(_left.Path, false); - } - - ImGui.SameLine(); - if (ImGui.Button("Save as TEX", buttonSize2)) - OpenSaveAsDialog(".tex"); - - if (ImGui.Button("Export as PNG", buttonSize2)) - OpenSaveAsDialog(".png"); - ImGui.SameLine(); - if (ImGui.Button("Export as DDS", buttonSize2)) - OpenSaveAsDialog(".dds"); - - ImGui.NewLine(); - - var canConvertInPlace = canSaveInPlace && _left.Type is TextureType.Tex && _center.IsLeftCopy; - - var buttonSize3 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X * 2) / 3, 0); - if (ImGuiUtil.DrawDisabledButton("Convert to BC7", buttonSize3, - "This converts the texture to BC7 format in place. This is not revertible.", - !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) - { - _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); - InvokeChange(Mod, _left.Path); - AddReloadTask(_left.Path, false); - } - string eyeMapResources = _config.ModDirectory + @"\LooseTextureCompilerDLC\"; - if (Directory.Exists(eyeMapResources)) - { - if (ImGui.Button("Image To Eye Maps", -Vector2.UnitX)) - { - var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); - _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => - { - if (a) - _center.ImageToEyeMaps(b, eyeMapResources); - }, _mod!.ModPath.FullName, _forceTextureStartPath); - _forceTextureStartPath = false; - } - if (ImGui.Button("Eye Multi To Grayscale", -Vector2.UnitX)) - { - var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); - _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => - { - if (a) - _center.EyeMultiToGrayscale(b); - }, _mod!.ModPath.FullName, _forceTextureStartPath); - _forceTextureStartPath = false; - } - } - if (ImGui.Button("Seperate Glow Information From Diffuse", -Vector2.UnitX)) - { - var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); - _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => - { - if (a) - _center.AtramentumLuminisDiffuseToGlowMap(b); - }, _mod!.ModPath.FullName, _forceTextureStartPath); - _forceTextureStartPath = false; - } - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Convert to BC3", buttonSize3, - "This converts the texture to BC3 format in place. This is not revertible.", - !canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) - { - _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); - InvokeChange(Mod, _left.Path); - AddReloadTask(_left.Path, false); - } - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Convert to RGBA", buttonSize3, - "This converts the texture to RGBA format in place. This is not revertible.", - !canConvertInPlace - || _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) - { - _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); - InvokeChange(Mod, _left.Path); - AddReloadTask(_left.Path, false); - } - } - - switch (_center.SaveTask.Status) - { - case TaskStatus.WaitingForActivation: - case TaskStatus.WaitingToRun: - case TaskStatus.Running: - ImGuiUtil.DrawTextButton("Computing...", -Vector2.UnitX, Colors.PressEnterWarningBg); - - break; - case TaskStatus.Canceled: - case TaskStatus.Faulted: - { - ImGui.TextUnformatted("Could not save file:"); - using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF); - ImGuiUtil.TextWrapped(_center.SaveTask.Exception?.ToString() ?? "Unknown Error"); - break; - } - default: - ImGui.Dummy(new Vector2(1, ImGui.GetFrameHeight())); - break; - } - - ImGui.NewLine(); - - using var child2 = ImRaii.Child("image"); - if (child2) - _center.Draw(_textures, imageSize); - } - - private void InvokeChange(Mod? mod, string path) - { - if (mod == null) - return; - - if (!_editor.Files.Tex.FindFirst(r => string.Equals(r.File.FullName, path, StringComparison.OrdinalIgnoreCase), - out var registry)) - return; - - _communicator.ModFileChanged.Invoke(mod, registry); - } - - private void OpenSaveAsDialog(string defaultExtension) - { - var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path); - _fileDialog.OpenSavePicker("Save Texture as TEX, DDS or PNG...", "Textures{.png,.dds,.tex},.tex,.dds,.png", fileName, defaultExtension, - (a, b) => - { - if (a) - { - _center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); - InvokeChange(Mod, b); - if (b == _left.Path) - AddReloadTask(_left.Path, false); - else if (b == _right.Path) - AddReloadTask(_right.Path, true); - } - }, Mod!.ModPath.FullName, _forceTextureStartPath); - _forceTextureStartPath = false; - } - - private void AddReloadTask(string path, bool right) - { - _center.SaveTask.ContinueWith(t => - { - if (!t.IsCompletedSuccessfully) - return; - - var tex = right ? _right : _left; - - if (tex.Path != path) - return; - - _framework.RunOnFrameworkThread(() => tex.Reload(_textures)); - }); - } - - private Vector2 GetChildWidth() - { - var windowWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - ImGui.GetTextLineHeight(); - if (_overlayCollapsed) - { - var width = windowWidth - ImGui.GetStyle().FramePadding.X * 3; - return new Vector2(width / 2, -1); - } - - return new Vector2((windowWidth - ImGui.GetStyle().FramePadding.X * 5) / 3, -1); - } - - private void DrawTextureTab() - { - using var tab = ImRaii.TabItem("Textures"); - if (!tab) - return; - - try - { - _dragDropManager.CreateImGuiSource("TextureDragDrop", - m => m.Extensions.Any(e => ValidTextureExtensions.Contains(e.ToLowerInvariant())), m => - { - if (!GetFirstTexture(m.Files, out var file)) - return false; - - ImGui.TextUnformatted($"Dragging texture for editing: {Path.GetFileName(file)}"); - return true; - }); - var childWidth = GetChildWidth(); - var imageSize = new Vector2(childWidth.X - ImGui.GetStyle().FramePadding.X * 2); - DrawInputChild("Input Texture", _left, childWidth, imageSize); - ImGui.SameLine(); - DrawOutputChild(childWidth, imageSize); - if (!_overlayCollapsed) - { - ImGui.SameLine(); - DrawInputChild("Overlay Texture", _right, childWidth, imageSize); - } - - ImGui.SameLine(); - DrawOverlayCollapseButton(); - } - catch (Exception e) - { - Penumbra.Log.Error($"Unknown Error while drawing textures:\n{e}"); - } - } - - private void DrawOverlayCollapseButton() - { - var (label, tooltip) = _overlayCollapsed - ? (">", "Show a third panel in which you can import an additional texture as an overlay for the primary texture.") - : ("<", "Hide the overlay texture panel and clear the currently loaded overlay texture, if any."); - if (ImGui.Button(label, new Vector2(ImGui.GetTextLineHeight(), ImGui.GetContentRegionAvail().Y))) - _overlayCollapsed = !_overlayCollapsed; - - ImGuiUtil.HoverTooltip(tooltip); - } - - private static bool GetFirstTexture(IEnumerable files, [NotNullWhen(true)] out string? file) - { - file = files.FirstOrDefault(f => ValidTextureExtensions.Contains(Path.GetExtension(f).ToLowerInvariant())); - return file != null; - } - - private static readonly string[] ValidTextureExtensions = - { - ".png", - ".dds", - ".tex", - }; -} + + private void SaveAsCombo() + { + var (text, desc) = SaveAsStrings[_currentSaveAs]; + ImGui.SetNextItemWidth(-ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X); + using var combo = ImRaii.Combo("##format", text); + ImGuiUtil.HoverTooltip(desc); + if (!combo) + return; + + foreach (var ((newText, newDesc), idx) in SaveAsStrings.WithIndex()) + { + if (ImGui.Selectable(newText, idx == _currentSaveAs)) + _currentSaveAs = idx; + + ImGuiUtil.SelectableHelpMarker(newDesc); + } + } + + private void RedrawOnSaveBox() + { + var redraw = _config.Ephemeral.ForceRedrawOnFileChange; + if (ImGui.Checkbox("Redraw on Save", ref redraw)) + { + _config.Ephemeral.ForceRedrawOnFileChange = redraw; + _config.Ephemeral.Save(); + } + + ImGuiUtil.HoverTooltip("Force a redraw of your player character whenever you save a file here."); + } + + private void MipMapInput() + { + ImGui.Checkbox("##mipMaps", ref _addMipMaps); + ImGuiUtil.HoverTooltip( + "Add the appropriate number of MipMaps to the file."); + } + + private bool _forceTextureStartPath = true; + + private void DrawOutputChild(Vector2 size, Vector2 imageSize) + { + using var child = ImRaii.Child("Output", size, true); + if (!child) + return; + + if (_center.IsLoaded) + { + RedrawOnSaveBox(); + ImGui.SameLine(); + SaveAsCombo(); + ImGui.SameLine(); + MipMapInput(); + + var canSaveInPlace = Path.IsPathRooted(_left.Path) && _left.Type is TextureType.Tex or TextureType.Dds or TextureType.Png; + var isActive = _config.DeleteModModifier.IsActive(); + var tt = isActive + ? "This saves the texture in place. This is not revertible." + : $"This saves the texture in place. This is not revertible. Hold {_config.DeleteModModifier} to save."; + + var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2, + tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) + { + _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); + InvokeChange(Mod, _left.Path); + AddReloadTask(_left.Path, false); + } + + ImGui.SameLine(); + if (ImGui.Button("Save as TEX", buttonSize2)) + OpenSaveAsDialog(".tex"); + + if (ImGui.Button("Export as PNG", buttonSize2)) + OpenSaveAsDialog(".png"); + ImGui.SameLine(); + if (ImGui.Button("Export as DDS", buttonSize2)) + OpenSaveAsDialog(".dds"); + + ImGui.NewLine(); + + var canConvertInPlace = canSaveInPlace && _left.Type is TextureType.Tex && _center.IsLeftCopy; + + var buttonSize3 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X * 2) / 3, 0); + if (ImGuiUtil.DrawDisabledButton("Convert to BC7", buttonSize3, + "This converts the texture to BC7 format in place. This is not revertible.", + !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) + { + _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); + InvokeChange(Mod, _left.Path); + AddReloadTask(_left.Path, false); + } + string eyeMapResources = _config.ModDirectory + @"\LooseTextureCompilerDLC\"; + if (Directory.Exists(eyeMapResources)) + { + if (ImGui.Button("Image To Eye Maps", -Vector2.UnitX)) + { + var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); + _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => + { + if (a) + _center.ImageToEyeMaps(b, eyeMapResources); + }, Mod!.ModPath.FullName, _forceTextureStartPath); + _forceTextureStartPath = false; + } + if (ImGui.Button("Eye Multi To Grayscale", -Vector2.UnitX)) + { + var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); + _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => + { + if (a) + _center.EyeMultiToGrayscale(b); + }, Mod!.ModPath.FullName, _forceTextureStartPath); + _forceTextureStartPath = false; + } + } + if (ImGui.Button("Seperate Glow Information From Diffuse", -Vector2.UnitX)) + { + var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); + _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => + { + if (a) + _center.AtramentumLuminisDiffuseToGlowMap(b); + }, Mod!.ModPath.FullName, _forceTextureStartPath); + _forceTextureStartPath = false; + } + + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton("Convert to BC3", buttonSize3, + "This converts the texture to BC3 format in place. This is not revertible.", + !canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) + { + _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); + InvokeChange(Mod, _left.Path); + AddReloadTask(_left.Path, false); + } + + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton("Convert to RGBA", buttonSize3, + "This converts the texture to RGBA format in place. This is not revertible.", + !canConvertInPlace + || _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) + { + _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); + InvokeChange(Mod, _left.Path); + AddReloadTask(_left.Path, false); + } + } + + switch (_center.SaveTask.Status) + { + case TaskStatus.WaitingForActivation: + case TaskStatus.WaitingToRun: + case TaskStatus.Running: + ImGuiUtil.DrawTextButton("Computing...", -Vector2.UnitX, Colors.PressEnterWarningBg); + + break; + case TaskStatus.Canceled: + case TaskStatus.Faulted: + { + ImGui.TextUnformatted("Could not save file:"); + using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF); + ImGuiUtil.TextWrapped(_center.SaveTask.Exception?.ToString() ?? "Unknown Error"); + break; + } + default: + ImGui.Dummy(new Vector2(1, ImGui.GetFrameHeight())); + break; + } + + ImGui.NewLine(); + + using var child2 = ImRaii.Child("image"); + if (child2) + _center.Draw(_textures, imageSize); + } + + private void InvokeChange(Mod? mod, string path) + { + if (mod == null) + return; + + if (!_editor.Files.Tex.FindFirst(r => string.Equals(r.File.FullName, path, StringComparison.OrdinalIgnoreCase), + out var registry)) + return; + + _communicator.ModFileChanged.Invoke(mod, registry); + } + + private void OpenSaveAsDialog(string defaultExtension) + { + var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path); + _fileDialog.OpenSavePicker("Save Texture as TEX, DDS or PNG...", "Textures{.png,.dds,.tex},.tex,.dds,.png", fileName, defaultExtension, + (a, b) => + { + if (a) + { + _center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); + InvokeChange(Mod, b); + if (b == _left.Path) + AddReloadTask(_left.Path, false); + else if (b == _right.Path) + AddReloadTask(_right.Path, true); + } + }, Mod!.ModPath.FullName, _forceTextureStartPath); + _forceTextureStartPath = false; + } + + private void AddReloadTask(string path, bool right) + { + _center.SaveTask.ContinueWith(t => + { + if (!t.IsCompletedSuccessfully) + return; + + var tex = right ? _right : _left; + + if (tex.Path != path) + return; + + _framework.RunOnFrameworkThread(() => tex.Reload(_textures)); + }); + } + + private Vector2 GetChildWidth() + { + var windowWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - ImGui.GetTextLineHeight(); + if (_overlayCollapsed) + { + var width = windowWidth - ImGui.GetStyle().FramePadding.X * 3; + return new Vector2(width / 2, -1); + } + + return new Vector2((windowWidth - ImGui.GetStyle().FramePadding.X * 5) / 3, -1); + } + + private void DrawTextureTab() + { + using var tab = ImRaii.TabItem("Textures"); + if (!tab) + return; + + try + { + _dragDropManager.CreateImGuiSource("TextureDragDrop", + m => m.Extensions.Any(e => ValidTextureExtensions.Contains(e.ToLowerInvariant())), m => + { + if (!GetFirstTexture(m.Files, out var file)) + return false; + + ImGui.TextUnformatted($"Dragging texture for editing: {Path.GetFileName(file)}"); + return true; + }); + var childWidth = GetChildWidth(); + var imageSize = new Vector2(childWidth.X - ImGui.GetStyle().FramePadding.X * 2); + DrawInputChild("Input Texture", _left, childWidth, imageSize); + ImGui.SameLine(); + DrawOutputChild(childWidth, imageSize); + if (!_overlayCollapsed) + { + ImGui.SameLine(); + DrawInputChild("Overlay Texture", _right, childWidth, imageSize); + } + + ImGui.SameLine(); + DrawOverlayCollapseButton(); + } + catch (Exception e) + { + Penumbra.Log.Error($"Unknown Error while drawing textures:\n{e}"); + } + } + + private void DrawOverlayCollapseButton() + { + var (label, tooltip) = _overlayCollapsed + ? (">", "Show a third panel in which you can import an additional texture as an overlay for the primary texture.") + : ("<", "Hide the overlay texture panel and clear the currently loaded overlay texture, if any."); + if (ImGui.Button(label, new Vector2(ImGui.GetTextLineHeight(), ImGui.GetContentRegionAvail().Y))) + _overlayCollapsed = !_overlayCollapsed; + + ImGuiUtil.HoverTooltip(tooltip); + } + + private static bool GetFirstTexture(IEnumerable files, [NotNullWhen(true)] out string? file) + { + file = files.FirstOrDefault(f => ValidTextureExtensions.Contains(Path.GetExtension(f).ToLowerInvariant())); + return file != null; + } + + private static readonly string[] ValidTextureExtensions = + { + ".png", + ".dds", + ".tex", + }; +} diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index a4abe47c8..0785c6c01 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -11,6 +11,15 @@ "Unosquare.Swan.Lite": "3.0.0" } }, + "Lumina.Excel": { + "type": "Direct", + "requested": "[6.5.2, )", + "resolved": "6.5.2", + "contentHash": "4bKX6ImW885rrgrasNve774FT3f0rjpsuoPltr0V0mxD3Ss5bFGwPQBvzgaio8sJyRNpFa25tMcwXTTKRNe+5g==", + "dependencies": { + "Lumina": "3.15.2" + } + }, "Microsoft.CodeAnalysis.Common": { "type": "Direct", "requested": "[4.8.0, )", @@ -55,14 +64,14 @@ }, "SixLabors.ImageSharp": { "type": "Direct", - "requested": "[3.1.2, )", - "resolved": "3.1.2", - "contentHash": "PYdR6GUI+gW6LBaAQKTik0Ai8oLpFAz3a/KrVusxoTg3kf7F3cuIqKMhJGsuQcmDHCF+iD81Pyn4cexyHrb1ZA==" + "requested": "[3.1.3, )", + "resolved": "3.1.3", + "contentHash": "wybtaqZQ1ZRZ4ZeU+9h+PaSeV14nyiGKIy7qRbDfSHzHq4ybqyOcjoifeaYbiKLO1u+PVxLBuy7MF/DMmwwbfg==" }, "Lumina": { "type": "Transitive", - "resolved": "3.10.2", - "contentHash": "2bm0s8yh9JsBrK93Pxaq66RkaQdQI5RGVz2c4IFJaRBH3OhueP50O9k0FA4+Z3m7Z+uHMM3Tz6b8qysK9LWvVw==" + "resolved": "3.16.0", + "contentHash": "ZKTjc/0rKj+3NlkyWIZ5HnxjERrrEpEDG22AcGvD8zO1lMkAV7x7o5kjo2nbHU/M4tMLXsFKhUvxRWDoashLHw==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", @@ -76,8 +85,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" + "resolved": "8.0.0", + "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, "Newtonsoft.Json": { "type": "Transitive", @@ -99,10 +108,10 @@ }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", + "resolved": "8.0.3", + "contentHash": "oDE9duAtHhxYJM2bsOlZCLBKdorU9DTV1tw7Mlc+VIT7HgwO5ddfOHk/An8C+fAS9oKdmn2PaIA5t1b484uz8g==", "dependencies": { - "Microsoft.Win32.SystemEvents": "7.0.0" + "Microsoft.Win32.SystemEvents": "8.0.0" } }, "System.Reflection.Metadata": { @@ -134,10 +143,10 @@ "loosetexturecompilercore": { "type": "Project", "dependencies": { - "Lumina": "[3.10.2, )", + "Lumina": "[3.16.0, )", "Newtonsoft.Json": "[13.0.3, )", - "SixLabors.ImageSharp": "[3.0.1, )", - "System.Drawing.Common": "[7.0.0, )" + "SixLabors.ImageSharp": "[3.1.3, )", + "System.Drawing.Common": "[8.0.3, )" } }, "ottergui": {