/// <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); } } }
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); } } }
/// <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); } } } }
/// <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); }