359 lines
13 KiB
C#
359 lines
13 KiB
C#
|
using Matrix_App.PregeneratedMods;
|
|||
|
using System;
|
|||
|
using System.Diagnostics;
|
|||
|
using System.Drawing;
|
|||
|
using System.Reflection;
|
|||
|
using System.Threading;
|
|||
|
using System.Windows.Forms;
|
|||
|
using Matrix_App.minecraft;
|
|||
|
using Matrix_App.PregeneratedMods.reflection;
|
|||
|
using static Matrix_App.Utils;
|
|||
|
using static Matrix_App.Defaults;
|
|||
|
using Timer = System.Windows.Forms.Timer;
|
|||
|
|
|||
|
namespace Matrix_App
|
|||
|
{
|
|||
|
public abstract class MatrixGifGenerator
|
|||
|
{
|
|||
|
private static readonly MatrixGifGenerator[] Generators =
|
|||
|
{
|
|||
|
new SimpleRainbow(),
|
|||
|
new Rain(),
|
|||
|
new Spiral(),
|
|||
|
new UvGrid(),
|
|||
|
new RandomPixels(),
|
|||
|
new Boxblur(),
|
|||
|
new ColorAdjust(),
|
|||
|
new Grayscale(),
|
|||
|
new Invert(),
|
|||
|
new Transfom(),
|
|||
|
new Minecraft()
|
|||
|
};
|
|||
|
|
|||
|
// Static generator accessible members
|
|||
|
// must work on multiple threads
|
|||
|
protected static int totalFrames; // total amount of frames to generate
|
|||
|
protected static byte[][]? actualStore; // image copy of previous GIF for generator
|
|||
|
|
|||
|
protected static int width;
|
|||
|
protected static int height;
|
|||
|
|
|||
|
// updates the preview matrix for animation
|
|||
|
private static readonly Timer PlaybackTimer = new Timer();
|
|||
|
// current frame to play
|
|||
|
private static int _playbackFrame;
|
|||
|
|
|||
|
// temporary buffer for storing snapshots for buttons
|
|||
|
private static readonly byte[][] Snapshot;
|
|||
|
private static byte[][] _initialBuffer; // temporary buffer for swapping
|
|||
|
|
|||
|
// Generator renderer
|
|||
|
private static readonly ThreadQueue Renderer;
|
|||
|
|
|||
|
// Current generator to use
|
|||
|
private static MatrixGifGenerator? _generator;
|
|||
|
|
|||
|
static MatrixGifGenerator()
|
|||
|
{
|
|||
|
PlaybackTimer.Tick += PlaybackFrame;
|
|||
|
|
|||
|
// Generate buffer for button filter snapshots
|
|||
|
Snapshot = CreateImageRGB_NT(FilterPreviewWidth, FilterPreviewHeight, 1);
|
|||
|
_initialBuffer = CreateImageRGB_NT(FilterPreviewWidth, FilterPreviewHeight, 1);
|
|||
|
|
|||
|
Renderer = new ThreadQueue("Matrix Gif Renderer", 2);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Plays the next frame of what is currently in the animation buffer
|
|||
|
/// </summary>
|
|||
|
/// <param name="sender"></param>
|
|||
|
/// <param name="e"></param>
|
|||
|
private static void PlaybackFrame(object? sender, EventArgs e)
|
|||
|
{
|
|||
|
if (_playbackFrame >= _animationBuffer.Length - 1)
|
|||
|
{
|
|||
|
_playbackFrame = 0;
|
|||
|
}
|
|||
|
|
|||
|
_preview.SetImage(_animationBuffer[_playbackFrame]);
|
|||
|
|
|||
|
_playbackFrame++;
|
|||
|
}
|
|||
|
|
|||
|
public delegate void update();
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Colors a single fragment at the specified pixel location (x|y) at frame frame.
|
|||
|
/// </summary>
|
|||
|
/// <param name="x"></param>
|
|||
|
/// <param name="y"></param>
|
|||
|
/// <param name="u">Normalized pixel X-coordinate</param>
|
|||
|
/// <param name="v">Normalized pixel Y-coordinate</param>
|
|||
|
/// <param name="frame">Current frame</param>
|
|||
|
/// <param name="r">Pixel Red value in range [0, 1] (saturated)</param>
|
|||
|
/// <param name="g">Pixel Green value in range [0, 1] (saturated)</param>
|
|||
|
/// <param name="b">Pixel Blue value in range [0, 1] (saturated)</param>
|
|||
|
protected abstract void ColorFragment(in int x, in int y, in float u, in float v, in int frame, out float r, out float g, out float b);
|
|||
|
|
|||
|
protected abstract void CreateUi(FlowLayoutPanel anchor, update runner);
|
|||
|
|
|||
|
// Buffer to store generator result in
|
|||
|
private static byte[][] _animationBuffer = null!;
|
|||
|
|
|||
|
// Main application reference
|
|||
|
private static MatrixDesignerMain _form = null!;
|
|||
|
private static Matrix _preview = null!; // preview matrix
|
|||
|
|
|||
|
public static void GenerateBaseUi(FlowLayoutPanel anchor, Matrix matrix, MatrixDesignerMain form1)
|
|||
|
{
|
|||
|
_form = form1;
|
|||
|
|
|||
|
// generate access buttons for available generators
|
|||
|
foreach (var generator in Generators)
|
|||
|
{
|
|||
|
// generate button
|
|||
|
var button = new Button
|
|||
|
{
|
|||
|
AutoSize = true,
|
|||
|
Text = FieldWidgets.GetBetterFieldName(generator.GetType().Name)
|
|||
|
};
|
|||
|
button.Width = anchor.ClientSize.Width - button.Margin.Right - button.Margin.Left;
|
|||
|
button.Click += (sender, e) => OpenGeneratorUi(generator, matrix);
|
|||
|
button.Image = CreateSnapshot(generator);
|
|||
|
button.TextImageRelation = TextImageRelation.ImageBeforeText;
|
|||
|
button.TextAlign = ContentAlignment.MiddleRight;
|
|||
|
button.ImageAlign = ContentAlignment.MiddleLeft;
|
|||
|
|
|||
|
anchor.Controls.Add(button);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static Image CreateSnapshot(MatrixGifGenerator matrixGifGenerator)
|
|||
|
{
|
|||
|
_generator = new RandomPixels();
|
|||
|
// put some random pixels in as default initial image to operate on for filter
|
|||
|
SetGlobalArgs(FilterPreviewWidth, FilterPreviewHeight, 1, null, _initialBuffer);
|
|||
|
InvokeGenerator();
|
|||
|
|
|||
|
BlockBuffer();
|
|||
|
|
|||
|
_generator = matrixGifGenerator;
|
|||
|
|
|||
|
// render filter
|
|||
|
SetGlobalArgs(FilterPreviewWidth, FilterPreviewHeight, 1, _initialBuffer, Snapshot);
|
|||
|
InvokeGenerator();
|
|||
|
|
|||
|
BlockBuffer();
|
|||
|
|
|||
|
// convert to bitmap
|
|||
|
return ImageWrap(Snapshot[0], width, height);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Blocks this thread until no more work is done by the Renderer thread queue
|
|||
|
/// </summary>
|
|||
|
private static void BlockBuffer()
|
|||
|
{
|
|||
|
while (Renderer.HasWork())
|
|||
|
{
|
|||
|
Thread.Sleep(50);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static void OpenGeneratorUi(MatrixGifGenerator matrixGifGenerator, Matrix matrix)
|
|||
|
{
|
|||
|
_generator = matrixGifGenerator;
|
|||
|
|
|||
|
if (!ShowEditDialog(matrix))
|
|||
|
return;
|
|||
|
|
|||
|
if (Renderer.HasWork())
|
|||
|
{
|
|||
|
if (DialogResult.Yes ==
|
|||
|
MessageBox.Show($@"The filter {_generator.GetType().Name} hasn't finished yet, wait for completion?",
|
|||
|
@"Filter incomplete", MessageBoxButtons.YesNo, MessageBoxIcon.Warning))
|
|||
|
{
|
|||
|
BlockBuffer();
|
|||
|
ColorStore(_animationBuffer, MatrixDesignerMain.gifBuffer);
|
|||
|
_form.ResetTimeline();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
MessageBox.Show($@"The filter {_generator.GetType().Name} has timedout", @"Failed applying filter", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ColorStore(_animationBuffer, MatrixDesignerMain.gifBuffer);
|
|||
|
_form.ResetTimeline();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static void SetGlobalArgs(int w, int h, int f, in byte[][]? previous, in byte[][] preview)
|
|||
|
{
|
|||
|
totalFrames = f;
|
|||
|
|
|||
|
width = w;
|
|||
|
height = h;
|
|||
|
|
|||
|
_animationBuffer = preview;
|
|||
|
actualStore = previous;
|
|||
|
}
|
|||
|
|
|||
|
private static bool ShowEditDialog(Matrix matrix)
|
|||
|
{
|
|||
|
if (_generator == null)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
var success = false;
|
|||
|
|
|||
|
Initialize(matrix);
|
|||
|
|
|||
|
Form prompt = new Form
|
|||
|
{
|
|||
|
AutoSize = true,
|
|||
|
AutoSizeMode = AutoSizeMode.GrowOnly,
|
|||
|
Text = @"Vorgenerierter Modus: " + _generator.GetType().Name
|
|||
|
};
|
|||
|
|
|||
|
var confirmation = new Button {Text = @"Apply", Anchor = AnchorStyles.Top | AnchorStyles.Left};
|
|||
|
confirmation.Click += (sender, e) => {
|
|||
|
success = true;
|
|||
|
prompt.Close();
|
|||
|
};
|
|||
|
|
|||
|
FlowLayoutPanel controlPanel = new FlowLayoutPanel
|
|||
|
{
|
|||
|
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
|||
|
FlowDirection = FlowDirection.BottomUp,
|
|||
|
Dock = DockStyle.Fill,
|
|||
|
WrapContents = false,
|
|||
|
AutoSizeMode = AutoSizeMode.GrowOnly,
|
|||
|
AutoSize = true
|
|||
|
};
|
|||
|
|
|||
|
_generator.CreateUi(controlPanel, InvokeGenerator);
|
|||
|
|
|||
|
PlaybackTimer.Interval = _form.GetDelayTime();
|
|||
|
PlaybackTimer.Enabled = true;
|
|||
|
|
|||
|
var type = _generator.GetType();
|
|||
|
var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|||
|
|
|||
|
CreateDivider(controlPanel, 2);
|
|||
|
foreach (var field in fields)
|
|||
|
{
|
|||
|
var widget = FieldWidgets.GetFieldWidget(field, _generator, InvokeGenerator);
|
|||
|
|
|||
|
if (widget == null)
|
|||
|
continue;
|
|||
|
|
|||
|
controlPanel.Controls.AddRange(widget);
|
|||
|
CreateDivider(controlPanel, 1);
|
|||
|
}
|
|||
|
|
|||
|
controlPanel.Controls.Add(_preview);
|
|||
|
|
|||
|
FlowLayoutPanel southPane = new FlowLayoutPanel
|
|||
|
{
|
|||
|
Dock = DockStyle.Bottom,
|
|||
|
Anchor = AnchorStyles.Top | AnchorStyles.Left
|
|||
|
};
|
|||
|
southPane.Controls.Add(confirmation);
|
|||
|
southPane.AutoSize = true;
|
|||
|
|
|||
|
// render once
|
|||
|
InvokeGenerator();
|
|||
|
|
|||
|
prompt.MinimumSize = prompt.Size;
|
|||
|
prompt.Controls.Add(controlPanel);
|
|||
|
prompt.Controls.Add(southPane);
|
|||
|
prompt.MaximizeBox = false;
|
|||
|
prompt.FormBorderStyle = FormBorderStyle.FixedDialog;
|
|||
|
prompt.StartPosition = FormStartPosition.CenterScreen;
|
|||
|
prompt.ShowDialog();
|
|||
|
|
|||
|
PlaybackTimer.Enabled = false;
|
|||
|
|
|||
|
return success;
|
|||
|
}
|
|||
|
|
|||
|
private static void Initialize(in Matrix matrix)
|
|||
|
{
|
|||
|
// Create new initial buffer and copy what ever was in the Gif buffer to it
|
|||
|
_initialBuffer = CreateImageRGB_NT(matrix.matrixWidth(), matrix.matrixHeight(), MatrixDesignerMain.gifBuffer.Length);
|
|||
|
ColorStore(MatrixDesignerMain.gifBuffer, _initialBuffer);
|
|||
|
// Set Generator args
|
|||
|
SetGlobalArgs(matrix.matrixWidth(),
|
|||
|
matrix.matrixHeight(),
|
|||
|
MatrixDesignerMain.gifBuffer.Length - 1,
|
|||
|
_initialBuffer,
|
|||
|
CreateImageRGB_NT(matrix.matrixWidth(), matrix.matrixHeight(), MatrixDesignerMain.gifBuffer.Length)
|
|||
|
);
|
|||
|
|
|||
|
// Create preview matrix
|
|||
|
_preview = new Matrix();
|
|||
|
_preview.SetEditable(false);
|
|||
|
_preview.Anchor = AnchorStyles.Top | AnchorStyles.Left;
|
|||
|
_preview.Size = new Size(480, 200);
|
|||
|
_preview.resize(matrix.matrixWidth(), matrix.matrixHeight());
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds a separating line to the controls
|
|||
|
/// </summary>
|
|||
|
/// <param name="controlPanel"></param>
|
|||
|
/// <param name="lineHeight"></param>
|
|||
|
private static void CreateDivider(Control controlPanel, int lineHeight)
|
|||
|
{
|
|||
|
var divider = new Label
|
|||
|
{
|
|||
|
BorderStyle = BorderStyle.Fixed3D,
|
|||
|
AutoSize = false,
|
|||
|
Dock = DockStyle.Fill,
|
|||
|
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
|||
|
Height = lineHeight,
|
|||
|
};
|
|||
|
|
|||
|
controlPanel.Controls.Add(divider);
|
|||
|
}
|
|||
|
|
|||
|
private static void InvokeGenerator()
|
|||
|
{
|
|||
|
Renderer.Enqueue(delegate
|
|||
|
{
|
|||
|
for (var frame = 0; frame < _animationBuffer.Length; frame++)
|
|||
|
{
|
|||
|
for (var x = 0; x < width; x++)
|
|||
|
{
|
|||
|
var u = x / (float) width;
|
|||
|
|
|||
|
for (var y = 0; y < height; y++)
|
|||
|
{
|
|||
|
var v = y / (float) height;
|
|||
|
|
|||
|
_generator!.ColorFragment(x, y, u, v, frame, out var r, out var g, out var b);
|
|||
|
|
|||
|
var index = (x + y * width) * 3;
|
|||
|
|
|||
|
_animationBuffer[frame][index] = (byte) Math.Clamp( (int)(r * 255), 0, 255);
|
|||
|
_animationBuffer[frame][index + 1] = (byte) Math.Clamp( (int)(g * 255), 0, 255);
|
|||
|
_animationBuffer[frame][index + 2] = (byte) Math.Clamp( (int)(b * 255), 0, 255);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
public static void Close()
|
|||
|
{
|
|||
|
Renderer.Stop();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|