Example #1
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);
                }
            }
        }
Example #2
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 #3
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 ValueTask InvokeAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next)
            {
                TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter;

                if (!(ifdWriter is null) && (_useSharedHuffmanTables || _useSharedQuantizationTables))
                {
                    return(new ValueTask(WriteTablesAndContinueAsync(context, next)));
                }

                return(next.RunAsync(context));
            }
Example #5
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 #6
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);
        }
Example #7
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);
                }
            }
        }
        /// <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));
        }
Example #10
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);
                }
            }
        }
Example #11
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 #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);
        }
            private async Task WriteTablesAndContinueAsync(TiffImageEncoderContext <TPixel> context, ITiffImageEncoderPipelineNode <TPixel> next)
            {
                TiffImageFileDirectoryWriter?ifdWriter = context.IfdWriter;

                using (await context.LockAsync().ConfigureAwait(false))
                {
                    if (_jpegTables is null)
                    {
                        InitializeTables();
                    }
                    byte[]? jpegTables = _jpegTables;

                    Debug.Assert(ifdWriter != null);
                    Debug.Assert(jpegTables != null);
                    await ifdWriter !.WriteTagAsync(TiffTag.JPEGTables, TiffFieldType.Undefined, TiffValueCollection.UnsafeWrap(jpegTables !)).ConfigureAwait(false);
                }

                await next.RunAsync(context).ConfigureAwait(false);
            }
Example #14
0
 private ITiffImageEncoderPipelineNode <TPixel> PrependOrientationMiddleware <TPixel>(ITiffImageEncoderPipelineNode <TPixel> node) where TPixel : unmanaged
 {
     return(new TiffImageEncoderPipelineNode <TPixel>(new TiffApplyOrientationMiddleware <TPixel>(Orientation))
     {
         Next = node
     });
 }
Example #15
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);
                }
            }
        }
