/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int bytesPerScanline = context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffGray8> writer = context.GetWriter <TiffGray8>(); int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffGray8> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <byte> rowSourceSpan = sourceSpan.Slice(context.SourceReadOffset.X, context.ReadSize.Width); Span <byte> rowDestinationSpan = MemoryMarshal.AsBytes(pixelSpanHandle.GetSpan()); InvertCopy(rowSourceSpan, rowDestinationSpan); sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } TiffYCbCrConverter8 converter = _converter; int bytesPerScanline = 3 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba32> writer = context.GetWriter <TiffRgba32>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba32> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <byte> rowSourceSpan = sourceSpan.Slice(3 * context.SourceReadOffset.X, 3 * context.ReadSize.Width); Span <TiffRgba32> rowDestinationSpan = pixelSpanHandle.GetSpan(); converter.ConvertToRgba32(rowSourceSpan, rowDestinationSpan, cols); sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
public async ValueTask InvokeAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next) { MemoryPool <byte> memoryPool = context.MemoryPool ?? MemoryPool <byte> .Shared; TiffSize imageSize = context.ImageSize; int arraySize = imageSize.Width * imageSize.Height; using (IMemoryOwner <byte> pixelData = memoryPool.Rent(arraySize)) { using (var writer = new TiffMemoryPixelBufferWriter <TiffGray8>(memoryPool, pixelData.Memory, imageSize.Width, imageSize.Height)) using (TiffPixelBufferWriter <TPixel> convertedWriter = context.ConvertWriter(writer.AsPixelBufferWriter())) { await context.GetReader().ReadAsync(convertedWriter, context.CancellationToken).ConfigureAwait(false); } context.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero; context.BitsPerSample = TiffValueCollection.Single <ushort>(8); context.UncompressedData = pixelData.Memory.Slice(0, arraySize); await next.RunAsync(context).ConfigureAwait(false); context.UncompressedData = default; } TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter; if (!(ifdWriter is null)) { using (await context.LockAsync().ConfigureAwait(false)) { await ifdWriter.WriteTagAsync(TiffTag.PhotometricInterpretation, TiffValueCollection.Single((ushort)context.PhotometricInterpretation)).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.BitsPerSample, context.BitsPerSample).ConfigureAwait(false); } } }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } TiffYCbCrConverter16 converter = _converter; int skippedRowOffset = context.SourceImageSize.Width * context.SourceReadOffset.Y; int planarByteCount = sizeof(ushort) * context.SourceImageSize.Width * context.SourceImageSize.Height; ReadOnlySpan <byte> sourceSpan = context.UncompressedData.Span; ReadOnlySpan <ushort> sourceY = MemoryMarshal.Cast <byte, ushort>(sourceSpan.Slice(0, planarByteCount)); ReadOnlySpan <ushort> sourceCb = MemoryMarshal.Cast <byte, ushort>(sourceSpan.Slice(planarByteCount, planarByteCount)); ReadOnlySpan <ushort> sourceCr = MemoryMarshal.Cast <byte, ushort>(sourceSpan.Slice(2 * planarByteCount, planarByteCount)); using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; bool reverseEndiannessNeeded = context.IsLittleEndian != BitConverter.IsLittleEndian; if (reverseEndiannessNeeded) { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba64> rowDestinationSpan = pixelSpanHandle.GetSpan(); int rowOffset = skippedRowOffset + row * context.SourceImageSize.Width + context.SourceReadOffset.X; for (int col = 0; col < cols; col++) { int componentOffset = rowOffset + col; rowDestinationSpan[col] = converter.ConvertToRgba64(BinaryPrimitives.ReverseEndianness(sourceY[componentOffset]), BinaryPrimitives.ReverseEndianness(sourceCb[componentOffset]), BinaryPrimitives.ReverseEndianness(sourceCr[componentOffset])); } } } else { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba64> rowDestinationSpan = pixelSpanHandle.GetSpan(); int rowOffset = skippedRowOffset + row * context.SourceImageSize.Width + context.SourceReadOffset.X; for (int col = 0; col < cols; col++) { int componentOffset = rowOffset + col; rowDestinationSpan[col] = converter.ConvertToRgba64(sourceY[componentOffset], sourceCb[componentOffset], sourceCr[componentOffset]); } } } return(next.RunAsync(context)); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int bitCount = _bitCount; bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; int bytesPerScanline = (context.SourceImageSize.Width * bitCount + 7) / 8; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffGray8> writer = context.GetWriter <TiffGray8>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffGray8> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffGray8> pixelSpan = pixelSpanHandle.GetSpan(); var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst); bitReader.Advance(context.SourceReadOffset.X * bitCount); if (bitCount * 2 >= 8) { // Fast path for bits >= 4 for (int col = 0; col < cols; col++) { uint value = bitReader.Read(bitCount); value = FastExpandBits(value, bitCount, 8); pixelSpan[col] = new TiffGray8((byte)value); } } else { // Slow path for (int col = 0; col < cols; col++) { uint value = bitReader.Read(bitCount); value = ExpandBits(value, bitCount, 8); pixelSpan[col] = new TiffGray8((byte)value); } } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
public void TestCrop() { ITiffPixelBuffer <TiffGray8> pixelBuffer = PixelBufferTests.InitializePixelBuffer(out TiffGray8[] pixels); var writer = new TiffPixelBufferWriterAdapter <TiffGray8>(pixelBuffer); TiffPixelBufferWriter <TiffGray8> structWriter = writer.AsPixelBufferWriter(); Assert.Equal(3, structWriter.Width); Assert.Equal(2, structWriter.Height); Assert.False(structWriter.IsEmpty); Assert.Throws <ArgumentOutOfRangeException>("offset", () => structWriter.Crop(new TiffPoint(4, 0))); Assert.Throws <ArgumentOutOfRangeException>("offset", () => structWriter.Crop(new TiffPoint(0, 3))); Assert.Throws <ArgumentOutOfRangeException>("offset", () => structWriter.Crop(new TiffPoint(4, 3))); Assert.Throws <ArgumentOutOfRangeException>("size", () => structWriter.Crop(new TiffPoint(0, 0), new TiffSize(4, 1))); Assert.Throws <ArgumentOutOfRangeException>("size", () => structWriter.Crop(new TiffPoint(0, 0), new TiffSize(1, 3))); Assert.Throws <ArgumentOutOfRangeException>("size", () => structWriter.Crop(new TiffPoint(0, 0), new TiffSize(4, 3))); structWriter = writer.Crop(new TiffPoint(1, 1), new TiffSize(1, 1)); Assert.Equal(1, structWriter.Width); Assert.Equal(1, structWriter.Height); structWriter = writer.AsPixelBufferWriter().Crop(new TiffPoint(1, 1), new TiffSize(1, 1)); Assert.Equal(1, structWriter.Width); Assert.Equal(1, structWriter.Height); structWriter = writer.Crop(new TiffPoint(1, 1)); Assert.Equal(2, structWriter.Width); Assert.Equal(1, structWriter.Height); structWriter = writer.AsPixelBufferWriter().Crop(new TiffPoint(1, 1)); Assert.Equal(2, structWriter.Width); Assert.Equal(1, structWriter.Height); Assert.Throws <ArgumentOutOfRangeException>("rowIndex", () => structWriter.GetRowSpan(-1, 0, 1)); Assert.Throws <ArgumentOutOfRangeException>("rowIndex", () => structWriter.GetRowSpan(2, 0, 1)); Assert.Throws <ArgumentOutOfRangeException>("start", () => structWriter.GetRowSpan(0, -1, 1)); Assert.Throws <ArgumentOutOfRangeException>("start", () => structWriter.GetRowSpan(0, 3, 1)); Assert.Throws <ArgumentOutOfRangeException>("length", () => structWriter.GetRowSpan(0, 1, -1)); Assert.Throws <ArgumentOutOfRangeException>("length", () => structWriter.GetRowSpan(0, 1, 2)); Assert.Throws <ArgumentOutOfRangeException>("colIndex", () => structWriter.GetColumnSpan(-1, 0, 1)); Assert.Throws <ArgumentOutOfRangeException>("colIndex", () => structWriter.GetColumnSpan(2, 0, 1)); Assert.Throws <ArgumentOutOfRangeException>("start", () => structWriter.GetColumnSpan(0, -1, 1)); Assert.Throws <ArgumentOutOfRangeException>("start", () => structWriter.GetColumnSpan(0, 2, 1)); Assert.Throws <ArgumentOutOfRangeException>("length", () => structWriter.GetColumnSpan(0, 1, -1)); Assert.Throws <ArgumentOutOfRangeException>("length", () => structWriter.GetColumnSpan(0, 1, 2)); ITiffPixelBufferWriter <TiffGray8> writer2 = TiffPixelBufferUnsafeMarshal.GetBuffer(structWriter, out TiffPoint offset, out TiffSize size); Assert.True(ReferenceEquals(writer, writer2)); Assert.Equal(1, offset.X); Assert.Equal(1, offset.Y); Assert.Equal(2, size.Width); Assert.Equal(1, size.Height); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } TiffYCbCrConverter16 converter = _converter; int elementsPerScanline = 3 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * elementsPerScanline * sizeof(ushort)); Span <ushort> sourceSpan = MemoryMarshal.Cast <byte, ushort>(source.Span); using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; bool reverseEndiannessNeeded = context.IsLittleEndian != BitConverter.IsLittleEndian; if (reverseEndiannessNeeded) { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <ushort> rowSourceSpan = sourceSpan.Slice(3 * context.SourceReadOffset.X, 3 * context.ReadSize.Width); Span <TiffRgba64> rowDestinationSpan = pixelSpanHandle.GetSpan(); for (int i = 0; i < rowSourceSpan.Length; i++) { rowSourceSpan[i] = BinaryPrimitives.ReverseEndianness(rowSourceSpan[i]); } converter.ConvertToRgba64(rowSourceSpan, rowDestinationSpan, cols); sourceSpan = sourceSpan.Slice(elementsPerScanline); } } else { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <ushort> rowSourceSpan = sourceSpan.Slice(3 * context.SourceReadOffset.X, 3 * context.ReadSize.Width); Span <TiffRgba64> rowDestinationSpan = pixelSpanHandle.GetSpan(); converter.ConvertToRgba64(rowSourceSpan, rowDestinationSpan, cols); sourceSpan = sourceSpan.Slice(elementsPerScanline); } } return(next.RunAsync(context)); }
public override TiffPixelBufferWriter <TPixel> GetWriter <TPixel>() { TiffPixelBufferWriter <TPixel> writer = InnerContext.GetWriter <TPixel>(); if (_isFlipOrOrientation) { return(new TiffOrientedPixelBufferWriter <TPixel>(writer, _flipLeftRigt, _flipTopBottom).AsPixelBufferWriter()); } return(new TiffFlippedPixelBufferWriter <TPixel>(writer, _flipLeftRigt, _flipTopBottom).AsPixelBufferWriter()); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int bytesPerScanline = 8 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <TiffCmyk64> cmykSourceSpan = MemoryMarshal.Cast <byte, TiffCmyk64>(source.Span); using TiffPixelBufferWriter <TiffCmyk64> writer = context.GetWriter <TiffCmyk64>(); int rows = context.ReadSize.Height; if (context.IsLittleEndian == BitConverter.IsLittleEndian) { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffCmyk64> pixelSpanHandle = writer.GetRowSpan(row); cmykSourceSpan.Slice(context.SourceReadOffset.X, context.ReadSize.Width).CopyTo(pixelSpanHandle.GetSpan()); cmykSourceSpan = cmykSourceSpan.Slice(context.SourceImageSize.Width); } } else { int cols = context.ReadSize.Width; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffCmyk64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffCmyk64> rowDestinationSpan = pixelSpanHandle.GetSpan(); for (int col = 0; col < cols; col++) { TiffCmyk64 cmyk = cmykSourceSpan[col]; cmyk.C = BinaryPrimitives.ReverseEndianness(cmyk.C); cmyk.M = BinaryPrimitives.ReverseEndianness(cmyk.M); cmyk.Y = BinaryPrimitives.ReverseEndianness(cmyk.Y); cmyk.K = BinaryPrimitives.ReverseEndianness(cmyk.K); rowDestinationSpan[col] = cmyk; } cmykSourceSpan = cmykSourceSpan.Slice(context.SourceImageSize.Width); } } return(next.RunAsync(context)); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } // Colormap array is read using TiffTagReader, its elements should be in machine-endian. ushort[] colorMap = _colorMap; int bitCount = _bitCount; bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; int bytesPerScanline = (context.SourceImageSize.Width * bitCount + 7) / 8; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; TiffRgba64 pixel = default; pixel.A = ushort.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba64> pixelSpan = pixelSpanHandle.GetSpan(); var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst); bitReader.Advance(context.SourceReadOffset.X * bitCount); for (int col = 0; col < cols; col++) { uint offset = bitReader.Read(bitCount); pixel.R = colorMap[offset]; pixel.G = colorMap[SingleColorCount + offset]; pixel.B = colorMap[2 * SingleColorCount + offset]; pixelSpan[col] = pixel; } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
public ValueTask ReadAsync(TiffPoint offset, TiffPixelBufferWriter <TPixel> destination, CancellationToken cancellationToken) { if (_flipLeftRight) { offset = new TiffPoint(Width - offset.X - destination.Width, offset.Y); } if (_flipTopBottom) { offset = new TiffPoint(offset.X, Height - offset.Y - destination.Height); } destination = new TiffFlippedPixelBufferWriter <TPixel>(destination, _flipLeftRight, _flipTopBottom).AsPixelBufferWriter(); return(_reader.ReadAsync(offset, destination, cancellationToken)); }
/// <summary> /// Converts pixel buffer writer of any pixel format <typeparamref name="TBuffer"/> into <see cref="TiffPixelBufferWriter{TPixel}"/>. /// </summary> /// <typeparam name="TBuffer">The specified pixel type.</typeparam> /// <param name="writer">The writer to be converted.</param> /// <returns>The converted writer.</returns> public override TiffPixelBufferWriter <TPixel> ConvertWriter <TBuffer>(TiffPixelBufferWriter <TBuffer> writer) { ITiffPixelConverterFactory?pixelConverterFactory = PixelConverterFactory; if (pixelConverterFactory is null) { throw new InvalidOperationException("Failed to acquire PixelConverterFactory"); } ITiffPixelBufferWriter <TBuffer> innerWriter = TiffPixelBufferUnsafeMarshal.GetBuffer(writer, out TiffPoint offset, out TiffSize size); ITiffPixelBufferWriter <TPixel> converted = pixelConverterFactory.CreateConverter <TPixel, TBuffer>(innerWriter); return(converted.Crop(offset, size)); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } // Colormap array is read using TiffTagReader, its elements should be in machine-endian. ushort[] colorMap = _colorMap; int bytesPerScanline = context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; TiffRgba64 pixel = default; pixel.A = ushort.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <byte> rowSourceSpan = sourceSpan.Slice(context.SourceReadOffset.X, context.ReadSize.Width); Span <TiffRgba64> rowDestinationSpan = pixelSpanHandle.GetSpan(); for (int i = 0; i < rowSourceSpan.Length; i++) { int offset = rowSourceSpan[i]; pixel.R = colorMap[offset]; pixel.G = colorMap[SingleColorCount + offset]; pixel.B = colorMap[2 * SingleColorCount + offset]; rowDestinationSpan[i] = pixel; } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int bytesPerScanline = context.SourceImageSize.Width * 2; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffGray16> writer = context.GetWriter <TiffGray16>(); bool reverseEndiannessNeeded = context.IsLittleEndian != BitConverter.IsLittleEndian; int rows = context.ReadSize.Height; if (reverseEndiannessNeeded) { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffGray16> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <ushort> scanline = MemoryMarshal.Cast <byte, ushort>(sourceSpan).Slice(context.SourceReadOffset.X, context.ReadSize.Width); Span <ushort> destination16 = MemoryMarshal.Cast <TiffGray16, ushort>(pixelSpanHandle.GetSpan()); for (int i = 0; i < scanline.Length; i++) { destination16[i] = BinaryPrimitives.ReverseEndianness(scanline[i]); } sourceSpan = sourceSpan.Slice(bytesPerScanline); } } else { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffGray16> pixelSpanHandle = writer.GetRowSpan(row); Span <byte> rowDestinationSpan = MemoryMarshal.AsBytes(pixelSpanHandle.GetSpan()); sourceSpan.Slice(sizeof(ushort) * context.SourceReadOffset.X, sizeof(ushort) * context.ReadSize.Width).CopyTo(rowDestinationSpan); sourceSpan = sourceSpan.Slice(bytesPerScanline); } } return(next.RunAsync(context)); }
private static void ProcessUnassociated(TiffImageDecoderContext context) { int bytesPerScanline = 4 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba32> writer = context.GetWriter <TiffRgba32>(); int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba32> pixelSpanHandle = writer.GetRowSpan(row); Span <byte> rowDestinationSpan = MemoryMarshal.Cast <TiffRgba32, byte>(pixelSpanHandle.GetSpan()); sourceSpan.Slice(4 * context.SourceReadOffset.X, 4 * context.ReadSize.Width).CopyTo(rowDestinationSpan); sourceSpan = sourceSpan.Slice(bytesPerScanline); } }
private static void ProcessUnassociated(TiffImageDecoderContext context) { int bytesPerScanline = 8 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffBgra64> writer = context.GetWriter <TiffBgra64>(); int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffBgra64> pixelSpanHandle = writer.GetRowSpan(row); Span <byte> rowDestinationSpan = MemoryMarshal.Cast <TiffBgra64, byte>(pixelSpanHandle.GetSpan()); CopyScanlineRgbaToBgra(sourceSpan.Slice(8 * context.SourceReadOffset.X, 8 * context.ReadSize.Width), rowDestinationSpan, context.ReadSize.Width, context.IsLittleEndian == BitConverter.IsLittleEndian); sourceSpan = sourceSpan.Slice(bytesPerScanline); } }
public async ValueTask InvokeAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next) { MemoryPool <byte> memoryPool = context.MemoryPool ?? MemoryPool <byte> .Shared; TiffSize imageSize = context.ImageSize; int arraySize = 3 * imageSize.Width * imageSize.Height; using (IMemoryOwner <byte> pixelData = memoryPool.Rent(arraySize)) { Memory <byte> pixelDataMemory = pixelData.Memory; using (var writer = new TiffMemoryPixelBufferWriter <TiffRgb24>(memoryPool, pixelDataMemory, imageSize.Width, imageSize.Height)) using (TiffPixelBufferWriter <TPixel> convertedWriter = context.ConvertWriter(writer.AsPixelBufferWriter())) { await context.GetReader().ReadAsync(convertedWriter, context.CancellationToken).ConfigureAwait(false); } TiffYCbCrConverter8.CreateDefault().ConvertFromRgb24(MemoryMarshal.Cast <byte, TiffRgb24>(pixelDataMemory.Span), pixelDataMemory.Span, imageSize.Width * imageSize.Height); context.PhotometricInterpretation = TiffPhotometricInterpretation.YCbCr; context.BitsPerSample = TiffValueCollection.UnsafeWrap(s_bitsPerSample); context.UncompressedData = pixelDataMemory.Slice(0, arraySize); await next.RunAsync(context).ConfigureAwait(false); context.UncompressedData = default; } TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter; if (!(ifdWriter is null)) { using (await context.LockAsync().ConfigureAwait(false)) { await ifdWriter.WriteTagAsync(TiffTag.PhotometricInterpretation, TiffValueCollection.Single((ushort)context.PhotometricInterpretation)).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.BitsPerSample, context.BitsPerSample).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.SamplesPerPixel, TiffValueCollection.Single <ushort>(3)).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.YCbCrCoefficients, TiffYCbCrConverter8.DefaultLuma).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.ReferenceBlackWhite, TiffYCbCrConverter8.DefaultReferenceBlackWhite).ConfigureAwait(false); } } }
public void Write(TiffImageDecoderContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } TiffRgba32 fillColor = _fillColor; using TiffPixelBufferWriter <TiffRgba32> writer = context.GetWriter <TiffRgba32>(); int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba32> pixelSpanHandle = writer.GetRowSpan(row); pixelSpanHandle.GetSpan().Fill(fillColor); } }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int skippedRowOffset = context.SourceImageSize.Width * context.SourceReadOffset.Y; int planarByteCount = context.SourceImageSize.Width * context.SourceImageSize.Height; ReadOnlySpan <byte> sourceSpan = context.UncompressedData.Span; ReadOnlySpan <byte> sourceC = sourceSpan.Slice(0, planarByteCount); ReadOnlySpan <byte> sourceM = sourceSpan.Slice(planarByteCount, planarByteCount); ReadOnlySpan <byte> sourceY = sourceSpan.Slice(2 * planarByteCount, planarByteCount); ReadOnlySpan <byte> sourceK = sourceSpan.Slice(3 * planarByteCount, planarByteCount); using TiffPixelBufferWriter <TiffCmyk32> writer = context.GetWriter <TiffCmyk32>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffCmyk32> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffCmyk32> rowDestinationSpan = pixelSpanHandle.GetSpan(); int rowOffset = skippedRowOffset + row * context.SourceImageSize.Width + context.SourceReadOffset.X; for (int col = 0; col < cols; col++) { int componentOffset = rowOffset + col; rowDestinationSpan[col] = new TiffCmyk32(sourceC[componentOffset], sourceM[componentOffset], sourceY[componentOffset], sourceK[componentOffset]); } } return(next.RunAsync(context)); }
/// <inheritdoc /> public ValueTask ReadAsync(TiffPoint offset, TiffPixelBufferWriter <TPixel> destination, CancellationToken cancellationToken) { if (offset.X >= (uint)_size.Width || offset.Y >= (uint)_size.Height) { throw new ArgumentOutOfRangeException(nameof(offset)); } int width = Math.Min(_size.Width - offset.X, destination.Width); int height = Math.Min(_size.Height - offset.Y, destination.Height); ReadOnlySpan <TPixel> buffer = _buffer.GetReadOnlySpan(); int bufferWidth = _size.Width; for (int row = 0; row < height; row++) { ReadOnlySpan <TPixel> sourceSpan = buffer.Slice(bufferWidth * (offset.Y + row) + offset.X, width); using TiffPixelSpanHandle <TPixel> destinationHandle = destination.GetRowSpan(row); sourceSpan.CopyTo(destinationHandle.GetSpan()); } return(default);
public ValueTask ReadAsync(TiffPoint offset, TiffPixelBufferWriter <TPixel> destination, CancellationToken cancellationToken) { if (offset.X >= (uint)_size.Width || offset.Y >= (uint)_size.Height) { throw new ArgumentOutOfRangeException(nameof(offset)); } ImageFrame <TPixel> image = _image; int offsetX = _offset.X + offset.X; int offsetY = _offset.Y + offset.Y; int width = Math.Min(_size.Width - offset.X, destination.Width); int height = Math.Min(_size.Height - offset.Y, destination.Height); for (int row = 0; row < height; row++) { Span <TPixel> sourceSpan = image.GetPixelRowSpan(offsetY + row).Slice(offsetX, width); using TiffPixelSpanHandle <TPixel> destinationHandle = destination.GetRowSpan(row); sourceSpan.CopyTo(destinationHandle.GetSpan()); } return(default);
public async ValueTask InvokeAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next) { MemoryPool <byte> memoryPool = context.MemoryPool ?? MemoryPool <byte> .Shared; TiffSize imageSize = context.ImageSize; using (IMemoryOwner <byte> pixelData = memoryPool.Rent(imageSize.Width * imageSize.Height)) { Memory <byte> pixelDataMemory = pixelData.Memory; using (var writer = new TiffMemoryPixelBufferWriter <TiffMask>(memoryPool, pixelDataMemory, imageSize.Width, imageSize.Height)) using (TiffPixelBufferWriter <TPixel> convertedWriter = context.ConvertWriter(writer.AsPixelBufferWriter())) { await context.GetReader().ReadAsync(convertedWriter, context.CancellationToken).ConfigureAwait(false); } int count = PackBytesIntoBits(pixelDataMemory.Span, imageSize, _threshold); context.PhotometricInterpretation = TiffPhotometricInterpretation.TransparencyMask; context.BitsPerSample = TiffValueCollection.Single <ushort>(1); context.UncompressedData = pixelData.Memory.Slice(0, count); await next.RunAsync(context).ConfigureAwait(false); context.UncompressedData = default; } TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter; if (!(ifdWriter is null)) { using (await context.LockAsync().ConfigureAwait(false)) { await ifdWriter.WriteTagAsync(TiffTag.PhotometricInterpretation, TiffValueCollection.Single((ushort)context.PhotometricInterpretation)).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.BitsPerSample, context.BitsPerSample).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.FillOrder, TiffValueCollection.Single((ushort)TiffFillOrder.HigherOrderBitsFirst)).ConfigureAwait(false); } } }
private static void ProcessAssociatedPreservingColorPreMultiplying(TiffImageDecoderContext context) { int bytesPerScanline = 4 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgb24> writer = context.GetWriter <TiffRgb24>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgb24> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <TiffRgba32> rowSourceSpan = MemoryMarshal.Cast <byte, TiffRgba32>(sourceSpan.Slice(4 * context.SourceReadOffset.X, 4 * context.ReadSize.Width)); Span <TiffRgb24> rowDestinationSpan = pixelSpanHandle.GetSpan(); for (int col = 0; col < cols; col++) { TiffRgba32 pixel = rowSourceSpan[col]; TiffRgb24 pixel24; byte a = pixel.A; if (a == 0) { pixel24 = default; } else { pixel24 = new TiffRgb24((byte)(pixel.R * 255 / a), (byte)(pixel.G * 255 / a), (byte)(pixel.B * 255 / a)); } rowDestinationSpan[col] = pixel24; } sourceSpan = sourceSpan.Slice(bytesPerScanline); } }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } Span <ushort> bitsPerSample = stackalloc ushort[3]; _bitsPerSample.CopyTo(bitsPerSample); bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; bool canDoFastPath = bitsPerSample[0] >= 4 && bitsPerSample[1] >= 4 && bitsPerSample[2] >= 4; int totalBitsPerSample = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2]; int bytesPerScanline = (context.SourceImageSize.Width * totalBitsPerSample + 7) / 8; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgb24> writer = context.GetWriter <TiffRgb24>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; TiffRgb24 pixel = default; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgb24> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgb24> pixelSpan = pixelSpanHandle.GetSpan(); var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst); bitReader.Advance(context.SourceReadOffset.X * totalBitsPerSample); if (canDoFastPath) { // Fast path for bits >= 8 for (int col = 0; col < cols; col++) { pixel.R = (byte)FastExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 8); pixel.G = (byte)FastExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 8); pixel.B = (byte)FastExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 8); pixelSpan[col] = pixel; } } else { // Slow path for (int col = 0; col < cols; col++) { pixel.R = (byte)ExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 8); pixel.G = (byte)ExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 8); pixel.B = (byte)ExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 8); pixelSpan[col] = pixel; } } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } Span <ushort> bitsPerSample = stackalloc ushort[3]; _bitsPerSample.CopyTo(bitsPerSample); bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; int totalBitsPerSample = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2]; int bytesPerScanline = (context.SourceImageSize.Width * totalBitsPerSample + 7) / 8; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; // BitReader.Read reads bytes in big-endian way, we only need to reverse the endianness if the source is little-endian. bool isLittleEndian = context.IsLittleEndian; bool reverseEndiannessR = isLittleEndian && bitsPerSample[0] % 8 == 0; bool reverseEndiannessG = isLittleEndian && bitsPerSample[1] % 8 == 0; bool reverseEndiannessB = isLittleEndian && bitsPerSample[2] % 8 == 0; bool canDoFastPath = bitsPerSample[0] >= 16 && bitsPerSample[1] >= 16 && bitsPerSample[2] >= 16 && !reverseEndiannessR & !reverseEndiannessG & !reverseEndiannessB; TiffRgba64 pixel = default; pixel.A = ushort.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba64> pixelSpan = pixelSpanHandle.GetSpan(); var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst); bitReader.Advance(context.SourceReadOffset.X * totalBitsPerSample); if (canDoFastPath) { // Fast path for bits >= 8 for (int col = 0; col < cols; col++) { pixel.R = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 32) >> 16); pixel.G = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 32) >> 16); pixel.B = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 32) >> 16); pixelSpan[col] = pixel; } } else { // Slow path for (int col = 0; col < cols; col++) { pixel.R = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 32, reverseEndiannessR) >> 16); pixel.G = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 32, reverseEndiannessG) >> 16); pixel.B = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 32, reverseEndiannessB) >> 16); pixelSpan[col] = pixel; } } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int bytesPerScanline = (context.SourceImageSize.Width + 7) / 8; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffGray8> writer = context.GetWriter <TiffGray8>(); int xOffset = context.SourceReadOffset.X; int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffGray8> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <byte> bitsSpan = sourceSpan.Slice(xOffset >> 3); // xOffset / 8 Span <byte> rowDestinationSpan = MemoryMarshal.AsBytes(pixelSpanHandle.GetSpan()); int bitsOffset = xOffset & 7; // xOffset % 8 int sourceIndex = 0; int destinationIndex = 0; int remainingWidth = context.ReadSize.Width; byte bits; if (bitsOffset > 0) { remainingWidth -= 8 - bitsOffset; bits = MaybeReverseBits(bitsSpan[sourceIndex++]); for (; bitsOffset < 8; bitsOffset++) { bool isSet = (bits >> (7 - bitsOffset) & 1) != 0; rowDestinationSpan[destinationIndex++] = isSet ? (byte)0 : (byte)255; } } while (remainingWidth >= 8) { bits = (bitsSpan[sourceIndex++]); bool bit0 = (bits >> 7 & 1) != 0; bool bit1 = (bits >> 6 & 1) != 0; bool bit2 = (bits >> 5 & 1) != 0; bool bit3 = (bits >> 4 & 1) != 0; bool bit4 = (bits >> 3 & 1) != 0; bool bit5 = (bits >> 2 & 1) != 0; bool bit6 = (bits >> 1 & 1) != 0; bool bit7 = (bits & 1) != 0; rowDestinationSpan[destinationIndex++] = bit0 ? (byte)0 : (byte)255; rowDestinationSpan[destinationIndex++] = bit1 ? (byte)0 : (byte)255; rowDestinationSpan[destinationIndex++] = bit2 ? (byte)0 : (byte)255; rowDestinationSpan[destinationIndex++] = bit3 ? (byte)0 : (byte)255; rowDestinationSpan[destinationIndex++] = bit4 ? (byte)0 : (byte)255; rowDestinationSpan[destinationIndex++] = bit5 ? (byte)0 : (byte)255; rowDestinationSpan[destinationIndex++] = bit6 ? (byte)0 : (byte)255; rowDestinationSpan[destinationIndex++] = bit7 ? (byte)0 : (byte)255; remainingWidth -= 8; } if (remainingWidth > 0) { bits = MaybeReverseBits(bitsSpan[sourceIndex++]); for (; remainingWidth > 0; remainingWidth--) { bool isSet = (bits & 0b10000000) != 0; rowDestinationSpan[destinationIndex++] = isSet ? (byte)0 : (byte)255; bits = (byte)(bits << 1); } } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
public override TiffPixelBufferWriter <TPixel> ConvertWriter <TBuffer>(TiffPixelBufferWriter <TBuffer> writer) { throw new NotSupportedException(); }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int skippedRowOffset = context.SourceImageSize.Width * context.SourceReadOffset.Y; int planarByteCount = sizeof(ushort) * context.SourceImageSize.Width * context.SourceImageSize.Height; ReadOnlySpan <byte> sourceSpan = context.UncompressedData.Span; ReadOnlySpan <byte> sourceR = sourceSpan.Slice(0, planarByteCount); ReadOnlySpan <byte> sourceG = sourceSpan.Slice(planarByteCount, planarByteCount); ReadOnlySpan <byte> sourceB = sourceSpan.Slice(2 * planarByteCount, planarByteCount); using TiffPixelBufferWriter <TiffBgra64> writer = context.GetWriter <TiffBgra64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; if (context.IsLittleEndian == BitConverter.IsLittleEndian) { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffBgra64> pixelSpanHandle = writer.GetRowSpan(row); Span <byte> rowDestinationSpan = MemoryMarshal.AsBytes(pixelSpanHandle.GetSpan()); int rowOffset = sizeof(ushort) * (skippedRowOffset + row * context.SourceImageSize.Width + context.SourceReadOffset.X); for (int col = 0; col < cols; col++) { int componentOffset = rowOffset + sizeof(ushort) * col; ulong value = 0xffff; value = (value << 16) | (uint)(sourceR[componentOffset + 1] << 8) | sourceR[componentOffset]; // r value = (value << 16) | (uint)(sourceG[componentOffset + 1] << 8) | sourceG[componentOffset]; // g value = (value << 16) | (uint)(sourceB[componentOffset + 1] << 8) | sourceB[componentOffset]; // b BinaryPrimitives.WriteUInt64LittleEndian(rowDestinationSpan, value); rowDestinationSpan = rowDestinationSpan.Slice(8); } } } else { for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffBgra64> pixelSpanHandle = writer.GetRowSpan(row); Span <byte> rowDestinationSpan = MemoryMarshal.AsBytes(pixelSpanHandle.GetSpan()); int rowOffset = sizeof(ushort) * (skippedRowOffset + row * context.SourceImageSize.Width + context.SourceReadOffset.X); for (int col = 0; col < cols; col++) { int componentOffset = rowOffset + sizeof(ushort) * col; ulong value = 0xffff; value = (value << 16) | (uint)(sourceR[componentOffset] << 8) | sourceR[componentOffset + 1]; // r value = (value << 16) | (uint)(sourceG[componentOffset] << 8) | sourceG[componentOffset + 1]; // g value = (value << 16) | (uint)(sourceB[componentOffset] << 8) | sourceB[componentOffset + 1]; // b BinaryPrimitives.WriteUInt64LittleEndian(rowDestinationSpan, value); rowDestinationSpan = rowDestinationSpan.Slice(8); } } } return(next.RunAsync(context)); }
/// <summary> /// Converts pixel buffer writer of any pixel format <typeparamref name="TBuffer"/> into <see cref="TiffPixelBufferWriter{TPixel}"/>. /// </summary> /// <typeparam name="TBuffer">The specified pixel type.</typeparam> /// <param name="writer">The writer to be converted.</param> /// <returns>The converted writer.</returns> public abstract TiffPixelBufferWriter <TPixel> ConvertWriter <TBuffer>(TiffPixelBufferWriter <TBuffer> writer) where TBuffer : unmanaged;
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } int bytesPerScanline = (context.SourceImageSize.Width + 1) / 2; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffGray8> writer = context.GetWriter <TiffGray8>(); int xOffset = context.SourceReadOffset.X; int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffGray8> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <byte> bitsSpan = sourceSpan.Slice(xOffset >> 1); // xOffset / 2 Span <byte> rowDestinationSpan = MemoryMarshal.AsBytes(pixelSpanHandle.GetSpan()); int bitsOffset = xOffset & 1; // xOffset % 2 int sourceIndex = 0; int destinationIndex = 0; int remainingWidth = context.ReadSize.Width; byte bits; if (bitsOffset > 0) { remainingWidth--; bits = bitsSpan[sourceIndex++]; bits = (byte)(bits & 0xf); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits); } // manual loop unrolling for (; (remainingWidth >> 4) > 0; remainingWidth -= 16) // for (; remainingWidth >= 16; remainingWidth -= 16) { bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); } for (; (remainingWidth >> 1) > 0; remainingWidth -= 2) // for (; remainingWidth >= 2; remainingWidth -= 2) { bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); rowDestinationSpan[destinationIndex++] = (byte)(bits << 4 | bits & 0xf); } if (remainingWidth != 0) { bits = bitsSpan[sourceIndex++]; rowDestinationSpan[destinationIndex++] = (byte)(bits & 0xf0 | bits >> 4); } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }