Skip to content

Commit ffac532

Browse files
Merge pull request #2197 from SixLabors/bp/tga-encoder
TGA Encoder/Decoder Improvements
2 parents 7dacf4f + 343b4af commit ffac532

File tree

7 files changed

+268
-99
lines changed

7 files changed

+268
-99
lines changed

src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

Lines changed: 98 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
7575
/// <summary>
7676
/// Gets the dimensions of the image.
7777
/// </summary>
78-
public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
78+
public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height);
7979

8080
/// <inheritdoc />
8181
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@@ -87,7 +87,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
8787
this.currentStream.Skip(this.fileHeader.IdLength);
8888

8989
// Parse the color map, if present.
90-
if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1)
90+
if (this.fileHeader.ColorMapType is not 0 and not 1)
9191
{
9292
TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found");
9393
}
@@ -117,7 +117,11 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
117117
using (IMemoryOwner<byte> palette = this.memoryAllocator.Allocate<byte>(colorMapSizeInBytes, AllocationOptions.Clean))
118118
{
119119
Span<byte> paletteSpan = palette.GetSpan();
120-
this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes);
120+
int bytesRead = this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes);
121+
if (bytesRead != colorMapSizeInBytes)
122+
{
123+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map");
124+
}
121125

122126
if (this.fileHeader.ImageType == TgaImageType.RleColorMapped)
123127
{
@@ -308,8 +312,7 @@ private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels
308312
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
309313
where TPixel : unmanaged, IPixel<TPixel>
310314
{
311-
int bytesPerPixel = 1;
312-
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
315+
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height, AllocationOptions.Clean))
313316
{
314317
TPixel color = default;
315318
Span<byte> bufferSpan = buffer.GetSpan();
@@ -319,7 +322,7 @@ private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pix
319322
{
320323
int newY = InvertY(y, height, origin);
321324
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
322-
int rowStartIdx = y * width * bytesPerPixel;
325+
int rowStartIdx = y * width;
323326
for (int x = 0; x < width; x++)
324327
{
325328
int idx = rowStartIdx + x;
@@ -418,7 +421,12 @@ private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels,
418421
{
419422
for (int x = width - 1; x >= 0; x--)
420423
{
421-
this.currentStream.Read(this.scratchBuffer, 0, 2);
424+
int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 2);
425+
if (bytesRead != 2)
426+
{
427+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
428+
}
429+
422430
if (!this.hasAlpha)
423431
{
424432
this.scratchBuffer[1] |= 1 << 7;
@@ -438,7 +446,11 @@ private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels,
438446
}
439447
else
440448
{
441-
this.currentStream.Read(rowSpan);
449+
int bytesRead = this.currentStream.Read(rowSpan);
450+
if (bytesRead != rowSpan.Length)
451+
{
452+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
453+
}
442454

443455
if (!this.hasAlpha)
444456
{
@@ -579,7 +591,7 @@ private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int
579591
where TPixel : unmanaged, IPixel<TPixel>
580592
{
581593
TPixel color = default;
582-
var alphaBits = this.tgaMetadata.AlphaChannelBits;
594+
byte alphaBits = this.tgaMetadata.AlphaChannelBits;
583595
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
584596
{
585597
Span<byte> bufferSpan = buffer.GetSpan();
@@ -624,8 +636,8 @@ private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int
624636
}
625637
else
626638
{
627-
var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3];
628-
color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha));
639+
byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3];
640+
color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha));
629641
}
630642

631643
break;
@@ -653,7 +665,12 @@ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancella
653665
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
654666
where TPixel : unmanaged, IPixel<TPixel>
655667
{
656-
this.currentStream.Read(row);
668+
int bytesRead = this.currentStream.Read(row);
669+
if (bytesRead != row.Length)
670+
{
671+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
672+
}
673+
657674
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
658675
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width);
659676
}
@@ -662,7 +679,7 @@ private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> ro
662679
private void ReadL8Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
663680
where TPixel : unmanaged, IPixel<TPixel>
664681
{
665-
var pixelValue = (byte)this.currentStream.ReadByte();
682+
byte pixelValue = (byte)this.currentStream.ReadByte();
666683
color.FromL8(Unsafe.As<byte, L8>(ref pixelValue));
667684
pixelSpan[x] = color;
668685
}
@@ -671,7 +688,12 @@ private void ReadL8Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
671688
private void ReadBgr24Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
672689
where TPixel : unmanaged, IPixel<TPixel>
673690
{
674-
this.currentStream.Read(this.scratchBuffer, 0, 3);
691+
int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 3);
692+
if (bytesRead != 3)
693+
{
694+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel");
695+
}
696+
675697
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref this.scratchBuffer[0]));
676698
pixelSpan[x] = color;
677699
}
@@ -680,7 +702,12 @@ private void ReadBgr24Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
680702
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
681703
where TPixel : unmanaged, IPixel<TPixel>
682704
{
683-
this.currentStream.Read(row);
705+
int bytesRead = this.currentStream.Read(row);
706+
if (bytesRead != row.Length)
707+
{
708+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
709+
}
710+
684711
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
685712
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width);
686713
}
@@ -689,8 +716,13 @@ private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte>
689716
private void ReadBgra32Pixel<TPixel>(int x, TPixel color, Span<TPixel> pixelRow)
690717
where TPixel : unmanaged, IPixel<TPixel>
691718
{
692-
this.currentStream.Read(this.scratchBuffer, 0, 4);
693-
var alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3];
719+
int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 4);
720+
if (bytesRead != 4)
721+
{
722+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel");
723+
}
724+
725+
byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3];
694726
color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha));
695727
pixelRow[x] = color;
696728
}
@@ -699,7 +731,12 @@ private void ReadBgra32Pixel<TPixel>(int x, TPixel color, Span<TPixel> pixelRow)
699731
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
700732
where TPixel : unmanaged, IPixel<TPixel>
701733
{
702-
this.currentStream.Read(row);
734+
int bytesRead = this.currentStream.Read(row);
735+
if (bytesRead != row.Length)
736+
{
737+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
738+
}
739+
703740
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
704741
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width);
705742
}
@@ -709,6 +746,11 @@ private void ReadPalettedBgra16Pixel<TPixel>(Span<byte> palette, int colorMapPix
709746
where TPixel : unmanaged, IPixel<TPixel>
710747
{
711748
int colorIndex = this.currentStream.ReadByte();
749+
if (colorIndex == -1)
750+
{
751+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
752+
}
753+
712754
this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color);
713755
pixelRow[x] = color;
714756
}
@@ -734,6 +776,11 @@ private void ReadPalettedBgr24Pixel<TPixel>(Span<byte> palette, int colorMapPixe
734776
where TPixel : unmanaged, IPixel<TPixel>
735777
{
736778
int colorIndex = this.currentStream.ReadByte();
779+
if (colorIndex == -1)
780+
{
781+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
782+
}
783+
737784
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
738785
pixelRow[x] = color;
739786
}
@@ -743,6 +790,11 @@ private void ReadPalettedBgra32Pixel<TPixel>(Span<byte> palette, int colorMapPix
743790
where TPixel : unmanaged, IPixel<TPixel>
744791
{
745792
int colorIndex = this.currentStream.ReadByte();
793+
if (colorIndex == -1)
794+
{
795+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
796+
}
797+
746798
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
747799
pixelRow[x] = color;
748800
}
@@ -757,7 +809,7 @@ private void ReadPalettedBgra32Pixel<TPixel>(Span<byte> palette, int colorMapPix
757809
private void UncompressRle(int width, int height, Span<byte> buffer, int bytesPerPixel)
758810
{
759811
int uncompressedPixels = 0;
760-
var pixel = new byte[bytesPerPixel];
812+
Span<byte> pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel);
761813
int totalPixels = width * height;
762814
while (uncompressedPixels < totalPixels)
763815
{
@@ -768,11 +820,16 @@ private void UncompressRle(int width, int height, Span<byte> buffer, int bytesPe
768820
if (highBit == 1)
769821
{
770822
int runLength = runLengthByte & 127;
771-
this.currentStream.Read(pixel, 0, bytesPerPixel);
823+
int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel);
824+
if (bytesRead != bytesPerPixel)
825+
{
826+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");
827+
}
828+
772829
int bufferIdx = uncompressedPixels * bytesPerPixel;
773830
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
774831
{
775-
pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
832+
pixel.CopyTo(buffer.Slice(bufferIdx));
776833
bufferIdx += bytesPerPixel;
777834
}
778835
}
@@ -783,8 +840,13 @@ private void UncompressRle(int width, int height, Span<byte> buffer, int bytesPe
783840
int bufferIdx = uncompressedPixels * bytesPerPixel;
784841
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
785842
{
786-
this.currentStream.Read(pixel, 0, bytesPerPixel);
787-
pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
843+
int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel);
844+
if (bytesRead != bytesPerPixel)
845+
{
846+
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");
847+
}
848+
849+
pixel.CopyTo(buffer.Slice(bufferIdx));
788850
bufferIdx += bytesPerPixel;
789851
}
790852
}
@@ -815,17 +877,12 @@ private static int InvertY(int y, int height, TgaImageOrigin origin)
815877
/// <param name="origin">The image origin.</param>
816878
/// <returns>True, if y coordinate needs to be inverted.</returns>
817879
[MethodImpl(MethodImplOptions.AggressiveInlining)]
818-
private static bool InvertY(TgaImageOrigin origin)
880+
private static bool InvertY(TgaImageOrigin origin) => origin switch
819881
{
820-
switch (origin)
821-
{
822-
case TgaImageOrigin.BottomLeft:
823-
case TgaImageOrigin.BottomRight:
824-
return true;
825-
default:
826-
return false;
827-
}
828-
}
882+
TgaImageOrigin.BottomLeft => true,
883+
TgaImageOrigin.BottomRight => true,
884+
_ => false
885+
};
829886

830887
/// <summary>
831888
/// Returns the x- value based on the given width.
@@ -851,17 +908,13 @@ private static int InvertX(int x, int width, TgaImageOrigin origin)
851908
/// <param name="origin">The image origin.</param>
852909
/// <returns>True, if x coordinate needs to be inverted.</returns>
853910
[MethodImpl(MethodImplOptions.AggressiveInlining)]
854-
private static bool InvertX(TgaImageOrigin origin)
855-
{
856-
switch (origin)
911+
private static bool InvertX(TgaImageOrigin origin) =>
912+
origin switch
857913
{
858-
case TgaImageOrigin.TopRight:
859-
case TgaImageOrigin.BottomRight:
860-
return true;
861-
default:
862-
return false;
863-
}
864-
}
914+
TgaImageOrigin.TopRight => true,
915+
TgaImageOrigin.BottomRight => true,
916+
_ => false
917+
};
865918

866919
/// <summary>
867920
/// Reads the tga file header from the stream.
@@ -880,8 +933,8 @@ private TgaImageOrigin ReadFileHeader(BufferedReadStream stream)
880933
this.tgaMetadata = this.metadata.GetTgaMetadata();
881934
this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
882935

883-
var alphaBits = this.fileHeader.ImageDescriptor & 0xf;
884-
if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8)
936+
int alphaBits = this.fileHeader.ImageDescriptor & 0xf;
937+
if (alphaBits is not 0 and not 1 and not 8)
885938
{
886939
TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits");
887940
}

0 commit comments

Comments
 (0)