public async Task TestAsync(string reference, string test) { using var refImage = Image.Load <Rgba32>(reference); await using TiffFileReader tiff = await TiffFileReader.OpenAsync(test); TiffStreamOffset ifdOffset = tiff.FirstImageFileDirectoryOffset; while (!ifdOffset.IsZero) { TiffImageFileDirectory ifd = await tiff.ReadImageFileDirectoryAsync(ifdOffset); TiffImageDecoder decoder = await tiff.CreateImageDecoderAsync(ifd, new TiffImageDecoderOptions { UndoColorPreMultiplying = true }); Assert.Equal(refImage.Width, decoder.Width); Assert.Equal(refImage.Height, decoder.Height); TiffRgba32[] pixels = new TiffRgba32[decoder.Width * decoder.Height]; await decoder.DecodeAsync(TiffPixelBuffer.Wrap(pixels, decoder.Width, decoder.Height)); AssertEqual(refImage, pixels); using (var image = new Image <Rgba32>(decoder.Width, decoder.Height)) { decoder.Decode(image); AssertEqual(refImage, image); } ifdOffset = ifd.NextOffset; } }
private static void ProcessAssociatedWithUndoColorPreMultiplying(TiffImageDecoderContext context) { int bytesPerScanline = 4 * context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba32> writer = context.GetWriter <TiffRgba32>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba32> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <TiffRgba32> rowSourceSpan = MemoryMarshal.Cast <byte, TiffRgba32>(sourceSpan.Slice(4 * context.SourceReadOffset.X, 4 * context.ReadSize.Width)); Span <TiffRgba32> rowDestinationSpan = pixelSpanHandle.GetSpan(); for (int col = 0; col < cols; col++) { TiffRgba32 pixel = rowSourceSpan[col]; byte a = pixel.A; if (a != 0) { pixel.R = (byte)(pixel.R * 255 / a); pixel.G = (byte)(pixel.G * 255 / a); pixel.B = (byte)(pixel.B * 255 / a); } rowDestinationSpan[col] = pixel; } sourceSpan = sourceSpan.Slice(bytesPerScanline); } }
public TiffRgba32 ConvertToRgba32(byte y, byte cb, byte cr) { CodingRangeExpander expanderY = _expanderY; CodingRangeExpander expanderCb = _expanderCb; CodingRangeExpander expanderCr = _expanderCr; YCbCrToRgbConverter converter = _converterFrom; float y64 = expanderY.Expand(y); float cb64 = expanderCb.Expand(cb); float cr64 = expanderCr.Expand(cr); TiffRgba32 pixel = default; // TODO: SkipInit converter.Convert(y64, cb64, cr64, ref pixel); return(pixel); }
private static void UndoColorPreMultiplying(Span <TiffRgba32> pixels) { for (int i = 0; i < pixels.Length; i++) { TiffRgba32 pixel = pixels[i]; int a = pixel.A; if (a == 0) { pixels[i] = default; } else { pixel.R = (byte)(pixel.R * 0xff / a); pixel.G = (byte)(pixel.G * 0xff / a); pixel.B = (byte)(pixel.B * 0xff / a); pixels[i] = pixel; } } }
public void Write(TiffImageDecoderContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } TiffRgba32 fillColor = _fillColor; using TiffPixelBufferWriter <TiffRgba32> writer = context.GetWriter <TiffRgba32>(); int rows = context.ReadSize.Height; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba32> pixelSpanHandle = writer.GetRowSpan(row); pixelSpanHandle.GetSpan().Fill(fillColor); } }
public void TestRgba32() { Assert.Equal(4, Unsafe.SizeOf <TiffRgba32>()); var defaultPixel = default(TiffRgba32); Assert.Equal(0, defaultPixel.R); Assert.Equal(0, defaultPixel.G); Assert.Equal(0, defaultPixel.B); Assert.Equal(0, defaultPixel.A); var pixel1 = new TiffRgba32(0x12, 0x34, 0x56, 0x78); Assert.Equal(0x12, pixel1.R); Assert.Equal(0x34, pixel1.G); Assert.Equal(0x56, pixel1.B); Assert.Equal(0x78, pixel1.A); Assert.False(pixel1.Equals(defaultPixel)); Assert.False(defaultPixel.Equals(pixel1)); Assert.False(pixel1 == defaultPixel); Assert.False(defaultPixel == pixel1); Assert.True(pixel1 != defaultPixel); Assert.True(defaultPixel != pixel1); Assert.False(pixel1.GetHashCode() == defaultPixel.GetHashCode()); var pixel2 = new TiffRgba32(0x12, 0x34, 0x56, 0x78); Assert.True(pixel1.Equals(pixel2)); Assert.True(pixel2.Equals(pixel1)); Assert.True(pixel1 == pixel2); Assert.True(pixel2 == pixel1); Assert.False(pixel1 != pixel2); Assert.False(pixel2 != pixel1); Assert.True(pixel1.GetHashCode() == pixel2.GetHashCode()); }
/// <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)); } Span <ushort> bitsPerSample = stackalloc ushort[4]; _bitsPerSample.CopyTo(bitsPerSample); bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; bool canDoFastPath = bitsPerSample[0] >= 4 && bitsPerSample[1] >= 4 && bitsPerSample[2] >= 4 && bitsPerSample[3] >= 4; int totalBitsPerSample = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2] + bitsPerSample[3]; int bytesPerScanline = (context.SourceImageSize.Width * totalBitsPerSample + 7) / 8; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba32> writer = context.GetWriter <TiffRgba32>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; TiffRgba32 pixel = default; pixel.A = byte.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba32> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba32> pixelSpan = pixelSpanHandle.GetSpan(); var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst); bitReader.Advance(context.SourceReadOffset.X * totalBitsPerSample); if (canDoFastPath) { // Fast path for bits >= 8 for (int col = 0; col < cols; col++) { pixel.R = (byte)FastExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 8); pixel.G = (byte)FastExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 8); pixel.B = (byte)FastExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 8); pixel.A = (byte)FastExpandBits(bitReader.Read(bitsPerSample[3]), bitsPerSample[3], 8); pixelSpan[col] = pixel; } } else { // Slow path for (int col = 0; col < cols; col++) { pixel.R = (byte)ExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 8); pixel.G = (byte)ExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 8); pixel.B = (byte)ExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 8); pixel.A = (byte)ExpandBits(bitReader.Read(bitsPerSample[3]), bitsPerSample[3], 8); pixelSpan[col] = pixel; } } if (_isAlphaAssociated) { if (_undoColorPreMultiplying) { UndoColorPreMultiplying(pixelSpan); } else { WipeAlphaChanel(pixelSpan); } } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
/// <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)); } Span <ushort> bitsPerSample = stackalloc ushort[3]; _bitsPerSample.CopyTo(bitsPerSample); bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; bool canDoFastPath = bitsPerSample[0] >= 4 && bitsPerSample[1] >= 4 && bitsPerSample[2] >= 4; int bytesPerScanlineR = (context.SourceImageSize.Width * bitsPerSample[0] + 7) / 8; int bytesPerScanlineG = (context.SourceImageSize.Width * bitsPerSample[1] + 7) / 8; int bytesPerScanlineB = (context.SourceImageSize.Width * bitsPerSample[2] + 7) / 8; ReadOnlySpan <byte> sourceSpan = context.UncompressedData.Span; ReadOnlySpan <byte> sourceR = sourceSpan.Slice(0, context.SourceImageSize.Height * bytesPerScanlineR); ReadOnlySpan <byte> sourceG = sourceSpan.Slice(sourceR.Length, context.SourceImageSize.Height * bytesPerScanlineG); ReadOnlySpan <byte> sourceB = sourceSpan.Slice(sourceR.Length + sourceG.Length, context.SourceImageSize.Height * bytesPerScanlineB); sourceR = sourceR.Slice(context.SourceReadOffset.Y * bytesPerScanlineR); sourceG = sourceG.Slice(context.SourceReadOffset.Y * bytesPerScanlineG); sourceB = sourceB.Slice(context.SourceReadOffset.Y * bytesPerScanlineB); using TiffPixelBufferWriter <TiffRgba32> writer = context.GetWriter <TiffRgba32>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; TiffRgba32 pixel = default; pixel.A = byte.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba32> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba32> pixelSpan = pixelSpanHandle.GetSpan(); var bitReaderR = new BitReader(sourceR.Slice(0, bytesPerScanlineR), isHigherOrderBitsFirst); var bitReaderG = new BitReader(sourceG.Slice(0, bytesPerScanlineG), isHigherOrderBitsFirst); var bitReaderB = new BitReader(sourceB.Slice(0, bytesPerScanlineB), isHigherOrderBitsFirst); bitReaderR.Advance(context.SourceReadOffset.X * bitsPerSample[0]); bitReaderG.Advance(context.SourceReadOffset.X * bitsPerSample[1]); bitReaderB.Advance(context.SourceReadOffset.X * bitsPerSample[2]); if (canDoFastPath) { // Fast path for bits >= 8 for (int col = 0; col < cols; col++) { pixel.R = (byte)FastExpandBits(bitReaderR.Read(bitsPerSample[0]), bitsPerSample[0], 8); pixel.G = (byte)FastExpandBits(bitReaderG.Read(bitsPerSample[1]), bitsPerSample[1], 8); pixel.B = (byte)FastExpandBits(bitReaderB.Read(bitsPerSample[2]), bitsPerSample[2], 8); pixelSpan[col] = pixel; } } else { // Slow path for (int col = 0; col < cols; col++) { pixel.R = (byte)ExpandBits(bitReaderR.Read(bitsPerSample[0]), bitsPerSample[0], 8); pixel.G = (byte)ExpandBits(bitReaderG.Read(bitsPerSample[1]), bitsPerSample[1], 8); pixel.B = (byte)ExpandBits(bitReaderB.Read(bitsPerSample[2]), bitsPerSample[2], 8); pixelSpan[col] = pixel; } } sourceR = sourceR.Slice(bytesPerScanlineR); sourceG = sourceG.Slice(bytesPerScanlineG); sourceB = sourceB.Slice(bytesPerScanlineB); } return(next.RunAsync(context)); }
public TiffEmptyStrileWriter(TiffRgba32 fillColor) { _fillColor = fillColor; }