diff --git a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
new file mode 100644
index 000000000..a6e9871e0
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Configuration options for use during tga encoding.
+ ///
+ internal interface ITgaEncoderOptions
+ {
+ ///
+ /// Gets the number of bits per pixel.
+ ///
+ TgaBitsPerPixel? BitsPerPixel { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
new file mode 100644
index 000000000..a0666fa84
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Enumerates the available bits per pixel the tga encoder supports.
+ ///
+ public enum TgaBitsPerPixel : byte
+ {
+ ///
+ /// 8 bits per pixel. Each pixel consists of 1 byte.
+ ///
+ Pixel8 = 8,
+
+ ///
+ /// 16 bits per pixel. Each pixel consists of 2 bytes.
+ ///
+ Pixel16 = 16,
+
+ ///
+ /// 24 bits per pixel. Each pixel consists of 3 bytes.
+ ///
+ Pixel24 = 24,
+
+ ///
+ /// 32 bits per pixel. Each pixel consists of 4 bytes.
+ ///
+ Pixel32 = 32
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
index c7bf6cc93..18fbf4acd 100644
--- a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
+++ b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
@@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
///
public void Configure(Configuration configuration)
{
+ configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder());
configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector());
}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
new file mode 100644
index 000000000..f5b9fa752
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions
+ {
+ ///
+ /// Gets or sets the number of bits per pixel.
+ ///
+ public TgaBitsPerPixel? BitsPerPixel { get; set; }
+
+ ///
+ public void Encode(Image image, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
+ encoder.Encode(image, stream);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
new file mode 100644
index 000000000..349963f7f
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -0,0 +1,131 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Image encoder for writing an image to a stream as a truevision targa image.
+ ///
+ internal sealed class TgaEncoderCore
+ {
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The global configuration.
+ ///
+ private Configuration configuration;
+
+ ///
+ /// The color depth, in number of bits per pixel.
+ ///
+ private TgaBitsPerPixel? bitsPerPixel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The encoder options.
+ /// The memory manager.
+ public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator)
+ {
+ this.memoryAllocator = memoryAllocator;
+ this.bitsPerPixel = options.BitsPerPixel;
+ }
+
+ public void Encode(Image image, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ Guard.NotNull(image, nameof(image));
+ Guard.NotNull(stream, nameof(stream));
+
+ this.configuration = image.GetConfiguration();
+ ImageMetadata metadata = image.Metadata;
+ TgaMetadata tgaMetadata = metadata.GetFormatMetadata(TgaFormat.Instance);
+ this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel;
+
+ var fileHeader = new TgaFileHeader(
+ idLength: 0,
+ colorMapType: 0,
+ imageType: TgaImageType.TrueColor,
+ cMapStart: 0,
+ cMapLength: 0,
+ cMapDepth: 0,
+ xOffset: 0,
+ yOffset: 0,
+ width: (short)image.Width,
+ height: (short)image.Height,
+ pixelDepth: (byte)this.bitsPerPixel.Value,
+ imageDescriptor: 0);
+
+#if NETCOREAPP2_1
+ Span buffer = stackalloc byte[TgaFileHeader.Size];
+#else
+ var buffer = new byte[TgaFileHeader.Size];
+#endif
+ fileHeader.WriteTo(buffer);
+
+ stream.Write(buffer, 0, TgaFileHeader.Size);
+
+ this.WriteImage(stream, image.Frames.RootFrame);
+
+ stream.Flush();
+ }
+
+ ///
+ /// Writes the pixel data to the binary stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ ///
+ /// The containing pixel data.
+ ///
+ private void WriteImage(Stream stream, ImageFrame image)
+ where TPixel : struct, IPixel
+ {
+ Buffer2D pixels = image.PixelBuffer;
+ switch (this.bitsPerPixel)
+ {
+ case TgaBitsPerPixel.Pixel24:
+ this.Write24Bit(stream, pixels);
+ break;
+ }
+ }
+
+ private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
+
+ ///
+ /// Writes the 24bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write24Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToBgr24Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs
index 51390e1b7..72c275b28 100644
--- a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs
+++ b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Tga
@@ -135,5 +136,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
return MemoryMarshal.Cast(data)[0];
}
+
+ public void WriteTo(Span buffer)
+ {
+ ref TgaFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer));
+
+ dest = this;
+ }
}
}
diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
index 185eaedc9..4ce61d2e4 100644
--- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs
+++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
@@ -15,7 +15,21 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The metadata to create an instance from.
+ private TgaMetadata(TgaMetadata other)
+ {
+ this.BitsPerPixel = other.BitsPerPixel;
+ }
+
+ ///
+ /// Gets or sets the number of bits per pixel.
+ ///
+ public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24;
+
///
- public IDeepCloneable DeepClone() => throw new System.NotImplementedException();
+ public IDeepCloneable DeepClone() => new TgaMetadata(this);
}
}