Example #1
0
        /// <summary>
        /// Apply chroma subsampling to <see cref="TiffImageEncoderContext{TPixel}.UncompressedData"/>, and runs the next middleware.
        /// </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));
            }

            // Make sure we are operating on YCbCr data.
            TiffValueCollection <ushort> bitsPerSample = context.BitsPerSample;

            if (bitsPerSample.Count != 3)
            {
                throw new InvalidOperationException("Chroma subsampling can not be applied to this image.");
            }
            for (int i = 0; i < bitsPerSample.Count; i++)
            {
                if (bitsPerSample[i] != 8)
                {
                    throw new InvalidOperationException("Chroma subsampling can not be applied to this image.");
                }
            }

            if (_horizontalSubsampling > 0 && _verticalSubsampling > 0)
            {
                return(ProcessAndContinueAsync(context, next));
            }

            return(next.RunAsync(context));
        }
Example #2
0
        /// <summary>
        /// Apply the predictor to <see cref="TiffImageEncoderContext{TPixel}.UncompressedData"/>, and runs the next middleware. Writes the <see cref="TiffTag.Predictor"/> tag to IFD writer.
        /// </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 async 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));
            }

            if (_predictor == TiffPredictor.None)
            {
                await next.RunAsync(context).ConfigureAwait(false);

                return;
            }
            if (_predictor != TiffPredictor.HorizontalDifferencing)
            {
                throw new NotSupportedException("Predictor not supportted.");
            }

            TiffValueCollection <ushort> bitsPerSample = context.BitsPerSample;
            int totalBits = 0;

            for (int i = 0; i < bitsPerSample.Count; i++)
            {
                if (bitsPerSample[i] % 8 != 0)
                {
                    throw new InvalidOperationException("Horizontal differencing predictor can not be applied to this image.");
                }
                totalBits += bitsPerSample[i];
            }

            Memory <byte> pixelData = context.UncompressedData;

            int width             = context.ImageSize.Width;
            int bytesPerScanlines = (totalBits / 8) * width;
            int height            = context.ImageSize.Height;

            for (int row = 0; row < height; row++)
            {
                ApplyHorizontalDifferencingForScanline(pixelData.Slice(row * bytesPerScanlines, bytesPerScanlines), bitsPerSample, width);
            }

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

            TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter;

            if (!(ifdWriter is null))
            {
                using (await context.LockAsync().ConfigureAwait(false))
                {
                    CancellationToken cancellationToken = context.CancellationToken;
                    await ifdWriter.WriteTagAsync(TiffTag.Predictor, TiffValueCollection.Single((ushort)_predictor), cancellationToken).ConfigureAwait(false);
                }
            }
        }
            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;
            }
        public static async Task <IDisposable> LockAsync <T>(this TiffImageEncoderContext <T> context) where T : unmanaged
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            ITiffParallelMutexService?mutexService = context.GetService(typeof(ITiffParallelMutexService)) as ITiffParallelMutexService;

            if (mutexService is null)
            {
                return(NullDisposable.Instance);
            }
            return(await mutexService.LockAsync(context.CancellationToken).ConfigureAwait(false));
        }
        private async ValueTask WrapContextAndRunAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next)
        {
            await next.RunAsync(new TiffOrientatedImageEncoderContext <TPixel>(context, _orientation)).ConfigureAwait(false);

            TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter;

            if (!(ifdWriter is null))
            {
                using (await context.LockAsync().ConfigureAwait(false))
                {
                    CancellationToken cancellationToken = context.CancellationToken;
                    await ifdWriter.WriteTagAsync(TiffTag.Orientation, TiffValueCollection.Single((ushort)_orientation), cancellationToken).ConfigureAwait(false);
                }
            }
        }
