From 6720f10fac5c1ec73db667602c71adca97f62082 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 13 Oct 2019 13:05:08 +0200 Subject: [PATCH] Add support for encoding RLE tga images --- .../Formats/Tga/ITgaEncoderOptions.cs | 5 ++ src/ImageSharp/Formats/Tga/TgaEncoder.cs | 8 +++ src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 63 ++++++++++++++++++- 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs index a6e9871e0..ef1fecc93 100644 --- a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs +++ b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs @@ -12,5 +12,10 @@ namespace SixLabors.ImageSharp.Formats.Tga /// Gets the number of bits per pixel. /// TgaBitsPerPixel? BitsPerPixel { get; } + + /// + /// Gets a value indicating whether run length compression should be used. + /// + bool Compress { get; } } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index f5b9fa752..85b4fadfc 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -8,6 +8,9 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tga { + /// + /// Image encoder for writing an image to a stream as a targa truevision image. + /// public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions { /// @@ -15,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Tga /// public TgaBitsPerPixel? BitsPerPixel { get; set; } + /// + /// Gets or sets a value indicating whether run length compression should be used. + /// + public bool Compress { get; set; } + /// public void Encode(Image image, Stream stream) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index ddd430b05..b48c35e05 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -32,6 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Tga /// private TgaBitsPerPixel? bitsPerPixel; + /// + /// Indicates if run length compression should be used. + /// + private readonly bool useCompression; + /// /// Initializes a new instance of the class. /// @@ -41,6 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; + this.useCompression = options.Compress; } public void Encode(Image image, Stream stream) @@ -54,10 +60,11 @@ namespace SixLabors.ImageSharp.Formats.Tga TgaMetadata tgaMetadata = metadata.GetFormatMetadata(TgaFormat.Instance); this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel; + TgaImageType imageType = this.useCompression ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; var fileHeader = new TgaFileHeader( idLength: 0, colorMapType: 0, - imageType: TgaImageType.TrueColor, + imageType: imageType, cMapStart: 0, cMapLength: 0, cMapDepth: 0, @@ -77,7 +84,14 @@ namespace SixLabors.ImageSharp.Formats.Tga stream.Write(buffer, 0, TgaFileHeader.Size); - this.WriteImage(stream, image.Frames.RootFrame); + if (this.useCompression) + { + this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame); + } + else + { + this.WriteImage(stream, image.Frames.RootFrame); + } stream.Flush(); } @@ -114,6 +128,51 @@ namespace SixLabors.ImageSharp.Formats.Tga } } + private void WriteRunLengthEndcodedImage(Stream stream, ImageFrame image) + where TPixel : struct, IPixel + { + Rgba32 color = default; + Buffer2D pixels = image.PixelBuffer; + Span pixelSpan = pixels.Span; + int totalPixels = image.Width * image.Height; + int encodedPixels = 0; + while (encodedPixels < totalPixels) + { + TPixel currentPixel = pixelSpan[encodedPixels]; + currentPixel.ToRgba32(ref color); + byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels)); + stream.WriteByte((byte)(equalPixelCount | 128)); + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + encodedPixels += equalPixelCount + 1; + } + } + + private byte FindEqualPixels(Span pixelSpan) + where TPixel : struct, IPixel + { + int idx = 0; + byte equalPixelCount = 0; + while (equalPixelCount < 127 && idx < pixelSpan.Length - 1) + { + TPixel currentPixel = pixelSpan[idx]; + TPixel nextPixel = pixelSpan[idx + 1]; + if (currentPixel.Equals(nextPixel)) + { + equalPixelCount++; + } + else + { + return equalPixelCount; + } + + idx++; + } + + return equalPixelCount; + } + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); ///