From 7b3018c3e191893fffaff38975ce7fc1c35ee95d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 13 Oct 2019 14:26:14 +0200 Subject: [PATCH] Add support for encoding rle 8, 16 and bit tga images --- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 61 +++++++++++++++- .../Formats/Tga/TgaEncoderTests.cs | 73 +++++++++++++++++++ 2 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index b48c35e05f..4a283260c5 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -3,6 +3,8 @@ using System; using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -61,6 +63,11 @@ namespace SixLabors.ImageSharp.Formats.Tga this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel; TgaImageType imageType = this.useCompression ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; + if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8) + { + imageType = this.useCompression ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite; + } + var fileHeader = new TgaFileHeader( idLength: 0, colorMapType: 0, @@ -141,10 +148,38 @@ namespace SixLabors.ImageSharp.Formats.Tga TPixel currentPixel = pixelSpan[encodedPixels]; currentPixel.ToRgba32(ref color); byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels)); + + // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. stream.WriteByte((byte)(equalPixelCount | 128)); - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); + switch (this.bitsPerPixel) + { + case TgaBitsPerPixel.Pixel8: + int luminance = GetLuminance(currentPixel); + stream.WriteByte((byte)luminance); + break; + + case TgaBitsPerPixel.Pixel16: + // TODO: this seems to be wrong + var bgra5551 = new Bgra5551(color.ToVector4()); + stream.WriteByte((byte)(bgra5551.PackedValue & 0xFF)); + stream.WriteByte((byte)(bgra5551.PackedValue & 0xFF00)); + + break; + + case TgaBitsPerPixel.Pixel24: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + break; + + case TgaBitsPerPixel.Pixel32: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + stream.WriteByte(color.A); + break; + } + encodedPixels += equalPixelCount + 1; } } @@ -270,5 +305,25 @@ namespace SixLabors.ImageSharp.Formats.Tga } } } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(TPixel sourcePixel) + where TPixel : struct, IPixel + { + Vector4 vector = sourcePixel.ToVector4(); + return GetLuminance(ref vector); + } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The vector to get the luminance from + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(ref Vector4 vector) + => (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (256 - 1)); } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs new file mode 100644 index 0000000000..a92b50516d --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tga +{ + using static TestImages.Tga; + + public class TgaEncoderTests + { + public static readonly TheoryData TgaBitsPerPixelFiles = + new TheoryData + { + { Grey, TgaBitsPerPixel.Pixel8 }, + { Bit32, TgaBitsPerPixel.Pixel32 }, + { Bit24, TgaBitsPerPixel.Pixel24 }, + { Bit16, TgaBitsPerPixel.Pixel16 }, + }; + + [Theory] + [MemberData(nameof(TgaBitsPerPixelFiles))] + public void Encode_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + { + var options = new TgaEncoder(); + + TestFile testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (Image output = Image.Load(memStream)) + { + TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance); + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); + } + } + } + } + + [Theory] + [MemberData(nameof(TgaBitsPerPixelFiles))] + public void Encode_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + { + var options = new TgaEncoder() + { + Compress = true + }; + + TestFile testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (Image output = Image.Load(memStream)) + { + TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance); + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); + } + } + } + } + } +}