/// <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 horizontalSubsampling = _horizontalSubsampling; int verticalSubsampling = _verticalSubsampling; if (horizontalSubsampling == 1 && verticalSubsampling == 1) { return(next.RunAsync(context)); } if (_isPlanar) { ProcessPlanarData(context); } else { ProcessChunkyData(context); } 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 = 6 * 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()); CopyScanlineRgbToBgra(sourceSpan.Slice(6 * context.SourceReadOffset.X, 6 * context.ReadSize.Width), rowDestinationSpan.Slice(0, 8 * context.ReadSize.Width), context.ReadSize.Width, context.IsLittleEndian == BitConverter.IsLittleEndian); 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 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 <TiffGray16> writer = context.GetWriter <TiffGray16>(); 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 reverseEndianness = context.IsLittleEndian && bitCount % 8 == 0; bool canDoFastPath = bitCount >= 16 && !reverseEndianness; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffGray16> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffGray16> pixelSpan = pixelSpanHandle.GetSpan(); var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst); bitReader.Advance(context.SourceReadOffset.X * bitCount); if (canDoFastPath) { // Fast path for bits >= 16 for (int col = 0; col < cols; col++) { uint value = bitReader.Read(bitCount); value = FastExpandBits(value, bitCount, 32); pixelSpan[col] = new TiffGray16((ushort)(~value >> 16)); } } else { // Slow path for (int col = 0; col < cols; col++) { uint value = bitReader.Read(bitCount); value = (uint)ExpandBits(value, bitCount, 32, reverseEndianness); pixelSpan[col] = new TiffGray16((ushort)(~value >> 16)); } } 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)); } if (!_isAlphaAssociated) { ProcessUnassociated(context); } else if (_undoColorPreMultiplying) { ProcessAssociatedWithUndoColorPreMultiplying(context); } else { ProcessAssociatedPreservingColorPreMultiplying(context); } 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 = 4 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffCmyk32> writer = context.GetWriter <TiffCmyk32>(); int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffCmyk32> pixelSpanHandle = writer.GetRowSpan(row); Span <byte> rowDestinationSpan = MemoryMarshal.AsBytes(pixelSpanHandle.GetSpan()); sourceSpan.Slice(4 * context.SourceReadOffset.X, 4 * context.ReadSize.Width).CopyTo(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)); }
/// <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)); }
/// <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)); }
/// <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)); }
public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { TiffParallelDecodingState?state = context.GetService(typeof(TiffParallelDecodingState)) as TiffParallelDecodingState; if (state is null) { return(next.RunAsync(context)); } return(new ValueTask(state.DispatchAsync(() => next.RunAsync(context), context.CancellationToken))); }
/// <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)); }
/// <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)); }
/// <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)); } if (_predictor == TiffPredictor.None) { return(next.RunAsync(context)); } if (_predictor != TiffPredictor.HorizontalDifferencing) { throw new NotSupportedException("Predictor not supportted."); } int skipped = 0; bool isMultiplePlanar = _bytesPerScanlines.Count > 1; for (int planarIndex = 0; planarIndex < _bytesPerScanlines.Count; planarIndex++) { int bytesPerScanline = _bytesPerScanlines[planarIndex]; // Current plane buffer Span <byte> plane = context.UncompressedData.Span.Slice(skipped, bytesPerScanline * context.SourceImageSize.Height); // Skip scanlines that are not to be decoded plane = plane.Slice(bytesPerScanline * context.SourceReadOffset.Y); TiffValueCollection <ushort> bitsPerSample = isMultiplePlanar ? TiffValueCollection.Single(_bitsPerSample[planarIndex]) : _bitsPerSample; for (int row = 0; row < context.ReadSize.Height; row++) { // Process every scanline Span <byte> scanline = plane.Slice(row * bytesPerScanline, bytesPerScanline); UndoHorizontalDifferencingForScanline(scanline, bitsPerSample, context.SourceImageSize.Width); } skipped += bytesPerScanline * context.SourceImageSize.Height; } 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)); } if (_orientation == 0 || _orientation == TiffOrientation.TopLeft) { return(next.RunAsync(context)); } return(next.RunAsync(new TiffOrientatedImageDecoderContext(context, _orientation))); }
public async ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { using var state = new TiffParallelDecodingState(_maxDegreeOfParallelism); using var mutexService = new ParallelMutexService(); context.RegisterService(typeof(TiffParallelDecodingState), state); context.RegisterService(typeof(ITiffParallelMutexService), mutexService); state.LockTaskCompletion(); await next.RunAsync(context).ConfigureAwait(false); state.ReleaseTaskCompletion(); await state.Complete !.Task.ConfigureAwait(false); context.RegisterService(typeof(TiffParallelDecodingState), null); context.RegisterService(typeof(ITiffParallelMutexService), null); }
/// <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)); }
public async ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { IDisposable?lockObj = null; var mutexService = context.GetService(typeof(ITiffParallelMutexService)) as ITiffParallelMutexService; if (!(mutexService is null)) { lockObj = await mutexService.LockAsync(context.CancellationToken).ConfigureAwait(false); } try { await next.RunAsync(context).ConfigureAwait(false); } finally { if (!(lockObj is null)) { lockObj.Dispose(); } } }
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; Span <ushort> uncompressedData = MemoryMarshal.Cast <byte, ushort>(context.UncompressedData.Span); for (int i = 0; i < uncompressedData.Length; i++) { uncompressedData[i] = (ushort)FastExpandBits(uncompressedData[i], bitCount); } return(next.RunAsync(new JpegDataEndianContextWrapper(context))); }
/// <inheritdoc /> public async ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } if (context.ContentReader is null) { throw new ArgumentException("ContentReader is not provided in the TiffImageDecoderContext instance."); } if (context.OperationContext is null) { throw new ArgumentException("OperationContext is not provided in the TiffImageDecoderContext instance."); } bool isParallel = !(context.GetService(typeof(TiffParallelDecodingState)) is null); // Initialize the cache TiffFieldReader? reader = null; TiffStrileOffsetCache cache; if (_lazyLoad) { reader = new TiffFieldReader(context.ContentReader, context.OperationContext, leaveOpen: true); cache = new TiffStrileOffsetCache(reader, _stripOffsetsEntry, _stripsByteCountEntry, CacheSize); } else { cache = new TiffStrileOffsetCache(_stripOffsets, _stripsByteCount); } try { int rowsPerStrip = _rowsPerStrip; CancellationToken cancellationToken = context.CancellationToken; // Special case for mailformed file. if (rowsPerStrip <= 0 && _stripCount == 1) { rowsPerStrip = context.SourceImageSize.Height; } // Make sure the region to read is in the image boundary. if (context.SourceReadOffset.X >= context.SourceImageSize.Width || context.SourceReadOffset.Y >= context.SourceImageSize.Height) { return; } context.ReadSize = new TiffSize(Math.Min(context.ReadSize.Width, context.SourceImageSize.Width - context.SourceReadOffset.X), Math.Min(context.ReadSize.Height, context.SourceImageSize.Height - context.SourceReadOffset.Y)); if (context.ReadSize.IsAreaEmpty) { return; } // Create a wrapped context int planeCount = _planeCount; TiffImageEnumeratorDecoderContext? wrapperContext = null; TiffMutableValueCollection <TiffStreamRegion> planarRegions = default; if (!isParallel) { wrapperContext = new TiffImageEnumeratorDecoderContext(context); planarRegions = new TiffMutableValueCollection <TiffStreamRegion>(planeCount); } // loop through all the strips overlapping with the region to read int stripStart = context.SourceReadOffset.Y / rowsPerStrip; int stripEnd = (context.SourceReadOffset.Y + context.ReadSize.Height + rowsPerStrip - 1) / rowsPerStrip; int actualStripCount = _stripCount / _planeCount; for (int stripIndex = stripStart; stripIndex < stripEnd; stripIndex++) { if (isParallel) { wrapperContext = new TiffImageEnumeratorDecoderContext(context); planarRegions = new TiffMutableValueCollection <TiffStreamRegion>(planeCount); } // Calculate size info of this strip int currentYOffset = stripIndex * rowsPerStrip; int stripImageHeight = Math.Min(rowsPerStrip, context.SourceImageSize.Height - currentYOffset); int skippedScanlines = Math.Max(0, context.SourceReadOffset.Y - currentYOffset); int requestedScanlines = Math.Min(stripImageHeight - skippedScanlines, context.SourceReadOffset.Y + context.ReadSize.Height - currentYOffset - skippedScanlines); wrapperContext !.SourceImageSize = new TiffSize(context.SourceImageSize.Width, stripImageHeight); wrapperContext.SourceReadOffset = new TiffPoint(context.SourceReadOffset.X, skippedScanlines); wrapperContext.ReadSize = new TiffSize(context.ReadSize.Width, requestedScanlines); // Update size info of the destination buffer wrapperContext.SetCropSize(new TiffPoint(0, Math.Max(0, currentYOffset - context.SourceReadOffset.Y)), context.ReadSize); // Check to see if there is any region area to be read if (wrapperContext.ReadSize.IsAreaEmpty) { continue; } // Prepare stream regions of this strip for (int planarIndex = 0; planarIndex < planeCount; planarIndex++) { int accessIndex = planarIndex * actualStripCount + stripIndex; planarRegions[planarIndex] = await cache.GetOffsetAndCountAsync(accessIndex, cancellationToken).ConfigureAwait(false); } wrapperContext.PlanarRegions = planarRegions.GetReadOnlyView(); cancellationToken.ThrowIfCancellationRequested(); // Pass down the data await next.RunAsync(wrapperContext).ConfigureAwait(false); } } finally { ((IDisposable)cache).Dispose(); reader?.Dispose(); } }
/// <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)); }
public async ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { MemoryPool <byte> memoryPool = context.MemoryPool ?? MemoryPool <byte> .Shared; TiffFileContentReader contentReader = context.ContentReader ?? throw new InvalidOperationException(); IMemoryOwner <byte>?dataHandle = null; Memory <byte> data; try { const int BufferSize = 65536; using (var bufferWriter = new MemoryPoolBufferWriter(memoryPool)) { // Read JPEG stream TiffStreamRegion streamRegion = _streamRegion; do { int readSize = Math.Min(streamRegion.Length, BufferSize); Memory <byte> memory = bufferWriter.GetMemory(readSize); memory = memory.Slice(0, Math.Min(streamRegion.Length, memory.Length)); readSize = await contentReader.ReadAsync(streamRegion.Offset, memory, context.CancellationToken).ConfigureAwait(false); bufferWriter.Advance(readSize); streamRegion = new TiffStreamRegion(streamRegion.Offset + readSize, streamRegion.Length - readSize); } while (streamRegion.Length > 0); // Identify the image var decoder = new JpegDecoder(); decoder.MemoryPool = memoryPool; decoder.SetInput(bufferWriter.GetReadOnlySequence()); decoder.Identify(); if (decoder.Width != context.SourceImageSize.Width || decoder.Height != context.SourceImageSize.Height) { throw new InvalidOperationException("The image size does not match."); } if (decoder.Precision != 8) { throw new InvalidOperationException("Only 8-bit JPEG is supported."); } // Adjust buffer size and reading region to reduce buffer size int skippedWidth = context.SourceReadOffset.X / 8 * 8; int skippedHeight = context.SourceReadOffset.Y / 8 * 8; context.SourceReadOffset = new TiffPoint(context.SourceReadOffset.X % 8, context.SourceReadOffset.Y % 8); int bufferWidth = context.SourceReadOffset.X + context.ReadSize.Width; int bufferHeight = context.SourceReadOffset.Y + context.ReadSize.Height; context.SourceImageSize = new TiffSize(bufferWidth, bufferHeight); // Allocate buffer and decode image int dataSize = bufferWidth * bufferHeight * decoder.NumberOfComponents; dataHandle = memoryPool.Rent(dataSize); data = dataHandle.Memory.Slice(0, dataSize); decoder.SetOutputWriter(new LegacyJpegBufferOutputWriter(skippedWidth, bufferWidth, skippedHeight, bufferHeight, decoder.NumberOfComponents, data)); decoder.Decode(); } // Pass the buffer to the next middleware context.UncompressedData = data; await next.RunAsync(context).ConfigureAwait(false); context.UncompressedData = null; } finally { dataHandle?.Dispose(); } }
/// <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)); }
/// <inheritdoc /> public async ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } if (context.ContentReader is null) { throw new ArgumentException("ContentReader is not provided in the TiffImageDecoderContext instance."); } if (context.OperationContext is null) { throw new ArgumentException("OperationContext is not provided in the TiffImageDecoderContext instance."); } bool isParallel = !(context.GetService(typeof(TiffParallelDecodingState)) is null); // Initialize the cache TiffFieldReader? reader = null; TiffStrileOffsetCache cache; if (_lazyLoad) { reader = new TiffFieldReader(context.ContentReader, context.OperationContext, leaveOpen: true); cache = new TiffStrileOffsetCache(reader, _tileOffsetsEntry, _tileByteCountsEntry, CacheSize); } else { cache = new TiffStrileOffsetCache(_tileOffsets, _tileByteCounts); } int tileWidth = _tileWidth; int tileHeight = _tileHeight; try { int tileAcross = (context.SourceImageSize.Width + tileWidth - 1) / tileWidth; int tileDown = (context.SourceImageSize.Height + tileHeight - 1) / tileHeight; int tileCount = tileAcross * tileDown; CancellationToken cancellationToken = context.CancellationToken; // Make sure the region to read is in the image boundary. if (context.SourceReadOffset.X >= context.SourceImageSize.Width || context.SourceReadOffset.Y >= context.SourceImageSize.Height) { return; } context.ReadSize = new TiffSize(Math.Min(context.ReadSize.Width, context.SourceImageSize.Width - context.SourceReadOffset.X), Math.Min(context.ReadSize.Height, context.SourceImageSize.Height - context.SourceReadOffset.Y)); if (context.ReadSize.IsAreaEmpty) { return; } int planeCount = _planaCount; TiffImageEnumeratorDecoderContext? wrapperContext = null; TiffMutableValueCollection <TiffStreamRegion> planarRegions = default; if (!isParallel) { wrapperContext = new TiffImageEnumeratorDecoderContext(context); planarRegions = new TiffMutableValueCollection <TiffStreamRegion>(planeCount); } // loop through all the tiles overlapping with the region to read. int colStart = context.SourceReadOffset.X / tileWidth; int colEnd = (context.SourceReadOffset.X + context.ReadSize.Width + tileWidth - 1) / tileWidth; int rowStart = context.SourceReadOffset.Y / tileHeight; int rowEnd = (context.SourceReadOffset.Y + context.ReadSize.Height + tileHeight - 1) / tileHeight; for (int row = rowStart; row < rowEnd; row++) { // Calculate coordinates on the y direction for the tiles on this row. int currentYOffset = row * tileHeight; int skippedScanlines = Math.Max(0, context.SourceReadOffset.Y - currentYOffset); int requestedScanlines = Math.Min(tileHeight - skippedScanlines, context.SourceReadOffset.Y + context.ReadSize.Height - currentYOffset - skippedScanlines); for (int col = colStart; col < colEnd; col++) { if (isParallel) { wrapperContext = new TiffImageEnumeratorDecoderContext(context); planarRegions = new TiffMutableValueCollection <TiffStreamRegion>(planeCount); } wrapperContext !.SourceImageSize = new TiffSize(tileWidth, tileHeight); // Calculate coordinates on the y direction for this tile. int currentXOffset = col * tileWidth; int skippedXOffset = Math.Max(0, context.SourceReadOffset.X - currentXOffset); int requestedWidth = Math.Min(tileWidth - skippedXOffset, context.SourceReadOffset.X + context.ReadSize.Width - currentXOffset - skippedXOffset); // Update size info of this tile wrapperContext.SourceReadOffset = new TiffPoint(skippedXOffset, skippedScanlines); wrapperContext.ReadSize = new TiffSize(requestedWidth, requestedScanlines); // Update size info of the destination buffer wrapperContext.SetCropSize(new TiffPoint(Math.Max(0, currentXOffset - context.SourceReadOffset.X), Math.Max(0, currentYOffset - context.SourceReadOffset.Y)), context.ReadSize); // Check to see if there is any region area to be read if (wrapperContext.ReadSize.IsAreaEmpty) { continue; } // Prepare stream regions of this tile for (int planarIndex = 0; planarIndex < planeCount; planarIndex++) { int tileIndex = planarIndex * tileCount + row * tileAcross + col; planarRegions[planarIndex] = await cache.GetOffsetAndCountAsync(tileIndex, cancellationToken).ConfigureAwait(false); } wrapperContext.PlanarRegions = planarRegions.GetReadOnlyView(); cancellationToken.ThrowIfCancellationRequested(); // Pass down the data await next.RunAsync(wrapperContext).ConfigureAwait(false); } } } finally { ((IDisposable)cache).Dispose(); reader?.Dispose(); } }
/// <inheritdoc /> public async ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } if (_bytesPerScanlines.Count != context.PlanarRegions.Count) { throw new InvalidOperationException(); } int planeCount = _bytesPerScanlines.Count; // calculate the uncompressed data buffer length int uncompressedDataLength = 0; int imageHeight = context.SourceImageSize.Height; foreach (int bytesPerScanline in _bytesPerScanlines) { uncompressedDataLength += bytesPerScanline * imageHeight; } TiffEmptyStrileWriter?emptyWriter = null; // calculate the maximum buffer needed to read from stream int readCount = 0; foreach (TiffStreamRegion planarRegion in context.PlanarRegions) { int length = planarRegion.Length; if (length == 0) { emptyWriter = new TiffEmptyStrileWriter(new TiffRgba32(255, 255, 255, 0)); break; } if (length > readCount) { readCount = planarRegion.Length; } } if (!(emptyWriter is null)) { emptyWriter.Write(context); return; } // allocate the raw data buffer and the uncompressed data buffer MemoryPool <byte> memoryPool = context.MemoryPool ?? MemoryPool <byte> .Shared; using IMemoryOwner <byte> bufferMemory = memoryPool.Rent(uncompressedDataLength); int planarUncompressedByteCount = 0; TiffFileContentReader?reader = context.ContentReader; if (reader is null) { throw new InvalidOperationException("Failed to acquire ContentReader."); } using (IMemoryOwner <byte> rawBuffer = memoryPool.Rent(readCount)) { TiffDecompressionContext decompressionContext = new TiffDecompressionContext(); // decompress each plane for (int i = 0; i < planeCount; i++) { TiffStreamRegion region = context.PlanarRegions[i]; // Read from stream readCount = await reader.ReadAsync(region.Offset, rawBuffer.Memory.Slice(0, region.Length), context.CancellationToken).ConfigureAwait(false); if (readCount != region.Length) { throw new InvalidDataException(); } // Decompress this plane int bytesPerScanline = _bytesPerScanlines[i]; decompressionContext.MemoryPool = memoryPool; decompressionContext.PhotometricInterpretation = _photometricInterpretation; decompressionContext.BitsPerSample = _bitsPerSample; decompressionContext.ImageSize = context.SourceImageSize; decompressionContext.BytesPerScanline = bytesPerScanline; decompressionContext.SkippedScanlines = context.SourceReadOffset.Y; decompressionContext.RequestedScanlines = context.ReadSize.Height; _decompressionAlgorithm.Decompress(decompressionContext, rawBuffer.Memory.Slice(0, readCount), bufferMemory.Memory.Slice(planarUncompressedByteCount, bytesPerScanline * imageHeight)); planarUncompressedByteCount += bytesPerScanline * imageHeight; } } // Pass down the data context.UncompressedData = bufferMemory.Memory.Slice(0, uncompressedDataLength); await next.RunAsync(context).ConfigureAwait(false); context.UncompressedData = default; }
/// <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)); }
/// <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)); }
/// <summary> /// Initialize the adapterwith the specified pipelines. /// </summary> /// <param name="parameters">Parameters of this TIFF file and IFD.</param> /// <param name="pipeline">The pipeline to use for decoding the IFD.</param> public TiffImageDecoderPipelineAdapter(TiffImageDecoderParameters parameters, ITiffImageDecoderPipelineNode pipeline) { _parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); _pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline)); CalculateOrientedSize(); }
/// <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)); }