Example #6
0
        private async ValueTask ProcessAndContinueAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next)
        {
            int width  = context.ImageSize.Width;
            int height = context.ImageSize.Height;

            int blockCols            = (width + _horizontalSubsampling - 1) / _horizontalSubsampling;
            int blockRows            = (height + _verticalSubsampling - 1) / _verticalSubsampling;
            int subsampledDataLength = width * height + blockCols * blockRows * 2;

            using IMemoryOwner <byte> subsampledDataHandle = (context.MemoryPool ?? MemoryPool <byte> .Shared).Rent(subsampledDataLength);
            Memory <byte> subsampledData = subsampledDataHandle.Memory.Slice(0, subsampledDataLength);

            ProcessChunkyData(context.ImageSize, context.UncompressedData.Span, subsampledData.Span);
            context.UncompressedData = subsampledData;
            await next.RunAsync(context).ConfigureAwait(false);
        }
Example #7
0
        public ValueTask RunAsync(TiffImageEncoderContext <TPixel> context)
        {
            ITiffImageEncoderMiddleware <TPixel>   middleware = Middleware;
            ITiffImageEncoderPipelineNode <TPixel>?next       = Next;

            context.CancellationToken.ThrowIfCancellationRequested();

            if (next is null)
            {
                return(middleware.InvokeAsync(context, EmptyImplementation.Instance));
            }
            else
            {
                return(middleware.InvokeAsync(context, next));
            }
        }
Example #8
0
        public async ValueTask InvokeAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next)
        {
            using var state = new TiffParallelEncodingState(_maxDegreeOfParallelism);

            using var mutexService = new ParallelMutexService();

            context.RegisterService(typeof(TiffParallelEncodingState), state);
            context.RegisterService(typeof(ITiffParallelMutexService), mutexService);

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

            await state.Complete !.Task.ConfigureAwait(false);

            context.RegisterService(typeof(TiffParallelEncodingState), null);
            context.RegisterService(typeof(ITiffParallelMutexService), null);
        }
        /// <summary>
        /// Wraps <paramref name="context"/> in a new context with the updated <see cref="TiffImageEncoderContext{TPixel}.ImageSize"/> as well as a wrapped reader that handles orientation. Then runs the <paramref name="next"/> middleware with the replaced context. Writes the <see cref="TiffTag.Orientation"/> tag to the IFD writer.
        /// </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));
            }

            if (_orientation == 0)
            {
                return(next.RunAsync(context));
            }

            return(WrapContextAndRunAsync(context, next));
        }
        /// <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));
        }
 public TiffDelegatingImageEncoderContext(TiffImageEncoderContext <TPixel> innerContext)
 {
     _innerContext = innerContext;
 }
Example #12
0
        /// <summary>
        /// Apply compression to <see cref="TiffImageEncoderContext{TPixel}.UncompressedData"/> and writes the compressed image to <see cref="TiffImageEncoderContext{TPixel}.FileWriter"/>. Writes <see cref="TiffTag.Compression"/> to thhe IFD writer and runs the next middleware.
        /// </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 async 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));
            }
            if (context.FileWriter is null)
            {
                throw new InvalidOperationException("Failed to acquire FileWriter");
            }

            TiffValueCollection <ushort> bitsPerSample = context.BitsPerSample;
            int totalBits = 0;

            for (int i = 0; i < bitsPerSample.Count; i++)
            {
                totalBits += bitsPerSample[i];
            }

            int width             = context.ImageSize.Width;
            int bytesPerScanlines = (totalBits * width + 7) / 8;

            var compressionContext = new TiffCompressionContext
            {
                MemoryPool = context.MemoryPool,
                PhotometricInterpretation = context.PhotometricInterpretation,
                ImageSize        = context.ImageSize,
                BitsPerSample    = context.BitsPerSample,
                BytesPerScanline = bytesPerScanlines
            };

            using (var bufferWriter = new MemoryPoolBufferWriter(context.MemoryPool))
            {
                _compressionAlgorithm.Compress(compressionContext, context.UncompressedData, bufferWriter);
                int length = bufferWriter.Length;

                using (await context.LockAsync().ConfigureAwait(false))
                {
                    TiffStreamOffset offset = await context.FileWriter !.WriteAlignedBytesAsync(bufferWriter.GetReadOnlySequence(), context.CancellationToken).ConfigureAwait(false);
                    context.BitsPerSample = compressionContext.BitsPerSample;
                    context.OutputRegion  = new TiffStreamRegion(offset, length);
                }
            }

            TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter;

            if (!(ifdWriter is null))
            {
                using (await context.LockAsync().ConfigureAwait(false))
                {
                    CancellationToken cancellationToken = context.CancellationToken;
                    await ifdWriter.WriteTagAsync(TiffTag.Compression, TiffValueCollection.Single((ushort)_compression), cancellationToken).ConfigureAwait(false);
                }
            }

            await next.RunAsync(context).ConfigureAwait(false);
        }
 public TiffCroppedImageEncoderContext(TiffImageEncoderContext <TPixel> innerContext) : base(innerContext)
 {
 }
Example #14
0
        /// <summary>
        /// Crops the input image into multiple strips and runs the next middleware for each strip.
        /// </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 async 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));
            }

            var state = context.GetService(typeof(TiffParallelEncodingState)) as TiffParallelEncodingState;
            TiffCroppedImageEncoderContext <TPixel>?wrappedContext = null;

            int width = context.ImageSize.Width, height = context.ImageSize.Height;
            int rowsPerStrip = _rowsPerStrip <= 0 ? height : _rowsPerStrip;
            int stripCount   = (height + rowsPerStrip - 1) / rowsPerStrip;

            ulong[] stripOffsets    = new ulong[stripCount];
            ulong[] stripByteCounts = new ulong[stripCount];

            state?.LockTaskCompletion();

            for (int i = 0; i < stripCount; i++)
            {
                int offsetY     = i * rowsPerStrip;
                int stripHeight = Math.Min(height - offsetY, rowsPerStrip);

                wrappedContext ??= new TiffCroppedImageEncoderContext <TPixel>(context);

                wrappedContext.ExposeIfdWriter = i == 0;
                wrappedContext.OutputRegion    = default;
                wrappedContext.Crop(new TiffPoint(0, offsetY), new TiffSize(width, stripHeight));

                if (state is null)
                {
                    await next.RunAsync(wrappedContext).ConfigureAwait(false);

                    stripOffsets[i]    = (ulong)(long)wrappedContext.OutputRegion.Offset;
                    stripByteCounts[i] = (ulong)wrappedContext.OutputRegion.Length;
                }
                else
                {
                    TiffCroppedImageEncoderContext <TPixel>?wContext = wrappedContext;
                    wrappedContext = null;
                    int currentIndex = i;
                    await state.DispatchAsync(async() =>
                    {
                        await next.RunAsync(wContext).ConfigureAwait(false);
                        stripOffsets[currentIndex]    = (ulong)(long)wContext.OutputRegion.Offset;
                        stripByteCounts[currentIndex] = (ulong)wContext.OutputRegion.Length;
                    }, context.CancellationToken).ConfigureAwait(false);
                }
            }

            // Wait until all strips are written.
            if (!(state is null))
            {
                state.ReleaseTaskCompletion();
                await state.Complete.Task.ConfigureAwait(false);
            }

            TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter;

            if (!(ifdWriter is null))
            {
                CancellationToken cancellationToken = context.CancellationToken;

                await ifdWriter.WriteTagAsync(TiffTag.ImageWidth, TiffValueCollection.Single((uint)width), cancellationToken).ConfigureAwait(false);

                await ifdWriter.WriteTagAsync(TiffTag.ImageLength, TiffValueCollection.Single((uint)height), cancellationToken).ConfigureAwait(false);

                await ifdWriter.WriteTagAsync(TiffTag.RowsPerStrip, TiffValueCollection.Single((ushort)rowsPerStrip), cancellationToken).ConfigureAwait(false);

                if (context.FileWriter?.UseBigTiff ?? false)
                {
                    await ifdWriter.WriteTagAsync(TiffTag.StripOffsets, TiffValueCollection.UnsafeWrap(stripOffsets), cancellationToken).ConfigureAwait(false);

                    await ifdWriter.WriteTagAsync(TiffTag.StripByteCounts, TiffValueCollection.UnsafeWrap(stripByteCounts), cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    uint[] stripOffsets32    = new uint[stripCount];
                    uint[] stripByteCounts32 = new uint[stripCount];

                    for (int i = 0; i < stripCount; i++)
                    {
                        stripOffsets32[i]    = (uint)stripOffsets[i];
                        stripByteCounts32[i] = (uint)stripByteCounts[i];
                    }

                    await ifdWriter.WriteTagAsync(TiffTag.StripOffsets, TiffValueCollection.UnsafeWrap(stripOffsets32), cancellationToken).ConfigureAwait(false);

                    await ifdWriter.WriteTagAsync(TiffTag.StripByteCounts, TiffValueCollection.UnsafeWrap(stripByteCounts32), cancellationToken).ConfigureAwait(false);
                }
            }
        }
 protected void UpdateContext(TiffImageEncoderContext <TPixel> innerContext)
 {
     _innerContext = innerContext;
 }
