Exemplo n.º 1
0
        public void TestSize()
        {
            var defaultSize = new TiffSize();

            Assert.Equal(0, defaultSize.Width);
            Assert.Equal(0, defaultSize.Height);

            var size1 = new TiffSize(1, 2);

            Assert.Equal(1, size1.Width);
            Assert.Equal(2, size1.Height);
            Assert.False(size1.Equals(defaultSize));
            Assert.False(defaultSize.Equals(size1));
            Assert.False(size1 == defaultSize);
            Assert.False(defaultSize == size1);
            Assert.True(size1 != defaultSize);
            Assert.True(defaultSize != size1);
            Assert.False(size1.GetHashCode() == defaultSize.GetHashCode());

            var size2 = new TiffSize(1, 2);

            Assert.True(size1.Equals(size2));
            Assert.True(size2.Equals(size1));
            Assert.True(size1 == size2);
            Assert.True(size2 == size1);
            Assert.False(size1 != size2);
            Assert.False(size2 != size1);
            Assert.True(size1.GetHashCode() == size2.GetHashCode());
        }
Exemplo n.º 2
0
        private static int PackBytesIntoBits(Span <byte> pixelData, TiffSize imageSize, int threshold)
        {
            // Calculate bytes per scanline
            int width  = imageSize.Width;
            int height = imageSize.Height;

            // Pack each byte into 1 bit
            int pixelIndex  = 0;
            int bufferIndex = 0;

            for (int row = 0; row < height; row++)
            {
                int buffer       = 0;
                int bitsInBuffer = 0;
                for (int col = 0; col < width; col++)
                {
                    int fillByte = pixelData[pixelIndex++] > threshold ? 1 : 0;
                    buffer = (buffer << 1) | fillByte;
                    bitsInBuffer++;
                    if (bitsInBuffer == 8)
                    {
                        pixelData[bufferIndex++] = (byte)buffer;
                        buffer       = 0;
                        bitsInBuffer = 0;
                    }
                }
                if (bitsInBuffer != 0)
                {
                    buffer <<= (8 - bitsInBuffer);
                    pixelData[bufferIndex++] = (byte)buffer;
                }
            }

            return(bufferIndex);
        }
        private void ProcessChunkyData(TiffSize size, ReadOnlySpan <ushort> source, Span <ushort> destination)
        {
            int width  = size.Width;
            int height = size.Height;
            int horizontalSubsampling = _horizontalSubsampling;
            int verticalSubsampling   = _verticalSubsampling;

            int blockWidth        = width / horizontalSubsampling;
            int blockHeight       = height / verticalSubsampling;
            int cbCrOffsetInBlock = horizontalSubsampling * verticalSubsampling;
            int blockByteCount    = cbCrOffsetInBlock + 2;

            for (int blockRow = blockHeight - 1; blockRow >= 0; blockRow--)
            {
                for (int blockCol = blockWidth - 1; blockCol >= 0; blockCol--)
                {
                    int blockOffset = blockRow * blockWidth + blockCol;
                    ReadOnlySpan <ushort> blockData = source.Slice(blockOffset * blockByteCount, blockByteCount);
                    ushort cr = blockData[cbCrOffsetInBlock + 1];
                    ushort cb = blockData[cbCrOffsetInBlock];

                    for (int row = verticalSubsampling - 1; row >= 0; row--)
                    {
                        for (int col = horizontalSubsampling - 1; col >= 0; col--)
                        {
                            int offset = 3 * ((blockRow * verticalSubsampling + row) * width + blockCol * horizontalSubsampling + col);
                            destination[offset + 2] = cr;
                            destination[offset + 1] = cb;
                            destination[offset]     = blockData[row * horizontalSubsampling + col];
                        }
                    }
                }
            }
        }
Exemplo n.º 4
0
        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);
                }
            }
        }