Example #16
0
 /// <summary>
 /// Initialize the adapter with the specified pipelines.
 /// </summary>
 /// <param name="memoryPool">The memory pool to use when allocating large chunk of memory.</param>
 /// <param name="imageEncoder">The pipeline to use for encoding a single image.</param>
 /// <param name="ifdEncoder">The pipeline to use for encoding an IFD.</param>
 public TiffImageEncoderPipelineAdapter(MemoryPool <byte>?memoryPool, ITiffImageEncoderPipelineNode <TPixel> imageEncoder, ITiffImageEncoderPipelineNode <TPixel> ifdEncoder)
 {
     _memoryPool   = memoryPool;
     _imageEncoder = imageEncoder;
     _ifdEncoder   = ifdEncoder;
 }
        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 #18
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 #19
0
        /// <summary>
        /// Build the <see cref="TiffImageEncoder{TPixel}"/> instance with the specified pixel format of input image.
        /// </summary>
        /// <typeparam name="TPixel">The pixel type of the input image.</typeparam>
        /// <returns>The <see cref="TiffImageEncoder{TPixel}"/> instance.</returns>
        public TiffImageEncoder <TPixel> Build <TPixel>() where TPixel : unmanaged
        {
            var pipelineBuilder = new TiffImageEncoderPipelineBuilder <TPixel>();

            bool useHorizontalDifferencingPredictor = Predictor == TiffPredictor.HorizontalDifferencing;

            switch (PhotometricInterpretation)
            {
            case TiffPhotometricInterpretation.WhiteIsZero:
                pipelineBuilder.Add(new WhiteIsZero8Encoder <TPixel>());
                break;

            case TiffPhotometricInterpretation.BlackIsZero:
                pipelineBuilder.Add(new BlackIsZero8Encoder <TPixel>());
                break;

            case TiffPhotometricInterpretation.RGB:
                if (EnableTransparencyForRgb)
                {
                    pipelineBuilder.Add(new Rgba32Encoder <TPixel>());
                }
                else
                {
                    pipelineBuilder.Add(new Rgb24Encoder <TPixel>());
                }
                break;

            case TiffPhotometricInterpretation.TransparencyMask:
                pipelineBuilder.Add(new TransparencyMaskEncoder <TPixel>(threshold: 127));
                break;

            case TiffPhotometricInterpretation.Seperated:
                pipelineBuilder.Add(new Cmyk32Encoder <TPixel>());
                break;

            case TiffPhotometricInterpretation.YCbCr:
                pipelineBuilder.Add(new YCbCr24Encoder <TPixel>());
                break;

            default:
                throw new NotSupportedException("The selected photometric interpretation is not supported.");
            }

            if (useHorizontalDifferencingPredictor)
            {
                pipelineBuilder.Add(new TiffApplyPredictorMiddleware <TPixel>(TiffPredictor.HorizontalDifferencing));
            }

            int horizontalSubsampling = HorizontalChromaSubSampling;
            int verticalSubsampling   = VerticalChromaSubSampling;

            if (PhotometricInterpretation == TiffPhotometricInterpretation.YCbCr)
            {
                horizontalSubsampling = horizontalSubsampling == 0 ? 1 : horizontalSubsampling;
                verticalSubsampling   = verticalSubsampling == 0 ? 1 : verticalSubsampling;

                if (horizontalSubsampling != 1 && horizontalSubsampling != 2 && horizontalSubsampling != 4)
                {
                    throw new InvalidOperationException("HorizontalChromaSubSampling can only be 1, 2, or 4.");
                }
                if (verticalSubsampling != 1 && verticalSubsampling != 2 && verticalSubsampling != 4)
                {
                    throw new InvalidOperationException("VerticalChromaSubSampling can only be 1, 2, or 4.");
                }

                var chromaSubsamplingMiddleware = new TiffApplyChromaSubsamplingMiddleware <TPixel>(horizontalSubsampling, verticalSubsampling);
                if (Compression != TiffCompression.Jpeg && (horizontalSubsampling > 1 || verticalSubsampling > 1))
                {
                    pipelineBuilder.Add(chromaSubsamplingMiddleware);
                }
                pipelineBuilder.Add(chromaSubsamplingMiddleware.GetFieldWriter());
            }

            switch (Compression)
            {
            case 0:
            case TiffCompression.NoCompression:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(TiffCompression.NoCompression, NoneCompressionAlgorithm.Instance));
                break;

            case TiffCompression.ModifiedHuffmanCompression:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, ModifiedHuffmanCompressionAlgorithm.GetSharedInstance(TiffFillOrder.HigherOrderBitsFirst)));
                break;

            case TiffCompression.T4Encoding:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, CcittGroup3OneDimensionalCompressionAlgorithm.GetSharedInstance(TiffFillOrder.HigherOrderBitsFirst)));
                break;

            case TiffCompression.T6Encoding:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, CcittGroup4Compression.GetSharedInstance(TiffFillOrder.HigherOrderBitsFirst)));
                break;

            case TiffCompression.Lzw:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, LzwCompressionAlgorithm.Instance));
                break;

            case TiffCompression.Deflate:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, DeflateCompressionLevel == TiffDeflateCompressionLevel.Default ? DeflateCompressionAlgorithm.Instance : new DeflateCompressionAlgorithm(DeflateCompressionLevel)));
                break;

            case TiffCompression.Jpeg:
                TiffJpegEncodingOptions jpegOptions = JpegOptions ?? TiffJpegEncodingOptions.Default;
                if ((uint)jpegOptions.Quality > 100)
                {
                    throw new InvalidOperationException("JpegQuality should be set between 0 and 100.");
                }
                var jpegCompressionAlgorithm = new JpegCompressionAlgorithm(PhotometricInterpretation, horizontalSubsampling, verticalSubsampling, jpegOptions);
                pipelineBuilder.Add(jpegCompressionAlgorithm.GetTableWriter <TPixel>());
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, jpegCompressionAlgorithm));
                break;

            case TiffCompression.PackBits:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, PackBitsCompressionAlgorithm.Instance));
                break;

            case TiffCompression.ThunderScan:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, ThunderScanCompressionAlgorithm.Instance));
                break;

            case TiffCompression.NeXT:
                pipelineBuilder.Add(new TiffImageCompressionMiddleware <TPixel>(Compression, NeXTCompressionAlgorithm.Instance));
                break;

            case TiffCompression.OldDeflate:
                throw new NotSupportedException("Legacy Deflate compression is not supported. Use TiffCompression.Deflate instead.");

            case TiffCompression.OldJpeg:
                throw new NotSupportedException("Legacy JPEG compression is not supported. Use TiffCompression.Jpeg instead.");

            default:
                throw new NotSupportedException("The selected compression algorithm is not supported.");
            }

            ITiffImageEncoderPipelineNode <TPixel> imageEncoder = pipelineBuilder.Build();

            if (IsTiled)
            {
                pipelineBuilder.InsertFirst(new TiffImageEncoderPaddingMiddleware <TPixel>(TileSize));
                pipelineBuilder.InsertFirst(new TiffTiledImageEncoderEnumeratorMiddleware <TPixel>(TileSize));
            }
            else
            {
                pipelineBuilder.InsertFirst(new TiffStrippedImageEncoderEnumeratorMiddleware <TPixel>(RowsPerStrip));
            }

            if (MaxDegreeOfParallelism > 1)
            {
                pipelineBuilder.InsertFirst(new TiffParallelStarterMiddleware <TPixel>(MaxDegreeOfParallelism));
            }

            ITiffImageEncoderPipelineNode <TPixel> ifdEncoder = pipelineBuilder.Build();

            if (Orientation != 0)
            {
                imageEncoder = PrependOrientationMiddleware(imageEncoder);
                ifdEncoder   = PrependOrientationMiddleware(ifdEncoder);
            }

            return(new TiffImageEncoderPipelineAdapter <TPixel>(MemoryPool, imageEncoder, ifdEncoder));
        }