public async Task TestImageDecodeAsync(string refImage, int refIfd, int refBitDepth, string testImage, int testIfd, int testBitDepth) { const int BufferBitDepth = 16; // We do not support decoding into 32 bit buffer yet. refBitDepth = Math.Min(refBitDepth, BufferBitDepth); testBitDepth = Math.Min(testBitDepth, BufferBitDepth); TiffStreamOffset ifdOffset; TiffImageFileDirectory ifd; // Load reference image await using TiffFileReader refTiff = await TiffFileReader.OpenAsync(refImage); ifdOffset = refTiff.FirstImageFileDirectoryOffset; Assert.False(ifdOffset.IsZero); for (int i = 0; i < refIfd; i++) { ifd = await refTiff.ReadImageFileDirectoryAsync(ifdOffset); ifdOffset = ifd.NextOffset; Assert.False(ifdOffset.IsZero); } ifd = await refTiff.ReadImageFileDirectoryAsync(ifdOffset); TiffImageDecoder refDecoder = await refTiff.CreateImageDecoderAsync(ifd); // Load test image await using TiffFileReader testTiff = await TiffFileReader.OpenAsync(testImage); ifdOffset = testTiff.FirstImageFileDirectoryOffset; Assert.False(ifdOffset.IsZero); for (int i = 0; i < testIfd; i++) { ifd = await testTiff.ReadImageFileDirectoryAsync(ifdOffset); ifdOffset = ifd.NextOffset; Assert.False(ifdOffset.IsZero); } ifd = await testTiff.ReadImageFileDirectoryAsync(ifdOffset); TiffImageDecoder testDecoder = await testTiff.CreateImageDecoderAsync(ifd); Assert.Equal(refDecoder.Width, testDecoder.Width); Assert.Equal(refDecoder.Height, testDecoder.Height); TiffRgba64[] refBuffer = new TiffRgba64[refDecoder.Width * refDecoder.Height]; TiffRgba64[] testBuffer = new TiffRgba64[testDecoder.Width * testDecoder.Height]; await refDecoder.DecodeAsync(TiffPixelBuffer.Wrap(refBuffer, refDecoder.Width, refDecoder.Height)); await testDecoder.DecodeAsync(TiffPixelBuffer.Wrap(testBuffer, testDecoder.Width, testDecoder.Height)); ShiftPixels(MemoryMarshal.Cast <TiffRgba64, ushort>(refBuffer), BufferBitDepth - refBitDepth); ShiftPixels(MemoryMarshal.Cast <TiffRgba64, ushort>(testBuffer), BufferBitDepth - testBitDepth); Assert.True(refBuffer.AsSpan().SequenceEqual(testBuffer)); }
/// <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)); } // Colormap array is read using TiffTagReader, its elements should be in machine-endian. ushort[] colorMap = _colorMap; int bitCount = _bitCount; bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; int bytesPerScanline = (context.SourceImageSize.Width * bitCount + 7) / 8; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; TiffRgba64 pixel = default; pixel.A = ushort.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba64> pixelSpan = pixelSpanHandle.GetSpan(); var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst); bitReader.Advance(context.SourceReadOffset.X * bitCount); for (int col = 0; col < cols; col++) { uint offset = bitReader.Read(bitCount); pixel.R = colorMap[offset]; pixel.G = colorMap[SingleColorCount + offset]; pixel.B = colorMap[2 * SingleColorCount + offset]; pixelSpan[col] = pixel; } 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)); } // Colormap array is read using TiffTagReader, its elements should be in machine-endian. ushort[] colorMap = _colorMap; int bytesPerScanline = context.SourceImageSize.Width; Memory <byte> source = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline); ReadOnlySpan <byte> sourceSpan = source.Span; using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; TiffRgba64 pixel = default; pixel.A = ushort.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); ReadOnlySpan <byte> rowSourceSpan = sourceSpan.Slice(context.SourceReadOffset.X, context.ReadSize.Width); Span <TiffRgba64> rowDestinationSpan = pixelSpanHandle.GetSpan(); for (int i = 0; i < rowSourceSpan.Length; i++) { int offset = rowSourceSpan[i]; pixel.R = colorMap[offset]; pixel.G = colorMap[SingleColorCount + offset]; pixel.B = colorMap[2 * SingleColorCount + offset]; rowDestinationSpan[i] = pixel; } sourceSpan = sourceSpan.Slice(bytesPerScanline); } return(next.RunAsync(context)); }
public TiffRgba64 ConvertToRgba64(ushort y, ushort cb, ushort 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); TiffRgba64 pixel = default; // TODO: SkipInit converter.Convert(y64, cb64, cr64, ref pixel); return(pixel); }
private static void UndoColorPreMultiplying(Span <TiffRgba64> pixels) { for (int i = 0; i < pixels.Length; i++) { TiffRgba64 pixel = pixels[i]; int a = pixel.A; if (a == 0) { pixels[i] = default; } else { pixel.R = (ushort)(pixel.R * 0xffff / a); pixel.G = (ushort)(pixel.G * 0xffff / a); pixel.B = (ushort)(pixel.B * 0xffff / a); pixels[i] = pixel; } } }
public void TestRgba64() { Assert.Equal(8, Unsafe.SizeOf <TiffRgba64>()); var defaultPixel = default(TiffRgba64); Assert.Equal(0, defaultPixel.R); Assert.Equal(0, defaultPixel.G); Assert.Equal(0, defaultPixel.B); Assert.Equal(0, defaultPixel.A); var pixel1 = new TiffRgba64(0x1221, 0x3443, 0x5665, 0x7887); Assert.Equal(0x1221, pixel1.R); Assert.Equal(0x3443, pixel1.G); Assert.Equal(0x5665, pixel1.B); Assert.Equal(0x7887, 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 TiffRgba64(0x1221, 0x3443, 0x5665, 0x7887); 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[3]; _bitsPerSample.CopyTo(bitsPerSample); bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; int totalBitsPerSample = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2]; 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 <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; // BitReader.Read reads bytes in big-endian way, we only need to reverse the endianness if the source is little-endian. bool isLittleEndian = context.IsLittleEndian; bool reverseEndiannessR = isLittleEndian && bitsPerSample[0] % 8 == 0; bool reverseEndiannessG = isLittleEndian && bitsPerSample[1] % 8 == 0; bool reverseEndiannessB = isLittleEndian && bitsPerSample[2] % 8 == 0; bool canDoFastPath = bitsPerSample[0] >= 16 && bitsPerSample[1] >= 16 && bitsPerSample[2] >= 16 && !reverseEndiannessR & !reverseEndiannessG & !reverseEndiannessB; TiffRgba64 pixel = default; pixel.A = ushort.MaxValue; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba64> 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 = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 32) >> 16); pixel.G = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 32) >> 16); pixel.B = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 32) >> 16); pixelSpan[col] = pixel; } } else { // Slow path for (int col = 0; col < cols; col++) { pixel.R = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 32, reverseEndiannessR) >> 16); pixel.G = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 32, reverseEndiannessG) >> 16); pixel.B = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 32, reverseEndiannessB) >> 16); pixelSpan[col] = pixel; } } 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[4]; _bitsPerSample.CopyTo(bitsPerSample); bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst; 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; int bytesPerScanlineA = (context.SourceImageSize.Width * bitsPerSample[3] + 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); ReadOnlySpan <byte> sourceA = sourceSpan.Slice(sourceR.Length + sourceG.Length + sourceB.Length, context.SourceImageSize.Height * bytesPerScanlineA); sourceR = sourceR.Slice(context.SourceReadOffset.Y * bytesPerScanlineR); sourceG = sourceG.Slice(context.SourceReadOffset.Y * bytesPerScanlineG); sourceB = sourceB.Slice(context.SourceReadOffset.Y * bytesPerScanlineB); sourceA = sourceB.Slice(context.SourceReadOffset.Y * bytesPerScanlineA); using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>(); int rows = context.ReadSize.Height; int cols = context.ReadSize.Width; // BitReader.Read reads bytes in big-endian way, we only need to reverse the endianness if the source is little-endian. bool isLittleEndian = context.IsLittleEndian; bool reverseEndiannessR = isLittleEndian && bitsPerSample[0] % 8 == 0; bool reverseEndiannessG = isLittleEndian && bitsPerSample[1] % 8 == 0; bool reverseEndiannessB = isLittleEndian && bitsPerSample[2] % 8 == 0; bool reverseEndiannessA = isLittleEndian && bitsPerSample[3] % 8 == 0; bool canDoFastPath = bitsPerSample[0] >= 16 && bitsPerSample[1] >= 16 && bitsPerSample[2] >= 16 && bitsPerSample[3] >= 16 && !reverseEndiannessR & !reverseEndiannessG & !reverseEndiannessB & !reverseEndiannessA; TiffRgba64 pixel = default; for (int row = 0; row < rows; row++) { using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row); Span <TiffRgba64> 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); var bitReaderA = new BitReader(sourceA.Slice(0, bytesPerScanlineA), isHigherOrderBitsFirst); bitReaderR.Advance(context.SourceReadOffset.X * bitsPerSample[0]); bitReaderG.Advance(context.SourceReadOffset.X * bitsPerSample[1]); bitReaderB.Advance(context.SourceReadOffset.X * bitsPerSample[2]); bitReaderA.Advance(context.SourceReadOffset.X * bitsPerSample[3]); if (canDoFastPath) { // Fast path for bits >= 8 for (int col = 0; col < cols; col++) { pixel.R = (ushort)(FastExpandBits(bitReaderR.Read(bitsPerSample[0]), bitsPerSample[0], 32) >> 16); pixel.G = (ushort)(FastExpandBits(bitReaderG.Read(bitsPerSample[1]), bitsPerSample[1], 32) >> 16); pixel.B = (ushort)(FastExpandBits(bitReaderB.Read(bitsPerSample[2]), bitsPerSample[2], 32) >> 16); pixel.A = (ushort)(FastExpandBits(bitReaderA.Read(bitsPerSample[3]), bitsPerSample[3], 32) >> 16); pixelSpan[col] = pixel; } } else { // Slow path for (int col = 0; col < cols; col++) { pixel.R = (ushort)(ExpandBits(bitReaderR.Read(bitsPerSample[0]), bitsPerSample[0], 32, reverseEndiannessR) >> 16); pixel.G = (ushort)(ExpandBits(bitReaderG.Read(bitsPerSample[1]), bitsPerSample[1], 32, reverseEndiannessG) >> 16); pixel.B = (ushort)(ExpandBits(bitReaderB.Read(bitsPerSample[2]), bitsPerSample[2], 32, reverseEndiannessB) >> 16); pixel.A = (ushort)(ExpandBits(bitReaderA.Read(bitsPerSample[3]), bitsPerSample[3], 32, reverseEndiannessA) >> 16); pixelSpan[col] = pixel; } } if (_isAlphaAssociated) { if (_undoColorPreMultiplying) { UndoColorPreMultiplying(pixelSpan); } else { WipeAlphaChanel(pixelSpan); } } sourceR = sourceR.Slice(bytesPerScanlineR); sourceG = sourceG.Slice(bytesPerScanlineG); sourceB = sourceB.Slice(bytesPerScanlineB); sourceA = sourceA.Slice(bytesPerScanlineA); } return(next.RunAsync(context)); }