Exemplo n.º 5
0
        /// <inheritdoc />
        public override void Decode <TPixel>(TiffPoint offset, TiffSize readSize, TiffPoint destinationOffset, ITiffPixelBufferWriter <TPixel> writer)
        {
            if (writer is null)
            {
                throw new ArgumentNullException(nameof(writer));
            }
            readSize = new TiffSize(Math.Max(0, Math.Min(writer.Width - destinationOffset.X, readSize.Width)), Math.Max(0, Math.Min(writer.Height - destinationOffset.Y, readSize.Height)));
            readSize = new TiffSize(Math.Max(0, Math.Min(Width - offset.X, readSize.Width)), Math.Max(0, Math.Min(Height - offset.Y, readSize.Height)));
            if (readSize.IsAreaEmpty)
            {
                return;
            }

            if (_parameters.ContentSource is null)
            {
                throw new InvalidOperationException("Failed to acquire ContentSource.");
            }

            using TiffFileContentReader reader = TiffSyncFileContentSource.WrapReader(_parameters.ContentSource.OpenReader());
            var context = new TiffDefaultImageDecoderContext <TPixel>()
            {
                MemoryPool            = _parameters.MemoryPool ?? MemoryPool <byte> .Shared,
                CancellationToken     = CancellationToken.None,
                OperationContext      = _parameters.OperationContext,
                ContentReader         = reader,
                SourceImageSize       = _parameters.ImageSize,
                SourceReadOffset      = offset,
                ReadSize              = readSize,
                PixelConverterFactory = _parameters.PixelConverterFactory ?? TiffDefaultPixelConverterFactory.Instance,
                DestinationWriter     = new TiffPixelBufferWriter <TPixel>(TiffNoopDisposablePixelBufferWriter.Wrap(writer)).Crop(destinationOffset, readSize)
            };

            _pipeline.RunAsync(context).AsTask().GetAwaiter().GetResult();
        }
            public PaddingContext(TiffImageEncoderContext <TPixel> context, TiffSize size) : base(context)
            {
                TiffSize originalSize = context.ImageSize;

                if (size.Width < originalSize.Width || size.Height < originalSize.Height)
                {
                    throw new ArgumentOutOfRangeException(nameof(context));
                }
                _size = size;
            }
Exemplo n.º 7
0
        public ImageSharpPixelBufferReader(ImageFrame <TPixel> image)
        {
            if (image is null)
            {
                throw new ArgumentNullException(nameof(image));
            }

            _image  = image;
            _offset = default;
            _size   = new TiffSize(image.Width, image.Height);
        }
        public void SetCropSize(TiffPoint offset, TiffSize originalReadSize)
        {
            CropOffset = offset;

            int copySizeX = Math.Max(0, Math.Min(SourceImageSize.Width - SourceReadOffset.X, ReadSize.Width));
            int copySizeY = Math.Max(0, Math.Min(SourceImageSize.Height - SourceReadOffset.Y, ReadSize.Height));

            copySizeX = Math.Max(0, Math.Min(originalReadSize.Width - offset.X, copySizeX));
            copySizeY = Math.Max(0, Math.Min(originalReadSize.Height - offset.Y, copySizeY));

            ReadSize = new TiffSize(copySizeX, copySizeY);
        }
Exemplo n.º 9
0
 /// <summary>
 /// Initialize the middleware with the specified tile size.
 /// </summary>
 /// <param name="tileSize">The size of each tile.</param>
 public TiffTiledImageEncoderEnumeratorMiddleware(TiffSize tileSize)
 {
     if (tileSize.Width < 16 || tileSize.Height < 16)
     {
         throw new ArgumentOutOfRangeException(nameof(tileSize));
     }
     if ((tileSize.Width % 16 != 0) || (tileSize.Height % 16 != 0))
     {
         throw new ArgumentOutOfRangeException(nameof(tileSize));
     }
     _tileSize = tileSize;
 }
Exemplo n.º 10
0
        private void ProcessChunkyData(TiffSize size, ReadOnlySpan <byte> source, Span <byte> destination)
        {
            int width  = size.Width;
            int height = size.Height;
            int horizontalSubsampling = _horizontalSubsampling;
            int verticalSubsampling   = _verticalSubsampling;

            int blockCols      = (width + horizontalSubsampling - 1) / horizontalSubsampling;
            int blockRows      = (height + verticalSubsampling - 1) / verticalSubsampling;
            int blockByteCount = horizontalSubsampling * verticalSubsampling + 2;

            Debug.Assert(source.Length >= width * height * 3);
            Debug.Assert(destination.Length == width * height + blockCols * blockRows * 2);

            ref byte sourceRef      = ref MemoryMarshal.GetReference(source);
Exemplo n.º 11
0
        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);
                }
            }
        }
