diff --git a/.gitattributes b/.gitattributes
index 537d9cef1..435f0bb91 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -93,6 +93,7 @@
*.gif binary
*.jpg binary
*.png binary
+*.tga binary
*.ttf binary
*.snk binary
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 1dc081782..01c1f1039 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -24,7 +24,7 @@
-
+
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index 7460c9cac..c51a54a40 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -1,7 +1,8 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
@@ -14,6 +15,20 @@ namespace SixLabors.ImageSharp
///
internal static class ImageMaths
{
+ ///
+ /// Vector for converting pixel to gray value as specified by ITU-R Recommendation BT.709.
+ ///
+ private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f);
+
+ ///
+ /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709.
+ ///
+ /// The vector to get the luminance from.
+ /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels)
+ => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1));
+
///
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
///
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index ae20490c7..4dba7a7e8 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory;
@@ -150,6 +151,7 @@ namespace SixLabors.ImageSharp
///
///
/// .
+ /// .
///
/// The default configuration of .
internal static Configuration CreateDefaultInstance()
@@ -158,7 +160,8 @@ namespace SixLabors.ImageSharp
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
- new BmpConfigurationModule());
+ new BmpConfigurationModule(),
+ new TgaConfigurationModule());
}
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index b7733e026..03e082cce 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -387,9 +387,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
+ int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
- int idx = (y * width * 3) + (x * 3);
+ int idx = rowStartIdx + (x * 3);
if (undefinedPixels[x, y])
{
switch (this.options.RleSkippedPixelHandling)
@@ -418,9 +419,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
else
{
// Fast path without any undefined pixels.
+ int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
- int idx = (y * width * 3) + (x * 3);
+ int idx = rowStartIdx + (x * 3);
color.FromBgr24(Unsafe.As(ref bufferSpan[idx]));
pixelRow[x] = color;
}
diff --git a/src/ImageSharp/Formats/README.md b/src/ImageSharp/Formats/README.md
new file mode 100644
index 000000000..4a2b401b1
--- /dev/null
+++ b/src/ImageSharp/Formats/README.md
@@ -0,0 +1,6 @@
+# Encoder/Decoder for true vision targa files
+
+Useful links for reference:
+
+- [FileFront](https://www.fileformat.info/format/tga/egff.htm)
+- [Tga Specification](http://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf)
diff --git a/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs
new file mode 100644
index 000000000..e99e8b0c8
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// The options for decoding tga images. Currently empty, but this may change in the future.
+ ///
+ internal interface ITgaDecoderOptions
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
new file mode 100644
index 000000000..49983d236
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
@@ -0,0 +1,21 @@
+// 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; }
+
+ ///
+ /// Gets a value indicating whether run length compression should be used.
+ ///
+ TgaCompression Compression { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TGA_Specification.pdf b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf
new file mode 100644
index 000000000..09c9a4ddd
Binary files /dev/null and b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf differ
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/TgaCompression.cs b/src/ImageSharp/Formats/Tga/TgaCompression.cs
new file mode 100644
index 000000000..cc6e005ed
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaCompression.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Indicates if compression is used.
+ ///
+ public enum TgaCompression
+ {
+ ///
+ /// No compression is used.
+ ///
+ None,
+
+ ///
+ /// Run length encoding is used.
+ ///
+ RunLength,
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
new file mode 100644
index 000000000..18fbf4acd
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the tga format.
+ ///
+ public sealed class TgaConfigurationModule : IConfigurationModule
+ {
+ ///
+ 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/TgaConstants.cs b/src/ImageSharp/Formats/Tga/TgaConstants.cs
new file mode 100644
index 000000000..5aabe92a1
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaConstants.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal static class TgaConstants
+ {
+ ///
+ /// The list of mimetypes that equate to a targa file.
+ ///
+ public static readonly IEnumerable MimeTypes = new[] { "image/x-tga", "image/x-targa" };
+
+ ///
+ /// The list of file extensions that equate to a targa file.
+ ///
+ public static readonly IEnumerable FileExtensions = new[] { "tga", "vda", "icb", "vst" };
+
+ ///
+ /// The file header length of a tga image in bytes.
+ ///
+ public const int FileHeaderLength = 18;
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
new file mode 100644
index 000000000..b97388773
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Image decoder for Truevision TGA images.
+ ///
+ public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
+ {
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new TgaDecoderCore(configuration, this).Decode(stream);
+ }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream);
+
+ ///
+ public IImageInfo Identify(Configuration configuration, Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new TgaDecoderCore(configuration, this).Identify(stream);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
new file mode 100644
index 000000000..d861450e0
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -0,0 +1,588 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal sealed class TgaDecoderCore
+ {
+ ///
+ /// The metadata.
+ ///
+ private ImageMetadata metadata;
+
+ ///
+ /// The tga specific metadata.
+ ///
+ private TgaMetadata tgaMetadata;
+
+ ///
+ /// The file header containing general information about the image.
+ ///
+ private TgaFileHeader fileHeader;
+
+ ///
+ /// The global configuration.
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The stream to decode from.
+ ///
+ private Stream currentStream;
+
+ ///
+ /// The bitmap decoder options.
+ ///
+ private readonly ITgaDecoderOptions options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The options.
+ public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
+ {
+ this.configuration = configuration;
+ this.memoryAllocator = configuration.MemoryAllocator;
+ this.options = options;
+ }
+
+ ///
+ /// Decodes the image from the specified stream.
+ ///
+ /// The pixel format.
+ /// The stream, where the image should be decoded from. Cannot be null.
+ ///
+ /// is null.
+ ///
+ /// The decoded image.
+ public Image Decode(Stream stream)
+ where TPixel : struct, IPixel
+ {
+ try
+ {
+ bool inverted = this.ReadFileHeader(stream);
+ this.currentStream.Skip(this.fileHeader.IdLength);
+
+ // Parse the color map, if present.
+ if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1)
+ {
+ TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found");
+ }
+
+ if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0)
+ {
+ throw new UnknownImageFormatException("Width or height cannot be 0");
+ }
+
+ var image = new Image(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+
+ if (this.fileHeader.ColorMapType is 1)
+ {
+ if (this.fileHeader.CMapLength <= 0)
+ {
+ TgaThrowHelper.ThrowImageFormatException("Missing tga color map length");
+ }
+
+ if (this.fileHeader.CMapDepth <= 0)
+ {
+ TgaThrowHelper.ThrowImageFormatException("Missing tga color map depth");
+ }
+
+ int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
+ int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes;
+ using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean))
+ {
+ this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes);
+
+ if (this.fileHeader.ImageType is TgaImageType.RleColorMapped)
+ {
+ this.ReadPalettedRle(
+ this.fileHeader.Width,
+ this.fileHeader.Height,
+ pixels,
+ palette.Array,
+ colorMapPixelSizeInBytes,
+ inverted);
+ }
+ else
+ {
+ this.ReadPaletted(
+ this.fileHeader.Width,
+ this.fileHeader.Height,
+ pixels,
+ palette.Array,
+ colorMapPixelSizeInBytes,
+ inverted);
+ }
+ }
+
+ return image;
+ }
+
+ // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes.
+ if (this.fileHeader.CMapLength > 0)
+ {
+ int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
+ this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes);
+ }
+
+ switch (this.fileHeader.PixelDepth)
+ {
+ case 8:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, inverted);
+ }
+ else
+ {
+ this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ case 15:
+ case 16:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted);
+ }
+ else
+ {
+ this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ case 24:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted);
+ }
+ else
+ {
+ this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ case 32:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted);
+ }
+ else
+ {
+ this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ default:
+ TgaThrowHelper.ThrowNotSupportedException("Does not support this kind of tga files.");
+ break;
+ }
+
+ return image;
+ }
+ catch (IndexOutOfRangeException e)
+ {
+ throw new ImageFormatException("TGA image does not have a valid format.", e);
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image with a palette.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// The color palette.
+ /// Color map size of one entry in bytes.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean))
+ {
+ TPixel color = default;
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ switch (colorMapPixelSizeInBytes)
+ {
+ case 2:
+ for (int x = 0; x < width; x++)
+ {
+ int colorIndex = rowSpan[x];
+
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ Bgra5551 bgra = Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]);
+ bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
+ color.FromBgra5551(bgra);
+ pixelRow[x] = color;
+ }
+
+ break;
+
+ case 3:
+ for (int x = 0; x < width; x++)
+ {
+ int colorIndex = rowSpan[x];
+ color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
+ pixelRow[x] = color;
+ }
+
+ break;
+
+ case 4:
+ for (int x = 0; x < width; x++)
+ {
+ int colorIndex = rowSpan[x];
+ color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
+ pixelRow[x] = color;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads a run length encoded TGA image with a palette.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// The color palette.
+ /// Color map size of one entry in bytes.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ int bytesPerPixel = 1;
+ using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
+ {
+ TPixel color = default;
+ Span bufferSpan = buffer.GetSpan();
+ this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1);
+
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ int rowStartIdx = y * width * bytesPerPixel;
+ for (int x = 0; x < width; x++)
+ {
+ int idx = rowStartIdx + x;
+ switch (colorMapPixelSizeInBytes)
+ {
+ case 1:
+ color.FromGray8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
+ break;
+ case 2:
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ Bgra5551 bgra = Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]);
+ bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
+ color.FromBgra5551(bgra);
+ break;
+ case 3:
+ color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
+ break;
+ case 4:
+ color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
+ break;
+ }
+
+ pixelRow[x] = color;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed monochrome TGA image.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadMonoChrome(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromGray8Bytes(
+ this.configuration,
+ row.GetSpan(),
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image where each pixels has 16 bit.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadBgra16(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ Span rowSpan = row.GetSpan();
+
+ // We need to set each alpha component value to fully opaque.
+ for (int x = 1; x < rowSpan.Length; x += 2)
+ {
+ rowSpan[x] = (byte)(rowSpan[x] | (1 << 7));
+ }
+
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromBgra5551Bytes(
+ this.configuration,
+ rowSpan,
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image where each pixels has 24 bit.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadBgr24(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromBgr24Bytes(
+ this.configuration,
+ row.GetSpan(),
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image where each pixels has 32 bit.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadBgra32(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromBgra32Bytes(
+ this.configuration,
+ row.GetSpan(),
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a run length encoded TGA image.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// The bytes per pixel.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default;
+ using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
+ {
+ Span bufferSpan = buffer.GetSpan();
+ this.UncompressRle(width, height, bufferSpan, bytesPerPixel);
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ int rowStartIdx = y * width * bytesPerPixel;
+ for (int x = 0; x < width; x++)
+ {
+ int idx = rowStartIdx + (x * bytesPerPixel);
+ switch (bytesPerPixel)
+ {
+ case 1:
+ color.FromGray8(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ case 2:
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128);
+ color.FromBgra5551(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ case 3:
+ color.FromBgr24(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ case 4:
+ color.FromBgra32(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ }
+
+ pixelRow[x] = color;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads the raw image information from the specified stream.
+ ///
+ /// The containing image data.
+ public IImageInfo Identify(Stream stream)
+ {
+ this.ReadFileHeader(stream);
+ return new ImageInfo(
+ new PixelTypeInfo(this.fileHeader.PixelDepth),
+ this.fileHeader.Width,
+ this.fileHeader.Height,
+ this.metadata);
+ }
+
+ ///
+ /// Produce uncompressed tga data from a run length encoded stream.
+ ///
+ /// The width of the image.
+ /// The height of the image.
+ /// Buffer for uncompressed data.
+ /// The bytes used per pixel.
+ private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel)
+ {
+ int uncompressedPixels = 0;
+ var pixel = new byte[bytesPerPixel];
+ int totalPixels = width * height;
+ while (uncompressedPixels < totalPixels)
+ {
+ byte runLengthByte = (byte)this.currentStream.ReadByte();
+
+ // The high bit of a run length packet is set to 1.
+ int highBit = runLengthByte >> 7;
+ if (highBit == 1)
+ {
+ int runLength = runLengthByte & 127;
+ this.currentStream.Read(pixel, 0, bytesPerPixel);
+ int bufferIdx = uncompressedPixels * bytesPerPixel;
+ for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
+ {
+ pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
+ bufferIdx += bytesPerPixel;
+ }
+ }
+ else
+ {
+ // Non-run-length encoded packet.
+ int runLength = runLengthByte;
+ int bufferIdx = uncompressedPixels * bytesPerPixel;
+ for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
+ {
+ this.currentStream.Read(pixel, 0, bytesPerPixel);
+ pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
+ bufferIdx += bytesPerPixel;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Returns the y- value based on the given height.
+ ///
+ /// The y- value representing the current row.
+ /// The height of the bitmap.
+ /// Whether the bitmap is inverted.
+ /// The representing the inverted value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y;
+
+ ///
+ /// Reads the tga file header from the stream.
+ ///
+ /// The containing image data.
+ /// true, if the image origin is top left.
+ private bool ReadFileHeader(Stream stream)
+ {
+ this.currentStream = stream;
+
+#if NETCOREAPP2_1
+ Span buffer = stackalloc byte[TgaFileHeader.Size];
+#else
+ var buffer = new byte[TgaFileHeader.Size];
+#endif
+ this.currentStream.Read(buffer, 0, TgaFileHeader.Size);
+ this.fileHeader = TgaFileHeader.Parse(buffer);
+ this.metadata = new ImageMetadata();
+ this.tgaMetadata = this.metadata.GetFormatMetadata(TgaFormat.Instance);
+ this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
+
+ // Bit at position 5 of the descriptor indicates, that the origin is top left instead of bottom right.
+ if ((this.fileHeader.ImageDescriptor & (1 << 5)) != 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
new file mode 100644
index 000000000..2fcbb822f
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
@@ -0,0 +1,34 @@
+// 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
+{
+ ///
+ /// Image encoder for writing an image to a stream as a targa truevision image.
+ ///
+ public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions
+ {
+ ///
+ /// Gets or sets the number of bits per pixel.
+ ///
+ public TgaBitsPerPixel? BitsPerPixel { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether no compression or run length compression should be used.
+ ///
+ public TgaCompression Compression { get; set; } = TgaCompression.RunLength;
+
+ ///
+ 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..28b87e985
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -0,0 +1,348 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.IO;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+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;
+
+ ///
+ /// Reusable buffer for writing data.
+ ///
+ private readonly byte[] buffer = new byte[2];
+
+ ///
+ /// The color depth, in number of bits per pixel.
+ ///
+ private TgaBitsPerPixel? bitsPerPixel;
+
+ ///
+ /// Indicates if run length compression should be used.
+ ///
+ private readonly TgaCompression compression;
+
+ ///
+ /// 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;
+ this.compression = options.Compression;
+ }
+
+ ///
+ /// Encodes the image to the specified stream from the .
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// The to encode the image data to.
+ 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;
+
+ TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor;
+ if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8)
+ {
+ imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite;
+ }
+
+ // If compression is used, set bit 5 of the image descriptor to indicate an left top origin.
+ byte imageDescriptor = (byte)(this.compression is TgaCompression.RunLength ? 32 : 0);
+
+ var fileHeader = new TgaFileHeader(
+ idLength: 0,
+ colorMapType: 0,
+ imageType: imageType,
+ cMapStart: 0,
+ cMapLength: 0,
+ cMapDepth: 0,
+ xOffset: 0,
+ yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left.
+ width: (short)image.Width,
+ height: (short)image.Height,
+ pixelDepth: (byte)this.bitsPerPixel.Value,
+ imageDescriptor: imageDescriptor);
+
+#if NETCOREAPP2_1
+ Span buffer = stackalloc byte[TgaFileHeader.Size];
+#else
+ byte[] buffer = new byte[TgaFileHeader.Size];
+#endif
+ fileHeader.WriteTo(buffer);
+
+ stream.Write(buffer, 0, TgaFileHeader.Size);
+
+ if (this.compression is TgaCompression.RunLength)
+ {
+ this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame);
+ }
+ else
+ {
+ 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.Pixel8:
+ this.Write8Bit(stream, pixels);
+ break;
+
+ case TgaBitsPerPixel.Pixel16:
+ this.Write16Bit(stream, pixels);
+ break;
+
+ case TgaBitsPerPixel.Pixel24:
+ this.Write24Bit(stream, pixels);
+ break;
+
+ case TgaBitsPerPixel.Pixel32:
+ this.Write32Bit(stream, pixels);
+ break;
+ }
+ }
+
+ ///
+ /// Writes a run length encoded tga image to the stream.
+ ///
+ /// The pixel type.
+ /// The stream to write the image to.
+ /// The image to encode.
+ private void WriteRunLengthEndcodedImage(Stream stream, ImageFrame image)
+ where TPixel : struct, IPixel
+ {
+ Rgba32 color = default;
+ Buffer2D pixels = image.PixelBuffer;
+ Span pixelSpan = pixels.GetSpan();
+ int totalPixels = image.Width * image.Height;
+ int encodedPixels = 0;
+ while (encodedPixels < totalPixels)
+ {
+ TPixel currentPixel = pixelSpan[encodedPixels];
+ currentPixel.ToRgba32(ref color);
+ byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels));
+
+ // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
+ stream.WriteByte((byte)(equalPixelCount | 128));
+ switch (this.bitsPerPixel)
+ {
+ case TgaBitsPerPixel.Pixel8:
+ int luminance = GetLuminance(currentPixel);
+ stream.WriteByte((byte)luminance);
+ break;
+
+ case TgaBitsPerPixel.Pixel16:
+ var bgra5551 = new Bgra5551(color.ToVector4());
+ BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue);
+ stream.WriteByte(this.buffer[0]);
+ stream.WriteByte(this.buffer[1]);
+
+ break;
+
+ case TgaBitsPerPixel.Pixel24:
+ stream.WriteByte(color.B);
+ stream.WriteByte(color.G);
+ stream.WriteByte(color.R);
+ break;
+
+ case TgaBitsPerPixel.Pixel32:
+ stream.WriteByte(color.B);
+ stream.WriteByte(color.G);
+ stream.WriteByte(color.R);
+ stream.WriteByte(color.A);
+ break;
+ }
+
+ encodedPixels += equalPixelCount + 1;
+ }
+ }
+
+ ///
+ /// Finds consecutive pixels, which have the same value starting from the pixel span offset 0.
+ ///
+ /// The pixel type.
+ /// The pixel span to search in.
+ /// The number of equal pixels.
+ private byte FindEqualPixels(Span pixelSpan)
+ where TPixel : struct, IPixel
+ {
+ int idx = 0;
+ byte equalPixelCount = 0;
+ while (equalPixelCount < 127 && idx < pixelSpan.Length - 1)
+ {
+ TPixel currentPixel = pixelSpan[idx];
+ TPixel nextPixel = pixelSpan[idx + 1];
+ if (currentPixel.Equals(nextPixel))
+ {
+ equalPixelCount++;
+ }
+ else
+ {
+ return equalPixelCount;
+ }
+
+ idx++;
+ }
+
+ return equalPixelCount;
+ }
+
+ private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
+
+ ///
+ /// Writes the 8bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write8Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToGray8Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+
+ ///
+ /// Writes the 16bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write16Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToBgra5551Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+
+ ///
+ /// 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());
+ }
+ }
+ }
+
+ ///
+ /// Writes the 32bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write32Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToBgra32Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+
+ ///
+ /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
+ ///
+ /// The pixel to get the luminance from.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int GetLuminance(TPixel sourcePixel)
+ where TPixel : struct, IPixel
+ {
+ var vector = sourcePixel.ToVector4();
+ return ImageMaths.GetBT709Luminance(ref vector, 256);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs
new file mode 100644
index 000000000..e2bbb6fbd
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs
@@ -0,0 +1,147 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// This block of bytes tells the application detailed information about the targa image.
+ ///
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal readonly struct TgaFileHeader
+ {
+ ///
+ /// Defines the size of the data structure in the targa file.
+ ///
+ public const int Size = TgaConstants.FileHeaderLength;
+
+ public TgaFileHeader(
+ byte idLength,
+ byte colorMapType,
+ TgaImageType imageType,
+ short cMapStart,
+ short cMapLength,
+ byte cMapDepth,
+ short xOffset,
+ short yOffset,
+ short width,
+ short height,
+ byte pixelDepth,
+ byte imageDescriptor)
+ {
+ this.IdLength = idLength;
+ this.ColorMapType = colorMapType;
+ this.ImageType = imageType;
+ this.CMapStart = cMapStart;
+ this.CMapLength = cMapLength;
+ this.CMapDepth = cMapDepth;
+ this.XOffset = xOffset;
+ this.YOffset = yOffset;
+ this.Width = width;
+ this.Height = height;
+ this.PixelDepth = pixelDepth;
+ this.ImageDescriptor = imageDescriptor;
+ }
+
+ ///
+ /// Gets the id length.
+ /// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number
+ /// of characters is 255. A value of zero indicates that no Image ID field is included with the image.
+ ///
+ public byte IdLength { get; }
+
+ ///
+ /// Gets the color map type.
+ /// This field indicates the type of color map (if any) included with the image. There are currently 2 defined
+ /// values for this field:
+ /// 0 - indicates that no color-map data is included with this image.
+ /// 1 - indicates that a color-map is included with this image.
+ ///
+ public byte ColorMapType { get; }
+
+ ///
+ /// Gets the image type.
+ /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various
+ /// pixel depths.
+ ///
+ public TgaImageType ImageType { get; }
+
+ ///
+ /// Gets the start of the color map.
+ /// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field
+ /// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero.
+ ///
+ public short CMapStart { get; }
+
+ ///
+ /// Gets the total number of color map entries included.
+ ///
+ public short CMapLength { get; }
+
+ ///
+ /// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used.
+ ///
+ public byte CMapDepth { get; }
+
+ ///
+ /// Gets the XOffset.
+ /// These bytes specify the absolute horizontal coordinate for the lower left
+ /// corner of the image as it is positioned on a display device having an
+ /// origin at the lower left of the screen.
+ ///
+ public short XOffset { get; }
+
+ ///
+ /// Gets the YOffset.
+ /// These bytes specify the absolute vertical coordinate for the lower left
+ /// corner of the image as it is positioned on a display device having an
+ /// origin at the lower left of the screen.
+ ///
+ public short YOffset { get; }
+
+ ///
+ /// Gets the width of the image in pixels.
+ ///
+ public short Width { get; }
+
+ ///
+ /// Gets the height of the image in pixels.
+ ///
+ public short Height { get; }
+
+ ///
+ /// Gets the number of bits per pixel. This number includes
+ /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and
+ /// 32 but other pixel depths could be used.
+ ///
+ public byte PixelDepth { get; }
+
+ ///
+ /// Gets the ImageDescriptor.
+ /// ImageDescriptor contains two pieces of information.
+ /// Bits 0 through 3 contain the number of attribute bits per pixel.
+ /// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel,
+ /// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image.
+ /// This position may be any of the four corners of the display screen.
+ /// When both of these bits are set to zero, the image origin is the lower-left corner of the screen.
+ /// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0.
+ ///
+ public byte ImageDescriptor { get; }
+
+ public static TgaFileHeader Parse(Span data)
+ {
+ 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/TgaFormat.cs b/src/ImageSharp/Formats/Tga/TgaFormat.cs
new file mode 100644
index 000000000..badb1d77a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaFormat.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the tga format.
+ ///
+ public sealed class TgaFormat : IImageFormat
+ {
+ ///
+ /// Gets the current instance.
+ ///
+ public static TgaFormat Instance { get; } = new TgaFormat();
+
+ ///
+ public string Name => "TGA";
+
+ ///
+ public string DefaultMimeType => "image/tga";
+
+ ///
+ public IEnumerable MimeTypes => TgaConstants.MimeTypes;
+
+ ///
+ public IEnumerable FileExtensions => TgaConstants.FileExtensions;
+
+ ///
+ public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata();
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
new file mode 100644
index 000000000..bd9cfa900
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Detects tga file headers.
+ ///
+ public sealed class TgaImageFormatDetector : IImageFormatDetector
+ {
+ ///
+ public int HeaderSize => TgaConstants.FileHeaderLength;
+
+ ///
+ public IImageFormat DetectFormat(ReadOnlySpan header)
+ {
+ return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null;
+ }
+
+ private bool IsSupportedFileFormat(ReadOnlySpan header)
+ {
+ if (header.Length >= this.HeaderSize)
+ {
+ // There is no magick bytes in a tga file, so at least the image type
+ // and the colormap type in the header will be checked for a valid value.
+ if (header[1] != 0 && header[1] != 1)
+ {
+ return false;
+ }
+
+ var imageType = (TgaImageType)header[2];
+ return imageType.IsValid();
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageType.cs b/src/ImageSharp/Formats/Tga/TgaImageType.cs
new file mode 100644
index 000000000..491fd3ea7
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageType.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.
+ ImageSharp.Formats.Tga
+{
+ ///
+ /// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color,
+ /// True-Color and Direct-Color images of various pixel depths.
+ ///
+ public enum TgaImageType : byte
+ {
+ ///
+ /// No image data included.
+ ///
+ NoImageData = 0,
+
+ ///
+ /// Uncompressed, color mapped image.
+ ///
+ ColorMapped = 1,
+
+ ///
+ /// Uncompressed true color image.
+ ///
+ TrueColor = 2,
+
+ ///
+ /// Uncompressed Black and white (grayscale) image.
+ ///
+ BlackAndWhite = 3,
+
+ ///
+ /// Run length encoded, color mapped image.
+ ///
+ RleColorMapped = 9,
+
+ ///
+ /// Run length encoded, true color image.
+ ///
+ RleTrueColor = 10,
+
+ ///
+ /// Run length encoded, black and white (grayscale) image.
+ ///
+ RleBlackAndWhite = 11,
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs
new file mode 100644
index 000000000..6a30cdddd
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Extension methods for TgaImageType enum.
+ ///
+ public static class TgaImageTypeExtensions
+ {
+ ///
+ /// Checks if this tga image type is run length encoded.
+ ///
+ /// The tga image type.
+ /// True, if this image type is run length encoded, otherwise false.
+ public static bool IsRunLengthEncoded(this TgaImageType imageType)
+ {
+ if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks, if the image type has valid value.
+ ///
+ /// The image type.
+ /// true, if its a valid tga image type.
+ public static bool IsValid(this TgaImageType imageType)
+ {
+ switch (imageType)
+ {
+ case TgaImageType.NoImageData:
+ case TgaImageType.ColorMapped:
+ case TgaImageType.TrueColor:
+ case TgaImageType.BlackAndWhite:
+ case TgaImageType.RleColorMapped:
+ case TgaImageType.RleTrueColor:
+ case TgaImageType.RleBlackAndWhite:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
new file mode 100644
index 000000000..4ce61d2e4
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Provides TGA specific metadata information for the image.
+ ///
+ public class TgaMetadata : IDeepCloneable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TgaMetadata()
+ {
+ }
+
+ ///
+ /// 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() => new TgaMetadata(this);
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
new file mode 100644
index 000000000..845d00922
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal static class TgaThrowHelper
+ {
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowImageFormatException(string errorMessage)
+ {
+ throw new ImageFormatException(errorMessage);
+ }
+
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowNotSupportedException(string errorMessage)
+ {
+ throw new NotSupportedException(errorMessage);
+ }
+ }
+}
diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
index 860301232..459924c31 100644
--- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
+++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
@@ -18,7 +18,7 @@
///
/// Converts all pixels in 'source` span of into a span of -s.
///
- /// A to configure internal operations
+ /// A to configure internal operations.
/// The source of data.
/// The to the destination pixels.
internal virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destPixels)
@@ -41,7 +41,7 @@
/// A helper for that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with layout.
///
- /// A to configure internal operations
+ /// A to configure internal operations.
/// The to the source bytes.
/// The to the destination pixels.
/// The number of pixels to convert.
diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
index f2f11cbfe..622c133ae 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
@@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
- int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
+ int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++;
}
}
@@ -366,7 +366,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
- int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
+ int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)--;
}
}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
index 6e4c16de7..284b9de1f 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
@@ -143,16 +143,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public static int GetLuminance(TPixel sourcePixel, int luminanceLevels)
{
var vector = sourcePixel.ToVector4();
- return GetLuminance(ref vector, luminanceLevels);
+ return ImageMaths.GetBT709Luminance(ref vector, luminanceLevels);
}
-
- ///
- /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
- ///
- /// The vector to get the luminance from
- /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)
- [MethodImpl(InliningOptions.ShortMethod)]
- public static int GetLuminance(ref Vector4 vector, int luminanceLevels)
- => (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1));
}
}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
new file mode 100644
index 000000000..e3c721610
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using BenchmarkDotNet.Attributes;
+
+using ImageMagick;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs
+{
+ [Config(typeof(Config.ShortClr))]
+ public class DecodeTga : BenchmarkBase
+ {
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Tga.Bit24)]
+ public string TestImage { get; set; }
+
+ [Benchmark(Baseline = true, Description = "ImageMagick Tga")]
+ public Size TgaImageMagick()
+ {
+ using (var magickImage = new MagickImage(this.TestImageFullPath))
+ {
+ return new Size(magickImage.Width, magickImage.Height);
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp Tga")]
+ public Size TgaCore()
+ {
+ using (var image = Image.Load(this.TestImageFullPath))
+ {
+ return new Size(image.Width, image.Height);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
new file mode 100644
index 000000000..ddcbec218
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using BenchmarkDotNet.Attributes;
+
+using ImageMagick;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs
+{
+ [Config(typeof(Config.ShortClr))]
+ public class EncodeTga : BenchmarkBase
+ {
+ private MagickImage tgaMagick;
+ private Image tgaCore;
+
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Tga.Bit24)]
+ public string TestImage { get; set; }
+
+ [GlobalSetup]
+ public void ReadImages()
+ {
+ if (this.tgaCore == null)
+ {
+ this.tgaCore = Image.Load(TestImageFullPath);
+ this.tgaMagick = new MagickImage(this.TestImageFullPath);
+ }
+ }
+
+ [Benchmark(Baseline = true, Description = "Magick Tga")]
+ public void BmpSystemDrawing()
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp Tga")]
+ public void BmpCore()
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ this.tgaCore.SaveAsBmp(memoryStream);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
index 14ad5635c..a57d388a9 100644
--- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
+++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
@@ -16,6 +16,7 @@
+
diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs
index a0e552aeb..6b35bbb97 100644
--- a/tests/ImageSharp.Tests/ConfigurationTests.cs
+++ b/tests/ImageSharp.Tests/ConfigurationTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests
public Configuration ConfigurationEmpty { get; }
public Configuration DefaultConfiguration { get; }
+ private readonly int expectedDefaultConfigurationCount = 5;
+
public ConfigurationTests()
{
// the shallow copy of configuration should behave exactly like the default configuration,
@@ -108,14 +110,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConfigurationCannotAddDuplicates()
{
- const int count = 4;
Configuration config = this.DefaultConfiguration;
- Assert.Equal(count, config.ImageFormats.Count());
+ Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance);
- Assert.Equal(count, config.ImageFormats.Count());
+ Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]
@@ -123,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests
{
Configuration config = Configuration.CreateDefaultInstance();
- Assert.Equal(4, config.ImageFormats.Count());
+ Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs
index 25cf29406..4c3fe3149 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs
@@ -1,4 +1,11 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
+using System.IO;
+using System.Linq;
+
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using Xunit;
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 2a76310fc..e064c0fb0 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image image = provider.GetImage(new PngDecoder()))
{
image.DebugSave(provider);
- // TODO: compare to expected output
+ image.CompareToOriginal(provider, ImageComparer.Exact);
}
});
Assert.Null(ex);
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
new file mode 100644
index 000000000..03ad10de4
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
@@ -0,0 +1,197 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+// ReSharper disable InconsistentNaming
+
+using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.PixelFormats;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ using static TestImages.Tga;
+
+ public class TgaDecoderTests
+ {
+ [Theory]
+ [WithFile(Grey, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_MonoChrome(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit15, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_15Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit15Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16PalRle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24TopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_32Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(GreyRle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_MonoChrome(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_32Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16Pal, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithPalette_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24Pal, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithPalette_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
new file mode 100644
index 000000000..e946729a1
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
@@ -0,0 +1,148 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+// ReSharper disable InconsistentNaming
+
+using System.IO;
+
+using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.PixelFormats;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ using static TestImages.Tga;
+
+ public class TgaEncoderTests
+ {
+ public static readonly TheoryData BitsPerPixel =
+ new TheoryData
+ {
+ TgaBitsPerPixel.Pixel24,
+ TgaBitsPerPixel.Pixel32
+ };
+
+ public static readonly TheoryData TgaBitsPerPixelFiles =
+ new TheoryData
+ {
+ { Grey, TgaBitsPerPixel.Pixel8 },
+ { Bit32, TgaBitsPerPixel.Pixel32 },
+ { Bit24, TgaBitsPerPixel.Pixel24 },
+ { Bit16, TgaBitsPerPixel.Pixel16 },
+ };
+
+ [Theory]
+ [MemberData(nameof(TgaBitsPerPixelFiles))]
+ public void Encode_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
+ {
+ var options = new TgaEncoder();
+
+ TestFile testFile = TestFile.Create(imagePath);
+ using (Image input = testFile.CreateRgba32Image())
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+ memStream.Position = 0;
+ using (Image output = Image.Load(memStream))
+ {
+ TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance);
+ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(TgaBitsPerPixelFiles))]
+ public void Encode_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
+ {
+ var options = new TgaEncoder()
+ {
+ Compression = TgaCompression.RunLength
+ };
+
+ TestFile testFile = TestFile.Create(imagePath);
+ using (Image input = testFile.CreateRgba32Image())
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+ memStream.Position = 0;
+ using (Image output = Image.Load(memStream))
+ {
+ TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance);
+ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit8_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8)
+ // using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit16_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit24_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit8_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8)
+ // using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit16_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit24_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
+
+ private static void TestTgaEncoderCore(
+ TestImageProvider provider,
+ TgaBitsPerPixel bitsPerPixel,
+ TgaCompression compression = TgaCompression.None,
+ bool useExactComparer = true,
+ float compareTolerance = 0.01f)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression};
+
+ using (var memStream = new MemoryStream())
+ {
+ image.Save(memStream, encoder);
+ memStream.Position = 0;
+ using (var encodedImage = (Image)Image.Load(memStream))
+ {
+ TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
new file mode 100644
index 000000000..c227b7957
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using SixLabors.ImageSharp.Formats;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ public class TgaFileHeaderTests
+ {
+ private static readonly byte[] Data = {
+ 0,
+ 0,
+ 15 // invalid tga image type
+ };
+
+ private MemoryStream Stream { get; } = new MemoryStream(Data);
+
+ [Fact]
+ public void ImageLoad_WithInvalidImageType_Throws_UnknownImageFormatException()
+ {
+ Assert.Throws(() =>
+ {
+ using (Image.Load(Configuration.Default, this.Stream, out IImageFormat _))
+ {
+ }
+ });
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs
new file mode 100644
index 000000000..a2f2e86d7
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+
+using ImageMagick;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ public static class TgaTestUtils
+ {
+ public static void CompareWithReferenceDecoder(TestImageProvider provider,
+ Image image,
+ bool useExactComparer = true,
+ float compareTolerance = 0.01f)
+ where TPixel : struct, IPixel
+ {
+ string path = TestImageProvider.GetFilePathOrNull(provider);
+ if (path == null)
+ {
+ throw new InvalidOperationException("CompareToOriginal() works only with file providers!");
+ }
+
+ TestFile testFile = TestFile.Create(path);
+ Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath));
+ if (useExactComparer)
+ {
+ ImageComparer.Exact.VerifySimilarity(magickImage, image);
+ }
+ else
+ {
+ ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image);
+ }
+ }
+
+ public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo)
+ where TPixel : struct, IPixel
+ {
+ using (var magickImage = new MagickImage(fileInfo))
+ {
+ var result = new Image(configuration, magickImage.Width, magickImage.Height);
+ Span resultPixels = result.GetPixelSpan();
+
+ using (IPixelCollection pixels = magickImage.GetPixelsUnsafe())
+ {
+ byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
+
+ PixelOperations.Instance.FromRgba32Bytes(
+ configuration,
+ data,
+ resultPixels,
+ resultPixels.Length);
+ }
+
+ return result;
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
index 018fabd98..817672f34 100644
--- a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
+++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
@@ -2,6 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+
using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers
@@ -131,6 +136,23 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Assert.Equal(expected, actual);
}
+ [Theory]
+ [InlineData(0.2f, 0.7f, 0.1f, 256, 140)]
+ [InlineData(0.5f, 0.5f, 0.5f, 256, 128)]
+ [InlineData(0.5f, 0.5f, 0.5f, 65536, 32768)]
+ [InlineData(0.2f, 0.7f, 0.1f, 65536, 36069)]
+ public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminanceLevels, int expected)
+ {
+ // arrange
+ var vector = new Vector4(x, y, z, 0.0f);
+
+ // act
+ int actual = ImageMaths.GetBT709Luminance(ref vector, luminanceLevels);
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
// TODO: We need to test all ImageMaths methods!
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 146f2efcd..177749869 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -365,5 +365,24 @@ namespace SixLabors.ImageSharp.Tests
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };
}
+
+ public static class Tga
+ {
+ public const string Bit15 = "Tga/rgb15.tga";
+ public const string Bit15Rle = "Tga/rgb15rle.tga";
+ public const string Bit16 = "Tga/targa_16bit.tga";
+ public const string Bit16PalRle = "Tga/ccm8.tga";
+ public const string Bit24 = "Tga/targa_24bit.tga";
+ public const string Bit24TopLeft = "Tga/targa_24bit_pal_origin_topleft.tga";
+ public const string Bit24RleTopLeft = "Tga/targa_24bit_rle_origin_topleft.tga";
+ public const string Bit32 = "Tga/targa_32bit.tga";
+ public const string Grey = "Tga/targa_8bit.tga";
+ public const string GreyRle = "Tga/targa_8bit_rle.tga";
+ public const string Bit16Rle = "Tga/targa_16bit_rle.tga";
+ public const string Bit24Rle = "Tga/targa_24bit_rle.tga";
+ public const string Bit32Rle = "Tga/targa_32bit_rle.tga";
+ public const string Bit16Pal = "Tga/targa_16bit_pal.tga";
+ public const string Bit24Pal = "Tga/targa_24bit_pal.tga";
+ }
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
index 7d0684722..e09b27c71 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
namespace SixLabors.ImageSharp.Tests
@@ -53,7 +54,8 @@ namespace SixLabors.ImageSharp.Tests
{
var cfg = new Configuration(
new JpegConfigurationModule(),
- new GifConfigurationModule()
+ new GifConfigurationModule(),
+ new TgaConfigurationModule()
);
// Magick codecs should work on all platforms
@@ -75,4 +77,4 @@ namespace SixLabors.ImageSharp.Tests
return cfg;
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Images/External b/tests/Images/External
index f0c403366..ca4cf8318 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit f0c4033667bd23ad9dde82ccb625c232d402ee05
+Subproject commit ca4cf8318fe4d09f0fc825686dcd477ebfb5e3e5
diff --git a/tests/Images/Input/Tga/ccm8.tga b/tests/Images/Input/Tga/ccm8.tga
new file mode 100644
index 000000000..7a21efef7
Binary files /dev/null and b/tests/Images/Input/Tga/ccm8.tga differ
diff --git a/tests/Images/Input/Tga/rgb15.tga b/tests/Images/Input/Tga/rgb15.tga
new file mode 100644
index 000000000..9506b418a
Binary files /dev/null and b/tests/Images/Input/Tga/rgb15.tga differ
diff --git a/tests/Images/Input/Tga/rgb15rle.tga b/tests/Images/Input/Tga/rgb15rle.tga
new file mode 100644
index 000000000..5d4672c9c
Binary files /dev/null and b/tests/Images/Input/Tga/rgb15rle.tga differ
diff --git a/tests/Images/Input/Tga/targa.png b/tests/Images/Input/Tga/targa.png
new file mode 100644
index 000000000..c18cf7e23
Binary files /dev/null and b/tests/Images/Input/Tga/targa.png differ
diff --git a/tests/Images/Input/Tga/targa_16bit.tga b/tests/Images/Input/Tga/targa_16bit.tga
new file mode 100644
index 000000000..eeaf33d59
Binary files /dev/null and b/tests/Images/Input/Tga/targa_16bit.tga differ
diff --git a/tests/Images/Input/Tga/targa_16bit_pal.tga b/tests/Images/Input/Tga/targa_16bit_pal.tga
new file mode 100644
index 000000000..8074ad78f
Binary files /dev/null and b/tests/Images/Input/Tga/targa_16bit_pal.tga differ
diff --git a/tests/Images/Input/Tga/targa_16bit_rle.tga b/tests/Images/Input/Tga/targa_16bit_rle.tga
new file mode 100644
index 000000000..968f4de32
Binary files /dev/null and b/tests/Images/Input/Tga/targa_16bit_rle.tga differ
diff --git a/tests/Images/Input/Tga/targa_24bit.tga b/tests/Images/Input/Tga/targa_24bit.tga
new file mode 100644
index 000000000..ebe78aea5
Binary files /dev/null and b/tests/Images/Input/Tga/targa_24bit.tga differ
diff --git a/tests/Images/Input/Tga/targa_24bit_pal.tga b/tests/Images/Input/Tga/targa_24bit_pal.tga
new file mode 100644
index 000000000..d97c64674
Binary files /dev/null and b/tests/Images/Input/Tga/targa_24bit_pal.tga differ
diff --git a/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga
new file mode 100644
index 000000000..0c3ad641d
Binary files /dev/null and b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga differ
diff --git a/tests/Images/Input/Tga/targa_24bit_rle.tga b/tests/Images/Input/Tga/targa_24bit_rle.tga
new file mode 100644
index 000000000..e887af750
Binary files /dev/null and b/tests/Images/Input/Tga/targa_24bit_rle.tga differ
diff --git a/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga
new file mode 100644
index 000000000..53e08c5e2
Binary files /dev/null and b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga differ
diff --git a/tests/Images/Input/Tga/targa_32bit.tga b/tests/Images/Input/Tga/targa_32bit.tga
new file mode 100644
index 000000000..7f29e022f
Binary files /dev/null and b/tests/Images/Input/Tga/targa_32bit.tga differ
diff --git a/tests/Images/Input/Tga/targa_32bit_rle.tga b/tests/Images/Input/Tga/targa_32bit_rle.tga
new file mode 100644
index 000000000..c5af174b1
Binary files /dev/null and b/tests/Images/Input/Tga/targa_32bit_rle.tga differ
diff --git a/tests/Images/Input/Tga/targa_8bit.tga b/tests/Images/Input/Tga/targa_8bit.tga
new file mode 100644
index 000000000..0b69052ec
Binary files /dev/null and b/tests/Images/Input/Tga/targa_8bit.tga differ
diff --git a/tests/Images/Input/Tga/targa_8bit_rle.tga b/tests/Images/Input/Tga/targa_8bit_rle.tga
new file mode 100644
index 000000000..21e9dfa3d
Binary files /dev/null and b/tests/Images/Input/Tga/targa_8bit_rle.tga differ