BinaryWriter basé sur les bits en C #

Je travaille sur un format de police pré-compilé B / W / Niveaux de gris à base de bits et je rencontrais des problèmes de lecture ou d’écriture du format (je n’ai pas pu déterminer où se trouvait le problème. (J’ai une version basée sur les bits N / B fonctionne, mais une police aliasée ne semble pas très belle, comme vous pouvez l’imaginer, en particulier lorsque vous travaillez avec un écran de 320 x 200 pixels)) un bool [] lorsque j’ai extrait les données d’image.

Le format de base d’un pixel dans le fichier ressemble à ceci:

1 – pixel blanc (le plus court, car ce serait la plupart des pixels)

00 – Pixel noir (aucune raison d’écrire 10 bits pour un pixel noir pur, dont il existe un nombre raisonnable)

01 – Pixel en niveaux de gris, suivi d’un octet décrivant la nuance du pixel

Maintenant, tout va bien avec l’écriture des informations requirejses, car il s’agit d’octets complets, mais le par défaut .Net 4.0 BinaryWriter écrit une valeur booléenne sous la forme d’un octet complet et, comme vous pouvez l’imaginer, cela empêche l’utilisation d’un format. Alors je me demandais, y at-il une implémentation BinaryWriter, (et BinaryReader) là-bas qui est basée sur les bits

Edit: J’ai fini par créer le mien. (Voir la réponse pour le code.)

Je ne crois pas qu’il y ait quoi que ce soit dans le cadre pour cela, non. En gros, vous devez écrire une classe pour envelopper un BinaryWriter (ou simplement un stream) et “l’octet écrit jusqu’à présent” et le nombre de bits écrits. Lorsque le nombre de bits atteint 8, écrivez l’octet dans le stream sous-jacent et effacez-le.

EDIT: le PO a publié une mise en œuvre possible et effective de la suggestion ci- dessus.

J’ai fini par écrire le mien, alors les voici.

Le BinaryWriter (j’ai seulement remplacé ceux dont j’avais besoin)

 private class BinaryWriter : System.IO.BinaryWriter { private bool[] curByte = new bool[8]; private byte curBitIndx = 0; private System.Collections.BitArray ba; public BinaryWriter(Stream s) : base(s) { } public override void Flush() { base.Write(ConvertToByte(curByte)); base.Flush(); } public override void Write(bool value) { curByte[curBitIndx] = value; curBitIndx++; if (curBitIndx == 8) { base.Write(ConvertToByte(curByte)); this.curBitIndx = 0; this.curByte = new bool[8]; } } public override void Write(byte value) { ba = new BitArray(new byte[] { value }); for (byte i = 0; i < 8; i++) { this.Write(ba[i]); } ba = null; } public override void Write(byte[] buffer) { for (int i = 0; i < buffer.Length; i++) { this.Write((byte)buffer[i]); } } public override void Write(uint value) { ba = new BitArray(BitConverter.GetBytes(value)); for (byte i = 0; i < 32; i++) { this.Write(ba[i]); } ba = null; } public override void Write(ulong value) { ba = new BitArray(BitConverter.GetBytes(value)); for (byte i = 0; i < 64; i++) { this.Write(ba[i]); } ba = null; } public override void Write(ushort value) { ba = new BitArray(BitConverter.GetBytes(value)); for (byte i = 0; i < 16; i++) { this.Write(ba[i]); } ba = null; } private static byte ConvertToByte(bool[] bools) { byte b = 0; byte bitIndex = 0; for (int i = 0; i < 8; i++) { if (bools[i]) { b |= (byte)(((byte)1) << bitIndex); } bitIndex++; } return b; } } 

