diff --git a/BlastIMG/BlastIMG.csproj b/BlastIMG/BlastIMG.csproj new file mode 100644 index 0000000..c79f317 --- /dev/null +++ b/BlastIMG/BlastIMG.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/BlastIMG/BlastImage.cs b/BlastIMG/BlastImage.cs new file mode 100644 index 0000000..700a27f --- /dev/null +++ b/BlastIMG/BlastImage.cs @@ -0,0 +1,41 @@ +namespace BlastIMG; + +public enum FileFormat +{ + BMP, + PNG, + JPEG, + QOI +} + +public class Pixel +{ + public int X { get; } + public int Y { get; } + public byte R { get; } + public byte G { get; } + public byte B { get; } + public byte A { get; } +} + +public class Image +{ + public FileFormat Format { get; } + public int Width { get; } + public int Height { get; } + public Pixel[,] Pixels { get; } + + public Pixel GetPixel(int x, int y) + { + return Pixels[x, y]; + } + + public static Image Load(string imagePath) + { + if (string.IsNullOrWhiteSpace(imagePath)) + throw new ArgumentException("Image path cannot be null or whitespace.", nameof(imagePath)); + + + return new Image(); + } +} \ No newline at end of file diff --git a/BlastIMG/ImageLoaders/BmpLoader.cs b/BlastIMG/ImageLoaders/BmpLoader.cs new file mode 100644 index 0000000..67ca951 --- /dev/null +++ b/BlastIMG/ImageLoaders/BmpLoader.cs @@ -0,0 +1,117 @@ +using System.Text; +using System.Text.Json.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using JsonConverter = Newtonsoft.Json.JsonConverter; + +namespace BlastIMG.ImageLoaders; + +enum CompressionMethod +{ + BI_RGB, // 0 + BI_RLE8, // 1 + BI_RLE4, // 2 + BI_BITFIELDS, // 3 + BI_JPEG, // 4 + BI_PNG, // 5 + BI_ALPHABITFIELDS, // 6 + BI_CMYK, // 11 + BI_CMYKRLE8, // 12 + BI_CMYKRLE4, // 13 +} + +class BitmapFileHeader +{ + public string MagicNumber { get; set; } + public int FileSize { get; set; } + // there are 4 bytes that are set by the program that creates the image but we probably don't need it + public int PixelArrayOffset { get; set; } +} + +class BitmapInfoHeader +{ + public int HeaderSize { get; set; } + public int BitmapWidth { get; set; } + public int BitmapHeight { get; set; } + public short ColorPlanes { get; set; } // must be 1 + public short BitsPerPixel { get; set; } + public CompressionMethod CompressionMethod { get; set; } + public int RawImageSize { get; set; } // can be set to 0 + public int HorizontalResolution { get; set; } // ppm + public int VerticalResolution { get; set; } // ppm + public int ColorPalette { get; set; } // 0 for all + public int ImportantColors { get; set; } +} + +public static class BmpLoader +{ + public static Image Load(string filepath) + { + using var imageFile = File.OpenRead(filepath); + + var fileHeader = imageFile.ReadBytes(0, 14); + var parsedFileHeader = new BitmapFileHeader + { + MagicNumber = Encoding.Default.GetString(fileHeader[..2]), + FileSize = BitConverter.ToInt32(fileHeader[2..6]), + PixelArrayOffset = BitConverter.ToInt32(fileHeader[10..14]) + }; + + var headerSize = BitConverter.ToInt32(imageFile.ReadBytes(14, 4)); + var dibHeader = imageFile.ReadBytes(14, headerSize); + // TODO pull more of the header data here if the header isn't the default header + var parsedDibHeader = new BitmapInfoHeader + { + HeaderSize = headerSize, + BitmapWidth = BitConverter.ToInt32(dibHeader[4..8]), + BitmapHeight = BitConverter.ToInt32(dibHeader[8..12]), + ColorPlanes = BitConverter.ToInt16(dibHeader[12..14]), + BitsPerPixel = BitConverter.ToInt16(dibHeader[14..16]), + CompressionMethod = BitConverter.ToInt32(dibHeader[16..20]).GetCompressionMethod(), + RawImageSize = BitConverter.ToInt32(dibHeader[20..24]), + HorizontalResolution = BitConverter.ToInt32(dibHeader[24..28]), + VerticalResolution = BitConverter.ToInt32(dibHeader[28..32]), + ColorPalette = BitConverter.ToInt32(dibHeader[32..36]), + ImportantColors = BitConverter.ToInt32(dibHeader[36..40]), + }; + + Console.Out.WriteLine($"{parsedFileHeader.MagicNumber}"); + Console.Out.WriteLine($"{parsedFileHeader.FileSize}"); + Console.Out.WriteLine($"{parsedFileHeader.PixelArrayOffset}"); + Console.Out.WriteLine($"Width: {parsedDibHeader.BitmapWidth}"); + Console.Out.WriteLine($"Height: {parsedDibHeader.BitmapHeight}"); + Console.Out.WriteLine(JsonConvert.SerializeObject(parsedDibHeader, Formatting.Indented, new JsonConverter[] {new StringEnumConverter()})); + + return new Image(); // DUMMY FOR NOW + } +} + +static class MyExtensions { + public static CompressionMethod GetCompressionMethod(this int method) + { + return method switch + { + 0 => CompressionMethod.BI_RGB, + 1 => CompressionMethod.BI_RLE8, + 2 => CompressionMethod.BI_RLE4, + 3 => CompressionMethod.BI_BITFIELDS, + 4 => CompressionMethod.BI_JPEG, + 5 => CompressionMethod.BI_PNG, + 6 => CompressionMethod.BI_ALPHABITFIELDS, + 11 => CompressionMethod.BI_CMYK, + 12 => CompressionMethod.BI_CMYKRLE8, + 13 => CompressionMethod.BI_CMYKRLE4, + _ => throw new ArgumentException("Unknown Bitmap Compression Method", nameof(method)) + }; + } + + public static byte[] ReadBytes(this Stream stream, int fileOffset, int count) + { + var buffer = new byte[count]; + stream.Seek(fileOffset, SeekOrigin.Begin); + var bytesRead = stream.Read(buffer, 0, count); + if (bytesRead != count) + Console.Error.WriteLine("Didn't read the correct amount of bytes"); + return buffer; + } +} \ No newline at end of file diff --git a/BlastPDF.sln b/BlastPDF.sln index 173e32a..dd8ed78 100644 --- a/BlastPDF.sln +++ b/BlastPDF.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShowCase", "ShowCase\ShowCa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlastPDF.Test", "BlastPDF.Test\BlastPDF.Test.csproj", "{331127D4-4996-4D21-A808-09CA2B2CC975}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlastIMG", "BlastIMG\BlastIMG.csproj", "{DA68CCF5-1B74-4539-8827-1BDC385BA4BE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,5 +60,17 @@ Global {331127D4-4996-4D21-A808-09CA2B2CC975}.Release|x64.Build.0 = Release|Any CPU {331127D4-4996-4D21-A808-09CA2B2CC975}.Release|x86.ActiveCfg = Release|Any CPU {331127D4-4996-4D21-A808-09CA2B2CC975}.Release|x86.Build.0 = Release|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Debug|x64.ActiveCfg = Debug|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Debug|x64.Build.0 = Debug|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Debug|x86.ActiveCfg = Debug|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Debug|x86.Build.0 = Debug|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Release|Any CPU.Build.0 = Release|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Release|x64.ActiveCfg = Release|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Release|x64.Build.0 = Release|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Release|x86.ActiveCfg = Release|Any CPU + {DA68CCF5-1B74-4539-8827-1BDC385BA4BE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/BlastPDF/BlastPDF.csproj b/BlastPDF/BlastPDF.csproj index 12b1dac..8431fce 100644 --- a/BlastPDF/BlastPDF.csproj +++ b/BlastPDF/BlastPDF.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/ShowCase/ImageParsingExample.cs b/ShowCase/ImageParsingExample.cs new file mode 100644 index 0000000..e17392e --- /dev/null +++ b/ShowCase/ImageParsingExample.cs @@ -0,0 +1,17 @@ +using System; +using BlastIMG; +using BlastIMG.ImageLoaders; + +namespace ShowCase; + +public class ImageParsingExample +{ + public static void Run(string imagePath) + { + var loaded = BmpLoader.Load(imagePath); + + Console.WriteLine($"Loaded: {imagePath}"); + Console.WriteLine($"Width: {loaded.Width}"); + Console.WriteLine($"Height: {loaded.Height}"); + } +} \ No newline at end of file diff --git a/ShowCase/PdfBuilderExample.cs b/ShowCase/PdfBuilderExample.cs new file mode 100644 index 0000000..ae6e3db --- /dev/null +++ b/ShowCase/PdfBuilderExample.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using BlastPDF.Builder; +using BlastPDF.Builder.Graphics; +using BlastPDF.Builder.Graphics.Drawing; +using BlastPDF.Exporter.Basic; + +namespace ShowCase; + +public class PdfBuilderExample +{ + public static void Run(string outputPdfName) + { + if (File.Exists(outputPdfName)) { + File.Delete(outputPdfName); + } + + var graphicsGroup = PdfGraphicsGroup.Create(); + + var triangleWidth = 5.0M; + for(int i = 0; i < 96; i++) { + graphicsGroup.Add( + PdfGraphicsObject.Create() + .Translate(500, 500) + .Rotate(i * (decimal)Math.PI / 96) + .LineWidth(2) + .SetStrokeRGB(i / 96.0M, 0.0M, 1.0M - (i / 96.0M)) + .DrawTriangle(0, 0, triangleWidth) + .ResetState() + ); + triangleWidth *= 1.070M; + } + + using FileStream fs = File.Create(outputPdfName); + PdfDocument.Create() + .AddPage(PdfPage.Create() + .DotsPerInch(100) + .Width(10) + .Height(10) + .AddResource("GoodImage", PdfImage.FromFile("cat.png", PdfColorSpace.DeviceRGB)) + .AddGraphics(PdfGraphicsObject.Create() + .SetCMYK(0.0M, 0.0M, 1.0M, 0.0M) + .Rect(0, 0, 1000, 1000) + .Paint(PaintModeEnum.CloseFillStroke)) + .AddGraphics(graphicsGroup) + .AddGraphics(PdfGraphicsObject.Create() + .Translate(250, 702) + .Resource("GoodImage"))) + .Save(fs); + } +} + +public static class MyExtension { + public static PdfGraphicsObject DrawTriangle(this PdfGraphicsObject path, decimal x, decimal y, decimal length) + { + var radius = length / (2 * (decimal)Math.Cos(Math.PI / 6)); + var apothem = length * (decimal)Math.Tan(Math.PI / 6) / 2; + + var top = (x, y + radius); + var left = (x - (length / 2), y - apothem); + var right = (x + (length / 2), y - apothem); + + return path + .Move(top.Item1, top.Item2) + .Line(left.Item1, left.Item2) + .Line(right.Item1, right.Item2) + .Paint(PaintModeEnum.CloseStroke); + } +} \ No newline at end of file diff --git a/ShowCase/Program.cs b/ShowCase/Program.cs index 7837658..8105c83 100644 --- a/ShowCase/Program.cs +++ b/ShowCase/Program.cs @@ -1,67 +1,10 @@ -using BlastPDF.Exporter.Basic; -using BlastPDF.Builder; -using BlastPDF.Builder.Graphics; -using BlastPDF.Builder.Graphics.Drawing; -using System; -using System.IO; - + namespace ShowCase; public class Program { public static void Main(string[] args) { - string path = "test.pdf"; - - if (File.Exists(path)) { - File.Delete(path); - } - - var graphicsGroup = PdfGraphicsGroup.Create(); - - var triangleWidth = 5.0M; - for(int i = 0; i < 96; i++) { - graphicsGroup.Add( - PdfGraphicsObject.Create() - .Translate(500, 500) - .Rotate(i * (decimal)Math.PI / 96) - .LineWidth(2) - .SetStrokeRGB(i / 96.0M, 0.0M, 1.0M - (i / 96.0M)) - .DrawTriangle(0, 0, triangleWidth) - .ResetState() - ); - triangleWidth *= 1.070M; - } - - using FileStream fs = File.Create(path); - PdfDocument.Create() - .AddPage(PdfPage.Create() - .DotsPerInch(100) - .Width(10) - .Height(10) - .AddGraphics(PdfGraphicsObject.Create() - .SetCMYK(0.0M, 0.0M, 1.0M, 0.0M) - .Rect(0, 0, 1000, 1000) - .Paint(PaintModeEnum.CloseFillStroke)) - .AddGraphics(graphicsGroup)) - .Save(fs); + //PdfBuilderExample.Run("test.pdf"); + ImageParsingExample.Run("../../../images/w3c_home_256.bmp"); } - - } -public static class MyExtension { - public static PdfGraphicsObject DrawTriangle(this PdfGraphicsObject path, decimal x, decimal y, decimal length) - { - var radius = length / (2 * (decimal)Math.Cos(Math.PI / 6)); - var apothem = length * (decimal)Math.Tan(Math.PI / 6) / 2; - - var top = (x, y + radius); - var left = (x - (length / 2), y - apothem); - var right = (x + (length / 2), y - apothem); - - return path - .Move(top.Item1, top.Item2) - .Line(left.Item1, left.Item2) - .Line(right.Item1, right.Item2) - .Paint(PaintModeEnum.CloseStroke); - } -} \ No newline at end of file diff --git a/ShowCase/cat.png b/ShowCase/cat.png new file mode 100644 index 0000000..b57f4cf Binary files /dev/null and b/ShowCase/cat.png differ diff --git a/ShowCase/images/cat.bmp b/ShowCase/images/cat.bmp new file mode 100644 index 0000000..1bcddce Binary files /dev/null and b/ShowCase/images/cat.bmp differ diff --git a/ShowCase/images/w3c_home.bmp b/ShowCase/images/w3c_home.bmp new file mode 100644 index 0000000..eaaac6d Binary files /dev/null and b/ShowCase/images/w3c_home.bmp differ diff --git a/ShowCase/images/w3c_home_2.bmp b/ShowCase/images/w3c_home_2.bmp new file mode 100644 index 0000000..47cb2c7 Binary files /dev/null and b/ShowCase/images/w3c_home_2.bmp differ diff --git a/ShowCase/images/w3c_home_256.bmp b/ShowCase/images/w3c_home_256.bmp new file mode 100644 index 0000000..4b714ff Binary files /dev/null and b/ShowCase/images/w3c_home_256.bmp differ diff --git a/ShowCase/images/w3c_home_gray.bmp b/ShowCase/images/w3c_home_gray.bmp new file mode 100644 index 0000000..6eb7bfe Binary files /dev/null and b/ShowCase/images/w3c_home_gray.bmp differ