/// <inheritdoc /> public void Compress(TiffCompressionContext context, ReadOnlyMemory <byte> input, IBufferWriter <byte> outputWriter) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.PhotometricInterpretation != TiffPhotometricInterpretation.WhiteIsZero && context.PhotometricInterpretation != TiffPhotometricInterpretation.BlackIsZero) { throw new NotSupportedException("ThunderScan compression does not support this photometric interpretation."); } if (context.BitsPerSample.Count != 1 || context.BitsPerSample[0] != 8) { throw new NotSupportedException("Unsupported bits per sample."); } context.BitsPerSample = TiffValueCollection.Single <ushort>(4); Span <byte> buffer = stackalloc byte[4]; var encoder = new ThunderScanEncoder(outputWriter, buffer); int height = context.ImageSize.Height; int bytesPerScanline = context.BytesPerScanline; ReadOnlySpan <byte> inputSpan = input.Span; for (int row = 0; row < height; row++) { ReadOnlySpan <byte> run = inputSpan.Slice(0, bytesPerScanline); inputSpan = inputSpan.Slice(bytesPerScanline); encoder.Encode(run); encoder.Reset(); } }
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); } } }
/// <inheritdoc /> public void Compress(TiffCompressionContext context, ReadOnlyMemory <byte> input, IBufferWriter <byte> outputWriter) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.PhotometricInterpretation != TiffPhotometricInterpretation.WhiteIsZero && context.PhotometricInterpretation != TiffPhotometricInterpretation.BlackIsZero) { throw new NotSupportedException("Modified Huffman compression does not support this photometric interpretation."); } if (context.BitsPerSample.Count != 1 || context.BitsPerSample[0] != 8) { throw new NotSupportedException("Unsupported bits per sample."); } context.BitsPerSample = TiffValueCollection.Single <ushort>(1); ReadOnlySpan <byte> inputSpan = input.Span; int width = context.ImageSize.Width; int height = context.ImageSize.Height; var bitWriter = new BitWriter2(outputWriter, 4096); // Process every scanline for (int row = 0; row < height; row++) { ReadOnlySpan <byte> rowSpan = inputSpan.Slice(0, width); inputSpan = inputSpan.Slice(width); CcittEncodingTable currentTable = CcittEncodingTable.WhiteInstance; CcittEncodingTable otherTable = CcittEncodingTable.BlackInstance; // ModifiedHuffman compression assumes WhiteIsZero photometric interpretation is used. // Since the first run is white run, we look for black pixel in the first iteration. byte nextRunPixel = 255; while (!rowSpan.IsEmpty) { // Get the length of the current run int runLength = rowSpan.IndexOf(nextRunPixel); if (runLength < 0) { runLength = rowSpan.Length; } currentTable.EncodeRun(ref bitWriter, runLength); rowSpan = rowSpan.Slice(runLength); // Switch to the other color CcittHelper.SwapTable(ref currentTable, ref otherTable); nextRunPixel = (byte)~nextRunPixel; } bitWriter.AdvanceAlignByte(); } bitWriter.Flush(); }
/// <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); } } }
/// <inheritdoc /> public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (next is null) { throw new ArgumentNullException(nameof(next)); } if (_predictor == TiffPredictor.None) { return(next.RunAsync(context)); } if (_predictor != TiffPredictor.HorizontalDifferencing) { throw new NotSupportedException("Predictor not supportted."); } int skipped = 0; bool isMultiplePlanar = _bytesPerScanlines.Count > 1; for (int planarIndex = 0; planarIndex < _bytesPerScanlines.Count; planarIndex++) { int bytesPerScanline = _bytesPerScanlines[planarIndex]; // Current plane buffer Span <byte> plane = context.UncompressedData.Span.Slice(skipped, bytesPerScanline * context.SourceImageSize.Height); // Skip scanlines that are not to be decoded plane = plane.Slice(bytesPerScanline * context.SourceReadOffset.Y); TiffValueCollection <ushort> bitsPerSample = isMultiplePlanar ? TiffValueCollection.Single(_bitsPerSample[planarIndex]) : _bitsPerSample; for (int row = 0; row < context.ReadSize.Height; row++) { // Process every scanline Span <byte> scanline = plane.Slice(row * bytesPerScanline, bytesPerScanline); UndoHorizontalDifferencingForScanline(scanline, bitsPerSample, context.SourceImageSize.Width); } skipped += bytesPerScanline * context.SourceImageSize.Height; } return(next.RunAsync(context)); }
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); } } }
/// <inheritdoc /> public void Compress(TiffCompressionContext context, ReadOnlyMemory <byte> input, IBufferWriter <byte> outputWriter) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.PhotometricInterpretation != TiffPhotometricInterpretation.WhiteIsZero && context.PhotometricInterpretation != TiffPhotometricInterpretation.BlackIsZero) { throw new NotSupportedException("Modified Huffman compression does not support this photometric interpretation."); } if (context.BitsPerSample.Count != 1 || context.BitsPerSample[0] != 8) { throw new NotSupportedException("Unsupported bits per sample."); } context.BitsPerSample = TiffValueCollection.Single <ushort>(1); ReadOnlySpan <byte> inputSpan = input.Span; int width = context.ImageSize.Width; int height = context.ImageSize.Height; var bitWriter = new BitWriter2(outputWriter, 4096); ReferenceScanline referenceScanline = new ReferenceScanline(whiteIsZero: true, width); // Process every scanline for (int row = 0; row < height; row++) { ReadOnlySpan <byte> scanline = inputSpan.Slice(0, width); inputSpan = inputSpan.Slice(width); Encode2DScanline(ref bitWriter, referenceScanline, scanline); referenceScanline = new ReferenceScanline(whiteIsZero: true, scanline); } bitWriter.Flush(); }
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> /// 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); }
public static async Task <int> Wrap(FileInfo source, FileInfo output, CancellationToken cancellationToken) { if (source is null || !source.Exists) { Console.WriteLine(source is null ? "Input JPEG image is not specified." : "File not found: " + source.FullName); return(1); } if (output is null) { Console.WriteLine("Output TIFF file is not specified."); return(1); } byte[] jpegFile = await File.ReadAllBytesAsync(source.FullName, cancellationToken); var decoder = new JpegDecoder(); decoder.SetInput(jpegFile); decoder.Identify(loadQuantizationTables: false); ushort[] bitsPerSample = new ushort[decoder.NumberOfComponents]; ushort bits = (ushort)decoder.Precision; for (int i = 0; i < bitsPerSample.Length; i++) { bitsPerSample[i] = bits; } TiffPhotometricInterpretation photometricInterpretation = bitsPerSample.Length == 1 ? TiffPhotometricInterpretation.BlackIsZero : bitsPerSample.Length == 3 ? TiffPhotometricInterpretation.YCbCr : throw new InvalidDataException("Photometric interpretation not supported."); using (TiffFileWriter writer = await TiffFileWriter.OpenAsync(output.FullName, useBigTiff: false)) { TiffStreamOffset imageOffset = await writer.WriteAlignedBytesAsync(jpegFile); TiffStreamOffset ifdOffset; using (TiffImageFileDirectoryWriter ifdWriter = writer.CreateImageFileDirectory()) { await ifdWriter.WriteTagAsync(TiffTag.ImageWidth, TiffValueCollection.Single((ushort)decoder.Width)); await ifdWriter.WriteTagAsync(TiffTag.ImageLength, TiffValueCollection.Single((ushort)decoder.Height)); await ifdWriter.WriteTagAsync(TiffTag.BitsPerSample, TiffValueCollection.UnsafeWrap(bitsPerSample)); await ifdWriter.WriteTagAsync(TiffTag.Compression, TiffValueCollection.Single((ushort)TiffCompression.Jpeg)); await ifdWriter.WriteTagAsync(TiffTag.PhotometricInterpretation, TiffValueCollection.Single((ushort)photometricInterpretation)); await ifdWriter.WriteTagAsync(TiffTag.SamplesPerPixel, TiffValueCollection.Single((ushort)bitsPerSample.Length)); await ifdWriter.WriteTagAsync(TiffTag.PlanarConfiguration, TiffValueCollection.Single((ushort)TiffPlanarConfiguration.Chunky)); await ifdWriter.WriteTagAsync(TiffTag.RowsPerStrip, TiffValueCollection.Single((ushort)decoder.Height)); await ifdWriter.WriteTagAsync(TiffTag.StripOffsets, TiffValueCollection.Single((uint)imageOffset.Offset)); await ifdWriter.WriteTagAsync(TiffTag.StripByteCounts, TiffValueCollection.Single((uint)jpegFile.Length)); if (photometricInterpretation == TiffPhotometricInterpretation.YCbCr) { int maxHorizontalSampling = decoder.GetMaximumHorizontalSampling(); int maxVerticalSampling = decoder.GetMaximumVerticalSampling(); int yHorizontalSubSampling = maxHorizontalSampling / decoder.GetHorizontalSampling(0); int yVerticalSubSampling = maxVerticalSampling / decoder.GetVerticalSampling(0); int cbHorizontalSubSampling = maxHorizontalSampling / decoder.GetHorizontalSampling(1) / yHorizontalSubSampling; int cbVerticalSubSampling = maxVerticalSampling / decoder.GetVerticalSampling(1) / yVerticalSubSampling; int crHorizontalSubSampling = maxHorizontalSampling / decoder.GetHorizontalSampling(2) / yHorizontalSubSampling; int crVerticalSubSampling = maxVerticalSampling / decoder.GetVerticalSampling(2) / yVerticalSubSampling; if (cbHorizontalSubSampling != crHorizontalSubSampling || cbVerticalSubSampling != crVerticalSubSampling) { throw new InvalidDataException("Unsupported JPEG image."); } await ifdWriter.WriteTagAsync(TiffTag.YCbCrSubSampling, TiffValueCollection.UnsafeWrap(new ushort[] { (ushort)cbHorizontalSubSampling, (ushort)cbVerticalSubSampling })); } // Write other properties here (eg, XResolution, YResolution) await ifdWriter.WriteTagAsync(TiffTag.XResolution, TiffValueCollection.Single(new TiffRational(96, 1))); await ifdWriter.WriteTagAsync(TiffTag.YResolution, TiffValueCollection.Single(new TiffRational(96, 1))); await ifdWriter.WriteTagAsync(TiffTag.ResolutionUnit, TiffValueCollection.Single((ushort)TiffResolutionUnit.Inch)); ifdOffset = await ifdWriter.FlushAsync(); } writer.SetFirstImageFileDirectoryOffset(ifdOffset); await writer.FlushAsync(); } return(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); } } }