mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Textures: Automatic resizing
This commit is contained in:
parent
792707a6e3
commit
99b43bf577
1 changed files with 105 additions and 26 deletions
|
|
@ -8,6 +8,8 @@ using OtterGui;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
namespace Penumbra.Import.Textures;
|
namespace Penumbra.Import.Textures;
|
||||||
|
|
||||||
|
|
@ -25,6 +27,13 @@ public partial class CombinedTexture
|
||||||
CopyChannels = 3,
|
CopyChannels = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum ResizeOp
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
ToLeft = 1,
|
||||||
|
ToRight = 2,
|
||||||
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
private enum Channels
|
private enum Channels
|
||||||
{
|
{
|
||||||
|
|
@ -41,8 +50,16 @@ public partial class CombinedTexture
|
||||||
private int _offsetX = 0;
|
private int _offsetX = 0;
|
||||||
private int _offsetY = 0;
|
private int _offsetY = 0;
|
||||||
private CombineOp _combineOp = CombineOp.Over;
|
private CombineOp _combineOp = CombineOp.Over;
|
||||||
|
private ResizeOp _resizeOp = ResizeOp.None;
|
||||||
private Channels _copyChannels = Channels.Red | Channels.Green | Channels.Blue | Channels.Alpha;
|
private Channels _copyChannels = Channels.Red | Channels.Green | Channels.Blue | Channels.Alpha;
|
||||||
|
|
||||||
|
private int _rightWidth = 0;
|
||||||
|
private int _rightHeight = 0;
|
||||||
|
private int _targetWidth = 0;
|
||||||
|
private int _targetHeight = 0;
|
||||||
|
private byte[] _leftPixels = Array.Empty<byte>();
|
||||||
|
private byte[] _rightPixels = Array.Empty<byte>();
|
||||||
|
|
||||||
private static readonly IReadOnlyList<string> CombineOpLabels = new string[]
|
private static readonly IReadOnlyList<string> CombineOpLabels = new string[]
|
||||||
{
|
{
|
||||||
"Overlay over Input",
|
"Overlay over Input",
|
||||||
|
|
@ -59,6 +76,26 @@ public partial class CombinedTexture
|
||||||
"Replace some input channels with those from the overlay.\nUseful for Multi maps.",
|
"Replace some input channels with those from the overlay.\nUseful for Multi maps.",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static (bool UsesLeft, bool UsesRight) GetCombineOpFlags(CombineOp combineOp)
|
||||||
|
=> combineOp switch
|
||||||
|
{
|
||||||
|
CombineOp.LeftCopy => (true, false),
|
||||||
|
CombineOp.LeftMultiply => (true, false),
|
||||||
|
CombineOp.RightCopy => (false, true),
|
||||||
|
CombineOp.RightMultiply => (false, true),
|
||||||
|
CombineOp.Over => (true, true),
|
||||||
|
CombineOp.Under => (true, true),
|
||||||
|
CombineOp.CopyChannels => (true, true),
|
||||||
|
_ => throw new ArgumentException($"Invalid combine operation {combineOp}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly IReadOnlyList<string> ResizeOpLabels = new string[]
|
||||||
|
{
|
||||||
|
"No Resizing",
|
||||||
|
"Adjust Overlay to Input",
|
||||||
|
"Adjust Input to Overlay",
|
||||||
|
};
|
||||||
|
|
||||||
private const float OneThird = 1.0f / 3.0f;
|
private const float OneThird = 1.0f / 3.0f;
|
||||||
private const float RWeight = 0.2126f;
|
private const float RWeight = 0.2126f;
|
||||||
private const float GWeight = 0.7152f;
|
private const float GWeight = 0.7152f;
|
||||||
|
|
@ -107,27 +144,27 @@ public partial class CombinedTexture
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector4 DataLeft(int offset)
|
private Vector4 DataLeft(int offset)
|
||||||
=> CappedVector(_left.RgbaPixels, offset, _multiplierLeft, _constantLeft);
|
=> CappedVector(_leftPixels, offset, _multiplierLeft, _constantLeft);
|
||||||
|
|
||||||
private Vector4 DataRight(int offset)
|
private Vector4 DataRight(int offset)
|
||||||
=> CappedVector(_right.RgbaPixels, offset, _multiplierRight, _constantRight);
|
=> CappedVector(_rightPixels, offset, _multiplierRight, _constantRight);
|
||||||
|
|
||||||
private Vector4 DataRight(int x, int y)
|
private Vector4 DataRight(int x, int y)
|
||||||
{
|
{
|
||||||
x += _offsetX;
|
x += _offsetX;
|
||||||
y += _offsetY;
|
y += _offsetY;
|
||||||
if (x < 0 || x >= _right.TextureWrap!.Width || y < 0 || y >= _right.TextureWrap!.Height)
|
if (x < 0 || x >= _rightWidth || y < 0 || y >= _rightHeight)
|
||||||
return Vector4.Zero;
|
return Vector4.Zero;
|
||||||
|
|
||||||
var offset = (y * _right.TextureWrap!.Width + x) * 4;
|
var offset = (y * _rightWidth + x) * 4;
|
||||||
return CappedVector(_right.RgbaPixels, offset, _multiplierRight, _constantRight);
|
return CappedVector(_rightPixels, offset, _multiplierRight, _constantRight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddPixelsMultiplied(int y, ParallelLoopState _)
|
private void AddPixelsMultiplied(int y, ParallelLoopState _)
|
||||||
{
|
{
|
||||||
for (var x = 0; x < _left.TextureWrap!.Width; ++x)
|
for (var x = 0; x < _targetWidth; ++x)
|
||||||
{
|
{
|
||||||
var offset = (_left.TextureWrap!.Width * y + x) * 4;
|
var offset = (_targetWidth * y + x) * 4;
|
||||||
var left = DataLeft(offset);
|
var left = DataLeft(offset);
|
||||||
var right = DataRight(x, y);
|
var right = DataRight(x, y);
|
||||||
var alpha = right.W + left.W * (1 - right.W);
|
var alpha = right.W + left.W * (1 - right.W);
|
||||||
|
|
@ -143,9 +180,9 @@ public partial class CombinedTexture
|
||||||
|
|
||||||
private void ReverseAddPixelsMultiplied(int y, ParallelLoopState _)
|
private void ReverseAddPixelsMultiplied(int y, ParallelLoopState _)
|
||||||
{
|
{
|
||||||
for (var x = 0; x < _left.TextureWrap!.Width; ++x)
|
for (var x = 0; x < _targetWidth; ++x)
|
||||||
{
|
{
|
||||||
var offset = (_left.TextureWrap!.Width * y + x) * 4;
|
var offset = (_targetWidth * y + x) * 4;
|
||||||
var left = DataLeft(offset);
|
var left = DataLeft(offset);
|
||||||
var right = DataRight(x, y);
|
var right = DataRight(x, y);
|
||||||
var alpha = left.W + right.W * (1 - left.W);
|
var alpha = left.W + right.W * (1 - left.W);
|
||||||
|
|
@ -162,9 +199,9 @@ public partial class CombinedTexture
|
||||||
private void ChannelMergePixelsMultiplied(int y, ParallelLoopState _)
|
private void ChannelMergePixelsMultiplied(int y, ParallelLoopState _)
|
||||||
{
|
{
|
||||||
var channels = _copyChannels;
|
var channels = _copyChannels;
|
||||||
for (var x = 0; x < _left.TextureWrap!.Width; ++x)
|
for (var x = 0; x < _targetWidth; ++x)
|
||||||
{
|
{
|
||||||
var offset = (_left.TextureWrap!.Width * y + x) * 4;
|
var offset = (_targetWidth * y + x) * 4;
|
||||||
var left = DataLeft(offset);
|
var left = DataLeft(offset);
|
||||||
var right = DataRight(x, y);
|
var right = DataRight(x, y);
|
||||||
var rgba = new Rgba32((channels & Channels.Red) != 0 ? right.X : left.X,
|
var rgba = new Rgba32((channels & Channels.Red) != 0 ? right.X : left.X,
|
||||||
|
|
@ -180,9 +217,9 @@ public partial class CombinedTexture
|
||||||
|
|
||||||
private void MultiplyPixelsLeft(int y, ParallelLoopState _)
|
private void MultiplyPixelsLeft(int y, ParallelLoopState _)
|
||||||
{
|
{
|
||||||
for (var x = 0; x < _left.TextureWrap!.Width; ++x)
|
for (var x = 0; x < _targetWidth; ++x)
|
||||||
{
|
{
|
||||||
var offset = (_left.TextureWrap!.Width * y + x) * 4;
|
var offset = (_targetWidth * y + x) * 4;
|
||||||
var left = DataLeft(offset);
|
var left = DataLeft(offset);
|
||||||
var rgba = new Rgba32(left);
|
var rgba = new Rgba32(left);
|
||||||
_centerStorage.RgbaPixels[offset] = rgba.R;
|
_centerStorage.RgbaPixels[offset] = rgba.R;
|
||||||
|
|
@ -194,9 +231,9 @@ public partial class CombinedTexture
|
||||||
|
|
||||||
private void MultiplyPixelsRight(int y, ParallelLoopState _)
|
private void MultiplyPixelsRight(int y, ParallelLoopState _)
|
||||||
{
|
{
|
||||||
for (var x = 0; x < _right.TextureWrap!.Width; ++x)
|
for (var x = 0; x < _targetWidth; ++x)
|
||||||
{
|
{
|
||||||
var offset = (_right.TextureWrap!.Width * y + x) * 4;
|
var offset = (_targetWidth * y + x) * 4;
|
||||||
var right = DataRight(offset);
|
var right = DataRight(offset);
|
||||||
var rgba = new Rgba32(right);
|
var rgba = new Rgba32(right);
|
||||||
_centerStorage.RgbaPixels[offset] = rgba.R;
|
_centerStorage.RgbaPixels[offset] = rgba.R;
|
||||||
|
|
@ -206,26 +243,59 @@ public partial class CombinedTexture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] ResizePixels(byte[] rgbaPixels, int sourceWidth, int sourceHeight)
|
||||||
|
{
|
||||||
|
if (sourceWidth == _targetWidth && sourceHeight == _targetHeight)
|
||||||
|
return rgbaPixels;
|
||||||
|
|
||||||
|
byte[] resizedPixels;
|
||||||
|
using (var image = Image.LoadPixelData<Rgba32>(rgbaPixels, sourceWidth, sourceHeight))
|
||||||
|
{
|
||||||
|
image.Mutate(ctx => ctx.Resize(_targetWidth, _targetHeight));
|
||||||
|
|
||||||
|
resizedPixels = new byte[_targetWidth * _targetHeight * 4];
|
||||||
|
image.CopyPixelDataTo(resizedPixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resizedPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private (int Width, int Height) CombineImage()
|
private (int Width, int Height) CombineImage()
|
||||||
{
|
{
|
||||||
var combineOp = GetActualCombineOp();
|
var combineOp = GetActualCombineOp();
|
||||||
var (width, height) = combineOp is not CombineOp.Invalid or CombineOp.RightCopy or CombineOp.RightMultiply
|
var (usesLeft, usesRight) = GetCombineOpFlags(combineOp);
|
||||||
|
var resizeOp = usesLeft && usesRight ? _resizeOp : ResizeOp.None;
|
||||||
|
(_targetWidth, _targetHeight) = usesLeft && resizeOp != ResizeOp.ToRight
|
||||||
? (_left.TextureWrap!.Width, _left.TextureWrap!.Height)
|
? (_left.TextureWrap!.Width, _left.TextureWrap!.Height)
|
||||||
: (_right.TextureWrap!.Width, _right.TextureWrap!.Height);
|
: (_right.TextureWrap!.Width, _right.TextureWrap!.Height);
|
||||||
_centerStorage.RgbaPixels = new byte[width * height * 4];
|
_centerStorage.RgbaPixels = new byte[_targetWidth * _targetHeight * 4];
|
||||||
_centerStorage.Type = TextureType.Bitmap;
|
_centerStorage.Type = TextureType.Bitmap;
|
||||||
Parallel.For(0, height, combineOp switch
|
try
|
||||||
{
|
{
|
||||||
CombineOp.Over => AddPixelsMultiplied,
|
if (usesLeft)
|
||||||
CombineOp.Under => ReverseAddPixelsMultiplied,
|
_leftPixels = (resizeOp == ResizeOp.ToRight) ? ResizePixels(_left.RgbaPixels, _left.TextureWrap!.Width, _left.TextureWrap!.Height) : _left.RgbaPixels;
|
||||||
CombineOp.LeftMultiply => MultiplyPixelsLeft,
|
if (usesRight)
|
||||||
CombineOp.RightMultiply => MultiplyPixelsRight,
|
(_rightWidth, _rightHeight, _rightPixels) = (resizeOp == ResizeOp.ToLeft)
|
||||||
CombineOp.CopyChannels => ChannelMergePixelsMultiplied,
|
? (_targetWidth, _targetHeight, ResizePixels(_right.RgbaPixels, _right.TextureWrap!.Width, _right.TextureWrap!.Height))
|
||||||
_ => throw new InvalidOperationException($"Cannot combine images with operation {combineOp}"),
|
: (_right.TextureWrap!.Width, _right.TextureWrap!.Height, _right.RgbaPixels);
|
||||||
});
|
Parallel.For(0, _targetHeight, 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 = Array.Empty<byte>();
|
||||||
|
_rightPixels = Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
|
||||||
return (width, height);
|
return (_targetWidth, _targetHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector4 CappedVector(IReadOnlyList<byte> bytes, int offset, Matrix4x4 transform, Vector4 constant)
|
private static Vector4 CappedVector(IReadOnlyList<byte> bytes, int offset, Matrix4x4 transform, Vector4 constant)
|
||||||
|
|
@ -266,6 +336,7 @@ public partial class CombinedTexture
|
||||||
{
|
{
|
||||||
var ret = DrawMatrixInput(ref _multiplierRight, ref _constantRight, width);
|
var ret = DrawMatrixInput(ref _multiplierRight, ref _constantRight, width);
|
||||||
ret |= DrawMatrixTools(ref _multiplierRight, ref _constantRight);
|
ret |= DrawMatrixTools(ref _multiplierRight, ref _constantRight);
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(75.0f * UiHelpers.Scale);
|
ImGui.SetNextItemWidth(75.0f * UiHelpers.Scale);
|
||||||
ImGui.DragInt("##XOffset", ref _offsetX, 0.5f);
|
ImGui.DragInt("##XOffset", ref _offsetX, 0.5f);
|
||||||
ret |= ImGui.IsItemDeactivatedAfterEdit();
|
ret |= ImGui.IsItemDeactivatedAfterEdit();
|
||||||
|
|
@ -273,6 +344,7 @@ public partial class CombinedTexture
|
||||||
ImGui.SetNextItemWidth(75.0f * UiHelpers.Scale);
|
ImGui.SetNextItemWidth(75.0f * UiHelpers.Scale);
|
||||||
ImGui.DragInt("Offsets##YOffset", ref _offsetY, 0.5f);
|
ImGui.DragInt("Offsets##YOffset", ref _offsetY, 0.5f);
|
||||||
ret |= ImGui.IsItemDeactivatedAfterEdit();
|
ret |= ImGui.IsItemDeactivatedAfterEdit();
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(200.0f * UiHelpers.Scale);
|
ImGui.SetNextItemWidth(200.0f * UiHelpers.Scale);
|
||||||
using (var c = ImRaii.Combo("Combine Operation", CombineOpLabels[(int)_combineOp]))
|
using (var c = ImRaii.Combo("Combine Operation", CombineOpLabels[(int)_combineOp]))
|
||||||
{
|
{
|
||||||
|
|
@ -292,6 +364,13 @@ public partial class CombinedTexture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (usesLeft, usesRight) = GetCombineOpFlags(_combineOp);
|
||||||
|
using (var dis = ImRaii.Disabled(!usesLeft || !usesRight))
|
||||||
|
{
|
||||||
|
ret |= ImGuiUtil.GenericEnumCombo("Resizing Mode", 200.0f * UiHelpers.Scale, _resizeOp, out _resizeOp,
|
||||||
|
Enum.GetValues<ResizeOp>(), op => ResizeOpLabels[(int)op]);
|
||||||
|
}
|
||||||
|
|
||||||
using (var dis = ImRaii.Disabled(_combineOp != CombineOp.CopyChannels))
|
using (var dis = ImRaii.Disabled(_combineOp != CombineOp.CopyChannels))
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Copy");
|
ImGui.TextUnformatted("Copy");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue