diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 9c32471fd..242876343 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Buffers; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -29,12 +29,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// /// The metadata. /// - private ImageMetadata metadata; + private ImageMetadata? metadata; /// /// The tga specific metadata. /// - private TgaMetadata tgaMetadata; + private TgaMetadata? tgaMetadata; /// /// The file header containing general information about the image. @@ -46,11 +46,6 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// private readonly MemoryAllocator memoryAllocator; - /// - /// The stream to decode from. - /// - private BufferedReadStream currentStream; - /// /// Indicates whether there is a alpha channel present. /// @@ -80,7 +75,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals try { TgaImageOrigin origin = this.ReadFileHeader(stream); - this.currentStream.Skip(this.fileHeader.IdLength); + stream.Skip(this.fileHeader.IdLength); // Parse the color map, if present. if (this.fileHeader.ColorMapType is not 0 and not 1) @@ -93,7 +88,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals throw new UnknownImageFormatException("Width or height cannot be 0"); } - var image = Image.CreateUninitialized(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); + Image image = Image.CreateUninitialized(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (this.fileHeader.ColorMapType == 1) @@ -113,7 +108,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals using (IMemoryOwner palette = this.memoryAllocator.Allocate(colorMapSizeInBytes, AllocationOptions.Clean)) { Span paletteSpan = palette.GetSpan(); - int bytesRead = this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); + int bytesRead = stream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); if (bytesRead != colorMapSizeInBytes) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map"); @@ -122,6 +117,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) { this.ReadPalettedRle( + stream, this.fileHeader.Width, this.fileHeader.Height, pixels, @@ -132,6 +128,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals else { this.ReadPaletted( + stream, this.fileHeader.Width, this.fileHeader.Height, pixels, @@ -148,7 +145,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals if (this.fileHeader.CMapLength > 0) { int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; - this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); + stream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); } switch (this.fileHeader.PixelDepth) @@ -156,11 +153,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals case 8: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); + this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); } else { - this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + this.ReadMonoChrome(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -169,11 +166,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals case 16: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); + this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); } else { - this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + this.ReadBgra16(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -181,11 +178,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals case 24: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); + this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); } else { - this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + this.ReadBgr24(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -193,11 +190,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals case 32: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); + this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); } else { - this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + this.ReadBgra32(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -219,13 +216,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// Reads a uncompressed TGA image with a palette. /// /// The pixel type. + /// The containing image data. /// The width of the image. /// The height of the image. /// The to assign the palette to. /// The color palette. /// Color map size of one entry in bytes. /// The image origin. - private void ReadPaletted(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + private void ReadPaletted(BufferedReadStream stream, int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { TPixel color = default; @@ -243,14 +241,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int x = width - 1; x >= 0; x--) { - this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { - this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } @@ -261,14 +259,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int x = width - 1; x >= 0; x--) { - this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { - this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } @@ -279,14 +277,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int x = width - 1; x >= 0; x--) { - this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { - this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } @@ -299,48 +297,47 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// Reads a run length encoded TGA image with a palette. /// /// The pixel type. + /// The containing image data. /// The width of the image. /// The height of the image. /// The to assign the palette to. /// The color palette. /// Color map size of one entry in bytes. /// The image origin. - private void ReadPalettedRle(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + private void ReadPalettedRle(BufferedReadStream stream, int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { - using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) - { - TPixel color = default; - Span bufferSpan = buffer.GetSpan(); - this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); + using IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean); + TPixel color = default; + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(stream, width, height, bufferSpan, bytesPerPixel: 1); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + int rowStartIdx = y * width; + for (int x = 0; x < width; x++) { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - int rowStartIdx = y * width; - for (int x = 0; x < width; x++) + int idx = rowStartIdx + x; + switch (colorMapPixelSizeInBytes) { - int idx = rowStartIdx + x; - switch (colorMapPixelSizeInBytes) - { - case 1: - color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - case 2: - this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); - break; - case 3: - color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - case 4: - color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - } - - int newX = InvertX(x, width, origin); - pixelRow[newX] = color; + case 1: + color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 2: + this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); + break; + case 3: + color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 4: + color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; } + + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } } } @@ -349,11 +346,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// Reads a uncompressed monochrome TGA image. /// /// The pixel type. + /// The containing image data. /// The width of the image. /// The height of the image. /// The to assign the palette to. /// the image origin. - private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + private void ReadMonoChrome(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { bool invertX = InvertX(origin); @@ -366,7 +364,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { - this.ReadL8Pixel(color, x, pixelSpan); + ReadL8Pixel(stream, color, x, pixelSpan); } } @@ -380,14 +378,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int y = height - 1; y >= 0; y--) { - this.ReadL8Row(width, pixels, rowSpan, y); + this.ReadL8Row(stream, width, pixels, rowSpan, y); } } else { for (int y = 0; y < height; y++) { - this.ReadL8Row(width, pixels, rowSpan, y); + this.ReadL8Row(stream, width, pixels, rowSpan, y); } } } @@ -396,11 +394,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// Reads a uncompressed TGA image where each pixels has 16 bit. /// /// The pixel type. + /// The containing image data. /// The width of the image. /// The height of the image. /// The to assign the palette to. /// The image origin. - private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + private void ReadBgra16(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { TPixel color = default; @@ -417,7 +416,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int x = width - 1; x >= 0; x--) { - int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 2); + int bytesRead = stream.Read(this.scratchBuffer, 0, 2); if (bytesRead != 2) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); @@ -442,7 +441,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } else { - int bytesRead = this.currentStream.Read(rowSpan); + int bytesRead = stream.Read(rowSpan); if (bytesRead != rowSpan.Length) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); @@ -473,11 +472,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// Reads a uncompressed TGA image where each pixels has 24 bit. /// /// The pixel type. + /// The containing image data. /// The width of the image. /// The height of the image. /// The to assign the palette to. /// The image origin. - private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + private void ReadBgr24(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { bool invertX = InvertX(origin); @@ -490,7 +490,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { - this.ReadBgr24Pixel(color, x, pixelSpan); + this.ReadBgr24Pixel(stream, color, x, pixelSpan); } } @@ -505,14 +505,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int y = height - 1; y >= 0; y--) { - this.ReadBgr24Row(width, pixels, rowSpan, y); + this.ReadBgr24Row(stream, width, pixels, rowSpan, y); } } else { for (int y = 0; y < height; y++) { - this.ReadBgr24Row(width, pixels, rowSpan, y); + this.ReadBgr24Row(stream, width, pixels, rowSpan, y); } } } @@ -521,15 +521,19 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// Reads a uncompressed TGA image where each pixels has 32 bit. /// /// The pixel type. + /// The containing image data. /// The width of the image. /// The height of the image. /// The to assign the palette to. /// The image origin. - private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + private void ReadBgra32(BufferedReadStream stream, int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { TPixel color = default; bool invertX = InvertX(origin); + + Guard.NotNull(this.tgaMetadata); + if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) { using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0); @@ -539,14 +543,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int y = height - 1; y >= 0; y--) { - this.ReadBgra32Row(width, pixels, rowSpan, y); + this.ReadBgra32Row(stream, width, pixels, rowSpan, y); } } else { for (int y = 0; y < height; y++) { - this.ReadBgra32Row(width, pixels, rowSpan, y); + this.ReadBgra32Row(stream, width, pixels, rowSpan, y); } } @@ -561,14 +565,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals { for (int x = width - 1; x >= 0; x--) { - this.ReadBgra32Pixel(x, color, pixelRow); + this.ReadBgra32Pixel(stream, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { - this.ReadBgra32Pixel(x, color, pixelRow); + this.ReadBgra32Pixel(stream, x, color, pixelRow); } } } @@ -578,70 +582,72 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// Reads a run length encoded TGA image. /// /// The pixel type. + /// The containing image data. /// The width of the image. /// The height of the image. /// The to assign the palette to. /// The bytes per pixel. /// The image origin. - private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) + private void ReadRle(BufferedReadStream stream, int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { TPixel color = default; + + Guard.NotNull(this.tgaMetadata); + byte alphaBits = this.tgaMetadata.AlphaChannelBits; - using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) + using IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean); + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(stream, width, height, bufferSpan, bytesPerPixel); + for (int y = 0; y < height; y++) { - Span bufferSpan = buffer.GetSpan(); - this.UncompressRle(width, height, bufferSpan, bytesPerPixel); - for (int y = 0; y < height; y++) + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + int rowStartIdx = y * width * bytesPerPixel; + for (int x = 0; x < width; x++) { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - int rowStartIdx = y * width * bytesPerPixel; - for (int x = 0; x < width; x++) + int idx = rowStartIdx + (x * bytesPerPixel); + switch (bytesPerPixel) { - int idx = rowStartIdx + (x * bytesPerPixel); - switch (bytesPerPixel) - { - case 1: - color.FromL8(Unsafe.As(ref bufferSpan[idx])); - break; - case 2: - if (!this.hasAlpha) - { - // Set alpha value to 1, to treat it as opaque for Bgra5551. - bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); - } - - if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) - { - color.FromLa16(Unsafe.As(ref bufferSpan[idx])); - } - else - { - color.FromBgra5551(Unsafe.As(ref bufferSpan[idx])); - } - - break; - case 3: - color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); - break; - case 4: - if (this.hasAlpha) - { - color.FromBgra32(Unsafe.As(ref bufferSpan[idx])); - } - else - { - byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; - color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha)); - } - - break; - } + case 1: + color.FromL8(Unsafe.As(ref bufferSpan[idx])); + break; + case 2: + if (!this.hasAlpha) + { + // Set alpha value to 1, to treat it as opaque for Bgra5551. + bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); + } + + if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) + { + color.FromLa16(Unsafe.As(ref bufferSpan[idx])); + } + else + { + color.FromBgra5551(Unsafe.As(ref bufferSpan[idx])); + } - int newX = InvertX(x, width, origin); - pixelRow[newX] = color; + break; + case 3: + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + break; + case 4: + if (this.hasAlpha) + { + color.FromBgra32(Unsafe.As(ref bufferSpan[idx])); + } + else + { + byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; + color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha)); + } + + break; } + + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } } } @@ -658,10 +664,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadL8Row(int width, Buffer2D pixels, Span row, int y) + private void ReadL8Row(BufferedReadStream stream, int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { - int bytesRead = this.currentStream.Read(row); + int bytesRead = stream.Read(row); if (bytesRead != row.Length) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); @@ -672,19 +678,19 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadL8Pixel(TPixel color, int x, Span pixelSpan) + private static void ReadL8Pixel(BufferedReadStream stream, TPixel color, int x, Span pixelSpan) where TPixel : unmanaged, IPixel { - byte pixelValue = (byte)this.currentStream.ReadByte(); + byte pixelValue = (byte)stream.ReadByte(); color.FromL8(Unsafe.As(ref pixelValue)); pixelSpan[x] = color; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan) + private void ReadBgr24Pixel(BufferedReadStream stream, TPixel color, int x, Span pixelSpan) where TPixel : unmanaged, IPixel { - int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 3); + int bytesRead = stream.Read(this.scratchBuffer, 0, 3); if (bytesRead != 3) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel"); @@ -695,10 +701,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgr24Row(int width, Buffer2D pixels, Span row, int y) + private void ReadBgr24Row(BufferedReadStream stream, int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { - int bytesRead = this.currentStream.Read(row); + int bytesRead = stream.Read(row); if (bytesRead != row.Length) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); @@ -709,25 +715,27 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow) + private void ReadBgra32Pixel(BufferedReadStream stream, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { - int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 4); + int bytesRead = stream.Read(this.scratchBuffer, 0, 4); if (bytesRead != 4) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel"); } + Guard.NotNull(this.tgaMetadata); + 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; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Row(int width, Buffer2D pixels, Span row, int y) + private void ReadBgra32Row(BufferedReadStream stream, int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { - int bytesRead = this.currentStream.Read(row); + int bytesRead = stream.Read(row); if (bytesRead != row.Length) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); @@ -738,10 +746,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra16Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + private void ReadPalettedBgra16Pixel(BufferedReadStream stream, Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { - int colorIndex = this.currentStream.ReadByte(); + int colorIndex = stream.ReadByte(); if (colorIndex == -1) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); @@ -768,10 +776,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgr24Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + private static void ReadPalettedBgr24Pixel(BufferedReadStream stream, Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { - int colorIndex = this.currentStream.ReadByte(); + int colorIndex = stream.ReadByte(); if (colorIndex == -1) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); @@ -782,10 +790,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra32Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + private static void ReadPalettedBgra32Pixel(BufferedReadStream stream, Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { - int colorIndex = this.currentStream.ReadByte(); + int colorIndex = stream.ReadByte(); if (colorIndex == -1) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); @@ -798,25 +806,26 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// /// Produce uncompressed tga data from a run length encoded stream. /// + /// The containing image data. /// The width of the image. /// The height of the image. /// Buffer for uncompressed data. /// The bytes used per pixel. - private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel) + private void UncompressRle(BufferedReadStream stream, int width, int height, Span buffer, int bytesPerPixel) { int uncompressedPixels = 0; Span pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel); int totalPixels = width * height; while (uncompressedPixels < totalPixels) { - byte runLengthByte = (byte)this.currentStream.ReadByte(); + byte runLengthByte = (byte)stream.ReadByte(); // The high bit of a run length packet is set to 1. int highBit = runLengthByte >> 7; if (highBit == 1) { int runLength = runLengthByte & 127; - int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel); + int bytesRead = stream.Read(pixel, 0, bytesPerPixel); if (bytesRead != bytesPerPixel) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); @@ -836,7 +845,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals int bufferIdx = uncompressedPixels * bytesPerPixel; for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) { - int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel); + int bytesRead = stream.Read(pixel, 0, bytesPerPixel); if (bytesRead != bytesPerPixel) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); @@ -917,13 +926,13 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals /// /// The containing image data. /// The image origin. + [MemberNotNull(nameof(metadata))] + [MemberNotNull(nameof(tgaMetadata))] private TgaImageOrigin ReadFileHeader(BufferedReadStream stream) { - this.currentStream = stream; - Span buffer = stackalloc byte[TgaFileHeader.Size]; - this.currentStream.Read(buffer, 0, TgaFileHeader.Size); + stream.Read(buffer, 0, TgaFileHeader.Size); this.fileHeader = TgaFileHeader.Parse(buffer); this.metadata = new ImageMetadata(); this.tgaMetadata = this.metadata.GetTgaMetadata(); @@ -939,7 +948,6 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals this.hasAlpha = alphaBits > 0; // Bits 4 and 5 describe the image origin. - var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); - return origin; + return (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 44799c894..f468ab9ae 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Buffers; using System.Buffers.Binary; @@ -23,11 +22,6 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals /// private readonly MemoryAllocator memoryAllocator; - /// - /// The global configuration. - /// - private Configuration configuration; - /// /// Reusable buffer for writing data. /// @@ -68,7 +62,6 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; TgaMetadata tgaMetadata = metadata.GetTgaMetadata(); this.bitsPerPixel ??= tgaMetadata.BitsPerPixel; @@ -124,7 +117,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals } else { - this.WriteImage(stream, image.Frames.RootFrame); + this.WriteImage(image.GetConfiguration(), stream, image.Frames.RootFrame); } stream.Flush(); @@ -134,30 +127,31 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals /// Writes the pixel data to the binary stream. /// /// The pixel format. + /// The global configuration. /// The to write to. /// /// The containing pixel data. /// - private void WriteImage(Stream stream, ImageFrame image) + private void WriteImage(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { Buffer2D pixels = image.PixelBuffer; switch (this.bitsPerPixel) { case TgaBitsPerPixel.Pixel8: - this.Write8Bit(stream, pixels); + this.Write8Bit(configuration, stream, pixels); break; case TgaBitsPerPixel.Pixel16: - this.Write16Bit(stream, pixels); + this.Write16Bit(configuration, stream, pixels); break; case TgaBitsPerPixel.Pixel24: - this.Write24Bit(stream, pixels); + this.Write24Bit(configuration, stream, pixels); break; case TgaBitsPerPixel.Pixel32: - this.Write32Bit(stream, pixels); + this.Write32Bit(configuration, stream, pixels); break; } } @@ -227,7 +221,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals case TgaBitsPerPixel.Pixel16: Bgra5551 bgra5551 = new(color.ToVector4()); - BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); + BinaryPrimitives.WriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); stream.WriteByte(this.buffer[0]); stream.WriteByte(this.buffer[1]); @@ -321,9 +315,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals /// Writes the 8bit pixels uncompressed to the stream. /// /// The pixel format. + /// The global configuration. /// The to write to. /// The containing pixel data. - private void Write8Bit(Stream stream, Buffer2D pixels) + private void Write8Bit(Configuration configuration, Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { using IMemoryOwner row = this.AllocateRow(pixels.Width, 1); @@ -333,7 +328,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals { Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8Bytes( - this.configuration, + configuration, pixelSpan, rowSpan, pixelSpan.Length); @@ -345,9 +340,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals /// Writes the 16bit pixels uncompressed to the stream. /// /// The pixel format. + /// The global configuration. /// The to write to. /// The containing pixel data. - private void Write16Bit(Stream stream, Buffer2D pixels) + private void Write16Bit(Configuration configuration, Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { using IMemoryOwner row = this.AllocateRow(pixels.Width, 2); @@ -357,7 +353,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals { Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra5551Bytes( - this.configuration, + configuration, pixelSpan, rowSpan, pixelSpan.Length); @@ -369,9 +365,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals /// Writes the 24bit pixels uncompressed to the stream. /// /// The pixel format. + /// The global configuration. /// The to write to. /// The containing pixel data. - private void Write24Bit(Stream stream, Buffer2D pixels) + private void Write24Bit(Configuration configuration, Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { using IMemoryOwner row = this.AllocateRow(pixels.Width, 3); @@ -381,7 +378,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals { Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgr24Bytes( - this.configuration, + configuration, pixelSpan, rowSpan, pixelSpan.Length); @@ -393,9 +390,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals /// Writes the 32bit pixels uncompressed to the stream. /// /// The pixel format. + /// The global configuration. /// The to write to. /// The containing pixel data. - private void Write32Bit(Stream stream, Buffer2D pixels) + private void Write32Bit(Configuration configuration, Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); @@ -405,7 +403,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals { Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra32Bytes( - this.configuration, + configuration, pixelSpan, rowSpan, pixelSpan.Length);