Browse Source

Merge pull request #2197 from SixLabors/bp/tga-encoder

TGA Encoder/Decoder Improvements
pull/2201/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
ffac532fd5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 143
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  2. 157
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  3. 14
      tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
  4. 44
      tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
  5. 3
      tests/ImageSharp.Tests/TestImages.cs
  6. 3
      tests/Images/Input/Tga/Github_RLE_legacy.tga
  7. 3
      tests/Images/Input/Tga/whitestripes.png

143
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
using (IMemoryOwner<byte> palette = this.memoryAllocator.Allocate<byte>(colorMapSizeInBytes, AllocationOptions.Clean))
{
Span<byte> 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 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesPerPixel = 1;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height, AllocationOptions.Clean))
{
TPixel color = default;
Span<byte> bufferSpan = buffer.GetSpan();
@ -319,7 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
int newY = InvertY(y, height, origin);
Span<TPixel> 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 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
var alphaBits = this.tgaMetadata.AlphaChannelBits;
byte alphaBits = this.tgaMetadata.AlphaChannelBits;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
{
Span<byte> bufferSpan = buffer.GetSpan();
@ -624,8 +636,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width);
}
@ -662,7 +679,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadL8Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
var pixelValue = (byte)this.currentStream.ReadByte();
byte pixelValue = (byte)this.currentStream.ReadByte();
color.FromL8(Unsafe.As<byte, L8>(ref pixelValue));
pixelSpan[x] = color;
}
@ -671,7 +688,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadBgr24Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
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<byte, Bgr24>(ref this.scratchBuffer[0]));
pixelSpan[x] = color;
}
@ -680,7 +702,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width);
}
@ -689,8 +716,13 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadBgra32Pixel<TPixel>(int x, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width);
}
@ -709,6 +746,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel<TPixel>
{
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();
if (colorIndex == -1)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
}
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
}
@ -743,6 +790,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();
if (colorIndex == -1)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
}
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
}
@ -757,7 +809,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void UncompressRle(int width, int height, Span<byte> buffer, int bytesPerPixel)
{
int uncompressedPixels = 0;
var pixel = new byte[bytesPerPixel];
Span<byte> pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel);
int totalPixels = width * height;
while (uncompressedPixels < totalPixels)
{
@ -768,11 +820,16 @@ namespace SixLabors.ImageSharp.Formats.Tga
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="origin">The image origin.</param>
/// <returns>True, if y coordinate needs to be inverted.</returns>
[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
};
/// <summary>
/// Returns the x- value based on the given width.
@ -851,17 +908,13 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="origin">The image origin.</param>
/// <returns>True, if x coordinate needs to be inverted.</returns>
[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
};
/// <summary>
/// Reads the tga file header from the stream.
@ -880,8 +933,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
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");
}

157
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -117,7 +117,6 @@ namespace SixLabors.ImageSharp.Formats.Tga
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
Rgba32 color = default;
Buffer2D<TPixel> 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<TPixel> 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;
/// <summary>
/// Writes a the pixel to the stream.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The stream to write to.</param>
/// <param name="currentPixel">The current pixel.</param>
/// <param name="color">The color of the pixel to write.</param>
private void WritePixel<TPixel>(Stream stream, TPixel currentPixel, Rgba32 color)
where TPixel : unmanaged, IPixel<TPixel>
{
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;
}
}
/// <summary>
/// Finds consecutive pixels which have the same value.
/// Finds consecutive pixels which have the same value up to 128 pixels maximum.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="pixels">The pixels of the image.</param>
/// <param name="pixelRow">A pixel row of the image to encode.</param>
/// <param name="xStart">X coordinate to start searching for the same pixels.</param>
/// <param name="yPos">Y coordinate to searching for the same pixels in only one scan line.</param>
/// <returns>The number of equal pixels.</returns>
private byte FindEqualPixels<TPixel>(Buffer2D<TPixel> pixels, int xStart, int yPos)
private byte FindEqualPixels<TPixel>(Span<TPixel> pixelRow, int xStart)
where TPixel : unmanaged, IPixel<TPixel>
{
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 @@ namespace SixLabors.ImageSharp.Formats.Tga
return equalPixelCount;
}
/// <summary>
/// Finds consecutive pixels which are unequal up to 128 pixels maximum.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="pixelRow">A pixel row of the image to encode.</param>
/// <param name="xStart">X coordinate to start searching for the unequal pixels.</param>
/// <returns>The number of equal pixels.</returns>
private byte FindUnEqualPixels<TPixel>(Span<TPixel> pixelRow, int xStart)
where TPixel : unmanaged, IPixel<TPixel>
{
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<byte> AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);

14
tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs

@ -733,6 +733,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
}
// Test case for legacy format, when RLE crosses multiple lines:
// https://github.com/SixLabors/ImageSharp/pull/2172
[Theory]
[WithFile(Github_RLE_legacy, PixelTypes.Rgba32)]
public void TgaDecoder_CanDecode_LegacyFormat<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
ImageComparingUtils.CompareWithReferenceDecoder(provider, image);
}
}
[Theory]
[WithFile(Bit16BottomLeft, PixelTypes.Rgba32)]
[WithFile(Bit24BottomLeft, PixelTypes.Rgba32)]

44
tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs

@ -56,12 +56,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
[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<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
@ -121,6 +117,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaEncoder_Bit32_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
where TPixel : unmanaged, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
[Theory]
[WithFile(WhiteStripesPattern, PixelTypes.Rgba32, 2748)]
public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets<TPixel>(TestImageProvider<TPixel> provider, int expectedBytes)
where TPixel : unmanaged, IPixel<TPixel>
{
// The test image has alternating black and white pixels, which should make using always RLE data inefficient.
using (Image<TPixel> 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<Rgba32>(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)]

3
tests/ImageSharp.Tests/TestImages.cs

@ -544,6 +544,9 @@ namespace SixLabors.ImageSharp.Tests
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

3
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

3
tests/Images/Input/Tga/whitestripes.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2bc5d67ce368d2a40fb99df994c6973287fca2d8c8cff78227996f9acb5c6e1e
size 127
Loading…
Cancel
Save