mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
403 lines
17 KiB
C#
403 lines
17 KiB
C#
using ImGuiNET;
|
|
using OtterGui.Raii;
|
|
using OtterGui;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using Dalamud.Interface.Utility;
|
|
using Penumbra.UI;
|
|
|
|
namespace Penumbra.Import.Textures;
|
|
|
|
public partial class CombinedTexture
|
|
{
|
|
private Matrix4x4 _multiplierLeft = Matrix4x4.Identity;
|
|
private Vector4 _constantLeft = Vector4.Zero;
|
|
private Matrix4x4 _multiplierRight = Matrix4x4.Identity;
|
|
private Vector4 _constantRight = Vector4.Zero;
|
|
private int _offsetX;
|
|
private int _offsetY;
|
|
private CombineOp _combineOp = CombineOp.Over;
|
|
private ResizeOp _resizeOp = ResizeOp.None;
|
|
private Channels _copyChannels = Channels.Red | Channels.Green | Channels.Blue | Channels.Alpha;
|
|
|
|
private RgbaPixelData _leftPixels = RgbaPixelData.Empty;
|
|
private RgbaPixelData _rightPixels = RgbaPixelData.Empty;
|
|
|
|
private const float OneThird = 1.0f / 3.0f;
|
|
private const float RWeight = 0.2126f;
|
|
private const float GWeight = 0.7152f;
|
|
private const float BWeight = 0.0722f;
|
|
|
|
// @formatter:off
|
|
private static readonly IReadOnlyList<(string Label, Matrix4x4 Multiplier, Vector4 Constant)> PredefinedColorTransforms =
|
|
new[]
|
|
{
|
|
("No Transform (Identity)", Matrix4x4.Identity, Vector4.Zero ),
|
|
("Grayscale (Average)", new Matrix4x4(OneThird, OneThird, OneThird, 0.0f, OneThird, OneThird, OneThird, 0.0f, OneThird, OneThird, OneThird, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), Vector4.Zero ),
|
|
("Grayscale (Weighted)", new Matrix4x4(RWeight, RWeight, RWeight, 0.0f, GWeight, GWeight, GWeight, 0.0f, BWeight, BWeight, BWeight, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), Vector4.Zero ),
|
|
("Grayscale (Average) to Alpha", new Matrix4x4(OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.Zero ),
|
|
("Grayscale (Weighted) to Alpha", new Matrix4x4(RWeight, RWeight, RWeight, RWeight, GWeight, GWeight, GWeight, GWeight, BWeight, BWeight, BWeight, BWeight, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.Zero ),
|
|
("Make Opaque (Drop Alpha)", new Matrix4x4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.UnitW ),
|
|
("Extract Red", new Matrix4x4(1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.UnitW ),
|
|
("Extract Green", new Matrix4x4(0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.UnitW ),
|
|
("Extract Blue", new Matrix4x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.UnitW ),
|
|
("Extract Alpha", new Matrix4x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f), Vector4.UnitW ),
|
|
};
|
|
// @formatter:on
|
|
|
|
private Vector4 DataLeft(int offset)
|
|
=> CappedVector(_leftPixels.PixelData, offset, _multiplierLeft, _constantLeft);
|
|
|
|
private Vector4 DataRight(int offset)
|
|
=> CappedVector(_rightPixels.PixelData, offset, _multiplierRight, _constantRight);
|
|
|
|
private Vector4 DataRight(int x, int y)
|
|
{
|
|
x += _offsetX;
|
|
y += _offsetY;
|
|
if (x < 0 || x >= _rightPixels.Width || y < 0 || y >= _rightPixels.Height)
|
|
return Vector4.Zero;
|
|
|
|
var offset = (y * _rightPixels.Width + x) * 4;
|
|
return CappedVector(_rightPixels.PixelData, offset, _multiplierRight, _constantRight);
|
|
}
|
|
|
|
private void AddPixelsMultiplied(int y, ParallelLoopState _)
|
|
{
|
|
for (var x = 0; x < _leftPixels.Width; ++x)
|
|
{
|
|
var offset = (_leftPixels.Width * y + x) * 4;
|
|
var left = DataLeft(offset);
|
|
var right = DataRight(x, y);
|
|
var alpha = right.W + left.W * (1 - right.W);
|
|
var rgba = alpha == 0
|
|
? new Rgba32()
|
|
: new Rgba32(((right * right.W + left * left.W * (1 - right.W)) / alpha) with { W = alpha });
|
|
_centerStorage.RgbaPixels[offset] = rgba.R;
|
|
_centerStorage.RgbaPixels[offset + 1] = rgba.G;
|
|
_centerStorage.RgbaPixels[offset + 2] = rgba.B;
|
|
_centerStorage.RgbaPixels[offset + 3] = rgba.A;
|
|
}
|
|
}
|
|
|
|
private void ReverseAddPixelsMultiplied(int y, ParallelLoopState _)
|
|
{
|
|
for (var x = 0; x < _leftPixels.Width; ++x)
|
|
{
|
|
var offset = (_leftPixels.Width * y + x) * 4;
|
|
var left = DataLeft(offset);
|
|
var right = DataRight(x, y);
|
|
var alpha = left.W + right.W * (1 - left.W);
|
|
var rgba = alpha == 0
|
|
? new Rgba32()
|
|
: new Rgba32(((left * left.W + right * right.W * (1 - left.W)) / alpha) with { W = alpha });
|
|
_centerStorage.RgbaPixels[offset] = rgba.R;
|
|
_centerStorage.RgbaPixels[offset + 1] = rgba.G;
|
|
_centerStorage.RgbaPixels[offset + 2] = rgba.B;
|
|
_centerStorage.RgbaPixels[offset + 3] = rgba.A;
|
|
}
|
|
}
|
|
|
|
private void ChannelMergePixelsMultiplied(int y, ParallelLoopState _)
|
|
{
|
|
var channels = _copyChannels;
|
|
for (var x = 0; x < _leftPixels.Width; ++x)
|
|
{
|
|
var offset = (_leftPixels.Width * y + x) * 4;
|
|
var left = DataLeft(offset);
|
|
var right = DataRight(x, y);
|
|
var rgba = new Rgba32((channels & Channels.Red) != 0 ? right.X : left.X,
|
|
(channels & Channels.Green) != 0 ? right.Y : left.Y,
|
|
(channels & Channels.Blue) != 0 ? right.Z : left.Z,
|
|
(channels & Channels.Alpha) != 0 ? right.W : left.W);
|
|
_centerStorage.RgbaPixels[offset] = rgba.R;
|
|
_centerStorage.RgbaPixels[offset + 1] = rgba.G;
|
|
_centerStorage.RgbaPixels[offset + 2] = rgba.B;
|
|
_centerStorage.RgbaPixels[offset + 3] = rgba.A;
|
|
}
|
|
}
|
|
|
|
private void MultiplyPixelsLeft(int y, ParallelLoopState _)
|
|
{
|
|
for (var x = 0; x < _leftPixels.Width; ++x)
|
|
{
|
|
var offset = (_leftPixels.Width * y + x) * 4;
|
|
var left = DataLeft(offset);
|
|
var rgba = new Rgba32(left);
|
|
_centerStorage.RgbaPixels[offset] = rgba.R;
|
|
_centerStorage.RgbaPixels[offset + 1] = rgba.G;
|
|
_centerStorage.RgbaPixels[offset + 2] = rgba.B;
|
|
_centerStorage.RgbaPixels[offset + 3] = rgba.A;
|
|
}
|
|
}
|
|
|
|
private void MultiplyPixelsRight(int y, ParallelLoopState _)
|
|
{
|
|
for (var x = 0; x < _rightPixels.Width; ++x)
|
|
{
|
|
var offset = (_rightPixels.Width * y + x) * 4;
|
|
var right = DataRight(offset);
|
|
var rgba = new Rgba32(right);
|
|
_centerStorage.RgbaPixels[offset] = rgba.R;
|
|
_centerStorage.RgbaPixels[offset + 1] = rgba.G;
|
|
_centerStorage.RgbaPixels[offset + 2] = rgba.B;
|
|
_centerStorage.RgbaPixels[offset + 3] = rgba.A;
|
|
}
|
|
}
|
|
|
|
private (int Width, int Height) CombineImage()
|
|
{
|
|
var combineOp = GetActualCombineOp();
|
|
var resizeOp = GetActualResizeOp(_resizeOp, combineOp);
|
|
|
|
var left = resizeOp != ResizeOp.RightOnly ? RgbaPixelData.FromTexture(_left) : RgbaPixelData.Empty;
|
|
var right = resizeOp != ResizeOp.LeftOnly ? RgbaPixelData.FromTexture(_right) : RgbaPixelData.Empty;
|
|
|
|
var targetSize = resizeOp switch
|
|
{
|
|
ResizeOp.RightOnly => right.Size,
|
|
ResizeOp.ToRight => right.Size,
|
|
_ => left.Size,
|
|
};
|
|
|
|
try
|
|
{
|
|
_centerStorage.RgbaPixels = RgbaPixelData.NewPixelData(targetSize);
|
|
_centerStorage.Type = TextureType.Bitmap;
|
|
|
|
_leftPixels = resizeOp switch
|
|
{
|
|
ResizeOp.RightOnly => RgbaPixelData.Empty,
|
|
_ => left.Resize(targetSize),
|
|
};
|
|
_rightPixels = resizeOp switch
|
|
{
|
|
ResizeOp.LeftOnly => RgbaPixelData.Empty,
|
|
ResizeOp.None => right,
|
|
_ => right.Resize(targetSize),
|
|
};
|
|
|
|
Parallel.For(0, targetSize.Height, combineOp switch
|
|
{
|
|
CombineOp.Over => AddPixelsMultiplied,
|
|
CombineOp.Under => ReverseAddPixelsMultiplied,
|
|
CombineOp.LeftMultiply => MultiplyPixelsLeft,
|
|
CombineOp.RightMultiply => MultiplyPixelsRight,
|
|
CombineOp.CopyChannels => ChannelMergePixelsMultiplied,
|
|
_ => throw new InvalidOperationException($"Cannot combine images with operation {combineOp}"),
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
_leftPixels = RgbaPixelData.Empty;
|
|
_rightPixels = RgbaPixelData.Empty;
|
|
}
|
|
|
|
return targetSize;
|
|
}
|
|
|
|
private static Vector4 CappedVector(IReadOnlyList<byte> bytes, int offset, Matrix4x4 transform, Vector4 constant)
|
|
{
|
|
if (bytes.Count == 0)
|
|
return Vector4.Zero;
|
|
|
|
var rgba = new Rgba32(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
|
|
var transformed = Vector4.Transform(rgba.ToVector4(), transform) + constant;
|
|
|
|
transformed.X = Math.Clamp(transformed.X, 0, 1);
|
|
transformed.Y = Math.Clamp(transformed.Y, 0, 1);
|
|
transformed.Z = Math.Clamp(transformed.Z, 0, 1);
|
|
transformed.W = Math.Clamp(transformed.W, 0, 1);
|
|
return transformed;
|
|
}
|
|
|
|
private static bool DragFloat(string label, float width, ref float value)
|
|
{
|
|
var tmp = value;
|
|
ImGui.TableNextColumn();
|
|
ImGui.SetNextItemWidth(width);
|
|
if (ImGui.DragFloat(label, ref tmp, 0.001f, -1f, 1f))
|
|
value = tmp;
|
|
|
|
return ImGui.IsItemDeactivatedAfterEdit();
|
|
}
|
|
|
|
public void DrawMatrixInputLeft(float width)
|
|
{
|
|
var ret = DrawMatrixInput(ref _multiplierLeft, ref _constantLeft, width);
|
|
ret |= DrawMatrixTools(ref _multiplierLeft, ref _constantLeft);
|
|
if (ret)
|
|
Update();
|
|
}
|
|
|
|
public void DrawMatrixInputRight(float width)
|
|
{
|
|
var ret = DrawMatrixInput(ref _multiplierRight, ref _constantRight, width);
|
|
ret |= DrawMatrixTools(ref _multiplierRight, ref _constantRight);
|
|
|
|
ImGui.SetNextItemWidth(75.0f * UiHelpers.Scale);
|
|
ImGui.DragInt("##XOffset", ref _offsetX, 0.5f);
|
|
ret |= ImGui.IsItemDeactivatedAfterEdit();
|
|
ImGui.SameLine();
|
|
ImGui.SetNextItemWidth(75.0f * UiHelpers.Scale);
|
|
ImGui.DragInt("Offsets##YOffset", ref _offsetY, 0.5f);
|
|
ret |= ImGui.IsItemDeactivatedAfterEdit();
|
|
|
|
ImGui.SetNextItemWidth(200.0f * UiHelpers.Scale);
|
|
using (var c = ImRaii.Combo("Combine Operation", CombineOpLabels[(int)_combineOp]))
|
|
{
|
|
if (c)
|
|
foreach (var op in Enum.GetValues<CombineOp>())
|
|
{
|
|
if ((int)op < 0) // Negative codes are for internal use only.
|
|
continue;
|
|
|
|
if (ImGui.Selectable(CombineOpLabels[(int)op], op == _combineOp))
|
|
{
|
|
_combineOp = op;
|
|
ret = true;
|
|
}
|
|
|
|
ImGuiUtil.SelectableHelpMarker(CombineOpTooltips[(int)op]);
|
|
}
|
|
}
|
|
|
|
var resizeOp = GetActualResizeOp(_resizeOp, _combineOp);
|
|
using (var dis = ImRaii.Disabled((int)resizeOp < 0))
|
|
{
|
|
ret |= ImGuiUtil.GenericEnumCombo("Resizing Mode", 200.0f * UiHelpers.Scale, _resizeOp, out _resizeOp,
|
|
Enum.GetValues<ResizeOp>().Where(op => (int)op >= 0), op => ResizeOpLabels[(int)op]);
|
|
}
|
|
|
|
using (var dis = ImRaii.Disabled(_combineOp != CombineOp.CopyChannels))
|
|
{
|
|
ImGui.TextUnformatted("Copy");
|
|
foreach (var channel in Enum.GetValues<Channels>())
|
|
{
|
|
ImGui.SameLine();
|
|
var copy = (_copyChannels & channel) != 0;
|
|
if (ImGui.Checkbox(channel.ToString(), ref copy))
|
|
{
|
|
_copyChannels = copy ? _copyChannels | channel : _copyChannels & ~channel;
|
|
ret = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
Update();
|
|
}
|
|
|
|
private static bool DrawMatrixInput(ref Matrix4x4 multiplier, ref Vector4 constant, float width)
|
|
{
|
|
using var table = ImRaii.Table(string.Empty, 5, ImGuiTableFlags.BordersInner | ImGuiTableFlags.SizingFixedFit);
|
|
if (!table)
|
|
return false;
|
|
|
|
var changes = false;
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableNextColumn();
|
|
ImGuiUtil.Center("R");
|
|
ImGui.TableNextColumn();
|
|
ImGuiUtil.Center("G");
|
|
ImGui.TableNextColumn();
|
|
ImGuiUtil.Center("B");
|
|
ImGui.TableNextColumn();
|
|
ImGuiUtil.Center("A");
|
|
|
|
var inputWidth = width / 6;
|
|
ImGui.TableNextColumn();
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.Text("R ");
|
|
changes |= DragFloat("##RR", inputWidth, ref multiplier.M11);
|
|
changes |= DragFloat("##RG", inputWidth, ref multiplier.M12);
|
|
changes |= DragFloat("##RB", inputWidth, ref multiplier.M13);
|
|
changes |= DragFloat("##RA", inputWidth, ref multiplier.M14);
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.Text("G ");
|
|
changes |= DragFloat("##GR", inputWidth, ref multiplier.M21);
|
|
changes |= DragFloat("##GG", inputWidth, ref multiplier.M22);
|
|
changes |= DragFloat("##GB", inputWidth, ref multiplier.M23);
|
|
changes |= DragFloat("##GA", inputWidth, ref multiplier.M24);
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.Text("B ");
|
|
changes |= DragFloat("##BR", inputWidth, ref multiplier.M31);
|
|
changes |= DragFloat("##BG", inputWidth, ref multiplier.M32);
|
|
changes |= DragFloat("##BB", inputWidth, ref multiplier.M33);
|
|
changes |= DragFloat("##BA", inputWidth, ref multiplier.M34);
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.Text("A ");
|
|
changes |= DragFloat("##AR", inputWidth, ref multiplier.M41);
|
|
changes |= DragFloat("##AG", inputWidth, ref multiplier.M42);
|
|
changes |= DragFloat("##AB", inputWidth, ref multiplier.M43);
|
|
changes |= DragFloat("##AA", inputWidth, ref multiplier.M44);
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.Text("1 ");
|
|
changes |= DragFloat("##1R", inputWidth, ref constant.X);
|
|
changes |= DragFloat("##1G", inputWidth, ref constant.Y);
|
|
changes |= DragFloat("##1B", inputWidth, ref constant.Z);
|
|
changes |= DragFloat("##1A", inputWidth, ref constant.W);
|
|
|
|
return changes;
|
|
}
|
|
|
|
private static bool DrawMatrixTools(ref Matrix4x4 multiplier, ref Vector4 constant)
|
|
{
|
|
var changes = PresetCombo(ref multiplier, ref constant);
|
|
ImGui.SameLine();
|
|
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
|
|
ImGui.SameLine();
|
|
ImGui.TextUnformatted("Invert");
|
|
ImGui.SameLine();
|
|
|
|
Channels channels = 0;
|
|
if (ImGui.Button("Colors"))
|
|
channels |= Channels.Red | Channels.Green | Channels.Blue;
|
|
ImGui.SameLine();
|
|
if (ImGui.Button("R"))
|
|
channels |= Channels.Red;
|
|
|
|
ImGui.SameLine();
|
|
if (ImGui.Button("G"))
|
|
channels |= Channels.Green;
|
|
|
|
ImGui.SameLine();
|
|
if (ImGui.Button("B"))
|
|
channels |= Channels.Blue;
|
|
|
|
ImGui.SameLine();
|
|
if (ImGui.Button("A"))
|
|
channels |= Channels.Alpha;
|
|
|
|
changes |= InvertChannels(channels, ref multiplier, ref constant);
|
|
return changes;
|
|
}
|
|
|
|
private static bool PresetCombo(ref Matrix4x4 multiplier, ref Vector4 constant)
|
|
{
|
|
using var combo = ImRaii.Combo("Presets", string.Empty, ImGuiComboFlags.NoPreview);
|
|
if (!combo)
|
|
return false;
|
|
|
|
var ret = false;
|
|
foreach (var (label, preMultiplier, preConstant) in PredefinedColorTransforms)
|
|
{
|
|
if (!ImGui.Selectable(label, multiplier == preMultiplier && constant == preConstant))
|
|
continue;
|
|
|
|
multiplier = preMultiplier;
|
|
constant = preConstant;
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
}
|