Et, BinaryReader, encore une fois, je n’ai remplacé que les méthodes dont j’avais besoin.

 private class BinaryReader : System.IO.BinaryReader { private bool[] curByte = new bool[8]; private byte curBitIndx = 0; private BitArray ba; public BinaryReader(Stream s) : base(s) { ba = new BitArray(new byte[] { base.ReadByte() }); ba.CopyTo(curByte, 0); ba = null; } public override bool ReadBoolean() { if (curBitIndx == 8) { ba = new BitArray(new byte[] { base.ReadByte() }); ba.CopyTo(curByte, 0); ba = null; this.curBitIndx = 0; } bool b = curByte[curBitIndx]; curBitIndx++; return b; } public override byte ReadByte() { bool[] bar = new bool[8]; byte i; for (i = 0; i < 8; i++) { bar[i] = this.ReadBoolean(); } byte b = 0; byte bitIndex = 0; for (i = 0; i < 8; i++) { if (bar[i]) { b |= (byte)(((byte)1) << bitIndex); } bitIndex++; } return b; } public override byte[] ReadBytes(int count) { byte[] bytes = new byte[count]; for (int i = 0; i < count; i++) { bytes[i] = this.ReadByte(); } return bytes; } public override ushort ReadUInt16() { byte[] bytes = ReadBytes(2); return BitConverter.ToUInt16(bytes, 0); } public override uint ReadUInt32() { byte[] bytes = ReadBytes(4); return BitConverter.ToUInt32(bytes, 0); } public override ulong ReadUInt64() { byte[] bytes = ReadBytes(8); return BitConverter.ToUInt64(bytes, 0); } } 

Si vous conservez vos données dans un tableau d’octets (les bool sont inutiles et prennent trop de place, si vous les utilisez pour des bits, ils prennent un octet en mémoire) ou dans un tableau d’une struct particulière qui convient à votre format de données.

Une fois que vous avez une représentation de la mémoire interne, vous n’avez plus besoin d’un scripteur binary. Vous pouvez simplement écrire les données sur un BinaryWriter et vous en avez fini.

… mais le par défaut .Net 4.0 BinaryWriter écrit une valeur booléenne sous forme d’octet complet et, comme vous pouvez l’imaginer, cela empêche l’utilisation d’un format à base de bits ….

La raison en est que: la valeur booléenne est, par définition, de 1 octet en C #. Le BinaryWriter écrit simplement ce que vous lui donnez.

Je me suis aussi retrouvé dans le besoin, alors je me suis basé sur OP et j’ai rempli toutes les lectures / écritures (sauf char & ssortingng car celles-ci sont un peu spéciales).

J’ai également fait un test unitaire rapide, essayez-le. Pour les stream ne contenant que des valeurs booléennes (ou d’autres types de valeurs sous-octets personnalisés), il est évidemment 87,5% moins cher, et pour un stream mélangé aléatoire contenant 75% de valeurs booléennes, il était d’environ 33% moins cher. Donc, pourrait être utile pour certains scénarios.

