|
|
|
@ -17,6 +17,11 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// </summary>
|
|
|
|
internal sealed class TgaDecoderCore |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// A scratch buffer to reduce allocations.
|
|
|
|
/// </summary>
|
|
|
|
private readonly byte[] scratchBuffer = new byte[4]; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The metadata.
|
|
|
|
/// </summary>
|
|
|
|
@ -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,60 +233,73 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="palette">The color palette.</param>
|
|
|
|
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
|
|
|
|
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|
|
|
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) |
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean)) |
|
|
|
TPixel color = default; |
|
|
|
bool invertX = InvertX(origin); |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
Span<byte> rowSpan = row.GetSpan(); |
|
|
|
int newY = InvertY(y, height, origin); |
|
|
|
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
|
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
switch (colorMapPixelSizeInBytes) |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
switch (colorMapPixelSizeInBytes) |
|
|
|
{ |
|
|
|
case 2: |
|
|
|
case 2: |
|
|
|
if (invertX) |
|
|
|
{ |
|
|
|
for (int x = width - 1; x >= 0; x--) |
|
|
|
{ |
|
|
|
this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
{ |
|
|
|
int colorIndex = rowSpan[x]; |
|
|
|
|
|
|
|
Bgra5551 bgra = Unsafe.As<byte, Bgra5551>(ref palette[colorIndex * colorMapPixelSizeInBytes]); |
|
|
|
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; |
|
|
|
this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
case 3: |
|
|
|
case 3: |
|
|
|
if (invertX) |
|
|
|
{ |
|
|
|
for (int x = width - 1; x >= 0; x--) |
|
|
|
{ |
|
|
|
this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
{ |
|
|
|
int colorIndex = rowSpan[x]; |
|
|
|
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes])); |
|
|
|
pixelRow[x] = color; |
|
|
|
this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
case 4: |
|
|
|
case 4: |
|
|
|
if (invertX) |
|
|
|
{ |
|
|
|
for (int x = width - 1; x >= 0; x--) |
|
|
|
{ |
|
|
|
this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
{ |
|
|
|
int colorIndex = rowSpan[x]; |
|
|
|
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes])); |
|
|
|
pixelRow[x] = color; |
|
|
|
this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -295,8 +313,8 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="palette">The color palette.</param>
|
|
|
|
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
|
|
|
|
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|
|
|
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) |
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int bytesPerPixel = 1; |
|
|
|
@ -309,7 +327,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<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
int rowStartIdx = y * width * bytesPerPixel; |
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
@ -348,7 +366,8 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
pixelRow[x] = color; |
|
|
|
int newX = InvertX(x, width, origin); |
|
|
|
pixelRow[newX] = color; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -361,18 +380,43 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// <param name="width">The width of the image.</param>
|
|
|
|
/// <param name="height">The height of the image.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|
|
|
private void ReadMonoChrome<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|
|
|
/// <param name="origin">the image origin.</param>
|
|
|
|
private void ReadMonoChrome<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) |
|
|
|
bool invertX = InvertX(origin); |
|
|
|
if (invertX) |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
int newY = InvertY(y, height, origin); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|
|
|
PixelOperations<TPixel>.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width); |
|
|
|
for (int x = width - 1; x >= 0; x--) |
|
|
|
{ |
|
|
|
this.ReadL8Pixel(color, x, pixelSpan); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) |
|
|
|
{ |
|
|
|
bool invertY = InvertY(origin); |
|
|
|
if (invertY) |
|
|
|
{ |
|
|
|
for (int y = height - 1; y >= 0; y--) |
|
|
|
{ |
|
|
|
this.ReadL8Row(width, pixels, row, y); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.ReadL8Row(width, pixels, row, y); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -384,29 +428,64 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// <param name="width">The width of the image.</param>
|
|
|
|
/// <param name="height">The height of the image.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|
|
|
private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
bool invertX = InvertX(origin); |
|
|
|
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
Span<byte> rowSpan = row.GetSpan(); |
|
|
|
int newY = InvertY(y, height, origin); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|
|
|
|
|
|
|
if (!this.hasAlpha) |
|
|
|
if (invertX) |
|
|
|
{ |
|
|
|
// We need to set the alpha component value to fully opaque.
|
|
|
|
for (int x = 1; x < rowSpan.Length; x += 2) |
|
|
|
for (int x = width - 1; x >= 0; x--) |
|
|
|
{ |
|
|
|
rowSpan[x] = (byte)(rowSpan[x] | (1 << 7)); |
|
|
|
this.currentStream.Read(this.scratchBuffer, 0, 2); |
|
|
|
if (!this.hasAlpha) |
|
|
|
{ |
|
|
|
this.scratchBuffer[1] |= 1 << 7; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) |
|
|
|
{ |
|
|
|
color.FromLa16(Unsafe.As<byte, La16>(ref this.scratchBuffer[0])); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref this.scratchBuffer[0])); |
|
|
|
} |
|
|
|
|
|
|
|
pixelSpan[x] = color; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
Span<byte> rowSpan = row.GetSpan(); |
|
|
|
|
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|
|
|
PixelOperations<TPixel>.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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) |
|
|
|
{ |
|
|
|
PixelOperations<TPixel>.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -418,18 +497,44 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// <param name="width">The width of the image.</param>
|
|
|
|
/// <param name="height">The height of the image.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|
|
|
private void ReadBgr24<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
private void ReadBgr24<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) |
|
|
|
bool invertX = InvertX(origin); |
|
|
|
if (invertX) |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
int newY = InvertY(y, height, origin); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|
|
|
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width); |
|
|
|
for (int x = width - 1; x >= 0; x--) |
|
|
|
{ |
|
|
|
this.ReadBgr24Pixel(color, x, pixelSpan); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) |
|
|
|
{ |
|
|
|
bool invertY = InvertY(origin); |
|
|
|
|
|
|
|
if (invertY) |
|
|
|
{ |
|
|
|
for (int y = height - 1; y >= 0; y--) |
|
|
|
{ |
|
|
|
this.ReadBgr24Row(width, pixels, row, y); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.ReadBgr24Row(width, pixels, row, y); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -441,44 +546,51 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// <param name="width">The width of the image.</param>
|
|
|
|
/// <param name="height">The height of the image.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|
|
|
private void ReadBgra32<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
private void ReadBgra32<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
if (this.tgaMetadata.AlphaChannelBits == 8) |
|
|
|
TPixel color = default; |
|
|
|
bool invertX = InvertX(origin); |
|
|
|
if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) |
|
|
|
{ |
|
|
|
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
if (InvertY(origin)) |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width); |
|
|
|
for (int y = height - 1; y >= 0; y--) |
|
|
|
{ |
|
|
|
this.ReadBgra32Row(width, pixels, row, y); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
{ |
|
|
|
this.ReadBgra32Row(width, pixels, row, y); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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++) |
|
|
|
{ |
|
|
|
for (int y = 0; y < height; y++) |
|
|
|
int newY = InvertY(y, height, origin); |
|
|
|
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
if (invertX) |
|
|
|
{ |
|
|
|
for (int x = width - 1; x >= 0; x--) |
|
|
|
{ |
|
|
|
this.ReadBgra32Pixel(x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
int newY = Invert(y, height, inverted); |
|
|
|
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
Span<byte> rowSpan = row.GetSpan(); |
|
|
|
|
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
{ |
|
|
|
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; |
|
|
|
this.ReadBgra32Pixel(x, color, pixelRow); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -492,8 +604,8 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// <param name="height">The height of the image.</param>
|
|
|
|
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
|
|
|
|
/// <param name="bytesPerPixel">The bytes per pixel.</param>
|
|
|
|
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|
|
|
private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int bytesPerPixel, bool inverted) |
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int bytesPerPixel, TgaImageOrigin origin) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
TPixel color = default; |
|
|
|
@ -504,7 +616,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<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|
|
|
int rowStartIdx = y * width * bytesPerPixel; |
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
@ -522,7 +634,15 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); |
|
|
|
} |
|
|
|
|
|
|
|
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx])); |
|
|
|
if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) |
|
|
|
{ |
|
|
|
color.FromLa16(Unsafe.As<byte, La16>(ref bufferSpan[idx])); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx])); |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
case 3: |
|
|
|
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx])); |
|
|
|
@ -541,7 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
pixelRow[x] = color; |
|
|
|
int newX = InvertX(x, width, origin); |
|
|
|
pixelRow[newX] = color; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -561,6 +682,95 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
this.metadata); |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|
|
|
PixelOperations<TPixel>.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width); |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadL8Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
var pixelValue = (byte)this.currentStream.ReadByte(); |
|
|
|
color.FromL8(Unsafe.As<byte, L8>(ref pixelValue)); |
|
|
|
pixelSpan[x] = color; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadBgr24Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
this.currentStream.Read(this.scratchBuffer, 0, 3); |
|
|
|
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref this.scratchBuffer[0])); |
|
|
|
pixelSpan[x] = color; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|
|
|
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width); |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
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]; |
|
|
|
color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha)); |
|
|
|
pixelRow[x] = color; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
this.currentStream.Read(row); |
|
|
|
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|
|
|
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width); |
|
|
|
} |
|
|
|
|
|
|
|
private void ReadPalettedBgr16Pixel<TPixel>(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int colorIndex = this.currentStream.ReadByte(); |
|
|
|
Bgra5551 bgra = default; |
|
|
|
bgra.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref palette[colorIndex * colorMapPixelSizeInBytes])); |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadPalettedBgr24Pixel<TPixel>(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int colorIndex = this.currentStream.ReadByte(); |
|
|
|
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes])); |
|
|
|
pixelRow[x] = color; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private void ReadPalettedBgra32Pixel<TPixel>(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
int colorIndex = this.currentStream.ReadByte(); |
|
|
|
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes])); |
|
|
|
pixelRow[x] = color; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Produce uncompressed tga data from a run length encoded stream.
|
|
|
|
/// </summary>
|
|
|
|
@ -609,18 +819,80 @@ namespace SixLabors.ImageSharp.Formats.Tga |
|
|
|
/// Returns the y- value based on the given height.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="y">The y- value representing the current row.</param>
|
|
|
|
/// <param name="height">The height of the bitmap.</param>
|
|
|
|
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|
|
|
/// <param name="height">The height of the image.</param>
|
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
/// <returns>The <see cref="int"/> representing the inverted value.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private static int InvertY(int y, int height, TgaImageOrigin origin) |
|
|
|
{ |
|
|
|
if (InvertY(origin)) |
|
|
|
{ |
|
|
|
return height - y - 1; |
|
|
|
} |
|
|
|
|
|
|
|
return y; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Indicates whether the y coordinates needs to be inverted, to keep a top left origin.
|
|
|
|
/// </summary>
|
|
|
|
/// <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) |
|
|
|
{ |
|
|
|
switch (origin) |
|
|
|
{ |
|
|
|
case TgaImageOrigin.BottomLeft: |
|
|
|
case TgaImageOrigin.BottomRight: |
|
|
|
return true; |
|
|
|
default: |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the x- value based on the given width.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="x">The x- value representing the current column.</param>
|
|
|
|
/// <param name="width">The width of the image.</param>
|
|
|
|
/// <param name="origin">The image origin.</param>
|
|
|
|
/// <returns>The <see cref="int"/> representing the inverted value.</returns>
|
|
|
|
[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) |
|
|
|
{ |
|
|
|
if (InvertX(origin)) |
|
|
|
{ |
|
|
|
return width - x - 1; |
|
|
|
} |
|
|
|
|
|
|
|
return x; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Indicates whether the x coordinates needs to be inverted, to keep a top left origin.
|
|
|
|
/// </summary>
|
|
|
|
/// <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) |
|
|
|
{ |
|
|
|
case TgaImageOrigin.TopRight: |
|
|
|
case TgaImageOrigin.BottomRight: |
|
|
|
return true; |
|
|
|
default: |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Reads the tga file header from the stream.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
|
/// <returns>true, if the image origin is top left.</returns>
|
|
|
|
private bool ReadFileHeader(Stream stream) |
|
|
|
/// <returns>The image origin.</returns>
|
|
|
|
private TgaImageOrigin ReadFileHeader(Stream stream) |
|
|
|
{ |
|
|
|
this.currentStream = stream; |
|
|
|
|
|
|
|
@ -641,15 +913,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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|