Example #16
0
 public ValueTask RunAsync(TiffImageEncoderContext <TPixel> context)
 {
     return(default);
Example #17
0
        /// <summary>
        /// Crops the input image into tiles and runs the next middleware for each tile.
        /// </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 async 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));
            }

            var state = context.GetService(typeof(TiffParallelEncodingState)) as TiffParallelEncodingState;
            TiffCroppedImageEncoderContext <TPixel>?wrappedContext = null;

            int width = context.ImageSize.Width, height = context.ImageSize.Height;
            int tileWidth = _tileSize.Width, tileHeight = _tileSize.Height;

            int tileAcross = (width + tileWidth - 1) / tileWidth;
            int tileDown   = (height + tileHeight - 1) / tileHeight;
            int tileCount  = tileAcross * tileDown;

            ulong[] tileOffsets    = new ulong[tileCount];
            ulong[] tileByteCounts = new ulong[tileCount];
            int     index          = 0;

            state?.LockTaskCompletion();

            for (int row = 0; row < tileDown; row++)
            {
                int offsetY     = row * tileHeight;
                int imageHeight = Math.Min(height - offsetY, tileHeight);

                for (int col = 0; col < tileAcross; col++)
                {
                    int offsetX    = col * tileWidth;
                    int imageWidth = Math.Min(width - offsetX, tileWidth);

                    wrappedContext ??= new TiffCroppedImageEncoderContext <TPixel>(context);

                    wrappedContext.ExposeIfdWriter = row == 0 && col == 0;
                    wrappedContext.OutputRegion    = default;
                    wrappedContext.Crop(new TiffPoint(offsetX, offsetY), new TiffSize(imageWidth, imageHeight));

                    if (state is null)
                    {
                        await next.RunAsync(wrappedContext).ConfigureAwait(false);

                        tileOffsets[index]    = (ulong)(long)wrappedContext.OutputRegion.Offset;
                        tileByteCounts[index] = (ulong)wrappedContext.OutputRegion.Length;
                    }
                    else
                    {
                        TiffCroppedImageEncoderContext <TPixel>?wContext = wrappedContext;
                        wrappedContext = null;
                        int currentIndex = index;
                        await state.DispatchAsync(async() =>
                        {
                            await next.RunAsync(wContext).ConfigureAwait(false);
                            tileOffsets[currentIndex]    = (ulong)(long)wContext.OutputRegion.Offset;
                            tileByteCounts[currentIndex] = (ulong)wContext.OutputRegion.Length;
                        }, context.CancellationToken).ConfigureAwait(false);
                    }

                    index++;
                }
            }

            // Wait until all tiles are written.
            if (!(state is null))
            {
                state.ReleaseTaskCompletion();
                await state.Complete.Task.ConfigureAwait(false);
            }

            TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter;

            if (!(ifdWriter is null))
            {
                using (await context.LockAsync().ConfigureAwait(false))
                {
                    CancellationToken cancellationToken = context.CancellationToken;

                    await ifdWriter.WriteTagAsync(TiffTag.ImageWidth, TiffValueCollection.Single((uint)width), cancellationToken).ConfigureAwait(false);

                    await ifdWriter.WriteTagAsync(TiffTag.ImageLength, TiffValueCollection.Single((uint)height), cancellationToken).ConfigureAwait(false);

                    await ifdWriter.WriteTagAsync(TiffTag.TileWidth, TiffValueCollection.Single((ushort)tileWidth), cancellationToken).ConfigureAwait(false);

                    await ifdWriter.WriteTagAsync(TiffTag.TileLength, TiffValueCollection.Single((ushort)tileHeight), cancellationToken).ConfigureAwait(false);

                    if (context.FileWriter?.UseBigTiff ?? false)
                    {
                        await ifdWriter.WriteTagAsync(TiffTag.TileOffsets, TiffValueCollection.UnsafeWrap(tileOffsets), cancellationToken).ConfigureAwait(false);

                        await ifdWriter.WriteTagAsync(TiffTag.TileByteCounts, TiffValueCollection.UnsafeWrap(tileByteCounts), cancellationToken).ConfigureAwait(false);
                    }
                    else
                    {
                        uint[] tileOffsets32    = new uint[tileCount];
                        uint[] tileByteCounts32 = new uint[tileCount];

                        for (int i = 0; i < tileCount; i++)
                        {
                            tileOffsets32[i]    = (uint)tileOffsets[i];
                            tileByteCounts32[i] = (uint)tileByteCounts[i];
                        }

                        await ifdWriter.WriteTagAsync(TiffTag.TileOffsets, TiffValueCollection.UnsafeWrap(tileOffsets32), cancellationToken).ConfigureAwait(false);

                        await ifdWriter.WriteTagAsync(TiffTag.TileByteCounts, TiffValueCollection.UnsafeWrap(tileByteCounts32), cancellationToken).ConfigureAwait(false);
                    }
                }
            }
        }