Exemplo n.º 12
0
        /// <inheritdoc />
        public override async Task DecodeAsync <TPixel>(TiffPoint offset, TiffSize readSize, TiffPoint destinationOffset, ITiffPixelBufferWriter <TPixel> writer, CancellationToken cancellationToken = default)
        {
            if (writer is null)
            {
                throw new ArgumentNullException(nameof(writer));
            }
            readSize = new TiffSize(Math.Max(0, Math.Min(writer.Width - destinationOffset.X, readSize.Width)), Math.Max(0, Math.Min(writer.Height - destinationOffset.Y, readSize.Height)));
            readSize = new TiffSize(Math.Max(0, Math.Min(Width - offset.X, readSize.Width)), Math.Max(0, Math.Min(Height - offset.Y, readSize.Height)));
            if (readSize.IsAreaEmpty)
            {
                return;
            }

            if (_parameters.ContentSource is null)
            {
                throw new InvalidOperationException("Failed to acquire ContentSource.");
            }

            TiffFileContentReader reader = await _parameters.ContentSource.OpenReaderAsync(cancellationToken).ConfigureAwait(false);

            try
            {
                var context = new TiffDefaultImageDecoderContext <TPixel>()
                {
                    MemoryPool            = _parameters.MemoryPool ?? MemoryPool <byte> .Shared,
                    CancellationToken     = cancellationToken,
                    OperationContext      = _parameters.OperationContext,
                    ContentReader         = reader,
                    SourceImageSize       = _parameters.ImageSize,
                    SourceReadOffset      = offset,
                    ReadSize              = readSize,
                    PixelConverterFactory = _parameters.PixelConverterFactory ?? TiffDefaultPixelConverterFactory.Instance,
                    DestinationWriter     = new TiffPixelBufferWriter <TPixel>(TiffNoopDisposablePixelBufferWriter.Wrap(writer)).Crop(destinationOffset, readSize)
                };

                await _pipeline.RunAsync(context).ConfigureAwait(false);
            }
            finally
            {
                await reader.DisposeAsync().ConfigureAwait(false);
            }
        }
Exemplo n.º 13
0
        public ImageSharpPixelBufferReader(ImageFrame <TPixel> image, TiffPoint offset, TiffSize size)
        {
            if (image is null)
            {
                throw new ArgumentNullException(nameof(image));
            }
            if (offset.X < 0)
            {
                size   = new TiffSize(size.Width + offset.X, size.Height);
                offset = new TiffPoint(0, offset.Y);
            }
            if (offset.Y < 0)
            {
                size   = new TiffSize(size.Width, size.Height + offset.Y);
                offset = new TiffPoint(offset.X, 0);
            }

            _image  = image;
            _offset = offset;
            _size   = new TiffSize(MathHelper.Clamp(size.Width, 0, image.Width), MathHelper.Clamp(size.Height, 0, image.Height));
        }
Exemplo n.º 14
0
        /// <inheritdoc />
        public override async Task <TiffStreamRegion> EncodeAsync(TiffFileWriter writer, TiffPoint offset, TiffSize size, ITiffPixelBufferReader <TPixel> reader, CancellationToken cancellationToken)
        {
            if (writer is null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            if (reader is null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            if (_imageEncoder is null)
            {
                throw new InvalidOperationException("Image encoder is not configured.");
            }

            TiffPixelBufferReader <TPixel> structReader = reader.AsPixelBufferReader();

            size = new TiffSize(Math.Max(0, Math.Min(structReader.Width - offset.X, size.Width)), Math.Max(0, Math.Min(structReader.Height - offset.Y, size.Height)));
            if (size.IsAreaEmpty)
            {
                throw new ArgumentOutOfRangeException(nameof(size), "The image size is zero.");
            }

            var context = new TiffDefaultImageEncoderContext <TPixel>
            {
                MemoryPool            = _memoryPool ?? MemoryPool <byte> .Shared,
                CancellationToken     = cancellationToken,
                FileWriter            = writer,
                ImageSize             = size,
                PixelConverterFactory = TiffDefaultPixelConverterFactory.Instance,
                PixelBufferReader     = structReader
            };

            await _imageEncoder.RunAsync(context).ConfigureAwait(false);

            return(context.OutputRegion);
        }
Exemplo n.º 15
0
        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);
                }
            }
        }
        /// <summary>
        /// Wraps the <paramref name="context"/> in a new context that handles extending the input image if either width or height of the input image is less than those specified in the constructor. Then runs the next middleware with the wrapped context.
        /// </summary>
        /// <param name="context">The encoder context.</param>
        /// <param name="next">The next middleware.</param>
        /// <returns>A <see cref="Task"/> that completes when the image has been encoded.</returns>
        public ValueTask InvokeAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (next is null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            TiffSize size = context.ImageSize;

            if (size.Width < _paddingSize.Width || size.Height < _paddingSize.Height)
            {
                size = new TiffSize(Math.Max(size.Width, _paddingSize.Width), Math.Max(size.Height, _paddingSize.Height));
                return(next.RunAsync(new PaddingContext(context, size)));
            }

            return(next.RunAsync(context));
        }
