Skip to content

Instantly share code, notes, and snippets.

@Buthrakaur
Created May 7, 2010 13:23
Show Gist options
  • Save Buthrakaur/393409 to your computer and use it in GitHub Desktop.
Save Buthrakaur/393409 to your computer and use it in GitHub Desktop.
using System;
using System.Drawing;
using System.IO;
using System.Text;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
namespace PNG.Net
{
public interface IPngStreamWriter
{
void WritePixels(Color[] pixels);
void Close();
}
/// <summary>
/// supports currently only 8bpp (256 color) grayscale
/// </summary>
public class PngStreamWriter: IPngStreamWriter
{
private const int BitDepth = 8;
private const byte FilterType = 0;
private readonly Stream output;
private readonly Crc32 crc = new Crc32();
private readonly Size imageSize;
private int ImagePixels { get { return imageSize.Width * imageSize.Height; } }
public PngStreamWriter(Stream output, Size imageSize)
{
this.output = output;
this.imageSize = imageSize;
WriteHeader();
}
private void WriteHeader()
{
//PNG header
WriteBuffer(new byte[] {137, 80, 78, 71, 13, 10, 26, 10});
//IHDR
WriteChunk("IHDR", GetIHDRChunkData());
WriteIDATHeader();
}
private long idatSectionOffset;
private void WriteIDATHeader()
{
idatSectionOffset = output.Position;
//Data length 4bytes
//tohle je potreba prepsat spravnou hodnotou po dokonceni
WriteBuffer(IntTo4Bytes(imageSize.Width * imageSize.Height));
//Chunk type 4bytes
var chunkTypeBytes = ChunkTypeToBytes("IDAT");
WriteBuffer(chunkTypeBytes);
deflaterStream = new DeflaterOutputStream(output);
}
private int pixelsWritten;
private DeflaterOutputStream deflaterStream;
public void WritePixels(Color[] pixels)
{
if (pixelsWritten >= ImagePixels)
{
throw new InvalidOperationException("no more free lines remaining");
}
if (pixelsWritten + pixels.Length > ImagePixels)
{
throw new InvalidOperationException("you are trying to write too many pixels");
}
var data = ConvertPixelsToBytes(pixels);
deflaterStream.Write(data, 0, data.Length);
//CRC must be calculated from all deflated data later
pixelsWritten += pixels.Length;
}
public void Close()
{
AddPixelstToFillUpImageDimensions();
FinalizeIDATSection();
pixelsWritten = 0;
WriteChunk("IEND", new byte[0]);
}
private void FinalizeIDATSection()
{
deflaterStream.Finish();
deflaterStream.Flush();
var idatDataLength = (int) (output.Position-idatSectionOffset-4-4);
deflaterStream = null;
//calculate and write CRC
var crcVal = IntTo4Bytes(CalculateCrcForIDATSection());
output.Write(crcVal, 0, crcVal.Length);
//repair length
output.Seek(idatSectionOffset, SeekOrigin.Begin);
output.Write(IntTo4Bytes(idatDataLength), 0, 4);
output.Seek(0, SeekOrigin.End);
}
private int CalculateCrcForIDATSection()
{
crc.Reset();
output.Seek(idatSectionOffset + 4, SeekOrigin.Begin);
var buf = new byte[512];
while(true)
{
var cnt = output.Read(buf, 0, buf.Length);
crc.Update(buf, 0, cnt);
if (cnt < buf.Length)
{
break;
}
}
var crcValue = (int) crc.Value;
crc.Reset();
return crcValue;
}
private void WriteChunk(string chunkType, byte[] chunkData)
{
if (chunkType.Length != 4)
{
throw new ArgumentException("chunk name must have 4 characters", "chunkType");
}
//Data length 4bytes
WriteBuffer(IntTo4Bytes(chunkData.Length));
//Chunk type 4bytes
var chunkTypeBytes = ChunkTypeToBytes(chunkType);
WriteBuffer(chunkTypeBytes);
//Chunk data
WriteBuffer(chunkData);
//CRC 4bytes
WriteBuffer(IntTo4Bytes(GetCrcForData(chunkTypeBytes, chunkData)));
}
private byte[] IntTo4Bytes(int n)
{
return new[] { (byte)((n >> 24) & 0xff), (byte)((n >> 16) & 0xff), (byte)((n >> 8) & 0xff), (byte)(n & 0xff) };
}
private byte[] ChunkTypeToBytes(string t)
{
var res = Encoding.ASCII.GetBytes(t);
if (res.Length != 4)
{
throw new ArgumentException("chunk type must have 4 characters", "t");
}
return res;
}
private int GetCrcForData(byte[] chunkType, byte[] data)
{
crc.Reset();
crc.Update(chunkType);
crc.Update(data);
var res = crc.Value;
crc.Reset();
return (int) res;
}
private void WriteBuffer(byte[] buf)
{
if (buf == null || buf.Length == 0) return;
output.Write(buf, 0, buf.Length);
}
private void AddPixelstToFillUpImageDimensions()
{
var pixelsTotal = ImagePixels;
if (pixelsWritten < pixelsTotal)
{
var remainingPixels = new Color[imageSize.Width];
for (var i = 0; i < pixelsTotal-pixelsWritten; i++)
{
remainingPixels[i] = Color.White;
}
WritePixels(remainingPixels);
}
}
private byte[] GetIHDRChunkData()
{
/*The IHDR chunk must appear FIRST. It contains:
Width: 4 bytes
Height: 4 bytes
Bit depth: 1 byte
Color type: 1 byte
Compression method: 1 byte
Filter method: 1 byte
Interlace method: 1 byte*/
var res = new byte[13];
Array.Copy(IntTo4Bytes(imageSize.Width), 0, res, 0, 4);
Array.Copy(IntTo4Bytes(imageSize.Height), 0, res, 4, 4);
//Bit depth is a single-byte integer giving the number of bits per sample or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, although not all values are allowed for all color types.
res[8] = BitDepth;
//Color type is a single-byte integer that describes the interpretation of the image data. Color type codes represent sums of the following values: 1 (palette used), 2 (color used), and 4 (alpha channel used). Valid values are 0, 2, 3, 4, and 6.
res[9] = 0;
//Compression method is a single-byte integer that indicates the method used to compress the image data. At present, only compression method 0 (deflate/inflate compression with a sliding window of at most 32768 bytes) is defined. All standard PNG images must be compressed with this scheme.
res[10] = 0;
//Filter method is a single-byte integer that indicates the preprocessing method applied to the image data before compression. At present, only filter method 0 (adaptive filtering with five basic filter types) is defined.
res[11] = 0;
//Interlace method is a single-byte integer that indicates the transmission order of the image data. Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace).
//no interlace at the moment
res[12] = 0;
return res;
}
private byte[] ConvertPixelsToBytes(Color[] pixels)
{
var res = new byte[pixels.Length + 1];
res[0] = FilterType;
for (var i = 0; i < pixels.Length;i++ )
{
res[i + 1] = ColorToGrayscale(pixels[i]);
}
return res;
}
private byte ColorToGrayscale(Color c)
{
return (byte) (0.3*c.R + 0.59*c.G + 0.11*c.B);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment