diff --git a/PDGServer_WPF/MainWindow.xaml b/PDGServer_WPF/MainWindow.xaml index 31cbd2a..2f568ef 100644 --- a/PDGServer_WPF/MainWindow.xaml +++ b/PDGServer_WPF/MainWindow.xaml @@ -42,13 +42,28 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/PDGServer_WPF/MainWindow.xaml.cs b/PDGServer_WPF/MainWindow.xaml.cs index 32e2fa0..0300c5e 100644 --- a/PDGServer_WPF/MainWindow.xaml.cs +++ b/PDGServer_WPF/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using Microsoft.Win32; +using RBG.Helpers; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -72,9 +73,7 @@ namespace RBG_Server.WPF catch (Exception e) { Console.WriteLine(e); - } - } /// /// Creates and starts the game server @@ -154,22 +153,17 @@ namespace RBG_Server.WPF } /// - /// Generates elements to go inside the game grid from the provided list of players + /// Generates the game board overlay grid /// The grid should be appropriately sized before using this /// private void GenerateGameBoard(List players) { - SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); - Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count]; + // Create an array of BoardCells Rows*Columns in size + BoardCell[] cells = new BoardCell[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count]; List unaddedPlayers = players; for (int i = 0; i < cells.Length; i++) { - cells[i] = new Grid(); - // Each cell has several components: - // Stackpanel, which has each sprite in the cell - UniformGrid cellStack = new(); - cellStack.Columns = 4; - //cellStack.ItemWidth = cellStack.Width / 4; + cells[i] = new BoardCell(); Queue removedPlayers = new(); foreach (Player item in unaddedPlayers) { @@ -182,50 +176,66 @@ namespace RBG_Server.WPF { Player removed = removedPlayers.Dequeue(); _ = unaddedPlayers.Remove(removed); - _ = cellStack.Children.Add(removed); + _ = cells[i].CellStack.Children.Add(removed); } - _ = cells[i].Children.Add(cellStack); - // Button, whose content is empty - Button cellButton = new() - { - Content = "" - }; - cellButton.Background = buttonBrush; - cellButton.Tag = new Tuple(i / PreviewImageOverlay.ColumnDefinitions.Count, i % PreviewImageOverlay.ColumnDefinitions.Count); // Tag is a Tuple - cellButton.Click += CellButton_Click; - _ = cells[i].Children.Add(cellButton); _ = PreviewImageOverlay.Children.Add(cells[i]); + cells[i].CellButton.Tag = new Tuple(i % PreviewImageOverlay.ColumnDefinitions.Count, i / PreviewImageOverlay.ColumnDefinitions.Count); Grid.SetColumn(cells[i], i % PreviewImageOverlay.ColumnDefinitions.Count); Grid.SetRow(cells[i], i / PreviewImageOverlay.ColumnDefinitions.Count); } } /// - /// Represents a cell on the board + /// Represents a cell on the board; as a grid /// - class BoardCell + class BoardCell : Grid { // brush the button should have + static SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); + // Each cell has a uniform grid that - UniformGrid cellStack = new() + public UniformGrid CellStack { get; } = new() { Columns = 4, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch }; - Button cellButton = new() + public Button CellButton { get; } = new() { - Content = "" + Content = "", }; Queue removedPlayers = new(); - Grid parentGrid; + // Grid parentGrid; // Parent grid is now this itself - public BoardCell(Grid parentGrid) + public BoardCell() : base() { - this.parentGrid = parentGrid; + CellButton.MouseEnter += CellButton_MouseEnter; + CellButton.MouseLeave += CellButton_MouseLeave; + CellButton.Click += CellButton_Click; + CellButton.Background = buttonBrush; + CellStack.Columns = 2; + CellStack.VerticalAlignment = VerticalAlignment.Top; + // Add our grid and button + _ = Children.Add(CellStack); + _ = Children.Add(CellButton); } + private void CellButton_Click(object sender, RoutedEventArgs e) + { + Button cellButton = sender as Button; + System.Diagnostics.Debug.WriteLine("Button at {0}, {1} was clicked", ((Tuple)cellButton.Tag).Item2, ((Tuple)cellButton.Tag).Item1); + } + + private void CellButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e) + { + //throw new NotImplementedException(); + } + + private void CellButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) + { + //throw new NotImplementedException(); + } } private void GenerateNewGameBoard(List players) @@ -239,11 +249,7 @@ namespace RBG_Server.WPF } } - private void CellButton_Click(object sender, RoutedEventArgs e) - { - Button cellButton = sender as Button; - Console.WriteLine("Button at {0}, {1} was clicked", ((Tuple)cellButton.Tag).Item2, ((Tuple)cellButton.Tag).Item1); - } + private void Sliders_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { @@ -275,26 +281,68 @@ namespace RBG_Server.WPF // Create a new player at a random location Player p = new("Player " + i, rand.Next(0, PreviewImageOverlay.RowDefinitions.Count), rand.Next(PreviewImageOverlay.ColumnDefinitions.Count)); // Load the sprite - BitmapSource pSprite = new BitmapImage(new Uri("pack://application:,,,/Sprites/" + sprites[rand.Next(0, sprites.Length)])); - // Recolour: - if (rand.Next(1, 4) == 1) + string[] keys = new string[loadedSprites.Count]; + loadedSprites.Keys.CopyTo(keys, 0); + string key = keys[rand.Next(keys.Length)]; + if (key.ToLower().EndsWith(".gif")) { - pSprite = RBG.Helpers.ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0); + try + { + AnimatedBitmapImage animatedBitmap = new(new MemoryStream(loadedSprites[key]), p.PlayerSprite, Application.Current.Dispatcher); + } + catch + { + BitmapSource pSprite; + { + // Scope & create the base data for pSprite + BitmapImage src = new(); + src.BeginInit(); + + src.StreamSource = new MemoryStream(loadedSprites[key]); + src.EndInit(); + pSprite = src; + } + // Recolour: + if (rand.Next(1, 4) == 1) + { + pSprite = ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0); + } + p.PlayerSprite.Source = pSprite; + + } } - p.Source = pSprite; + else + { + BitmapSource pSprite; + { + // Scope & create the base data for pSprite + BitmapImage src = new(); + src.BeginInit(); + src.StreamSource = new MemoryStream(loadedSprites[key]); + src.EndInit(); + pSprite = src; + } + // Recolour: + if (rand.Next(1, 4) == 1) + { + pSprite = ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0); + } + p.PlayerSprite.Source = pSprite; + } + randPlayers.Add(p); } GenerateGameBoard(randPlayers); } - ConcurrentDictionary loadedSprites = new(); + ConcurrentDictionary loadedSprites = new(); Task loadingFiles; private void GameSpritesBrowser_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new(); - ofd.Filter = "Image Files|*.png;*.jpg"; + ofd.Filter = "Image Files|*.png;*.jpg;*.gif"; ofd.Multiselect = true; if (ofd.ShowDialog() == true) { @@ -327,23 +375,57 @@ namespace RBG_Server.WPF { fs.CopyTo(ms); } - _ = loadedSprites.TryAdd(file, ms); + _ = loadedSprites.TryAdd(file, ms.ToArray()); }); // Invoke the dispatcher to add all of the sprites to the loaded sprites list _ = Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() => { LoadedSprites.Children.Clear(); - foreach (KeyValuePair item in loadedSprites) + foreach (KeyValuePair item in loadedSprites) { - BitmapImage image = new(); - image.BeginInit(); - image.StreamSource = item.Value; - image.EndInit(); - Image spriteImage = new() + if (item.Key.ToLower().EndsWith(".gif")) { - Source = image - }; - _ = LoadedSprites.Children.Add(spriteImage); + try + { + /* + // Must write-out the stream to + MediaElement gifMedia = new(); + gifMedia.LoadedBehavior = MediaState.Play; + gifMedia.Source = new Uri(item.Key); + _ = LoadedSprites.Children.Add(gifMedia); + */ + Image spriteImage = new(); + RBG.Helpers.AnimatedBitmapImage animatedBitmap = new(new MemoryStream(item.Value), spriteImage, Application.Current.Dispatcher); + _ = LoadedSprites.Children.Add(spriteImage); + } + catch + { + // On failed, use the cached data + BitmapImage image = new(); + image.BeginInit(); + image.StreamSource = new MemoryStream(item.Value); + image.EndInit(); + Image spriteImage = new() + { + Source = image + }; + _ = LoadedSprites.Children.Add(spriteImage); + + } + } + else + { + + BitmapImage image = new(); + image.BeginInit(); + image.StreamSource = new MemoryStream(item.Value); + image.EndInit(); + Image spriteImage = new() + { + Source = image + }; + _ = LoadedSprites.Children.Add(spriteImage); + } } })); }); diff --git a/RBG.Helpers/ImageProcessing.cs b/RBG.Helpers/ImageProcessing.cs index 3f88666..09a2444 100644 --- a/RBG.Helpers/ImageProcessing.cs +++ b/RBG.Helpers/ImageProcessing.cs @@ -1,6 +1,12 @@ using System; using System.Windows; using System.Windows.Media.Imaging; +using System.Drawing.Imaging; +using System.IO; +using System.Threading; +using System.Windows.Controls; +using System.Windows.Threading; +using System.Collections.Generic; namespace RBG.Helpers { @@ -83,4 +89,47 @@ namespace RBG.Helpers } + + public class AnimatedBitmapImage + { + public BitmapSource BaseImage { get; set; } + BitmapFrame[] frames; + int framerate; + int currentFrame; + Timer frameChange; + public AnimatedBitmapImage(Stream source, Image target, Dispatcher dispatcher) + { + GifBitmapDecoder decoder = new(source, BitmapCreateOptions.None, BitmapCacheOption.Default); + frames = new BitmapFrame[decoder.Frames.Count]; + decoder.Frames.CopyTo(frames, 0); + + byte[] frameratebytes = new byte[2]; + source.Position = 0x324; + frameratebytes[0] = (byte)source.ReadByte(); + frameratebytes[1] = (byte)source.ReadByte(); + framerate = 33; + + BaseImage = frames[0]; + // cycle through frames + frameChange = new Timer((t) => + { + currentFrame++; + currentFrame %= frames.Length; + BaseImage = frames[currentFrame]; + _ = dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() => + { + try + { + target.Source = BaseImage; + } + catch + { + + } + + })); + },null, framerate, framerate); + } + } + } diff --git a/RBG.Helpers/RBG.Helpers.csproj b/RBG.Helpers/RBG.Helpers.csproj index 29f3391..72f7ba3 100644 --- a/RBG.Helpers/RBG.Helpers.csproj +++ b/RBG.Helpers/RBG.Helpers.csproj @@ -1,7 +1,7 @@ - net5.0 + net5.0-windows10.0.19041.0 diff --git a/RBG_Server.Core/Player.cs b/RBG_Server.Core/Player.cs index 56052c9..73c42fa 100644 --- a/RBG_Server.Core/Player.cs +++ b/RBG_Server.Core/Player.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; namespace RBG_Server { @@ -11,8 +13,18 @@ namespace RBG_Server /// Data class containing information about a player. Drawn directly to the screen, hence the inheritance /// of Image, which allows this entire object to be /// - public class Player : Image + public class Player : Grid { + static GradientStopCollection gradientStops = new(5) + { + new GradientStop(Color.FromArgb(255, 0, 255, 255), 0), + new GradientStop(Color.FromArgb(192, 0, 255, 255), 0.75), + new GradientStop(Color.FromArgb(128, 0, 255, 255), 0.85), + new GradientStop(Color.FromArgb(32, 0, 255, 255), 0.95), + new GradientStop(Color.FromArgb(0, 0, 255, 255), 1) + }; + static RadialGradientBrush shadowBrush = new(gradientStops); + // C# uses implicit field definitions; i.e. declaring a property creates an implicit private field // Note that it is also possible to assign values, and specify access modifiers for the get & set independantly public string PlayerName { get; set; } @@ -21,6 +33,10 @@ namespace RBG_Server public long LastTime { get; set; } = DateTime.Now.Ticks; public bool Connected { get; private set; } public byte[] UnhandledBuffer { get; set; } + + public Image PlayerSprite { get; set; } = new(); + private Rectangle spriteShadow = new(); + private int processing; public int ObtainLock() @@ -43,6 +59,10 @@ namespace RBG_Server LastTime = DateTime.Now.Ticks; Connected = true; UnhandledBuffer = Array.Empty(); + + spriteShadow.Fill = shadowBrush; + Children.Add(spriteShadow); + Children.Add(PlayerSprite); } public new bool Equals(object obj)