/// <summary> /// Function to write out the DDS header to the stream. /// </summary> /// <param name="settings">Meta data for the image header.</param> /// <param name="conversionFlags">Flags required for image conversion.</param> /// <returns>A TGA header value, populated with the correct settings.</returns> private TgaHeader GetHeader(IGorgonImageInfo settings, out TGAConversionFlags conversionFlags) { TgaHeader header = default; conversionFlags = TGAConversionFlags.None; if ((settings.Width > 0xFFFF) || (settings.Height > 0xFFFF)) { throw new IOException(string.Format(Resources.GORIMG_ERR_FILE_FORMAT_NOT_CORRECT, Codec)); } header.Width = (ushort)(settings.Width); header.Height = (ushort)(settings.Height); switch (settings.Format) { case BufferFormat.R8G8B8A8_UNorm: case BufferFormat.R8G8B8A8_UNorm_SRgb: header.ImageType = TgaImageType.TrueColor; header.BPP = 32; header.Descriptor = TgaDescriptor.InvertY | TgaDescriptor.RGB888A8; conversionFlags |= TGAConversionFlags.Swizzle; break; case BufferFormat.B8G8R8A8_UNorm: case BufferFormat.B8G8R8A8_UNorm_SRgb: header.ImageType = TgaImageType.TrueColor; header.BPP = 32; header.Descriptor = TgaDescriptor.InvertY | TgaDescriptor.RGB888A8; break; case BufferFormat.B8G8R8X8_UNorm: case BufferFormat.B8G8R8X8_UNorm_SRgb: header.ImageType = TgaImageType.TrueColor; header.BPP = 24; header.Descriptor = TgaDescriptor.InvertY; conversionFlags |= TGAConversionFlags.RGB888; break; case BufferFormat.R8_UNorm: case BufferFormat.A8_UNorm: header.ImageType = TgaImageType.BlackAndWhite; header.BPP = 8; header.Descriptor = TgaDescriptor.InvertY; break; case BufferFormat.B5G5R5A1_UNorm: header.ImageType = TgaImageType.TrueColor; header.BPP = 16; header.Descriptor = TgaDescriptor.InvertY | TgaDescriptor.RGB555A1; break; default: throw new IOException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, settings.Format)); } // Persist to stream. return(header); }
/// <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 read in the TGA header from a stream. /// </summary> /// <param name="reader">The reader used to read the stream containing the data.</param> /// <param name="conversionFlags">Flags for conversion.</param> /// <returns>New image settings.</returns> private static IGorgonImageInfo ReadHeader(GorgonBinaryReader reader, out TGAConversionFlags conversionFlags) { conversionFlags = TGAConversionFlags.None; // Get the header for the file. TgaHeader header = reader.ReadValue <TgaHeader>(); if ((header.ColorMapType != 0) || (header.ColorMapLength != 0) || (header.Width <= 0) || (header.Height <= 0) || ((header.Descriptor & TgaDescriptor.Interleaved2Way) == TgaDescriptor.Interleaved2Way) || ((header.Descriptor & TgaDescriptor.Interleaved4Way) == TgaDescriptor.Interleaved4Way)) { throw new NotSupportedException(Resources.GORIMG_ERR_TGA_TYPE_NOT_SUPPORTED); } BufferFormat pixelFormat = BufferFormat.Unknown; switch (header.ImageType) { case TgaImageType.TrueColor: case TgaImageType.TrueColorRLE: switch (header.BPP) { case 16: pixelFormat = BufferFormat.B5G5R5A1_UNorm; break; case 24: case 32: pixelFormat = BufferFormat.R8G8B8A8_UNorm; if (header.BPP == 24) { conversionFlags |= TGAConversionFlags.Expand; } break; } if (header.ImageType == TgaImageType.TrueColorRLE) { conversionFlags |= TGAConversionFlags.RLE; } break; case TgaImageType.BlackAndWhite: case TgaImageType.BlackAndWhiteRLE: if (header.BPP == 8) { pixelFormat = BufferFormat.R8_UNorm; } else { throw new IOException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, header.ImageType)); } if (header.ImageType == TgaImageType.BlackAndWhiteRLE) { conversionFlags |= TGAConversionFlags.RLE; } break; default: throw new IOException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, header.ImageType)); } var settings = new GorgonImageInfo(ImageType.Image2D, pixelFormat) { MipCount = 1, ArrayCount = 1, Width = header.Width, Height = header.Height }; if ((header.Descriptor & TgaDescriptor.InvertX) == TgaDescriptor.InvertX) { conversionFlags |= TGAConversionFlags.InvertX; } if ((header.Descriptor & TgaDescriptor.InvertY) == TgaDescriptor.InvertY) { conversionFlags |= TGAConversionFlags.InvertY; } if (header.IDLength <= 0) { return(settings); } // Skip these bytes. for (int i = 0; i < header.IDLength; i++) { reader.ReadByte(); } return(settings); }