diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index cca34c9b0..270dfb22b 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -17,6 +17,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
///
internal sealed class TgaDecoderCore
{
+ ///
+ /// A scratch buffer to reduce allocations.
+ ///
+ private readonly byte[] buffer = new byte[4];
+
///
/// The metadata.
///
@@ -88,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
try
{
- bool inverted = this.ReadFileHeader(stream);
+ TgaImageOrigin origin = this.ReadFileHeader(stream);
this.currentStream.Skip(this.fileHeader.IdLength);
// Parse the color map, if present.
@@ -131,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
pixels,
palette.Array,
colorMapPixelSizeInBytes,
- inverted);
+ origin);
}
else
{
@@ -141,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
pixels,
palette.Array,
colorMapPixelSizeInBytes,
- inverted);
+ origin);
}
}
@@ -160,11 +165,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 8:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin);
}
else
{
- this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -173,11 +178,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 16:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin);
}
else
{
- this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -185,11 +190,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 24:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin);
}
else
{
- this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -197,11 +202,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 32:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin);
}
else
{
- this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -228,8 +233,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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)
+ /// The image origin.
+ private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean))
@@ -240,7 +245,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
- int newY = Invert(y, height, inverted);
+ int newY = InvertY(y, height, origin);
Span pixelRow = pixels.GetRowSpan(newY);
switch (colorMapPixelSizeInBytes)
{
@@ -257,7 +262,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
color.FromBgra5551(bgra);
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
break;
@@ -267,7 +273,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
int colorIndex = rowSpan[x];
color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
break;
@@ -277,7 +284,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
int colorIndex = rowSpan[x];
color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
break;
@@ -295,8 +303,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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)
+ /// The image origin.
+ private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
int bytesPerPixel = 1;
@@ -309,7 +317,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
- int newY = Invert(y, height, inverted);
+ int newY = InvertY(y, height, origin);
Span pixelRow = pixels.GetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
@@ -348,7 +356,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
break;
}
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
}
}
@@ -361,16 +370,36 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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)
+ /// the image origin.
+ private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
+ bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight;
+ if (isXInverted)
+ {
+ TPixel color = default;
+ for (int y = 0; y < height; y++)
+ {
+ int newY = InvertY(y, height, origin);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ for (int x = 0; x < width; x++)
+ {
+ var pixelValue = (byte)this.currentStream.ReadByte();
+ color.FromL8(Unsafe.As(ref pixelValue));
+ int newX = InvertX(x, width, origin);
+ pixelSpan[newX] = color;
+ }
+ }
+
+ return;
+ }
+
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);
+ int newY = InvertY(y, height, origin);
Span pixelSpan = pixels.GetRowSpan(newY);
PixelOperations.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
}
@@ -384,29 +413,50 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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)
+ /// The image origin.
+ private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
+ TPixel color = default;
+ bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight;
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
{
for (int y = 0; y < height; y++)
{
- this.currentStream.Read(row);
- Span rowSpan = row.GetSpan();
+ int newY = InvertY(y, height, origin);
+ Span pixelSpan = pixels.GetRowSpan(newY);
- if (!this.hasAlpha)
+ if (isXInverted)
{
- // We need to set the alpha component value to fully opaque.
- for (int x = 1; x < rowSpan.Length; x += 2)
+ for (int x = 0; x < width; x++)
{
- rowSpan[x] = (byte)(rowSpan[x] | (1 << 7));
+ this.currentStream.Read(this.buffer, 0, 2);
+ if (!this.hasAlpha)
+ {
+ this.buffer[1] |= 1 << 7;
+ }
+
+ color.FromBgra5551(Unsafe.As(ref this.buffer[0]));
+ int newX = InvertX(x, width, origin);
+ pixelSpan[newX] = color;
}
}
+ else
+ {
+ this.currentStream.Read(row);
+ Span rowSpan = row.GetSpan();
- int newY = Invert(y, height, inverted);
- Span pixelSpan = pixels.GetRowSpan(newY);
- PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width);
+ if (!this.hasAlpha)
+ {
+ // We need to set the alpha component value to fully opaque.
+ for (int x = 1; x < rowSpan.Length; x += 2)
+ {
+ rowSpan[x] |= (1 << 7);
+ }
+ }
+
+ PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width);
+ }
}
}
}
@@ -418,16 +468,36 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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)
+ /// The image origin.
+ private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
+ bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight;
+ if (isXInverted)
+ {
+ TPixel color = default;
+ for (int y = 0; y < height; y++)
+ {
+ int newY = InvertY(y, height, origin);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ for (int x = 0; x < width; x++)
+ {
+ this.currentStream.Read(this.buffer, 0, 3);
+ color.FromBgr24(Unsafe.As(ref this.buffer[0]));
+ int newX = InvertX(x, width, origin);
+ pixelSpan[newX] = color;
+ }
+ }
+
+ return;
+ }
+
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);
+ int newY = InvertY(y, height, origin);
Span pixelSpan = pixels.GetRowSpan(newY);
PixelOperations.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
}
@@ -441,18 +511,38 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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)
+ /// The image origin.
+ private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
+ TPixel color = default;
if (this.tgaMetadata.AlphaChannelBits == 8)
{
+ bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight;
+ if (isXInverted)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ int newY = InvertY(y, height, origin);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ for (int x = 0; x < width; x++)
+ {
+ this.currentStream.Read(this.buffer, 0, 4);
+ color.FromBgra32(Unsafe.As(ref this.buffer[0]));
+ int newX = InvertX(x, width, origin);
+ pixelSpan[newX] = color;
+ }
+ }
+
+ return;
+ }
+
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);
+ int newY = InvertY(y, height, origin);
Span pixelSpan = pixels.GetRowSpan(newY);
PixelOperations.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
@@ -462,14 +552,13 @@ namespace SixLabors.ImageSharp.Formats.Tga
return;
}
- TPixel color = default;
var alphaBits = this.tgaMetadata.AlphaChannelBits;
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);
+ int newY = InvertY(y, height, origin);
Span pixelRow = pixels.GetRowSpan(newY);
Span rowSpan = row.GetSpan();
@@ -478,7 +567,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
int idx = x * 4;
var alpha = alphaBits == 0 ? byte.MaxValue : rowSpan[idx + 3];
color.FromBgra32(new Bgra32(rowSpan[idx + 2], rowSpan[idx + 1], rowSpan[idx], (byte)alpha));
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
}
}
@@ -492,8 +582,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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)
+ /// The image origin.
+ private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
TPixel color = default;
@@ -504,7 +594,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.UncompressRle(width, height, bufferSpan, bytesPerPixel);
for (int y = 0; y < height; y++)
{
- int newY = Invert(y, height, inverted);
+ int newY = InvertY(y, height, origin);
Span pixelRow = pixels.GetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
@@ -541,7 +631,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
break;
}
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
}
}
@@ -609,18 +700,48 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// 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 height of the image.
+ /// The image origin.
+ /// The representing the inverted value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int InvertY(int y, int height, TgaImageOrigin origin)
+ {
+ switch (origin)
+ {
+ case TgaImageOrigin.BottomLeft:
+ case TgaImageOrigin.BottomRight:
+ return height - y - 1;
+ default:
+ return y;
+ }
+ }
+
+ ///
+ /// Returns the x- value based on the given width.
+ ///
+ /// The x- value representing the current column.
+ /// The width of the image.
+ /// The image origin.
/// The representing the inverted value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y;
+ private static int InvertX(int x, int width, TgaImageOrigin origin)
+ {
+ switch (origin)
+ {
+ case TgaImageOrigin.TopRight:
+ case TgaImageOrigin.BottomRight:
+ return width - x - 1;
+ default:
+ return x;
+ }
+ }
///
/// 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)
+ /// The image origin.
+ private TgaImageOrigin ReadFileHeader(Stream stream)
{
this.currentStream = stream;
@@ -641,15 +762,9 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.tgaMetadata.AlphaChannelBits = (byte)alphaBits;
this.hasAlpha = alphaBits > 0;
- // TODO: bits 4 and 5 describe the image origin. See spec page 9. bit 4 is currently ignored.
- // Theoretically the origin could also be top right and bottom right.
- // Bit at position 5 of the descriptor indicates, that the origin is top left instead of bottom left.
- if ((this.fileHeader.ImageDescriptor & (1 << 5)) != 0)
- {
- return true;
- }
-
- return false;
+ // Bits 4 and 5 describe the image origin.
+ var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4);
+ return origin;
}
}
}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs
new file mode 100644
index 000000000..06d7b5945
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal enum TgaImageOrigin
+ {
+ ///
+ /// Bottom left origin.
+ ///
+ BottomLeft = 0,
+
+ ///
+ /// Bottom right origin.
+ ///
+ BottomRight = 1,
+
+ ///
+ /// Top left origin.
+ ///
+ TopLeft = 2,
+
+ ///
+ /// Top right origin.
+ ///
+ TopRight = 3,
+ }
+}