From 85501f3c007c48af54b54084a2245ab014ef94fc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 21 Jan 2022 21:38:44 +0100 Subject: [PATCH] Add support for decoding RLE exr images --- .../Compressors/RunLengthCompression.cs | 94 +++++++++++++++++++ .../Compressors/ZipsExrCompression.cs | 32 +------ .../Compression/ExrBaseDecompressor.cs | 23 +++++ .../Compression/ExrDecompressorFactory.cs | 2 + .../Formats/OpenExr/ExrDecoderCore.cs | 3 +- 5 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs new file mode 100644 index 0000000000..de1bf52328 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +{ + internal class RunLengthCompression : ExrBaseDecompressor + { + private readonly IMemoryOwner tmpBuffer; + + public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + int maxLength = (int)this.UncompressedBytes; + int offset = 0; + while (compressedBytes > 0) + { + byte nextByte = ReadNextByte(stream); + + sbyte input = (sbyte)nextByte; + if (input < 0) + { + int count = -input; + compressedBytes -= (uint)(count + 1); + + if ((maxLength -= count) < 0) + { + return; + } + + // Check the input buffer is big enough to contain 'count' bytes of remaining data. + if (compressedBytes < 0) + { + return; + } + + for (int i = 0; i < count; i++) + { + uncompressed[offset + i] = ReadNextByte(stream); + } + + offset += count; + } + else + { + int count = input; + byte value = ReadNextByte(stream); + compressedBytes -= 2; + + if ((maxLength -= count + 1) < 0) + { + return; + } + + // Check the input buffer is big enough to contain byte to be duplicated. + if (compressedBytes < 0) + { + return; + } + + for (int i = 0; i < count + 1; i++) + { + uncompressed[offset + i] = value; + } + + offset += count + 1; + } + } + + Reconstruct(uncompressed, this.UncompressedBytes); + Interleave(uncompressed, this.UncompressedBytes, buffer); + } + + private static byte ReadNextByte(BufferedReadStream stream) + { + int nextByte = stream.ReadByte(); + if (nextByte == -1) + { + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!"); + } + + return (byte)nextByte; + } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs index 3f3db53279..162893b11b 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { + Span uncompressed = this.tmpBuffer.GetSpan(); + long pos = stream.Position; using var deframeStream = new ZlibInflateStream( stream, @@ -30,11 +32,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); DeflateStream dataStream = deframeStream.CompressedStream; - Span tmp = this.tmpBuffer.GetSpan(); int totalRead = 0; while (totalRead < buffer.Length) { - int bytesRead = dataStream.Read(tmp, totalRead, buffer.Length - totalRead); + int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); if (bytesRead <= 0) { break; @@ -43,31 +44,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors totalRead += bytesRead; } - Reconstruct(tmp, this.UncompressedBytes); - Interleave(tmp, this.UncompressedBytes, buffer); - } - - private static void Reconstruct(Span buffer, uint unCompressedBytes) - { - int offset = 0; - for (int i = 0; i < unCompressedBytes - 1; i++) - { - byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); - buffer[offset + 1] = d; - offset++; - } - } - - private static void Interleave(Span source, uint unCompressedBytes, Span output) - { - int sourceOffset = 0; - int offset0 = 0; - int offset1 = (int)((unCompressedBytes + 1) / 2); - while (sourceOffset < unCompressedBytes) - { - output[sourceOffset++] = source[offset0++]; - output[sourceOffset++] = source[offset1++]; - } + Reconstruct(uncompressed, this.UncompressedBytes); + Interleave(uncompressed, this.UncompressedBytes, buffer); } protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs index 1eb31ff528..f8b811e502 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs @@ -15,5 +15,28 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression } public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); + + protected static void Reconstruct(Span buffer, uint unCompressedBytes) + { + int offset = 0; + for (int i = 0; i < unCompressedBytes - 1; i++) + { + byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); + buffer[offset + 1] = d; + offset++; + } + } + + protected static void Interleave(Span source, uint unCompressedBytes, Span output) + { + int sourceOffset = 0; + int offset0 = 0; + int offset1 = (int)((unCompressedBytes + 1) / 2); + while (sourceOffset < unCompressedBytes) + { + output[sourceOffset++] = source[offset0++]; + output[sourceOffset++] = source[offset1++]; + } + } } } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs index 7ce46b4af5..ebbd3416fe 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression return new NoneExrCompression(memoryAllocator, bytesPerRow); case ExrCompressionType.Zips: return new ZipsExrCompression(memoryAllocator, bytesPerRow); + case ExrCompressionType.RunLengthEncoded: + return new RunLengthCompression(memoryAllocator, bytesPerRow); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index b5cae391df..8674aa12c4 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading; using SixLabors.ImageSharp.Formats.OpenExr.Compression; +using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -76,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { this.ReadExrHeader(stream); - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips) + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.RunLengthEncoded) { ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); }