feat: start adding image viewer

This commit is contained in:
Anna 2024-02-21 23:35:14 -05:00
parent 1c50528fee
commit 9c8f07e3a7
Signed by: anna
GPG Key ID: D0943384CD9F87D1
9 changed files with 192 additions and 1 deletions

View File

@ -12,6 +12,7 @@ using FFXIVClientStructs.FFXIV.Client.UI;
using Newtonsoft.Json;
using Screenie.Ui;
using WebP.Net;
using WebPDotNet;
namespace Screenie;

4
Ui/DrawStatus.cs Normal file
View File

@ -0,0 +1,4 @@
internal enum DrawStatus {
Continuing,
Finished,
}

3
Ui/IDrawable.cs Normal file
View File

@ -0,0 +1,3 @@
internal interface IDrawable : IDisposable {
DrawStatus Draw();
}

View File

@ -8,6 +8,8 @@ namespace Screenie.Ui;
internal class PluginUi : IDisposable {
private Plugin Plugin { get; }
private List<ITab> Tabs { get; } = [];
internal List<IDrawable> Drawables { get; } = [];
private List<IDrawable> ToDispose { get; } = [];
internal bool Visible;
@ -36,6 +38,31 @@ internal class PluginUi : IDisposable {
}
private void Draw() {
foreach (var drawable in this.ToDispose) {
try {
drawable.Dispose();
} catch (Exception ex) {
Plugin.Log.Error(ex, "Failed to dispose drawable");
}
}
this.ToDispose.Clear();
this.Drawables.RemoveAll(drawable => {
// return true to stop drawing
try {
if (drawable.Draw() == DrawStatus.Finished) {
this.ToDispose.Add(drawable);
return true;
}
return false;
} catch (Exception ex) {
Plugin.Log.Error(ex, "Error drawing drawable");
return true;
}
});
if (!this.Visible) {
return;
}

View File

@ -191,7 +191,7 @@ internal class DatabaseTab : ITab {
ImGui.SameLine();
if (ImGui.Button("View", new Vector2(buttonWidth, 0))) {
// TODO: make image viewer
this.Plugin.Ui.Drawables.Add(new Viewer(this.Plugin, shot.Path));
// TODO: draw dots where players are (on hover?)
}
@ -200,8 +200,27 @@ internal class DatabaseTab : ITab {
ImGui.TextUnformatted("None");
}
var viewers = this.Plugin.Ui.Drawables
.Where(drawable => drawable is Viewer)
.Cast<Viewer>()
.Where(viewer => viewer.ImagePath == shot.Path)
.ToList();
var anyHovered = false;
foreach (var chara in shot.Metadata.VisibleCharacters) {
ImGui.TextUnformatted($"{chara.Name} ({chara.HomeWorld})");
if (ImGui.IsItemHovered()) {
foreach (var viewer in viewers) {
anyHovered = true;
viewer.DotPosition = chara.ImagePosition;
}
}
}
if (!anyHovered) {
foreach (var viewer in viewers) {
viewer.DotPosition = null;
}
}
}

108
Ui/Viewer.cs Normal file
View File

@ -0,0 +1,108 @@
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility;
using ImGuiNET;
using Screenie.Util;
namespace Screenie.Ui;
internal class Viewer : IDrawable {
private Plugin Plugin { get; }
private Guid Id { get; } = Guid.NewGuid();
private IDalamudTextureWrap? Image { get; set; }
private Exception? Error { get; set; }
private bool _disposed;
private bool _visible;
internal string ImagePath { get; }
internal Vector2? DotPosition { get; set; }
internal Viewer(Plugin plugin, string path) {
this.Plugin = plugin;
this.ImagePath = path;
Task.Factory.StartNew(async () => {
try {
var bytes = await File.ReadAllBytesAsync(this.ImagePath);
this.Image = await ImageHelper.LoadImageAsync(this.Plugin.Interface.UiBuilder, bytes);
} catch (Exception ex) {
this.Error = ex;
Plugin.Log.Warning(ex, $"Failed to load image at {this.ImagePath}");
}
});
}
public void Dispose() {
if (this._disposed) {
return;
}
this._disposed = true;
// if (this.Image != null) {
// this.Plugin.Framework.RunOnTick(this.Image.Dispose);
// }
this.Image?.Dispose();
}
public DrawStatus Draw() {
if (!this._visible) {
return DrawStatus.Finished;
}
ImGui.PushID($"viewer-{this.ImagePath}-{this.Id}");
using var popId = new OnDispose(ImGui.PopID);
using var end = new OnDispose(ImGui.End);
if (!ImGui.Begin($"View {Path.GetFileName(this.ImagePath)}", ref this._visible, ImGuiWindowFlags.NoSavedSettings)) {
return DrawStatus.Continuing;
}
var viewport = ImGui.GetWindowViewport();
var viewportSize = viewport.Size;
ImGui.SetWindowSize(viewportSize * new Vector2(0.20f), ImGuiCond.Appearing);
if (this.Error != null) {
ImGuiHelpers.CenteredText("An error ocurred loading the image.");
ImGui.PushFont(UiBuilder.MonoFont);
using var popFont = new OnDispose(ImGui.PopFont);
ImGui.TextUnformatted(this.Error.Message);
}
if (this.Image != null) {
var beforeImage = ImGui.GetCursorScreenPos();
var avail = ImGui.GetContentRegionAvail();
var imageSize = this.Image.Size;
var constrainedX = avail.X < imageSize.X;
var constrainedY = avail.Y < imageSize.Y;
var sizeToUse = imageSize;
if (constrainedX || constrainedY) {
// var dividend = imageSize / avail;
// var inv = Vector2.One / dividend;
// scale the image based on the more-constrained dimension
// var ratio = dividend.X > dividend.Y
// ? inv.X // X is more constrained
// : inv.Y; // Y is more constrained
var dividend = avail / imageSize;
// scale the image based on the more-constrained dimension
var ratio = dividend.Y > dividend.X
? dividend.X // X is more constrained
: dividend.Y; // Y is more constrained
sizeToUse = new Vector2(
imageSize.X * ratio,
imageSize.Y * ratio
);
}
ImGui.Image(this.Image.ImGuiHandle, sizeToUse);
}
return DrawStatus.Continuing;
}
}

14
Util/ImageHelper.cs Normal file
View File

@ -0,0 +1,14 @@
using Dalamud.Interface;
using Dalamud.Interface.Internal;
namespace Screenie.Util;
internal static class ImageHelper {
internal static async Task<IDalamudTextureWrap?> LoadImageAsync(UiBuilder builder, byte[] buffer) {
if (buffer.Length >= 12 && buffer.AsSpan()[..4].SequenceEqual("RIFF"u8) && buffer.AsSpan()[8..12].SequenceEqual("WEBP"u8)) {
return await WebPHelper.LoadAsync(builder, buffer);
}
return await builder.LoadImageAsync(buffer);
}
}

15
Util/WebPHelper.cs Normal file
View File

@ -0,0 +1,15 @@
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using WebP.Net;
namespace Screenie.Util;
internal static class WebPHelper {
internal static async Task<IDalamudTextureWrap?> LoadAsync(UiBuilder builder, byte[] imageBytes) {
using var webp = new WebPObject(imageBytes);
var info = webp.GetInfo();
var outputBuffer = webp.GetWebPLossy(80);
return await builder.LoadImageRawAsync(outputBuffer, info.Width, info.Height, info.HasAlpha ? 4 : 3);
}
}

Binary file not shown.