diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
index 2249c86bf..c3b8526ce 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index ead004003..bba04f98a 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -12,6 +12,9 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
{
+ ///
+ /// Performs the tga decoding operation.
+ ///
internal sealed class TgaDecoderCore
{
///
@@ -49,6 +52,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
///
private readonly ITgaDecoderOptions options;
+ ///
+ /// Indicates whether there is a alpha channel present.
+ ///
+ private bool hasAlpha;
+
///
/// Initializes a new instance of the class.
///
@@ -89,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found");
}
- if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0)
+ if (this.fileHeader.Width is 0 || this.fileHeader.Height is 0)
{
throw new UnknownImageFormatException("Width or height cannot be 0");
}
@@ -199,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
break;
default:
- TgaThrowHelper.ThrowNotSupportedException("Does not support this kind of tga files.");
+ TgaThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of tga files.");
break;
}
@@ -241,9 +249,13 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
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);
+ if (!this.hasAlpha)
+ {
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
+ }
+
color.FromBgra5551(bgra);
pixelRow[x] = color;
}
@@ -291,6 +303,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
{
TPixel color = default;
+ var alphaBits = this.tgaMetadata.AlphaChannelBits;
Span bufferSpan = buffer.GetSpan();
this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1);
@@ -308,16 +321,30 @@ namespace SixLabors.ImageSharp.Formats.Tga
color.FromL8(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);
+ if (!this.hasAlpha)
+ {
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ 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]));
+ if (this.hasAlpha)
+ {
+ color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
+ }
+ else
+ {
+ var alpha = alphaBits is 0 ? byte.MaxValue : bufferSpan[idx + 3];
+ color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha));
+ }
+
break;
}
@@ -345,11 +372,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.currentStream.Read(row);
int newY = Invert(y, height, inverted);
Span pixelSpan = pixels.GetRowSpan(newY);
- PixelOperations.Instance.FromL8Bytes(
- this.configuration,
- row.GetSpan(),
- pixelSpan,
- width);
+ PixelOperations.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
}
}
}
@@ -372,19 +395,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
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)
+ if (!this.hasAlpha)
{
- rowSpan[x] = (byte)(rowSpan[x] | (1 << 7));
+ // We need to set the 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);
+ PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width);
}
}
}
@@ -407,11 +429,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.currentStream.Read(row);
int newY = Invert(y, height, inverted);
Span pixelSpan = pixels.GetRowSpan(newY);
- PixelOperations.Instance.FromBgr24Bytes(
- this.configuration,
- row.GetSpan(),
- pixelSpan,
- width);
+ PixelOperations.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
}
}
}
@@ -427,18 +445,41 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void ReadBgra32(int width, int height, Buffer2D pixels, bool inverted)
where TPixel : unmanaged, IPixel
{
+ if (this.tgaMetadata.AlphaChannelBits is 8)
+ {
+ 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);
+ }
+ }
+
+ 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);
- Span pixelSpan = pixels.GetRowSpan(newY);
- PixelOperations.Instance.FromBgra32Bytes(
- this.configuration,
- row.GetSpan(),
- pixelSpan,
- width);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ Span rowSpan = row.GetSpan();
+
+ for (int x = 0; x < width; x++)
+ {
+ int idx = x * 4;
+ var alpha = alphaBits is 0 ? byte.MaxValue : rowSpan[idx + 3];
+ color.FromBgra32(new Bgra32(rowSpan[idx + 2], rowSpan[idx + 1], rowSpan[idx], (byte)alpha));
+ pixelRow[x] = color;
+ }
}
}
}
@@ -456,6 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
where TPixel : unmanaged, IPixel
{
TPixel color = default;
+ var alphaBits = this.tgaMetadata.AlphaChannelBits;
using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
{
Span bufferSpan = buffer.GetSpan();
@@ -474,15 +516,28 @@ namespace SixLabors.ImageSharp.Formats.Tga
color.FromL8(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);
+ if (!this.hasAlpha)
+ {
+ // 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]));
+ if (this.hasAlpha)
+ {
+ color.FromBgra32(Unsafe.As(ref bufferSpan[idx]));
+ }
+ else
+ {
+ var alpha = alphaBits is 0 ? byte.MaxValue : bufferSpan[idx + 3];
+ color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha));
+ }
+
break;
}
@@ -524,7 +579,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
// The high bit of a run length packet is set to 1.
int highBit = runLengthByte >> 7;
- if (highBit == 1)
+ if (highBit is 1)
{
int runLength = runLengthByte & 127;
this.currentStream.Read(pixel, 0, bytesPerPixel);
@@ -577,7 +632,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.tgaMetadata = this.metadata.GetTgaMetadata();
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.
+ var alphaBits = this.fileHeader.ImageDescriptor & 0xf;
+ if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8)
+ {
+ TgaThrowHelper.ThrowImageFormatException("Invalid alpha channel bits");
+ }
+
+ 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;
diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
index 4ce61d2e4..69dee768a 100644
--- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs
+++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
@@ -29,6 +29,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
///
public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24;
+ ///
+ /// Gets or sets the the number of alpha bits per pixel.
+ ///
+ public byte AlphaChannelBits { get; set; } = 0;
+
///
public IDeepCloneable DeepClone() => new TgaMetadata(this);
}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
index bcd98d714..ec2621e65 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
@@ -198,6 +198,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
}
+ [Theory]
+ [WithFile(NoAlphaBits32Bit, PixelTypes.Rgba32)]
+ [WithFile(NoAlphaBits16Bit, PixelTypes.Rgba32)]
+ [WithFile(NoAlphaBits32BitRle, PixelTypes.Rgba32)]
+ [WithFile(NoAlphaBits16BitRle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
[Theory]
[WithFile(Bit16, PixelTypes.Rgba32)]
[WithFile(Bit24, PixelTypes.Rgba32)]
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 2e58ac970..db4c9a448 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -390,6 +390,10 @@ namespace SixLabors.ImageSharp.Tests
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";
+ public const string NoAlphaBits16Bit = "Tga/16bit_noalphabits.tga";
+ public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga";
+ public const string NoAlphaBits32Bit = "Tga/32bit_noalphabits.tga";
+ public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga";
}
}
}
diff --git a/tests/Images/Input/Tga/16bit_noalphabits.tga b/tests/Images/Input/Tga/16bit_noalphabits.tga
new file mode 100644
index 000000000..0e97e86e3
Binary files /dev/null and b/tests/Images/Input/Tga/16bit_noalphabits.tga differ
diff --git a/tests/Images/Input/Tga/16bit_rle_noalphabits.tga b/tests/Images/Input/Tga/16bit_rle_noalphabits.tga
new file mode 100644
index 000000000..1f42f3de0
Binary files /dev/null and b/tests/Images/Input/Tga/16bit_rle_noalphabits.tga differ
diff --git a/tests/Images/Input/Tga/32bit_noalphabits.tga b/tests/Images/Input/Tga/32bit_noalphabits.tga
new file mode 100644
index 000000000..b239ca46d
Binary files /dev/null and b/tests/Images/Input/Tga/32bit_noalphabits.tga differ
diff --git a/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga b/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga
new file mode 100644
index 000000000..2c12d52da
Binary files /dev/null and b/tests/Images/Input/Tga/32bit_rle_no_alphabits.tga differ