From c850e2d41b288eba66420c7539466da02df49988 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 7 Nov 2022 17:17:11 +0100 Subject: [PATCH] Fix decoding tiled tiff's with planar configuration --- .../Formats/Tiff/TiffDecoderCore.cs | 125 +++++++++++------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 6d55d9981..6bdbf442b 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -520,65 +520,96 @@ internal class TiffDecoderCore : IImageDecoderInternals int width = pixels.Width; int height = pixels.Height; int bitsPerPixel = this.BitsPerPixel; + int channels = this.BitsPerSample.Channels; + int tilesPerChannel = tileOffsets.Length / channels; - int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; - int bytesPerTileRow = ((tileWidth * bitsPerPixel) + 7) / 8; - int uncompressedTilesSize = bytesPerTileRow * tileLength; - using IMemoryOwner tileBuffer = this.memoryAllocator.Allocate(uncompressedTilesSize, AllocationOptions.Clean); - using IMemoryOwner uncompressedPixelBuffer = this.memoryAllocator.Allocate(tilesDown * tileLength * bytesPerRow, AllocationOptions.Clean); - Span tileBufferSpan = tileBuffer.GetSpan(); - Span uncompressedPixelBufferSpan = uncompressedPixelBuffer.GetSpan(); - - using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); + IMemoryOwner[] tilesBuffers = new IMemoryOwner[channels]; - TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create( - this.configuration, - this.memoryAllocator, - this.ColorType, - this.BitsPerSample, - this.ExtraSamplesType, - this.ColorMap, - this.ReferenceBlackAndWhite, - this.YcbcrCoefficients, - this.YcbcrSubSampling, - this.byteOrder); - - int tileIndex = 0; - for (int tileY = 0; tileY < tilesDown; tileY++) + try { - int uncompressedPixelBufferOffset = tileY * tileLength * bytesPerRow; - int remainingPixelsInRow = width; - for (int tileX = 0; tileX < tilesAcross; tileX++) + int bytesPerTileRow = ((tileWidth * bitsPerPixel) + 7) / 8; + int uncompressedTilesSize = bytesPerTileRow * tileLength; + for (int i = 0; i < tilesBuffers.Length; i++) { - cancellationToken.ThrowIfCancellationRequested(); + tilesBuffers[i] = this.memoryAllocator.Allocate(uncompressedTilesSize, AllocationOptions.Clean); + } - bool isLastHorizontalTile = tileX == tilesAcross - 1; + using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); - decompressor.Decompress( - this.inputStream, - tileOffsets[tileIndex], - tileByteCounts[tileIndex], - tileLength, - tileBufferSpan, - cancellationToken); + TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar( + this.ColorType, + this.BitsPerSample, + this.ExtraSamplesType, + this.ColorMap, + this.ReferenceBlackAndWhite, + this.YcbcrCoefficients, + this.YcbcrSubSampling, + this.byteOrder); - int tileBufferOffset = 0; - uncompressedPixelBufferOffset += bytesPerTileRow * tileX; - int bytesToCopy = isLastHorizontalTile ? ((bitsPerPixel * remainingPixelsInRow) + 7) / 8 : bytesPerTileRow; - for (int y = 0; y < tileLength; y++) + int tileIndex = 0; + int remainingPixelsInColumn = height; + for (int tileY = 0; tileY < tilesDown; tileY++) + { + int remainingPixelsInRow = width; + int pixelColumnOffset = tileY * tileLength; + bool isLastVerticalTile = tileY == tilesDown - 1; + for (int tileX = 0; tileX < tilesAcross; tileX++) { - Span uncompressedPixelRow = uncompressedPixelBufferSpan.Slice(uncompressedPixelBufferOffset, bytesToCopy); - tileBufferSpan.Slice(tileBufferOffset, bytesToCopy).CopyTo(uncompressedPixelRow); - tileBufferOffset += bytesPerTileRow; - uncompressedPixelBufferOffset += bytesPerRow; + int pixelRowOffset = tileX * tileWidth; + bool isLastHorizontalTile = tileX == tilesAcross - 1; + int tileIndexForChannel = tileIndex; + for (int i = 0; i < channels; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + decompressor.Decompress( + this.inputStream, + tileOffsets[tileIndexForChannel], + tileByteCounts[tileIndexForChannel], + tileLength, + tilesBuffers[i].GetSpan(), + cancellationToken); + + tileIndexForChannel += tilesPerChannel; + } + + if (isLastHorizontalTile && remainingPixelsInRow < tileWidth) + { + // Adjust pixel data in the tile buffer to fit the smaller then usual tile width. + for (int i = 0; i < channels; i++) + { + Span tileBufferSpan = tilesBuffers[i].GetSpan(); + for (int y = 0; y < tileLength; y++) + { + int currentRowOffset = y * tileWidth; + Span adjustedRow = tileBufferSpan.Slice(y * remainingPixelsInRow, remainingPixelsInRow); + tileBufferSpan.Slice(currentRowOffset, remainingPixelsInRow).CopyTo(adjustedRow); + } + } + } + + colorDecoder.Decode( + tilesBuffers, + pixels, + pixelRowOffset, + pixelColumnOffset, + isLastHorizontalTile ? remainingPixelsInRow : tileWidth, + isLastVerticalTile ? remainingPixelsInColumn : tileLength); + + remainingPixelsInRow -= tileWidth; + tileIndex++; } - remainingPixelsInRow -= tileWidth; - tileIndex++; + remainingPixelsInColumn -= tileLength; + } + } + finally + { + foreach (IMemoryOwner buf in tilesBuffers) + { + buf?.Dispose(); } } - - colorDecoder.Decode(uncompressedPixelBufferSpan, pixels, 0, 0, width, height); } ///