diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs similarity index 85% rename from src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs rename to src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs index 162893b11b..508a5b4ac7 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs @@ -10,11 +10,11 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors { - internal class ZipsExrCompression : ExrBaseDecompressor + internal class ZipExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - public ZipsExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) @@ -44,8 +44,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors totalRead += bytesRead; } - Reconstruct(uncompressed, this.UncompressedBytes); - Interleave(uncompressed, this.UncompressedBytes, buffer); + Reconstruct(uncompressed, (uint)totalRead); + Interleave(uncompressed, (uint)totalRead, buffer); } protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs index ebbd3416fe..8ba255d41c 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -8,16 +8,18 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression { internal static class ExrDecompressorFactory { - public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint bytesPerRow) + public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes) { switch (method) { case ExrCompressionType.None: - return new NoneExrCompression(memoryAllocator, bytesPerRow); + return new NoneExrCompression(memoryAllocator, uncompressedBytes); case ExrCompressionType.Zips: - return new ZipsExrCompression(memoryAllocator, bytesPerRow); + return new ZipExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.Zip: + return new ZipExrCompression(memoryAllocator, uncompressedBytes); case ExrCompressionType.RunLengthEncoded: - return new RunLengthCompression(memoryAllocator, bytesPerRow); + return new RunLengthCompression(memoryAllocator, uncompressedBytes); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 8674aa12c4..aadbe425e5 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -9,7 +9,6 @@ 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; @@ -77,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { this.ReadExrHeader(stream); - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.RunLengthEncoded) + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) { ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } @@ -109,19 +108,21 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { bool hasAlpha = this.HasAlpha(); uint bytesPerRow = this.CalculateBytesPerRow(); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerRow); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); TPixel color = default; - for (int y = 0; y < this.Height; y++) + for (uint y = 0; y < this.Height; y += rowsPerBlock) { stream.Read(this.buffer, 0, 8); ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); @@ -129,100 +130,102 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Position = (long)rowOffset; stream.Read(this.buffer, 0, 4); - uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); stream.Read(this.buffer, 0, 4); uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); int offset = 0; - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++) { - ExrChannelInfo channel = this.Channels[channelIdx]; - switch (channel.ChannelName) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - case ExrConstants.ChannelNames.Red: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Blue: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Green: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Alpha: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData); - break; - } - - break; - - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - break; + ExrChannelInfo channel = this.Channels[channelIdx]; + switch (channel.ChannelName) + { + case ExrConstants.ChannelNames.Red: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Blue: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Green: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Alpha: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData); + break; + } + + break; + + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + break; + } } - } - - stream.Position = nextRowOffsetPosition; - if (hasAlpha) - { - for (int x = 0; x < this.Width; x++) + if (hasAlpha) { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } - } - else - { - for (int x = 0; x < this.Width; x++) + else { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } } + + stream.Position = nextRowOffsetPosition; } } @@ -231,19 +234,21 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { bool hasAlpha = this.HasAlpha(); uint bytesPerRow = this.CalculateBytesPerRow(); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerRow); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); TPixel color = default; - for (int y = 0; y < this.Height; y++) + for (uint y = 0; y < this.Height; y += rowsPerBlock) { stream.Read(this.buffer, 0, 8); ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); @@ -251,86 +256,88 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Position = (long)rowOffset; stream.Read(this.buffer, 0, 4); - uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); stream.Read(this.buffer, 0, 4); uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); int offset = 0; - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++) { - ExrChannelInfo channel = this.Channels[channelIdx]; - switch (channel.ChannelName) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - case ExrConstants.ChannelNames.Red: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Blue: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Green: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Alpha: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData); - break; - } - - break; - - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - break; + ExrChannelInfo channel = this.Channels[channelIdx]; + switch (channel.ChannelName) + { + case ExrConstants.ChannelNames.Red: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Blue: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Green: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Alpha: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData); + break; + } + + break; + + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + break; + } } - } - stream.Position = nextRowOffsetPosition; + stream.Position = nextRowOffsetPosition; - if (hasAlpha) - { - for (int x = 0; x < this.Width; x++) + if (hasAlpha) { - var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } - } - else - { - for (int x = 0; x < this.Width; x++) + else { - var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } } } @@ -339,10 +346,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData) { int offset = 0; - ushort shortValue = 0; for (int x = 0; x < this.Width; x++) { - shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); + ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); channelData[x] = HalfTypeHelper.Unpack(shortValue); offset += 2; } @@ -353,10 +359,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData) { int offset = 0; - int intValue = 0; for (int x = 0; x < this.Width; x++) { - intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); channelData[x] = Unsafe.As(ref intValue); offset += 4; } @@ -668,5 +673,22 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return bytesPerRow; } + + private uint RowsPerBlock() + { + switch (this.Compression) + { + case ExrCompressionType.Zip: + case ExrCompressionType.Pxr24: + return 16; + case ExrCompressionType.B44: + case ExrCompressionType.B44A: + case ExrCompressionType.Piz: + return 32; + + default: + return 1; + } + } } }