Voici les deux classes au cas où quelqu’un d’autre en aurait besoin, utilisez-le à vos risques et périls:

 ///  /// A binary writer that packs data into bits, to preserve space when using many bit/boolean values. Up to about 87.5% cheaper for streams that only contains boolean values. /// By: jsmars@gmail.com, based on posters classes in this post: https://stackoverflow.com/questions/7051939/bit-based-binarywriter-in-c-sharp ///  public class BinaryBitWriter : BinaryWriter { public byte BitPosition { get; private set; } = 0; private bool[] curByte = new bool[8]; private System.Collections.BitArray ba; public BinaryBitWriter(Stream s) : base(s) { } public override void Flush() { flushBitBuffer(); base.Flush(); } public override void Write(byte[] buffer, int index, int count) { for (int i = index; i < index + count; i++) Write((byte)buffer[i]); } public override void Write(byte value) { ba = new BitArray(new byte[] { value }); for (byte i = 0; i < 8; i++) Write(ba[i]); } public override void Write(bool value) { curByte[BitPosition] = value; BitPosition++; if (BitPosition == 8) flushBitBuffer(); } public override void Write(char[] chars, int index, int count) { for (int i = index; i < index + count; i++) Write(chars[i]); } public override void Write(string value) { // write strings as normal for now, so flush the bits first flushBitBuffer(); base.Write(value); } public override void Write(decimal value) { var ints = decimal.GetBits(value); for (int i = 0; i < ints.Length; i++) Write(ints[i]); } public override void Write(float value) => Write(BitConverter.GetBytes(value)); public override void Write(ulong value) => Write(BitConverter.GetBytes(value)); public override void Write(long value) => Write(BitConverter.GetBytes(value)); public override void Write(uint value) => Write(BitConverter.GetBytes(value)); public override void Write(int value) => Write(BitConverter.GetBytes(value)); public override void Write(ushort value) => Write(BitConverter.GetBytes(value)); public override void Write(short value) => Write(BitConverter.GetBytes(value)); public override void Write(double value) => Write(BitConverter.GetBytes(value)); public override void Write(char[] value) => Write(value, 0, value.Length); public override void Write(char value) { // write ssortingngs as normal for now, so flush the bits first flushBitBuffer(); base.Write(value); //var b = BitConverter.GetBytes(value); //Write(b); } public override void Write(byte[] buffer) => Write(buffer, 0, buffer.Length); public override void Write(sbyte value) => Write((byte)value); void flushBitBuffer() { if (BitPosition == 0) // Nothing to flush return; base.Write(ConvertToByte(curByte)); BitPosition = 0; curByte = new bool[8]; } private static byte ConvertToByte(bool[] bools) { byte b = 0; byte bitIndex = 0; for (int i = 0; i < 8; i++) { if (bools[i]) b |= (byte)(((byte)1) << bitIndex); bitIndex++; } return b; } } public class BinaryBitReader : BinaryReader { public byte BitPosition { get; private set; } = 8; private bool[] curByte = new bool[8]; public BinaryBitReader(Stream s) : base(s) { } public override bool ReadBoolean() { if (BitPosition == 8) { var ba = new BitArray(new byte[] { base.ReadByte() }); ba.CopyTo(curByte, 0); BitPosition = 0; } bool b = curByte[BitPosition]; BitPosition++; return b; } public override byte ReadByte() { bool[] bar = new bool[8]; byte i; for (i = 0; i < 8; i++) { bar[i] = this.ReadBoolean(); } byte b = 0; byte bitIndex = 0; for (i = 0; i < 8; i++) { if (bar[i]) { b |= (byte)(((byte)1) << bitIndex); } bitIndex++; } return b; } public override byte[] ReadBytes(int count) { byte[] bytes = new byte[count]; for (int i = 0; i < count; i++) { bytes[i] = this.ReadByte(); } return bytes; } //public override int Read() => BitConverter.ToUInt64(ReadBytes(8), 0); public override int Read(byte[] buffer, int index, int count) { for (int i = index; i < index + count; i++) buffer[i] = ReadByte(); return count; // we can return this here, it will die at the above row if anything is off } public override int Read(char[] buffer, int index, int count) { for (int i = index; i < index + count; i++) buffer[i] = ReadChar(); return count; // we can return this here, it will die at the above row if anything is off } public override char ReadChar() { BitPosition = 8; return base.ReadChar(); //BitConverter.ToChar(ReadBytes(2), 0); } public override char[] ReadChars(int count) { var chars = new char[count]; Read(chars, 0, count); return chars; } public override decimal ReadDecimal() { int[] ints = new int[4]; for (int i = 0; i < ints.Length; i++) ints[i] = ReadInt32(); return new decimal(ints); } public override double ReadDouble() => BitConverter.ToDouble(ReadBytes(8), 0); public override short ReadInt16() => BitConverter.ToInt16(ReadBytes(2), 0); public override int ReadInt32() => BitConverter.ToInt32(ReadBytes(4), 0); public override long ReadInt64() => BitConverter.ToInt64(ReadBytes(8), 0); public override sbyte ReadSByte() => (sbyte)ReadByte(); public override float ReadSingle() => BitConverter.ToSingle(ReadBytes(4), 0); public override ssortingng ReadSsortingng() { BitPosition = 8; // Make sure we read a new byte when we start reading the ssortingng return base.ReadSsortingng(); } public override ushort ReadUInt16() => BitConverter.ToUInt16(ReadBytes(2), 0); public override uint ReadUInt32() => BitConverter.ToUInt32(ReadBytes(4), 0); public override ulong ReadUInt64() => BitConverter.ToUInt64(ReadBytes(8), 0); } 

