diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index eaf4e63a0a..af586e2544 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -75,7 +75,7 @@ public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
///
/// Gets the dimensions of the image.
///
- public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
+ public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height);
///
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
@@ -87,7 +87,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken
this.currentStream.Skip(this.fileHeader.IdLength);
// Parse the color map, if present.
- if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1)
+ if (this.fileHeader.ColorMapType is not 0 and not 1)
{
TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found");
}
@@ -117,7 +117,11 @@ public Image Decode(BufferedReadStream stream, CancellationToken
using (IMemoryOwner palette = this.memoryAllocator.Allocate(colorMapSizeInBytes, AllocationOptions.Clean))
{
Span paletteSpan = palette.GetSpan();
- this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes);
+ int bytesRead = this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes);
+ if (bytesRead != colorMapSizeInBytes)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map");
+ }
if (this.fileHeader.ImageType == TgaImageType.RleColorMapped)
{
@@ -308,8 +312,7 @@ private void ReadPaletted(int width, int height, Buffer2D pixels
private void ReadPalettedRle(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
- int bytesPerPixel = 1;
- using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
+ using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean))
{
TPixel color = default;
Span bufferSpan = buffer.GetSpan();
@@ -319,7 +322,7 @@ private void ReadPalettedRle(int width, int height, Buffer2D pix
{
int newY = InvertY(y, height, origin);
Span pixelRow = pixels.DangerousGetRowSpan(newY);
- int rowStartIdx = y * width * bytesPerPixel;
+ int rowStartIdx = y * width;
for (int x = 0; x < width; x++)
{
int idx = rowStartIdx + x;
@@ -418,7 +421,12 @@ private void ReadBgra16(int width, int height, Buffer2D pixels,
{
for (int x = width - 1; x >= 0; x--)
{
- this.currentStream.Read(this.scratchBuffer, 0, 2);
+ int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 2);
+ if (bytesRead != 2)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
+ }
+
if (!this.hasAlpha)
{
this.scratchBuffer[1] |= 1 << 7;
@@ -438,7 +446,11 @@ private void ReadBgra16(int width, int height, Buffer2D pixels,
}
else
{
- this.currentStream.Read(rowSpan);
+ int bytesRead = this.currentStream.Read(rowSpan);
+ if (bytesRead != rowSpan.Length)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
+ }
if (!this.hasAlpha)
{
@@ -579,7 +591,7 @@ private void ReadRle(int width, int height, Buffer2D pixels, int
where TPixel : unmanaged, IPixel
{
TPixel color = default;
- var alphaBits = this.tgaMetadata.AlphaChannelBits;
+ byte alphaBits = this.tgaMetadata.AlphaChannelBits;
using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
{
Span bufferSpan = buffer.GetSpan();
@@ -624,8 +636,8 @@ private void ReadRle(int width, int height, Buffer2D pixels, int
}
else
{
- var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3];
- color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha));
+ byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3];
+ color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha));
}
break;
@@ -653,7 +665,12 @@ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancella
private void ReadL8Row(int width, Buffer2D pixels, Span row, int y)
where TPixel : unmanaged, IPixel
{
- this.currentStream.Read(row);
+ int bytesRead = this.currentStream.Read(row);
+ if (bytesRead != row.Length)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
+ }
+
Span pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width);
}
@@ -662,7 +679,7 @@ private void ReadL8Row(int width, Buffer2D pixels, Span ro
private void ReadL8Pixel(TPixel color, int x, Span pixelSpan)
where TPixel : unmanaged, IPixel
{
- var pixelValue = (byte)this.currentStream.ReadByte();
+ byte pixelValue = (byte)this.currentStream.ReadByte();
color.FromL8(Unsafe.As(ref pixelValue));
pixelSpan[x] = color;
}
@@ -671,7 +688,12 @@ private void ReadL8Pixel(TPixel color, int x, Span pixelSpan)
private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan)
where TPixel : unmanaged, IPixel
{
- this.currentStream.Read(this.scratchBuffer, 0, 3);
+ int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 3);
+ if (bytesRead != 3)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel");
+ }
+
color.FromBgr24(Unsafe.As(ref this.scratchBuffer[0]));
pixelSpan[x] = color;
}
@@ -680,7 +702,12 @@ private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan)
private void ReadBgr24Row(int width, Buffer2D pixels, Span row, int y)
where TPixel : unmanaged, IPixel
{
- this.currentStream.Read(row);
+ int bytesRead = this.currentStream.Read(row);
+ if (bytesRead != row.Length)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
+ }
+
Span pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width);
}
@@ -689,8 +716,13 @@ private void ReadBgr24Row(int width, Buffer2D pixels, Span
private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow)
where TPixel : unmanaged, IPixel
{
- this.currentStream.Read(this.scratchBuffer, 0, 4);
- var alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3];
+ int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 4);
+ if (bytesRead != 4)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel");
+ }
+
+ byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3];
color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha));
pixelRow[x] = color;
}
@@ -699,7 +731,12 @@ private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow)
private void ReadBgra32Row(int width, Buffer2D pixels, Span row, int y)
where TPixel : unmanaged, IPixel
{
- this.currentStream.Read(row);
+ int bytesRead = this.currentStream.Read(row);
+ if (bytesRead != row.Length)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
+ }
+
Span pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width);
}
@@ -709,6 +746,11 @@ private void ReadPalettedBgra16Pixel(Span palette, int colorMapPix
where TPixel : unmanaged, IPixel
{
int colorIndex = this.currentStream.ReadByte();
+ if (colorIndex == -1)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
+ }
+
this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color);
pixelRow[x] = color;
}
@@ -734,6 +776,11 @@ private void ReadPalettedBgr24Pixel(Span palette, int colorMapPixe
where TPixel : unmanaged, IPixel
{
int colorIndex = this.currentStream.ReadByte();
+ if (colorIndex == -1)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
+ }
+
color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
}
@@ -743,6 +790,11 @@ private void ReadPalettedBgra32Pixel(Span palette, int colorMapPix
where TPixel : unmanaged, IPixel
{
int colorIndex = this.currentStream.ReadByte();
+ if (colorIndex == -1)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
+ }
+
color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
}
@@ -757,7 +809,7 @@ private void ReadPalettedBgra32Pixel(Span palette, int colorMapPix
private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel)
{
int uncompressedPixels = 0;
- var pixel = new byte[bytesPerPixel];
+ Span pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel);
int totalPixels = width * height;
while (uncompressedPixels < totalPixels)
{
@@ -768,11 +820,16 @@ private void UncompressRle(int width, int height, Span buffer, int bytesPe
if (highBit == 1)
{
int runLength = runLengthByte & 127;
- this.currentStream.Read(pixel, 0, bytesPerPixel);
+ int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel);
+ if (bytesRead != bytesPerPixel)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");
+ }
+
int bufferIdx = uncompressedPixels * bytesPerPixel;
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
{
- pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
+ pixel.CopyTo(buffer.Slice(bufferIdx));
bufferIdx += bytesPerPixel;
}
}
@@ -783,8 +840,13 @@ private void UncompressRle(int width, int height, Span buffer, int bytesPe
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));
+ int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel);
+ if (bytesRead != bytesPerPixel)
+ {
+ TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");
+ }
+
+ pixel.CopyTo(buffer.Slice(bufferIdx));
bufferIdx += bytesPerPixel;
}
}
@@ -815,17 +877,12 @@ private static int InvertY(int y, int height, TgaImageOrigin origin)
/// The image origin.
/// True, if y coordinate needs to be inverted.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool InvertY(TgaImageOrigin origin)
+ private static bool InvertY(TgaImageOrigin origin) => origin switch
{
- switch (origin)
- {
- case TgaImageOrigin.BottomLeft:
- case TgaImageOrigin.BottomRight:
- return true;
- default:
- return false;
- }
- }
+ TgaImageOrigin.BottomLeft => true,
+ TgaImageOrigin.BottomRight => true,
+ _ => false
+ };
///
/// Returns the x- value based on the given width.
@@ -851,17 +908,13 @@ private static int InvertX(int x, int width, TgaImageOrigin origin)
/// The image origin.
/// True, if x coordinate needs to be inverted.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool InvertX(TgaImageOrigin origin)
- {
- switch (origin)
+ private static bool InvertX(TgaImageOrigin origin) =>
+ origin switch
{
- case TgaImageOrigin.TopRight:
- case TgaImageOrigin.BottomRight:
- return true;
- default:
- return false;
- }
- }
+ TgaImageOrigin.TopRight => true,
+ TgaImageOrigin.BottomRight => true,
+ _ => false
+ };
///
/// Reads the tga file header from the stream.
@@ -880,8 +933,8 @@ private TgaImageOrigin ReadFileHeader(BufferedReadStream stream)
this.tgaMetadata = this.metadata.GetTgaMetadata();
this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
- var alphaBits = this.fileHeader.ImageDescriptor & 0xf;
- if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8)
+ int alphaBits = this.fileHeader.ImageDescriptor & 0xf;
+ if (alphaBits is not 0 and not 1 and not 8)
{
TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits");
}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
index 16910040c5..fa0ea6f90b 100644
--- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -117,7 +117,6 @@ public void Encode(Image image, Stream stream, CancellationToken
fileHeader.WriteTo(buffer);
stream.Write(buffer, 0, TgaFileHeader.Size);
-
if (this.compression is TgaCompression.RunLength)
{
this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame);
@@ -175,69 +174,98 @@ private void WriteRunLengthEncodedImage(Stream stream, ImageFrame pixels = image.PixelBuffer;
- int totalPixels = image.Width * image.Height;
- int encodedPixels = 0;
- while (encodedPixels < totalPixels)
+ for (int y = 0; y < image.Height; y++)
{
- int x = encodedPixels % pixels.Width;
- int y = encodedPixels / pixels.Width;
- TPixel currentPixel = pixels[x, y];
- currentPixel.ToRgba32(ref color);
- byte equalPixelCount = this.FindEqualPixels(pixels, x, y);
-
- // 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)
+ Span pixelRow = pixels.DangerousGetRowSpan(y);
+ for (int x = 0; x < image.Width;)
{
- 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;
- default:
- break;
+ TPixel currentPixel = pixelRow[x];
+ currentPixel.ToRgba32(ref color);
+ byte equalPixelCount = this.FindEqualPixels(pixelRow, x);
+
+ if (equalPixelCount > 0)
+ {
+ // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
+ stream.WriteByte((byte)(equalPixelCount | 128));
+ this.WritePixel(stream, currentPixel, color);
+ x += equalPixelCount + 1;
+ }
+ else
+ {
+ // Write Raw Packet (i.e., Non-Run-Length Encoded):
+ byte unEqualPixelCount = this.FindUnEqualPixels(pixelRow, x);
+ stream.WriteByte(unEqualPixelCount);
+ this.WritePixel(stream, currentPixel, color);
+ x++;
+ for (int i = 0; i < unEqualPixelCount; i++)
+ {
+ currentPixel = pixelRow[x];
+ currentPixel.ToRgba32(ref color);
+ this.WritePixel(stream, currentPixel, color);
+ x++;
+ }
+ }
}
+ }
+ }
- encodedPixels += equalPixelCount + 1;
+ ///
+ /// Writes a the pixel to the stream.
+ ///
+ /// The type of the pixel.
+ /// The stream to write to.
+ /// The current pixel.
+ /// The color of the pixel to write.
+ private void WritePixel(Stream stream, TPixel currentPixel, Rgba32 color)
+ where TPixel : unmanaged, IPixel
+ {
+ 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;
+ default:
+ break;
}
}
///
- /// Finds consecutive pixels which have the same value.
+ /// Finds consecutive pixels which have the same value up to 128 pixels maximum.
///
/// The pixel type.
- /// The pixels of the image.
+ /// A pixel row of the image to encode.
/// X coordinate to start searching for the same pixels.
- /// Y coordinate to searching for the same pixels in only one scan line.
/// The number of equal pixels.
- private byte FindEqualPixels(Buffer2D pixels, int xStart, int yPos)
+ private byte FindEqualPixels(Span pixelRow, int xStart)
where TPixel : unmanaged, IPixel
{
byte equalPixelCount = 0;
- TPixel startPixel = pixels[xStart, yPos];
- for (int x = xStart + 1; x < pixels.Width; x++)
+ TPixel startPixel = pixelRow[xStart];
+ for (int x = xStart + 1; x < pixelRow.Length; x++)
{
- TPixel nextPixel = pixels[x, yPos];
+ TPixel nextPixel = pixelRow[x];
if (startPixel.Equals(nextPixel))
{
equalPixelCount++;
@@ -256,6 +284,39 @@ private byte FindEqualPixels(Buffer2D pixels, int xStart, int yP
return equalPixelCount;
}
+ ///
+ /// Finds consecutive pixels which are unequal up to 128 pixels maximum.
+ ///
+ /// The pixel type.
+ /// A pixel row of the image to encode.
+ /// X coordinate to start searching for the unequal pixels.
+ /// The number of equal pixels.
+ private byte FindUnEqualPixels(Span pixelRow, int xStart)
+ where TPixel : unmanaged, IPixel
+ {
+ byte unEqualPixelCount = 0;
+ TPixel currentPixel = pixelRow[xStart];
+ for (int x = xStart + 1; x < pixelRow.Length; x++)
+ {
+ TPixel nextPixel = pixelRow[x];
+ if (currentPixel.Equals(nextPixel))
+ {
+ return unEqualPixelCount;
+ }
+
+ unEqualPixelCount++;
+
+ if (unEqualPixelCount >= 127)
+ {
+ return unEqualPixelCount;
+ }
+
+ currentPixel = nextPixel;
+ }
+
+ return unEqualPixelCount;
+ }
+
private IMemoryOwner AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
index f3aa38df99..57d8aeff51 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
@@ -733,6 +733,20 @@ public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet(TestImageProvider(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ ImageComparingUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
[Theory]
[WithFile(Bit16BottomLeft, PixelTypes.Rgba32)]
[WithFile(Bit24BottomLeft, PixelTypes.Rgba32)]
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
index 12e24622a8..abe9e2a2d8 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
@@ -56,12 +56,8 @@ public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bm
[MemberData(nameof(TgaBitsPerPixelFiles))]
public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
{
- var options = new TgaEncoder()
- {
- Compression = TgaCompression.RunLength
- };
-
- TestFile testFile = TestFile.Create(imagePath);
+ var options = new TgaEncoder() { Compression = TgaCompression.RunLength };
+ var testFile = TestFile.Create(imagePath);
using (Image input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
@@ -121,6 +117,42 @@ public void TgaEncoder_Bit24_WithRunLengthEncoding_Works(TestImageProvid
public void TgaEncoder_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
+ [Theory]
+ [WithFile(WhiteStripesPattern, PixelTypes.Rgba32, 2748)]
+ public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets(TestImageProvider provider, int expectedBytes)
+ where TPixel : unmanaged, IPixel
+ {
+ // The test image has alternating black and white pixels, which should make using always RLE data inefficient.
+ using (Image image = provider.GetImage())
+ {
+ var options = new TgaEncoder() { Compression = TgaCompression.RunLength };
+ using (var memStream = new MemoryStream())
+ {
+ image.Save(memStream, options);
+ byte[] imageBytes = memStream.ToArray();
+ Assert.Equal(expectedBytes, imageBytes.Length);
+ }
+ }
+ }
+
+ // Run length encoded pixels should not exceed row boundaries.
+ // https://github.com/SixLabors/ImageSharp/pull/2172
+ [Fact]
+ public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries()
+ {
+ var options = new TgaEncoder() { Compression = TgaCompression.RunLength };
+
+ using (var input = new Image(30, 30))
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+ byte[] imageBytes = memStream.ToArray();
+ Assert.Equal(138, imageBytes.Length);
+ }
+ }
+ }
+
[Theory]
[WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)]
[WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)]
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 3efb528a82..306a28dae9 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -544,6 +544,9 @@ public static class Tga
public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga";
public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga";
public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga";
+
+ public const string Github_RLE_legacy = "Tga/Github_RLE_legacy.tga";
+ public const string WhiteStripesPattern = "Tga/whitestripes.png";
}
public static class Webp
diff --git a/tests/Images/Input/Tga/Github_RLE_legacy.tga b/tests/Images/Input/Tga/Github_RLE_legacy.tga
new file mode 100644
index 0000000000..0cb1f73c19
--- /dev/null
+++ b/tests/Images/Input/Tga/Github_RLE_legacy.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3570d2883a10a764577dd5174a9168320e8653b220800714da8e3880f752ab5e
+size 51253
diff --git a/tests/Images/Input/Tga/whitestripes.png b/tests/Images/Input/Tga/whitestripes.png
new file mode 100644
index 0000000000..b7f6c94b48
--- /dev/null
+++ b/tests/Images/Input/Tga/whitestripes.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2bc5d67ce368d2a40fb99df994c6973287fca2d8c8cff78227996f9acb5c6e1e
+size 127