Example #18
0
        public TiffOrientatedImageEncoderContext(TiffImageEncoderContext <TPixel> innerContext, TiffOrientation orientation) : base(innerContext)
        {
            if (orientation == 0)
            {
                _isFlipOrOrientation = false;
                _flipLeftRigt        = false;
                _flipTopBottom       = false;
                return;
            }
            switch (orientation)
            {
            case TiffOrientation.TopLeft:
                _isFlipOrOrientation = false;
                _flipLeftRigt        = false;
                _flipTopBottom       = false;
                break;

            case TiffOrientation.TopRight:
                _isFlipOrOrientation = false;
                _flipLeftRigt        = true;
                _flipTopBottom       = false;
                break;

            case TiffOrientation.BottomRight:
                _isFlipOrOrientation = false;
                _flipLeftRigt        = true;
                _flipTopBottom       = true;
                break;

            case TiffOrientation.BottomLeft:
                _isFlipOrOrientation = false;
                _flipLeftRigt        = false;
                _flipTopBottom       = true;
                break;

            case TiffOrientation.LeftTop:
                _isFlipOrOrientation = true;
                _flipLeftRigt        = false;
                _flipTopBottom       = false;
                break;

            case TiffOrientation.RightTop:
                _isFlipOrOrientation = true;
                _flipLeftRigt        = true;
                _flipTopBottom       = false;
                break;

            case TiffOrientation.RightBottom:
                _isFlipOrOrientation = true;
                _flipLeftRigt        = true;
                _flipTopBottom       = true;
                break;

            case TiffOrientation.LeftBottom:
                _isFlipOrOrientation = true;
                _flipLeftRigt        = false;
                _flipTopBottom       = true;
                break;

            default:
                throw new InvalidDataException("Unknown orientation tag.");
            }
        }