Et les tests unitaires:

 public static bool UnitTest() { const int testPairs = 512; var bitstream = new MemoryStream(); var bitwriter = new BinaryBitWriter(bitstream); var bitreader = new BinaryBitReader(bitstream); byte[] bytes = new byte[] { 1, 2, 3, 4, 255 }; byte Byte = 128; bool Bool = true; char[] chars = new char[] { 'a', 'b', 'c' }; ssortingng str = "hello"; var Float = 2.5f; ulong Ulong = 12345678901234567890; long Long = 1122334455667788; uint Uint = 1234567890; int Int = 999998888; ushort UShort = 12345; short Short = 4321; double Double = 9.9; char Char = 'A'; sbyte Sbyte = -128; decimal Decimal = 10000.00001m; List pairs = new List(); // Make pairs of write and read tests pairs.Add(new BBTest(Bool, (w) => w.Write(Bool), (r) => { if (r.ReadBoolean() != Bool) throw new Exception(); })); pairs.Add(new BBTest(bytes, (w) => w.Write(bytes, 0, 5), (r) => { if (arrayCompare(r.ReadBytes(5), bytes)) throw new Exception(); })); pairs.Add(new BBTest(Byte, (w) => w.Write(Byte), (r) => { if (r.ReadByte() != Byte) throw new Exception(); })); pairs.Add(new BBTest(chars, (w) => w.Write(chars, 0, 3), (r) => { if (arrayCompare(r.ReadChars(3), chars)) throw new Exception(); })); ///////////// pairs.Add(new BBTest(str, (w) => w.Write(str), (r) => { ssortingng s; if ((s = r.ReadSsortingng()) != str) throw new Exception(); })); pairs.Add(new BBTest(Decimal, (w) => w.Write(Decimal), (r) => { if (r.ReadDecimal() != Decimal) throw new Exception(); })); pairs.Add(new BBTest(Float, (w) => w.Write(Float), (r) => { if (r.ReadSingle() != Float) throw new Exception(); })); pairs.Add(new BBTest(Ulong, (w) => w.Write(Ulong), (r) => { if (r.ReadUInt64() != Ulong) throw new Exception(); })); pairs.Add(new BBTest(Long, (w) => w.Write(Long), (r) => { if (r.ReadInt64() != Long) throw new Exception(); })); pairs.Add(new BBTest(Uint, (w) => w.Write(Uint), (r) => { if (r.ReadUInt32() != Uint) throw new Exception(); })); pairs.Add(new BBTest(Int, (w) => w.Write(Int), (r) => { if (r.ReadInt32() != Int) throw new Exception(); })); pairs.Add(new BBTest(UShort, (w) => w.Write(UShort), (r) => { if (r.ReadUInt16() != UShort) throw new Exception(); })); pairs.Add(new BBTest(Short, (w) => w.Write(Short), (r) => { if (r.ReadInt16() != Short) throw new Exception(); })); pairs.Add(new BBTest(Double, (w) => w.Write(Double), (r) => { if (r.ReadDouble() != Double) throw new Exception(); })); pairs.Add(new BBTest(Char, (w) => w.Write(Char), (r) => { if (r.ReadChar() != Char) throw new Exception(); })); /////////////// pairs.Add(new BBTest(bytes, (w) => w.Write(bytes), (r) => { if (arrayCompare(r.ReadBytes(5), bytes)) throw new Exception(); })); pairs.Add(new BBTest(Sbyte, (w) => w.Write(Sbyte), (r) => { if (r.ReadSByte() != Sbyte) throw new Exception(); })); // Now add all tests, and then a bunch of randomized tests, to make sure we test lots of combinations incase there is some offsetting error List test = new List(); test.AddRange(pairs); var rnd = new Random(); for (int i = 0; i < testPairs - test.Count; i++) { if (rnd.NextDouble() < 0.75) test.Add(pairs[0]); else test.Add(pairs[rnd.Next(pairs.Count)]); } // now write all the tests for (int i = 0; i < test.Count; i++) test[i].Writer(bitwriter); bitwriter.Flush(); // now reset the stream and test to see that they are the same bitstream.Position = 0; for (int i = 0; i < test.Count; i++) test[i].ReadTest(bitreader); // As comparison, lets write the same stuff to a normal binarywriter and compare sized var binstream = new MemoryStream(); var binwriter = new BinaryWriter(binstream); for (int i = 0; i < test.Count; i++) test[i].Writer(binwriter); binwriter.Flush(); var saved = 1 - bitstream.Length / (float)binstream.Length; var result = $"BinaryBitWriter was {(saved * 100).ToString("0.00")}% cheaper than a normal BinaryWriter with random data"; bool arrayCompare(IEnumerable a, IEnumerable b) { var B = b.GetEnumerator(); B.MoveNext(); foreach (var item in a) { if (item != B.Current) return false; B.MoveNext(); } return true; } return true; } delegate void writer(BinaryWriter w); delegate void reader(BinaryReader r); class BBTest { public object Object; public writer Writer; public reader ReadTest; public BBTest(object obj, writer w, reader r) { Object = obj; Writer = w; ReadTest = r; } public override string ToString() => Object.ToSsortingng(); }