From 6e98969ab3148c682b50e9fb0ce8531132880e98 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sun, 19 May 2019 15:00:55 +0100 Subject: [PATCH] add the unlicense, remove unused code and add documentation --- .gitignore | 3 ++ LICENSE | 24 ++++++++++++ README.md | 45 ++++++++++++++++++++--- src/BigGustave.Tests/PngBuilderTests.cs | 23 ++++++++++++ src/BigGustave/Adam7.cs | 1 - src/BigGustave/BigGustave.csproj | 9 +++++ src/BigGustave/ChunkHeader.cs | 19 ++++++++++ src/BigGustave/ColorType.cs | 12 ++++++ src/BigGustave/Crc32.cs | 12 ++++++ src/BigGustave/Decoder.cs | 24 ------------ src/BigGustave/FilterType.cs | 26 +++++++++++++ src/BigGustave/GrayscaleAlphaPixel.cs | 15 -------- src/BigGustave/GrayscalePixel.cs | 17 --------- src/BigGustave/IPixel.cs | 9 ----- src/BigGustave/ImageHeader.cs | 28 ++++++++++++++ src/BigGustave/Palette.cs | 19 ++++++++++ src/BigGustave/Pixel.cs | 49 ++++++++++++++++++++++++- src/BigGustave/Png.cs | 22 ++++++++++- src/BigGustave/PngBuilder.cs | 27 ++++++++++++++ src/BigGustave/PngOpener.cs | 17 --------- src/BigGustave/RgbPixel.cs | 18 --------- src/BigGustave/RgbaPixel.cs | 26 ------------- 22 files changed, 311 insertions(+), 134 deletions(-) create mode 100644 LICENSE create mode 100644 src/BigGustave/FilterType.cs delete mode 100644 src/BigGustave/GrayscaleAlphaPixel.cs delete mode 100644 src/BigGustave/GrayscalePixel.cs delete mode 100644 src/BigGustave/IPixel.cs create mode 100644 src/BigGustave/Palette.cs delete mode 100644 src/BigGustave/RgbPixel.cs delete mode 100644 src/BigGustave/RgbaPixel.cs diff --git a/.gitignore b/.gitignore index 56fef45..994c63d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ bld/ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +# Doc comments xml +src/BigGustave/BigGustave.xml + # NUNIT *.VisualState.xml TestResult.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6bb8a29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/README.md b/README.md index c4a662a..ec3db98 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,29 @@ [![Build status](https://ci.appveyor.com/api/projects/status/nh12x7vg36qxunp0?svg=true)](https://ci.appveyor.com/project/EliotJones/biggustave) -An attempt at PNG decoding according to the PNG spec using .NET standard libraries only. +Open, read and create PNG images in fully managed C#. ## Usage ## -Still being written but the idea is calling: +To open a PNG image from file and get some pixel values: - Png png = Png.Open(Stream stream) + using (var stream = File.OpenRead(@"C:\my\file\path\file.png")) + { + Png image = Png.Open(stream); -Will return a PNG object. + Pixel pixel = image.GetPixel(image.Width - 1, image.Height - 1); + + int pixelRedAverage = 0; + + pixelRedAverage += pixel.R; + + pixel = image.GetPixel(0, 0); + + pixelRedAverage += pixel.R; + + Console.WriteLine(pixelRedAverage / 2.0); + + } The PNG object has methods to inspect the header and get the pixel values. The header has properties for: @@ -35,4 +49,25 @@ To get a pixel use: Pixel pixel = png.GetPixel(0, 7); -Where the first argument is x (column) and the second is y (row). The `Pixel` is used for all image types, e.g. Grayscale, Colour, with/without transparency. \ No newline at end of file +Where the first argument is x (column) and the second is y (row). The `Pixel` is used for all image types, e.g. Grayscale, Colour, with/without transparency. + +## Creation ## + +Because of some issues with ZLib compatibility the created images work with most, but not all image viewers. Of the viewers tested the images work with all browsers, Paint, Gimp, Paint3D etc. + +To create a PNG use: + + var builder = PngBuilder.Create(2, 2, false); + + var red = new Pixel(255, 0, 12, 255, false); + var black = new Pixel(0, 0, 0, 255, false); + + builder.SetPixel(new Pixel(255, 0, 12, 255, false), 0, 0); + builder.SetPixel(new Pixel(255, 0, 12, 255, false), 1, 1); + + using (var memory = new MemoryStream()) + { + builder.Save(memory); + + return memory.ToArray(); + } \ No newline at end of file diff --git a/src/BigGustave.Tests/PngBuilderTests.cs b/src/BigGustave.Tests/PngBuilderTests.cs index 4d545ab..5f60028 100644 --- a/src/BigGustave.Tests/PngBuilderTests.cs +++ b/src/BigGustave.Tests/PngBuilderTests.cs @@ -34,5 +34,28 @@ public void SimpleCheckerboard() Assert.Equal(red, bottomRight); } } + + [Fact] + public void BiggerImage() + { + var builder = PngBuilder.Create(10, 10, false); + + var green = new Pixel(0, 255, 25, 255, false); + var color1 = new Pixel(60, 201, 32, 255, false); + var color2 = new Pixel(100, 5, 250, 255, false); + + builder.SetPixel(green, 1, 1).SetPixel(green, 2, 1).SetPixel(green, 3, 1).SetPixel(green, 4, 1).SetPixel(green, 5, 1); + + builder.SetPixel(color1, 5, 7).SetPixel(color1, 5, 8) + .SetPixel(color1, 6, 7).SetPixel(color1, 6, 8) + .SetPixel(color1, 7, 7).SetPixel(color1, 7, 8); + + builder.SetPixel(color2, 9, 9).SetPixel(color2, 8, 8); + + using (var memoryStream = new MemoryStream()) + { + builder.Save(memoryStream); + } + } } } diff --git a/src/BigGustave/Adam7.cs b/src/BigGustave/Adam7.cs index d001843..f1ae358 100644 --- a/src/BigGustave/Adam7.cs +++ b/src/BigGustave/Adam7.cs @@ -1,6 +1,5 @@ namespace BigGustave { - using System; using System.Collections.Generic; internal static class Adam7 diff --git a/src/BigGustave/BigGustave.csproj b/src/BigGustave/BigGustave.csproj index 37d1f5c..9f4c6fe 100644 --- a/src/BigGustave/BigGustave.csproj +++ b/src/BigGustave/BigGustave.csproj @@ -3,6 +3,15 @@ netstandard2.0 7.2 + Open, read and create PNG images. + https://github.com/EliotJones/BigGustave + https://raw.githubusercontent.com/UglyToad/DataTable/master/uglytoadsmall.png + https://github.com/EliotJones/BigGustave/blob/master/LICENSE + https://github.com/EliotJones/BigGustave + + + + C:\git\csharp\BigGustave\src\BigGustave\BigGustave.xml diff --git a/src/BigGustave/ChunkHeader.cs b/src/BigGustave/ChunkHeader.cs index a5a088e..0fb5c70 100644 --- a/src/BigGustave/ChunkHeader.cs +++ b/src/BigGustave/ChunkHeader.cs @@ -2,6 +2,9 @@ { using System; + /// + /// The header for a data chunk in a PNG file. + /// public readonly struct ChunkHeader { /// @@ -19,10 +22,25 @@ public readonly struct ChunkHeader /// public string Name { get; } + /// + /// Whether the chunk is critical (must be read by all readers) or ancillary (may be ignored). + /// public bool IsCritical => char.IsUpper(Name[0]); + + /// + /// A public chunk is one that is defined in the International Standard or is registered in the list of public chunk types maintained by the Registration Authority. + /// Applications can also define private (unregistered) chunk types for their own purposes. + /// public bool IsPublic => char.IsUpper(Name[1]); + + /// + /// Whether the (if unrecognized) chunk is safe to copy. + /// public bool IsSafeToCopy => char.IsUpper(Name[3]); + /// + /// Create a new . + /// public ChunkHeader(long position, int length, string name) { if (length < 0) @@ -35,6 +53,7 @@ public ChunkHeader(long position, int length, string name) Name = name; } + /// public override string ToString() { return $"{Name} at {Position} (length: {Length})."; diff --git a/src/BigGustave/ColorType.cs b/src/BigGustave/ColorType.cs index 64e6d13..3869533 100644 --- a/src/BigGustave/ColorType.cs +++ b/src/BigGustave/ColorType.cs @@ -8,9 +8,21 @@ [Flags] public enum ColorType : byte { + /// + /// Grayscale. + /// None = 0, + /// + /// Colors are stored in a palette rather than directly in the data. + /// PaletteUsed = 1, + /// + /// The image uses color. + /// ColorUsed = 2, + /// + /// The image has an alpha channel. + /// AlphaChannelUsed = 4 } } \ No newline at end of file diff --git a/src/BigGustave/Crc32.cs b/src/BigGustave/Crc32.cs index f197dc9..1812ada 100644 --- a/src/BigGustave/Crc32.cs +++ b/src/BigGustave/Crc32.cs @@ -2,6 +2,9 @@ { using System.Collections.Generic; + /// + /// 32-bit Cyclic Redundancy Code used by the PNG for checking the data is intact. + /// public static class Crc32 { private const uint Polynomial = 0xEDB88320; @@ -30,6 +33,9 @@ static Crc32() } } + /// + /// Calculate the CRC32 for data. + /// public static uint Calculate(byte[] data) { var crc32 = uint.MaxValue; @@ -42,6 +48,9 @@ public static uint Calculate(byte[] data) return crc32 ^ uint.MaxValue; } + /// + /// Calculate the CRC32 for data. + /// public static uint Calculate(List data) { var crc32 = uint.MaxValue; @@ -54,6 +63,9 @@ public static uint Calculate(List data) return crc32 ^ uint.MaxValue; } + /// + /// Calculate the combined CRC32 for data. + /// public static uint Calculate(byte[] data, byte[] data2) { var crc32 = uint.MaxValue; diff --git a/src/BigGustave/Decoder.cs b/src/BigGustave/Decoder.cs index 2dde920..c0c1bf1 100644 --- a/src/BigGustave/Decoder.cs +++ b/src/BigGustave/Decoder.cs @@ -208,28 +208,4 @@ private static byte GetPaethValue(byte a, byte b, byte c) return pb <= pc ? b : c; } } - - public enum FilterType - { - /// - /// The raw byte is unaltered. - /// - None = 0, - /// - /// The byte to the left. - /// - Sub = 1, - /// - /// The byte above. - /// - Up = 2, - /// - /// The mean of bytes left and above, rounded down. - /// - Average = 3, - /// - /// Byte to the left, above or top-left based on Paeth's algorithm. - /// - Paeth = 4 - } } diff --git a/src/BigGustave/FilterType.cs b/src/BigGustave/FilterType.cs new file mode 100644 index 0000000..fbea036 --- /dev/null +++ b/src/BigGustave/FilterType.cs @@ -0,0 +1,26 @@ +namespace BigGustave +{ + internal enum FilterType + { + /// + /// The raw byte is unaltered. + /// + None = 0, + /// + /// The byte to the left. + /// + Sub = 1, + /// + /// The byte above. + /// + Up = 2, + /// + /// The mean of bytes left and above, rounded down. + /// + Average = 3, + /// + /// Byte to the left, above or top-left based on Paeth's algorithm. + /// + Paeth = 4 + } +} \ No newline at end of file diff --git a/src/BigGustave/GrayscaleAlphaPixel.cs b/src/BigGustave/GrayscaleAlphaPixel.cs deleted file mode 100644 index 6c45bee..0000000 --- a/src/BigGustave/GrayscaleAlphaPixel.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace BigGustave -{ - public readonly struct GrayscaleAlphaPixel : IPixel - { - public byte Value { get; } - - public byte Alpha { get; } - - public GrayscaleAlphaPixel(byte value, byte alpha) - { - Value = value; - Alpha = alpha; - } - } -} \ No newline at end of file diff --git a/src/BigGustave/GrayscalePixel.cs b/src/BigGustave/GrayscalePixel.cs deleted file mode 100644 index 6a0dedd..0000000 --- a/src/BigGustave/GrayscalePixel.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace BigGustave -{ - public readonly struct GrayscalePixel : IPixel - { - public byte Value { get; } - - public GrayscalePixel(byte value) - { - Value = value; - } - - public override string ToString() - { - return Value.ToString(); - } - } -} \ No newline at end of file diff --git a/src/BigGustave/IPixel.cs b/src/BigGustave/IPixel.cs deleted file mode 100644 index 190b2b9..0000000 --- a/src/BigGustave/IPixel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BigGustave -{ - /// - /// Marker pixel interface. - /// - public interface IPixel - { - } -} \ No newline at end of file diff --git a/src/BigGustave/ImageHeader.cs b/src/BigGustave/ImageHeader.cs index ea1b801..c26514d 100644 --- a/src/BigGustave/ImageHeader.cs +++ b/src/BigGustave/ImageHeader.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; + /// + /// The high level information about the image. + /// public readonly struct ImageHeader { internal static readonly byte[] HeaderBytes = { @@ -18,20 +21,44 @@ public readonly struct ImageHeader {ColorType.AlphaChannelUsed | ColorType.ColorUsed, new HashSet {8, 16}}, }; + /// + /// The width of the image in pixels. + /// public int Width { get; } + /// + /// The height of the image in pixels. + /// public int Height { get; } + /// + /// The bit depth of the image. + /// public byte BitDepth { get; } + /// + /// The color type of the image. + /// public ColorType ColorType { get; } + /// + /// The compression method used for the image. + /// public CompressionMethod CompressionMethod { get; } + /// + /// The filter method used for the image. + /// public FilterMethod FilterMethod { get; } + /// + /// The interlace method used by the image.. + /// public InterlaceMethod InterlaceMethod { get; } + /// + /// Create a new . + /// public ImageHeader(int width, int height, byte bitDepth, ColorType colorType, CompressionMethod compressionMethod, FilterMethod filterMethod, InterlaceMethod interlaceMethod) { if (width == 0) @@ -59,6 +86,7 @@ public ImageHeader(int width, int height, byte bitDepth, ColorType colorType, Co InterlaceMethod = interlaceMethod; } + /// public override string ToString() { return $"w: {Width}, h: {Height}, bitDepth: {BitDepth}, colorType: {ColorType}, " + diff --git a/src/BigGustave/Palette.cs b/src/BigGustave/Palette.cs new file mode 100644 index 0000000..278b671 --- /dev/null +++ b/src/BigGustave/Palette.cs @@ -0,0 +1,19 @@ +namespace BigGustave +{ + internal class Palette + { + public byte[] Data { get; } + + public Palette(byte[] data) + { + Data = data; + } + + public Pixel GetPixel(int index) + { + var start = index * 3; + + return new Pixel(Data[start], Data[start + 1], Data[start + 2], 255, false); + } + } +} \ No newline at end of file diff --git a/src/BigGustave/Pixel.cs b/src/BigGustave/Pixel.cs index 51ae831..6b9fb06 100644 --- a/src/BigGustave/Pixel.cs +++ b/src/BigGustave/Pixel.cs @@ -1,17 +1,43 @@ namespace BigGustave { + /// + /// A pixel in a image. + /// public readonly struct Pixel { + /// + /// The red value for the pixel. + /// public byte R { get; } + /// + /// The green value for the pixel. + /// public byte G { get; } + /// + /// The blue value for the pixel. + /// public byte B { get; } + /// + /// The alpha transparency value for the pixel. + /// public byte A { get; } - + + /// + /// Whether the pixel is grayscale (if , and will all have the same value). + /// public bool IsGrayscale { get; } + /// + /// Create a new . + /// + /// The red value for the pixel. + /// The green value for the pixel. + /// The blue value for the pixel. + /// The alpha transparency value for the pixel. + /// Whether the pixel is grayscale. public Pixel(byte r, byte g, byte b, byte a, bool isGrayscale) { R = r; @@ -21,6 +47,20 @@ public Pixel(byte r, byte g, byte b, byte a, bool isGrayscale) IsGrayscale = isGrayscale; } + /// + /// Create a new grayscale . + /// + /// The grayscale value. + public Pixel(byte grayscale) + { + R = grayscale; + G = grayscale; + B = grayscale; + A = 255; + IsGrayscale = true; + } + + /// public override bool Equals(object obj) { if (obj is Pixel pixel) @@ -35,11 +75,17 @@ public override bool Equals(object obj) return false; } + /// + /// Whether the pixel values are equal. + /// + /// The other pixel. + /// if all pixel values are equal otherwise . public bool Equals(Pixel other) { return R == other.R && G == other.G && B == other.B && A == other.A && IsGrayscale == other.IsGrayscale; } + /// public override int GetHashCode() { unchecked @@ -53,6 +99,7 @@ public override int GetHashCode() } } + /// public override string ToString() { return $"({R}, {G}, {B}, {A})"; diff --git a/src/BigGustave/Png.cs b/src/BigGustave/Png.cs index 42585a2..5766e98 100644 --- a/src/BigGustave/Png.cs +++ b/src/BigGustave/Png.cs @@ -3,10 +3,16 @@ using System; using System.IO; + /// + /// A PNG image. Call to open from file or bytes. + /// public class Png { private readonly RawPngData data; + /// + /// The header data from the PNG image. + /// public ImageHeader Header { get; } /// @@ -47,8 +53,22 @@ internal Png(ImageHeader header, RawPngData data) /// /// The stream containing PNG data to be read. /// Optional: A visitor which is called whenever a chunk is read by the library. - /// + /// The data from the stream. public static Png Open(Stream stream, IChunkVisitor chunkVisitor = null) => PngOpener.Open(stream, chunkVisitor); + + /// + /// Read the PNG image from the bytes. + /// + /// The bytes of the PNG data to be read. + /// Optional: A visitor which is called whenever a chunk is read by the library. + /// The data from the bytes. + public static Png Open(byte[] bytes, IChunkVisitor chunkVisitor = null) + { + using (var memoryStream = new MemoryStream(bytes)) + { + return PngOpener.Open(memoryStream, chunkVisitor); + } + } } } diff --git a/src/BigGustave/PngBuilder.cs b/src/BigGustave/PngBuilder.cs index d004b13..b06ebed 100644 --- a/src/BigGustave/PngBuilder.cs +++ b/src/BigGustave/PngBuilder.cs @@ -4,6 +4,12 @@ using System.IO.Compression; using System.Text; + /// + /// Used to construct PNG images. Call to make a new builder. + /// + /// + /// The created image is not compliant with all image viewers due to ZLib compatibility issues. + /// public class PngBuilder { private static readonly byte[] FakeDeflateHeader = { 120, 156 }; @@ -14,6 +20,9 @@ public class PngBuilder private readonly int height; private readonly int bytesPerPixel; + /// + /// Create a builder for a PNG with the given width and size. + /// public static PngBuilder Create(int width, int height, bool hasAlphaChannel) { var bpp = hasAlphaChannel ? 4 : 3; @@ -32,6 +41,9 @@ private PngBuilder(byte[] rawData, bool hasAlphaChannel, int width, int height, this.bytesPerPixel = bytesPerPixel; } + /// + /// Set the pixel value for the given column (x) and row (y). + /// public PngBuilder SetPixel(Pixel pixel, int x, int y) { var start = (y * ((width * bytesPerPixel) + 1)) + 1 + (x * bytesPerPixel); @@ -48,6 +60,21 @@ public PngBuilder SetPixel(Pixel pixel, int x, int y) return this; } + /// + /// Get the bytes of the PNG file for this builder. + /// + public byte[] Save() + { + using (var memoryStream = new MemoryStream()) + { + Save(memoryStream); + return memoryStream.ToArray(); + } + } + + /// + /// Write the PNG file bytes to the provided stream. + /// public void Save(Stream outputStream) { outputStream.Write(HeaderValidationResult.ExpectedHeader, 0, HeaderValidationResult.ExpectedHeader.Length); diff --git a/src/BigGustave/PngOpener.cs b/src/BigGustave/PngOpener.cs index 04d1118..a595d64 100644 --- a/src/BigGustave/PngOpener.cs +++ b/src/BigGustave/PngOpener.cs @@ -180,21 +180,4 @@ private static ImageHeader ReadImageHeader(Stream stream, byte[] crc) (InterlaceMethod)interlaceMethod); } } - - public class Palette - { - public byte[] Data { get; } - - public Palette(byte[] data) - { - Data = data; - } - - public Pixel GetPixel(int index) - { - var start = index * 3; - - return new Pixel(Data[start], Data[start + 1], Data[start + 2], 255, false); - } - } } \ No newline at end of file diff --git a/src/BigGustave/RgbPixel.cs b/src/BigGustave/RgbPixel.cs deleted file mode 100644 index a603b2d..0000000 --- a/src/BigGustave/RgbPixel.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace BigGustave -{ - public readonly struct RgbPixel : IPixel - { - public byte R { get; } - - public byte G { get; } - - public byte B { get; } - - public RgbPixel(byte r, byte g, byte b) - { - R = r; - G = g; - B = b; - } - } -} \ No newline at end of file diff --git a/src/BigGustave/RgbaPixel.cs b/src/BigGustave/RgbaPixel.cs deleted file mode 100644 index cdf940e..0000000 --- a/src/BigGustave/RgbaPixel.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace BigGustave -{ - public readonly struct RgbaPixel : IPixel - { - public byte R { get; } - - public byte G { get; } - - public byte B { get; } - - public byte A { get; } - - public RgbaPixel(byte r, byte g, byte b, byte a) - { - R = r; - G = g; - B = b; - A = a; - } - - public override string ToString() - { - return $"{R}, {G}, {B}, {A}"; - } - } -} \ No newline at end of file