diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 77b0540f..9a5b63b6 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -94,6 +94,10 @@ namespace FModel } else if (_applicationView.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) OnSearchViewClick(null, null); + else if (e.Key == Key.Left) + _applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage(); + else if (e.Key == Key.Right) + _applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage(); else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key)) _applicationView.CUE4Parse.TabControl.AddTab(); else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key)) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 17cb7388..82b581f3 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -494,6 +494,7 @@ namespace FModel.ViewModels TabControl.SelectedTab.Directory = directory; } + TabControl.SelectedTab.ClearImages(); TabControl.SelectedTab.ResetDocumentText(); TabControl.SelectedTab.ScrollTrigger = null; TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(ext); @@ -504,9 +505,13 @@ namespace FModel.ViewModels { var exports = Provider.LoadObjectExports(fullPath); TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), bulkSave); + if (bulkSave) break; - if (bulkSave || !exports.Any(CheckExport)) - TabControl.SelectedTab.Image = null; + foreach (var e in exports) + { + if (CheckExport(e)) + break; + } break; } case "ini": @@ -529,7 +534,6 @@ namespace FModel.ViewModels case "archive": case "manifest": { - TabControl.SelectedTab.Image = null; if (Provider.TrySaveAsset(fullPath, out var data)) { using var stream = new MemoryStream(data) {Position = 0}; @@ -541,7 +545,6 @@ namespace FModel.ViewModels } case "locmeta": { - TabControl.SelectedTab.Image = null; if (Provider.TryCreateReader(fullPath, out var archive)) { var metadata = new FTextLocalizationMetaDataResource(archive); @@ -551,7 +554,6 @@ namespace FModel.ViewModels } case "locres": { - TabControl.SelectedTab.Image = null; if (Provider.TryCreateReader(fullPath, out var archive)) { var locres = new FTextLocalizationResource(archive); @@ -561,7 +563,6 @@ namespace FModel.ViewModels } case "bin" when fileName.Contains("AssetRegistry"): { - TabControl.SelectedTab.Image = null; if (Provider.TryCreateReader(fullPath, out var archive)) { var registry = new FAssetRegistryState(archive); @@ -572,7 +573,6 @@ namespace FModel.ViewModels case "bnk": case "pck": { - TabControl.SelectedTab.Image = null; if (Provider.TryCreateReader(fullPath, out var archive)) { var wwise = new WwiseReader(archive); @@ -586,7 +586,6 @@ namespace FModel.ViewModels } case "wem": { - TabControl.SelectedTab.Image = null; if (Provider.TrySaveAsset(fullPath, out var input)) SaveAndPlaySound(fullPath, "WEM", input); @@ -594,7 +593,6 @@ namespace FModel.ViewModels } case "udic": { - TabControl.SelectedTab.Image = null; if (Provider.TryCreateReader(fullPath, out var archive)) { var header = new FOodleDictionaryArchive(archive).Header; @@ -609,7 +607,7 @@ namespace FModel.ViewModels if (Provider.TrySaveAsset(fullPath, out var data)) { using var stream = new MemoryStream(data) {Position = 0}; - TabControl.SelectedTab.SetImage(SKImage.FromBitmap(SKBitmap.Decode(stream))); + TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, SKImage.FromBitmap(SKBitmap.Decode(stream))); } break; } @@ -619,7 +617,7 @@ namespace FModel.ViewModels { using var stream = new MemoryStream(data) { Position = 0 }; var svg = new SkiaSharp.Extended.Svg.SKSvg(new SKSize(512, 512)); - TabControl.SelectedTab.SetImage(SKImage.FromPicture(svg.Load(stream), new SKSizeI(512, 512))); + TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, SKImage.FromPicture(svg.Load(stream), new SKSizeI(512, 512))); } break; } @@ -632,7 +630,6 @@ namespace FModel.ViewModels case "ushaderbytecode": case "ushadercode": { - TabControl.SelectedTab.Image = null; if (Provider.TryCreateReader(fullPath, out var archive)) { var ar = new FShaderCodeArchive(archive); @@ -659,8 +656,11 @@ namespace FModel.ViewModels TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), false); - if (!exports.Any(CheckExport)) - TabControl.SelectedTab.Image = null; + foreach (var e in exports) + { + if (CheckExport(e)) + break; + } } private bool CheckExport(UObject export) // return true once you wanna stop searching for exports @@ -669,9 +669,8 @@ namespace FModel.ViewModels { case UTexture2D texture: { - TabControl.SelectedTab.RenderNearestNeighbor = texture.bRenderNearestNeighbor; - TabControl.SelectedTab.SetImage(texture.Decode()); - return true; + TabControl.SelectedTab.AddImage(texture); + return false; } case UAkMediaAssetData: case USoundWave: @@ -722,7 +721,7 @@ namespace FModel.ViewModels if (!package.TryConstructCreator(out var creator)) return false; creator.ParseForInfo(); - TabControl.SelectedTab.SetImage(creator.Draw()); + TabControl.SelectedTab.AddImage(export.Name, false, creator.Draw()); return true; } } diff --git a/FModel/ViewModels/Commands/ImageCommand.cs b/FModel/ViewModels/Commands/ImageCommand.cs index bb3c5ef8..dff136db 100644 --- a/FModel/ViewModels/Commands/ImageCommand.cs +++ b/FModel/ViewModels/Commands/ImageCommand.cs @@ -23,23 +23,23 @@ namespace FModel.ViewModels.Commands { case "Open": { - Helper.OpenWindow(contextViewModel.Header + " (Image)", () => + Helper.OpenWindow(contextViewModel.SelectedImage.ExportName + " (Image)", () => { var popout = new ImagePopout { - Title = contextViewModel.Header + " (Image)", - Width = contextViewModel.Image.Width, - Height = contextViewModel.Image.Height, - WindowState = contextViewModel.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, - ImageCtrl = {Source = contextViewModel.Image} + Title = contextViewModel.SelectedImage.ExportName + " (Image)", + Width = contextViewModel.SelectedImage.Image.Width, + Height = contextViewModel.SelectedImage.Image.Height, + WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, + ImageCtrl = {Source = contextViewModel.SelectedImage.Image} }; - RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.RenderNearestNeighbor)); + RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor)); popout.Show(); }); break; } case "Copy": - ClipboardExtensions.SetImage(contextViewModel.ImageBuffer, Path.ChangeExtension(contextViewModel.Header, ".png")); + ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png"); break; case "Save": contextViewModel.SaveImage(false); @@ -47,4 +47,4 @@ namespace FModel.ViewModels.Commands } } } -} \ No newline at end of file +} diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 77a8ac6c..21ee7b7e 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -15,9 +15,71 @@ using System.IO; using System.Linq; using System.Windows; using System.Windows.Media.Imaging; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse_Conversion.Textures; namespace FModel.ViewModels { + public class TabImage : ViewModel + { + public string ExportName { get; } + public byte[] ImageBuffer { get; set; } + + public TabImage(string name, bool rnn, SKImage img) + { + ExportName = name; + RenderNearestNeighbor = rnn; + SetImage(img); + } + + private BitmapImage _image; + public BitmapImage Image + { + get => _image; + set + { + if (_image == value) return; + SetProperty(ref _image, value); + } + } + + private bool _renderNearestNeighbor; + public bool RenderNearestNeighbor + { + get => _renderNearestNeighbor; + set => SetProperty(ref _renderNearestNeighbor, value); + } + + private bool _noAlpha; + public bool NoAlpha + { + get => _noAlpha; + set + { + SetProperty(ref _noAlpha, value); + ResetImage(); + } + } + + private void SetImage(SKImage img) + { + _img = img; + + using var data = _img.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100); + using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false); + var image = new BitmapImage(); + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = stream; + image.EndInit(); + image.Freeze(); + Image = image; + } + + private SKImage _img; + private void ResetImage() => SetImage(_img); + } + public class TabItem : ViewModel { private string _header; @@ -115,39 +177,25 @@ namespace FModel.ViewModels } } - public byte[] ImageBuffer { get; private set; } - - private BitmapImage _image; - public BitmapImage Image + private TabImage _selectedImage; + public TabImage SelectedImage { - get => _image; + get => _selectedImage; set { - if (_image == value) return; - SetProperty(ref _image, value); + if (_selectedImage == value) return; + SetProperty(ref _selectedImage, value); RaisePropertyChanged("HasImage"); + RaisePropertyChanged("Page"); } } - private bool _noAlpha; - public bool NoAlpha - { - get => _noAlpha; - set - { - SetProperty(ref _noAlpha, value); - ResetImage(); - } - } + public bool HasImage => SelectedImage != null; + public bool HasMultipleImages => _images.Count > 1; + public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}"; - private bool _renderNearestNeighbor; - public bool RenderNearestNeighbor - { - get => _renderNearestNeighbor; - set => SetProperty(ref _renderNearestNeighbor, value); - } + private readonly ObservableCollection _images; - public bool HasImage => Image != null; public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger); private TabCommand _tabCommand; @@ -161,8 +209,37 @@ namespace FModel.ViewModels { Header = header; Directory = directory; + _images = new ObservableCollection(); } + public void ClearImages() + { + Application.Current.Dispatcher.Invoke(() => + { + _images.Clear(); + SelectedImage = null; + }); + } + + public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode()); + public void AddImage(string name, bool rnn, SKImage img) + { + Application.Current.Dispatcher.Invoke(() => + { + var t = new TabImage(name, rnn, img); + if (UserSettings.Default.IsAutoSaveTextures) + SaveImage(t, true); + + _images.Add(t); + SelectedImage ??= t; + RaisePropertyChanged("Page"); + RaisePropertyChanged("HasMultipleImages"); + }); + } + + public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage); + public void GoNextImage() => SelectedImage = _images.Next(SelectedImage); + public void SetDocumentText(string text, bool bulkSave) { Application.Current.Dispatcher.Invoke(() => @@ -174,7 +251,6 @@ namespace FModel.ViewModels SaveProperty(true); }); } - public void ResetDocumentText() { Application.Current.Dispatcher.Invoke(() => @@ -184,6 +260,40 @@ namespace FModel.ViewModels }); } + public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave); + private void SaveImage(TabImage image, bool autoSave) + { + if (image == null) return; + var fileName = $"{image.ExportName}.png"; + var directory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports", + UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/'); + + if (!autoSave) + { + var saveFileDialog = new SaveFileDialog + { + Title = "Save Texture", + FileName = fileName, + InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"), + Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*" + }; + var result = saveFileDialog.ShowDialog(); + if (!result.HasValue || !result.Value) return; + directory = saveFileDialog.FileName; + } + else + { + System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/')); + } + + using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length); + } + + SaveCheck(directory, fileName); + } + public void SaveProperty(bool autoSave) { var fileName = Path.ChangeExtension(Header, ".json"); @@ -212,60 +322,7 @@ namespace FModel.ViewModels SaveCheck(directory, fileName); } - private SKImage _img; - public void ResetImage() => SetImage(_img); - public void SetImage(SKImage img) - { - _img = img; - - using var data = _img.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100); - using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false); - var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = stream; - image.EndInit(); - image.Freeze(); - - Image = image; - if (UserSettings.Default.IsAutoSaveTextures) - SaveImage(true); - } - - public void SaveImage(bool autoSave) - { - if (!HasImage) return; - var fileName = Path.ChangeExtension(Header, ".png"); - var directory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports", - UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/'); - - if (!autoSave) - { - var saveFileDialog = new SaveFileDialog - { - Title = "Save Texture", - FileName = fileName, - InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"), - Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*" - }; - var result = saveFileDialog.ShowDialog(); - if (!result.HasValue || !result.Value) return; - directory = saveFileDialog.FileName; - } - else - { - System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/')); - } - - using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - fs.Write(ImageBuffer, 0, ImageBuffer.Length); - } - - SaveCheck(directory, fileName); - } - - private static void SaveCheck(string path, string fileName) + private void SaveCheck(string path, string fileName) { if (File.Exists(path)) { diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index 6ba9cf9b..c1fd9080 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -764,13 +764,13 @@ + Source="{Binding SelectedItem.SelectedImage.Image, ElementName=TabControlName}" RenderOptions.BitmapScalingMode="{Binding SelectedItem.SelectedImage.RenderNearestNeighbor, ElementName=TabControlName, Converter={x:Static converters:BoolToRenderModeConverter.Instance}}"> - + @@ -803,6 +803,9 @@ +