From 462ba485f58a3ecb5f4e6739bd6df2abd9bc0d20 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 10 Oct 2019 20:55:52 +0200 Subject: [PATCH] Add support for decoding tga image with palette --- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 144 +++++++++++++++---- 1 file changed, 120 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index a9002fd8c..12f81a269 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -73,9 +73,47 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.ReadFileHeader(stream); + // TODO: parse ID + + // Parse the color map, if present. + if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1) + { + TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); + } + + byte[] palette = null; + int colorMapPixelSizeInBytes = 0; + if (this.fileHeader.ColorMapType == 1) + { + if (this.fileHeader.CMapLength <= 0) + { + TgaThrowHelper.ThrowImageFormatException("Missing tga color map length"); + } + + if (this.fileHeader.CMapDepth <= 0) + { + TgaThrowHelper.ThrowImageFormatException("Missing tga color map depth"); + } + + colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; + palette = new byte[this.fileHeader.CMapLength * colorMapPixelSizeInBytes]; + this.currentStream.Read(palette, this.fileHeader.CMapStart, palette.Length); + } + var image = new Image(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); + if (this.fileHeader.ImageType == TgaImageType.ColorMapped) + { + if (palette is null) + { + TgaThrowHelper.ThrowImageFormatException("Tga image is missing a color palette"); + } + + this.ReadPaletted(this.fileHeader.Width, this.fileHeader.Height, pixels, palette, colorMapPixelSizeInBytes); + return image; + } + switch (this.fileHeader.PixelDepth) { case 8: @@ -85,7 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } else { - this.ReadMonoChrome(pixels); + this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels); } break; @@ -98,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } else { - this.ReadBgra16(pixels); + this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels); } break; @@ -110,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } else { - this.ReadBgr24(pixels); + this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels); } break; @@ -122,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } else { - this.ReadBgra32(pixels); + this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels); } break; @@ -140,41 +178,99 @@ namespace SixLabors.ImageSharp.Formats.Tga } } - private void ReadMonoChrome(Buffer2D pixels) + private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int pixelSizeInBytes) where TPixel : struct, IPixel { - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(this.fileHeader.Width, 1, 0)) + using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean)) { - for (int y = 0; y < this.fileHeader.Height; y++) + TPixel color = default; + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(this.fileHeader.Height - y - 1); + Span pixelRow = pixels.GetRowSpan(height - y - 1); + switch (pixelSizeInBytes) + { + case 1: + for (int x = 0; x < width; x++) + { + int colorIndex = rowSpan[x]; + color.FromGray8(Unsafe.As(ref palette[colorIndex])); + pixelRow[x] = color; + } + + break; + + case 2: + for (int x = 0; x < width; x++) + { + int colorIndex = rowSpan[x]; + color.FromBgra5551(Unsafe.As(ref palette[colorIndex * pixelSizeInBytes])); + pixelRow[x] = color; + } + + break; + + case 3: + for (int x = 0; x < width; x++) + { + int colorIndex = rowSpan[x]; + color.FromBgr24(Unsafe.As(ref palette[colorIndex * pixelSizeInBytes])); + pixelRow[x] = color; + } + + break; + + case 4: + for (int x = 0; x < width; x++) + { + int colorIndex = rowSpan[x]; + color.FromBgra32(Unsafe.As(ref palette[colorIndex * pixelSizeInBytes])); + pixelRow[x] = color; + } + + break; + } + } + } + } + + private void ReadMonoChrome(int width, int height, Buffer2D pixels) + where TPixel : struct, IPixel + { + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) + { + for (int y = 0; y < height; y++) + { + this.currentStream.Read(row); + Span pixelSpan = pixels.GetRowSpan(height - y - 1); PixelOperations.Instance.FromGray8Bytes( this.configuration, row.GetSpan(), pixelSpan, - this.fileHeader.Width); + width); } } } - private void ReadBgra16(Buffer2D pixels) + private void ReadBgra16(int width, int height, Buffer2D pixels) where TPixel : struct, IPixel { - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(this.fileHeader.Width, 2, 0)) - using (IMemoryOwner bgraRow = this.memoryAllocator.Allocate(this.fileHeader.Width)) + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) + using (IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width)) { Span bgraRowSpan = bgraRow.GetSpan(); long currentPosition = this.currentStream.Position; for (int y = 0; y < this.fileHeader.Height; y++) { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(this.fileHeader.Height - y - 1); + Span pixelSpan = pixels.GetRowSpan(height - y - 1); PixelOperations.Instance.FromBgra5551Bytes( this.configuration, row.GetSpan(), pixelSpan, - this.fileHeader.Width); + width); } // We need to set each alpha component value to fully opaque. @@ -182,38 +278,38 @@ namespace SixLabors.ImageSharp.Formats.Tga } } - private void ReadBgr24(Buffer2D pixels) + private void ReadBgr24(int width, int height, Buffer2D pixels) where TPixel : struct, IPixel { - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(this.fileHeader.Width, 3, 0)) + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) { - for (int y = 0; y < this.fileHeader.Height; y++) + for (int y = 0; y < height; y++) { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(this.fileHeader.Height - y - 1); + Span pixelSpan = pixels.GetRowSpan(height - y - 1); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row.GetSpan(), pixelSpan, - this.fileHeader.Width); + width); } } } - private void ReadBgra32(Buffer2D pixels) + private void ReadBgra32(int width, int height, Buffer2D pixels) where TPixel : struct, IPixel { - using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(this.fileHeader.Width, 4, 0)) + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) { - for (int y = 0; y < this.fileHeader.Height; y++) + for (int y = 0; y < height; y++) { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(this.fileHeader.Height - y - 1); + Span pixelSpan = pixels.GetRowSpan(height - y - 1); PixelOperations.Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), pixelSpan, - this.fileHeader.Width); + width); } } }