Skip to content

Instantly share code, notes, and snippets.

@equinox2k
Created September 20, 2019 18:20
Show Gist options
  • Select an option

  • Save equinox2k/5b7f593c6442a5c81d9c570c04dce5d5 to your computer and use it in GitHub Desktop.

Select an option

Save equinox2k/5b7f593c6442a5c81d9c570c04dce5d5 to your computer and use it in GitHub Desktop.
PNG Optimization
using SixLabors.ImageSharp;
using System;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Advanced;
namespace TestCatalog
{
public class PNGOptimizer
{
private byte[] _imageData;
internal PNGOptimizer(byte[] imageData)
{
_imageData = imageData;
}
private static uint ReverseBytes(uint value)
{
return (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 | (value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
}
private static byte[] RemoveChunks(Stream inputStream)
{
var IHDR = new byte[] { (byte)'I', (byte)'H', (byte)'D', (byte)'R' };
var PLTE = new byte[] { (byte)'P', (byte)'L', (byte)'T', (byte)'E' };
var IEND = new byte[] { (byte)'I', (byte)'E', (byte)'N', (byte)'D' };
var tRNS = new byte[] { (byte)'t', (byte)'R', (byte)'N', (byte)'S' };
var IDAT = new byte[] { (byte)'I', (byte)'D', (byte)'A', (byte)'T' };
using (var outputStream = new MemoryStream())
{
using (var binaryReader = new BinaryReader(inputStream))
{
using (var binaryWriter = new BinaryWriter(outputStream))
{
var header = binaryReader.ReadBytes(8);
var expectedHeader = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
if (!header.SequenceEqual(expectedHeader))
{
throw new Exception("Unexpected header");
}
binaryWriter.Write(header);
while (true)
{
var length = ReverseBytes(binaryReader.ReadUInt32());
var type = binaryReader.ReadBytes(4);
if (!type.SequenceEqual(IHDR) && !type.SequenceEqual(PLTE) && !type.SequenceEqual(IEND) && !type.SequenceEqual(tRNS) && !type.SequenceEqual(IDAT))
{
var saved = length + 4;
binaryReader.BaseStream.Position = binaryReader.BaseStream.Position + saved;
}
else
{
binaryWriter.Write(ReverseBytes(length));
binaryWriter.Write(type);
binaryWriter.Write(binaryReader.ReadBytes((int)length + 4));
if (type.SequenceEqual(IEND))
{
break;
}
}
}
}
}
return outputStream.ToArray();
}
}
public static PNGOptimizer Load(string filePath)
{
using (var fileStream = File.OpenRead(filePath))
{
return Load(fileStream);
}
}
public static PNGOptimizer Load(Stream inputStream)
{
using (var image = Image.Load<Rgba32>(inputStream))
{
var span = image.GetPixelSpan();
for (int i = 0; i < span.Length; i++)
{
Rgba32 rgba32 = default;
span[i].ToRgba32(ref rgba32);
if (rgba32.A == 0)
{
rgba32.R = 0;
rgba32.G = 0;
rgba32.B = 0;
}
span[i].FromRgba32(rgba32);
}
using (var processStream = new MemoryStream())
{
var pngEncoder = new PngEncoder
{
ColorType = PngColorType.Palette,
BitDepth = PngBitDepth.Bit8,
FilterMethod = PngFilterMethod.Adaptive,
InterlaceMethod = PngInterlaceMode.None,
CompressionLevel = 9
};
image.Save(processStream, pngEncoder);
processStream.Position = 0;
return new PNGOptimizer(RemoveChunks(processStream));
}
}
}
public void Save(string filePath)
{
using (var fileStream = File.Create(filePath))
{
Save(fileStream);
}
}
public void Save(Stream fileStream)
{
using (var binaryWriter = new BinaryWriter(fileStream))
{
binaryWriter.Write(_imageData);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment