From f6cce786ea0f8d66b42acc122304443e454f30b5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 8 Oct 2019 19:13:01 +0200 Subject: [PATCH] Add decoding of 24bit RLE tga images --- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 84 +++++++++++++++++++- src/ImageSharp/Formats/Tga/TgaImageType.cs | 24 +++++- src/ImageSharp/Formats/Tga/TgaThrowHelper.cs | 10 +++ 3 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 92811fa46d..692876cea3 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -82,7 +82,15 @@ namespace SixLabors.ImageSharp.Formats.Tga break; case 24: - this.ReadBgr24(pixels); + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle24(pixels, this.fileHeader.Width, this.fileHeader.Height); + } + else + { + this.ReadBgr24(pixels); + } + break; case 32: @@ -181,6 +189,80 @@ namespace SixLabors.ImageSharp.Formats.Tga } } + private void ReadRle24(Buffer2D pixels, int width, int height) + where TPixel : struct, IPixel + { + TPixel color = default; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean)) + { + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle24(width, bufferSpan); + for (int y = 0; y < height; y++) + { + Span pixelRow = pixels.GetRowSpan(this.fileHeader.Height - y - 1); + for (int x = 0; x < width; x++) + { + int idx = (y * width * 3) + (x * 3); + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + pixelRow[x] = color; + } + } + } + } + + private void UncompressRle24(int w, Span buffer) + { + int uncompressedPixels = 0; +#if NETCOREAPP2_1 + Span pixel = stackalloc byte[3]; +#else + var pixel = new byte[3]; +#endif + int totalPixels = this.fileHeader.Height * this.fileHeader.Width; + while (uncompressedPixels < totalPixels) + { + byte runLengthByte = (byte)this.currentStream.ReadByte(); + + // The high bit of the run length value is always 1, to indicate that this is a run-length encoded packet. + int highBit = runLengthByte >> 7; + if (highBit == 1) + { + int runLength = runLengthByte & 127; + if (runLength == 0) + { + // TgaThrowHelper.ThrowImageFormatException("invalid run length of zero"); + } + + this.currentStream.Read(pixel, 0, 3); + int bufferIdx = uncompressedPixels * 3; + for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) + { + buffer[bufferIdx++] = pixel[0]; + buffer[bufferIdx++] = pixel[1]; + buffer[bufferIdx++] = pixel[2]; + } + } + else + { + // Non-run-length encoded packet. + int runLength = runLengthByte; + if (runLength == 0) + { + // TgaThrowHelper.ThrowImageFormatException("invalid run length of zero"); + } + + int bufferIdx = uncompressedPixels * 3; + for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) + { + this.currentStream.Read(pixel, 0, 3); + buffer[bufferIdx++] = pixel[0]; + buffer[bufferIdx++] = pixel[1]; + buffer[bufferIdx++] = pixel[2]; + } + } + } + } + private void ReadBgra32(Buffer2D pixels) where TPixel : struct, IPixel { diff --git a/src/ImageSharp/Formats/Tga/TgaImageType.cs b/src/ImageSharp/Formats/Tga/TgaImageType.cs index d8140d5c6e..2c19a06954 100644 --- a/src/ImageSharp/Formats/Tga/TgaImageType.cs +++ b/src/ImageSharp/Formats/Tga/TgaImageType.cs @@ -1,7 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors. + ImageSharp.Formats.Tga { /// /// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color, @@ -45,4 +46,25 @@ namespace SixLabors.ImageSharp.Formats.Tga /// RleBlackAndWhite = 11, } + + /// + /// Extension methods for TgaImageType enum. + /// + public static class TgaImageTypeExtensions + { + /// + /// Checks if this tga image type is run length encoded. + /// + /// The tga image type. + /// True, if this image type is run length encoded, otherwise false. + public static bool IsRunLengthEncoded(this TgaImageType imageType) + { + if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor) + { + return true; + } + + return false; + } + } } diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs index 9e36b20539..845d009227 100644 --- a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs +++ b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs @@ -8,6 +8,16 @@ namespace SixLabors.ImageSharp.Formats.Tga { internal static class TgaThrowHelper { + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) + { + throw new ImageFormatException(errorMessage); + } + /// /// Cold path optimization for throwing -s ///