Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 98 additions & 45 deletions src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
/// <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)
Expand All @@ -87,7 +87,7 @@ public Image<TPixel> Decode<TPixel>(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");
}
Expand Down Expand Up @@ -117,7 +117,11 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
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)
{
Expand Down Expand Up @@ -308,8 +312,7 @@ private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels
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();
Expand All @@ -319,7 +322,7 @@ private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pix
{
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;
Expand Down Expand Up @@ -418,7 +421,12 @@ private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> 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;
Expand All @@ -438,7 +446,11 @@ private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> 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)
{
Expand Down Expand Up @@ -579,7 +591,7 @@ private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int
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();
Expand Down Expand Up @@ -624,8 +636,8 @@ private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> 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];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use Bgra here when the buffer is Rgba?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the Intel byte ordering, the data needs to be interpreted as bgra. Quote from the Spec:

|  Image Data Field.                                         |
|                                                            |
|  This field specifies (width) x (height) pixels.  Each     |
|  pixel specifies an RGB color value, which is stored as    |
|  an integral number of bytes.                              |
|  The 2 byte entry is broken down as follows:               |
|  ARRRRRGG GGGBBBBB, where each letter represents a bit.    |
|  But, because of the lo-hi storage order, the first byte   |
|  coming from the file will actually be GGGBBBBB, and the   |
|  second will be ARRRRRGG. "A" represents an attribute bit. |
|  The 3 byte entry contains 1 byte each of blue, green,     |
|  and red.                                                  |
|  The 4 byte entry contains 1 byte each of blue, green,     |
|  red, and attribute.  For faster speed (because of the     |
|  hardware of the Targa board itself), Targa 24 images are  |
|  sometimes stored as Targa 32 images.                      |
--------------------------------------------------------------------------------

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks... I was thrown by the assignment order in the Bgra32 constructor. Forgot the order was r,g,b,a.

color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha));
}

break;
Expand Down Expand Up @@ -653,7 +665,12 @@ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancella
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);
}
Expand All @@ -662,7 +679,7 @@ private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> ro
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;
}
Expand All @@ -671,7 +688,12 @@ private void ReadL8Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
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;
}
Expand All @@ -680,7 +702,12 @@ private void ReadBgr24Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
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);
}
Expand All @@ -689,8 +716,13 @@ private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte>
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;
}
Expand All @@ -699,7 +731,12 @@ private void ReadBgra32Pixel<TPixel>(int x, TPixel color, Span<TPixel> pixelRow)
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);
}
Expand All @@ -709,6 +746,11 @@ private void ReadPalettedBgra16Pixel<TPixel>(Span<byte> palette, int colorMapPix
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;
}
Expand All @@ -734,6 +776,11 @@ private void ReadPalettedBgr24Pixel<TPixel>(Span<byte> palette, int colorMapPixe
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;
}
Expand All @@ -743,6 +790,11 @@ private void ReadPalettedBgra32Pixel<TPixel>(Span<byte> palette, int colorMapPix
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;
}
Expand All @@ -757,7 +809,7 @@ private void ReadPalettedBgra32Pixel<TPixel>(Span<byte> palette, int colorMapPix
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)
{
Expand All @@ -768,11 +820,16 @@ private void UncompressRle(int width, int height, Span<byte> 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;
}
}
Expand All @@ -783,8 +840,13 @@ private void UncompressRle(int width, int height, Span<byte> 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;
}
}
Expand Down Expand Up @@ -815,17 +877,12 @@ private static int InvertY(int y, int height, TgaImageOrigin origin)
/// <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.
Expand All @@ -851,17 +908,13 @@ private static int InvertX(int x, int width, TgaImageOrigin origin)
/// <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.
Expand All @@ -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");
}
Expand Down
Loading