From 137ebbba0654b91000cbfaa665467591c1f9df99 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 19 Mar 2026 19:02:47 +0100 Subject: [PATCH] Add Exr compressor factory --- .../Compression/Compressors/NoCompressor.cs | 8 ++++ .../Compression/Compressors/ZipCompressor.cs | 47 +++++++++++++++++++ .../Decompressors/B44Compression.cs | 4 +- .../Decompressors/NoneExrCompression.cs | 6 +-- .../Decompressors/RunLengthCompression.cs | 6 +-- .../Decompressors/ZipExrCompression.cs | 6 +-- .../Exr/Compression/ExrBaseCompression.cs | 23 +++++++-- .../Exr/Compression/ExrBaseDecompressor.cs | 4 +- .../Exr/Compression/ExrCompressorFactory.cs | 26 ++++++++++ .../Exr/Compression/ExrDecompressorFactory.cs | 23 ++++----- .../ExrCompression.cs} | 7 ++- .../Formats/Exr/ExrBaseCompressor.cs | 43 +++++++++++++++++ src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 26 +++++----- src/ImageSharp/Formats/Exr/ExrEncoder.cs | 5 ++ src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 5 +- .../Formats/Exr/ExrHeaderAttributes.cs | 5 +- src/ImageSharp/Formats/Exr/ExrThrowHelper.cs | 11 +++++ 17 files changed, 206 insertions(+), 49 deletions(-) create mode 100644 src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs create mode 100644 src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs create mode 100644 src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs rename src/ImageSharp/Formats/Exr/{Compression/ExrCompressionType.cs => Constants/ExrCompression.cs} (91%) create mode 100644 src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs new file mode 100644 index 0000000000..9a9bc8fff6 --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; + +internal class NoCompressor +{ +} diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs new file mode 100644 index 0000000000..d205c2f1a3 --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; + +internal class ZipCompressor : ExrBaseCompressor +{ + private readonly DeflateCompressionLevel compressionLevel; + + private readonly MemoryStream memoryStream = new(); + + public ZipCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel) + : base(output, allocator, bytesPerBlock) + => this.compressionLevel = compressionLevel; + + /// + public override ExrCompression Method => ExrCompression.Zip; + + /// + public override void Initialize(int rowsPerStrip) + { + } + + /// + public override void CompressStrip(Span rows, int height) + { + this.memoryStream.Seek(0, SeekOrigin.Begin); + using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) + { + stream.Write(rows); + stream.Flush(); + } + + int size = (int)this.memoryStream.Position; + byte[] buffer = this.memoryStream.GetBuffer(); + this.Output.Write(buffer, 0, size); + } + + /// + protected override void Dispose(bool disposing) + { + } +} diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs index b8cb468142..d0f9eb6efe 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs @@ -24,8 +24,8 @@ internal class B44Compression : ExrBaseDecompressor private IMemoryOwner tmpBuffer; - public B44Compression(MemoryAllocator allocator, uint uncompressedBytes, int width, int height, uint rowsPerBlock, int channelCount) - : base(allocator, uncompressedBytes) + public B44Compression(MemoryAllocator allocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) + : base(allocator, bytesPerBlock) { this.width = width; this.height = height; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index 6b36dffa2b..75bf67cb24 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -8,13 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; internal class NoneExrCompression : ExrBaseDecompressor { - public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) + public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) { } public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) - => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes)); + => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); protected override void Dispose(bool disposing) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs index f1c7bb759b..15538a813e 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs @@ -19,7 +19,7 @@ internal class RunLengthCompression : ExrBaseDecompressor public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { Span uncompressed = this.tmpBuffer.GetSpan(); - int maxLength = (int)this.UncompressedBytes; + int maxLength = (int)this.BytesPerBlock; int offset = 0; while (compressedBytes > 0) { @@ -63,8 +63,8 @@ internal class RunLengthCompression : ExrBaseDecompressor } } - Reconstruct(uncompressed, this.UncompressedBytes); - Interleave(uncompressed, this.UncompressedBytes, buffer); + Reconstruct(uncompressed, this.BytesPerBlock); + Interleave(uncompressed, this.BytesPerBlock, buffer); } private static byte ReadNextByte(BufferedReadStream stream) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 4d9b42732f..95d4c03137 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -13,8 +13,8 @@ internal class ZipExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { @@ -28,7 +28,7 @@ internal class ZipExrCompression : ExrBaseDecompressor int left = (int)(compressedBytes - (stream.Position - pos)); return left > 0 ? left : 0; }); - inflateStream.AllocateNewBytes((int)this.UncompressedBytes, true); + inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true); DeflateStream dataStream = inflateStream.CompressedStream!; int totalRead = 0; diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index 3dab1006ed..e510135c2c 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -9,10 +9,10 @@ internal abstract class ExrBaseCompression : IDisposable { private bool isDisposed; - protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow) + protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock) { this.Allocator = allocator; - this.UncompressedBytes = bytePerRow; + this.BytesPerBlock = bytesPerBlock; } /// @@ -21,9 +21,24 @@ internal abstract class ExrBaseCompression : IDisposable protected MemoryAllocator Allocator { get; } /// - /// Gets the uncompressed bytes. + /// Gets the bits per pixel. /// - public uint UncompressedBytes { get; } + public int BitsPerPixel { get; } + + /// + /// Gets the bytes per row. + /// + public int BytesPerRow { get; } + + /// + /// Gets the uncompressed bytes per block. + /// + public uint BytesPerBlock { get; } + + /// + /// Gets the image width. + /// + public int Width { get; } /// public void Dispose() diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 12edcc1046..0dd7d01108 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -8,8 +8,8 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal abstract class ExrBaseDecompressor : ExrBaseCompression { - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow) - : base(allocator, bytePerRow) + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs new file mode 100644 index 0000000000..fc403be3a0 --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression; + +internal static class ExrCompressorFactory +{ + public static ExrBaseCompressor Create( + ExrCompression method, + Stream output, + MemoryAllocator allocator, + int width, + DeflateCompressionLevel compressionLevel) + { + switch (method) + { + default: + throw ExrThrowHelper.NotSupportedCompressor(method.ToString()); + } + } +} diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index a293787fad..6764a2a130 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -2,26 +2,27 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal static class ExrDecompressorFactory { - public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes, int width, int height, uint rowsPerBlock, int channelCount) + public static ExrBaseDecompressor Create(ExrCompression method, MemoryAllocator memoryAllocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) { switch (method) { - case ExrCompressionType.None: - return new NoneExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zips: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zip: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.RunLengthEncoded: - return new RunLengthCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.B44: - return new B44Compression(memoryAllocator, uncompressedBytes, width, height, rowsPerBlock, channelCount); + case ExrCompression.None: + return new NoneExrCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.Zips: + return new ZipExrCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.Zip: + return new ZipExrCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.RunLengthEncoded: + return new RunLengthCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.B44: + return new B44Compression(memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrCompression.cs similarity index 91% rename from src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs rename to src/ImageSharp/Formats/Exr/Constants/ExrCompression.cs index a7b584ed4c..d0964bf33b 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrCompression.cs @@ -1,9 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Exr.Compression; +namespace SixLabors.ImageSharp.Formats.Exr.Constants; -internal enum ExrCompressionType +/// +/// Enumeration representing the compression formats defined by the EXR file-format. +/// +public enum ExrCompression { /// /// Pixel data is not compressed. diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs new file mode 100644 index 0000000000..c2a752c614 --- /dev/null +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression; + +internal abstract class ExrBaseCompressor : ExrBaseCompression +{ + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed image to. + /// The memory allocator. + /// Bytes per block. + protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) + => this.Output = output; + + /// + /// Gets the compression method to use. + /// + public abstract ExrCompression Method { get; } + + /// + /// Gets the output stream to write the compressed image to. + /// + public Stream Output { get; } + + /// + /// Does any initialization required for the compression. + /// + /// The number of rows per strip. + public abstract void Initialize(int rowsPerStrip); + + /// + /// Compresses a strip of the image. + /// + /// Image rows to compress. + /// Image height. + public abstract void CompressStrip(Span rows, int height); +} diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 38e9c3ec90..7f53d8a24b 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -79,7 +79,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// /// Gets or sets the compression method. /// - private ExrCompressionType Compression { get; set; } + private ExrCompression Compression { get; set; } /// /// Gets or sets the image data type, either RGB, RGBA or gray. @@ -453,7 +453,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore IList channels = null; ExrBox2i? dataWindow = null; - ExrCompressionType? compression = null; + ExrCompression? compression = null; ExrBox2i? displayWindow = null; ExrLineOrder? lineOrder = null; float? aspectRatio = null; @@ -471,7 +471,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore channels = this.ReadChannelList(stream, attribute.Length); break; case ExrConstants.AttributeNames.Compression: - compression = (ExrCompressionType)stream.ReadByte(); + compression = (ExrCompression)stream.ReadByte(); break; case ExrConstants.AttributeNames.DataWindow: dataWindow = this.ReadBoxInteger(stream); @@ -648,11 +648,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore { switch (this.Compression) { - case ExrCompressionType.None: - case ExrCompressionType.Zip: - case ExrCompressionType.Zips: - case ExrCompressionType.RunLengthEncoded: - case ExrCompressionType.B44: + case ExrCompression.None: + case ExrCompression.Zip: + case ExrCompression.Zips: + case ExrCompression.RunLengthEncoded: + case ExrCompression.B44: return true; } @@ -757,12 +757,12 @@ internal sealed class ExrDecoderCore : ImageDecoderCore { switch (this.Compression) { - case ExrCompressionType.Zip: - case ExrCompressionType.Pxr24: + case ExrCompression.Zip: + case ExrCompression.Pxr24: return 16; - case ExrCompressionType.B44: - case ExrCompressionType.B44A: - case ExrCompressionType.Piz: + case ExrCompression.B44: + case ExrCompression.B44A: + case ExrCompression.Piz: return 32; default: diff --git a/src/ImageSharp/Formats/Exr/ExrEncoder.cs b/src/ImageSharp/Formats/Exr/ExrEncoder.cs index 15e10ba2fa..2ea1b91161 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoder.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoder.cs @@ -15,6 +15,11 @@ public sealed class ExrEncoder : ImageEncoder /// public ExrPixelType? PixelType { get; set; } + /// + /// Gets the compression type to use. + /// + public ExrCompression? Compression { get; init; } + /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 400915d220..ff5287b752 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -6,7 +6,6 @@ using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Channels; -using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -77,7 +76,7 @@ internal sealed class ExrEncoderCore this.pixelType ??= exrMetadata.PixelType; int width = image.Width; int height = image.Height; - ExrCompressionType compression = ExrCompressionType.None; + ExrCompression compression = ExrCompression.None; float aspectRatio = 1.0f; ExrBox2i dataWindow = new(0, 0, width - 1, height - 1); ExrBox2i displayWindow = new(0, 0, width - 1, height - 1); @@ -311,7 +310,7 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } - private void WriteCompression(Stream stream, ExrCompressionType compression) + private void WriteCompression(Stream stream, ExrCompression compression) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); stream.WriteByte((byte)compression); diff --git a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs index 35611bb6eb..afda62bf92 100644 --- a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; namespace SixLabors.ImageSharp.Formats.Exr; @@ -10,7 +9,7 @@ internal class ExrHeaderAttributes { public ExrHeaderAttributes( IList channels, - ExrCompressionType compression, + ExrCompression compression, ExrBox2i dataWindow, ExrBox2i displayWindow, ExrLineOrder lineOrder, @@ -36,7 +35,7 @@ internal class ExrHeaderAttributes public IList Channels { get; set; } - public ExrCompressionType Compression { get; set; } + public ExrCompression Compression { get; set; } public ExrBox2i DataWindow { get; set; } diff --git a/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs b/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs index f668dcfefd..51419ec95c 100644 --- a/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; + namespace SixLabors.ImageSharp.Formats.Exr; /// @@ -8,15 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Exr; /// internal static class ExrThrowHelper { + [DoesNotReturn] public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); + [DoesNotReturn] public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + [DoesNotReturn] public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); + [DoesNotReturn] public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); + [DoesNotReturn] public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); + [DoesNotReturn] public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); + + [DoesNotReturn] + public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); }