From f12c69b5599a4b1d8aa399e6eb41928ed48cdbb8 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 5 Aug 2021 01:10:43 -0500 Subject: [PATCH 1/4] Start on AnimatedTexture, start GIF reading --- PathfinderAPI/GUI/AnimatedTexture.cs | 102 ++++++++++++++++++++++++++ PathfinderAPI/GUI/PFButton.cs | 2 +- PathfinderAPI/PathfinderAPI.csproj | 2 + PathfinderAPI/Util/LZWBinaryReader.cs | 19 +++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 PathfinderAPI/GUI/AnimatedTexture.cs create mode 100644 PathfinderAPI/Util/LZWBinaryReader.cs diff --git a/PathfinderAPI/GUI/AnimatedTexture.cs b/PathfinderAPI/GUI/AnimatedTexture.cs new file mode 100644 index 00000000..a5420982 --- /dev/null +++ b/PathfinderAPI/GUI/AnimatedTexture.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Pathfinder.GUI +{ + public class AnimatedTexture : IDisposable + { + public Texture2D[] Frames { get; } + public int Framerate { get; set; } + private float frameTime; + public bool ShouldLoop { get; set; } + public Rectangle DestRect { get; set; } + public bool Paused { get; set; } + + public AnimatedTexture(IEnumerable frames, Rectangle dest, int framerate, bool loop = false, bool startPaused = false) + { + Frames = frames?.ToArray() ?? throw new ArgumentNullException(nameof(frames)); + DestRect = dest; + Framerate = framerate; + frameTime = 1f / framerate; + ShouldLoop = loop; + Paused = startPaused; + } + + private float timeSinceLastFrame = 0f; + private int currentFrame = 0; + public void Draw(float t, SpriteBatch spriteBatch) + { + if (Paused) + return; + + timeSinceLastFrame += t; + if (timeSinceLastFrame > frameTime) + { + currentFrame++; + timeSinceLastFrame = 0f; + } + + if (currentFrame < 0 || currentFrame >= Frames.Length) + { + currentFrame = 0; + Paused = !ShouldLoop; + if (Paused) + return; + } + + spriteBatch.Draw(Frames[0], DestRect, Color.White); + } + + public static AnimatedTexture ReadFromGIF(string filename) + { + var stream = new FileStream(filename, FileMode.Open); + var ret = ReadFromGIF(stream); + stream.Dispose(); + return ret; + } + + public static AnimatedTexture ReadFromGIF(Stream stream, int x = 0, int y = 0) + { + var frameList = new List(); + ushort width; + ushort height; + using (var r = new BinaryReader(stream)) + { + var magic = r.ReadBytes(6); + if (!magic.SequenceEqual(new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 })) + throw new NotSupportedException("GIF magic does not match!"); + + width = r.ReadUInt16(); + height = r.ReadUInt16(); + + byte tableInfo = r.ReadByte(); + int tableSize = tableInfo >> 4; + + int bitDepth = (tableInfo & 0x08) + 1; + + int backgroundIndex = r.ReadByte(); + r.ReadByte(); // ratio, i dont wanna bother + + Color[] colorTable = new Color[tableSize]; + + int offset = 0; + for (int i = 0; i <= tableSize; i++) + { + + } + } + + return null; + } + + public void Dispose() + { + foreach (var frame in Frames) + frame.Dispose(); + } + } +} diff --git a/PathfinderAPI/GUI/PFButton.cs b/PathfinderAPI/GUI/PFButton.cs index a0f0dbd9..a76716fd 100644 --- a/PathfinderAPI/GUI/PFButton.cs +++ b/PathfinderAPI/GUI/PFButton.cs @@ -60,7 +60,7 @@ public bool Do() public void Dispose() { - returnedIds.Add(ID); + ReturnID(ID); invalid = true; } } diff --git a/PathfinderAPI/PathfinderAPI.csproj b/PathfinderAPI/PathfinderAPI.csproj index 473fd552..08154ccb 100644 --- a/PathfinderAPI/PathfinderAPI.csproj +++ b/PathfinderAPI/PathfinderAPI.csproj @@ -102,6 +102,7 @@ + @@ -121,6 +122,7 @@ + diff --git a/PathfinderAPI/Util/LZWBinaryReader.cs b/PathfinderAPI/Util/LZWBinaryReader.cs new file mode 100644 index 00000000..0dbf5201 --- /dev/null +++ b/PathfinderAPI/Util/LZWBinaryReader.cs @@ -0,0 +1,19 @@ +using System.IO; + +namespace Pathfinder.Util +{ + public class LZWBinaryReader + { + public BinaryReader Reader { get; } + + public LZWBinaryReader(BinaryReader reader) + { + Reader = reader; + } + + public byte[] ReadBytes(int length) + { + return null; + } + } +} From c76c2cb81cc6283b7bc8f7ae48677fcf5c5d8dde Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sun, 8 Aug 2021 03:46:26 -0500 Subject: [PATCH 2/4] Switch out GIFs for APNGs, currently have required metadata done --- PathfinderAPI/GUI/AnimatedTexture.cs | 106 +++++++++++++++----- PathfinderAPI/PathfinderAPI.csproj | 3 +- PathfinderAPI/Util/BitReader.cs | 76 +++++++++++++++ PathfinderAPI/Util/LZWBinaryReader.cs | 19 ---- PathfinderAPI/Util/SBAwareBinaryReader.cs | 112 ++++++++++++++++++++++ 5 files changed, 272 insertions(+), 44 deletions(-) create mode 100644 PathfinderAPI/Util/BitReader.cs delete mode 100644 PathfinderAPI/Util/LZWBinaryReader.cs create mode 100644 PathfinderAPI/Util/SBAwareBinaryReader.cs diff --git a/PathfinderAPI/GUI/AnimatedTexture.cs b/PathfinderAPI/GUI/AnimatedTexture.cs index a5420982..107fc136 100644 --- a/PathfinderAPI/GUI/AnimatedTexture.cs +++ b/PathfinderAPI/GUI/AnimatedTexture.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Linq; +using Hacknet.Gui; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Pathfinder.Util; namespace Pathfinder.GUI { @@ -51,42 +55,96 @@ public void Draw(float t, SpriteBatch spriteBatch) spriteBatch.Draw(Frames[0], DestRect, Color.White); } - public static AnimatedTexture ReadFromGIF(string filename) + public static AnimatedTexture ReadFromAPNG(string filename) { var stream = new FileStream(filename, FileMode.Open); - var ret = ReadFromGIF(stream); + var ret = ReadFromAPNG(stream); stream.Dispose(); return ret; } - - public static AnimatedTexture ReadFromGIF(Stream stream, int x = 0, int y = 0) + + public static AnimatedTexture ReadFromAPNG(Stream stream, int x = 0, int y = 0) { var frameList = new List(); - ushort width; - ushort height; - using (var r = new BinaryReader(stream)) + using (var r = new SBAwareBinaryReader(stream, SBAwareBinaryReader.Endianess.BIG)) { - var magic = r.ReadBytes(6); - if (!magic.SequenceEqual(new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 })) - throw new NotSupportedException("GIF magic does not match!"); - - width = r.ReadUInt16(); - height = r.ReadUInt16(); - - byte tableInfo = r.ReadByte(); - int tableSize = tableInfo >> 4; - - int bitDepth = (tableInfo & 0x08) + 1; + var magic = r.ReadBytes(8); + if (!magic.SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 })) + throw new NotSupportedException("PNG magic mismatch!"); + + uint width = 0; + uint height = 0; + int bitDepth = 0; + byte colorType = 0; + Color[] colorTable = null; // required if color type == 3 + Color[,] imageBuffer; + + while (true) + { + var length = r.ReadInt32(); + switch (r.ReadUInt32()) + { + case 0x49484452: // IHDR + width = r.ReadUInt32(); + height = r.ReadUInt32(); + bitDepth = r.ReadByte(); + colorType = r.ReadByte(); + if (r.ReadByte() != 0) // compression method + throw new NotSupportedException("PNGs compressed with anything other than Deflate are not supported!"); + r.ReadByte(); // filter method + r.ReadByte(); // interlace method + break; + case 0x504C5445: // PLTE + if (length % 3 != 0) + throw new NotSupportedException("PLTE color table length must be divisible by 3"); + var entries = length / 3; + colorTable = new Color[entries]; + for (int i = 0; i < entries; i++) + { + colorTable[i] = new Color(r.ReadByte(), r.ReadByte(), r.ReadByte()); + } + break; + case 0x49444154: // IDAT + if (colorType == 3 && colorTable == null) + throw new NotSupportedException("Color type of 3 must have PLTE chunk before first IDAT!"); + var dataBytes = r.ReadBytes(length); + var ms = new MemoryStream(dataBytes); + using (var ds = new DeflateStream(ms, CompressionMode.Decompress)) + { + using (var dr = new SBAwareBinaryReader(ds, SBAwareBinaryReader.Endianess.BIG)) + { + bool hasData = true; + switch (colorType) + { + case 2: + while (true) + { + if (bitDepth == 8) + { + + } + } + break; + case 3: - int backgroundIndex = r.ReadByte(); - r.ReadByte(); // ratio, i dont wanna bother + break; + case 6: - Color[] colorTable = new Color[tableSize]; + break; + } + } + } + break; + case 0x49454E44: // IEND + goto exit; + } - int offset = 0; - for (int i = 0; i <= tableSize; i++) - { + r.ReadUInt32(); // CRC, dont wanna bother, and its Steam should verify on its end so the only way this could be wrong is a disk read error, and honestly i dont care enough to handle that edge case + continue; + exit: + r.ReadUInt32(); // CRC for IEND + break; } } diff --git a/PathfinderAPI/PathfinderAPI.csproj b/PathfinderAPI/PathfinderAPI.csproj index 08154ccb..32b467cf 100644 --- a/PathfinderAPI/PathfinderAPI.csproj +++ b/PathfinderAPI/PathfinderAPI.csproj @@ -118,11 +118,12 @@ + - + diff --git a/PathfinderAPI/Util/BitReader.cs b/PathfinderAPI/Util/BitReader.cs new file mode 100644 index 00000000..f691469b --- /dev/null +++ b/PathfinderAPI/Util/BitReader.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; + +namespace Pathfinder.Util +{ + public class BitReader : IDisposable + { + private BinaryReader Reader; + + private byte currentByte = 0; + private int offset = 8; + + public BitReader(Stream stream) + { + Reader = new BinaryReader(stream); + } + + public byte ReadByte() + { + if (offset == 8) + { + return Reader.ReadByte(); + } + return (byte)ReadBitsDepth(8); + } + + public byte[] ReadBytes(int count) + { + byte[] ret = new byte[count]; + for (int i = 0; i < count; i++) + { + ret[i] = ReadByte(); + } + + return ret; + } + + public ushort ReadUInt16() + { + ushort ret = 0; + for (int i = 0; i < 2 ; i++) + { + ret |= (ushort)(ReadByte() << i * 8); + } + + return ret; + } + + public int ReadBit() + { + if (offset == 8) + { + currentByte = Reader.ReadByte(); + offset = 0; + } + + return (currentByte >> offset++) & 1; + } + + public uint ReadBitsDepth(int bitDepth) + { + byte ret = 0; + for (int i = 0; i < bitDepth; i++) + { + ret |= (byte)(ReadBit() << i); + } + + return ret; + } + + public void Dispose() + { + Reader.Dispose(); + } + } +} \ No newline at end of file diff --git a/PathfinderAPI/Util/LZWBinaryReader.cs b/PathfinderAPI/Util/LZWBinaryReader.cs deleted file mode 100644 index 0dbf5201..00000000 --- a/PathfinderAPI/Util/LZWBinaryReader.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; - -namespace Pathfinder.Util -{ - public class LZWBinaryReader - { - public BinaryReader Reader { get; } - - public LZWBinaryReader(BinaryReader reader) - { - Reader = reader; - } - - public byte[] ReadBytes(int length) - { - return null; - } - } -} diff --git a/PathfinderAPI/Util/SBAwareBinaryReader.cs b/PathfinderAPI/Util/SBAwareBinaryReader.cs new file mode 100644 index 00000000..a2283b55 --- /dev/null +++ b/PathfinderAPI/Util/SBAwareBinaryReader.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Linq; + +namespace Pathfinder.Util +{ + public class SBAwareBinaryReader : BinaryReader + { + public enum Endianess + { + LITTLE, + BIG + } + + public Endianess Endian; + + public SBAwareBinaryReader(Stream stream, Endianess endian) : base(stream) + { + Endian = endian; + } + + public override short ReadInt16() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadInt16(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt16(ReadBytes(2).Reverse().ToArray(), 0); + } + + return BitConverter.ToInt16(ReadBytes(2), 0); + } + + public override ushort ReadUInt16() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadUInt16(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToUInt16(ReadBytes(2).Reverse().ToArray(), 0); + } + + return BitConverter.ToUInt16(ReadBytes(2), 0); + } + + public override int ReadInt32() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadInt32(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt32(ReadBytes(4).Reverse().ToArray(), 0); + } + + return BitConverter.ToInt32(ReadBytes(4), 0); + } + + public override uint ReadUInt32() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadUInt32(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToUInt32(ReadBytes(4).Reverse().ToArray(), 0); + } + + return BitConverter.ToUInt32(ReadBytes(4), 0); + } + + public override long ReadInt64() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadInt64(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt64(ReadBytes(8).Reverse().ToArray(), 0); + } + + return BitConverter.ToInt64(ReadBytes(8), 0); + } + + public override ulong ReadUInt64() + { + if (Endian == Endianess.LITTLE) + { + return base.ReadUInt64(); + } + + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToUInt64(ReadBytes(8).Reverse().ToArray(), 0); + } + + return BitConverter.ToUInt64(ReadBytes(8), 0); + } + } +} \ No newline at end of file From 3de3dc0db143357e220035ba3bd11ff561905fcf Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sat, 21 Aug 2021 18:59:29 -0500 Subject: [PATCH 3/4] WIP Image Data Loading --- PathfinderAPI/GUI/AnimatedTexture.cs | 64 ++++++++++++++++++++++------ PathfinderAPI/PathfinderAPIPlugin.cs | 10 +++++ 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/PathfinderAPI/GUI/AnimatedTexture.cs b/PathfinderAPI/GUI/AnimatedTexture.cs index 107fc136..0d117490 100644 --- a/PathfinderAPI/GUI/AnimatedTexture.cs +++ b/PathfinderAPI/GUI/AnimatedTexture.cs @@ -4,6 +4,7 @@ using System.IO; using System.IO.Compression; using System.Linq; +using Hacknet; using Hacknet.Gui; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -73,11 +74,12 @@ public static AnimatedTexture ReadFromAPNG(Stream stream, int x = 0, int y = 0) throw new NotSupportedException("PNG magic mismatch!"); uint width = 0; + // uint scanline = 0; uint height = 0; int bitDepth = 0; byte colorType = 0; Color[] colorTable = null; // required if color type == 3 - Color[,] imageBuffer; + Color[] imageBuffer = null; while (true) { @@ -87,12 +89,30 @@ public static AnimatedTexture ReadFromAPNG(Stream stream, int x = 0, int y = 0) case 0x49484452: // IHDR width = r.ReadUInt32(); height = r.ReadUInt32(); - bitDepth = r.ReadByte(); + imageBuffer = new Color[width * height]; + if (r.ReadByte() != 8) + throw new NotSupportedException("PNG decoder only supports 8 bit color!"); colorType = r.ReadByte(); + /* + switch (colorType) + { + case 2: // truecolor, rgb values per pixel + scanline = width * 3 + 1; + break; + case 3: // index color, index per pixel + scanline = width + 1; + break; + case 6: // truecolor + alpha, rgba values per pixel + scanline = width * 4 + 1; + break; + } + */ if (r.ReadByte() != 0) // compression method throw new NotSupportedException("PNGs compressed with anything other than Deflate are not supported!"); - r.ReadByte(); // filter method - r.ReadByte(); // interlace method + if (r.ReadByte() != 0) + throw new NotSupportedException("PNG decoder only supports filter method 0"); + if (r.ReadByte() != 0) // interlace method + throw new NotSupportedException("PNG decoder only supports non-interlaced PNGs!"); break; case 0x504C5445: // PLTE if (length % 3 != 0) @@ -116,27 +136,45 @@ public static AnimatedTexture ReadFromAPNG(Stream stream, int x = 0, int y = 0) bool hasData = true; switch (colorType) { - case 2: - while (true) + case 2: // truecolor, rgb values per pixel + for (int h = 0; h < height; h++) { - if (bitDepth == 8) + if (r.ReadByte() != 0) + throw new NotSupportedException("PNG decoder only supports unfiltered images"); + for (int w = 0; w < width; w++) { - - } + imageBuffer[h * w] = new Color(r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()); + } } break; - case 3: + case 3: // index color, index per pixel break; - case 6: - + case 6: // truecolor + alpha, rgba values per pixel + for (int h = 0; h < height; h++) + { + if (r.ReadByte() != 0) + throw new NotSupportedException("PNG decoder only supports unfiltered images"); + for (int w = 0; w < width; w++) + { + imageBuffer[h * w] = new Color(r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()); + } + } break; } } } break; case 0x49454E44: // IEND + var newFrame = new Texture2D(GuiData.spriteBatch.GraphicsDevice, (int)width, (int)height, false, SurfaceFormat.Color); + newFrame.SetData(imageBuffer); + frameList.Add(newFrame); + goto exit; + break; + default: + r.ReadBytes(length); + break; } r.ReadUInt32(); // CRC, dont wanna bother, and its Steam should verify on its end so the only way this could be wrong is a disk read error, and honestly i dont care enough to handle that edge case @@ -146,6 +184,8 @@ public static AnimatedTexture ReadFromAPNG(Stream stream, int x = 0, int y = 0) r.ReadUInt32(); // CRC for IEND break; } + + return new AnimatedTexture(frameList, Rectangle.Empty, 30); } return null; diff --git a/PathfinderAPI/PathfinderAPIPlugin.cs b/PathfinderAPI/PathfinderAPIPlugin.cs index bd497376..fe443dd2 100644 --- a/PathfinderAPI/PathfinderAPIPlugin.cs +++ b/PathfinderAPI/PathfinderAPIPlugin.cs @@ -3,7 +3,10 @@ using BepInEx; using BepInEx.Configuration; using BepInEx.Hacknet; +using Hacknet; using HarmonyLib; +using Microsoft.Xna.Framework; +using Pathfinder.GUI; using Pathfinder.Util; namespace Pathfinder @@ -35,6 +38,13 @@ public override bool Load() if (Environment.GetCommandLineArgs().Any(x => x.ToLower() == "-enabledebug")) Command.DebugCommands.AddCommands(); + + + Command.CommandManager.RegisterCommand("loadtest", (os, args) => + { + var animated = AnimatedTexture.ReadFromAPNG("test.png"); + }); + return true; } } From 64d7265bd0af2bd60d1a4c8e81af30de95934436 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sun, 3 Oct 2021 01:46:46 -0500 Subject: [PATCH 4/4] Switch to using System.Drawing --- .../Event/Menu/DrawMainMenuTitlesEvent.cs | 10 ++ PathfinderAPI/GUI/AnimatedTexture.cs | 148 ++++-------------- PathfinderAPI/PathfinderAPI.csproj | 2 + PathfinderAPI/PathfinderAPIPlugin.cs | 7 - 4 files changed, 41 insertions(+), 126 deletions(-) diff --git a/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs b/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs index 825a4b4b..3ad72c66 100644 --- a/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs +++ b/PathfinderAPI/Event/Menu/DrawMainMenuTitlesEvent.cs @@ -7,6 +7,7 @@ using Hacknet.Effects; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Pathfinder.GUI; namespace Pathfinder.Event.Menu { @@ -23,6 +24,8 @@ public class DrawMainMenuTitlesEvent : MainMenuEvent delegate void EmitDelegate(MainMenu menu, ref Rectangle rect); + private static AnimatedTexture anim = null; + [HarmonyILManipulator] [HarmonyPatch(typeof(MainMenu), nameof(MainMenu.DrawBackgroundAndTitle))] internal static void onDrawMainMenuTitlesIL(ILContext il) @@ -71,6 +74,13 @@ internal static void onDrawMainMenuTitlesIL(ILContext il) main.Color ); TextItem.doFontLabel(new Vector2(sub.Destination.Location.X, sub.Destination.Location.Y), sub.Title, sub.Font, sub.Color, 600f, 26f); + + if (anim == null) + { + anim = AnimatedTexture.ReadFromFile("swirl.gif"); + anim.ShouldLoop = true; + } + anim.Draw(1f / 60f, GuiData.spriteBatch); }); var firstLabel = c.MarkLabel(); diff --git a/PathfinderAPI/GUI/AnimatedTexture.cs b/PathfinderAPI/GUI/AnimatedTexture.cs index 0d117490..075f9078 100644 --- a/PathfinderAPI/GUI/AnimatedTexture.cs +++ b/PathfinderAPI/GUI/AnimatedTexture.cs @@ -1,14 +1,17 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; using System.IO; using System.IO.Compression; using System.Linq; using Hacknet; using Hacknet.Gui; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Pathfinder.Util; +using Color = Microsoft.Xna.Framework.Color; +using Rectangle = Microsoft.Xna.Framework.Rectangle; namespace Pathfinder.GUI { @@ -56,139 +59,46 @@ public void Draw(float t, SpriteBatch spriteBatch) spriteBatch.Draw(Frames[0], DestRect, Color.White); } - public static AnimatedTexture ReadFromAPNG(string filename) + public static AnimatedTexture ReadFromFile(string filename) { var stream = new FileStream(filename, FileMode.Open); - var ret = ReadFromAPNG(stream); + var ret = ReadFromStream(stream); stream.Dispose(); return ret; } - public static AnimatedTexture ReadFromAPNG(Stream stream, int x = 0, int y = 0) + public static AnimatedTexture ReadFromStream(Stream stream) { var frameList = new List(); - using (var r = new SBAwareBinaryReader(stream, SBAwareBinaryReader.Endianess.BIG)) + + int width = 0; + int height = 0; + using (var image = new Bitmap(stream)) { - var magic = r.ReadBytes(8); - if (!magic.SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 })) - throw new NotSupportedException("PNG magic mismatch!"); - - uint width = 0; - // uint scanline = 0; - uint height = 0; - int bitDepth = 0; - byte colorType = 0; - Color[] colorTable = null; // required if color type == 3 - Color[] imageBuffer = null; - - while (true) + width = image.Width; + height = image.Height; + for (int i = 0; i < image.GetFrameCount(FrameDimension.Time); i++) { - var length = r.ReadInt32(); - switch (r.ReadUInt32()) + image.SelectActiveFrame(FrameDimension.Page, i); + var data = image.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + Color[] colors = new Color[height * width]; + unsafe { - case 0x49484452: // IHDR - width = r.ReadUInt32(); - height = r.ReadUInt32(); - imageBuffer = new Color[width * height]; - if (r.ReadByte() != 8) - throw new NotSupportedException("PNG decoder only supports 8 bit color!"); - colorType = r.ReadByte(); - /* - switch (colorType) - { - case 2: // truecolor, rgb values per pixel - scanline = width * 3 + 1; - break; - case 3: // index color, index per pixel - scanline = width + 1; - break; - case 6: // truecolor + alpha, rgba values per pixel - scanline = width * 4 + 1; - break; - } - */ - if (r.ReadByte() != 0) // compression method - throw new NotSupportedException("PNGs compressed with anything other than Deflate are not supported!"); - if (r.ReadByte() != 0) - throw new NotSupportedException("PNG decoder only supports filter method 0"); - if (r.ReadByte() != 0) // interlace method - throw new NotSupportedException("PNG decoder only supports non-interlaced PNGs!"); - break; - case 0x504C5445: // PLTE - if (length % 3 != 0) - throw new NotSupportedException("PLTE color table length must be divisible by 3"); - var entries = length / 3; - colorTable = new Color[entries]; - for (int i = 0; i < entries; i++) - { - colorTable[i] = new Color(r.ReadByte(), r.ReadByte(), r.ReadByte()); - } - break; - case 0x49444154: // IDAT - if (colorType == 3 && colorTable == null) - throw new NotSupportedException("Color type of 3 must have PLTE chunk before first IDAT!"); - var dataBytes = r.ReadBytes(length); - var ms = new MemoryStream(dataBytes); - using (var ds = new DeflateStream(ms, CompressionMode.Decompress)) - { - using (var dr = new SBAwareBinaryReader(ds, SBAwareBinaryReader.Endianess.BIG)) - { - bool hasData = true; - switch (colorType) - { - case 2: // truecolor, rgb values per pixel - for (int h = 0; h < height; h++) - { - if (r.ReadByte() != 0) - throw new NotSupportedException("PNG decoder only supports unfiltered images"); - for (int w = 0; w < width; w++) - { - imageBuffer[h * w] = new Color(r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()); - } - } - break; - case 3: // index color, index per pixel - - break; - case 6: // truecolor + alpha, rgba values per pixel - for (int h = 0; h < height; h++) - { - if (r.ReadByte() != 0) - throw new NotSupportedException("PNG decoder only supports unfiltered images"); - for (int w = 0; w < width; w++) - { - imageBuffer[h * w] = new Color(r.ReadByte(), r.ReadByte(), r.ReadByte(), r.ReadByte()); - } - } - break; - } - } - } - break; - case 0x49454E44: // IEND - var newFrame = new Texture2D(GuiData.spriteBatch.GraphicsDevice, (int)width, (int)height, false, SurfaceFormat.Color); - newFrame.SetData(imageBuffer); - frameList.Add(newFrame); - - goto exit; - break; - default: - r.ReadBytes(length); - break; + Color* ptr = (Color*)data.Scan0.ToPointer(); + for (int arrayIndex = 0; arrayIndex < colors.Length; arrayIndex++) + { + Color color = *ptr++; + colors[arrayIndex] = new Color(color.B, color.G, color.R, color.A); + } } - - r.ReadUInt32(); // CRC, dont wanna bother, and its Steam should verify on its end so the only way this could be wrong is a disk read error, and honestly i dont care enough to handle that edge case - continue; - - exit: - r.ReadUInt32(); // CRC for IEND - break; + image.UnlockBits(data); + var frame = new Texture2D(GuiData.spriteBatch.GraphicsDevice, width, height, false, SurfaceFormat.Color); + frame.SetData(colors); + frameList.Add(frame); } - - return new AnimatedTexture(frameList, Rectangle.Empty, 30); } - return null; + return new AnimatedTexture(frameList, new Rectangle(0, 0, width, height), 30); } public void Dispose() diff --git a/PathfinderAPI/PathfinderAPI.csproj b/PathfinderAPI/PathfinderAPI.csproj index 32b467cf..28435280 100644 --- a/PathfinderAPI/PathfinderAPI.csproj +++ b/PathfinderAPI/PathfinderAPI.csproj @@ -12,6 +12,7 @@ v4.0 512 true + true true @@ -57,6 +58,7 @@ + diff --git a/PathfinderAPI/PathfinderAPIPlugin.cs b/PathfinderAPI/PathfinderAPIPlugin.cs index fe443dd2..024adf45 100644 --- a/PathfinderAPI/PathfinderAPIPlugin.cs +++ b/PathfinderAPI/PathfinderAPIPlugin.cs @@ -38,13 +38,6 @@ public override bool Load() if (Environment.GetCommandLineArgs().Any(x => x.ToLower() == "-enabledebug")) Command.DebugCommands.AddCommands(); - - - Command.CommandManager.RegisterCommand("loadtest", (os, args) => - { - var animated = AnimatedTexture.ReadFromAPNG("test.png"); - }); - return true; } }