/// <summary> /// Function to persist the shader data to a stream as a <see cref="GorgonChunkFile{T}"/>. /// </summary> /// <param name="stream">The stream to write the data into.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="stream"/> parameter is <b>null</b>.</exception> /// <exception cref="ArgumentException">Thrown when the stream is read only.</exception> /// <remarks> /// <para> /// This will write the shader data as <see cref="GorgonChunkFile{T}"/> formatted data into the supplied <paramref name="stream"/>. Shaders may take some time to compile, saving them to a binary /// format in a stream will help cut down on the time it takes to initialize an application. /// </para> /// <para> /// This makes use of the Gorgon <see cref="GorgonChunkFile{T}"/> format to allow flexible storage of data. The Gorgon shader format is broken into 2 chunks, both of which are available in the /// <see cref="GorgonShaderFactory.BinaryShaderMetaData"/>, and <see cref="GorgonShaderFactory.BinaryShaderByteCode"/> constants. The file header for the format is stored in the /// <see cref="GorgonShaderFactory.BinaryShaderFileHeader"/> constant. /// </para> /// <para> /// The file format is as follows: /// <list type="bullet"> /// <item> /// <term><see cref="GorgonShaderFactory.BinaryShaderFileHeader"/></term> /// <description>This describes the type of file, and the version.</description> /// </item> /// <item> /// <term><see cref="GorgonShaderFactory.BinaryShaderMetaData"/></term> /// <description>Shader metadata, such as the <see cref="Core.ShaderType"/> (<see cref="int"/>), debug flag (<see cref="bool"/>), and the entry point name (<see cref="string"/>) is stored here.</description> /// </item> /// <item> /// <term><see cref="GorgonShaderFactory.BinaryShaderByteCode"/></term> /// <description>The compiled shader byte code is stored here and is loaded as a <see cref="byte"/> array.</description> /// </item> /// </list> /// </para> /// </remarks> /// <seealso cref="GorgonChunkFile{T}"/> /// <seealso cref="GorgonChunkFileReader"/> /// <seealso cref="GorgonChunkFileWriter"/> public void SaveToStream(Stream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (!stream.CanWrite) { throw new ArgumentException(Resources.GORGFX_ERR_STREAM_WRITE_ONLY, nameof(stream)); } var chunkFile = new GorgonChunkFileWriter(stream, GorgonShaderFactory.BinaryShaderFileHeader.ChunkID()); try { GorgonBinaryWriter writer = chunkFile.OpenChunk(GorgonShaderFactory.BinaryShaderMetaData); ShaderType shaderType = ShaderType; writer.WriteValue(ref shaderType); writer.WriteValue(ref _isDebug); writer.Write(Name); chunkFile.CloseChunk(); writer = chunkFile.OpenChunk(GorgonShaderFactory.BinaryShaderByteCode); writer.Write(D3DByteCode.Data); } finally { chunkFile.CloseChunk(); } }
/// <summary> /// Function to write an array of value types to a stream using the GorgonBinaryWriter and reading it back again using the GorgonBinaryReader. /// </summary> /// <param name="stream">The stream that will receive the data.</param> private static void WriteArrayValues(MemoryStream stream) { stream.Position = 0; var writer = new GorgonBinaryWriter(stream, true); var reader = new GorgonBinaryReader(stream, true); try { var expected = new SomeTestData[3]; for (int i = 1; i < 4; ++i) { expected[i - 1] = new SomeTestData { Value1 = i, Value2 = System.Math.PI * i, Value3 = (short)(i & 2) }; } Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Writing/Reading an array of value types to a memory stream."); Console.ForegroundColor = ConsoleColor.White; writer.WriteRange(expected); stream.Position = 0; var actual = new SomeTestData[4]; reader.ReadRange(actual, 1); for (int i = 1; i < 4; ++i) { Console.ForegroundColor = ConsoleColor.Yellow; Console.Write($"[{i - 1}] "); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine($"int32 Value1 = {expected[i - 1].Value1}: {actual[i].Value1 == expected[i - 1].Value1}"); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write($"[{i - 1}] "); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine($"double Value2 = {expected[i - 1].Value2:0.00000}: {actual[i].Value2.EqualsEpsilon(expected[i - 1].Value2)}"); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write($"[{i - 1}] "); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine($"int16 Value3 = {expected[i - 1].Value3}: {actual[i].Value3 == expected[i - 1].Value3}"); } stream.Position = 0; } finally { writer.Dispose(); reader.Dispose(); } }
/// <summary> /// Function to write the font data to the stream. /// </summary> /// <param name="fontData">The font data to write.</param> /// <param name="stream">The stream to write into.</param> /// <remarks> /// <para> /// Implementors must override this method to write out the font data in the expected format. /// </para> /// </remarks> protected override void OnWriteFontData(GorgonFont fontData, Stream stream) { var fontFile = new GorgonChunkFileWriter(stream, FileHeader.ChunkID()); IGorgonFontInfo fontInfo = fontData.Info; try { fontFile.Open(); GorgonBinaryWriter writer = fontFile.OpenChunk(FontInfoChunk); writer.Write(fontInfo.FontFamilyName); writer.Write(fontInfo.Size); writer.WriteValue(fontInfo.FontHeightMode); writer.WriteValue(fontInfo.FontStyle); writer.Write(fontInfo.DefaultCharacter); writer.Write(string.Join(string.Empty, fontInfo.Characters)); writer.WriteValue(fontInfo.AntiAliasingMode); writer.Write(fontInfo.OutlineColor1.ToARGB()); writer.Write(fontInfo.OutlineColor2.ToARGB()); writer.Write(fontInfo.OutlineSize); writer.Write(fontInfo.PackingSpacing); writer.Write(fontInfo.TextureWidth); writer.Write(fontInfo.TextureHeight); writer.Write(fontInfo.UsePremultipliedTextures); writer.Write(fontInfo.UseKerningPairs); fontFile.CloseChunk(); writer = fontFile.OpenChunk(FontHeightChunk); writer.Write(fontData.FontHeight); writer.Write(fontData.LineHeight); writer.Write(fontData.Ascent); writer.Write(fontData.Descent); fontFile.CloseChunk(); if (fontInfo.Brush != null) { writer = fontFile.OpenChunk(BrushChunk); writer.Write((int)fontInfo.Brush.BrushType); fontInfo.Brush.WriteBrushData(writer); fontFile.CloseChunk(); } if (fontInfo.UseKerningPairs) { WriteKerningValues(fontData, fontFile); } } finally { fontFile.Close(); } }
/// <summary>Function to write out the specifics of the font brush data to a file writer.</summary> /// <param name="writer">The writer used to write the brush data.</param> internal override void WriteBrushData(GorgonBinaryWriter writer) { writer.Write(Angle); writer.Write(ScaleAngle); writer.Write(GammaCorrection); writer.Write(Interpolation.Count); for (int i = 0; i < Interpolation.Count; ++i) { GorgonGlyphBrushInterpolator interp = Interpolation[i]; writer.Write(interp.Weight); writer.Write(interp.Color.ToARGB()); } }
/// <summary> /// Function to write out the kerning pair information for the font. /// </summary> /// <param name="fontData">The font data to write.</param> /// <param name="fontFile">The font file that is being persisted.</param> private static void WriteKerningValues(GorgonFont fontData, GorgonChunkFileWriter fontFile) { GorgonBinaryWriter writer = fontFile.OpenChunk(KernDataChunk); writer.Write(fontData.KerningPairs.Count); foreach (KeyValuePair <GorgonKerningPair, int> kerningInfo in fontData.KerningPairs) { writer.Write(kerningInfo.Key.LeftCharacter); writer.Write(kerningInfo.Key.RightCharacter); writer.Write(kerningInfo.Value); } fontFile.CloseChunk(); }
/// <summary>Function to write out the specifics of the font brush data to a file writer.</summary> /// <param name="writer">The writer used to write the brush data.</param> internal override void WriteBrushData(GorgonBinaryWriter writer) { writer.Write((int)WrapMode); writer.Write(Points.Count); for (int i = 0; i < Points.Count; ++i) { writer.WriteValue(Points[i]); } writer.Write(BlendFactors.Count); for (int i = 0; i < BlendFactors.Count; ++i) { writer.Write(BlendFactors[i]); } writer.Write(BlendPositions.Count); for (int i = 0; i < BlendPositions.Count; ++i) { writer.Write(BlendPositions[i]); } writer.Write(CenterColor.ToARGB()); writer.WriteValue(CenterPoint); writer.WriteValue(FocusScales); writer.Write(Interpolation.Count); for (int i = 0; i < Interpolation.Count; ++i) { GorgonGlyphBrushInterpolator interp = Interpolation[i]; writer.Write(interp.Weight); writer.Write(interp.Color.ToARGB()); } writer.Write(SurroundColors.Count); for (int i = 0; i < SurroundColors.Count; ++i) { writer.Write(SurroundColors[i].ToARGB()); } }
/// <summary> /// Function to copy the contents of this buffer into a stream. /// </summary> /// <param name="stream">The stream to write into.</param> /// <param name="startIndex">[Optional] The index in the buffer to start copying from.</param> /// <param name="count">[Optional] The maximum number of items to read from the stream.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="stream"/> parameter is <b>null</b>.</exception> /// <exception cref="IOException">Thrown when the <paramref name="stream"/> is read only.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="startIndex"/> is less than 0.</exception> /// <exception cref="ArgumentException">Thrown when the <paramref name="startIndex"/> + <paramref name="count"/> are equal to or greater than the <see cref="Length"/>.</exception> /// <remarks> /// <para> /// If the <paramref name="count"/> parameter is ommitted, then the full length of the source buffer, minus the <paramref name="startIndex"/> is used. Ensure that there is enough space in the /// <paramref name="stream"/> to accomodate the amount of data required. /// </para> /// </remarks> /// <seealso cref="ToStream(int, int?)"/> public void CopyTo(Stream stream, int startIndex = 0, int?count = null) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (!stream.CanWrite) { throw new IOException(Resources.GOR_ERR_STREAM_IS_READONLY); } if (startIndex < 0) { throw new ArgumentOutOfRangeException(nameof(startIndex), Resources.GOR_ERR_DATABUFF_OFFSET_TOO_SMALL); } if (count == null) { count = Length - startIndex; } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), Resources.GOR_ERR_DATABUFF_SIZE_TOO_SMALL); } if (startIndex + count.Value > Length) { throw new ArgumentException(string.Format(Resources.GOR_ERR_DATABUFF_SIZE_OFFSET_TOO_LARGE, startIndex, count.Value)); } using (var writer = new GorgonBinaryWriter(stream, true)) { for (int i = 0; i < count.Value; ++i) { writer.WriteValue(ref this[i + startIndex]); } } }
/// <summary> /// Function to write the contents pointed at by an unsafe pointer to a stream using the GorgonBinaryWriter and reading it back again using the GorgonBinaryReader. /// </summary> /// <param name="stream">The stream that will receive the data.</param> private static unsafe void WritePointer(MemoryStream stream) { stream.Position = 0; var writer = new GorgonBinaryWriter(stream, true); var reader = new GorgonBinaryReader(stream, true); try { var data = new SomeTestData { Value1 = 1, Value2 = 2.1, Value3 = 3 }; Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Writing/Reading pointer to memory into a memory stream."); Console.ForegroundColor = ConsoleColor.White; writer.Write(&data, Unsafe.SizeOf <SomeTestData>()); stream.Position = 0; SomeTestData readData = default; reader.Read(&readData, Unsafe.SizeOf <SomeTestData>()); Console.WriteLine($"int32 Value1 = 1: {readData.Value1 == 1}"); Console.WriteLine($"double Value2 = 2.1: {readData.Value2.EqualsEpsilon(2.1)}"); Console.WriteLine($"int16 Value3 = 3: {readData.Value3 == 3}"); stream.Position = 0; } finally { writer.Dispose(); reader.Dispose(); } }
/// <summary> /// Function to write the contents of a value type to a stream using the GorgonBinaryWriter and reading it back again using the GorgonBinaryReader. /// </summary> /// <param name="stream">The stream that will receive the data.</param> private static void WriteByRefValueType(MemoryStream stream) { stream.Position = 0; var writer = new GorgonBinaryWriter(stream, true); var reader = new GorgonBinaryReader(stream, true); try { var data = new SomeTestData { Value1 = 1234, Value2 = 3.1459, Value3 = 42 }; Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Writing/Reading a value type to a memory stream."); Console.ForegroundColor = ConsoleColor.White; writer.WriteValue(ref data); stream.Position = 0; reader.ReadValue(out SomeTestData readData); Console.WriteLine($"int32 Value1 = 1234: {readData.Value1 == 1234}"); Console.WriteLine($"double Value2 = 3.1459: {readData.Value2.EqualsEpsilon(3.1459)}"); Console.WriteLine($"int16 Value3 = 42: {readData.Value3 == 42}"); stream.Position = 0; } finally { writer.Dispose(); reader.Dispose(); } }
/// <summary> /// Function to write out the specifics of the font brush data to a file writer. /// </summary> /// <param name="writer">The writer used to write the brush data.</param> internal abstract void WriteBrushData(GorgonBinaryWriter writer);
/// <summary> /// Function to write the file to the specified path. /// </summary> /// <param name="path">Path to the file.</param> /// <param name="token">Token used to cancel the task.</param> protected override void WriteFile(string path, CancellationToken token) { // Don't allow other threads in here. lock (_syncLock) { _token = token; _tempPath = ScratchFileSystem.WriteLocation + Path.GetFileName(Path.GetTempFileName()); // Calculate compression ratio. 9 is the max compression level for bzip 2. _compressionRatio = (int)System.Math.Round(Compression * 9.0f, 0, MidpointRounding.AwayFromZero); try { // Build file system. BuildFileSystem(); if (_token.IsCancellationRequested) { return; } // Turn off cancellation at this point. CanCancel(false); if (_token.IsCancellationRequested) { return; } // Write out our file. using (var outputFile = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read)) { // Combine the file system parts into a single packed file. using (var writer = new GorgonBinaryWriter(outputFile, true)) { // Write the file header. writer.Write("GORPACK1.SharpZip.BZ2"); // Copy FAT. using (var fatData = new GorgonDataStream(Encoding.UTF8.GetBytes(_fat.ToStringWithDeclaration()))) { using (var compressData = new MemoryStream()) { CompressData(fatData, compressData); compressData.Position = 0; writer.Write((int)compressData.Length); compressData.CopyTo(outputFile); } } } // Copy the file data. using (var fileData = File.Open(_tempPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { fileData.CopyTo(outputFile); } } } finally { // Destroy the temp data file. File.Delete(_tempPath); } } }
/// <summary>Function to write out the specifics of the font brush data to a file writer.</summary> /// <param name="writer">The writer used to write the brush data.</param> internal override void WriteBrushData(GorgonBinaryWriter writer) { writer.Write((int)HatchStyle); writer.Write(ForegroundColor.ToARGB()); writer.Write(BackgroundColor.ToARGB()); }
/// <summary> /// Function to persist a <see cref="IGorgonImage"/> to a stream. /// </summary> /// <param name="imageData">A <see cref="IGorgonImage"/> to persist to the stream.</param> /// <param name="stream">The stream that will receive the image data.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="stream"/>, or the <paramref name="imageData"/> parameter is <b>null</b>.</exception> /// <exception cref="ArgumentEmptyException">Thrown when the <paramref name="stream"/> is read only.</exception> /// <exception cref="NotSupportedException">Thrown when the image data in the stream has a pixel format that is unsupported by the codec.</exception> /// <remarks> /// <para> /// When persisting image data via a codec, the image must have a format that the codec can recognize. This list of supported formats is provided by the <see cref="SupportedPixelFormats"/> /// property. Applications may convert their image data a supported format before saving the data using a codec. /// </para> /// </remarks> public override void SaveToStream(IGorgonImage imageData, Stream stream) { // Ensure that we can actually read this format. We do not perform total pixel conversion on behalf of the user, they are responsible for that. // We will, however, support swizzling and pixel compression (e.g. 32 -> 24 bit). if (Array.IndexOf(_supportedFormats, imageData.Format) == -1) { throw new NotSupportedException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, imageData.Format)); } using (var writer = new GorgonBinaryWriter(stream, true)) { // Write the header for the file before we dump the file contents. TgaHeader header = GetHeader(imageData, out TGAConversionFlags conversionFlags); GorgonPitchLayout destPitch; if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888) { destPitch = new GorgonPitchLayout(imageData.Width * 3, imageData.Width * 3 * imageData.Height); } else { var formatInfo = new GorgonFormatInfo(imageData.Format); destPitch = formatInfo.GetPitchForFormat(imageData.Width, imageData.Height); } GorgonPitchLayout srcPitch = imageData.Buffers[0].PitchInformation; // If the two pitches are equal and we have no conversion requirements, then just write out the buffer. if ((destPitch == srcPitch) && (conversionFlags == TGAConversionFlags.None)) { writer.WriteValue(ref header); writer.WriteRange(imageData.Buffers[0].Data, count: srcPitch.SlicePitch); return; } unsafe { // Get the pointer to the first mip/array/depth level. byte *srcPointer = (byte *)imageData.Buffers[0].Data; var lineBuffer = new GorgonNativeBuffer <byte>(srcPitch.RowPitch); try { // Persist the working buffer to the stream. writer.WriteValue(ref header); // Write out each scan line. for (int y = 0; y < imageData.Height; y++) { byte *destPtr = (byte *)lineBuffer; if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888) { ImageUtilities.Compress24BPPScanLine(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, (conversionFlags & TGAConversionFlags.Swizzle) == TGAConversionFlags.Swizzle); } else if ((conversionFlags & TGAConversionFlags.Swizzle) == TGAConversionFlags.Swizzle) { ImageUtilities.SwizzleScanline(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, imageData.Format, ImageBitFlags.None); } else { ImageUtilities.CopyScanline(srcPointer, srcPitch.RowPitch, destPtr, destPitch.RowPitch, imageData.Format, ImageBitFlags.None); } srcPointer += srcPitch.RowPitch; writer.WriteRange(lineBuffer, count: destPitch.RowPitch); } } finally { lineBuffer?.Dispose(); } } } }
/// <summary>Function to write out the specifics of the font brush data to a file writer.</summary> /// <param name="writer">The writer used to write the brush data.</param> internal override void WriteBrushData(GorgonBinaryWriter writer) => writer.Write(Color.ToARGB());
/// <summary> /// Function to persist image data to a stream. /// </summary> /// <param name="imageData"><see cref="GorgonLibrary.Graphics.GorgonImageData">Gorgon image data</see> to persist.</param> /// <param name="stream">Stream that will contain the data.</param> protected override void SaveToStream(GorgonImageData imageData, Stream stream) { if (imageData.Settings.Format != BufferFormat.R8G8B8A8_UIntNormal) { throw new ArgumentException(@"The image format must be R8G8B8A8_UIntNormal", "imageData"); } // First, we'll need to set up our header metadata. var header = new TvHeader { MagicValueData = MagicValue, Width = imageData.Settings.Width, Height = imageData.Settings.Height }; // Write the metadata to the stream. using (var writer = new GorgonBinaryWriter(stream, true)) { writer.WriteValue(header); // Now, we need to encode the image data as 1 byte for every other color component per pixel. // In essence, we'll be writing one channel as a byte and moving to the next pixel. unsafe { // We're in unsafe land now. Again, if you're uncomfortable with pointers, the GorgonDataStream object // provides safe methods to read image data. // Get the pointer to our image buffer. var imagePtr = (byte *)imageData.UnsafePointer; // Allocate a buffer to store our scanline before dumping to the file. var scanLineBuffer = new byte[imageData.Settings.Width * 2]; // For each scan line in the image we'll encode the data as described above. for (int y = 0; y < imageData.Settings.Height; ++y) { // Read 4 bytes at a time. var colorPtr = (uint *)imagePtr; // Reset to the beginning of the scanline buffer. fixed(byte *scanLinePtr = scanLineBuffer) { // Need to alias the pointer because the result value in the fixed // block can't be changed. var scanLine = (ushort *)scanLinePtr; // Loop through the scan line until we're at its end. for (int x = 0; x < imageData.Settings.Width; ++x) { // We're assuming our image data is 4 bytes/pixel, but in real world scenarios this is dependent upon // the format of the data. var pixel = *(colorPtr++); // Get the alpha channel for this pixel. var alpha = (byte)((pixel >> 24) & 0xff); // Since we encode 1 byte per color component for each pixel, we need to bump up the bit shift // by 8 bits. Once we get above 24 bits we'll start over since we're only working with 4 bytes // per pixel in the destination. // We determine how many bits to shift the pixel based on horizontal positioning. // We assume that the image is based on 4 bytes/pixel. In most cases this value should be // determined by dividing the row pitch by the image width. // Get the color component for the pixel. var color = (byte)((pixel >> (8 * (x % 3))) & 0xff); // Write it to the scanline. // We're encoding a pixel as a single color component with its alpha channel // value into an unsigned 16 bit number. *(scanLine++) = (ushort)((color << 8) | alpha); } } // Ensure that we move to the next line by the row pitch and not the amount of pixels. // Some images put padding in for alignment reasons which can throw off the data offsets. // Also, the width is not suitable as a pixel is often more than 1 byte. imagePtr += imageData.Buffers[0].PitchInformation.RowPitch; // Send the scanline to the file. writer.Write(scanLineBuffer); } } } }
public void ChunkReaderWriter() { const string stringChunk = "STRSCHNK"; const string intChunk = "INTSCHNK"; const string header = "TheHeader"; string[] strs = { "Cow", "Dog", "Cat", "Rabbit", "Duck" }; int[] ints = { 32, 11, 89, 64, 87, 77, 16, 2, 42 }; int expectedStrLength = strs.Length; int expectedIntsLength = ints.Length; using (MemoryStream stream = new MemoryStream()) { var fileWriter = new GorgonChunkFileWriter(stream, header.ChunkID()); fileWriter.Open(); GorgonBinaryWriter writer = fileWriter.OpenChunk(stringChunk.ChunkID()); writer.Write(strs.Length); foreach (string str in strs) { writer.Write(str); } fileWriter.CloseChunk(); writer = fileWriter.OpenChunk(intChunk.ChunkID()); writer.Write(ints.Length); foreach (int intVal in ints) { writer.Write(intVal); } fileWriter.CloseChunk(); fileWriter.Close(); stream.Position = 0; var fileReader = new GorgonChunkFileReader(stream, new[] { header.ChunkID() }); fileReader.Open(); GorgonBinaryReader reader = fileReader.OpenChunk(intChunk.ChunkID()); int numInts = reader.ReadInt32(); Assert.AreEqual(expectedIntsLength, numInts); int[] intVals = new int[numInts]; for (int i = 0; i < numInts; ++i) { intVals[i] = reader.ReadInt32(); } Assert.IsTrue(ints.SequenceEqual(intVals)); fileReader.CloseChunk(); reader = fileReader.OpenChunk(stringChunk.ChunkID()); int numStrs = reader.ReadInt32(); Assert.AreEqual(expectedStrLength, numStrs); string[] strVals = new string[numStrs]; for (int i = 0; i < numStrs; ++i) { strVals[i] = reader.ReadString(); } Assert.IsTrue(strs.SequenceEqual(strVals)); fileReader.CloseChunk(); fileReader.Close(); } }