/// <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; int arraySize = 4 * imageSize.Width * imageSize.Height; using (IMemoryOwner <byte> pixelData = memoryPool.Rent(arraySize)) { using (var writer = new TiffMemoryPixelBufferWriter <TiffRgba32>(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.RGB; context.BitsPerSample = TiffValueCollection.UnsafeWrap(s_bitsPerSample); 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); await ifdWriter.WriteTagAsync(TiffTag.SamplesPerPixel, TiffValueCollection.Single <ushort>(4)).ConfigureAwait(false); await ifdWriter.WriteTagAsync(TiffTag.ExtraSamples, TiffValueCollection.Single((ushort)TiffExtraSample.UnassociatedAlphaData)).ConfigureAwait(false); } } }
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); } } }
public void TestSingle() { var singleVal = new TiffValueCollection <int>(42); Assert.Single(singleVal); Assert.False(singleVal.IsEmpty); Assert.True(singleVal.Count == 1); Assert.Equal(42, singleVal[0]); Assert.Throws <IndexOutOfRangeException>(() => singleVal[-1]); Assert.Throws <IndexOutOfRangeException>(() => singleVal[2]); var singleValZero = new TiffValueCollection <int>(0); Assert.Single(singleVal); Assert.False(singleVal.IsEmpty); Assert.True(singleVal.Count == 1); Assert.Equal(0, singleValZero[0]); Assert.Throws <IndexOutOfRangeException>(() => singleValZero[-1]); Assert.Throws <IndexOutOfRangeException>(() => singleValZero[2]); var singleRef = new TiffValueCollection <string>("hello world."); Assert.Single(singleRef); Assert.False(singleRef.IsEmpty); Assert.True(singleRef.Count == 1); Assert.Equal("hello world.", singleRef[0]); Assert.Throws <IndexOutOfRangeException>(() => singleValZero[-1]); Assert.Throws <IndexOutOfRangeException>(() => singleValZero[2]); var singleRefNull = new TiffValueCollection <string>((string)null); Assert.Single(singleRefNull); Assert.False(singleRefNull.IsEmpty); Assert.True(singleRefNull.Count == 1); Assert.Null(singleRefNull[0]); Assert.Throws <IndexOutOfRangeException>(() => singleRefNull[-1]); Assert.Throws <IndexOutOfRangeException>(() => singleRefNull[2]); }
private static void DumpValueCollecionSimple <T>(TiffValueCollection <T> values) { Console.Write("["); if (values.IsEmpty) { // Do nothing } else if (values.Count == 1) { Console.Write(values.GetFirstOrDefault()); } else { for (int i = 0; i < values.Count; i++) { Console.Write(values[i]); if (i != values.Count - 1) { Console.Write(", "); } } } Console.Write("]"); }
/// <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; // EOL code bitWriter.Write(0b000000000001, 12); // 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; } } // RTC bitWriter.Write(0b000000000001, 12); bitWriter.Write(0b000000000001, 12); bitWriter.Write(0b000000000001, 12); bitWriter.Write(0b000000000001, 12); bitWriter.Write(0b000000000001, 12); bitWriter.Write(0b000000000001, 12); bitWriter.AdvanceAlignByte(); bitWriter.Flush(); }
private static async Task TestValidConversionAsync <T>(TiffFieldReader fieldReader, TiffImageFileDirectoryEntry entry, string methodName, T[] refData) where T : unmanaged { MethodInfo method1 = typeof(TiffFieldReader).GetMethod(methodName, new Type[] { typeof(TiffImageFileDirectoryEntry), typeof(bool), typeof(CancellationToken) }); MethodInfo method2 = typeof(TiffFieldReader).GetMethod(methodName, new Type[] { typeof(TiffImageFileDirectoryEntry), typeof(int), typeof(bool), typeof(CancellationToken) }); MethodInfo method3 = typeof(TiffFieldReader).GetMethod(methodName, new Type[] { typeof(TiffImageFileDirectoryEntry), typeof(int), typeof(Memory <T>), typeof(bool), typeof(CancellationToken) }); Assert.NotNull(method1); Assert.NotNull(method2); Assert.NotNull(method3); var delegate1 = (Func <TiffImageFileDirectoryEntry, bool, CancellationToken, ValueTask <TiffValueCollection <T> > >)method1.CreateDelegate(typeof(Func <TiffImageFileDirectoryEntry, bool, CancellationToken, ValueTask <TiffValueCollection <T> > >), fieldReader); var delegate2 = (Func <TiffImageFileDirectoryEntry, int, bool, CancellationToken, ValueTask <TiffValueCollection <T> > >)method2.CreateDelegate(typeof(Func <TiffImageFileDirectoryEntry, int, bool, CancellationToken, ValueTask <TiffValueCollection <T> > >), fieldReader); var delegate3 = (Func <TiffImageFileDirectoryEntry, int, Memory <T>, bool, CancellationToken, ValueTask>)method3.CreateDelegate(typeof(Func <TiffImageFileDirectoryEntry, int, Memory <T>, bool, CancellationToken, ValueTask>), fieldReader); // Test basic overload TiffValueCollection <T> testData = await delegate1(entry, false, default); Assert.True(MemoryMarshal.AsBytes(refData.AsSpan()).SequenceEqual(MemoryMarshal.AsBytes(testData.ToArray().AsSpan()))); // Test overload with sizeLimit argument int sizeLimit = refData.Length / 2; testData = await delegate2(entry, sizeLimit, false, default); Assert.Equal(sizeLimit, testData.Count); Assert.True(MemoryMarshal.AsBytes(refData.AsSpan(0, sizeLimit)).SequenceEqual(MemoryMarshal.AsBytes(testData.ToArray().AsSpan()))); // Test overload with external buffer int offset = refData.Length / 4; T[] testArray = new T[sizeLimit]; await delegate3(entry, offset, testArray, false, default); Assert.True(MemoryMarshal.AsBytes(refData.AsSpan(offset, sizeLimit)).SequenceEqual(MemoryMarshal.AsBytes(testArray.AsSpan()))); // Test invalid parameter Assert.Throws <ArgumentOutOfRangeException>("offset", () => delegate3(entry, -1, new T[entry.ValueCount], false, default)); Assert.Throws <ArgumentOutOfRangeException>("offset", () => delegate3(entry, (int)(entry.ValueCount + 1), new T[1], false, default)); Assert.Throws <ArgumentOutOfRangeException>("destination", () => delegate3(entry, 0, new T[entry.ValueCount + 1], false, default)); // Now do it again with the sync version if (methodName.EndsWith("Async", StringComparison.Ordinal)) { methodName = methodName.Substring(0, methodName.Length - 5); MethodInfo method4 = typeof(TiffFieldReader).GetMethod(methodName, new Type[] { typeof(TiffImageFileDirectoryEntry), typeof(bool) }); MethodInfo method5 = typeof(TiffFieldReader).GetMethod(methodName, new Type[] { typeof(TiffImageFileDirectoryEntry), typeof(int), typeof(bool) }); MethodInfo method6 = typeof(TiffFieldReader).GetMethod(methodName, new Type[] { typeof(TiffImageFileDirectoryEntry), typeof(int), typeof(Memory <T>), typeof(bool) }); Assert.NotNull(method4); Assert.NotNull(method5); Assert.NotNull(method6); var delegate4 = (Func <TiffImageFileDirectoryEntry, bool, TiffValueCollection <T> >)method4.CreateDelegate(typeof(Func <TiffImageFileDirectoryEntry, bool, TiffValueCollection <T> >), fieldReader); var delegate5 = (Func <TiffImageFileDirectoryEntry, int, bool, TiffValueCollection <T> >)method5.CreateDelegate(typeof(Func <TiffImageFileDirectoryEntry, int, bool, TiffValueCollection <T> >), fieldReader); var delegate6 = (Action <TiffImageFileDirectoryEntry, int, Memory <T>, bool>)method6.CreateDelegate(typeof(Action <TiffImageFileDirectoryEntry, int, Memory <T>, bool>), fieldReader); // Test basic overload testData = delegate4(entry, false); Assert.True(MemoryMarshal.AsBytes(refData.AsSpan()).SequenceEqual(MemoryMarshal.AsBytes(testData.ToArray().AsSpan()))); // Test overload with sizeLimit argument sizeLimit = refData.Length / 2; testData = delegate5(entry, sizeLimit, false); Assert.Equal(sizeLimit, testData.Count); Assert.True(MemoryMarshal.AsBytes(refData.AsSpan(0, sizeLimit)).SequenceEqual(MemoryMarshal.AsBytes(testData.ToArray().AsSpan()))); // Test overload with external buffer offset = refData.Length / 4; testArray = new T[sizeLimit]; delegate6(entry, offset, testArray, false); Assert.True(MemoryMarshal.AsBytes(refData.AsSpan(offset, sizeLimit)).SequenceEqual(MemoryMarshal.AsBytes(testArray.AsSpan()))); // Test invalid parameter Assert.Throws <ArgumentOutOfRangeException>("offset", () => delegate6(entry, -1, new T[entry.ValueCount], false)); Assert.Throws <ArgumentOutOfRangeException>("offset", () => delegate6(entry, (int)(entry.ValueCount + 1), new T[1], false)); Assert.Throws <ArgumentOutOfRangeException>("destination", () => delegate6(entry, 0, new T[entry.ValueCount + 1], false)); } }
private static void UndoHorizontalDifferencingForScanline(Span <byte> scanline, TiffValueCollection <ushort> bitsPerSample, int width) { if (width <= 1) { return; } int sampleCount = bitsPerSample.Count; if (sampleCount > 8) { throw new NotSupportedException("Too many samples."); } Span <ushort> bitsPerSampleSpan = stackalloc ushort[8]; ref ushort bitsPerSampleSpanRef = ref MemoryMarshal.GetReference(bitsPerSampleSpan);
/// <summary> /// Initialize the middleware. /// </summary> /// <param name="coefficients">The YCbCrCoefficients tag.</param> /// <param name="referenceBlackWhite">The ReferenceBlackWhite tag.</param> public TiffChunkyYCbCr161616Interpreter(TiffValueCollection <TiffRational> coefficients, TiffValueCollection <TiffRational> referenceBlackWhite) { if (!coefficients.IsEmpty && coefficients.Count != 3) { throw new ArgumentException("coefficient should have 3 none-zero elements."); } if (!referenceBlackWhite.IsEmpty && referenceBlackWhite.Count != 6) { throw new ArgumentException("referenceWhiteBlack should have 6 elements."); } _converter = TiffYCbCrConverter16.Create(coefficients.GetOrCreateArray(), referenceBlackWhite.GetOrCreateArray()); }
private static async Task DumpIfdEntryAsync(int index, TiffFieldReader fieldReader, TiffImageFileDirectoryEntry entry, CancellationToken cancellationToken) { string tagName = Enum.IsDefined(typeof(TiffTag), entry.Tag) ? $"{entry.Tag} ({(int)entry.Tag})" : ((int)entry.Tag).ToString(); string typeName = Enum.IsDefined(typeof(TiffFieldType), entry.Type) ? entry.Type.ToString() : "Unknown"; Console.Write($" Tag #{index}: {tagName}, {typeName}[{entry.ValueCount}]."); switch (entry.Type) { case TiffFieldType.Byte: Console.Write(" Binary data not shown."); break; case TiffFieldType.ASCII: TiffValueCollection <string> valuesAscii = await fieldReader.ReadASCIIFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); if (valuesAscii.IsEmpty) { // Do nothing } else if (valuesAscii.Count == 1) { Console.Write(" Value = " + valuesAscii.GetFirstOrDefault()); } else { Console.WriteLine(); for (int i = 0; i < valuesAscii.Count; i++) { Console.Write($" [{i}] = {valuesAscii[i]}"); } } break; case TiffFieldType.Short: TiffValueCollection <ushort> valuesShort = await fieldReader.ReadShortFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesShort); break; case TiffFieldType.Long: TiffValueCollection <uint> valuesLong = await fieldReader.ReadLongFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesLong); break; case TiffFieldType.Rational: TiffValueCollection <TiffRational> valuesRational = await fieldReader.ReadRationalFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesRational); break; case TiffFieldType.SByte: Console.Write(" Binary data not shown."); break; case TiffFieldType.Undefined: Console.Write(" Binary data not shown."); break; case TiffFieldType.SShort: TiffValueCollection <short> valuesSShort = await fieldReader.ReadSShortFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesSShort); break; case TiffFieldType.SLong: TiffValueCollection <int> valuesSLong = await fieldReader.ReadSLongFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesSLong); break; case TiffFieldType.SRational: TiffValueCollection <TiffSRational> valuesSRational = await fieldReader.ReadSRationalFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesSRational); break; case TiffFieldType.Float: TiffValueCollection <float> valuesFloat = await fieldReader.ReadFloatFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesFloat); break; case TiffFieldType.Double: TiffValueCollection <double> valuesDouble = await fieldReader.ReadDoubleFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesDouble); break; case TiffFieldType.IFD: TiffValueCollection <TiffStreamOffset> valuesIfd = await fieldReader.ReadIFDFieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesIfd); break; case TiffFieldType.Long8: TiffValueCollection <ulong> valuesLong8 = await fieldReader.ReadLong8FieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesLong8); break; case TiffFieldType.SLong8: TiffValueCollection <long> valuesSLong8 = await fieldReader.ReadSLong8FieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesSLong8); break; case TiffFieldType.IFD8: TiffValueCollection <TiffStreamOffset> valuesIfd8 = await fieldReader.ReadIFD8FieldAsync(entry, skipTypeValidation : true, cancellationToken : cancellationToken); DumpValueCollecion(valuesIfd8); break; default: Console.Write(" Unsupported field type."); break; } Console.WriteLine(); }
static async Task <string?> TransformValueTaskAsync(ValueTask <TiffValueCollection <string> > valueTask) { TiffValueCollection <string> result = await valueTask.ConfigureAwait(false); return(result.GetFirstOrDefault()); }
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> /// Initialize the middleware with the default YCbCrCoefficients and ReferenceBlackWhite tags. /// </summary> public TiffPlanarYCbCr888Interpreter() : this(TiffValueCollection.Empty <TiffRational>(), TiffValueCollection.Empty <TiffRational>()) { }
/// <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); } } }
/// <summary> /// Initialize the middleware. /// </summary> /// <param name="photometricInterpretation">The photometric interpretation of the image.</param> /// <param name="bitsPerSample">Bits per sample.</param> /// <param name="bytesPerScanlines">Byte count per scanline.</param> /// <param name="decompressionAlgorithm">The decompression algorithm.</param> public TiffImageDecompressionMiddleware(TiffPhotometricInterpretation photometricInterpretation, TiffValueCollection <ushort> bitsPerSample, TiffValueCollection <int> bytesPerScanlines, ITiffDecompressionAlgorithm decompressionAlgorithm) { if (bytesPerScanlines.IsEmpty) { throw new ArgumentException("BytesPerScanlines not specified."); } _photometricInterpretation = photometricInterpretation; _bitsPerSample = bitsPerSample; _bytesPerScanlines = bytesPerScanlines; _decompressionAlgorithm = decompressionAlgorithm; }
/// <summary> /// Initialize the middleware with the default YCbCrCoefficients and ReferenceBlackWhite tags. /// </summary> public TiffChunkyYCbCr161616Interpreter() : this(TiffValueCollection.Empty <TiffRational>(), TiffValueCollection.Empty <TiffRational>()) { }
/// <summary> /// Initialize the middleware. /// </summary> /// <param name="tileWidth">The TileWidth tag.</param> /// <param name="tileHeight">The TileLength tag.</param> /// <param name="tileOffsets">The TileOffsets tag.</param> /// <param name="tileByteCounts">The TileByteCounts tag.</param> /// <param name="planeCount">The plane count.</param> public TiffTiledImageDecoderEnumeratorMiddleware(int tileWidth, int tileHeight, TiffValueCollection <ulong> tileOffsets, TiffValueCollection <ulong> tileByteCounts, int planeCount) { _tileWidth = tileWidth; _tileHeight = tileHeight; _planaCount = planeCount; _lazyLoad = false; _tileOffsets = tileOffsets; _tileByteCounts = tileByteCounts; if (tileOffsets.Count != tileByteCounts.Count) { throw new ArgumentException("tileOffsets does not have the same element count as tileByteCounts.", nameof(tileByteCounts)); } }
/// <summary> /// Initialize the middleware. /// </summary> /// <param name="bytesPerScanlines">Byte count per scanline.</param> /// <param name="bitsPerSample">Bits per sample.</param> /// <param name="predictor">The predictor tag.</param> public TiffReversePredictorMiddleware(TiffValueCollection <int> bytesPerScanlines, TiffValueCollection <ushort> bitsPerSample, TiffPredictor predictor) { _bytesPerScanlines = bytesPerScanlines; _bitsPerSample = bitsPerSample; _predictor = predictor; }
/// <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); }
/// <summary> /// Initialize the middleware. /// </summary> /// <param name="isAlphaAssociated">Whether the alpha channel is associated.</param> /// <param name="undoColorPreMultiplying">Whether to undo color pre-multiplying.</param> /// <param name="bitsPerSample">The BitsPerSample flags.</param> /// <param name="fillOrder">The FillOrder tag.</param> public TiffChunkyRgbaAny8888Interpreter(bool isAlphaAssociated, bool undoColorPreMultiplying, TiffValueCollection <ushort> bitsPerSample, TiffFillOrder fillOrder = TiffFillOrder.HigherOrderBitsFirst) { _isAlphaAssociated = isAlphaAssociated; _undoColorPreMultiplying = undoColorPreMultiplying; if (bitsPerSample.Count != 4) { throw new ArgumentOutOfRangeException(nameof(bitsPerSample)); } if ((uint)bitsPerSample[0] > 8 || (uint)bitsPerSample[1] > 8 || (uint)bitsPerSample[2] > 8 || (uint)bitsPerSample[3] > 8) { throw new ArgumentOutOfRangeException(nameof(bitsPerSample)); } if (fillOrder == 0) { fillOrder = TiffFillOrder.HigherOrderBitsFirst; } _bitsPerSample = bitsPerSample; _fillOrder = fillOrder; }
/// <summary> /// Initialize the middleware. /// </summary> /// <param name="rowsPerStrip">Rows per strip.</param> /// <param name="stripOffsets">The StripOffsets tag.</param> /// <param name="stripsByteCount">The StripsByteCount tag.</param> /// <param name="planeCount">The number of planes.</param> public TiffStrippedImageDecoderEnumeratorMiddleware(int rowsPerStrip, TiffValueCollection <ulong> stripOffsets, TiffValueCollection <ulong> stripsByteCount, int planeCount) { _rowsPerStrip = rowsPerStrip; _planeCount = planeCount; _lazyLoad = false; _stripOffsets = stripOffsets; _stripsByteCount = stripsByteCount; if (stripOffsets.Count != stripsByteCount.Count) { throw new ArgumentException("stripsByteCount does not have the same element count as stripsOffsets.", nameof(stripsByteCount)); } _stripCount = stripOffsets.Count; }
/// <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); } } } }
public void TestMultiValue() { int[] values = new int[] { 42, 88, 100 }; var multiVal = TiffValueCollection.UnsafeWrap(values); Assert.Equal(3, multiVal.Count); Assert.False(multiVal.IsEmpty); Assert.Equal(42, multiVal[0]); Assert.Equal(88, multiVal[1]); Assert.Equal(100, multiVal[2]); Assert.Throws <IndexOutOfRangeException>(() => multiVal[-1]); Assert.Throws <IndexOutOfRangeException>(() => multiVal[3]); int count = 0; foreach (int v in multiVal) { switch (count) { case 0: Assert.Equal(42, v); break; case 1: Assert.Equal(88, v); break; case 2: Assert.Equal(100, v); break; } count++; } Assert.Equal(3, count); // TiffValueCollection contains the reference to the original array. values[1] = 128; Assert.Equal(128, multiVal[1]); // When using the ReadOnlySpan<T> constructor, TiffValueCollection creates an array internally and copys all the values from the span to the array. var multiVal2 = new TiffValueCollection <int>(values.AsSpan()); Assert.Equal(3, multiVal2.Count); Assert.False(multiVal2.IsEmpty); Assert.Equal(42, multiVal2[0]); Assert.Equal(128, multiVal2[1]); Assert.Equal(100, multiVal2[2]); Assert.Throws <IndexOutOfRangeException>(() => multiVal2[-1]); Assert.Throws <IndexOutOfRangeException>(() => multiVal2[3]); values[1] = 256; Assert.Equal(128, multiVal2[1]); count = 0; foreach (int v in multiVal2) { switch (count) { case 0: Assert.Equal(42, v); break; case 1: Assert.Equal(128, v); break; case 2: Assert.Equal(100, v); break; } count++; } Assert.Equal(3, count); string[] strings = new string[] { "hello", "world", "." }; var multiRef = new TiffValueCollection <string>(strings); Assert.Equal(3, multiRef.Count); Assert.False(multiRef.IsEmpty); Assert.Equal("hello", multiRef[0]); Assert.Equal("world", multiRef[1]); Assert.Equal(".", multiRef[2]); Assert.Throws <IndexOutOfRangeException>(() => multiRef[-1]); Assert.Throws <IndexOutOfRangeException>(() => multiRef[3]); count = 0; foreach (string v in multiRef) { switch (count) { case 0: Assert.Equal("hello", v); break; case 1: Assert.Equal("world", v); break; case 2: Assert.Equal(".", v); break; } count++; } Assert.Equal(3, count); }