Exemplo n.º 17
0
        public override Task EncodeAsync(TiffImageFileDirectoryWriter writer, TiffPoint offset, TiffSize size, ITiffPixelBufferReader <TExposedPixel> reader, CancellationToken cancellationToken = default)
        {
            if (writer is null)
            {
                throw new ArgumentNullException(nameof(writer));
            }
            if (reader is null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            if (typeof(TExposedPixel) == typeof(TIntermediate))
            {
                return(_encoder.EncodeAsync(writer, offset, size, new TiffPassthroughPixelBufferReader <TExposedPixel, TTiffPixel>(reader), cancellationToken));
            }

            return(_encoder.EncodeAsync(writer, offset, size, new ImageSharpConversionPixelBufferReader <TExposedPixel, TIntermediate, TTiffPixel>(reader), cancellationToken));
        }
        /// <inheritdoc />
        public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // Identify this block
            JpegDecoder decoder = Interlocked.Exchange(ref _decoder, null) ?? LoadJpegDecoder();

            decoder.MemoryPool = context.MemoryPool;
            decoder.SetInput(input);
            decoder.Identify();

            // Validate we are capable of decoding this.
            TiffSize outputBufferSize = context.ImageSize;

            if (decoder.Width < outputBufferSize.Width || decoder.Height < outputBufferSize.Height)
            {
                throw new InvalidDataException("Image dimension is too small.");
            }

            // Check number of components
            if (decoder.NumberOfComponents != _numberOfComponents)
            {
                throw new InvalidDataException($"Expect {_numberOfComponents} components, but got {decoder.NumberOfComponents} components in the JPEG stream.");
            }

            JpegBlockOutputWriter outputWriter;

            if (decoder.Precision == 8)
            {
                if (context.BitsPerSample.GetFirstOrDefault() != 8)
                {
                    throw new InvalidDataException("Precision of 8 bit is not expected.");
                }
                outputWriter = new JpegBuffer8BitOutputWriter(outputBufferSize.Width, context.SkippedScanlines, context.SkippedScanlines + context.RequestedScanlines, decoder.NumberOfComponents, output);
            }
            else if (decoder.Precision < 8)
            {
                if (context.BitsPerSample.GetFirstOrDefault() != 8)
                {
                    throw new InvalidDataException($"Precision of {decoder.Precision} bit is not expected.");
                }
                outputWriter = new JpegBufferAny8BitOutputWriter(outputBufferSize.Width, context.SkippedScanlines, context.SkippedScanlines + context.RequestedScanlines, decoder.NumberOfComponents, decoder.Precision, output);
            }
            else if (decoder.Precision <= 16)
            {
                if (context.BitsPerSample.GetFirstOrDefault() != 16)
                {
                    throw new InvalidDataException($"Precision of {decoder.Precision} bit is not expected.");
                }
                outputWriter = new JpegBufferAny16BitOutputWriter(outputBufferSize.Width, context.SkippedScanlines, context.SkippedScanlines + context.RequestedScanlines, decoder.NumberOfComponents, decoder.Precision, output);
            }
            else
            {
                throw new InvalidDataException($"Precision of {decoder.Precision} bit is not expected.");
            }

            // Decode
            decoder.SetOutputWriter(outputWriter);
            decoder.Decode();

            // Reset state
            decoder.ResetInput();
            decoder.ResetHeader();
            decoder.ResetOutputWriter();

            // Cache the instances
            Interlocked.CompareExchange(ref _decoder, decoder, null);
        }
Exemplo n.º 19
0
        /// <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 (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();
            }
        }
 /// <summary>
 /// Initialize the object to wrap <paramref name="buffer"/>.
 /// </summary>
 /// <param name="buffer">The pixel buffer to wrap.</param>
 public TiffPixelBufferReaderAdapter(ITiffPixelBuffer <TPixel> buffer)
 {
     _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
     _size   = new TiffSize(buffer.Width, buffer.Height);
 }
 /// <summary>
 /// Initialize the middleware with the specified padding size.
 /// </summary>
 /// <param name="paddingSize">The padding size.</param>
 public TiffImageEncoderPaddingMiddleware(TiffSize paddingSize)
 {
     _paddingSize = paddingSize;
 }