Created
May 7, 2010 13:23
-
-
Save Buthrakaur/393409 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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