diff --git a/AA2Data/AA2Card.cs b/AA2Data/AA2Card.cs index 873b70c..1768002 100644 --- a/AA2Data/AA2Card.cs +++ b/AA2Data/AA2Card.cs @@ -6,6 +6,7 @@ using System.IO; using System.Drawing; using System.Drawing.Imaging; +using PNGNet; namespace AA2Data { @@ -16,16 +17,40 @@ public byte[] raw get { byte[] buffer = new byte[_image.Length + Offset]; - using (MemoryStream mem = new MemoryStream(_image.Length + Offset)) + using (MemoryStream mem = new MemoryStream()) //_image.Length + Offset using (BinaryWriter bw = new BinaryWriter(mem)) { + //AA2Unlimited chunk + using (MemoryStream ms = new MemoryStream(_image)) + using (MemoryStream ex = new MemoryStream()) + { + var img = new PNGImage(ms); + + bool containsChunk = img.Chunks.Any(x => x.Type == "aaUd"); + + if (!containsChunk && AA2UChunk != null) + { + img.Chunks.Insert(img.Chunks.FindIndex(x => x.Type == "IEND"), AA2UChunk); + } + else if (AA2UChunk != null) + { + int index = img.Chunks.FindIndex(x => x.Type == "aaUd"); + img.Chunks.RemoveAt(index); + img.Chunks.Insert(index, AA2UChunk); + } + + img.Write(ex, false); + + _image = ex.ToArray(); + } + bw.Write(_image); bw.Write(data.raw); bw.Write(RosterLength); bw.Write(_RosterImage); bw.Write(Offset); - mem.Position = 0; - mem.Read(buffer, 0, (int)mem.Length); + + buffer = mem.ToArray(); } return buffer; } @@ -34,17 +59,28 @@ public byte[] raw using (MemoryStream mem = new MemoryStream(value)) using (BinaryReader br = new BinaryReader(mem)) { - br.BaseStream.Seek(-4, SeekOrigin.End); - int offset = br.ReadInt32(); - br.BaseStream.Seek(0, SeekOrigin.Begin); + br.BaseStream.Seek(-4, SeekOrigin.End); + int offset = br.ReadInt32(); + br.BaseStream.Seek(0, SeekOrigin.Begin); - _image = br.ReadBytes((int)br.BaseStream.Length - offset); - data.raw = br.ReadBytes(3011); - int length = br.ReadInt32(); - _RosterImage = br.ReadBytes(length); + _image = br.ReadBytes((int)br.BaseStream.Length - offset); + + //AA2Unlimited chunk + using (MemoryStream ms = new MemoryStream(_image)) + { + var img = new PNGImage(ms); + + AA2UChunk = img.Chunks.DefaultIfEmpty(null).FirstOrDefault(x => x.Type == "aaUd"); + } + + data.raw = br.ReadBytes(3011); + int length = br.ReadInt32(); + _RosterImage = br.ReadBytes(length); } } } + + private Chunk AA2UChunk = null; private byte[] _image; public Image Image diff --git a/AA2Data/AA2Data.csproj b/AA2Data/AA2Data.csproj index 845e60c..4bb178f 100644 --- a/AA2Data/AA2Data.csproj +++ b/AA2Data/AA2Data.csproj @@ -67,6 +67,12 @@ + + + {d5cb46e9-e69b-4b45-a15a-c0087833dd4d} + PNGNet + + INIT_FINISHING_STATE ---. + * / | (2) (5) | + * / v (5) | + * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) + * \ | (3) | ,-------' + * | | | (3) / + * v v (5) v v + * (1) -> BUSY_STATE ----> FINISHING_STATE + * | (6) + * v + * FINISHED_STATE + * \_____________________________________/ + * | (7) + * v + * CLOSED_STATE + * + * (1) If we should produce a header we start in INIT_STATE, otherwise + * we start in BUSY_STATE. + * (2) A dictionary may be set only when we are in INIT_STATE, then + * we change the state as indicated. + * (3) Whether a dictionary is set or not, on the first call of deflate + * we change to BUSY_STATE. + * (4) -- intentionally left blank -- :) + * (5) FINISHING_STATE is entered, when flush() is called to indicate that + * there is no more INPUT. There are also states indicating, that + * the header wasn't written yet. + * (6) FINISHED_STATE is entered, when everything has been flushed to the + * internal pending output buffer. + * (7) At any time (7) + * + */ + + private static int IS_SETDICT = 0x01; + private static int IS_FLUSHING = 0x04; + private static int IS_FINISHING = 0x08; + + private static int INIT_STATE = 0x00; + private static int SETDICT_STATE = 0x01; +// private static int INIT_FINISHING_STATE = 0x08; +// private static int SETDICT_FINISHING_STATE = 0x09; + private static int BUSY_STATE = 0x10; + private static int FLUSHING_STATE = 0x14; + private static int FINISHING_STATE = 0x1c; + private static int FINISHED_STATE = 0x1e; + private static int CLOSED_STATE = 0x7f; + + /// + /// Compression level. + /// + private int level; + + /// + /// should we include a header. + /// + private bool noHeader; + +// /// +// /// Compression strategy. +// /// +// private int strategy; + + /// + /// The current state. + /// + private int state; + + /// + /// The total bytes of output written. + /// + private int totalOut; + + /// + /// The pending output. + /// + private DeflaterPending pending; + + /// + /// The deflater engine. + /// + private DeflaterEngine engine; + + /// + /// Creates a new deflater with default compression level. + /// + public Deflater() : this(DEFAULT_COMPRESSION, false) + { + + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. + /// + /// if lvl is out of range. + public Deflater(int lvl) : this(lvl, false) + { + + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION. + /// + /// + /// true, if we should suppress the deflate header at the + /// beginning and the adler checksum at the end of the output. This is + /// useful for the GZIP format. + /// + /// if lvl is out of range. + public Deflater(int lvl, bool nowrap) + { + if (lvl == DEFAULT_COMPRESSION) { + lvl = 6; + } else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION) { + throw new ArgumentOutOfRangeException("lvl"); + } + + pending = new DeflaterPending(); + engine = new DeflaterEngine(pending); + this.noHeader = nowrap; + SetStrategy(DeflateStrategy.Default); + SetLevel(lvl); + Reset(); + } + + + /// + /// Resets the deflater. The deflater acts afterwards as if it was + /// just created with the same compression level and strategy as it + /// had before. + /// + public void Reset() + { + state = (noHeader ? BUSY_STATE : INIT_STATE); + totalOut = 0; + pending.Reset(); + engine.Reset(); + } + + /// + /// Gets the current adler checksum of the data that was processed so far. + /// + public int Adler { + get { + return engine.Adler; + } + } + + /// + /// Gets the number of input bytes processed so far. + /// + public int TotalIn { + get { + return engine.TotalIn; + } + } + + /// + /// Gets the number of output bytes so far. + /// + public int TotalOut { + get { + return totalOut; + } + } + + /// + /// Flushes the current input block. Further calls to deflate() will + /// produce enough output to inflate everything in the current input + /// block. This is not part of Sun's JDK so I have made it package + /// private. It is used by DeflaterOutputStream to implement + /// flush(). + /// + public void Flush() + { + state |= IS_FLUSHING; + } + + /// + /// Finishes the deflater with the current input block. It is an error + /// to give more input after this method was called. This method must + /// be called to force all bytes to be flushed. + /// + public void Finish() + { + state |= IS_FLUSHING | IS_FINISHING; + } + + /// + /// Returns true if the stream was finished and no more output bytes + /// are available. + /// + public bool IsFinished { + get { + return state == FINISHED_STATE && pending.IsFlushed; + } + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method can also return true when the stream + /// was finished. + /// + public bool IsNeedingInput { + get { + return engine.NeedsInput(); + } + } + + /// + /// Sets the data which should be compressed next. This should be only + /// called when needsInput indicates that more input is needed. + /// If you call setInput when needsInput() returns false, the + /// previous input that is still pending will be thrown away. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// This call is equivalent to setInput(input, 0, input.length). + /// + /// + /// the buffer containing the input data. + /// + /// + /// if the buffer was finished() or ended(). + /// + public void SetInput(byte[] input) + { + SetInput(input, 0, input.Length); + } + + /// + /// Sets the data which should be compressed next. This should be + /// only called when needsInput indicates that more input is needed. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// + /// + /// the buffer containing the input data. + /// + /// + /// the start of the data. + /// + /// + /// the length of the data. + /// + /// + /// if the buffer was finished() or ended() or if previous input is still pending. + /// + public void SetInput(byte[] input, int off, int len) + { + if ((state & IS_FINISHING) != 0) { + throw new InvalidOperationException("finish()/end() already called"); + } + engine.SetInput(input, off, len); + } + + /// + /// Sets the compression level. There is no guarantee of the exact + /// position of the change, but if you call this when needsInput is + /// true the change of compression level will occur somewhere near + /// before the end of the so far given input. + /// + /// + /// the new compression level. + /// + public void SetLevel(int lvl) + { + if (lvl == DEFAULT_COMPRESSION) { + lvl = 6; + } else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION) { + throw new ArgumentOutOfRangeException("lvl"); + } + + + if (level != lvl) { + level = lvl; + engine.SetLevel(lvl); + } + } + + /// + /// Sets the compression strategy. Strategy is one of + /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact + /// position where the strategy is changed, the same as for + /// setLevel() applies. + /// + /// + /// the new compression strategy. + /// + public void SetStrategy(DeflateStrategy stgy) + { + engine.Strategy = stgy; + } + + /// + /// Deflates the current input block to the given array. It returns + /// the number of bytes compressed, or 0 if either + /// needsInput() or finished() returns true or length is zero. + /// + /// + /// the buffer where to write the compressed data. + /// + public int Deflate(byte[] output) + { + return Deflate(output, 0, output.Length); + } + + /// + /// Deflates the current input block to the given array. It returns + /// the number of bytes compressed, or 0 if either + /// needsInput() or finished() returns true or length is zero. + /// + /// + /// the buffer where to write the compressed data. + /// + /// + /// the offset into the output array. + /// + /// + /// the maximum number of bytes that may be written. + /// + /// + /// if end() was called. + /// + /// + /// if offset and/or length don't match the array length. + /// + public int Deflate(byte[] output, int offset, int length) + { + int origLength = length; + + if (state == CLOSED_STATE) { + throw new InvalidOperationException("Deflater closed"); + } + + if (state < BUSY_STATE) { + /* output header */ + int header = (DEFLATED + + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; + int level_flags = (level - 1) >> 1; + if (level_flags < 0 || level_flags > 3) { + level_flags = 3; + } + header |= level_flags << 6; + if ((state & IS_SETDICT) != 0) { + /* Dictionary was set */ + header |= DeflaterConstants.PRESET_DICT; + } + header += 31 - (header % 31); + + + pending.WriteShortMSB(header); + if ((state & IS_SETDICT) != 0) { + int chksum = engine.Adler; + engine.ResetAdler(); + pending.WriteShortMSB(chksum >> 16); + pending.WriteShortMSB(chksum & 0xffff); + } + + state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING)); + } + + for (;;) { + int count = pending.Flush(output, offset, length); + offset += count; + totalOut += count; + length -= count; + + if (length == 0 || state == FINISHED_STATE) { + break; + } + + if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0)) { + if (state == BUSY_STATE) { + /* We need more input now */ + return origLength - length; + } else if (state == FLUSHING_STATE) { + if (level != NO_COMPRESSION) { + /* We have to supply some lookahead. 8 bit lookahead + * are needed by the zlib inflater, and we must fill + * the next byte, so that all bits are flushed. + */ + int neededbits = 8 + ((-pending.BitCount) & 7); + while (neededbits > 0) { + /* write a static tree block consisting solely of + * an EOF: + */ + pending.WriteBits(2, 10); + neededbits -= 10; + } + } + state = BUSY_STATE; + } else if (state == FINISHING_STATE) { + pending.AlignToByte(); + /* We have completed the stream */ + if (!noHeader) { + int adler = engine.Adler; + pending.WriteShortMSB(adler >> 16); + pending.WriteShortMSB(adler & 0xffff); + } + state = FINISHED_STATE; + } + } + } + return origLength - length; + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// This call is equivalent to setDictionary(dict, 0, dict.Length). + /// + /// + /// the dictionary. + /// + /// + /// if setInput () or deflate () were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dict) + { + SetDictionary(dict, 0, dict.Length); + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// The dictionary should be a byte array containing strings that are + /// likely to occur in the data which should be compressed. The + /// dictionary is not stored in the compressed output, only a + /// checksum. To decompress the output you need to supply the same + /// dictionary again. + /// + /// + /// the dictionary. + /// + /// + /// an offset into the dictionary. + /// + /// + /// the length of the dictionary. + /// + /// + /// if setInput () or deflate () were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dict, int offset, int length) + { + if (state != INIT_STATE) { + throw new InvalidOperationException(); + } + + state = SETDICT_STATE; + engine.SetDictionary(dict, offset, length); + } + } +} diff --git a/PNGNet/Compression/DeflaterConstants.cs b/PNGNet/Compression/DeflaterConstants.cs new file mode 100644 index 0000000..6d73200 --- /dev/null +++ b/PNGNet/Compression/DeflaterConstants.cs @@ -0,0 +1,84 @@ +// DeflaterConstants.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + /// + /// This class contains constants used for the deflater. + /// + public class DeflaterConstants + { + public const bool DEBUGGING = false; + + public const int STORED_BLOCK = 0; + public const int STATIC_TREES = 1; + public const int DYN_TREES = 2; + public const int PRESET_DICT = 0x20; + + public const int DEFAULT_MEM_LEVEL = 8; + + public const int MAX_MATCH = 258; + public const int MIN_MATCH = 3; + + public const int MAX_WBITS = 15; + public const int WSIZE = 1 << MAX_WBITS; + public const int WMASK = WSIZE - 1; + + public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; + public const int HASH_SIZE = 1 << HASH_BITS; + public const int HASH_MASK = HASH_SIZE - 1; + public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; + + public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; + public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; + + public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); + public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE-5); + + public const int DEFLATE_STORED = 0; + public const int DEFLATE_FAST = 1; + public const int DEFLATE_SLOW = 2; + + public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; + public static int[] MAX_LAZY = { 0, 4, 5, 6, 4,16, 16, 32, 128, 258 }; + public static int[] NICE_LENGTH = { 0, 8,16,32,16,32,128,128, 258, 258 }; + public static int[] MAX_CHAIN = { 0, 4, 8,32,16,32,128,256,1024,4096 }; + public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; + } +} diff --git a/PNGNet/Compression/DeflaterEngine.cs b/PNGNet/Compression/DeflaterEngine.cs new file mode 100644 index 0000000..6c9d4e2 --- /dev/null +++ b/PNGNet/Compression/DeflaterEngine.cs @@ -0,0 +1,650 @@ +// DeflaterEngine.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Checksums; + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + public enum DeflateStrategy { + // The default strategy. + Default = 0, + + // This strategy will only allow longer string repetitions. It is + // useful for random data with a small character set. + Filtered = 1, + + // This strategy will not look for string repetitions at all. It + // only encodes with Huffman trees (which means, that more common + // characters get a smaller encoding. + HuffmanOnly = 2 + } + + public class DeflaterEngine : DeflaterConstants + { + static int TOO_FAR = 4096; + + int ins_h; +// private byte[] buffer; + short[] head; + short[] prev; + + int matchStart, matchLen; + bool prevAvailable; + int blockStart; + int strstart, lookahead; + byte[] window; + + DeflateStrategy strategy; + int max_chain, max_lazy, niceLength, goodLength; + + /// + /// The current compression function. + /// + int comprFunc; + + /// + /// The input data for compression. + /// + byte[] inputBuf; + + /// + /// The total bytes of input read. + /// + int totalIn; + + /// + /// The offset into inputBuf, where input data starts. + /// + int inputOff; + + /// + /// The end offset of the input data. + /// + int inputEnd; + + DeflaterPending pending; + DeflaterHuffman huffman; + + /// + /// The adler checksum + /// + Adler32 adler; + + public DeflaterEngine(DeflaterPending pending) + { + this.pending = pending; + huffman = new DeflaterHuffman(pending); + adler = new Adler32(); + + window = new byte[2 * WSIZE]; + head = new short[HASH_SIZE]; + prev = new short[WSIZE]; + + /* We start at index 1, to avoid a implementation deficiency, that + * we cannot build a repeat pattern at index 0. + */ + blockStart = strstart = 1; + } + + public void Reset() + { + huffman.Reset(); + adler.Reset(); + blockStart = strstart = 1; + lookahead = 0; + totalIn = 0; + prevAvailable = false; + matchLen = MIN_MATCH - 1; + + for (int i = 0; i < HASH_SIZE; i++) { + head[i] = 0; + } + + for (int i = 0; i < WSIZE; i++) { + prev[i] = 0; + } + } + + public void ResetAdler() + { + adler.Reset(); + } + + public int Adler { + get { + return (int)adler.Value; + } + } + + public int TotalIn { + get { + return totalIn; + } + } + + public DeflateStrategy Strategy { + get { + return strategy; + } + set { + strategy = value; + } + } + + public void SetLevel(int lvl) + { + goodLength = DeflaterConstants.GOOD_LENGTH[lvl]; + max_lazy = DeflaterConstants.MAX_LAZY[lvl]; + niceLength = DeflaterConstants.NICE_LENGTH[lvl]; + max_chain = DeflaterConstants.MAX_CHAIN[lvl]; + + if (DeflaterConstants.COMPR_FUNC[lvl] != comprFunc) { +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("Change from "+comprFunc +" to " +// + DeflaterConstants.COMPR_FUNC[lvl]); +// } + switch (comprFunc) { + case DEFLATE_STORED: + if (strstart > blockStart) { + huffman.FlushStoredBlock(window, blockStart, + strstart - blockStart, false); + blockStart = strstart; + } + UpdateHash(); + break; + case DEFLATE_FAST: + if (strstart > blockStart) { + huffman.FlushBlock(window, blockStart, strstart - blockStart, + false); + blockStart = strstart; + } + break; + case DEFLATE_SLOW: + if (prevAvailable) { + huffman.TallyLit(window[strstart-1] & 0xff); + } + if (strstart > blockStart) { + huffman.FlushBlock(window, blockStart, strstart - blockStart, + false); + blockStart = strstart; + } + prevAvailable = false; + matchLen = MIN_MATCH - 1; + break; + } + comprFunc = COMPR_FUNC[lvl]; + } + } + + private void UpdateHash() + { +// if (DEBUGGING) { +// Console.WriteLine("updateHash: "+strstart); +// } + ins_h = (window[strstart] << HASH_SHIFT) ^ window[strstart + 1]; + } + + private int InsertString() + { + short match; + int hash = ((ins_h << HASH_SHIFT) ^ window[strstart + (MIN_MATCH -1)]) & HASH_MASK; + +// if (DEBUGGING) { +// if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^ +// (window[strstart + 1] << HASH_SHIFT) ^ +// (window[strstart + 2])) & HASH_MASK)) { +// throw new Exception("hash inconsistent: "+hash+"/" +// +window[strstart]+"," +// +window[strstart+1]+"," +// +window[strstart+2]+","+HASH_SHIFT); +// } +// } + + prev[strstart & WMASK] = match = head[hash]; + head[hash] = (short)strstart; + ins_h = hash; + return match & 0xffff; + } + + void SlideWindow() + { + Array.Copy(window, WSIZE, window, 0, WSIZE); + matchStart -= WSIZE; + strstart -= WSIZE; + blockStart -= WSIZE; + + /* Slide the hash table (could be avoided with 32 bit values + * at the expense of memory usage). + */ + for (int i = 0; i < HASH_SIZE; ++i) { + int m = head[i] & 0xffff; + head[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0); + } + + /* Slide the prev table. */ + for (int i = 0; i < WSIZE; i++) { + int m = prev[i] & 0xffff; + prev[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0); + } + } + + public void FillWindow() + { + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (strstart >= WSIZE + MAX_DIST) { + SlideWindow(); + } + + /* If there is not enough lookahead, but still some input left, + * read in the input + */ + while (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) { + int more = 2 * WSIZE - lookahead - strstart; + + if (more > inputEnd - inputOff) { + more = inputEnd - inputOff; + } + + System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more); + adler.Update(inputBuf, inputOff, more); + + inputOff += more; + totalIn += more; + lookahead += more; + } + + if (lookahead >= MIN_MATCH) { + UpdateHash(); + } + } + + private bool FindLongestMatch(int curMatch) + { + int chainLength = this.max_chain; + int niceLength = this.niceLength; + short[] prev = this.prev; + int scan = this.strstart; + int match; + int best_end = this.strstart + matchLen; + int best_len = Math.Max(matchLen, MIN_MATCH - 1); + + int limit = Math.Max(strstart - MAX_DIST, 0); + + int strend = strstart + MAX_MATCH - 1; + byte scan_end1 = window[best_end - 1]; + byte scan_end = window[best_end]; + + /* Do not waste too much time if we already have a good match: */ + if (best_len >= this.goodLength) { + chainLength >>= 2; + } + + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (niceLength > lookahead) { + niceLength = lookahead; + } + + if (DeflaterConstants.DEBUGGING && strstart > 2 * WSIZE - MIN_LOOKAHEAD) { + throw new InvalidOperationException("need lookahead"); + } + + do { + if (DeflaterConstants.DEBUGGING && curMatch >= strstart) { + throw new InvalidOperationException("future match"); + } + if (window[curMatch + best_len] != scan_end || + window[curMatch + best_len - 1] != scan_end1 || + window[curMatch] != window[scan] || + window[curMatch + 1] != window[scan + 1]) { + continue; + } + + match = curMatch + 2; + scan += 2; + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && scan < strend) ; + + if (scan > best_end) { + // if (DeflaterConstants.DEBUGGING && ins_h == 0) + // System.err.println("Found match: "+curMatch+"-"+(scan-strstart)); + matchStart = curMatch; + best_end = scan; + best_len = scan - strstart; + if (best_len >= niceLength) { + break; + } + + scan_end1 = window[best_end - 1]; + scan_end = window[best_end]; + } + scan = strstart; + } while ((curMatch = (prev[curMatch & WMASK] & 0xffff)) > limit && --chainLength != 0); + + matchLen = Math.Min(best_len, lookahead); + return matchLen >= MIN_MATCH; + } + + public void SetDictionary(byte[] buffer, int offset, int length) + { + if (DeflaterConstants.DEBUGGING && strstart != 1) { + throw new InvalidOperationException("strstart not 1"); + } + adler.Update(buffer, offset, length); + if (length < MIN_MATCH) { + return; + } + if (length > MAX_DIST) { + offset += length - MAX_DIST; + length = MAX_DIST; + } + + System.Array.Copy(buffer, offset, window, strstart, length); + + UpdateHash(); + --length; + while (--length > 0) { + InsertString(); + strstart++; + } + strstart += 2; + blockStart = strstart; + } + + private bool DeflateStored(bool flush, bool finish) + { + if (!flush && lookahead == 0) { + return false; + } + + strstart += lookahead; + lookahead = 0; + + int storedLen = strstart - blockStart; + + if ((storedLen >= DeflaterConstants.MAX_BLOCK_SIZE) || /* Block is full */ + (blockStart < WSIZE && storedLen >= MAX_DIST) || /* Block may move out of window */ + flush) { + bool lastBlock = finish; + if (storedLen > DeflaterConstants.MAX_BLOCK_SIZE) { + storedLen = DeflaterConstants.MAX_BLOCK_SIZE; + lastBlock = false; + } + +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("storedBlock["+storedLen+","+lastBlock+"]"); +// } + + huffman.FlushStoredBlock(window, blockStart, storedLen, lastBlock); + blockStart += storedLen; + return !lastBlock; + } + return true; + } + + private bool DeflateFast(bool flush, bool finish) + { + if (lookahead < MIN_LOOKAHEAD && !flush) { + return false; + } + + while (lookahead >= MIN_LOOKAHEAD || flush) { + if (lookahead == 0) { + /* We are flushing everything */ + huffman.FlushBlock(window, blockStart, strstart - blockStart, finish); + blockStart = strstart; + return false; + } + + if (strstart > 2 * WSIZE - MIN_LOOKAHEAD) + { + /* slide window, as findLongestMatch need this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + int hashHead; + if (lookahead >= MIN_MATCH && + (hashHead = InsertString()) != 0 && + strategy != DeflateStrategy.HuffmanOnly && + strstart - hashHead <= MAX_DIST && + FindLongestMatch(hashHead)) { + /* longestMatch sets matchStart and matchLen */ +// if (DeflaterConstants.DEBUGGING) { +// for (int i = 0 ; i < matchLen; i++) { +// if (window[strstart+i] != window[matchStart + i]) { +// throw new Exception(); +// } +// } +// } + + huffman.TallyDist(strstart - matchStart, matchLen); + + lookahead -= matchLen; + if (matchLen <= max_lazy && lookahead >= MIN_MATCH) { + while (--matchLen > 0) { + ++strstart; + InsertString(); + } + ++strstart; + } else { + strstart += matchLen; + if (lookahead >= MIN_MATCH - 1) { + UpdateHash(); + } + } + matchLen = MIN_MATCH - 1; + continue; + } else { + /* No match found */ + huffman.TallyLit(window[strstart] & 0xff); + ++strstart; + --lookahead; + } + + if (huffman.IsFull()) { + bool lastBlock = finish && lookahead == 0; + huffman.FlushBlock(window, blockStart, strstart - blockStart, + lastBlock); + blockStart = strstart; + return !lastBlock; + } + } + return true; + } + + private bool DeflateSlow(bool flush, bool finish) + { + if (lookahead < MIN_LOOKAHEAD && !flush) { + return false; + } + + while (lookahead >= MIN_LOOKAHEAD || flush) { + if (lookahead == 0) { + if (prevAvailable) { + huffman.TallyLit(window[strstart-1] & 0xff); + } + prevAvailable = false; + + /* We are flushing everything */ + if (DeflaterConstants.DEBUGGING && !flush) { + throw new Exception("Not flushing, but no lookahead"); + } + huffman.FlushBlock(window, blockStart, strstart - blockStart, + finish); + blockStart = strstart; + return false; + } + + if (strstart >= 2 * WSIZE - MIN_LOOKAHEAD) + { + /* slide window, as findLongestMatch need this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + int prevMatch = matchStart; + int prevLen = matchLen; + if (lookahead >= MIN_MATCH) { + int hashHead = InsertString(); + if (strategy != DeflateStrategy.HuffmanOnly && hashHead != 0 && strstart - hashHead <= MAX_DIST && FindLongestMatch(hashHead)) + { + /* longestMatch sets matchStart and matchLen */ + + /* Discard match if too small and too far away */ + if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == MIN_MATCH && strstart - matchStart > TOO_FAR))) { + matchLen = MIN_MATCH - 1; + } + } + } + + /* previous match was better */ + if (prevLen >= MIN_MATCH && matchLen <= prevLen) { +// if (DeflaterConstants.DEBUGGING) { +// for (int i = 0 ; i < matchLen; i++) { +// if (window[strstart-1+i] != window[prevMatch + i]) +// throw new Exception(); +// } +// } + huffman.TallyDist(strstart - 1 - prevMatch, prevLen); + prevLen -= 2; + do { + strstart++; + lookahead--; + if (lookahead >= MIN_MATCH) { + InsertString(); + } + } while (--prevLen > 0); + strstart ++; + lookahead--; + prevAvailable = false; + matchLen = MIN_MATCH - 1; + } else { + if (prevAvailable) { + huffman.TallyLit(window[strstart-1] & 0xff); + } + prevAvailable = true; + strstart++; + lookahead--; + } + + if (huffman.IsFull()) { + int len = strstart - blockStart; + if (prevAvailable) { + len--; + } + bool lastBlock = (finish && lookahead == 0 && !prevAvailable); + huffman.FlushBlock(window, blockStart, len, lastBlock); + blockStart += len; + return !lastBlock; + } + } + return true; + } + + public bool Deflate(bool flush, bool finish) + { + bool progress; + do { + FillWindow(); + bool canFlush = flush && inputOff == inputEnd; +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("window: ["+blockStart+","+strstart+"," +// +lookahead+"], "+comprFunc+","+canFlush); +// } + switch (comprFunc) { + case DEFLATE_STORED: + progress = DeflateStored(canFlush, finish); + break; + case DEFLATE_FAST: + progress = DeflateFast(canFlush, finish); + break; + case DEFLATE_SLOW: + progress = DeflateSlow(canFlush, finish); + break; + default: + throw new InvalidOperationException("unknown comprFunc"); + } + } while (pending.IsFlushed && progress); /* repeat while we have no pending output and progress was made */ + return progress; + } + + public void SetInput(byte[] buf, int off, int len) + { + if (inputOff < inputEnd) { + throw new InvalidOperationException("Old input was not completely processed"); + } + + int end = off + len; + + /* We want to throw an ArrayIndexOutOfBoundsException early. The + * check is very tricky: it also handles integer wrap around. + */ + if (0 > off || off > end || end > buf.Length) { + throw new ArgumentOutOfRangeException(); + } + + inputBuf = buf; + inputOff = off; + inputEnd = end; + } + + public bool NeedsInput() + { + return inputEnd == inputOff; + } + } +} diff --git a/PNGNet/Compression/DeflaterHuffman.cs b/PNGNet/Compression/DeflaterHuffman.cs new file mode 100644 index 0000000..ad1e288 --- /dev/null +++ b/PNGNet/Compression/DeflaterHuffman.cs @@ -0,0 +1,778 @@ +// DeflaterHuffman.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + /// + /// This is the DeflaterHuffman class. + /// + /// This class is not thread safe. This is inherent in the API, due + /// to the split of deflate and setInput. + /// + /// author of the original java version : Jochen Hoenicke + /// + public class DeflaterHuffman + { + private static int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); + private static int LITERAL_NUM = 286; + private static int DIST_NUM = 30; + private static int BITLEN_NUM = 19; + private static int REP_3_6 = 16; + private static int REP_3_10 = 17; + private static int REP_11_138 = 18; + private static int EOF_SYMBOL = 256; + private static int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + private static byte[] bit4Reverse = { + 0, + 8, + 4, + 12, + 2, + 10, + 6, + 14, + 1, + 9, + 5, + 13, + 3, + 11, + 7, + 15 + }; + + + public class Tree + { + public short[] freqs; + public short[] codes; + public byte[] length; + public int[] bl_counts; + public int minNumCodes, numCodes; + public int maxLength; + DeflaterHuffman dh; + + public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength) + { + this.dh = dh; + this.minNumCodes = minCodes; + this.maxLength = maxLength; + freqs = new short[elems]; + bl_counts = new int[maxLength]; + } + + public void Reset() + { + for (int i = 0; i < freqs.Length; i++) { + freqs[i] = 0; + } + codes = null; + length = null; + } + + public void WriteSymbol(int code) + { +// if (DeflaterConstants.DEBUGGING) { +// freqs[code]--; +// // Console.Write("writeSymbol("+freqs.length+","+code+"): "); +// } + dh.pending.WriteBits(codes[code] & 0xffff, length[code]); + } + + public void CheckEmpty() + { + bool empty = true; + for (int i = 0; i < freqs.Length; i++) { + if (freqs[i] != 0) { + Console.WriteLine("freqs["+i+"] == "+freqs[i]); + empty = false; + } + } + if (!empty) { + throw new Exception(); + } + Console.WriteLine("checkEmpty suceeded!"); + } + + public void SetStaticCodes(short[] stCodes, byte[] stLength) + { + codes = stCodes; + length = stLength; + } + + public void BuildCodes() + { + int numSymbols = freqs.Length; + int[] nextCode = new int[maxLength]; + int code = 0; + codes = new short[freqs.Length]; + +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("buildCodes: "+freqs.Length); +// } + + for (int bits = 0; bits < maxLength; bits++) { + nextCode[bits] = code; + code += bl_counts[bits] << (15 - bits); +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("bits: "+(bits+1)+" count: "+bl_counts[bits] +// +" nextCode: "+code); // HACK : Integer.toHexString( +// } + } + if (DeflaterConstants.DEBUGGING && code != 65536) { + throw new Exception("Inconsistent bl_counts!"); + } + + for (int i=0; i < numCodes; i++) { + int bits = length[i]; + if (bits > 0) { +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+")," // HACK : Integer.toHexString( +// +bits); +// } + codes[i] = BitReverse(nextCode[bits-1]); + nextCode[bits-1] += 1 << (16 - bits); + } + } + } + + private void BuildLength(int[] childs) + { + this.length = new byte [freqs.Length]; + int numNodes = childs.Length / 2; + int numLeafs = (numNodes + 1) / 2; + int overflow = 0; + + for (int i = 0; i < maxLength; i++) { + bl_counts[i] = 0; + } + + /* First calculate optimal bit lengths */ + int[] lengths = new int[numNodes]; + lengths[numNodes-1] = 0; + + for (int i = numNodes - 1; i >= 0; i--) { + if (childs[2*i+1] != -1) { + int bitLength = lengths[i] + 1; + if (bitLength > maxLength) { + bitLength = maxLength; + overflow++; + } + lengths[childs[2*i]] = lengths[childs[2*i+1]] = bitLength; + } else { + /* A leaf node */ + int bitLength = lengths[i]; + bl_counts[bitLength - 1]++; + this.length[childs[2*i]] = (byte) lengths[i]; + } + } + +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("Tree "+freqs.Length+" lengths:"); +// for (int i=0; i < numLeafs; i++) { +// Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] +// + " len: "+length[childs[2*i]]); +// } +// } + + if (overflow == 0) { + return; + } + + int incrBitLen = maxLength - 1; + do { + /* Find the first bit length which could increase: */ + while (bl_counts[--incrBitLen] == 0) + ; + + /* Move this node one down and remove a corresponding + * amount of overflow nodes. + */ + do { + bl_counts[incrBitLen]--; + bl_counts[++incrBitLen]++; + overflow -= 1 << (maxLength - 1 - incrBitLen); + } while (overflow > 0 && incrBitLen < maxLength - 1); + } while (overflow > 0); + + /* We may have overshot above. Move some nodes from maxLength to + * maxLength-1 in that case. + */ + bl_counts[maxLength-1] += overflow; + bl_counts[maxLength-2] -= overflow; + + /* Now recompute all bit lengths, scanning in increasing + * frequency. It is simpler to reconstruct all lengths instead of + * fixing only the wrong ones. This idea is taken from 'ar' + * written by Haruhiko Okumura. + * + * The nodes were inserted with decreasing frequency into the childs + * array. + */ + int nodePtr = 2 * numLeafs; + for (int bits = maxLength; bits != 0; bits--) { + int n = bl_counts[bits-1]; + while (n > 0) { + int childPtr = 2*childs[nodePtr++]; + if (childs[childPtr + 1] == -1) { + /* We found another leaf */ + length[childs[childPtr]] = (byte) bits; + n--; + } + } + } +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("*** After overflow elimination. ***"); +// for (int i=0; i < numLeafs; i++) { +// Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] +// + " len: "+length[childs[2*i]]); +// } +// } + } + + public void BuildTree() + { + int numSymbols = freqs.Length; + + /* heap is a priority queue, sorted by frequency, least frequent + * nodes first. The heap is a binary tree, with the property, that + * the parent node is smaller than both child nodes. This assures + * that the smallest node is the first parent. + * + * The binary tree is encoded in an array: 0 is root node and + * the nodes 2*n+1, 2*n+2 are the child nodes of node n. + */ + int[] heap = new int[numSymbols]; + int heapLen = 0; + int maxCode = 0; + for (int n = 0; n < numSymbols; n++) { + int freq = freqs[n]; + if (freq != 0) { + /* Insert n into heap */ + int pos = heapLen++; + int ppos; + while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq) { + heap[pos] = heap[ppos]; + pos = ppos; + } + heap[pos] = n; + + maxCode = n; + } + } + + /* We could encode a single literal with 0 bits but then we + * don't see the literals. Therefore we force at least two + * literals to avoid this case. We don't care about order in + * this case, both literals get a 1 bit code. + */ + while (heapLen < 2) { + int node = maxCode < 2 ? ++maxCode : 0; + heap[heapLen++] = node; + } + + numCodes = Math.Max(maxCode + 1, minNumCodes); + + int numLeafs = heapLen; + int[] childs = new int[4*heapLen - 2]; + int[] values = new int[2*heapLen - 1]; + int numNodes = numLeafs; + for (int i = 0; i < heapLen; i++) { + int node = heap[i]; + childs[2*i] = node; + childs[2*i+1] = -1; + values[i] = freqs[node] << 8; + heap[i] = i; + } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + do { + int first = heap[0]; + int last = heap[--heapLen]; + + /* Propagate the hole to the leafs of the heap */ + int ppos = 0; + int path = 1; + while (path < heapLen) { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path+1]]) { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = path * 2 + 1; + } + + /* Now propagate the last element down along path. Normally + * it shouldn't go too deep. + */ + int lastVal = values[last]; + while ((path = ppos) > 0 && values[heap[ppos = (path - 1)/2]] > lastVal) { + heap[path] = heap[ppos]; + } + heap[path] = last; + + + int second = heap[0]; + + /* Create a new node father of first and second */ + last = numNodes++; + childs[2*last] = first; + childs[2*last+1] = second; + int mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff); + values[last] = lastVal = values[first] + values[second] - mindepth + 1; + + /* Again, propagate the hole to the leafs */ + ppos = 0; + path = 1; + while (path < heapLen) { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path+1]]) { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = ppos * 2 + 1; + } + + /* Now propagate the new element down along path */ + while ((path = ppos) > 0 && values[heap[ppos = (path - 1)/2]] > lastVal) { + heap[path] = heap[ppos]; + } + heap[path] = last; + } while (heapLen > 1); + + if (heap[0] != childs.Length / 2 - 1) { + throw new Exception("Weird!"); + } + BuildLength(childs); + } + + public int GetEncodedLength() + { + int len = 0; + for (int i = 0; i < freqs.Length; i++) { + len += freqs[i] * length[i]; + } + return len; + } + + public void CalcBLFreq(Tree blTree) + { + int max_count; /* max repeat count */ + int min_count; /* min repeat count */ + int count; /* repeat count of the current code */ + int curlen = -1; /* length of current code */ + + int i = 0; + while (i < numCodes) { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) { + max_count = 138; + min_count = 3; + } else { + max_count = 6; + min_count = 3; + if (curlen != nextlen) { + blTree.freqs[nextlen]++; + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) { + i++; + if (++count >= max_count) { + break; + } + } + + if (count < min_count) { + blTree.freqs[curlen] += (short)count; + } else if (curlen != 0) { + blTree.freqs[REP_3_6]++; + } else if (count <= 10) { + blTree.freqs[REP_3_10]++; + } else { + blTree.freqs[REP_11_138]++; + } + } + } + + public void WriteTree(Tree blTree) + { + int max_count; /* max repeat count */ + int min_count; /* min repeat count */ + int count; /* repeat count of the current code */ + int curlen = -1; /* length of current code */ + + int i = 0; + while (i < numCodes) { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) { + max_count = 138; + min_count = 3; + } else { + max_count = 6; + min_count = 3; + if (curlen != nextlen) { + blTree.WriteSymbol(nextlen); + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) { + i++; + if (++count >= max_count) { + break; + } + } + + if (count < min_count) { + while (count-- > 0) { + blTree.WriteSymbol(curlen); + } + } + else if (curlen != 0) { + blTree.WriteSymbol(REP_3_6); + dh.pending.WriteBits(count - 3, 2); + } else if (count <= 10) { + blTree.WriteSymbol(REP_3_10); + dh.pending.WriteBits(count - 3, 3); + } else { + blTree.WriteSymbol(REP_11_138); + dh.pending.WriteBits(count - 11, 7); + } + } + } + } + + public DeflaterPending pending; + private Tree literalTree, distTree, blTree; + + private short[] d_buf; + private byte[] l_buf; + private int last_lit; + private int extra_bits; + + private static short[] staticLCodes; + private static byte[] staticLLength; + private static short[] staticDCodes; + private static byte[] staticDLength; + + /// + /// Reverse the bits of a 16 bit value. + /// + public static short BitReverse(int value) + { + return (short) (bit4Reverse[value & 0xF] << 12 + | bit4Reverse[(value >> 4) & 0xF] << 8 + | bit4Reverse[(value >> 8) & 0xF] << 4 + | bit4Reverse[value >> 12]); + } + + + static DeflaterHuffman() + { + /* See RFC 1951 3.2.6 */ + /* Literal codes */ + staticLCodes = new short[LITERAL_NUM]; + staticLLength = new byte[LITERAL_NUM]; + int i = 0; + while (i < 144) { + staticLCodes[i] = BitReverse((0x030 + i) << 8); + staticLLength[i++] = 8; + } + while (i < 256) { + staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); + staticLLength[i++] = 9; + } + while (i < 280) { + staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); + staticLLength[i++] = 7; + } + while (i < LITERAL_NUM) { + staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); + staticLLength[i++] = 8; + } + + /* Distant codes */ + staticDCodes = new short[DIST_NUM]; + staticDLength = new byte[DIST_NUM]; + for (i = 0; i < DIST_NUM; i++) { + staticDCodes[i] = BitReverse(i << 11); + staticDLength[i] = 5; + } + } + + public DeflaterHuffman(DeflaterPending pending) + { + this.pending = pending; + + literalTree = new Tree(this, LITERAL_NUM, 257, 15); + distTree = new Tree(this, DIST_NUM, 1, 15); + blTree = new Tree(this, BITLEN_NUM, 4, 7); + + d_buf = new short[BUFSIZE]; + l_buf = new byte [BUFSIZE]; + } + + public void Reset() + { + last_lit = 0; + extra_bits = 0; + literalTree.Reset(); + distTree.Reset(); + blTree.Reset(); + } + + private int L_code(int len) + { + if (len == 255) { + return 285; + } + + int code = 257; + while (len >= 8) { + code += 4; + len >>= 1; + } + return code + len; + } + + private int D_code(int distance) + { + int code = 0; + while (distance >= 4) { + code += 2; + distance >>= 1; + } + return code + distance; + } + + public void SendAllTrees(int blTreeCodes) + { + blTree.BuildCodes(); + literalTree.BuildCodes(); + distTree.BuildCodes(); + pending.WriteBits(literalTree.numCodes - 257, 5); + pending.WriteBits(distTree.numCodes - 1, 5); + pending.WriteBits(blTreeCodes - 4, 4); + for (int rank = 0; rank < blTreeCodes; rank++) { + pending.WriteBits(blTree.length[BL_ORDER[rank]], 3); + } + literalTree.WriteTree(blTree); + distTree.WriteTree(blTree); +// if (DeflaterConstants.DEBUGGING) { +// blTree.CheckEmpty(); +// } + } + + public void CompressBlock() + { + for (int i = 0; i < last_lit; i++) { + int litlen = l_buf[i] & 0xff; + int dist = d_buf[i]; + if (dist-- != 0) { +// if (DeflaterConstants.DEBUGGING) { +// Console.Write("["+(dist+1)+","+(litlen+3)+"]: "); +// } + + int lc = L_code(litlen); + literalTree.WriteSymbol(lc); + + int bits = (lc - 261) / 4; + if (bits > 0 && bits <= 5) { + pending.WriteBits(litlen & ((1 << bits) - 1), bits); + } + + int dc = D_code(dist); + distTree.WriteSymbol(dc); + + bits = dc / 2 - 1; + if (bits > 0) { + pending.WriteBits(dist & ((1 << bits) - 1), bits); + } + } else { +// if (DeflaterConstants.DEBUGGING) { +// if (litlen > 32 && litlen < 127) { +// Console.Write("("+(char)litlen+"): "); +// } else { +// Console.Write("{"+litlen+"}: "); +// } +// } + literalTree.WriteSymbol(litlen); + } + } +// if (DeflaterConstants.DEBUGGING) { +// Console.Write("EOF: "); +// } + literalTree.WriteSymbol(EOF_SYMBOL); +// if (DeflaterConstants.DEBUGGING) { +// literalTree.CheckEmpty(); +// distTree.CheckEmpty(); +// } + } + + public void FlushStoredBlock(byte[] stored, int stored_offset, int stored_len, bool lastBlock) + { +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("Flushing stored block "+ stored_len); +// } + pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + + (lastBlock ? 1 : 0), 3); + pending.AlignToByte(); + pending.WriteShort(stored_len); + pending.WriteShort(~stored_len); + pending.WriteBlock(stored, stored_offset, stored_len); + Reset(); + } + + public void FlushBlock(byte[] stored, int stored_offset, int stored_len, bool lastBlock) + { + literalTree.freqs[EOF_SYMBOL]++; + + /* Build trees */ + literalTree.BuildTree(); + distTree.BuildTree(); + + /* Calculate bitlen frequency */ + literalTree.CalcBLFreq(blTree); + distTree.CalcBLFreq(blTree); + + /* Build bitlen tree */ + blTree.BuildTree(); + + int blTreeCodes = 4; + for (int i = 18; i > blTreeCodes; i--) { + if (blTree.length[BL_ORDER[i]] > 0) { + blTreeCodes = i+1; + } + } + int opt_len = 14 + blTreeCodes * 3 + blTree.GetEncodedLength() + + literalTree.GetEncodedLength() + distTree.GetEncodedLength() + + extra_bits; + + int static_len = extra_bits; + for (int i = 0; i < LITERAL_NUM; i++) { + static_len += literalTree.freqs[i] * staticLLength[i]; + } + for (int i = 0; i < DIST_NUM; i++) { + static_len += distTree.freqs[i] * staticDLength[i]; + } + if (opt_len >= static_len) { + /* Force static trees */ + opt_len = static_len; + } + + if (stored_offset >= 0 && stored_len+4 < opt_len >> 3) { + /* Store Block */ +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("Storing, since " + stored_len + " < " + opt_len +// + " <= " + static_len); +// } + FlushStoredBlock(stored, stored_offset, stored_len, lastBlock); + } else if (opt_len == static_len) { + /* Encode with static tree */ + pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); + literalTree.SetStaticCodes(staticLCodes, staticLLength); + distTree.SetStaticCodes(staticDCodes, staticDLength); + CompressBlock(); + Reset(); + } else { + /* Encode with dynamic tree */ + pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); + SendAllTrees(blTreeCodes); + CompressBlock(); + Reset(); + } + } + + public bool IsFull() + { + return last_lit + 16 >= BUFSIZE; // HACK: This was == 'last_lit == BUFSIZE', but errors occured with DeflateFast + } + + public bool TallyLit(int lit) + { +// if (DeflaterConstants.DEBUGGING) { +// if (lit > 32 && lit < 127) { +// Console.WriteLine("("+(char)lit+")"); +// } else { +// Console.WriteLine("{"+lit+"}"); +// } +// } + d_buf[last_lit] = 0; + l_buf[last_lit++] = (byte)lit; + literalTree.freqs[lit]++; + return IsFull(); + } + + public bool TallyDist(int dist, int len) + { +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("["+dist+","+len+"]"); +// } + + d_buf[last_lit] = (short)dist; + l_buf[last_lit++] = (byte)(len - 3); + + int lc = L_code(len - 3); + literalTree.freqs[lc]++; + if (lc >= 265 && lc < 285) { + extra_bits += (lc - 261) / 4; + } + + int dc = D_code(dist - 1); + distTree.freqs[dc]++; + if (dc >= 4) { + extra_bits += dc / 2 - 1; + } + return IsFull(); + } + } +} diff --git a/PNGNet/Compression/DeflaterPending.cs b/PNGNet/Compression/DeflaterPending.cs new file mode 100644 index 0000000..f50b3b7 --- /dev/null +++ b/PNGNet/Compression/DeflaterPending.cs @@ -0,0 +1,51 @@ +// DeflaterPending.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + /// + /// This class stores the pending output of the Deflater. + /// + /// author of the original java version : Jochen Hoenicke + /// + public class DeflaterPending : PendingBuffer + { + public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) + { + } + } +} diff --git a/PNGNet/Compression/Inflater.cs b/PNGNet/Compression/Inflater.cs new file mode 100644 index 0000000..cb6c841 --- /dev/null +++ b/PNGNet/Compression/Inflater.cs @@ -0,0 +1,778 @@ +// Inflater.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Checksums; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + /// + /// Inflater is used to decompress data that has been compressed according + /// to the "deflate" standard described in rfc1950. + /// + /// The usage is as following. First you have to set some input with + /// setInput(), then inflate() it. If inflate doesn't + /// inflate any bytes there may be three reasons: + ///
    + ///
  • needsInput() returns true because the input buffer is empty. + /// You have to provide more input with setInput(). + /// NOTE: needsInput() also returns true when, the stream is finished. + ///
  • + ///
  • needsDictionary() returns true, you have to provide a preset + /// dictionary with setDictionary().
  • + ///
  • finished() returns true, the inflater has finished.
  • + ///
+ /// Once the first output byte is produced, a dictionary will not be + /// needed at a later stage. + /// + /// author of the original java version : John Leuner, Jochen Hoenicke + ///
+ public class Inflater + { + /// + /// Copy lengths for literal codes 257..285 + /// + private static int[] CPLENS = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + + /// + /// Extra bits for literal codes 257..285 + /// + private static int[] CPLEXT = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + + /// + /// Copy offsets for distance codes 0..29 + /// + private static int[] CPDIST = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + /// + /// Extra bits for distance codes + /// + private static int[] CPDEXT = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + }; + + /// + /// This are the state in which the inflater can be. + /// + private const int DECODE_HEADER = 0; + private const int DECODE_DICT = 1; + private const int DECODE_BLOCKS = 2; + private const int DECODE_STORED_LEN1 = 3; + private const int DECODE_STORED_LEN2 = 4; + private const int DECODE_STORED = 5; + private const int DECODE_DYN_HEADER = 6; + private const int DECODE_HUFFMAN = 7; + private const int DECODE_HUFFMAN_LENBITS = 8; + private const int DECODE_HUFFMAN_DIST = 9; + private const int DECODE_HUFFMAN_DISTBITS = 10; + private const int DECODE_CHKSUM = 11; + private const int FINISHED = 12; + + /// + /// This variable contains the current state. + /// + private int mode; + + /// + /// The adler checksum of the dictionary or of the decompressed + /// stream, as it is written in the header resp. footer of the + /// compressed stream. + /// Only valid if mode is DECODE_DICT or DECODE_CHKSUM. + /// + private int readAdler; + + /// + /// The number of bits needed to complete the current state. This + /// is valid, if mode is DECODE_DICT, DECODE_CHKSUM, + /// DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. + /// + private int neededBits; + private int repLength, repDist; + private int uncomprLen; + + /// + /// True, if the last block flag was set in the last block of the + /// inflated stream. This means that the stream ends after the + /// current block. + /// + private bool isLastBlock; + + /// + /// The total number of inflated bytes. + /// + private int totalOut; + + /// + /// The total number of bytes set with setInput(). This is not the + /// value returned by getTotalIn(), since this also includes the + /// unprocessed input. + /// + private int totalIn; + + /// + /// This variable stores the nowrap flag that was given to the constructor. + /// True means, that the inflated stream doesn't contain a header nor the + /// checksum in the footer. + /// + private bool nowrap; + + private StreamManipulator input; + private OutputWindow outputWindow; + private InflaterDynHeader dynHeader; + private InflaterHuffmanTree litlenTree, distTree; + private Adler32 adler; + + /// + /// Creates a new inflater. + /// + public Inflater() : this(false) + { + } + + /// + /// Creates a new inflater. + /// + /// + /// true if no header and checksum field appears in the + /// stream. This is used for GZIPed input. For compatibility with + /// Sun JDK you should provide one byte of input more than needed in + /// this case. + /// + public Inflater(bool nowrap) + { + this.nowrap = nowrap; + this.adler = new Adler32(); + input = new StreamManipulator(); + outputWindow = new OutputWindow(); + mode = nowrap ? DECODE_BLOCKS : DECODE_HEADER; + } + + /// + /// Resets the inflater so that a new stream can be decompressed. All + /// pending input and output will be discarded. + /// + public void Reset() + { + mode = nowrap ? DECODE_BLOCKS : DECODE_HEADER; + totalIn = totalOut = 0; + input.Reset(); + outputWindow.Reset(); + dynHeader = null; + litlenTree = null; + distTree = null; + isLastBlock = false; + adler.Reset(); + } + + /// + /// Decodes the deflate header. + /// + /// + /// false if more input is needed. + /// + /// + /// if header is invalid. + /// + private bool DecodeHeader() + { + int header = input.PeekBits(16); + if (header < 0) { + return false; + } + input.DropBits(16); + /* The header is written in "wrong" byte order */ + header = ((header << 8) | (header >> 8)) & 0xffff; + if (header % 31 != 0) { + throw new FormatException("Header checksum illegal"); + } + + if ((header & 0x0f00) != (Deflater.DEFLATED << 8)) { + throw new FormatException("Compression Method unknown"); + } + + /* Maximum size of the backwards window in bits. + * We currently ignore this, but we could use it to make the + * inflater window more space efficient. On the other hand the + * full window (15 bits) is needed most times, anyway. + int max_wbits = ((header & 0x7000) >> 12) + 8; + */ + + if ((header & 0x0020) == 0) { // Dictionary flag? + mode = DECODE_BLOCKS; + } else { + mode = DECODE_DICT; + neededBits = 32; + } + return true; + } + + /// + /// Decodes the dictionary checksum after the deflate header. + /// + /// + /// false if more input is needed. + /// + private bool DecodeDict() + { + while (neededBits > 0) { + int dictByte = input.PeekBits(8); + if (dictByte < 0) { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | dictByte; + neededBits -= 8; + } + return false; + } + + /// + /// Decodes the huffman encoded symbols in the input stream. + /// + /// + /// false if more input is needed, true if output window is + /// full or the current block ends. + /// + /// + /// if deflated stream is invalid. + /// + private bool DecodeHuffman() + { + int free = outputWindow.GetFreeSpace(); + while (free >= 258) { + int symbol; + switch (mode) { + case DECODE_HUFFMAN: + /* This is the inner loop so it is optimized a bit */ + while (((symbol = litlenTree.GetSymbol(input)) & ~0xff) == 0) { + outputWindow.Write(symbol); + if (--free < 258) { + return true; + } + } + if (symbol < 257) { + if (symbol < 0) { + return false; + } else { + /* symbol == 256: end of block */ + distTree = null; + litlenTree = null; + mode = DECODE_BLOCKS; + return true; + } + } + + try { + repLength = CPLENS[symbol - 257]; + neededBits = CPLEXT[symbol - 257]; + } catch (Exception) { + throw new FormatException("Illegal rep length code"); + } + goto case DECODE_HUFFMAN_LENBITS;/* fall through */ + case DECODE_HUFFMAN_LENBITS: + if (neededBits > 0) { + mode = DECODE_HUFFMAN_LENBITS; + int i = input.PeekBits(neededBits); + if (i < 0) { + return false; + } + input.DropBits(neededBits); + repLength += i; + } + mode = DECODE_HUFFMAN_DIST; + goto case DECODE_HUFFMAN_DIST;/* fall through */ + case DECODE_HUFFMAN_DIST: + symbol = distTree.GetSymbol(input); + if (symbol < 0) { + return false; + } + try { + repDist = CPDIST[symbol]; + neededBits = CPDEXT[symbol]; + } catch (Exception) { + throw new FormatException("Illegal rep dist code"); + } + + goto case DECODE_HUFFMAN_DISTBITS;/* fall through */ + case DECODE_HUFFMAN_DISTBITS: + if (neededBits > 0) { + mode = DECODE_HUFFMAN_DISTBITS; + int i = input.PeekBits(neededBits); + if (i < 0) { + return false; + } + input.DropBits(neededBits); + repDist += i; + } + outputWindow.Repeat(repLength, repDist); + free -= repLength; + mode = DECODE_HUFFMAN; + break; + default: + throw new FormatException(); + } + } + return true; + } + + /// + /// Decodes the adler checksum after the deflate stream. + /// + /// + /// false if more input is needed. + /// + /// + /// DataFormatException, if checksum doesn't match. + /// + private bool DecodeChksum() + { + while (neededBits > 0) { + int chkByte = input.PeekBits(8); + if (chkByte < 0) { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | chkByte; + neededBits -= 8; + } + if ((int) adler.Value != readAdler) { + throw new FormatException("Adler chksum doesn't match: " + + (int)adler.Value + + " vs. " + readAdler); + } + mode = FINISHED; + return false; + } + + /// + /// Decodes the deflated stream. + /// + /// + /// false if more input is needed, or if finished. + /// + /// + /// DataFormatException, if deflated stream is invalid. + /// + private bool Decode() + { + switch (mode) { + case DECODE_HEADER: + return DecodeHeader(); + case DECODE_DICT: + return DecodeDict(); + case DECODE_CHKSUM: + return DecodeChksum(); + + case DECODE_BLOCKS: + if (isLastBlock) { + if (nowrap) { + mode = FINISHED; + return false; + } else { + input.SkipToByteBoundary(); + neededBits = 32; + mode = DECODE_CHKSUM; + return true; + } + } + + int type = input.PeekBits(3); + if (type < 0) { + return false; + } + input.DropBits(3); + + if ((type & 1) != 0) { + isLastBlock = true; + } + switch (type >> 1) { + case DeflaterConstants.STORED_BLOCK: + input.SkipToByteBoundary(); + mode = DECODE_STORED_LEN1; + break; + case DeflaterConstants.STATIC_TREES: + litlenTree = InflaterHuffmanTree.defLitLenTree; + distTree = InflaterHuffmanTree.defDistTree; + mode = DECODE_HUFFMAN; + break; + case DeflaterConstants.DYN_TREES: + dynHeader = new InflaterDynHeader(); + mode = DECODE_DYN_HEADER; + break; + default: + throw new FormatException("Unknown block type "+type); + } + return true; + + case DECODE_STORED_LEN1: { + if ((uncomprLen = input.PeekBits(16)) < 0) { + return false; + } + input.DropBits(16); + mode = DECODE_STORED_LEN2; + } + goto case DECODE_STORED_LEN2; /* fall through */ + case DECODE_STORED_LEN2: { + int nlen = input.PeekBits(16); + if (nlen < 0) { + return false; + } + input.DropBits(16); + if (nlen != (uncomprLen ^ 0xffff)) { + throw new FormatException("broken uncompressed block"); + } + mode = DECODE_STORED; + } + goto case DECODE_STORED;/* fall through */ + case DECODE_STORED: { + int more = outputWindow.CopyStored(input, uncomprLen); + uncomprLen -= more; + if (uncomprLen == 0) { + mode = DECODE_BLOCKS; + return true; + } + return !input.IsNeedingInput; + } + + case DECODE_DYN_HEADER: + if (!dynHeader.Decode(input)) { + return false; + } + + litlenTree = dynHeader.BuildLitLenTree(); + distTree = dynHeader.BuildDistTree(); + mode = DECODE_HUFFMAN; + goto case DECODE_HUFFMAN; /* fall through */ + case DECODE_HUFFMAN: + case DECODE_HUFFMAN_LENBITS: + case DECODE_HUFFMAN_DIST: + case DECODE_HUFFMAN_DISTBITS: + return DecodeHuffman(); + case FINISHED: + return false; + default: + throw new FormatException(); + } + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// the dictionary. + /// + /// + /// if no dictionary is needed. + /// + /// + /// if the dictionary checksum is wrong. + /// + public void SetDictionary(byte[] buffer) + { + SetDictionary(buffer, 0, buffer.Length); + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// the dictionary. + /// + /// + /// the offset into buffer where the dictionary starts. + /// + /// + /// the length of the dictionary. + /// + /// + /// if no dictionary is needed. + /// + /// + /// if the dictionary checksum is wrong. + /// + /// + /// if the off and/or len are wrong. + /// + public void SetDictionary(byte[] buffer, int off, int len) + { + if (!IsNeedingDictionary) { + throw new InvalidOperationException(); + } + + adler.Update(buffer, off, len); + if ((int) adler.Value != readAdler) { + throw new ArgumentException("Wrong adler checksum"); + } + adler.Reset(); + outputWindow.CopyDict(buffer, off, len); + mode = DECODE_BLOCKS; + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// the input. + /// + /// + /// if no input is needed. + /// + public void SetInput(byte[] buf) + { + SetInput(buf, 0, buf.Length); + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// the input. + /// + /// + /// the offset into buffer where the input starts. + /// + /// + /// the length of the input. + /// + /// + /// if no input is needed. + /// + /// + /// if the off and/or len are wrong. + /// + public void SetInput(byte[] buf, int off, int len) + { + input.SetInput(buf, off, len); + totalIn += len; + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether needsDictionary(), + /// needsInput() or finished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// the number of bytes written to the buffer, 0 if no further + /// output can be produced. + /// + /// + /// if buf has length 0. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buf) + { + return Inflate(buf, 0, buf.Length); + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether needsDictionary(), + /// needsInput() or finished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// the offset into buffer where the output should start. + /// + /// + /// the maximum length of the output. + /// + /// + /// the number of bytes written to the buffer, 0 if no further output can be produced. + /// + /// + /// if len is <= 0. + /// + /// + /// if the off and/or len are wrong. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buf, int off, int len) + { + if (len < 0) { + throw new ArgumentOutOfRangeException("len < 0"); + } + // Special case: len may be zero + if (len == 0) { + return 0; + } +/* // Check for correct buff, off, len triple + if (off < 0 || off + len >= buf.Length) { + throw new ArgumentException("off/len outside buf bounds"); + }*/ + int count = 0; + int more; + do { + if (mode != DECODE_CHKSUM) { + /* Don't give away any output, if we are waiting for the + * checksum in the input stream. + * + * With this trick we have always: + * needsInput() and not finished() + * implies more output can be produced. + */ + more = outputWindow.CopyOutput(buf, off, len); + adler.Update(buf, off, more); + off += more; + count += more; + totalOut += more; + len -= more; + if (len == 0) { + return count; + } + } + } while (Decode() || (outputWindow.GetAvailable() > 0 && + mode != DECODE_CHKSUM)); + return count; + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method also returns true when the stream is finished. + /// + public bool IsNeedingInput { + get { + return input.IsNeedingInput; + } + } + + /// + /// Returns true, if a preset dictionary is needed to inflate the input. + /// + public bool IsNeedingDictionary { + get { + return mode == DECODE_DICT && neededBits == 0; + } + } + + /// + /// Returns true, if the inflater has finished. This means, that no + /// input is needed and no output can be produced. + /// + public bool IsFinished { + get { + return mode == FINISHED && outputWindow.GetAvailable() == 0; + } + } + + /// + /// Gets the adler checksum. This is either the checksum of all + /// uncompressed bytes returned by inflate(), or if needsDictionary() + /// returns true (and thus no output was yet produced) this is the + /// adler checksum of the expected dictionary. + /// + /// + /// the adler checksum. + /// + public int Adler { + get { + return IsNeedingDictionary ? readAdler : (int) adler.Value; + } + } + + /// + /// Gets the total number of output bytes returned by inflate(). + /// + /// + /// the total number of output bytes. + /// + public int TotalOut { + get { + return totalOut; + } + } + + /// + /// Gets the total number of processed compressed input bytes. + /// + /// + /// the total number of bytes of processed input bytes. + /// + public int TotalIn { + get { + return totalIn - RemainingInput; + } + } + + /// + /// Gets the number of unprocessed input. Useful, if the end of the + /// stream is reached and you want to further process the bytes after + /// the deflate stream. + /// + /// + /// the number of bytes of the input which were not processed. + /// + public int RemainingInput { + get { + return input.AvailableBytes; + } + } + } +} diff --git a/PNGNet/Compression/InflaterDynHeader.cs b/PNGNet/Compression/InflaterDynHeader.cs new file mode 100644 index 0000000..94a7da3 --- /dev/null +++ b/PNGNet/Compression/InflaterDynHeader.cs @@ -0,0 +1,203 @@ +// InflaterDynHeader.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + class InflaterDynHeader + { + const int LNUM = 0; + const int DNUM = 1; + const int BLNUM = 2; + const int BLLENS = 3; + const int LENS = 4; + const int REPS = 5; + + static readonly int[] repMin = { 3, 3, 11 }; + static readonly int[] repBits = { 2, 3, 7 }; + + byte[] blLens; + byte[] litdistLens; + + InflaterHuffmanTree blTree; + + int mode; + int lnum, dnum, blnum, num; + int repSymbol; + byte lastLen; + int ptr; + + static readonly int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + public InflaterDynHeader() + { + } + + public bool Decode(StreamManipulator input) + { + decode_loop: + for (;;) { + switch (mode) { + case LNUM: + lnum = input.PeekBits(5); + if (lnum < 0) { + return false; + } + lnum += 257; + input.DropBits(5); + // System.err.println("LNUM: "+lnum); + mode = DNUM; + goto case DNUM; // fall through + case DNUM: + dnum = input.PeekBits(5); + if (dnum < 0) { + return false; + } + dnum++; + input.DropBits(5); + // System.err.println("DNUM: "+dnum); + num = lnum+dnum; + litdistLens = new byte[num]; + mode = BLNUM; + goto case BLNUM; // fall through + case BLNUM: + blnum = input.PeekBits(4); + if (blnum < 0) { + return false; + } + blnum += 4; + input.DropBits(4); + blLens = new byte[19]; + ptr = 0; + // System.err.println("BLNUM: "+blnum); + mode = BLLENS; + goto case BLLENS; // fall through + case BLLENS: + while (ptr < blnum) { + int len = input.PeekBits(3); + if (len < 0) { + return false; + } + input.DropBits(3); + // System.err.println("blLens["+BL_ORDER[ptr]+"]: "+len); + blLens[BL_ORDER[ptr]] = (byte) len; + ptr++; + } + blTree = new InflaterHuffmanTree(blLens); + blLens = null; + ptr = 0; + mode = LENS; + goto case LENS; // fall through + case LENS: { + int symbol; + while (((symbol = blTree.GetSymbol(input)) & ~15) == 0) { + /* Normal case: symbol in [0..15] */ + + // System.err.println("litdistLens["+ptr+"]: "+symbol); + litdistLens[ptr++] = lastLen = (byte)symbol; + + if (ptr == num) { + /* Finished */ + return true; + } + } + + /* need more input ? */ + if (symbol < 0) + return false; + + /* otherwise repeat code */ + if (symbol >= 17) { + /* repeat zero */ + // System.err.println("repeating zero"); + lastLen = 0; + } else { + if (ptr == 0) { + throw new Exception(); + } + } + repSymbol = symbol-16; + } + mode = REPS; + goto case REPS; // fall through + case REPS: + { + int bits = repBits[repSymbol]; + int count = input.PeekBits(bits); + if (count < 0) { + return false; + } + input.DropBits(bits); + count += repMin[repSymbol]; + // System.err.println("litdistLens repeated: "+count); + + if (ptr + count > num) { + throw new Exception(); + } + while (count-- > 0) { + litdistLens[ptr++] = lastLen; + } + + if (ptr == num) { + /* Finished */ + return true; + } + } + mode = LENS; + goto decode_loop; + } + } + } + + public InflaterHuffmanTree BuildLitLenTree() + { + byte[] litlenLens = new byte[lnum]; + Array.Copy(litdistLens, 0, litlenLens, 0, lnum); + return new InflaterHuffmanTree(litlenLens); + } + + public InflaterHuffmanTree BuildDistTree() + { + byte[] distLens = new byte[dnum]; + Array.Copy(litdistLens, lnum, distLens, 0, dnum); + return new InflaterHuffmanTree(distLens); + } + } +} diff --git a/PNGNet/Compression/InflaterHuffmanTree.cs b/PNGNet/Compression/InflaterHuffmanTree.cs new file mode 100644 index 0000000..dd4bbf0 --- /dev/null +++ b/PNGNet/Compression/InflaterHuffmanTree.cs @@ -0,0 +1,208 @@ +// InflaterHuffmanTree.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + public class InflaterHuffmanTree + { + private static int MAX_BITLEN = 15; + private short[] tree; + + public static InflaterHuffmanTree defLitLenTree, defDistTree; + + static InflaterHuffmanTree() + { + try { + byte[] codeLengths = new byte[288]; + int i = 0; + while (i < 144) { + codeLengths[i++] = 8; + } + while (i < 256) { + codeLengths[i++] = 9; + } + while (i < 280) { + codeLengths[i++] = 7; + } + while (i < 288) { + codeLengths[i++] = 8; + } + defLitLenTree = new InflaterHuffmanTree(codeLengths); + + codeLengths = new byte[32]; + i = 0; + while (i < 32) { + codeLengths[i++] = 5; + } + defDistTree = new InflaterHuffmanTree(codeLengths); + } catch (Exception) { + throw new ApplicationException("InflaterHuffmanTree: static tree length illegal"); + } + } + + /// + /// Constructs a Huffman tree from the array of code lengths. + /// + /// + /// the array of code lengths + /// + public InflaterHuffmanTree(byte[] codeLengths) + { + BuildTree(codeLengths); + } + + private void BuildTree(byte[] codeLengths) + { + int[] blCount = new int[MAX_BITLEN + 1]; + int[] nextCode = new int[MAX_BITLEN + 1]; + + for (int i = 0; i < codeLengths.Length; i++) { + int bits = codeLengths[i]; + if (bits > 0) + blCount[bits]++; + } + + int code = 0; + int treeSize = 512; + for (int bits = 1; bits <= MAX_BITLEN; bits++) { + nextCode[bits] = code; + code += blCount[bits] << (16 - bits); + if (bits >= 10) { + /* We need an extra table for bit lengths >= 10. */ + int start = nextCode[bits] & 0x1ff80; + int end = code & 0x1ff80; + treeSize += (end - start) >> (16 - bits); + } + } + if (code != 65536) { + throw new Exception("Code lengths don't add up properly."); + } + /* Now create and fill the extra tables from longest to shortest + * bit len. This way the sub trees will be aligned. + */ + tree = new short[treeSize]; + int treePtr = 512; + for (int bits = MAX_BITLEN; bits >= 10; bits--) { + int end = code & 0x1ff80; + code -= blCount[bits] << (16 - bits); + int start = code & 0x1ff80; + for (int i = start; i < end; i += 1 << 7) { + tree[DeflaterHuffman.BitReverse(i)] = (short) ((-treePtr << 4) | bits); + treePtr += 1 << (bits-9); + } + } + + for (int i = 0; i < codeLengths.Length; i++) { + int bits = codeLengths[i]; + if (bits == 0) { + continue; + } + code = nextCode[bits]; + int revcode = DeflaterHuffman.BitReverse(code); + if (bits <= 9) { + do { + tree[revcode] = (short) ((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < 512); + } else { + int subTree = tree[revcode & 511]; + int treeLen = 1 << (subTree & 15); + subTree = -(subTree >> 4); + do { + tree[subTree | (revcode >> 9)] = (short) ((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < treeLen); + } + nextCode[bits] = code + (1 << (16 - bits)); + } + + } + + /// + /// Reads the next symbol from input. The symbol is encoded using the + /// huffman tree. + /// + /// + /// input the input source. + /// + /// + /// the next symbol, or -1 if not enough input is available. + /// + public int GetSymbol(StreamManipulator input) + { + int lookahead, symbol; + if ((lookahead = input.PeekBits(9)) >= 0) { + if ((symbol = tree[lookahead]) >= 0) { + input.DropBits(symbol & 15); + return symbol >> 4; + } + int subtree = -(symbol >> 4); + int bitlen = symbol & 15; + if ((lookahead = input.PeekBits(bitlen)) >= 0) { + symbol = tree[subtree | (lookahead >> 9)]; + input.DropBits(symbol & 15); + return symbol >> 4; + } else { + int bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[subtree | (lookahead >> 9)]; + if ((symbol & 15) <= bits) { + input.DropBits(symbol & 15); + return symbol >> 4; + } else { + return -1; + } + } + } else { + int bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[lookahead]; + if (symbol >= 0 && (symbol & 15) <= bits) { + input.DropBits(symbol & 15); + return symbol >> 4; + } else { + return -1; + } + } + } + } +} + diff --git a/PNGNet/Compression/PendingBuffer.cs b/PNGNet/Compression/PendingBuffer.cs new file mode 100644 index 0000000..f823fbe --- /dev/null +++ b/PNGNet/Compression/PendingBuffer.cs @@ -0,0 +1,208 @@ +// PendingBuffer.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression { + + /// + /// This class is general purpose class for writing data to a buffer. + /// + /// It allows you to write bits as well as bytes + /// Based on DeflaterPending.java + /// + /// author of the original java version : Jochen Hoenicke + /// + public class PendingBuffer + { + protected byte[] buf; + int start; + int end; + + uint bits; + int bitCount; + + public PendingBuffer() : this( 4096 ) + { + + } + + public PendingBuffer(int bufsize) + { + buf = new byte[bufsize]; + } + + public void Reset() + { + start = end = bitCount = 0; + } + + public void WriteByte(int b) + { + if (DeflaterConstants.DEBUGGING && start != 0) + throw new Exception(); + buf[end++] = (byte) b; + } + + public void WriteShort(int s) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + buf[end++] = (byte) s; + buf[end++] = (byte) (s >> 8); + } + + public void WriteInt(int s) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + buf[end++] = (byte) s; + buf[end++] = (byte) (s >> 8); + buf[end++] = (byte) (s >> 16); + buf[end++] = (byte) (s >> 24); + } + + public void WriteBlock(byte[] block, int offset, int len) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + System.Array.Copy(block, offset, buf, end, len); + end += len; + } + + public int BitCount { + get { + return bitCount; + } + } + + public void AlignToByte() + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + if (bitCount > 0) { + buf[end++] = (byte) bits; + if (bitCount > 8) { + buf[end++] = (byte) (bits >> 8); + } + } + bits = 0; + bitCount = 0; + } + + public void WriteBits(int b, int count) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } +// if (DeflaterConstants.DEBUGGING) { +// Console.WriteLine("writeBits("+b+","+count+")"); +// } + bits |= (uint)(b << bitCount); + bitCount += count; + if (bitCount >= 16) { + buf[end++] = (byte) bits; + buf[end++] = (byte) (bits >> 8); + bits >>= 16; + bitCount -= 16; + } + } + + public void WriteShortMSB(int s) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + buf[end++] = (byte) (s >> 8); + buf[end++] = (byte) s; + } + + public bool IsFlushed { + get { + return end == 0; + } + } + + /// + /// Flushes the pending buffer into the given output array. If the + /// output array is to small, only a partial flush is done. + /// + /// + /// the output array; + /// + /// + /// the offset into output array; + /// + /// + /// length the maximum number of bytes to store; + /// + /// + /// IndexOutOfBoundsException if offset or length are invalid. + /// + public int Flush(byte[] output, int offset, int length) + { + if (bitCount >= 8) { + buf[end++] = (byte) bits; + bits >>= 8; + bitCount -= 8; + } + if (length > end - start) { + length = end - start; + System.Array.Copy(buf, start, output, offset, length); + start = 0; + end = 0; + } else { + System.Array.Copy(buf, start, output, offset, length); + start += length; + } + return length; + } + + public byte[] ToByteArray() + { + byte[] ret = new byte[ end - start ]; + System.Array.Copy(buf, start, ret, 0, ret.Length); + start = 0; + end = 0; + return ret; + } + } +} diff --git a/PNGNet/Compression/Streams/DeflaterOutputStream.cs b/PNGNet/Compression/Streams/DeflaterOutputStream.cs new file mode 100644 index 0000000..acaad07 --- /dev/null +++ b/PNGNet/Compression/Streams/DeflaterOutputStream.cs @@ -0,0 +1,296 @@ +// DeflaterOutputStream.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; +using System.IO; + +using ICSharpCode.SharpZipLib.Zip.Compression; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams { + + /// + /// This is a special FilterOutputStream deflating the bytes that are + /// written through it. It uses the Deflater for deflating. + /// + /// authors of the original java version : Tom Tromey, Jochen Hoenicke + /// + public class DeflaterOutputStream : Stream + { + /// + /// This buffer is used temporarily to retrieve the bytes from the + /// deflater and write them to the underlying output stream. + /// + protected byte[] buf; + + /// + /// The deflater which is used to deflate the stream. + /// + protected Deflater def; + + /// + /// base stream the deflater depends on. + /// + protected Stream baseOutputStream; + + /// + /// I needed to implement the abstract member. + /// + public override bool CanRead { + get { + return baseOutputStream.CanRead; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanSeek { + get { + return baseOutputStream.CanSeek; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanWrite { + get { + return baseOutputStream.CanWrite; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Length { + get { + return baseOutputStream.Length; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Position { + get { + return baseOutputStream.Position; + } + set { + baseOutputStream.Position = value; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Seek(long offset, SeekOrigin origin) + { + return baseOutputStream.Seek(offset, origin); + } + + /// + /// I needed to implement the abstract member. + /// + public override void SetLength(long val) + { + baseOutputStream.SetLength(val); + } + + /// + /// I needed to implement the abstract member. + /// + public override int ReadByte() + { + return baseOutputStream.ReadByte(); + } + + /// + /// I needed to implement the abstract member. + /// + public override int Read(byte[] b, int off, int len) + { + return baseOutputStream.Read(b, off, len); + } + + + /// + /// Deflates everything in the def's input buffers. This will call + /// def.deflate() until all bytes from the input buffers + /// are processed. + /// + protected void deflate() + { + while (!def.IsNeedingInput) { + int len = def.Deflate(buf, 0, buf.Length); + + // System.err.println("DOS deflated " + len + " baseOutputStream of " + buf.length); + if (len <= 0) { + break; + } + baseOutputStream.Write(buf, 0, len); + } + + if (!def.IsNeedingInput) { + throw new ApplicationException("Can't deflate all input?"); + } + } + + /// + /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + public DeflaterOutputStream(Stream baseOutputStream) : this(baseOutputStream, new Deflater(), 512) + { + + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + /// + /// the underlying deflater. + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater defl) :this(baseOutputStream, defl, 512) + { + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + /// + /// the underlying deflater. + /// + /// + /// the buffer size. + /// + /// + /// if bufsize isn't positive. + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater defl, int bufsize) + { + this.baseOutputStream = baseOutputStream; + if (bufsize <= 0) { + throw new InvalidOperationException("bufsize <= 0"); + } + buf = new byte[bufsize]; + def = defl; + } + + /// + /// Flushes the stream by calling flush() on the deflater and then + /// on the underlying stream. This ensures that all bytes are + /// flushed. + /// + public override void Flush() + { + def.Flush(); + deflate(); + baseOutputStream.Flush(); + } + + /// + /// Finishes the stream by calling finish() on the deflater. + /// + public virtual void Finish() + { + def.Finish(); + while (!def.IsFinished) { + int len = def.Deflate(buf, 0, buf.Length); + if (len <= 0) { + break; + } + baseOutputStream.Write(buf, 0, len); + } + if (!def.IsFinished) { + throw new ApplicationException("Can't deflate all input?"); + } + baseOutputStream.Flush(); + } + + /// + /// Calls finish () and closes the stream. + /// + public override void Close() + { + Finish(); + baseOutputStream.Close(); + } + + /// + /// Writes a single byte to the compressed output stream. + /// + /// + /// the byte value. + /// + public override void WriteByte(byte bval) + { + byte[] b = new byte[1]; + b[0] = (byte) bval; + Write(b, 0, 1); + } + + /// + /// Writes a len bytes from an array to the compressed stream. + /// + /// + /// the byte array. + /// + /// + /// the offset into the byte array where to start. + /// + /// + /// the number of bytes to write. + /// + public override void Write(byte[] buf, int off, int len) + { + // System.err.println("DOS with off " + off + " and len " + len); + def.SetInput(buf, off, len); + deflate(); + } + + } +} diff --git a/PNGNet/Compression/Streams/InflaterInputStream.cs b/PNGNet/Compression/Streams/InflaterInputStream.cs new file mode 100644 index 0000000..9e963a1 --- /dev/null +++ b/PNGNet/Compression/Streams/InflaterInputStream.cs @@ -0,0 +1,333 @@ +// InflaterInputStream.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; +using System.IO; + +using ICSharpCode.SharpZipLib.Zip.Compression; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams { + + /// + /// This filter stream is used to decompress data compressed baseInputStream the "deflate" + /// format. The "deflate" format is described baseInputStream RFC 1951. + /// + /// This stream may form the basis for other decompression filters, such + /// as the GzipInputStream. + /// + /// author of the original java version : John Leuner + /// + public class InflaterInputStream : Stream + { + //Variables + + /// + /// Decompressor for this filter + /// + protected Inflater inf; + + + /// + /// Byte array used as a buffer + /// + protected byte[] buf; + + /// + /// Size of buffer + /// + protected int len; + + //We just use this if we are decoding one byte at a time with the read() call + private byte[] onebytebuffer = new byte[1]; + + /// + /// base stream the inflater depends on. + /// + protected Stream baseInputStream; + + /// + /// I needed to implement the abstract member. + /// + public override bool CanRead { + get { + return baseInputStream.CanRead; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanSeek { + get { + return baseInputStream.CanSeek; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanWrite { + get { + return baseInputStream.CanWrite; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Length { + get { + return len; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Position { + get { + return baseInputStream.Position; + } + set { + baseInputStream.Position = value; + } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + baseInputStream.Flush(); + } + + /// + /// I needed to implement the abstract member. + /// + public override long Seek(long offset, SeekOrigin origin) + { + return baseInputStream.Seek(offset, origin); + } + + /// + /// I needed to implement the abstract member. + /// + public override void SetLength(long val) + { + baseInputStream.SetLength(val); + } + + /// + /// I needed to implement the abstract member. + /// + public override void Write(byte[] array, int offset, int count) + { + baseInputStream.Write(array, offset, count); + } + + /// + /// I needed to implement the abstract member. + /// + public override void WriteByte(byte val) + { + baseInputStream.WriteByte(val); + } + + //Constructors + + /// + /// Create an InflaterInputStream with the default decompresseor + /// and a default buffer size. + /// + /// + /// the InputStream to read bytes from + /// + public InflaterInputStream(Stream baseInputStream) : this(baseInputStream, new Inflater(), 4096) + { + + } + + /// + /// Create an InflaterInputStream with the specified decompresseor + /// and a default buffer size. + /// + /// + /// the InputStream to read bytes from + /// + /// + /// the decompressor used to decompress data read from baseInputStream + /// + public InflaterInputStream(Stream baseInputStream, Inflater inf) : this(baseInputStream, inf, 4096) + { + + } + + /// + /// Create an InflaterInputStream with the specified decompresseor + /// and a specified buffer size. + /// + /// + /// the InputStream to read bytes from + /// + /// + /// the decompressor used to decompress data read from baseInputStream + /// + /// + /// size of the buffer to use + /// + public InflaterInputStream(Stream baseInputStream, Inflater inf, int size) + { + this.baseInputStream = baseInputStream; + this.inf = inf; + try { + this.len = (int)baseInputStream.Length; + } catch (Exception) { // the stream may not support .Length + this.len = 0; + } + + if (size <= 0) { + throw new ArgumentOutOfRangeException("size <= 0"); + } + + buf = new byte[size]; //Create the buffer + } + + //Methods + + /// + /// Returns 0 once the end of the stream (EOF) has been reached. + /// Otherwise returns 1. + /// + public virtual int Available { + get { + return inf.IsFinished ? 0 : 1; + } + } + + /// + /// Closes the input stream + /// + public override void Close() + { + baseInputStream.Close(); + } + + /// + /// Fills the buffer with more data to decompress. + /// + protected void Fill() + { + len = baseInputStream.Read(buf, 0, buf.Length); + + if (len < 0) { + throw new ApplicationException("Deflated stream ends early."); + } + inf.SetInput(buf, 0, len); + } + + /// + /// Reads one byte of decompressed data. + /// + /// The byte is baseInputStream the lower 8 bits of the int. + /// + public override int ReadByte() + { + int nread = Read(onebytebuffer, 0, 1); //read one byte + if (nread > 0) { + return onebytebuffer[0] & 0xff; + } + return -1; + } + + /// + /// Decompresses data into the byte array + /// + /// + /// the array to read and decompress data into + /// + /// + /// the offset indicating where the data should be placed + /// + /// + /// the number of bytes to decompress + /// + public override int Read(byte[] b, int off, int len) + { + for (;;) { + int count; + try { + count = inf.Inflate(b, off, len); + } catch (Exception e) { + throw new ZipException(e.ToString()); + } + + if (count > 0) { + return count; + } + + if (inf.IsNeedingDictionary) { + throw new ZipException("Need a dictionary"); + } else if (inf.IsFinished) { + return 0; + } else if (inf.IsNeedingInput) { + Fill(); + } else { + throw new InvalidOperationException("Don't know what to do"); + } + } + } + + /// + /// Skip specified number of bytes of uncompressed data + /// + /// + /// number of bytes to skip + /// + public long Skip(long n) + { + if (n < 0) { + throw new ArgumentOutOfRangeException("n"); + } + int len = 2048; + if (n < len) { + len = (int) n; + } + byte[] tmp = new byte[len]; + return (long)baseInputStream.Read(tmp, 0, tmp.Length); + } + } +} diff --git a/PNGNet/Compression/Streams/OutputWindow.cs b/PNGNet/Compression/Streams/OutputWindow.cs new file mode 100644 index 0000000..eef6b77 --- /dev/null +++ b/PNGNet/Compression/Streams/OutputWindow.cs @@ -0,0 +1,176 @@ +// OutputWindow.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams { + + /// + /// Contains the output from the Inflation process. + /// We need to have a window so that we can refer backwards into the output stream + /// to repeat stuff. + /// + /// author of the original java version : John Leuner + /// + public class OutputWindow + { + private static int WINDOW_SIZE = 1 << 15; + private static int WINDOW_MASK = WINDOW_SIZE - 1; + + private byte[] window = new byte[WINDOW_SIZE]; //The window is 2^15 bytes + private int window_end = 0; + private int window_filled = 0; + + public void Write(int abyte) + { + if (window_filled++ == WINDOW_SIZE) { + throw new InvalidOperationException("Window full"); + } + window[window_end++] = (byte) abyte; + window_end &= WINDOW_MASK; + } + + + private void SlowRepeat(int rep_start, int len, int dist) + { + while (len-- > 0) { + window[window_end++] = window[rep_start++]; + window_end &= WINDOW_MASK; + rep_start &= WINDOW_MASK; + } + } + + public void Repeat(int len, int dist) + { + if ((window_filled += len) > WINDOW_SIZE) { + throw new InvalidOperationException("Window full"); + } + + int rep_start = (window_end - dist) & WINDOW_MASK; + int border = WINDOW_SIZE - len; + if (rep_start <= border && window_end < border) { + if (len <= dist) { + System.Array.Copy(window, rep_start, window, window_end, len); + window_end += len; + } else { + /* We have to copy manually, since the repeat pattern overlaps. + */ + while (len-- > 0) { + window[window_end++] = window[rep_start++]; + } + } + } else { + SlowRepeat(rep_start, len, dist); + } + } + + public int CopyStored(StreamManipulator input, int len) + { + len = Math.Min(Math.Min(len, WINDOW_SIZE - window_filled), input.AvailableBytes); + int copied; + + int tailLen = WINDOW_SIZE - window_end; + if (len > tailLen) { + copied = input.CopyBytes(window, window_end, tailLen); + if (copied == tailLen) { + copied += input.CopyBytes(window, 0, len - tailLen); + } + } else { + copied = input.CopyBytes(window, window_end, len); + } + + window_end = (window_end + copied) & WINDOW_MASK; + window_filled += copied; + return copied; + } + + public void CopyDict(byte[] dict, int offset, int len) + { + if (window_filled > 0) { + throw new InvalidOperationException(); + } + + if (len > WINDOW_SIZE) { + offset += len - WINDOW_SIZE; + len = WINDOW_SIZE; + } + System.Array.Copy(dict, offset, window, 0, len); + window_end = len & WINDOW_MASK; + } + + public int GetFreeSpace() + { + return WINDOW_SIZE - window_filled; + } + + public int GetAvailable() + { + return window_filled; + } + + public int CopyOutput(byte[] output, int offset, int len) + { + int copy_end = window_end; + if (len > window_filled) { + len = window_filled; + } else { + copy_end = (window_end - window_filled + len) & WINDOW_MASK; + } + + int copied = len; + int tailLen = len - copy_end; + + if (tailLen > 0) { + System.Array.Copy(window, WINDOW_SIZE - tailLen, + output, offset, tailLen); + offset += tailLen; + len = copy_end; + } + System.Array.Copy(window, copy_end - len, output, offset, len); + window_filled -= copied; + if (window_filled < 0) { + throw new InvalidOperationException(); + } + return copied; + } + + public void Reset() + { + window_filled = window_end = 0; + } + } +} diff --git a/PNGNet/Compression/Streams/StreamManipulator.cs b/PNGNet/Compression/Streams/StreamManipulator.cs new file mode 100644 index 0000000..23540ad --- /dev/null +++ b/PNGNet/Compression/Streams/StreamManipulator.cs @@ -0,0 +1,244 @@ +// StreamManipulator.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams { + + /// + /// This class allows us to retrieve a specified amount of bits from + /// the input buffer, as well as copy big byte blocks. + /// + /// It uses an int buffer to store up to 31 bits for direct + /// manipulation. This guarantees that we can get at least 16 bits, + /// but we only need at most 15, so this is all safe. + /// + /// There are some optimizations in this class, for example, you must + /// never peek more then 8 bits more than needed, and you must first + /// peek bits before you may drop them. This is not a general purpose + /// class but optimized for the behaviour of the Inflater. + /// + /// authors of the original java version : John Leuner, Jochen Hoenicke + /// + public class StreamManipulator + { + private byte[] window; + private int window_start = 0; + private int window_end = 0; + + private uint buffer = 0; + private int bits_in_buffer = 0; + + /// + /// Get the next n bits but don't increase input pointer. n must be + /// less or equal 16 and if you if this call succeeds, you must drop + /// at least n-8 bits in the next call. + /// + /// + /// the value of the bits, or -1 if not enough bits available. */ + /// + public int PeekBits(int n) + { + if (bits_in_buffer < n) { + if (window_start == window_end) { + return -1; + } + buffer |= (uint)((window[window_start++] & 0xff | + (window[window_start++] & 0xff) << 8) << bits_in_buffer); + bits_in_buffer += 16; + } + return (int)(buffer & ((1 << n) - 1)); + } + + /// + /// Drops the next n bits from the input. You should have called peekBits + /// with a bigger or equal n before, to make sure that enough bits are in + /// the bit buffer. + /// + public void DropBits(int n) + { + buffer >>= n; + bits_in_buffer -= n; + } + + /// + /// Gets the next n bits and increases input pointer. This is equivalent + /// to peekBits followed by dropBits, except for correct error handling. + /// + /// + /// the value of the bits, or -1 if not enough bits available. + /// + public int GetBits(int n) + { + int bits = PeekBits(n); + if (bits >= 0) { + DropBits(n); + } + return bits; + } + + /// + /// Gets the number of bits available in the bit buffer. This must be + /// only called when a previous peekBits() returned -1. + /// + /// + /// the number of bits available. + /// + public int AvailableBits { + get { + return bits_in_buffer; + } + } + + /// + /// Gets the number of bytes available. + /// + /// + /// the number of bytes available. + /// + public int AvailableBytes { + get { + return window_end - window_start + (bits_in_buffer >> 3); + } + } + + /// + /// Skips to the next byte boundary. + /// + public void SkipToByteBoundary() + { + buffer >>= (bits_in_buffer & 7); + bits_in_buffer &= ~7; + } + + public bool IsNeedingInput { + get { + return window_start == window_end; + } + } + + /// + /// Copies length bytes from input buffer to output buffer starting + /// at output[offset]. You have to make sure, that the buffer is + /// byte aligned. If not enough bytes are available, copies fewer + /// bytes. + /// + /// + /// the buffer. + /// + /// + /// the offset in the buffer. + /// + /// + /// the length to copy, 0 is allowed. + /// + /// + /// the number of bytes copied, 0 if no byte is available. + /// + public int CopyBytes(byte[] output, int offset, int length) + { + if (length < 0) { + throw new ArgumentOutOfRangeException("length negative"); + } + if ((bits_in_buffer & 7) != 0) { + /* bits_in_buffer may only be 0 or 8 */ + throw new InvalidOperationException("Bit buffer is not aligned!"); + } + + int count = 0; + while (bits_in_buffer > 0 && length > 0) { + output[offset++] = (byte) buffer; + buffer >>= 8; + bits_in_buffer -= 8; + length--; + count++; + } + if (length == 0) { + return count; + } + + int avail = window_end - window_start; + if (length > avail) { + length = avail; + } + System.Array.Copy(window, window_start, output, offset, length); + window_start += length; + + if (((window_start - window_end) & 1) != 0) { + /* We always want an even number of bytes in input, see peekBits */ + buffer = (uint)(window[window_start++] & 0xff); + bits_in_buffer = 8; + } + return count + length; + } + + public StreamManipulator() + { + } + + public void Reset() + { + buffer = (uint)(window_start = window_end = bits_in_buffer = 0); + } + + public void SetInput(byte[] buf, int off, int len) + { + if (window_start < window_end) { + throw new InvalidOperationException("Old input was not completely processed"); + } + + int end = off + len; + + /* We want to throw an ArrayIndexOutOfBoundsException early. The + * check is very tricky: it also handles integer wrap around. + */ + if (0 > off || off > end || end > buf.Length) { + throw new ArgumentOutOfRangeException(); + } + + if ((len & 1) != 0) { + /* We always want an even number of bytes in input, see peekBits */ + buffer |= (uint)((buf[off++] & 0xff) << bits_in_buffer); + bits_in_buffer += 8; + } + + window = buf; + window_start = off; + window_end = end; + } + } +} diff --git a/PNGNet/Compression/ZipException.cs b/PNGNet/Compression/ZipException.cs new file mode 100644 index 0000000..6c82fc4 --- /dev/null +++ b/PNGNet/Compression/ZipException.cs @@ -0,0 +1,82 @@ +// ZipException.cs +// +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +// modified by wj32 + +using System; + +#if !NETCF_1_0 && !NETCF_2_0 +using System.Runtime.Serialization; +#endif + +namespace ICSharpCode.SharpZipLib.Zip +{ + + /// + /// Represents exception conditions specific to Zip archive handling + /// + public class ZipException : Exception + { + + /// + /// Initializes a new instance of the ZipException class. + /// + public ZipException() + { + } + + /// + /// Initializes a new instance of the ZipException class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public ZipException(string message) + : base(message) + { + } + + /// + /// Initialise a new instance of ZipException. + /// + /// A message describing the error. + /// The exception that is the cause of the current exception. + public ZipException(string message, Exception exception) + : base(message, exception) + { + } + } +} diff --git a/PNGNet/CompressionLevel.cs b/PNGNet/CompressionLevel.cs new file mode 100644 index 0000000..ab75866 --- /dev/null +++ b/PNGNet/CompressionLevel.cs @@ -0,0 +1,96 @@ +/* + * PNG.Net + * + * Copyright (C) 2008 wj32 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +using System; +using ICSharpCode.SharpZipLib.Zip.Compression; + +namespace PNGNet +{ + /// + /// Provides methods for changing the global compression level. + /// + public sealed class CompressionLevel : IDisposable + { + #region Static Members + + /// + /// Indicates that the compressor should store blocks uncompressed. + /// + public const int NoCompression = 0; + + /// + /// Indicates that the compressor should compress at the fastest speed possible. + /// + public const int BestSpeed = 1; + + /// + /// Indicates that the compressor should compress to the smallest size possible. + /// + public const int BestCompression = 9; + + private static int _level = 6; + + /// + /// Gets or sets the compression level for compressing PNG data. + /// + public static int Level + { + get { return _level; } + set + { + if (value < CompressionLevel.NoCompression) + throw new ArgumentException("Compression level must be equal to or above 0."); + if (value > CompressionLevel.BestCompression) + throw new ArgumentException("Compression level must be equal to or below 9."); + + _level = value; + } + } + + #endregion + + #region Class + + private int _oldLevel; + + /// + /// Temporarily sets the compression level. + /// + /// The new level. + public CompressionLevel(int newLevel) + { + _oldLevel = CompressionLevel.Level; + CompressionLevel.Level = newLevel; + } + + #region IDisposable Members + + /// + /// Sets the compression level to the original value. + /// + public void Dispose() + { + CompressionLevel.Level = _oldLevel; + } + + #endregion + + #endregion + } +} diff --git a/PNGNet/PNGBitmap.cs b/PNGNet/PNGBitmap.cs new file mode 100644 index 0000000..10ed1be --- /dev/null +++ b/PNGNet/PNGBitmap.cs @@ -0,0 +1,1130 @@ +/* + * PNG.Net + * + * Copyright (C) 2008 wj32 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Text; +using ICSharpCode.SharpZipLib.Zip.Compression; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +namespace PNGNet +{ + /// + /// Provides high-level methods to read and write PNG bitmaps. + /// + public sealed class PNGBitmap + { + /// + /// The type of filter, applied to each scanline. + /// + public enum FilterType : byte + { + None = 0, + Sub, + Up, + Average, + Paeth + } + + #region Adam7 Sequence + + /* 0_1_2_3_4_5_6_7 + * 0|1 6 4 6 2 6 4 6 + * 1|7 7 7 7 7 7 7 7 + * 2|5 6 5 6 5 6 5 6 + * 3|7 7 7 7 7 7 7 7 + * 4|3 6 4 6 3 6 4 6 + * 5|7 7 7 7 7 7 7 7 + * 6|5 6 5 6 5 6 5 6 + * 7|7 7 7 7 7 7 7 7 + */ + private byte[][] _adam7 = new byte[][] + { + new byte[] { 0, 5, 3, 5, 1, 5, 3, 5 }, + new byte[] { 6, 6, 6, 6, 6, 6, 6, 6 }, + new byte[] { 4, 5, 4, 5, 4, 5, 4, 5 }, + new byte[] { 6, 6, 6, 6, 6, 6, 6, 6 }, + new byte[] { 2, 5, 3, 5, 2, 5, 3, 5 }, + new byte[] { 6, 6, 6, 6, 6, 6, 6, 6 }, + new byte[] { 4, 5, 4, 5, 4, 5, 4, 5 }, + new byte[] { 6, 6, 6, 6, 6, 6, 6, 6 } + }; + + int[] _adam7Pixels, _adam7Lines; + + // stored as lines, x, y, x, y, ... + private byte[][] _adam7Lookup = new byte[][] + { + new byte[] { 1, 0, 0 }, // 1 + new byte[] { 1, 4, 0 }, // 2 + new byte[] { 1, 0, 4, 4, 4}, // 3 + new byte[] + { + 2, + 2, 0, 6, 0, + 2, 4, 6, 4 + }, // 4 + new byte[] + { + 2, + 0, 2, 2, 2, 4, 2, 6, 2, + 0, 6, 2, 6, 4, 6, 6, 6 + }, // 5 + new byte[] + { + 4, + 1, 0, 3, 0, 5, 0, 7, 0, + 1, 2, 3, 2, 5, 2, 7, 2, + 1, 4, 3, 4, 5, 4, 7, 4, + 1, 6, 3, 6, 5, 6, 7, 6 + }, // 6 + new byte[] + { + 4, + 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, + 0, 3, 1, 3, 2, 3, 3, 3, 4, 3, 5, 3, 6, 3, 7, 3, + 0, 5, 1, 5, 2, 5, 3, 5, 4, 5, 5, 5, 6, 5, 7, 5, + 0, 7, 1, 7, 2, 7, 3, 7, 4, 7, 5, 7, 6, 7, 7, 7 + } // 7 + }; + + #endregion + + private PNGImage _image; + private Bitmap _bitmap; + + // state + int _scanlineLength; + int _bpp; + + /// + /// Creates a new PNG bitmap. + /// + /// The size. + /// The color type. + /// The bit depth. + public PNGBitmap(Size size, ColorType ct, byte bitDepth) : this(size.Width, size.Height, ct, bitDepth) { } + + /// + /// Creates a new PNG bitmap. + /// + /// The width. + /// The height. + /// The color type. + /// The bit depth. + public PNGBitmap(int width, int height, ColorType ct, byte bitDepth) + { + _bitmap = CreateBitmap(ct, bitDepth, width, height); + } + + /// + /// Reads a PNG file from the specified stream. + /// + /// The stream to read from. + public PNGBitmap(Stream s) + { + _image = new PNGImage(s); + + this.Read(this.Decompress()); + } + + /// + /// Creates a Bitmap from the given information. + /// + /// + /// + /// + /// + /// + private Bitmap CreateBitmap(ColorType ct, byte bitDepth, int width, int height) + { + // don't need to use the extra information for now + + return new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + } + + /// + /// Decompresses the compressed data stored in one or more IDAT chunks. + /// + private byte[] Decompress() + { + Inflater inf = new Inflater(); + MemoryStream decompressed = new MemoryStream(); + MemoryStream compressed = new MemoryStream(); + byte[] buf = new byte[1]; + + // decompress the image data + foreach (Chunk c in _image.Chunks) + { + if (c.Type == "IDAT") + { + IDATChunk idat = c as IDATChunk; + + compressed.Write(idat.Data, 0, idat.Data.Length); + + if (compressed.Length > 15) + { + inf.SetInput(compressed.ToArray()); + + while (!inf.IsNeedingInput) + { + if (inf.Inflate(buf) == -1) + break; + + decompressed.WriteByte(buf[0]); + } + + compressed = new MemoryStream(); + } + } + } + + inf.SetInput(compressed.ToArray()); + + while (!inf.IsNeedingInput) + { + if (inf.Inflate(buf) == -1) + break; + + decompressed.WriteByte(buf[0]); + } + + if (!inf.IsFinished) + throw new InvalidCompressedDataException("Inflater is not finished but there are no more IDAT chunks."); + + byte[] arr = decompressed.ToArray(); + + decompressed.Close(); + + return arr; + } + + /// + /// Returns the paeth predictor of the three values. + /// + /// The left value. + /// The above value. + /// The upper left value. + /// The closest neighbouring value. + private byte PaethPredictor(byte a, byte b, byte c) + { + int p = (int)a + (int)b - (int)c; + int pa = (int)Math.Abs((int)p - a); + int pb = (int)Math.Abs((int)p - b); + int pc = (int)Math.Abs((int)p - c); + + if ((pa <= pb) && (pa <= pc)) + return a; + else if (pb <= pc) + return b; + else + return c; + } + + /// + /// Defilters a scanline. + /// + /// The raw image data. + /// The scanline number. + /// The previous defiltered line. + /// The defiltered data. + private byte[] Defilter(byte[] data, int line, byte[] priorLine) + { + FilterType ft = (FilterType)data[line * _scanlineLength]; + byte[] lineData = new byte[_scanlineLength - 1]; + int lds = line * _scanlineLength + 1; + + switch (ft) + { + case FilterType.None: + // no filtering, just copy the line over, skipping the filter type byte. + Array.Copy(data, lds, lineData, 0, lineData.Length); + break; + + case FilterType.Sub: + for (int i = 0; i < lineData.Length; i++) + { + byte orig = data[lds + i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : lineData[i - _bpp]; + + lineData[i] = (byte)(( + orig + rawxbpp + ) % 256); + } + + break; + + case FilterType.Up: + if (line == 0) + { + Array.Copy(data, lds, lineData, 0, lineData.Length); + } + else + { + for (int i = 0; i < lineData.Length; i++) + { + byte orig = data[lds + i]; + byte priorx = priorLine[i]; + + lineData[i] = (byte)(( + orig + priorx + ) % 256); + } + } + + break; + + case FilterType.Average: + if (line == 0) + { + for (int i = 0; i < lineData.Length; i++) + { + byte orig = data[lds + i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : lineData[i - _bpp]; + + lineData[i] = (byte)(( + orig + (int)Math.Floor((double)rawxbpp / 2) + ) % 256); + } + } + else + { + for (int i = 0; i < lineData.Length; i++) + { + byte orig = data[lds + i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : lineData[i - _bpp]; + byte priorx = priorLine[i]; + + lineData[i] = (byte)(( + orig + (int)Math.Floor((double)(rawxbpp + priorx) / 2) + ) % 256); + } + } + + break; + + case FilterType.Paeth: + if (line == 0) + { + for (int i = 0; i < lineData.Length; i++) + { + byte orig = data[lds + i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : lineData[i - _bpp]; + + lineData[i] = (byte)(( + orig + this.PaethPredictor(rawxbpp, 0, 0) + ) % 256); + } + } + else + { + for (int i = 0; i < lineData.Length; i++) + { + byte orig = data[lds + i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : lineData[i - _bpp]; + byte priorx = priorLine[i]; + byte priorxbpp = (i - _bpp < 0) ? (byte)0 : priorLine[i - _bpp]; + + lineData[i] = (byte)(( + orig + this.PaethPredictor(rawxbpp, priorx, priorxbpp) + ) % 256); + } + } + + break; + + default: + throw new Exception(string.Format("Unknown filter type {0}.", ft)); + } + + return lineData; + } + + /// + /// Filters a scanline. + /// + /// A single line of data. + /// The line number. + /// The filter type to use. + /// The previous unfiltered line. + /// The filtered bytes. + private byte[] Filter(byte[] data, int line, FilterType ft, byte[] priorLine) + { + byte[] lineData = new byte[_scanlineLength]; + + lineData[0] = (byte)ft; + + switch (ft) + { + case FilterType.None: + // no filtering, just copy the line over, skipping the filter type byte. + Array.Copy(data, 0, lineData, 1, data.Length); + break; + + case FilterType.Sub: + for (int i = 0; i < data.Length; i++) + { + byte orig = data[i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : data[i - _bpp]; + + lineData[i + 1] = (byte)(( + orig - rawxbpp + ) % 256); + } + + break; + + case FilterType.Up: + if (line == 0) + { + Array.Copy(data, 0, lineData, 1, data.Length); + } + else + { + for (int i = 0; i < data.Length; i++) + { + byte orig = data[i]; + byte priorx = priorLine[i]; + + lineData[i + 1] = (byte)(( + orig - priorx + ) % 256); + } + } + + break; + + case FilterType.Average: + if (line == 0) + { + for (int i = 0; i < data.Length; i++) + { + byte orig = data[i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : data[i - _bpp]; + + lineData[i + 1] = (byte)(( + orig - (int)Math.Floor((double)rawxbpp / 2) + ) % 256); + } + } + else + { + for (int i = 0; i < data.Length; i++) + { + byte orig = data[i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : data[i - _bpp]; + byte priorx = priorLine[i]; + + lineData[i + 1] = (byte)(( + orig - (int)Math.Floor((double)(rawxbpp + priorx) / 2) + ) % 256); + } + } + + break; + + case FilterType.Paeth: + if (line == 0) + { + for (int i = 0; i < data.Length; i++) + { + byte orig = data[i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : lineData[i - _bpp]; + + lineData[i + 1] = (byte)(( + orig - this.PaethPredictor(rawxbpp, 0, 0) + ) % 256); + } + } + else + { + for (int i = 0; i < data.Length; i++) + { + byte orig = data[i]; + byte rawxbpp = (i - _bpp < 0) ? (byte)0 : data[i - _bpp]; + byte priorx = priorLine[i]; + byte priorxbpp = (i - _bpp < 0) ? (byte)0 : priorLine[i - _bpp]; + + lineData[i + 1] = (byte)(( + orig - this.PaethPredictor(rawxbpp, priorx, priorxbpp) + ) % 256); + } + } + + break; + + default: + throw new Exception(string.Format("Unknown filter type {0}.", ft)); + } + + return lineData; + } + + /// + /// Calculates the best filter method for the given scanline by applying each filter to the + /// given scanline, computing the sum of each filtered byte (as a signed value), then returning + /// the filter which produces the lowest absolute value of the sum. + /// + /// The scanline to test. + /// The line number. + /// The previous unfiltered line. + /// + private FilterType GetBestFilterMethod(byte[] data, int line, byte[] priorLine) + { + Dictionary values = new Dictionary(); + + foreach (FilterType ft in Enum.GetValues(typeof(FilterType))) + { + byte[] f = this.Filter(data, line, ft, priorLine); + long sum = 0; + + foreach (byte b in f) + { + if (b > 127) + sum -= 256 - b; + else + sum += b; + } + + values.Add(ft, (long)Math.Abs(sum)); + } + + FilterType minType = FilterType.None; + long minValue = (1 << 62) - 1; + + foreach (FilterType ft in values.Keys) + { + if (values[ft] < minValue) + { + minType = ft; + minValue = values[ft]; + } + } + + return minType; + } + + private void FillAdam7Tables(int width, int height) + { + // yCounts[pass] = count of lines in pass + // xCounts[line][pass] = count of pass in each line + int[] yCounts = new int[7]; + int[][] xCounts = new int[8][]; + + for (int i = 0; i < 7; i++) + xCounts[i] = new int[7]; + } + + /// + /// Reads the image pixels and stores them in Bitmap. + /// + /// The image bytes. + private void Read(byte[] data) + { + // optimized for speed (it's very slow already) + + IHDRChunk hdr = _image.Chunks["IHDR"] as IHDRChunk; + int width = (int)hdr.Width; + int height = (int)hdr.Height; + byte bitDepth = hdr.BitDepth; + + _bitmap = CreateBitmap(hdr.ColorType, hdr.BitDepth, width, height); + _scanlineLength = Utils.IntCeilDiv(Utils.GetBitsPerPixel(hdr.ColorType, hdr.BitDepth) * width, 8) + 1; + _bpp = Utils.GetBytesPerPixel(hdr.ColorType, hdr.BitDepth); // bytes per pixel + + byte[] decoded = null; + + // palette + PLTEChunk palette = _image.Chunks["PLTE"] as PLTEChunk; + + #region tRNS Chunk + + tRNSChunk trns = _image.Chunks["tRNS"] as tRNSChunk; + int tGray1 = -1; + int tGray2 = -1; + int tGray4 = -1; + int tGray8 = -1; + int tGray16 = -1; + Color tRgb8 = Color.FromArgb(0, 0, 0, 0); + Color tRgb16 = Color.FromArgb(0, 0, 0, 0); + int[] tPalette = null; + + if (trns != null) + { + tGray1 = trns.Gray & 0x1; + tGray2 = trns.Gray & 0x3; + tGray4 = trns.Gray & 0xf; + tGray8 = trns.Gray & 0xff; + tGray16 = trns.Gray; + tRgb8 = Color.FromArgb(trns.Red & 0xff, trns.Green & 0xff, trns.Blue & 0xff); + tRgb16 = Color.FromArgb(trns.Red * 255 / 65535, trns.Green * 255 / 65535, trns.Blue * 255 / 65535); + + if (palette != null) + { + tPalette = new int[palette.Entries.Length]; + + for (int i = 0; i < tPalette.Length; i++) + { + if (i < trns.PaletteAlpha.Length) + tPalette[i] = trns.PaletteAlpha[i]; + else + tPalette[i] = 255; + } + } + } + else + { + if (palette != null) + { + tPalette = new int[palette.Entries.Length]; + + for (int i = 0; i < tPalette.Length; i++) + tPalette[i] = 255; + } + } + + #endregion + + if (hdr.InterlaceMethod == InterlaceMethod.Adam7) + { + #region Adam7 + + for (int pass = 0; pass < _adam7.Length; pass++) + { + + } + + #endregion + } + else + { + #region Normal + + for (int line = 0; line < data.Length / _scanlineLength; line++) + { + decoded = this.Defilter(data, line, decoded); + + switch (hdr.ColorType) + { + case ColorType.Grayscale: + if (bitDepth == 1) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 8); i++) + { + int[] pixels = new int[8]; + + for (int j = 0; j < 8; j++) + pixels[j] = (decoded[i] >> (7 - j)) & 0x1; + + for (int j = 0; j < 8; j++) + if (i * 8 + j < hdr.Width) + _bitmap.SetPixel(i * 8 + j, line, + Utils.MakeGray(pixels[j] == tGray1 ? 0 : 255, pixels[j] * 255)); + } + } + else if (bitDepth == 2) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 4); i++) + { + int[] pixels = new int[4]; + + for (int j = 0; j < 4; j++) + pixels[j] = (decoded[i] >> ((3 - j) * 2)) & 0x3; + + for (int j = 0; j < 4; j++) + if (i * 4 + j < hdr.Width) + _bitmap.SetPixel(i * 4 + j, line, + Utils.MakeGray(pixels[j] == tGray2 ? 0 : 255, pixels[j] * 255 / 3)); + } + } + else if (bitDepth == 4) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 2); i++) + { + int pixel1 = decoded[i] >> 4; // upper four bits + int pixel2 = decoded[i] & 0xf; // lower two bits + + _bitmap.SetPixel(i * 2, line, Utils.MakeGray( + pixel1 == tGray4 ? 0 : 255, pixel1 * 255 / 15)); + + if (i * 2 + 1 < hdr.Width) + _bitmap.SetPixel(i * 2 + 1, line, Utils.MakeGray( + pixel2 == tGray4 ? 0 : 255, pixel2 * 255 / 15)); + } + } + else if (bitDepth == 8) + { + for (int i = 0; i < hdr.Width; i++) + _bitmap.SetPixel(i, line, + Utils.MakeGray(decoded[i] == tGray8 ? 0 : 255, decoded[i])); + } + else if (bitDepth == 16) + { + for (int i = 0; i < hdr.Width; i++) + { + int value = Utils.BytesToUShort(decoded, i * 2, Utils.Endianness.Big); + + _bitmap.SetPixel(i, line, Utils.MakeGray( + value == tGray16 ? 0 : 255, value * 255 / 65535)); + } + } + + break; + + case ColorType.GrayscaleWithAlpha: + if (bitDepth == 8) + { + for (int i = 0; i < hdr.Width; i++) + _bitmap.SetPixel(i, line, Color.FromArgb( + decoded[i * 2 + 1], + decoded[i * 2], decoded[i * 2], decoded[i * 2] + )); + } + else if (bitDepth == 16) + { + for (int i = 0; i < hdr.Width; i++) + _bitmap.SetPixel(i, line, Color.FromArgb( + Utils.BytesToUShort(decoded, i * 4 + 2, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 4, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 4, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 4, Utils.Endianness.Big) * 255 / 65535 + )); + } + + break; + + case ColorType.IndexedColor: + if (bitDepth == 1) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 8); i++) + { + int[] pixels = new int[8]; + + for (int j = 0; j < 8; j++) + pixels[j] = (decoded[i] >> (7 - j)) & 0x1; + + for (int j = 0; j < 8; j++) + if (i * 8 + j < hdr.Width) + _bitmap.SetPixel(i * 8 + j, line, + Color.FromArgb(tPalette[pixels[j]], palette.Entries[pixels[j]])); + } + } + else if (bitDepth == 2) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 4); i++) + { + int[] pixels = new int[4]; + + for (int j = 0; j < 4; j++) + pixels[j] = (decoded[i] >> ((3 - j) * 2)) & 0x3; + + for (int j = 0; j < 4; j++) + if (i * 4 + j < hdr.Width) + _bitmap.SetPixel(i * 4 + j, line, + Color.FromArgb(tPalette[pixels[j]], palette.Entries[pixels[j]])); + } + } + else if (bitDepth == 4) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 2); i++) + { + int pixel1 = decoded[i] >> 4; // upper four bits + int pixel2 = decoded[i] & 0xf; // lower two bits + + _bitmap.SetPixel(i * 2, line, + Color.FromArgb(tPalette[pixel1], palette.Entries[pixel1])); + + if (i * 2 + 1 < hdr.Width) + _bitmap.SetPixel(i * 2 + 1, line, + Color.FromArgb(tPalette[pixel2], palette.Entries[pixel2])); + } + } + else if (bitDepth == 8) + { + for (int i = 0; i < hdr.Width; i++) + _bitmap.SetPixel(i, line, + Color.FromArgb(tPalette[decoded[i]], palette.Entries[decoded[i]])); + } + + break; + + case ColorType.Truecolor: + if (bitDepth == 8) + { + for (int i = 0; i < hdr.Width; i++) + { + Color c = Color.FromArgb( + decoded[i * 3], + decoded[i * 3 + 1], + decoded[i * 3 + 2] + ); + + _bitmap.SetPixel(i, line, Color.FromArgb( + Utils.ColorsEqual(c, tRgb8) ? 0 : 255, c)); + } + } + else if (bitDepth == 16) + { + // .NET doesn't support 16-bit bit depths + for (int i = 0; i < hdr.Width; i++) + { + Color c = Color.FromArgb( + Utils.BytesToUShort(decoded, i * 6, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 6 + 2, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 6 + 4, Utils.Endianness.Big) * 255 / 65535 + ); + + _bitmap.SetPixel(i, line, Color.FromArgb( + Utils.ColorsEqual(c, tRgb16) ? 0 : 255, c)); + } + } + + break; + + case ColorType.TruecolorWithAlpha: + if (bitDepth == 8) + { + for (int i = 0; i < hdr.Width; i++) + _bitmap.SetPixel(i, line, Color.FromArgb( + decoded[i * 4 + 3], + decoded[i * 4], + decoded[i * 4 + 1], + decoded[i * 4 + 2])); + } + else if (bitDepth == 16) + { + for (int i = 0; i < hdr.Width; i++) + _bitmap.SetPixel(i, line, Color.FromArgb( + Utils.BytesToUShort(decoded, i * 8 + 6, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 8, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 8 + 2, Utils.Endianness.Big) * 255 / 65535, + Utils.BytesToUShort(decoded, i * 8 + 4, Utils.Endianness.Big) * 255 / 65535 + )); + } + + break; + + default: + throw new Exception("Invalid color type."); + } + } + + #endregion + } + } + + /// + /// Saves the PNG bitmap to the specified file. + /// + /// The file name. + public void Save(string path) + { + this.Save(path, FileMode.OpenOrCreate); + } + + /// + /// Saves the PNG bitmap to the specified file. + /// + /// The file name. + /// The file mode. + public void Save(string path, FileMode mode) + { + FileStream fs = new FileStream(path, mode); + + this.Save(fs); + + fs.Close(); + } + + /// + /// Saves the PNG image to a stream. + /// + /// The stream to write to. + public void Save(Stream s) + { + IHDRChunk hdr = _image.Chunks["IHDR"] as IHDRChunk; + int width = (int)hdr.Width; + int height = (int)hdr.Height; + + _scanlineLength = Utils.IntCeilDiv(Utils.GetBitsPerPixel(hdr.ColorType, hdr.BitDepth) * width, 8) + 1; + _bpp = Utils.GetBytesPerPixel(hdr.ColorType, hdr.BitDepth); // bytes per pixel + + byte bitDepth = hdr.BitDepth; + + MemoryStream data = new MemoryStream(); + BinaryWriter bwdata = new BinaryWriter(data); + byte[] scanline = null; + + for (int line = 0; line < height; line++) + { + byte[] prevLine = scanline; + + scanline = new byte[_scanlineLength - 1]; + + switch (hdr.ColorType) + { + case ColorType.Grayscale: + if (bitDepth == 1) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 8); i++) + { + byte b = 0; + + for (int j = 0; j < 8; j++) + { + if (i * 8 + j < hdr.Width) + { + Color c = _bitmap.GetPixel(i * 8 + j, line); + byte value = (byte)((c.R + c.G + c.B) / 3); + + b |= (byte)(((value / 255) >> (7 - j)) & 0x1); + } + } + + scanline[i] = b; + } + } + else if (bitDepth == 2) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 4); i++) + { + byte b = 0; + + for (int j = 0; j < 4; j++) + { + if (i * 4 + j < hdr.Width) + { + Color c = _bitmap.GetPixel(i * 4 + j, line); + byte value = (byte)((c.R + c.G + c.B) / 3); + + b |= (byte)(((value * 3 / 255) >> ((3 - j) * 2)) & 0x3); + } + } + + scanline[i] = b; + } + } + else if (bitDepth == 4) + { + for (int i = 0; i < Utils.IntCeilDiv(width, 2); i++) + { + byte b = 0; + + Color c = _bitmap.GetPixel(i * 2, line); + byte value = (byte)((c.R + c.G + c.B) / 3); + + b |= (byte)((value * 15 / 255) << 4); + + if (i * 2 + 1 < width) + { + c = _bitmap.GetPixel(i * 2 + 1, line); + value = (byte)((c.R + c.G + c.B) / 3); + + b |= (byte)(value * 15 / 255); + } + + scanline[i] = b; + } + } + else if (bitDepth == 8) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + + scanline[i] = (byte)((c.R + c.G + c.B) / 3); + } + } + else if (bitDepth == 16) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + byte value = (byte)((c.R + c.G + c.B) / 3); + + scanline[i * 2] = (byte)((value * 65535 / 255) >> 8); + scanline[i * 2 + 1] = (byte)((value * 65535 / 255) & 0xff); + } + } + + break; + + case ColorType.GrayscaleWithAlpha: + if (bitDepth == 8) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + + scanline[i * 2] = (byte)((c.R + c.G + c.B) / 3); + scanline[i * 2 + 1] = c.A; + } + } + else if (bitDepth == 16) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + byte value = (byte)((c.R + c.G + c.B) / 3); + + scanline[i * 4] = (byte)((value * 65535 / 255) >> 8); + scanline[i * 4 + 1] = (byte)((value * 65535 / 255) & 0xff); + scanline[i * 4 + 2] = (byte)((c.A * 65535 / 255) >> 8); + scanline[i * 4 + 3] = (byte)((c.A * 65535 / 255) & 0xff); + } + } + + break; + + case ColorType.IndexedColor: + + + break; + + case ColorType.Truecolor: + if (bitDepth == 8) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + + scanline[i * 3] = c.R; + scanline[i * 3 + 1] = c.G; + scanline[i * 3 + 2] = c.B; + } + } + else if (bitDepth == 16) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + + scanline[i * 6] = (byte)((c.R * 65535 / 255) >> 8); + scanline[i * 6 + 1] = (byte)((c.R * 65535 / 255) & 0xff); + scanline[i * 6 + 2] = (byte)((c.G * 65535 / 255) >> 8); + scanline[i * 6 + 3] = (byte)((c.G * 65535 / 255) & 0xff); + scanline[i * 6 + 4] = (byte)((c.B * 65535 / 255) >> 8); + scanline[i * 6 + 5] = (byte)((c.B * 65535 / 255) & 0xff); + } + } + + break; + + case ColorType.TruecolorWithAlpha: + if (bitDepth == 8) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + + scanline[i * 4] = c.R; + scanline[i * 4 + 1] = c.G; + scanline[i * 4 + 2] = c.B; + scanline[i * 4 + 3] = c.A; + } + } + else if (bitDepth == 16) + { + for (int i = 0; i < width; i++) + { + Color c = _bitmap.GetPixel(i, line); + + scanline[i * 8] = (byte)((c.R * 65535 / 255) >> 8); + scanline[i * 8 + 1] = (byte)((c.R * 65535 / 255) & 0xff); + scanline[i * 8 + 2] = (byte)((c.G * 65535 / 255) >> 8); + scanline[i * 8 + 3] = (byte)((c.G * 65535 / 255) & 0xff); + scanline[i * 8 + 4] = (byte)((c.B * 65535 / 255) >> 8); + scanline[i * 8 + 5] = (byte)((c.B * 65535 / 255) & 0xff); + scanline[i * 8 + 6] = (byte)((c.A * 65535 / 255) >> 8); + scanline[i * 8 + 7] = (byte)((c.A * 65535 / 255) & 0xff); + } + } + + break; + + default: + throw new Exception("Invalid color type."); + } + + byte[] filtered = this.Filter(scanline, line, this.GetBestFilterMethod(scanline, line, prevLine), prevLine); + + bwdata.Write(filtered); + } + + this.SaveChunks(data.ToArray()); + _image.Write(s, true); + + bwdata.Close(); + } + + /// + /// Modifies the underlying image's chunks to save the image. + /// + /// The uncompressed image data. + private void SaveChunks(byte[] data) + { + // remove all existing data chunks + _image.Chunks.RemoveAll(new Predicate( + delegate(Chunk c) + { + if (c.Type == "IDAT") return true; else return false; + })); + + // insert our new data chunk just before the end + for (int i = 0; i < _image.Chunks.Count; i++) + { + if (_image.Chunks[i].Type == "IEND") + { + IDATChunk idat = new IDATChunk(_image); + MemoryStream ms = new MemoryStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(ms, new Deflater(CompressionLevel.Level)); + BinaryWriter bwdata = new BinaryWriter(dos); + + bwdata.Write(data); + dos.Finish(); + idat.Data = ms.ToArray(); + bwdata.Close(); + ms.Close(); + + _image.Chunks.Insert(i, idat); + + break; + } + } + } + + /// + /// Gets the underlying PNG image. + /// + public PNGImage Image + { + get { return _image; } + } + + /// + /// Gets the bitmap. + /// + public Bitmap Bitmap + { + get { return _bitmap; } + } + } +} diff --git a/PNGNet/PNGImage.cs b/PNGNet/PNGImage.cs new file mode 100644 index 0000000..e142181 --- /dev/null +++ b/PNGNet/PNGImage.cs @@ -0,0 +1,377 @@ +/* + * PNG.Net + * + * Copyright (C) 2008 wj32 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Reflection; +using System.Text; + +namespace PNGNet +{ + /// + /// This exception indicates that the PNG image has an invalid header. + /// + public class InvalidHeaderException : Exception + { + public InvalidHeaderException(string message) : base(message) { } + } + + /// + /// Indicates that the PNG image is invalid. + /// + public class InvalidImageException : Exception + { + public InvalidImageException(string message) : base(message) { } + } + + /// + /// Indicates that the PNG image has invalid compressed data. + /// + public class InvalidCompressedDataException : Exception + { + public InvalidCompressedDataException(string message) : base(message) { } + } + + /// + /// Represents a PNG image and provides methods to read and write chunks. + /// + public sealed class PNGImage + { + [Flags] + public enum PNGImageOptions + { + /// + /// Specifies that errors in ancillary chunks should be fatal. + /// + AncillaryChunkErrors + } + + public const uint MaxLength = 2147483647; + public static PNGImageOptions Options; + + private byte[] _header = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; + private ChunkCollection _chunks = new ChunkCollection(); + private Dictionary _classes = new Dictionary(); + private bool _ccMod = false; + + /// + /// Creates a PNG image with no chunks. + /// + public PNGImage() { } + + /// + /// Creates a PNG image from a stream. + /// + /// The stream to read from. + public PNGImage(Stream s) : this(s, new Type[0]) { } + + /// + /// Creates a PNG image from a stream. + /// + /// The stream to read from. + /// Any custom chunk classes you may wish to specify. + public PNGImage(Stream s, Type[] customHandlers) + { + this.LoadClasses(customHandlers); + + byte[] header = new byte[8]; + + s.Read(header, 0, 8); + + if (!Utils.BytesEqual(header, _header)) + throw new InvalidHeaderException("Invalid PNG image header."); + + uint chunkNumber = 0; + + while (s.Position < s.Length) + { + uint goodcrc, crc32; + int length; + string type; + byte[] data; + + length = Utils.ReadInt(s, Utils.Endianness.Big); + + // this decoder does NOT support chunk lengths greater than 2^31-5 because of limitations with uint. + if (length > MaxLength - 4) + throw new InvalidChunkLengthException( + string.Format("Length {0} for chunk {1} exceeds 2^31-5.", length, chunkNumber)); + + type = Utils.ReadString(s, 4); + + // terminate on critical unrecognized chunk + if (char.IsUpper(type[0]) && !_classes.ContainsKey(type)) + throw new InvalidChunkException(string.Format("Critical chunk {0} was not recognized.", type)); + + data = new byte[length + 4]; + ASCIIEncoding.ASCII.GetBytes(type).CopyTo(data, 0); + + s.Read(data, 4, length); + + goodcrc = Utils.ReadUInt(s, Utils.Endianness.Big); + crc32 = CRC32.Hash(0, data, 0, length + 4); + + /*if (goodcrc != crc32) + throw new CorruptChunkDataException( + string.Format("Invalid CRC32 (actual value is 0x{0:x8}, specified value is 0x{1:x8}) for {2} chunk.", crc32, goodcrc, type)); + */ + Chunk newChunk = null; + + try + { + newChunk = Activator.CreateInstance(GetChunkHandler(type), data, this) as Chunk; + } + catch (Exception ex) + { + if (char.IsUpper(type[0])) + throw ex; + else if ((Options & PNGImageOptions.AncillaryChunkErrors) != 0) + throw ex; + } + + _chunks.Add(newChunk); + + if (type == "IEND") + break; + + chunkNumber++; + } + + this.Verify(); + } + + /// + /// Checks the image's chunks for conformance to the PNG specification. + /// + public void Verify() + { + // verify the critical chunks + // IHDR and IEND + if (_chunks["IHDR"] == null) + throw new InvalidImageException("Critical chunk IHDR was not found."); + if (_chunks[0].Type != "IHDR") + throw new InvalidImageException("IHDR chunk must appear first."); + + if (_chunks["IEND"] == null) + throw new InvalidImageException("Critical chunk IEND was not found."); + if (_chunks[_chunks.Count - 1].Type != "IEND") + throw new InvalidImageException("IEND chunk must appear last."); + + // PLTE + IHDRChunk hdr = _chunks["IHDR"] as IHDRChunk; + + if (hdr.ColorType == ColorType.IndexedColor) + if (_chunks["PLTE"] == null) + throw new InvalidImageException( + "Critical chunk PLTE required for color type 3 (indexed color) was not found."); + + if (hdr.ColorType == ColorType.Grayscale || hdr.ColorType == ColorType.GrayscaleWithAlpha) + if (_chunks["PLTE"] != null) + throw new InvalidImageException( + "PLTE chunk cannot appear for color types 0 and 4 (grayscale and grayscale with alpha)."); + + // IDAT + if (_chunks["IDAT"] == null) + throw new InvalidImageException("Critical chunk IDAT was not found."); + + // verify chunk counts + Dictionary counts = new Dictionary(); + + foreach (Chunk c in _chunks) + if (!counts.ContainsKey(c.GetType())) + counts.Add(c.GetType(), 1); + else + counts[c.GetType()]++; + + foreach (Type t in counts.Keys) + { + if (_classes.ContainsValue(t)) + { + ChunkAttribute a = t.GetCustomAttributes(typeof(ChunkAttribute), true)[0] as ChunkAttribute; + + if (a.AllowMultiple == false && counts[t] > 1) + throw new InvalidImageException( + string.Format("Multiple instances ({0}) of {1} chunk are not allowed.", counts[t], a.Type)); + } + } + + // verify ancillary chunks + if (_chunks["iCCP"] != null && _chunks["sRGB"] != null) + throw new InvalidImageException("iCCP chunk and sRGB chunk cannot be both present."); + + // verify chunk ordering + int plteLoc, idatLoc; + + plteLoc = _chunks.IndexOf("PLTE"); + idatLoc = _chunks.IndexOf("IDAT"); + + // verify that IDAT chunks are consecutive + bool idatEnded = false; + + for (int i = idatLoc; i < _chunks.Count; i++) + { + if (_chunks[i].Type == "IDAT" && idatEnded) + throw new InvalidImageException("IDAT chunks must be consecutive."); + else if (_chunks[i].Type != "IDAT" && !idatEnded) + idatEnded = true; + } + + // PLTE must be before IDAT + if (plteLoc >= idatLoc) + throw new InvalidImageException("PLTE chunk must be before first IDAT chunk."); + + // verify other chunks' ordering + string[] beforePLTEbeforeIDAT = new string[] { "cHRM", "gAMA", "iCCP", "sBIT", "sRGB" }; + string[] afterPLTEbeforeIDAT = new string[] { "bKGD", "hIST", "tRNS" }; + string[] beforeIDAT = new string[] { "pHYs", "sPLT" }; + + for (int i = 0; i < _chunks.Count; i++) + { + if (Utils.ArrayContains(beforePLTEbeforeIDAT, _chunks[i].Type)) + { + if (((plteLoc == -1) ? false : (i >= plteLoc)) || i >= idatLoc) + throw new InvalidImageException( + string.Format("{0} chunk must be before PLTE chunk and first IDAT chunk.", _chunks[i].Type)); + } + + if (Utils.ArrayContains(afterPLTEbeforeIDAT, _chunks[i].Type)) + { + if (((plteLoc == -1) ? false : (i <= plteLoc)) || i >= idatLoc) + throw new InvalidImageException( + string.Format("{0} must be after PLTE chunk and before first IDAT chunk.", _chunks[i].Type)); + } + + if (Utils.ArrayContains(beforeIDAT, _chunks[i].Type)) + { + if (i >= idatLoc) + throw new InvalidImageException( + string.Format("{0} must be before first IDAT chunk.", _chunks[i].Type)); + } + } + } + + /// + /// Writes the PNG image to a file. + /// + /// The filename. + public void Write(string path) + { + Write(path, FileMode.OpenOrCreate); + } + + /// + /// Writes the PNG image to a file. + /// + /// The filename. + /// The file mode. + public void Write(string path, FileMode mode) + { + FileStream fs = new FileStream(path, mode); + + Write(fs, true); + fs.Close(); + } + + /// + /// Writes the PNG image to a stream. + /// + /// The stream to which the image is written. + public void Write(Stream s, bool verify) + { + if (verify) + this.Verify(); + + BinaryWriter bw = new BinaryWriter(s); + + bw.Write(_header); + + foreach (Chunk c in _chunks) + c.Write(s); + + bw.Flush(); + bw.Close(); + } + + /// + /// Gets a copy of the standard PNG file header. + /// + public byte[] Header + { + get { return (byte[])_header.Clone(); } + } + + /// + /// Gets the chunk handler classes. + /// + internal Dictionary Classes + { + get { return _classes; } + } + + /// + /// Gets the chunk handler class for the specified type. + /// + /// The PNG chunk type. + /// The handler class. + public Type GetChunkHandler(string type) + { + Type classType = typeof(Chunk); + + if (_classes.ContainsKey(type)) + classType = _classes[type]; + + return classType; + } + + /// + /// Loads the list of chunk handler classes from both this assembly and any specified handlers. + /// + /// The list of custom chunk handlers. + private void LoadClasses(Type[] customHandlers) + { + List types = new List(); + + foreach (Type t in Assembly.GetExecutingAssembly().GetTypes()) + types.Add(t); + + foreach (Type t in customHandlers) + types.Add(t); + + foreach (Type t in types) + { + object[] attributes = t.GetCustomAttributes(typeof(ChunkAttribute), false); + + foreach (ChunkAttribute a in attributes) + { + _classes.Add(a.Type, t); + } + } + } + + /// + /// Gets the list chunks in this PNG image. + /// + public ChunkCollection Chunks + { + get { return _chunks; } + } + } +} diff --git a/PNGNet/PNGNet.csproj b/PNGNet/PNGNet.csproj new file mode 100644 index 0000000..f01fdaa --- /dev/null +++ b/PNGNet/PNGNet.csproj @@ -0,0 +1,99 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {D5CB46E9-E69B-4B45-A15A-C0087833DD4D} + Library + Properties + PNGNet + PNGNet + v2.0 + 512 + + + + + 3.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PNGNet/Properties/AssemblyInfo.cs b/PNGNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f5adcb8 --- /dev/null +++ b/PNGNet/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PNGNet")] +[assembly: AssemblyDescription("PNG Library for .NET")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("wj32")] +[assembly: AssemblyProduct("PNGNet")] +[assembly: AssemblyCopyright("Copyright © wj32 2008. Licensed under the GNU LGPL v3.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5680bf06-46e1-414b-b915-f01bf0005429")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/PNGNet/Utils.cs b/PNGNet/Utils.cs new file mode 100644 index 0000000..4502b28 --- /dev/null +++ b/PNGNet/Utils.cs @@ -0,0 +1,285 @@ +/* + * PNG.Net + * + * Copyright (C) 2008 wj32 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Drawing; + +namespace PNGNet +{ + public static class Utils + { + public enum Endianness + { + Little, Big + } + + public static bool ArrayContains(T[] array, T element) + { + foreach (T e in array) + if (e.Equals(element)) + return true; + + return false; + } + + public static bool BytesEqual(byte[] b1, byte[] b2) + { + for (int i = 0; i < b1.Length; i++) + if (b1[i] != b2[i]) + return false; + + return true; + } + + public static int BytesToInt(byte[] data, Endianness type) + { + if (type == Endianness.Little) + { + return (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + } + else if (type == Endianness.Big) + { + return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]); + } + else + { + throw new ArgumentException(); + } + } + + public static uint BytesToUInt(byte[] data, Endianness type) + { + return BytesToUInt(data, 0, type); + } + + public static uint BytesToUInt(byte[] data, int offset, Endianness type) + { + if (type == Endianness.Little) + { + return (uint)(data[offset]) | (uint)(data[offset + 1] << 8) | + (uint)(data[offset + 2] << 16) | (uint)(data[offset + 3] << 24); + } + else if (type == Endianness.Big) + { + return (uint)(data[offset] << 24) | (uint)(data[offset + 1] << 16) | + (uint)(data[offset + 2] << 8) | (uint)(data[offset + 3]); + } + else + { + throw new ArgumentException(); + } + } + + public static ushort BytesToUShort(byte[] data, Endianness type) + { + return BytesToUShort(data, 0, type); + } + + public static ushort BytesToUShort(byte[] data, int offset, Endianness type) + { + if (type == Endianness.Little) + { + return (ushort)(data[offset] | (data[offset + 1] << 8)); + } + else if (type == Endianness.Big) + { + return (ushort)((data[offset] << 8) | data[offset + 1]); + } + else + { + throw new ArgumentException(); + } + } + + public static bool ColorsEqual(Color a, Color b) + { + return (a.R == b.R) && (a.G == b.G) && (a.B == b.B) && (a.A == b.A); + } + + public static int IntCeilDiv(int a, int b) + { + return (int)Math.Ceiling(((double)a / b)); + } + + public static byte[] IntToBytes(int n, Endianness type) + { + byte[] data = new byte[4]; + + if (type == Endianness.Little) + { + data[0] = (byte)(n & 0xff); + data[1] = (byte)((n >> 8) & 0xff); + data[2] = (byte)((n >> 16) & 0xff); + data[3] = (byte)((n >> 24) & 0xff); + } + else if (type == Endianness.Big) + { + data[0] = (byte)((n >> 24) & 0xff); + data[1] = (byte)((n >> 16) & 0xff); + data[2] = (byte)((n >> 8) & 0xff); + data[3] = (byte)(n & 0xff); + } + else + { + throw new ArgumentException(); + } + + return data; + } + + public static int GetBitsPerPixel(ColorType ct, int bpp) + { + switch (ct) + { + case ColorType.Grayscale: + return bpp; + case ColorType.GrayscaleWithAlpha: + return bpp * 2; + case ColorType.IndexedColor: + return bpp; + case ColorType.Truecolor: + return bpp * 3; + case ColorType.TruecolorWithAlpha: + return bpp * 4; + default: + throw new Exception("Invalid color type."); + } + } + + public static int GetBytesPerPixel(ColorType ct, int bpp) + { + return GetBitsPerPixel(ct, bpp) / 8; + } + + public static Color MakeGray(int value) + { + return Color.FromArgb(value, value, value); + } + + public static Color MakeGray(int alpha, int value) + { + return Color.FromArgb(alpha, value, value, value); + } + + public static byte[] ReverseBytes(byte[] data) + { + byte[] newdata = new byte[data.Length]; + + for (int i = 0; i < data.Length; i++) + newdata[i] = data[data.Length - i - 1]; + + return newdata; + } + + public static uint ReverseEndian(uint n) + { + uint b0 = n & 0xff; + uint b1 = (n >> 8) & 0xff; + uint b2 = (n >> 16) & 0xff; + uint b3 = (n >> 24) & 0xff; + + b0 <<= 24; + b1 <<= 16; + b2 <<= 8; + + return b0 | b1 | b2 | b3; + } + + public static int ReadInt(Stream s, Utils.Endianness type) + { + byte[] buffer = new byte[4]; + + if (s.Read(buffer, 0, 4) == 0) + throw new EndOfStreamException(); + + return BytesToInt(buffer, type); + } + + public static string ReadString(Stream s, int length) + { + byte[] buffer = new byte[length]; + + if (s.Read(buffer, 0, length) == 0) + throw new EndOfStreamException(); + + return System.Text.ASCIIEncoding.ASCII.GetString(buffer); + } + + public static uint ReadUInt(Stream s, Utils.Endianness type) + { + byte[] buffer = new byte[4]; + + if (s.Read(buffer, 0, 4) == 0) + throw new EndOfStreamException(); + + return BytesToUInt(buffer, type); + } + + public static byte[] UIntToBytes(uint n, Endianness type) + { + byte[] data = new byte[4]; + + if (type == Endianness.Little) + { + data[0] = (byte)(n & 0xff); + data[1] = (byte)((n >> 8) & 0xff); + data[2] = (byte)((n >> 16) & 0xff); + data[3] = (byte)((n >> 24) & 0xff); + } + else if (type == Endianness.Big) + { + data[0] = (byte)((n >> 24) & 0xff); + data[1] = (byte)((n >> 16) & 0xff); + data[2] = (byte)((n >> 8) & 0xff); + data[3] = (byte)(n & 0xff); + } + else + { + throw new ArgumentException(); + } + + return data; + } + + public static byte[] UShortToBytes(ushort n, Endianness type) + { + byte[] data = new byte[2]; + + if (type == Endianness.Little) + { + data[0] = (byte)(n & 0xff); + data[1] = (byte)((n >> 8) & 0xff); + } + else if (type == Endianness.Big) + { + data[0] = (byte)((n >> 8) & 0xff); + data[1] = (byte)(n & 0xff); + } + else + { + throw new ArgumentException(); + } + + return data; + } + } +}