mirror of https://github.com/SixLabors/ImageSharp
382 changed files with 13254 additions and 990 deletions
@ -0,0 +1,23 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The byte order of the data stream.
|
||||
|
/// </summary>
|
||||
|
public enum ByteOrder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The big-endian byte order (Motorola).
|
||||
|
/// Most-significant byte comes first, and ends with the least-significant byte.
|
||||
|
/// </summary>
|
||||
|
BigEndian, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The little-endian byte order (Intel).
|
||||
|
/// Least-significant byte comes first and ends with the most-significant byte.
|
||||
|
/// </summary>
|
||||
|
LittleEndian |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
namespace SixLabors.ImageSharp.Compression.Zlib |
||||
{ |
{ |
||||
/// <content>
|
/// <content>
|
||||
/// Contains precalulated tables for scalar calculations.
|
/// Contains precalulated tables for scalar calculations.
|
||||
@ -0,0 +1,81 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Compression.Zlib |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides enumeration of available deflate compression levels.
|
||||
|
/// </summary>
|
||||
|
public enum DeflateCompressionLevel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Level 0. Equivalent to <see cref="NoCompression"/>.
|
||||
|
/// </summary>
|
||||
|
Level0 = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// No compression. Equivalent to <see cref="Level0"/>.
|
||||
|
/// </summary>
|
||||
|
NoCompression = Level0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 1. Equivalent to <see cref="BestSpeed"/>.
|
||||
|
/// </summary>
|
||||
|
Level1 = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Best speed compression level.
|
||||
|
/// </summary>
|
||||
|
BestSpeed = Level1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 2.
|
||||
|
/// </summary>
|
||||
|
Level2 = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 3.
|
||||
|
/// </summary>
|
||||
|
Level3 = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 4.
|
||||
|
/// </summary>
|
||||
|
Level4 = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 5.
|
||||
|
/// </summary>
|
||||
|
Level5 = 5, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 6. Equivalent to <see cref="DefaultCompression"/>.
|
||||
|
/// </summary>
|
||||
|
Level6 = 6, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default compression level. Equivalent to <see cref="Level6"/>.
|
||||
|
/// </summary>
|
||||
|
DefaultCompression = Level6, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 7.
|
||||
|
/// </summary>
|
||||
|
Level7 = 7, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 8.
|
||||
|
/// </summary>
|
||||
|
Level8 = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Level 9. Equivalent to <see cref="BestCompression"/>.
|
||||
|
/// </summary>
|
||||
|
Level9 = 9, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Best compression level. Equivalent to <see cref="Level9"/>.
|
||||
|
/// </summary>
|
||||
|
BestCompression = Level9, |
||||
|
} |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
# Encoder/Decoder for true vision targa files |
# Encoder/Decoder for true vision targa files |
||||
|
|
||||
Useful links for reference: |
Useful links for reference: |
||||
|
|
||||
@ -0,0 +1,51 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
internal static class BitWriterUtils |
||||
|
{ |
||||
|
public static void WriteBits(Span<byte> buffer, int pos, uint count, byte value) |
||||
|
{ |
||||
|
int bitPos = pos % 8; |
||||
|
int bufferPos = pos / 8; |
||||
|
int startIdx = bufferPos + bitPos; |
||||
|
int endIdx = (int)(startIdx + count); |
||||
|
|
||||
|
if (value == 1) |
||||
|
{ |
||||
|
for (int i = startIdx; i < endIdx; i++) |
||||
|
{ |
||||
|
WriteBit(buffer, bufferPos, bitPos); |
||||
|
|
||||
|
bitPos++; |
||||
|
if (bitPos >= 8) |
||||
|
{ |
||||
|
bitPos = 0; |
||||
|
bufferPos++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (int i = startIdx; i < endIdx; i++) |
||||
|
{ |
||||
|
WriteZeroBit(buffer, bufferPos, bitPos); |
||||
|
|
||||
|
bitPos++; |
||||
|
if (bitPos >= 8) |
||||
|
{ |
||||
|
bitPos = 0; |
||||
|
bufferPos++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void WriteBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos)); |
||||
|
|
||||
|
public static void WriteZeroBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos))); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Compression.Zlib; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
internal sealed class DeflateCompressor : TiffBaseCompressor |
||||
|
{ |
||||
|
private readonly DeflateCompressionLevel compressionLevel; |
||||
|
|
||||
|
private readonly MemoryStream memoryStream = new MemoryStream(); |
||||
|
|
||||
|
public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel) |
||||
|
: base(output, allocator, width, bitsPerPixel, predictor) |
||||
|
=> this.compressionLevel = compressionLevel; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override TiffCompression Method => TiffCompression.Deflate; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Initialize(int rowsPerStrip) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void CompressStrip(Span<byte> rows, int height) |
||||
|
{ |
||||
|
this.memoryStream.Seek(0, SeekOrigin.Begin); |
||||
|
using (var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel)) |
||||
|
{ |
||||
|
if (this.Predictor == TiffPredictor.Horizontal) |
||||
|
{ |
||||
|
HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); |
||||
|
} |
||||
|
|
||||
|
stream.Write(rows); |
||||
|
stream.Flush(); |
||||
|
} |
||||
|
|
||||
|
int size = (int)this.memoryStream.Position; |
||||
|
|
||||
|
#if !NETSTANDARD1_3
|
||||
|
byte[] buffer = this.memoryStream.GetBuffer(); |
||||
|
this.Output.Write(buffer, 0, size); |
||||
|
#else
|
||||
|
this.memoryStream.SetLength(size); |
||||
|
this.memoryStream.Position = 0; |
||||
|
this.memoryStream.CopyTo(this.Output); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
internal sealed class LzwCompressor : TiffBaseCompressor |
||||
|
{ |
||||
|
private TiffLzwEncoder lzwEncoder; |
||||
|
|
||||
|
public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor) |
||||
|
: base(output, allocator, width, bitsPerPixel, predictor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override TiffCompression Method => TiffCompression.Lzw; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void CompressStrip(Span<byte> rows, int height) |
||||
|
{ |
||||
|
if (this.Predictor == TiffPredictor.Horizontal) |
||||
|
{ |
||||
|
HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); |
||||
|
} |
||||
|
|
||||
|
this.lzwEncoder.Encode(rows, this.Output); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
internal sealed class NoCompressor : TiffBaseCompressor |
||||
|
{ |
||||
|
public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel) |
||||
|
: base(output, memoryAllocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override TiffCompression Method => TiffCompression.None; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Initialize(int rowsPerStrip) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void CompressStrip(Span<byte> rows, int height) => this.Output.Write(rows); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
internal sealed class PackBitsCompressor : TiffBaseCompressor |
||||
|
{ |
||||
|
private IManagedByteBuffer pixelData; |
||||
|
|
||||
|
public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) |
||||
|
: base(output, allocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override TiffCompression Method => TiffCompression.PackBits; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Initialize(int rowsPerStrip) |
||||
|
{ |
||||
|
int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1; |
||||
|
this.pixelData = this.Allocator.AllocateManagedByteBuffer(this.BytesPerRow + additionalBytes); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void CompressStrip(Span<byte> rows, int height) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height"); |
||||
|
DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match"); |
||||
|
|
||||
|
Span<byte> span = this.pixelData.GetSpan(); |
||||
|
for (int i = 0; i < height; i++) |
||||
|
{ |
||||
|
Span<byte> row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow); |
||||
|
int size = PackBitsWriter.PackBits(row, span); |
||||
|
this.Output.Write(span.Slice(0, size)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) => this.pixelData?.Dispose(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,128 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pack Bits compression for tiff images. See Tiff Spec v6, section 9.
|
||||
|
/// </summary>
|
||||
|
internal static class PackBitsWriter |
||||
|
{ |
||||
|
public static int PackBits(ReadOnlySpan<byte> rowSpan, Span<byte> compressedRowSpan) |
||||
|
{ |
||||
|
int maxRunLength = 127; |
||||
|
int posInRowSpan = 0; |
||||
|
int bytesWritten = 0; |
||||
|
int literalRunLength = 0; |
||||
|
|
||||
|
while (posInRowSpan < rowSpan.Length) |
||||
|
{ |
||||
|
bool useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan); |
||||
|
if (useReplicateRun) |
||||
|
{ |
||||
|
if (literalRunLength > 0) |
||||
|
{ |
||||
|
WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); |
||||
|
bytesWritten += literalRunLength + 1; |
||||
|
} |
||||
|
|
||||
|
// Write a run with the same bytes.
|
||||
|
int runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength); |
||||
|
WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten); |
||||
|
|
||||
|
bytesWritten += 2; |
||||
|
literalRunLength = 0; |
||||
|
posInRowSpan += runLength; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
literalRunLength++; |
||||
|
posInRowSpan++; |
||||
|
|
||||
|
if (literalRunLength >= maxRunLength) |
||||
|
{ |
||||
|
WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); |
||||
|
bytesWritten += literalRunLength + 1; |
||||
|
literalRunLength = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (literalRunLength > 0) |
||||
|
{ |
||||
|
WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); |
||||
|
bytesWritten += literalRunLength + 1; |
||||
|
} |
||||
|
|
||||
|
return bytesWritten; |
||||
|
} |
||||
|
|
||||
|
private static void WriteLiteralRun(ReadOnlySpan<byte> rowSpan, int end, int literalRunLength, Span<byte> compressedRowSpan, int compressedRowPos) |
||||
|
{ |
||||
|
DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength)); |
||||
|
|
||||
|
int literalRunStart = end - literalRunLength; |
||||
|
sbyte runLength = (sbyte)(literalRunLength - 1); |
||||
|
compressedRowSpan[compressedRowPos] = (byte)runLength; |
||||
|
rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan.Slice(compressedRowPos + 1)); |
||||
|
} |
||||
|
|
||||
|
private static void WriteRun(ReadOnlySpan<byte> rowSpan, int start, int runLength, Span<byte> compressedRowSpan, int compressedRowPos) |
||||
|
{ |
||||
|
DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength)); |
||||
|
|
||||
|
sbyte headerByte = (sbyte)(-runLength + 1); |
||||
|
compressedRowSpan[compressedRowPos] = (byte)headerByte; |
||||
|
compressedRowSpan[compressedRowPos + 1] = rowSpan[start]; |
||||
|
} |
||||
|
|
||||
|
private static bool IsReplicateRun(ReadOnlySpan<byte> rowSpan, int startPos) |
||||
|
{ |
||||
|
// We consider run which has at least 3 same consecutive bytes a candidate for a run.
|
||||
|
var startByte = rowSpan[startPos]; |
||||
|
int count = 0; |
||||
|
for (int i = startPos + 1; i < rowSpan.Length; i++) |
||||
|
{ |
||||
|
if (rowSpan[i] == startByte) |
||||
|
{ |
||||
|
count++; |
||||
|
if (count >= 2) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static int FindRunLength(ReadOnlySpan<byte> rowSpan, int startPos, int maxRunLength) |
||||
|
{ |
||||
|
var startByte = rowSpan[startPos]; |
||||
|
int count = 1; |
||||
|
for (int i = startPos + 1; i < rowSpan.Length; i++) |
||||
|
{ |
||||
|
if (rowSpan[i] == startByte) |
||||
|
{ |
||||
|
count++; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (count == maxRunLength) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return count; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,592 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Bitwriter for writing compressed CCITT T4 1D data.
|
||||
|
/// </summary>
|
||||
|
internal sealed class T4BitCompressor : TiffBaseCompressor |
||||
|
{ |
||||
|
private const uint WhiteZeroRunTermCode = 0x35; |
||||
|
|
||||
|
private const uint BlackZeroRunTermCode = 0x37; |
||||
|
|
||||
|
private static readonly uint[] MakeupRunLength = |
||||
|
{ |
||||
|
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, |
||||
|
{ 27, 0x24 }, { 28, 0x18 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, |
||||
|
{ 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, |
||||
|
{ 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, |
||||
|
{ 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, |
||||
|
{ 63, 0x34 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 2, 0x3 }, { 3, 0x2 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 1, 0x2 }, { 4, 0x3 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 5, 0x3 }, { 6, 0x2 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 7, 0x3 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 8, 0x5 }, { 9, 0x4 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 13, 0x4 }, { 14, 0x7 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 15, 0x18 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, |
||||
|
{ 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, |
||||
|
{ 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, |
||||
|
{ 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, |
||||
|
{ 62, 0x66 }, { 63, 0x67 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 64, 0x1B }, { 128, 0x12 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 192, 0x17 }, { 1664, 0x18 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 256, 0x37 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, |
||||
|
{ 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, |
||||
|
{ 1600, 0x9A }, { 1728, 0x9B } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, |
||||
|
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 64, 0xF } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, |
||||
|
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, |
||||
|
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, |
||||
|
{ 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, |
||||
|
{ 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows.
|
||||
|
/// </summary>
|
||||
|
private readonly bool useModifiedHuffman; |
||||
|
|
||||
|
private IMemoryOwner<byte> compressedDataBuffer; |
||||
|
|
||||
|
private int bytePosition; |
||||
|
|
||||
|
private byte bitPosition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="T4BitCompressor" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="output">The output.</param>
|
||||
|
/// <param name="allocator">The allocator.</param>
|
||||
|
/// <param name="width">The width.</param>
|
||||
|
/// <param name="bitsPerPixel">The bits per pixel.</param>
|
||||
|
/// <param name="useModifiedHuffman">Indicates if the modified huffman RLE should be used.</param>
|
||||
|
public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false) |
||||
|
: base(output, allocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
this.bytePosition = 0; |
||||
|
this.bitPosition = 0; |
||||
|
this.useModifiedHuffman = useModifiedHuffman; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Initialize(int rowsPerStrip) |
||||
|
{ |
||||
|
// This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good.
|
||||
|
int maxNeededBytes = this.Width * rowsPerStrip; |
||||
|
this.compressedDataBuffer = this.Allocator.Allocate<byte>(maxNeededBytes); |
||||
|
} |
||||
|
|
||||
|
/// <summary>Writes a image compressed with CCITT T4 to the stream.</summary>
|
||||
|
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
|
||||
|
/// <param name="height">The strip height.</param>
|
||||
|
public override void CompressStrip(Span<byte> pixelsAsGray, int height) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals"); |
||||
|
DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals"); |
||||
|
|
||||
|
this.compressedDataBuffer.Clear(); |
||||
|
Span<byte> compressedData = this.compressedDataBuffer.GetSpan(); |
||||
|
|
||||
|
this.bytePosition = 0; |
||||
|
this.bitPosition = 0; |
||||
|
|
||||
|
if (!this.useModifiedHuffman) |
||||
|
{ |
||||
|
// An EOL code is expected at the start of the data.
|
||||
|
this.WriteCode(12, 1, compressedData); |
||||
|
} |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
bool isWhiteRun = true; |
||||
|
bool isStartOrRow = true; |
||||
|
int x = 0; |
||||
|
|
||||
|
Span<byte> row = pixelsAsGray.Slice(y * this.Width, this.Width); |
||||
|
while (x < this.Width) |
||||
|
{ |
||||
|
uint runLength = 0; |
||||
|
for (int i = x; i < this.Width; i++) |
||||
|
{ |
||||
|
if (isWhiteRun && row[i] != 255) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (isWhiteRun && row[i] == 255) |
||||
|
{ |
||||
|
runLength++; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (!isWhiteRun && row[i] != 0) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (!isWhiteRun && row[i] == 0) |
||||
|
{ |
||||
|
runLength++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isStartOrRow && runLength == 0) |
||||
|
{ |
||||
|
this.WriteCode(8, WhiteZeroRunTermCode, compressedData); |
||||
|
|
||||
|
isWhiteRun = false; |
||||
|
isStartOrRow = false; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
uint code; |
||||
|
uint codeLength; |
||||
|
if (runLength <= 63) |
||||
|
{ |
||||
|
code = this.GetTermCode(runLength, out codeLength, isWhiteRun); |
||||
|
this.WriteCode(codeLength, code, compressedData); |
||||
|
x += (int)runLength; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
runLength = this.GetBestFittingMakeupRunLength(runLength); |
||||
|
code = this.GetMakeupCode(runLength, out codeLength, isWhiteRun); |
||||
|
this.WriteCode(codeLength, code, compressedData); |
||||
|
x += (int)runLength; |
||||
|
|
||||
|
// If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero.
|
||||
|
if (x == this.Width) |
||||
|
{ |
||||
|
if (isWhiteRun) |
||||
|
{ |
||||
|
this.WriteCode(8, WhiteZeroRunTermCode, compressedData); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.WriteCode(10, BlackZeroRunTermCode, compressedData); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
isStartOrRow = false; |
||||
|
isWhiteRun = !isWhiteRun; |
||||
|
} |
||||
|
|
||||
|
this.WriteEndOfLine(compressedData); |
||||
|
} |
||||
|
|
||||
|
// Write the compressed data to the stream.
|
||||
|
int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; |
||||
|
this.Output.Write(compressedData.Slice(0, bytesToWrite)); |
||||
|
} |
||||
|
|
||||
|
protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); |
||||
|
|
||||
|
private void WriteEndOfLine(Span<byte> compressedData) |
||||
|
{ |
||||
|
if (this.useModifiedHuffman) |
||||
|
{ |
||||
|
// Check if padding is necessary.
|
||||
|
if (this.bitPosition % 8 != 0) |
||||
|
{ |
||||
|
// Skip padding bits, move to next byte.
|
||||
|
this.bytePosition++; |
||||
|
this.bitPosition = 0; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Write EOL.
|
||||
|
this.WriteCode(12, 1, compressedData); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void WriteCode(uint codeLength, uint code, Span<byte> compressedData) |
||||
|
{ |
||||
|
while (codeLength > 0) |
||||
|
{ |
||||
|
int bitNumber = (int)codeLength; |
||||
|
bool bit = (code & (1 << (bitNumber - 1))) != 0; |
||||
|
if (bit) |
||||
|
{ |
||||
|
BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); |
||||
|
} |
||||
|
|
||||
|
this.bitPosition++; |
||||
|
if (this.bitPosition == 8) |
||||
|
{ |
||||
|
this.bytePosition++; |
||||
|
this.bitPosition = 0; |
||||
|
} |
||||
|
|
||||
|
codeLength--; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private uint GetBestFittingMakeupRunLength(uint runLength) |
||||
|
{ |
||||
|
for (int i = 0; i < MakeupRunLength.Length - 1; i++) |
||||
|
{ |
||||
|
if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) |
||||
|
{ |
||||
|
return MakeupRunLength[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return MakeupRunLength[MakeupRunLength.Length - 1]; |
||||
|
} |
||||
|
|
||||
|
private uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) |
||||
|
{ |
||||
|
if (isWhiteRun) |
||||
|
{ |
||||
|
return this.GetWhiteTermCode(runLength, out codeLength); |
||||
|
} |
||||
|
|
||||
|
return this.GetBlackTermCode(runLength, out codeLength); |
||||
|
} |
||||
|
|
||||
|
private uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) |
||||
|
{ |
||||
|
if (isWhiteRun) |
||||
|
{ |
||||
|
return this.GetWhiteMakeupCode(runLength, out codeLength); |
||||
|
} |
||||
|
|
||||
|
return this.GetBlackMakeupCode(runLength, out codeLength); |
||||
|
} |
||||
|
|
||||
|
private uint GetWhiteMakeupCode(uint runLength, out uint codeLength) |
||||
|
{ |
||||
|
codeLength = 0; |
||||
|
|
||||
|
if (WhiteLen5MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 5; |
||||
|
return WhiteLen5MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen6MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 6; |
||||
|
return WhiteLen6MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen7MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 7; |
||||
|
return WhiteLen7MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen8MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 8; |
||||
|
return WhiteLen8MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen9MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 9; |
||||
|
return WhiteLen9MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen11MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 11; |
||||
|
return WhiteLen11MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen12MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 12; |
||||
|
return WhiteLen12MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
private uint GetBlackMakeupCode(uint runLength, out uint codeLength) |
||||
|
{ |
||||
|
codeLength = 0; |
||||
|
|
||||
|
if (BlackLen10MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 10; |
||||
|
return BlackLen10MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen11MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 11; |
||||
|
return BlackLen11MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen12MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 12; |
||||
|
return BlackLen12MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen13MakeupCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 13; |
||||
|
return BlackLen13MakeupCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
private uint GetWhiteTermCode(uint runLength, out uint codeLength) |
||||
|
{ |
||||
|
codeLength = 0; |
||||
|
|
||||
|
if (WhiteLen4TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 4; |
||||
|
return WhiteLen4TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen5TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 5; |
||||
|
return WhiteLen5TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen6TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 6; |
||||
|
return WhiteLen6TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen7TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 7; |
||||
|
return WhiteLen7TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (WhiteLen8TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 8; |
||||
|
return WhiteLen8TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
private uint GetBlackTermCode(uint runLength, out uint codeLength) |
||||
|
{ |
||||
|
codeLength = 0; |
||||
|
|
||||
|
if (BlackLen2TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 2; |
||||
|
return BlackLen2TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen3TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 3; |
||||
|
return BlackLen3TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen4TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 4; |
||||
|
return BlackLen4TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen5TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 5; |
||||
|
return BlackLen5TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen6TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 6; |
||||
|
return BlackLen6TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen7TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 7; |
||||
|
return BlackLen7TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen8TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 8; |
||||
|
return BlackLen8TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen9TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 9; |
||||
|
return BlackLen9TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen10TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 10; |
||||
|
return BlackLen10TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen11TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 11; |
||||
|
return BlackLen11TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
if (BlackLen12TermCodes.ContainsKey(runLength)) |
||||
|
{ |
||||
|
codeLength = 12; |
||||
|
return BlackLen12TermCodes[runLength]; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,270 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Gif; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors |
||||
|
{ |
||||
|
/* |
||||
|
This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys
|
||||
|
|
||||
|
Original licence: |
||||
|
|
||||
|
BSD 3-Clause License |
||||
|
|
||||
|
* Copyright (c) 2015, Harald Kuhr |
||||
|
* All rights reserved. |
||||
|
* |
||||
|
* Redistribution and use in source and binary forms, with or without |
||||
|
* modification, are permitted provided that the following conditions are met: |
||||
|
* |
||||
|
* * Redistributions of source code must retain the above copyright notice, this |
||||
|
* list of conditions and the following disclaimer. |
||||
|
* |
||||
|
* * Redistributions in binary form must reproduce the above copyright notice, |
||||
|
* this list of conditions and the following disclaimer in the documentation |
||||
|
* and/or other materials provided with the distribution. |
||||
|
* |
||||
|
** Neither the name of the copyright holder nor the names of its |
||||
|
* contributors may be used to endorse or promote products derived from |
||||
|
* this software without specific prior written permission. |
||||
|
* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
|
* DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
||||
|
* OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
*/ |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// <para>
|
||||
|
/// This code is based on the <see cref="LzwEncoder"/> used for GIF encoding. There is potential
|
||||
|
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
|
||||
|
/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
|
||||
|
/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
|
||||
|
/// byte indicating the length of the sub-block. In TIFF the data is written as a single block
|
||||
|
/// with no length indicator (this can be determined from the 'StripByteCounts' entry).
|
||||
|
/// </para>
|
||||
|
/// </remarks>
|
||||
|
internal sealed class TiffLzwEncoder : IDisposable |
||||
|
{ |
||||
|
// Clear: Re-initialize tables.
|
||||
|
private static readonly int ClearCode = 256; |
||||
|
|
||||
|
// End of Information.
|
||||
|
private static readonly int EoiCode = 257; |
||||
|
|
||||
|
private static readonly int MinBits = 9; |
||||
|
private static readonly int MaxBits = 12; |
||||
|
|
||||
|
private static readonly int TableSize = 1 << MaxBits; |
||||
|
|
||||
|
// A child is made up of a parent (or prefix) code plus a suffix byte
|
||||
|
// and siblings are strings with a common parent(or prefix) and different suffix bytes.
|
||||
|
private readonly IMemoryOwner<int> children; |
||||
|
|
||||
|
private readonly IMemoryOwner<int> siblings; |
||||
|
|
||||
|
private readonly IMemoryOwner<int> suffixes; |
||||
|
|
||||
|
// Initial setup
|
||||
|
private int parent; |
||||
|
private int bitsPerCode; |
||||
|
private int nextValidCode; |
||||
|
private int maxCode; |
||||
|
|
||||
|
// Buffer for partial codes
|
||||
|
private int bits; |
||||
|
private int bitPos; |
||||
|
private int bufferPosition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="memoryAllocator">The memory allocator.</param>
|
||||
|
public TiffLzwEncoder(MemoryAllocator memoryAllocator) |
||||
|
{ |
||||
|
this.children = memoryAllocator.Allocate<int>(TableSize); |
||||
|
this.siblings = memoryAllocator.Allocate<int>(TableSize); |
||||
|
this.suffixes = memoryAllocator.Allocate<int>(TableSize); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encodes and compresses the indexed pixels to the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The data to compress.</param>
|
||||
|
/// <param name="stream">The stream to write to.</param>
|
||||
|
public void Encode(Span<byte> data, Stream stream) |
||||
|
{ |
||||
|
this.Reset(); |
||||
|
|
||||
|
Span<int> childrenSpan = this.children.GetSpan(); |
||||
|
Span<int> suffixesSpan = this.suffixes.GetSpan(); |
||||
|
Span<int> siblingsSpan = this.siblings.GetSpan(); |
||||
|
int length = data.Length; |
||||
|
|
||||
|
if (length == 0) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (this.parent == -1) |
||||
|
{ |
||||
|
// Init stream.
|
||||
|
this.WriteCode(stream, ClearCode); |
||||
|
this.parent = this.ReadNextByte(data); |
||||
|
} |
||||
|
|
||||
|
while (this.bufferPosition < data.Length) |
||||
|
{ |
||||
|
int value = this.ReadNextByte(data); |
||||
|
int child = childrenSpan[this.parent]; |
||||
|
|
||||
|
if (child > 0) |
||||
|
{ |
||||
|
if (suffixesSpan[child] == value) |
||||
|
{ |
||||
|
this.parent = child; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int sibling = child; |
||||
|
|
||||
|
while (true) |
||||
|
{ |
||||
|
if (siblingsSpan[sibling] > 0) |
||||
|
{ |
||||
|
sibling = siblingsSpan[sibling]; |
||||
|
|
||||
|
if (suffixesSpan[sibling] == value) |
||||
|
{ |
||||
|
this.parent = sibling; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
siblingsSpan[sibling] = (short)this.nextValidCode; |
||||
|
suffixesSpan[this.nextValidCode] = (short)value; |
||||
|
this.WriteCode(stream, this.parent); |
||||
|
this.parent = value; |
||||
|
this.nextValidCode++; |
||||
|
|
||||
|
this.IncreaseCodeSizeOrResetIfNeeded(stream); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
childrenSpan[this.parent] = (short)this.nextValidCode; |
||||
|
suffixesSpan[this.nextValidCode] = (short)value; |
||||
|
this.WriteCode(stream, this.parent); |
||||
|
this.parent = value; |
||||
|
this.nextValidCode++; |
||||
|
|
||||
|
this.IncreaseCodeSizeOrResetIfNeeded(stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Write EOI when we are done.
|
||||
|
this.WriteCode(stream, this.parent); |
||||
|
this.WriteCode(stream, EoiCode); |
||||
|
|
||||
|
// Flush partial codes by writing 0 pad.
|
||||
|
if (this.bitPos > 0) |
||||
|
{ |
||||
|
this.WriteCode(stream, 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
this.children.Dispose(); |
||||
|
this.siblings.Dispose(); |
||||
|
this.suffixes.Dispose(); |
||||
|
} |
||||
|
|
||||
|
private void Reset() |
||||
|
{ |
||||
|
this.children.Clear(); |
||||
|
this.siblings.Clear(); |
||||
|
this.suffixes.Clear(); |
||||
|
|
||||
|
this.parent = -1; |
||||
|
this.bitsPerCode = MinBits; |
||||
|
this.nextValidCode = EoiCode + 1; |
||||
|
this.maxCode = (1 << this.bitsPerCode) - 1; |
||||
|
|
||||
|
this.bits = 0; |
||||
|
this.bitPos = 0; |
||||
|
this.bufferPosition = 0; |
||||
|
} |
||||
|
|
||||
|
private byte ReadNextByte(Span<byte> data) => data[this.bufferPosition++]; |
||||
|
|
||||
|
private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) |
||||
|
{ |
||||
|
if (this.nextValidCode > this.maxCode) |
||||
|
{ |
||||
|
if (this.bitsPerCode == MaxBits) |
||||
|
{ |
||||
|
// Reset stream by writing Clear code.
|
||||
|
this.WriteCode(stream, ClearCode); |
||||
|
|
||||
|
// Reset tables.
|
||||
|
this.ResetTables(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Increase code size.
|
||||
|
this.bitsPerCode++; |
||||
|
this.maxCode = MaxValue(this.bitsPerCode); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void WriteCode(Stream stream, int code) |
||||
|
{ |
||||
|
this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); |
||||
|
this.bitPos += this.bitsPerCode; |
||||
|
|
||||
|
while (this.bitPos >= 8) |
||||
|
{ |
||||
|
int b = (this.bits >> (this.bitPos - 8)) & 0xff; |
||||
|
stream.WriteByte((byte)b); |
||||
|
this.bitPos -= 8; |
||||
|
} |
||||
|
|
||||
|
this.bits &= BitmaskFor(this.bitPos); |
||||
|
} |
||||
|
|
||||
|
private void ResetTables() |
||||
|
{ |
||||
|
this.children.GetSpan().Fill(0); |
||||
|
this.siblings.GetSpan().Fill(0); |
||||
|
this.bitsPerCode = MinBits; |
||||
|
this.maxCode = MaxValue(this.bitsPerCode); |
||||
|
this.nextValidCode = EoiCode + 1; |
||||
|
} |
||||
|
|
||||
|
private static int MaxValue(int codeLen) => (1 << codeLen) - 1; |
||||
|
|
||||
|
private static int BitmaskFor(int bits) => MaxValue(bits); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO.Compression; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Compression.Zlib; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Compression; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed using Deflate compression.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
|
||||
|
/// </remarks>
|
||||
|
internal class DeflateTiffCompression : TiffBaseDecompressor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
|
||||
|
/// <param name="width">The image width.</param>
|
||||
|
/// <param name="bitsPerPixel">The bits used per pixel.</param>
|
||||
|
/// <param name="predictor">The tiff predictor used.</param>
|
||||
|
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) |
||||
|
: base(memoryAllocator, width, bitsPerPixel, predictor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) |
||||
|
{ |
||||
|
long pos = stream.Position; |
||||
|
using (var deframeStream = new ZlibInflateStream( |
||||
|
stream, |
||||
|
() => |
||||
|
{ |
||||
|
int left = (int)(byteCount - (stream.Position - pos)); |
||||
|
return left > 0 ? left : 0; |
||||
|
})) |
||||
|
{ |
||||
|
deframeStream.AllocateNewBytes(byteCount, true); |
||||
|
DeflateStream dataStream = deframeStream.CompressedStream; |
||||
|
dataStream.Read(buffer, 0, buffer.Length); |
||||
|
} |
||||
|
|
||||
|
if (this.Predictor == TiffPredictor.Horizontal) |
||||
|
{ |
||||
|
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a lzw string with a code word and a code length.
|
||||
|
/// </summary>
|
||||
|
public class LzwString |
||||
|
{ |
||||
|
private static readonly LzwString Empty = new LzwString(0, 0, 0, null); |
||||
|
|
||||
|
private readonly LzwString previous; |
||||
|
private readonly byte value; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="LzwString"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="code">The code word.</param>
|
||||
|
public LzwString(byte code) |
||||
|
: this(code, code, 1, null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private LzwString(byte value, byte firstChar, int length, LzwString previous) |
||||
|
{ |
||||
|
this.value = value; |
||||
|
this.FirstChar = firstChar; |
||||
|
this.Length = length; |
||||
|
this.previous = previous; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the code length;
|
||||
|
/// </summary>
|
||||
|
public int Length { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the first character of the codeword.
|
||||
|
/// </summary>
|
||||
|
public byte FirstChar { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Concatenates two code words.
|
||||
|
/// </summary>
|
||||
|
/// <param name="other">The code word to concatenate.</param>
|
||||
|
/// <returns>A concatenated lzw string.</returns>
|
||||
|
public LzwString Concatenate(byte other) |
||||
|
{ |
||||
|
if (this == Empty) |
||||
|
{ |
||||
|
return new LzwString(other); |
||||
|
} |
||||
|
|
||||
|
return new LzwString(other, this.FirstChar, this.Length + 1, this); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes decoded pixel to buffer at a given position.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The buffer to write to.</param>
|
||||
|
/// <param name="offset">The position to write to.</param>
|
||||
|
/// <returns>The number of bytes written.</returns>
|
||||
|
public int WriteTo(Span<byte> buffer, int offset) |
||||
|
{ |
||||
|
if (this.Length == 0) |
||||
|
{ |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (this.Length == 1) |
||||
|
{ |
||||
|
buffer[offset] = this.value; |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
LzwString e = this; |
||||
|
int endIdx = this.Length - 1; |
||||
|
if (endIdx >= buffer.Length) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("Error reading lzw compressed stream. Either pixel buffer to write to is to small or code length is invalid!"); |
||||
|
} |
||||
|
|
||||
|
for (int i = endIdx; i >= 0; i--) |
||||
|
{ |
||||
|
buffer[offset + i] = e.value; |
||||
|
e = e.previous; |
||||
|
} |
||||
|
|
||||
|
return this.Length; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed using LZW compression.
|
||||
|
/// </summary>
|
||||
|
internal class LzwTiffCompression : TiffBaseDecompressor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
|
||||
|
/// <param name="width">The image width.</param>
|
||||
|
/// <param name="bitsPerPixel">The bits used per pixel.</param>
|
||||
|
/// <param name="predictor">The tiff predictor used.</param>
|
||||
|
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) |
||||
|
: base(memoryAllocator, width, bitsPerPixel, predictor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) |
||||
|
{ |
||||
|
var decoder = new TiffLzwDecoder(stream); |
||||
|
decoder.DecodePixels(buffer); |
||||
|
|
||||
|
if (this.Predictor == TiffPredictor.Horizontal) |
||||
|
{ |
||||
|
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression.
|
||||
|
/// </summary>
|
||||
|
internal class ModifiedHuffmanTiffCompression : T4TiffCompression |
||||
|
{ |
||||
|
private readonly byte whiteValue; |
||||
|
|
||||
|
private readonly byte blackValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="allocator">The memory allocator.</param>
|
||||
|
/// <param name="width">The image width.</param>
|
||||
|
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
|
||||
|
/// <param name="photometricInterpretation">The photometric interpretation.</param>
|
||||
|
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) |
||||
|
: base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) |
||||
|
{ |
||||
|
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; |
||||
|
this.whiteValue = (byte)(isWhiteZero ? 0 : 1); |
||||
|
this.blackValue = (byte)(isWhiteZero ? 1 : 0); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) |
||||
|
{ |
||||
|
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); |
||||
|
|
||||
|
buffer.Clear(); |
||||
|
uint bitsWritten = 0; |
||||
|
uint pixelsWritten = 0; |
||||
|
while (bitReader.HasMoreData) |
||||
|
{ |
||||
|
bitReader.ReadNextRun(); |
||||
|
|
||||
|
if (bitReader.RunLength > 0) |
||||
|
{ |
||||
|
if (bitReader.IsWhiteRun) |
||||
|
{ |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); |
||||
|
bitsWritten += bitReader.RunLength; |
||||
|
pixelsWritten += bitReader.RunLength; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); |
||||
|
bitsWritten += bitReader.RunLength; |
||||
|
pixelsWritten += bitReader.RunLength; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (pixelsWritten % this.Width == 0) |
||||
|
{ |
||||
|
bitReader.StartNewRow(); |
||||
|
|
||||
|
// Write padding bits, if necessary.
|
||||
|
uint pad = 8 - (bitsWritten % 8); |
||||
|
if (pad != 8) |
||||
|
{ |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); |
||||
|
bitsWritten += pad; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is not compressed.
|
||||
|
/// </summary>
|
||||
|
internal class NoneTiffCompression : TiffBaseDecompressor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="NoneTiffCompression" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="memoryAllocator">The memory allocator.</param>
|
||||
|
/// <param name="width">The width of the image.</param>
|
||||
|
/// <param name="bitsPerPixel">The bits per pixel.</param>
|
||||
|
public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) |
||||
|
: base(memoryAllocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
|
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
|
||||
|
/// </summary>
|
||||
|
internal class PackBitsTiffCompression : TiffBaseDecompressor |
||||
|
{ |
||||
|
private IMemoryOwner<byte> compressedDataMemory; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PackBitsTiffCompression" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
|
||||
|
/// <param name="width">The width of the image.</param>
|
||||
|
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
|
||||
|
public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) |
||||
|
: base(memoryAllocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) |
||||
|
{ |
||||
|
if (this.compressedDataMemory == null) |
||||
|
{ |
||||
|
this.compressedDataMemory = this.Allocator.Allocate<byte>(byteCount); |
||||
|
} |
||||
|
else if (this.compressedDataMemory.Length() < byteCount) |
||||
|
{ |
||||
|
this.compressedDataMemory.Dispose(); |
||||
|
this.compressedDataMemory = this.Allocator.Allocate<byte>(byteCount); |
||||
|
} |
||||
|
|
||||
|
Span<byte> compressedData = this.compressedDataMemory.GetSpan(); |
||||
|
|
||||
|
stream.Read(compressedData, 0, byteCount); |
||||
|
int compressedOffset = 0; |
||||
|
int decompressedOffset = 0; |
||||
|
|
||||
|
while (compressedOffset < byteCount) |
||||
|
{ |
||||
|
byte headerByte = compressedData[compressedOffset]; |
||||
|
|
||||
|
if (headerByte <= 127) |
||||
|
{ |
||||
|
int literalOffset = compressedOffset + 1; |
||||
|
int literalLength = compressedData[compressedOffset] + 1; |
||||
|
|
||||
|
if ((literalOffset + literalLength) > compressedData.Length) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data."); |
||||
|
} |
||||
|
|
||||
|
compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset)); |
||||
|
|
||||
|
compressedOffset += literalLength + 1; |
||||
|
decompressedOffset += literalLength; |
||||
|
} |
||||
|
else if (headerByte == 0x80) |
||||
|
{ |
||||
|
compressedOffset += 1; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
byte repeatData = compressedData[compressedOffset + 1]; |
||||
|
int repeatLength = 257 - headerByte; |
||||
|
|
||||
|
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength); |
||||
|
|
||||
|
compressedOffset += 2; |
||||
|
decompressedOffset += repeatLength; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ArrayCopyRepeat(byte value, Span<byte> destinationArray, int destinationIndex, int length) |
||||
|
{ |
||||
|
for (int i = 0; i < length; i++) |
||||
|
{ |
||||
|
destinationArray[i + destinationIndex] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,842 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Bitreader for reading compressed CCITT T4 1D data.
|
||||
|
/// </summary>
|
||||
|
internal class T4BitReader : IDisposable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Number of bits read.
|
||||
|
/// </summary>
|
||||
|
private int bitsRead; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Current value.
|
||||
|
/// </summary>
|
||||
|
private uint value; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Number of bits read for the current run value.
|
||||
|
/// </summary>
|
||||
|
private int curValueBitsRead; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Byte position in the buffer.
|
||||
|
/// </summary>
|
||||
|
private ulong position; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates whether its the first line of data which is read from the image.
|
||||
|
/// </summary>
|
||||
|
private bool isFirstScanLine; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates whether we have found a termination code which signals the end of a run.
|
||||
|
/// </summary>
|
||||
|
private bool terminationCodeFound; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// We keep track if its the start of the row, because each run is expected to start with a white run.
|
||||
|
/// If the image row itself starts with black, a white run of zero is expected.
|
||||
|
/// </summary>
|
||||
|
private bool isStartOfRow; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used.
|
||||
|
/// </summary>
|
||||
|
private readonly bool isModifiedHuffmanRle; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.
|
||||
|
/// </summary>
|
||||
|
private readonly bool eolPadding; |
||||
|
|
||||
|
private readonly int dataLength; |
||||
|
|
||||
|
private const int MinCodeLength = 2; |
||||
|
|
||||
|
private readonly int maxCodeLength = 13; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x7, 1 }, { 0x8, 12 }, { 0x3, 13 }, { 0x34, 14 }, { 0x35, 15 }, { 0x2A, 16 }, { 0x2B, 17 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, |
||||
|
{ 0x24, 27 }, { 0x18, 28 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, |
||||
|
{ 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, |
||||
|
{ 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, |
||||
|
{ 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x3, 2 }, { 0x2, 3 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x2, 1 }, { 0x3, 4 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x3, 5 }, { 0x2, 6 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x3, 7 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x5, 8 }, { 0x4, 9 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x4, 13 }, { 0x7, 14 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x18, 15 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x67, 19 }, { 0x68, 20 }, { 0x6C, 21 }, { 0x37, 22 }, { 0x28, 23 }, { 0x17, 24 }, { 0x18, 25 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0xCA, 26 }, { 0xCB, 27 }, { 0xCC, 28 }, { 0xCD, 29 }, { 0x68, 30 }, { 0x69, 31 }, { 0x6A, 32 }, { 0x6B, 33 }, { 0xD2, 34 }, |
||||
|
{ 0xD3, 35 }, { 0xD4, 36 }, { 0xD5, 37 }, { 0xD6, 38 }, { 0xD7, 39 }, { 0x6C, 40 }, { 0x6D, 41 }, { 0xDA, 42 }, { 0xDB, 43 }, |
||||
|
{ 0x54, 44 }, { 0x55, 45 }, { 0x56, 46 }, { 0x57, 47 }, { 0x64, 48 }, { 0x65, 49 }, { 0x52, 50 }, { 0x53, 51 }, { 0x24, 52 }, |
||||
|
{ 0x37, 53 }, { 0x38, 54 }, { 0x27, 55 }, { 0x28, 56 }, { 0x58, 57 }, { 0x59, 58 }, { 0x2B, 59 }, { 0x2C, 60 }, { 0x5A, 61 }, |
||||
|
{ 0x66, 62 }, { 0x67, 63 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x1B, 64 }, { 0x12, 128 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x17, 192 }, { 0x18, 1664 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x37, 256 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0xCC, 704 }, { 0xCD, 768 }, { 0xD2, 832 }, { 0xD3, 896 }, { 0xD4, 960 }, { 0xD5, 1024 }, { 0xD6, 1088 }, |
||||
|
{ 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 }, |
||||
|
{ 0x9A, 1600 }, { 0x9B, 1728 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, |
||||
|
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0xF, 64 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 }, |
||||
|
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, |
||||
|
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } |
||||
|
}; |
||||
|
|
||||
|
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>() |
||||
|
{ |
||||
|
{ 0x6C, 512 }, { 0x6D, 576 }, { 0x4A, 640 }, { 0x4B, 704 }, { 0x4C, 768 }, { 0x4D, 832 }, { 0x72, 896 }, |
||||
|
{ 0x73, 960 }, { 0x74, 1024 }, { 0x75, 1088 }, { 0x76, 1152 }, { 0x77, 1216 }, { 0x52, 1280 }, { 0x53, 1344 }, |
||||
|
{ 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 } |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="T4BitReader" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="input">The compressed input stream.</param>
|
||||
|
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
|
||||
|
/// <param name="allocator">The memory allocator.</param>
|
||||
|
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
|
||||
|
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation. Defaults to false.</param>
|
||||
|
public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false) |
||||
|
{ |
||||
|
this.Data = allocator.Allocate<byte>(bytesToRead); |
||||
|
this.ReadImageDataFromStream(input, bytesToRead); |
||||
|
|
||||
|
this.isModifiedHuffmanRle = isModifiedHuffman; |
||||
|
this.dataLength = bytesToRead; |
||||
|
this.bitsRead = 0; |
||||
|
this.value = 0; |
||||
|
this.curValueBitsRead = 0; |
||||
|
this.position = 0; |
||||
|
this.IsWhiteRun = true; |
||||
|
this.isFirstScanLine = true; |
||||
|
this.isStartOfRow = true; |
||||
|
this.terminationCodeFound = false; |
||||
|
this.RunLength = 0; |
||||
|
this.eolPadding = eolPadding; |
||||
|
|
||||
|
if (this.eolPadding) |
||||
|
{ |
||||
|
this.maxCodeLength = 24; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the compressed image data.
|
||||
|
/// </summary>
|
||||
|
public IMemoryOwner<byte> Data { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether there is more data to read left.
|
||||
|
/// </summary>
|
||||
|
public bool HasMoreData |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (this.isModifiedHuffmanRle) |
||||
|
{ |
||||
|
return this.position < (ulong)this.dataLength - 1 || (this.bitsRead > 0 && this.bitsRead < 7); |
||||
|
} |
||||
|
|
||||
|
return this.position < (ulong)this.dataLength - 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
|
||||
|
/// </summary>
|
||||
|
public bool IsWhiteRun { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of pixels in the current run.
|
||||
|
/// </summary>
|
||||
|
public uint RunLength { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the end of a pixel row has been reached.
|
||||
|
/// </summary>
|
||||
|
public bool IsEndOfScanLine |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (this.eolPadding) |
||||
|
{ |
||||
|
return this.curValueBitsRead >= 12 && this.value == 1; |
||||
|
} |
||||
|
|
||||
|
return this.curValueBitsRead == 12 && this.value == 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Read the next run of pixels.
|
||||
|
/// </summary>
|
||||
|
public void ReadNextRun() |
||||
|
{ |
||||
|
if (this.terminationCodeFound) |
||||
|
{ |
||||
|
this.IsWhiteRun = !this.IsWhiteRun; |
||||
|
this.terminationCodeFound = false; |
||||
|
} |
||||
|
|
||||
|
this.Reset(); |
||||
|
|
||||
|
if (this.isFirstScanLine && !this.isModifiedHuffmanRle) |
||||
|
{ |
||||
|
// We expect an EOL before the first data.
|
||||
|
this.value = this.ReadValue(this.eolPadding ? 16 : 12); |
||||
|
|
||||
|
if (!this.IsEndOfScanLine) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found"); |
||||
|
} |
||||
|
|
||||
|
this.Reset(); |
||||
|
} |
||||
|
|
||||
|
// A code word must have at least 2 bits.
|
||||
|
this.value = this.ReadValue(MinCodeLength); |
||||
|
|
||||
|
do |
||||
|
{ |
||||
|
if (this.curValueBitsRead > this.maxCodeLength) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read"); |
||||
|
} |
||||
|
|
||||
|
bool isMakeupCode = this.IsMakeupCode(); |
||||
|
if (isMakeupCode) |
||||
|
{ |
||||
|
if (this.IsWhiteRun) |
||||
|
{ |
||||
|
this.RunLength += this.WhiteMakeupCodeRunLength(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.RunLength += this.BlackMakeupCodeRunLength(); |
||||
|
} |
||||
|
|
||||
|
this.isStartOfRow = false; |
||||
|
this.Reset(resetRunLength: false); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
bool isTerminatingCode = this.IsTerminatingCode(); |
||||
|
if (isTerminatingCode) |
||||
|
{ |
||||
|
// Each line starts with a white run. If the image starts with black, a white run with length zero is written.
|
||||
|
if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) |
||||
|
{ |
||||
|
this.IsWhiteRun = !this.IsWhiteRun; |
||||
|
this.Reset(); |
||||
|
this.isStartOfRow = false; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (this.IsWhiteRun) |
||||
|
{ |
||||
|
this.RunLength += this.WhiteTerminatingCodeRunLength(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.RunLength += this.BlackTerminatingCodeRunLength(); |
||||
|
} |
||||
|
|
||||
|
this.terminationCodeFound = true; |
||||
|
this.isStartOfRow = false; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
var currBit = this.ReadValue(1); |
||||
|
this.value = (this.value << 1) | currBit; |
||||
|
|
||||
|
if (this.IsEndOfScanLine) |
||||
|
{ |
||||
|
this.StartNewRow(); |
||||
|
} |
||||
|
} |
||||
|
while (!this.IsEndOfScanLine); |
||||
|
|
||||
|
this.isFirstScanLine = false; |
||||
|
} |
||||
|
|
||||
|
public void StartNewRow() |
||||
|
{ |
||||
|
// Each new row starts with a white run.
|
||||
|
this.IsWhiteRun = true; |
||||
|
this.isStartOfRow = true; |
||||
|
this.terminationCodeFound = false; |
||||
|
|
||||
|
if (this.isModifiedHuffmanRle) |
||||
|
{ |
||||
|
int pad = 8 - (this.bitsRead % 8); |
||||
|
if (pad != 8) |
||||
|
{ |
||||
|
// Skip padding bits, move to next byte.
|
||||
|
this.position++; |
||||
|
this.bitsRead = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Dispose() => this.Data.Dispose(); |
||||
|
|
||||
|
private uint WhiteTerminatingCodeRunLength() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 4: |
||||
|
{ |
||||
|
return WhiteLen4TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 5: |
||||
|
{ |
||||
|
return WhiteLen5TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 6: |
||||
|
{ |
||||
|
return WhiteLen6TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 7: |
||||
|
{ |
||||
|
return WhiteLen7TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 8: |
||||
|
{ |
||||
|
return WhiteLen8TermCodes[this.value]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
private uint BlackTerminatingCodeRunLength() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 2: |
||||
|
{ |
||||
|
return BlackLen2TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 3: |
||||
|
{ |
||||
|
return BlackLen3TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 4: |
||||
|
{ |
||||
|
return BlackLen4TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 5: |
||||
|
{ |
||||
|
return BlackLen5TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 6: |
||||
|
{ |
||||
|
return BlackLen6TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 7: |
||||
|
{ |
||||
|
return BlackLen7TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 8: |
||||
|
{ |
||||
|
return BlackLen8TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 9: |
||||
|
{ |
||||
|
return BlackLen9TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 10: |
||||
|
{ |
||||
|
return BlackLen10TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 11: |
||||
|
{ |
||||
|
return BlackLen11TermCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 12: |
||||
|
{ |
||||
|
return BlackLen12TermCodes[this.value]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
private uint WhiteMakeupCodeRunLength() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 5: |
||||
|
{ |
||||
|
return WhiteLen5MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 6: |
||||
|
{ |
||||
|
return WhiteLen6MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 7: |
||||
|
{ |
||||
|
return WhiteLen7MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 8: |
||||
|
{ |
||||
|
return WhiteLen8MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 9: |
||||
|
{ |
||||
|
return WhiteLen9MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 11: |
||||
|
{ |
||||
|
return WhiteLen11MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 12: |
||||
|
{ |
||||
|
return WhiteLen12MakeupCodes[this.value]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
private uint BlackMakeupCodeRunLength() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 10: |
||||
|
{ |
||||
|
return BlackLen10MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 11: |
||||
|
{ |
||||
|
return BlackLen11MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 12: |
||||
|
{ |
||||
|
return BlackLen12MakeupCodes[this.value]; |
||||
|
} |
||||
|
|
||||
|
case 13: |
||||
|
{ |
||||
|
return BlackLen13MakeupCodes[this.value]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
private bool IsMakeupCode() |
||||
|
{ |
||||
|
if (this.IsWhiteRun) |
||||
|
{ |
||||
|
return this.IsWhiteMakeupCode(); |
||||
|
} |
||||
|
|
||||
|
return this.IsBlackMakeupCode(); |
||||
|
} |
||||
|
|
||||
|
private bool IsWhiteMakeupCode() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 5: |
||||
|
{ |
||||
|
return WhiteLen5MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 6: |
||||
|
{ |
||||
|
return WhiteLen6MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 7: |
||||
|
{ |
||||
|
return WhiteLen7MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 8: |
||||
|
{ |
||||
|
return WhiteLen8MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 9: |
||||
|
{ |
||||
|
return WhiteLen9MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 11: |
||||
|
{ |
||||
|
return WhiteLen11MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 12: |
||||
|
{ |
||||
|
if (this.isModifiedHuffmanRle) |
||||
|
{ |
||||
|
if (this.value == 1) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return WhiteLen12MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private bool IsBlackMakeupCode() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 10: |
||||
|
{ |
||||
|
return BlackLen10MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 11: |
||||
|
{ |
||||
|
if (this.isModifiedHuffmanRle) |
||||
|
{ |
||||
|
if (this.value == 0) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return BlackLen11MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 12: |
||||
|
{ |
||||
|
return BlackLen12MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 13: |
||||
|
{ |
||||
|
return BlackLen13MakeupCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private bool IsTerminatingCode() |
||||
|
{ |
||||
|
if (this.IsWhiteRun) |
||||
|
{ |
||||
|
return this.IsWhiteTerminatingCode(); |
||||
|
} |
||||
|
|
||||
|
return this.IsBlackTerminatingCode(); |
||||
|
} |
||||
|
|
||||
|
private bool IsWhiteTerminatingCode() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 4: |
||||
|
{ |
||||
|
return WhiteLen4TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 5: |
||||
|
{ |
||||
|
return WhiteLen5TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 6: |
||||
|
{ |
||||
|
return WhiteLen6TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 7: |
||||
|
{ |
||||
|
return WhiteLen7TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 8: |
||||
|
{ |
||||
|
return WhiteLen8TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private bool IsBlackTerminatingCode() |
||||
|
{ |
||||
|
switch (this.curValueBitsRead) |
||||
|
{ |
||||
|
case 2: |
||||
|
{ |
||||
|
return BlackLen2TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 3: |
||||
|
{ |
||||
|
return BlackLen3TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 4: |
||||
|
{ |
||||
|
return BlackLen4TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 5: |
||||
|
{ |
||||
|
return BlackLen5TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 6: |
||||
|
{ |
||||
|
return BlackLen6TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 7: |
||||
|
{ |
||||
|
return BlackLen7TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 8: |
||||
|
{ |
||||
|
return BlackLen8TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 9: |
||||
|
{ |
||||
|
return BlackLen9TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 10: |
||||
|
{ |
||||
|
return BlackLen10TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 11: |
||||
|
{ |
||||
|
return BlackLen11TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
|
||||
|
case 12: |
||||
|
{ |
||||
|
return BlackLen12TermCodes.ContainsKey(this.value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private void Reset(bool resetRunLength = true) |
||||
|
{ |
||||
|
this.value = 0; |
||||
|
this.curValueBitsRead = 0; |
||||
|
|
||||
|
if (resetRunLength) |
||||
|
{ |
||||
|
this.RunLength = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private uint ReadValue(int nBits) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); |
||||
|
|
||||
|
uint v = 0; |
||||
|
int shift = nBits; |
||||
|
while (shift-- > 0) |
||||
|
{ |
||||
|
uint bit = this.GetBit(); |
||||
|
v |= bit << shift; |
||||
|
this.curValueBitsRead++; |
||||
|
} |
||||
|
|
||||
|
return v; |
||||
|
} |
||||
|
|
||||
|
private uint GetBit() |
||||
|
{ |
||||
|
if (this.bitsRead >= 8) |
||||
|
{ |
||||
|
this.LoadNewByte(); |
||||
|
} |
||||
|
|
||||
|
Span<byte> dataSpan = this.Data.GetSpan(); |
||||
|
int shift = 8 - this.bitsRead - 1; |
||||
|
var bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); |
||||
|
this.bitsRead++; |
||||
|
|
||||
|
return bit; |
||||
|
} |
||||
|
|
||||
|
private void LoadNewByte() |
||||
|
{ |
||||
|
this.position++; |
||||
|
this.bitsRead = 0; |
||||
|
|
||||
|
if (this.position >= (ulong)this.dataLength) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("tiff image has invalid t4 compressed data"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ReadImageDataFromStream(Stream input, int bytesToRead) |
||||
|
{ |
||||
|
Span<byte> dataSpan = this.Data.GetSpan(); |
||||
|
input.Read(dataSpan, 0, bytesToRead); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression.
|
||||
|
/// </summary>
|
||||
|
internal class T4TiffCompression : TiffBaseDecompressor |
||||
|
{ |
||||
|
private readonly FaxCompressionOptions faxCompressionOptions; |
||||
|
|
||||
|
private readonly byte whiteValue; |
||||
|
|
||||
|
private readonly byte blackValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="T4TiffCompression" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="allocator">The memory allocator.</param>
|
||||
|
/// <param name="width">The image width.</param>
|
||||
|
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
|
||||
|
/// <param name="faxOptions">Fax compression options.</param>
|
||||
|
/// <param name="photometricInterpretation">The photometric interpretation.</param>
|
||||
|
public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) |
||||
|
: base(allocator, width, bitsPerPixel) |
||||
|
{ |
||||
|
this.faxCompressionOptions = faxOptions; |
||||
|
|
||||
|
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; |
||||
|
this.whiteValue = (byte)(isWhiteZero ? 0 : 1); |
||||
|
this.blackValue = (byte)(isWhiteZero ? 1 : 0); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) |
||||
|
{ |
||||
|
if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); |
||||
|
} |
||||
|
|
||||
|
var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); |
||||
|
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding); |
||||
|
|
||||
|
buffer.Clear(); |
||||
|
uint bitsWritten = 0; |
||||
|
while (bitReader.HasMoreData) |
||||
|
{ |
||||
|
bitReader.ReadNextRun(); |
||||
|
|
||||
|
if (bitReader.RunLength > 0) |
||||
|
{ |
||||
|
if (bitReader.IsWhiteRun) |
||||
|
{ |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); |
||||
|
bitsWritten += bitReader.RunLength; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); |
||||
|
bitsWritten += bitReader.RunLength; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (bitReader.IsEndOfScanLine) |
||||
|
{ |
||||
|
// Write padding bytes, if necessary.
|
||||
|
uint pad = 8 - (bitsWritten % 8); |
||||
|
if (pad != 8) |
||||
|
{ |
||||
|
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); |
||||
|
bitsWritten += pad; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,257 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
||||
|
{ |
||||
|
/* |
||||
|
This implementation is based on a port of a java tiff decoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys
|
||||
|
|
||||
|
Original licence: |
||||
|
|
||||
|
BSD 3-Clause License |
||||
|
|
||||
|
* Copyright (c) 2015, Harald Kuhr |
||||
|
* All rights reserved. |
||||
|
* |
||||
|
* Redistribution and use in source and binary forms, with or without |
||||
|
* modification, are permitted provided that the following conditions are met: |
||||
|
* |
||||
|
* * Redistributions of source code must retain the above copyright notice, this |
||||
|
* list of conditions and the following disclaimer. |
||||
|
* |
||||
|
* * Redistributions in binary form must reproduce the above copyright notice, |
||||
|
* this list of conditions and the following disclaimer in the documentation |
||||
|
* and/or other materials provided with the distribution. |
||||
|
* |
||||
|
** Neither the name of the copyright holder nor the names of its |
||||
|
* contributors may be used to endorse or promote products derived from |
||||
|
* this software without specific prior written permission. |
||||
|
* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
|
* DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
||||
|
* OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
*/ |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decompresses and decodes data using the dynamic LZW algorithms, see TIFF spec Section 13.
|
||||
|
/// </summary>
|
||||
|
internal sealed class TiffLzwDecoder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The stream to decode.
|
||||
|
/// </summary>
|
||||
|
private readonly Stream stream; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// As soon as we use entry 4094 of the table (maxTableSize - 2), the lzw compressor write out a (12-bit) ClearCode.
|
||||
|
/// At this point, the compressor reinitializes the string table and then writes out 9-bit codes again.
|
||||
|
/// </summary>
|
||||
|
private const int ClearCode = 256; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// End of Information.
|
||||
|
/// </summary>
|
||||
|
private const int EoiCode = 257; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Minimum code length of 9 bits.
|
||||
|
/// </summary>
|
||||
|
private const int MinBits = 9; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maximum code length of 12 bits.
|
||||
|
/// </summary>
|
||||
|
private const int MaxBits = 12; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maximum table size of 4096.
|
||||
|
/// </summary>
|
||||
|
private const int TableSize = 1 << MaxBits; |
||||
|
|
||||
|
private readonly LzwString[] table; |
||||
|
|
||||
|
private int tableLength; |
||||
|
private int bitsPerCode; |
||||
|
private int oldCode = ClearCode; |
||||
|
private int maxCode; |
||||
|
private int bitMask; |
||||
|
private int maxString; |
||||
|
private bool eofReached; |
||||
|
private int nextData; |
||||
|
private int nextBits; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffLzwDecoder" /> class
|
||||
|
/// and sets the stream, where the compressed data should be read from.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream to read from.</param>
|
||||
|
/// <exception cref="ArgumentNullException"><paramref name="stream" /> is null.</exception>
|
||||
|
public TiffLzwDecoder(Stream stream) |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
this.stream = stream; |
||||
|
|
||||
|
// TODO: Investigate a manner by which we can avoid this allocation.
|
||||
|
this.table = new LzwString[TableSize]; |
||||
|
for (int i = 0; i < 256; i++) |
||||
|
{ |
||||
|
this.table[i] = new LzwString((byte)i); |
||||
|
} |
||||
|
|
||||
|
this.Init(); |
||||
|
} |
||||
|
|
||||
|
private void Init() |
||||
|
{ |
||||
|
// Table length is 256 + 2, because of special clear code and end of information code.
|
||||
|
this.tableLength = 258; |
||||
|
this.bitsPerCode = MinBits; |
||||
|
this.bitMask = BitmaskFor(this.bitsPerCode); |
||||
|
this.maxCode = this.MaxCode(); |
||||
|
this.maxString = 1; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes and decompresses all pixel indices from the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pixels">The pixel array to decode to.</param>
|
||||
|
public void DecodePixels(Span<byte> pixels) |
||||
|
{ |
||||
|
// Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992.
|
||||
|
// See Section 13: "LZW Compression"/"LZW Decoding", page 61+
|
||||
|
int code; |
||||
|
int offset = 0; |
||||
|
|
||||
|
while ((code = this.GetNextCode()) != EoiCode) |
||||
|
{ |
||||
|
if (code == ClearCode) |
||||
|
{ |
||||
|
this.Init(); |
||||
|
code = this.GetNextCode(); |
||||
|
|
||||
|
if (code == EoiCode) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (this.table[code] == null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})"); |
||||
|
} |
||||
|
|
||||
|
offset += this.table[code].WriteTo(pixels, offset); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (this.table[this.oldCode] == null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})"); |
||||
|
} |
||||
|
|
||||
|
if (this.IsInTable(code)) |
||||
|
{ |
||||
|
offset += this.table[code].WriteTo(pixels, offset); |
||||
|
|
||||
|
this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar); |
||||
|
|
||||
|
offset += outString.WriteTo(pixels, offset); |
||||
|
this.AddStringToTable(outString); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.oldCode = code; |
||||
|
|
||||
|
if (offset >= pixels.Length) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void AddStringToTable(LzwString lzwString) |
||||
|
{ |
||||
|
if (this.tableLength > this.table.Length) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)"); |
||||
|
} |
||||
|
|
||||
|
this.table[this.tableLength++] = lzwString; |
||||
|
|
||||
|
if (this.tableLength > this.maxCode) |
||||
|
{ |
||||
|
this.bitsPerCode++; |
||||
|
|
||||
|
if (this.bitsPerCode > MaxBits) |
||||
|
{ |
||||
|
// Continue reading MaxBits (12 bit) length codes.
|
||||
|
this.bitsPerCode = MaxBits; |
||||
|
} |
||||
|
|
||||
|
this.bitMask = BitmaskFor(this.bitsPerCode); |
||||
|
this.maxCode = this.MaxCode(); |
||||
|
} |
||||
|
|
||||
|
if (lzwString.Length > this.maxString) |
||||
|
{ |
||||
|
this.maxString = lzwString.Length; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private int GetNextCode() |
||||
|
{ |
||||
|
if (this.eofReached) |
||||
|
{ |
||||
|
return EoiCode; |
||||
|
} |
||||
|
|
||||
|
int read = this.stream.ReadByte(); |
||||
|
if (read < 0) |
||||
|
{ |
||||
|
this.eofReached = true; |
||||
|
return EoiCode; |
||||
|
} |
||||
|
|
||||
|
this.nextData = (this.nextData << 8) | read; |
||||
|
this.nextBits += 8; |
||||
|
|
||||
|
if (this.nextBits < this.bitsPerCode) |
||||
|
{ |
||||
|
read = this.stream.ReadByte(); |
||||
|
if (read < 0) |
||||
|
{ |
||||
|
this.eofReached = true; |
||||
|
return EoiCode; |
||||
|
} |
||||
|
|
||||
|
this.nextData = (this.nextData << 8) | read; |
||||
|
this.nextBits += 8; |
||||
|
} |
||||
|
|
||||
|
int code = (this.nextData >> (this.nextBits - this.bitsPerCode)) & this.bitMask; |
||||
|
this.nextBits -= this.bitsPerCode; |
||||
|
|
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
private bool IsInTable(int code) => code < this.tableLength; |
||||
|
|
||||
|
private int MaxCode() => this.bitMask - 1; |
||||
|
|
||||
|
private static int BitmaskFor(int bits) => (1 << bits) - 1; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fax compression options, see TIFF spec page 51f (T4Options).
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
public enum FaxCompressionOptions : uint |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No options.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// If set, 2-dimensional coding is used (otherwise 1-dimensional is assumed).
|
||||
|
/// </summary>
|
||||
|
TwoDimensionalCoding = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// If set, uncompressed mode is used.
|
||||
|
/// </summary>
|
||||
|
UncompressedMode = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// If set, fill bits have been added as necessary before EOL codes such that
|
||||
|
/// EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte
|
||||
|
/// preceded by a zero nibble: xxxx-0000 0000-0001.
|
||||
|
/// </summary>
|
||||
|
EolPadding = 4 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,138 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images.
|
||||
|
/// </summary>
|
||||
|
internal static class HorizontalPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Inverts the horizontal prediction.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
|
||||
|
/// <param name="width">The width of the image or strip.</param>
|
||||
|
/// <param name="bitsPerPixel">Bits per pixel.</param>
|
||||
|
public static void Undo(Span<byte> pixelBytes, int width, int bitsPerPixel) |
||||
|
{ |
||||
|
if (bitsPerPixel == 8) |
||||
|
{ |
||||
|
Undo8Bit(pixelBytes, width); |
||||
|
} |
||||
|
else if (bitsPerPixel == 24) |
||||
|
{ |
||||
|
Undo24Bit(pixelBytes, width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ApplyHorizontalPrediction(Span<byte> rows, int width, int bitsPerPixel) |
||||
|
{ |
||||
|
if (bitsPerPixel == 8) |
||||
|
{ |
||||
|
ApplyHorizontalPrediction8Bit(rows, width); |
||||
|
} |
||||
|
else if (bitsPerPixel == 24) |
||||
|
{ |
||||
|
ApplyHorizontalPrediction24Bit(rows, width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a horizontal predictor to the rgb row.
|
||||
|
/// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next.
|
||||
|
/// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus
|
||||
|
/// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rows">The rgb pixel rows.</param>
|
||||
|
/// <param name="width">The width.</param>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private static void ApplyHorizontalPrediction24Bit(Span<byte> rows, int width) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); |
||||
|
int height = rows.Length / width; |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<byte> rowSpan = rows.Slice(y * width, width); |
||||
|
Span<Rgb24> rowRgb = MemoryMarshal.Cast<byte, Rgb24>(rowSpan); |
||||
|
|
||||
|
for (int x = rowRgb.Length - 1; x >= 1; x--) |
||||
|
{ |
||||
|
byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R); |
||||
|
byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G); |
||||
|
byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B); |
||||
|
var rgb = new Rgb24(r, g, b); |
||||
|
rowRgb[x].FromRgb24(rgb); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a horizontal predictor to a gray pixel row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rows">The gray pixel rows.</param>
|
||||
|
/// <param name="width">The width.</param>
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private static void ApplyHorizontalPrediction8Bit(Span<byte> rows, int width) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); |
||||
|
int height = rows.Length / width; |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<byte> rowSpan = rows.Slice(y * width, width); |
||||
|
for (int x = rowSpan.Length - 1; x >= 1; x--) |
||||
|
{ |
||||
|
rowSpan[x] -= rowSpan[x - 1]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void Undo8Bit(Span<byte> pixelBytes, int width) |
||||
|
{ |
||||
|
int rowBytesCount = width; |
||||
|
int height = pixelBytes.Length / rowBytesCount; |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); |
||||
|
|
||||
|
byte pixelValue = rowBytes[0]; |
||||
|
for (int x = 1; x < width; x++) |
||||
|
{ |
||||
|
pixelValue += rowBytes[x]; |
||||
|
rowBytes[x] = pixelValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void Undo24Bit(Span<byte> pixelBytes, int width) |
||||
|
{ |
||||
|
int rowBytesCount = width * 3; |
||||
|
int height = pixelBytes.Length / rowBytesCount; |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); |
||||
|
Span<Rgb24> rowRgb = MemoryMarshal.Cast<byte, Rgb24>(rowBytes).Slice(0, width); |
||||
|
ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); |
||||
|
byte r = rowRgbBase.R; |
||||
|
byte g = rowRgbBase.G; |
||||
|
byte b = rowRgbBase.B; |
||||
|
|
||||
|
for (int x = 1; x < rowRgb.Length; x++) |
||||
|
{ |
||||
|
ref Rgb24 pixel = ref rowRgb[x]; |
||||
|
r += pixel.R; |
||||
|
g += pixel.G; |
||||
|
b += pixel.B; |
||||
|
var rgb = new Rgb24(r, g, b); |
||||
|
pixel.FromRgb24(rgb); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
internal abstract class TiffBaseCompression : IDisposable |
||||
|
{ |
||||
|
private bool isDisposed; |
||||
|
|
||||
|
protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) |
||||
|
{ |
||||
|
this.Allocator = allocator; |
||||
|
this.Width = width; |
||||
|
this.BitsPerPixel = bitsPerPixel; |
||||
|
this.Predictor = predictor; |
||||
|
this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the image width.
|
||||
|
/// </summary>
|
||||
|
public int Width { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bits per pixel.
|
||||
|
/// </summary>
|
||||
|
public int BitsPerPixel { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bytes per row.
|
||||
|
/// </summary>
|
||||
|
public int BytesPerRow { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the predictor to use. Should only be used with deflate or lzw compression.
|
||||
|
/// </summary>
|
||||
|
public TiffPredictor Predictor { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the memory allocator.
|
||||
|
/// </summary>
|
||||
|
protected MemoryAllocator Allocator { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if (this.isDisposed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.isDisposed = true; |
||||
|
this.Dispose(true); |
||||
|
} |
||||
|
|
||||
|
protected abstract void Dispose(bool disposing); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
internal abstract class TiffBaseCompressor : TiffBaseCompression |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffBaseCompressor"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="output">The output stream to write the compressed image to.</param>
|
||||
|
/// <param name="allocator">The memory allocator.</param>
|
||||
|
/// <param name="width">The image width.</param>
|
||||
|
/// <param name="bitsPerPixel">Bits per pixel.</param>
|
||||
|
/// <param name="predictor">The predictor to use (should only be used with deflate or lzw compression). Defaults to none.</param>
|
||||
|
protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) |
||||
|
: base(allocator, width, bitsPerPixel, predictor) |
||||
|
=> this.Output = output; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the compression method to use.
|
||||
|
/// </summary>
|
||||
|
public abstract TiffCompression Method { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the output stream to write the compressed image to.
|
||||
|
/// </summary>
|
||||
|
public Stream Output { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Does any initialization required for the compression.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rowsPerStrip">The number of rows per strip.</param>
|
||||
|
public abstract void Initialize(int rowsPerStrip); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compresses a strip of the image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rows">Image rows to compress.</param>
|
||||
|
/// <param name="height">Image height.</param>
|
||||
|
public abstract void CompressStrip(Span<byte> rows, int height); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The base tiff decompressor class.
|
||||
|
/// </summary>
|
||||
|
internal abstract class TiffBaseDecompressor : TiffBaseCompression |
||||
|
{ |
||||
|
protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) |
||||
|
: base(allocator, width, bitsPerPixel, predictor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decompresses image data into the supplied buffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
|
||||
|
/// <param name="stripOffset">The strip offset of stream.</param>
|
||||
|
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
|
||||
|
/// <param name="buffer">The output buffer for uncompressed data.</param>
|
||||
|
public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, Span<byte> buffer) |
||||
|
{ |
||||
|
if (stripByteCount > int.MaxValue) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("The StripByteCount value is too big."); |
||||
|
} |
||||
|
|
||||
|
stream.Seek(stripOffset, SeekOrigin.Begin); |
||||
|
this.Decompress(stream, (int)stripByteCount, buffer); |
||||
|
|
||||
|
if (stripOffset + stripByteCount < stream.Position) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decompresses image data into the supplied buffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
|
||||
|
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
|
||||
|
/// <param name="buffer">The output buffer for uncompressed data.</param>
|
||||
|
protected abstract void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Compression.Zlib; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
internal static class TiffCompressorFactory |
||||
|
{ |
||||
|
public static TiffBaseCompressor Create( |
||||
|
TiffCompression method, |
||||
|
Stream output, |
||||
|
MemoryAllocator allocator, |
||||
|
int width, |
||||
|
int bitsPerPixel, |
||||
|
DeflateCompressionLevel compressionLevel, |
||||
|
TiffPredictor predictor) |
||||
|
{ |
||||
|
switch (method) |
||||
|
{ |
||||
|
// The following compression types are not implemented in the encoder and will default to no compression instead.
|
||||
|
case TiffCompression.ItuTRecT43: |
||||
|
case TiffCompression.ItuTRecT82: |
||||
|
case TiffCompression.Jpeg: |
||||
|
case TiffCompression.OldJpeg: |
||||
|
case TiffCompression.OldDeflate: |
||||
|
case TiffCompression.None: |
||||
|
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
|
||||
|
return new NoCompressor(output, allocator, width, bitsPerPixel); |
||||
|
|
||||
|
case TiffCompression.PackBits: |
||||
|
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
return new PackBitsCompressor(output, allocator, width, bitsPerPixel); |
||||
|
|
||||
|
case TiffCompression.Deflate: |
||||
|
return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel); |
||||
|
|
||||
|
case TiffCompression.Lzw: |
||||
|
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); |
||||
|
return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor); |
||||
|
|
||||
|
case TiffCompression.CcittGroup3Fax: |
||||
|
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
return new T4BitCompressor(output, allocator, width, bitsPerPixel, false); |
||||
|
|
||||
|
case TiffCompression.Ccitt1D: |
||||
|
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
return new T4BitCompressor(output, allocator, width, bitsPerPixel, true); |
||||
|
|
||||
|
default: |
||||
|
throw TiffThrowHelper.NotSupportedCompressor(method.ToString()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides enumeration of the various TIFF compression types the decoder can handle.
|
||||
|
/// </summary>
|
||||
|
internal enum TiffDecoderCompressionType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Image data is stored uncompressed in the TIFF file.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Image data is compressed using PackBits compression.
|
||||
|
/// </summary>
|
||||
|
PackBits = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Image data is compressed using Deflate compression.
|
||||
|
/// </summary>
|
||||
|
Deflate = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Image data is compressed using LZW compression.
|
||||
|
/// </summary>
|
||||
|
Lzw = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Image data is compressed using T4-encoding: CCITT T.4.
|
||||
|
/// </summary>
|
||||
|
T4 = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Image data is compressed using modified huffman compression.
|
||||
|
/// </summary>
|
||||
|
HuffmanRle = 5, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Compression |
||||
|
{ |
||||
|
internal static class TiffDecompressorsFactory |
||||
|
{ |
||||
|
public static TiffBaseDecompressor Create( |
||||
|
TiffDecoderCompressionType method, |
||||
|
MemoryAllocator allocator, |
||||
|
TiffPhotometricInterpretation photometricInterpretation, |
||||
|
int width, |
||||
|
int bitsPerPixel, |
||||
|
TiffPredictor predictor, |
||||
|
FaxCompressionOptions faxOptions) |
||||
|
{ |
||||
|
switch (method) |
||||
|
{ |
||||
|
case TiffDecoderCompressionType.None: |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); |
||||
|
return new NoneTiffCompression(allocator, width, bitsPerPixel); |
||||
|
|
||||
|
case TiffDecoderCompressionType.PackBits: |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); |
||||
|
return new PackBitsTiffCompression(allocator, width, bitsPerPixel); |
||||
|
|
||||
|
case TiffDecoderCompressionType.Deflate: |
||||
|
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); |
||||
|
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor); |
||||
|
|
||||
|
case TiffDecoderCompressionType.Lzw: |
||||
|
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); |
||||
|
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor); |
||||
|
|
||||
|
case TiffDecoderCompressionType.T4: |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation); |
||||
|
|
||||
|
case TiffDecoderCompressionType.HuffmanRle: |
||||
|
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); |
||||
|
return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation); |
||||
|
|
||||
|
default: |
||||
|
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the compression formats defined by the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
public enum TiffCompression : ushort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A invalid compression value.
|
||||
|
/// </summary>
|
||||
|
Invalid = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// No compression.
|
||||
|
/// </summary>
|
||||
|
None = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding.
|
||||
|
/// </summary>
|
||||
|
Ccitt1D = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// PackBits compression
|
||||
|
/// </summary>
|
||||
|
PackBits = 32773, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
|
||||
|
/// </summary>
|
||||
|
CcittGroup3Fax = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
|
||||
|
///
|
||||
|
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
|
||||
|
/// if this is choosen.
|
||||
|
/// </summary>
|
||||
|
CcittGroup4Fax = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// LZW compression (see Section 13 of the TIFF 6.0 specification).
|
||||
|
/// </summary>
|
||||
|
Lzw = 5, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification).
|
||||
|
///
|
||||
|
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
|
||||
|
/// if this is chosen.
|
||||
|
/// </summary>
|
||||
|
OldJpeg = 6, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// JPEG compression (see TIFF Specification, supplement 2).
|
||||
|
///
|
||||
|
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
|
||||
|
/// if this is chosen.
|
||||
|
/// </summary>
|
||||
|
Jpeg = 7, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Deflate compression, using zlib data format (see TIFF Specification, supplement 2).
|
||||
|
/// </summary>
|
||||
|
Deflate = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Deflate compression - old.
|
||||
|
///
|
||||
|
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
|
||||
|
/// if this is chosen.
|
||||
|
/// </summary>
|
||||
|
OldDeflate = 32946, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301).
|
||||
|
///
|
||||
|
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
|
||||
|
/// if this is chosen.
|
||||
|
/// </summary>
|
||||
|
ItuTRecT82 = 9, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301).
|
||||
|
///
|
||||
|
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
|
||||
|
/// if this is chosen.
|
||||
|
/// </summary>
|
||||
|
ItuTRecT43 = 10 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,113 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines constants defined in the TIFF specification.
|
||||
|
/// </summary>
|
||||
|
internal static class TiffConstants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Byte order markers for indicating little endian encoding.
|
||||
|
/// </summary>
|
||||
|
public const byte ByteOrderLittleEndian = 0x49; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Byte order markers for indicating big endian encoding.
|
||||
|
/// </summary>
|
||||
|
public const byte ByteOrderBigEndian = 0x4D; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Byte order markers for indicating little endian encoding.
|
||||
|
/// </summary>
|
||||
|
public const ushort ByteOrderLittleEndianShort = 0x4949; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Byte order markers for indicating big endian encoding.
|
||||
|
/// </summary>
|
||||
|
public const ushort ByteOrderBigEndianShort = 0x4D4D; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Magic number used within the image file header to identify a TIFF format file.
|
||||
|
/// </summary>
|
||||
|
public const ushort HeaderMagicNumber = 42; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// RowsPerStrip default value, which is effectively infinity.
|
||||
|
/// </summary>
|
||||
|
public const int RowsPerStripInfinity = 2147483647; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Size (in bytes) of the TIFF file header.
|
||||
|
/// </summary>
|
||||
|
public const int SizeOfTiffHeader = 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Size (in bytes) of each individual TIFF IFD entry
|
||||
|
/// </summary>
|
||||
|
public const int SizeOfIfdEntry = 12; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Size (in bytes) of the Short and SShort data types
|
||||
|
/// </summary>
|
||||
|
public const int SizeOfShort = 2; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Size (in bytes) of the Long and SLong data types
|
||||
|
/// </summary>
|
||||
|
public const int SizeOfLong = 4; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Size (in bytes) of the Rational and SRational data types
|
||||
|
/// </summary>
|
||||
|
public const int SizeOfRational = 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Size (in bytes) of the Float data type
|
||||
|
/// </summary>
|
||||
|
public const int SizeOfFloat = 4; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Size (in bytes) of the Double data type
|
||||
|
/// </summary>
|
||||
|
public const int SizeOfDouble = 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default strip size is 8k.
|
||||
|
/// </summary>
|
||||
|
public const int DefaultStripSize = 8 * 1024; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The bits per sample for 1 bit bicolor images.
|
||||
|
/// </summary>
|
||||
|
public static readonly ushort[] BitsPerSample1Bit = { 1 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The bits per sample for images with a 4 color palette.
|
||||
|
/// </summary>
|
||||
|
public static readonly ushort[] BitsPerSample4Bit = { 4 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The bits per sample for 8 bit images.
|
||||
|
/// </summary>
|
||||
|
public static readonly ushort[] BitsPerSample8Bit = { 8 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The bits per sample for images with 8 bits for each color channel.
|
||||
|
/// </summary>
|
||||
|
public static readonly ushort[] BitsPerSampleRgb8Bit = { 8, 8, 8 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The list of mimetypes that equate to a tiff.
|
||||
|
/// </summary>
|
||||
|
public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The list of file extensions that equate to a tiff.
|
||||
|
/// </summary>
|
||||
|
public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" }; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the possible uses of extra components in TIFF format files.
|
||||
|
/// </summary>
|
||||
|
internal enum TiffExtraSamples |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Unspecified data.
|
||||
|
/// </summary>
|
||||
|
Unspecified = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Associated alpha data (with pre-multiplied color).
|
||||
|
/// </summary>
|
||||
|
AssociatedAlpha = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unassociated alpha data.
|
||||
|
/// </summary>
|
||||
|
UnassociatedAlpha = 2 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the fill orders defined by the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
internal enum TiffFillOrder : ushort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Pixels with lower column values are stored in the higher-order bits of the byte.
|
||||
|
/// </summary>
|
||||
|
MostSignificantBitFirst = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Pixels with lower column values are stored in the lower-order bits of the byte.
|
||||
|
/// </summary>
|
||||
|
LeastSignificantBitFirst = 2 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the sub-file types defined by the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
public enum TiffNewSubfileType : uint |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A full-resolution image.
|
||||
|
/// </summary>
|
||||
|
FullImage = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reduced-resolution version of another image in this TIFF file.
|
||||
|
/// </summary>
|
||||
|
Preview = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A single page of a multi-page image.
|
||||
|
/// </summary>
|
||||
|
SinglePage = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A transparency mask for another image in this TIFF file.
|
||||
|
/// </summary>
|
||||
|
TransparencyMask = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification).
|
||||
|
/// </summary>
|
||||
|
AlternativePreview = 65536, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Mixed raster content (see RFC2301).
|
||||
|
/// </summary>
|
||||
|
MixedRasterContent = 8 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the image orientations defined by the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
internal enum TiffOrientation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual top and left-hand side of the image respectively.
|
||||
|
/// </summary>
|
||||
|
TopLeft = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual top and right-hand side of the image respectively.
|
||||
|
/// </summary>
|
||||
|
TopRight = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively.
|
||||
|
/// </summary>
|
||||
|
BottomRight = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively.
|
||||
|
/// </summary>
|
||||
|
BottomLeft = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual left-hand side and top of the image respectively.
|
||||
|
/// </summary>
|
||||
|
LeftTop = 5, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual right-hand side and top of the image respectively.
|
||||
|
/// </summary>
|
||||
|
RightTop = 6, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively.
|
||||
|
/// </summary>
|
||||
|
RightBottom = 7, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively.
|
||||
|
/// </summary>
|
||||
|
LeftBottom = 8 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the photometric interpretation formats defined by the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
public enum TiffPhotometricInterpretation : ushort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black.
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
WhiteIsZero = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white.
|
||||
|
/// </summary>
|
||||
|
BlackIsZero = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// RGB image.
|
||||
|
/// </summary>
|
||||
|
Rgb = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Palette Color.
|
||||
|
/// </summary>
|
||||
|
PaletteColor = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A transparency mask.
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
TransparencyMask = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification).
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
Separated = 5, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// YCbCr (see Section 21 of the TIFF 6.0 specification).
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
YCbCr = 6, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification).
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
CieLab = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ICC L*a*b* (see TIFF Specification, supplement 1).
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
IccLab = 9, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ITU L*a*b* (see RFC2301).
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
ItuLab = 10, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Color Filter Array (see the DNG specification).
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
ColorFilterArray = 32803, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Linear Raw (see the DNG specification).
|
||||
|
///
|
||||
|
/// Not supported by the TiffEncoder.
|
||||
|
/// </summary>
|
||||
|
LinearRaw = 34892 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing how the components of each pixel are stored the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
public enum TiffPlanarConfiguration : ushort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Chunky format.
|
||||
|
/// The component values for each pixel are stored contiguously.
|
||||
|
/// The order of the components within the pixel is specified by
|
||||
|
/// PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB.
|
||||
|
/// </summary>
|
||||
|
Chunky = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Planar format.
|
||||
|
/// The components are stored in separate “component planes.” The
|
||||
|
/// values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional
|
||||
|
/// array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns
|
||||
|
/// for row 0 are stored first, followed by the columns of row 1, and so on.)
|
||||
|
/// PhotometricInterpretation describes the type of data stored in each component
|
||||
|
/// plane. For example, RGB data is stored with the Red components in one component
|
||||
|
/// plane, the Green in another, and the Blue in another.
|
||||
|
/// </summary>
|
||||
|
Planar = 2 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A mathematical operator that is applied to the image data before an encoding scheme is applied.
|
||||
|
/// </summary>
|
||||
|
public enum TiffPredictor : ushort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No prediction.
|
||||
|
/// </summary>
|
||||
|
None = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Horizontal differencing.
|
||||
|
/// </summary>
|
||||
|
Horizontal = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Floating point horizontal differencing.
|
||||
|
///
|
||||
|
/// Note: The Tiff Encoder does not yet support this. If this is chosen, the encoder will fallback to none.
|
||||
|
/// </summary>
|
||||
|
FloatingPoint = 3 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Specifies how to interpret each data sample in a pixel.
|
||||
|
/// </summary>
|
||||
|
public enum TiffSampleFormat : ushort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Unsigned integer data. Default value.
|
||||
|
/// </summary>
|
||||
|
UnsignedInteger = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Signed integer data.
|
||||
|
/// </summary>
|
||||
|
SignedInteger = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// IEEE floating point data.
|
||||
|
/// </summary>
|
||||
|
Float = 3, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Undefined data format.
|
||||
|
/// </summary>
|
||||
|
Undefined = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The complex int.
|
||||
|
/// </summary>
|
||||
|
ComplexInt = 5, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The complex float.
|
||||
|
/// </summary>
|
||||
|
ComplexFloat = 6 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the sub-file types defined by the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
public enum TiffSubfileType : ushort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Full-resolution image data.
|
||||
|
/// </summary>
|
||||
|
FullImage = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reduced-resolution image data.
|
||||
|
/// </summary>
|
||||
|
Preview = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A single page of a multi-page image.
|
||||
|
/// </summary>
|
||||
|
SinglePage = 3 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.Constants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumeration representing the thresholding applied to image data defined by the Tiff file-format.
|
||||
|
/// </summary>
|
||||
|
internal enum TiffThresholding |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No dithering or halftoning.
|
||||
|
/// </summary>
|
||||
|
None = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An ordered dither or halftone technique.
|
||||
|
/// </summary>
|
||||
|
Ordered = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A randomized process such as error diffusion.
|
||||
|
/// </summary>
|
||||
|
Random = 3 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Encapsulates the options for the <see cref="TiffDecoder"/>.
|
||||
|
/// </summary>
|
||||
|
internal interface ITiffDecoderOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
|
||||
|
/// </summary>
|
||||
|
bool IgnoreMetadata { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Compression.Zlib; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
|
||||
|
/// </summary>
|
||||
|
internal interface ITiffEncoderOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the number of bits per pixel.
|
||||
|
/// </summary>
|
||||
|
TiffBitsPerPixel? BitsPerPixel { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the compression type to use.
|
||||
|
/// </summary>
|
||||
|
TiffCompression? Compression { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the compression level 1-9 for the deflate compression mode.
|
||||
|
/// <remarks>Defaults to <see cref="DeflateCompressionLevel.DefaultCompression"/>.</remarks>
|
||||
|
/// </summary>
|
||||
|
DeflateCompressionLevel? CompressionLevel { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
|
||||
|
/// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used.
|
||||
|
/// </summary>
|
||||
|
TiffPhotometricInterpretation? PhotometricInterpretation { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression.
|
||||
|
/// </summary>
|
||||
|
TiffPredictor? HorizontalPredictor { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the quantizer for creating a color palette image.
|
||||
|
/// </summary>
|
||||
|
IQuantizer Quantizer { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The TIFF IFD reader class.
|
||||
|
/// </summary>
|
||||
|
internal class DirectoryReader |
||||
|
{ |
||||
|
private readonly Stream stream; |
||||
|
|
||||
|
private uint nextIfdOffset; |
||||
|
|
||||
|
// used for sequential read big values (actual for multiframe big files)
|
||||
|
// todo: different tags can link to the same data (stream offset) - investigate
|
||||
|
private readonly SortedList<uint, Action> lazyLoaders = new SortedList<uint, Action>(new DuplicateKeyComparer<uint>()); |
||||
|
|
||||
|
public DirectoryReader(Stream stream) => this.stream = stream; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the byte order.
|
||||
|
/// </summary>
|
||||
|
public ByteOrder ByteOrder { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads image file directories.
|
||||
|
/// </summary>
|
||||
|
/// <returns>Image file directories.</returns>
|
||||
|
public IEnumerable<ExifProfile> Read() |
||||
|
{ |
||||
|
this.ByteOrder = ReadByteOrder(this.stream); |
||||
|
this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader(); |
||||
|
return this.ReadIfds(); |
||||
|
} |
||||
|
|
||||
|
private static ByteOrder ReadByteOrder(Stream stream) |
||||
|
{ |
||||
|
var headerBytes = new byte[2]; |
||||
|
stream.Read(headerBytes, 0, 2); |
||||
|
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) |
||||
|
{ |
||||
|
return ByteOrder.LittleEndian; |
||||
|
} |
||||
|
else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) |
||||
|
{ |
||||
|
return ByteOrder.BigEndian; |
||||
|
} |
||||
|
|
||||
|
throw TiffThrowHelper.ThrowInvalidHeader(); |
||||
|
} |
||||
|
|
||||
|
private IEnumerable<ExifProfile> ReadIfds() |
||||
|
{ |
||||
|
var readers = new List<EntryReader>(); |
||||
|
while (this.nextIfdOffset != 0 && this.nextIfdOffset < this.stream.Length) |
||||
|
{ |
||||
|
var reader = new EntryReader(this.stream, this.ByteOrder, this.nextIfdOffset, this.lazyLoaders); |
||||
|
reader.ReadTags(); |
||||
|
|
||||
|
this.nextIfdOffset = reader.NextIfdOffset; |
||||
|
|
||||
|
readers.Add(reader); |
||||
|
} |
||||
|
|
||||
|
// Sequential reading big values.
|
||||
|
foreach (Action loader in this.lazyLoaders.Values) |
||||
|
{ |
||||
|
loader(); |
||||
|
} |
||||
|
|
||||
|
var list = new List<ExifProfile>(); |
||||
|
foreach (EntryReader reader in readers) |
||||
|
{ |
||||
|
var profile = new ExifProfile(reader.Values, reader.InvalidTags); |
||||
|
list.Add(profile); |
||||
|
} |
||||
|
|
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// <see cref="DuplicateKeyComparer{TKey}"/> used for possibility add a duplicate offsets (but tags don't duplicate).
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
|
private class DuplicateKeyComparer<TKey> : IComparer<TKey> |
||||
|
where TKey : IComparable |
||||
|
{ |
||||
|
public int Compare(TKey x, TKey y) |
||||
|
{ |
||||
|
int result = x.CompareTo(y); |
||||
|
|
||||
|
// Handle equality as being greater.
|
||||
|
return (result == 0) ? 1 : result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
internal class EntryReader : BaseExifReader |
||||
|
{ |
||||
|
private readonly uint startOffset; |
||||
|
|
||||
|
private readonly SortedList<uint, Action> lazyLoaders; |
||||
|
|
||||
|
public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList<uint, Action> lazyLoaders) |
||||
|
: base(stream) |
||||
|
{ |
||||
|
this.IsBigEndian = byteOrder == ByteOrder.BigEndian; |
||||
|
this.startOffset = ifdOffset; |
||||
|
this.lazyLoaders = lazyLoaders; |
||||
|
} |
||||
|
|
||||
|
public List<IExifValue> Values { get; } = new List<IExifValue>(); |
||||
|
|
||||
|
public uint NextIfdOffset { get; private set; } |
||||
|
|
||||
|
public void ReadTags() |
||||
|
{ |
||||
|
this.ReadValues(this.Values, this.startOffset); |
||||
|
this.NextIfdOffset = this.ReadUInt32(); |
||||
|
|
||||
|
this.ReadSubIfd(this.Values); |
||||
|
} |
||||
|
|
||||
|
protected override void RegisterExtLoader(uint offset, Action reader) => |
||||
|
this.lazyLoaders.Add(offset, reader); |
||||
|
} |
||||
|
|
||||
|
internal class HeaderReader : BaseExifReader |
||||
|
{ |
||||
|
public HeaderReader(Stream stream, ByteOrder byteOrder) |
||||
|
: base(stream) => |
||||
|
this.IsBigEndian = byteOrder == ByteOrder.BigEndian; |
||||
|
|
||||
|
public uint FirstIfdOffset { get; private set; } |
||||
|
|
||||
|
public uint ReadFileHeader() |
||||
|
{ |
||||
|
ushort magic = this.ReadUInt16(); |
||||
|
if (magic != TiffConstants.HeaderMagicNumber) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowInvalidHeader(); |
||||
|
} |
||||
|
|
||||
|
this.FirstIfdOffset = this.ReadUInt32(); |
||||
|
return this.FirstIfdOffset; |
||||
|
} |
||||
|
|
||||
|
protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotSupportedException(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="ImageMetadata"/> type.
|
||||
|
/// </summary>
|
||||
|
public static partial class MetadataExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the tiff format specific metadata for the image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="metadata">The metadata this method extends.</param>
|
||||
|
/// <returns>The <see cref="TiffMetadata"/>.</returns>
|
||||
|
public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the tiff format specific metadata for the image frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="metadata">The metadata this method extends.</param>
|
||||
|
/// <returns>The <see cref="TiffFrameMetadata"/>.</returns>
|
||||
|
public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'BlackIsZero' photometric interpretation (optimized for bilevel images).
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class BlackIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
int offset = 0; |
||||
|
|
||||
|
Color black = Color.Black; |
||||
|
Color white = Color.White; |
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x += 8) |
||||
|
{ |
||||
|
byte b = data[offset++]; |
||||
|
int maxShift = Math.Min(left + width - x, 8); |
||||
|
|
||||
|
for (int shift = 0; shift < maxShift; shift++) |
||||
|
{ |
||||
|
int bit = (b >> (7 - shift)) & 1; |
||||
|
|
||||
|
color.FromRgba32(bit == 0 ? black : white); |
||||
|
|
||||
|
pixels[x + shift, y] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'BlackIsZero' photometric interpretation (optimized for 4-bit grayscale images).
|
||||
|
/// </summary>
|
||||
|
internal class BlackIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
int offset = 0; |
||||
|
bool isOddWidth = (width & 1) == 1; |
||||
|
|
||||
|
var l8 = default(L8); |
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width - 1;) |
||||
|
{ |
||||
|
byte byteData = data[offset++]; |
||||
|
|
||||
|
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); |
||||
|
l8.PackedValue = intensity1; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[x++, y] = color; |
||||
|
|
||||
|
byte intensity2 = (byte)((byteData & 0x0F) * 17); |
||||
|
l8.PackedValue = intensity2; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[x++, y] = color; |
||||
|
} |
||||
|
|
||||
|
if (isOddWidth) |
||||
|
{ |
||||
|
byte byteData = data[offset++]; |
||||
|
|
||||
|
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); |
||||
|
l8.PackedValue = intensity1; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[left + width - 1, y] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'BlackIsZero' photometric interpretation (optimized for 8-bit grayscale images).
|
||||
|
/// </summary>
|
||||
|
internal class BlackIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
int offset = 0; |
||||
|
|
||||
|
var l8 = default(L8); |
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
byte intensity = data[offset++]; |
||||
|
|
||||
|
l8.PackedValue = intensity; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[x, y] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Utils; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
|
||||
|
/// </summary>
|
||||
|
internal class BlackIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly ushort bitsPerSample0; |
||||
|
|
||||
|
private readonly float factor; |
||||
|
|
||||
|
public BlackIsZeroTiffColor(ushort[] bitsPerSample) |
||||
|
{ |
||||
|
this.bitsPerSample0 = bitsPerSample[0]; |
||||
|
this.factor = (1 << this.bitsPerSample0) - 1.0f; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
var bitReader = new BitReader(data); |
||||
|
|
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
int value = bitReader.ReadBits(this.bitsPerSample0); |
||||
|
float intensity = value / this.factor; |
||||
|
|
||||
|
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); |
||||
|
pixels[x, y] = color; |
||||
|
} |
||||
|
|
||||
|
bitReader.NextRow(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Utils; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
|
||||
|
/// </summary>
|
||||
|
internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly ushort bitsPerSample0; |
||||
|
|
||||
|
private readonly TPixel[] palette; |
||||
|
|
||||
|
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
|
||||
|
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
|
||||
|
public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap) |
||||
|
{ |
||||
|
this.bitsPerSample0 = bitsPerSample[0]; |
||||
|
int colorCount = 1 << this.bitsPerSample0; |
||||
|
this.palette = GeneratePalette(colorMap, colorCount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var bitReader = new BitReader(data); |
||||
|
|
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
int index = bitReader.ReadBits(this.bitsPerSample0); |
||||
|
pixels[x, y] = this.palette[index]; |
||||
|
} |
||||
|
|
||||
|
bitReader.NextRow(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount) |
||||
|
{ |
||||
|
var palette = new TPixel[colorCount]; |
||||
|
|
||||
|
const int rOffset = 0; |
||||
|
int gOffset = colorCount; |
||||
|
int bOffset = colorCount * 2; |
||||
|
|
||||
|
for (int i = 0; i < palette.Length; i++) |
||||
|
{ |
||||
|
float r = colorMap[rOffset + i] / 65535F; |
||||
|
float g = colorMap[gOffset + i] / 65535F; |
||||
|
float b = colorMap[bOffset + i] / 65535F; |
||||
|
palette[i].FromVector4(new Vector4(r, g, b, 1.0f)); |
||||
|
} |
||||
|
|
||||
|
return palette; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'RGB' photometric interpretation (optimized for 8-bit full color images).
|
||||
|
/// </summary>
|
||||
|
internal class Rgb888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
int offset = 0; |
||||
|
|
||||
|
var rgba = default(Rgba32); |
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
Span<TPixel> pixelRow = pixels.GetRowSpan(y); |
||||
|
|
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
byte r = data[offset++]; |
||||
|
byte g = data[offset++]; |
||||
|
byte b = data[offset++]; |
||||
|
|
||||
|
rgba.PackedValue = (uint)(r | (g << 8) | (b << 16) | (0xff << 24)); |
||||
|
color.FromRgba32(rgba); |
||||
|
|
||||
|
pixelRow[x] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Utils; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
|
||||
|
/// </summary>
|
||||
|
internal class RgbPlanarTiffColor<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly float rFactor; |
||||
|
|
||||
|
private readonly float gFactor; |
||||
|
|
||||
|
private readonly float bFactor; |
||||
|
|
||||
|
private readonly ushort bitsPerSampleR; |
||||
|
|
||||
|
private readonly ushort bitsPerSampleG; |
||||
|
|
||||
|
private readonly ushort bitsPerSampleB; |
||||
|
|
||||
|
public RgbPlanarTiffColor(ushort[] bitsPerSample) |
||||
|
/* : base(bitsPerSample, null) */ |
||||
|
{ |
||||
|
this.bitsPerSampleR = bitsPerSample[0]; |
||||
|
this.bitsPerSampleG = bitsPerSample[1]; |
||||
|
this.bitsPerSampleB = bitsPerSample[2]; |
||||
|
|
||||
|
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; |
||||
|
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; |
||||
|
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes pixel data using the current photometric interpretation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The buffers to read image data from.</param>
|
||||
|
/// <param name="pixels">The image buffer to write pixels to.</param>
|
||||
|
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
||||
|
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
||||
|
/// <param name="width">The width of the image block.</param>
|
||||
|
/// <param name="height">The height of the image block.</param>
|
||||
|
public void Decode(IManagedByteBuffer[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
var rBitReader = new BitReader(data[0].GetSpan()); |
||||
|
var gBitReader = new BitReader(data[1].GetSpan()); |
||||
|
var bBitReader = new BitReader(data[2].GetSpan()); |
||||
|
|
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; |
||||
|
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; |
||||
|
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; |
||||
|
|
||||
|
color.FromVector4(new Vector4(r, g, b, 1.0f)); |
||||
|
pixels[x, y] = color; |
||||
|
} |
||||
|
|
||||
|
rBitReader.NextRow(); |
||||
|
gBitReader.NextRow(); |
||||
|
bBitReader.NextRow(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Utils; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'RGB' photometric interpretation (for all bit depths).
|
||||
|
/// </summary>
|
||||
|
internal class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly float rFactor; |
||||
|
|
||||
|
private readonly float gFactor; |
||||
|
|
||||
|
private readonly float bFactor; |
||||
|
|
||||
|
private readonly ushort bitsPerSampleR; |
||||
|
|
||||
|
private readonly ushort bitsPerSampleG; |
||||
|
|
||||
|
private readonly ushort bitsPerSampleB; |
||||
|
|
||||
|
public RgbTiffColor(ushort[] bitsPerSample) |
||||
|
{ |
||||
|
this.bitsPerSampleR = bitsPerSample[0]; |
||||
|
this.bitsPerSampleG = bitsPerSample[1]; |
||||
|
this.bitsPerSampleB = bitsPerSample[2]; |
||||
|
|
||||
|
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; |
||||
|
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; |
||||
|
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
var bitReader = new BitReader(data); |
||||
|
|
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; |
||||
|
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; |
||||
|
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; |
||||
|
|
||||
|
color.FromVector4(new Vector4(r, g, b, 1.0f)); |
||||
|
pixels[x, y] = color; |
||||
|
} |
||||
|
|
||||
|
bitReader.NextRow(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The base class for photometric interpretation decoders.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal abstract class TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Decodes source raw pixel data using the current photometric interpretation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The buffer to read image data from.</param>
|
||||
|
/// <param name="pixels">The image buffer to write pixels to.</param>
|
||||
|
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
||||
|
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
||||
|
/// <param name="width">The width of the image block.</param>
|
||||
|
/// <param name="height">The height of the image block.</param>
|
||||
|
public abstract void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
internal static class TiffColorDecoderFactory<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) |
||||
|
{ |
||||
|
switch (colorType) |
||||
|
{ |
||||
|
case TiffColorType.WhiteIsZero: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new WhiteIsZeroTiffColor<TPixel>(bitsPerSample); |
||||
|
|
||||
|
case TiffColorType.WhiteIsZero1: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new WhiteIsZero1TiffColor<TPixel>(); |
||||
|
|
||||
|
case TiffColorType.WhiteIsZero4: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new WhiteIsZero4TiffColor<TPixel>(); |
||||
|
|
||||
|
case TiffColorType.WhiteIsZero8: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new WhiteIsZero8TiffColor<TPixel>(); |
||||
|
|
||||
|
case TiffColorType.BlackIsZero: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new BlackIsZeroTiffColor<TPixel>(bitsPerSample); |
||||
|
|
||||
|
case TiffColorType.BlackIsZero1: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new BlackIsZero1TiffColor<TPixel>(); |
||||
|
|
||||
|
case TiffColorType.BlackIsZero4: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new BlackIsZero4TiffColor<TPixel>(); |
||||
|
|
||||
|
case TiffColorType.BlackIsZero8: |
||||
|
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new BlackIsZero8TiffColor<TPixel>(); |
||||
|
|
||||
|
case TiffColorType.Rgb: |
||||
|
DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new RgbTiffColor<TPixel>(bitsPerSample); |
||||
|
|
||||
|
case TiffColorType.Rgb888: |
||||
|
DebugGuard.IsTrue( |
||||
|
bitsPerSample.Length == 3 |
||||
|
&& bitsPerSample[0] == 8 |
||||
|
&& bitsPerSample[1] == 8 |
||||
|
&& bitsPerSample[2] == 8, |
||||
|
"bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new Rgb888TiffColor<TPixel>(); |
||||
|
|
||||
|
case TiffColorType.PaletteColor: |
||||
|
DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); |
||||
|
DebugGuard.NotNull(colorMap, "colorMap"); |
||||
|
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap); |
||||
|
|
||||
|
default: |
||||
|
throw TiffThrowHelper.InvalidColorType(colorType.ToString()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static RgbPlanarTiffColor<TPixel> CreatePlanar(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) |
||||
|
{ |
||||
|
switch (colorType) |
||||
|
{ |
||||
|
case TiffColorType.RgbPlanar: |
||||
|
DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); |
||||
|
DebugGuard.IsTrue(colorMap == null, "colorMap"); |
||||
|
return new RgbPlanarTiffColor<TPixel>(bitsPerSample); |
||||
|
|
||||
|
default: |
||||
|
throw TiffThrowHelper.InvalidColorType(colorType.ToString()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides enumeration of the various TIFF photometric interpretation implementation types.
|
||||
|
/// </summary>
|
||||
|
internal enum TiffColorType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white.
|
||||
|
/// </summary>
|
||||
|
BlackIsZero, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for bilevel images.
|
||||
|
/// </summary>
|
||||
|
BlackIsZero1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 4-bit images.
|
||||
|
/// </summary>
|
||||
|
BlackIsZero4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 8-bit images.
|
||||
|
/// </summary>
|
||||
|
BlackIsZero8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
|
||||
|
/// </summary>
|
||||
|
WhiteIsZero, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for bilevel images.
|
||||
|
/// </summary>
|
||||
|
WhiteIsZero1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 4-bit images.
|
||||
|
/// </summary>
|
||||
|
WhiteIsZero4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 8-bit images.
|
||||
|
/// </summary>
|
||||
|
WhiteIsZero8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Palette-color.
|
||||
|
/// </summary>
|
||||
|
PaletteColor, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// RGB Full Color.
|
||||
|
/// </summary>
|
||||
|
Rgb, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// RGB Full Color. Optimized implementation for 8-bit images.
|
||||
|
/// </summary>
|
||||
|
Rgb888, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// RGB Full Color. Planar configuration of data.
|
||||
|
/// </summary>
|
||||
|
RgbPlanar, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for bilevel images).
|
||||
|
/// </summary>
|
||||
|
internal class WhiteIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
int offset = 0; |
||||
|
|
||||
|
Color black = Color.Black; |
||||
|
Color white = Color.White; |
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x += 8) |
||||
|
{ |
||||
|
byte b = data[offset++]; |
||||
|
int maxShift = Math.Min(left + width - x, 8); |
||||
|
|
||||
|
for (int shift = 0; shift < maxShift; shift++) |
||||
|
{ |
||||
|
int bit = (b >> (7 - shift)) & 1; |
||||
|
|
||||
|
color.FromRgba32(bit == 0 ? white : black); |
||||
|
|
||||
|
pixels[x + shift, y] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 4-bit grayscale images).
|
||||
|
/// </summary>
|
||||
|
internal class WhiteIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
int offset = 0; |
||||
|
bool isOddWidth = (width & 1) == 1; |
||||
|
|
||||
|
var l8 = default(L8); |
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width - 1;) |
||||
|
{ |
||||
|
byte byteData = data[offset++]; |
||||
|
|
||||
|
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); |
||||
|
l8.PackedValue = intensity1; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[x++, y] = color; |
||||
|
|
||||
|
byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17); |
||||
|
l8.PackedValue = intensity2; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[x++, y] = color; |
||||
|
} |
||||
|
|
||||
|
if (isOddWidth) |
||||
|
{ |
||||
|
byte byteData = data[offset++]; |
||||
|
|
||||
|
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); |
||||
|
l8.PackedValue = intensity1; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[left + width - 1, y] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 8-bit grayscale images).
|
||||
|
/// </summary>
|
||||
|
internal class WhiteIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
int offset = 0; |
||||
|
|
||||
|
var l8 = default(L8); |
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
byte intensity = (byte)(255 - data[offset++]); |
||||
|
|
||||
|
l8.PackedValue = intensity; |
||||
|
color.FromL8(l8); |
||||
|
|
||||
|
pixels[x, y] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Utils; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
|
||||
|
/// </summary>
|
||||
|
internal class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly ushort bitsPerSample0; |
||||
|
|
||||
|
private readonly float factor; |
||||
|
|
||||
|
public WhiteIsZeroTiffColor(ushort[] bitsPerSample) |
||||
|
{ |
||||
|
this.bitsPerSample0 = bitsPerSample[0]; |
||||
|
this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
||||
|
{ |
||||
|
var color = default(TPixel); |
||||
|
|
||||
|
var bitReader = new BitReader(data); |
||||
|
|
||||
|
for (int y = top; y < top + height; y++) |
||||
|
{ |
||||
|
for (int x = left; x < left + width; x++) |
||||
|
{ |
||||
|
int value = bitReader.ReadBits(this.bitsPerSample0); |
||||
|
float intensity = 1.0f - (value / this.factor); |
||||
|
|
||||
|
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); |
||||
|
pixels[x, y] = color; |
||||
|
} |
||||
|
|
||||
|
bitReader.NextRow(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,253 @@ |
|||||
|
# ImageSharp TIFF codec |
||||
|
|
||||
|
## References |
||||
|
- TIFF |
||||
|
- [TIFF 6.0 Specification](http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf),(http://www.npes.org/pdf/TIFF-v6.pdf) |
||||
|
- [TIFF Supplement 1](http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf) |
||||
|
- [TIFF Supplement 2](http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf) |
||||
|
- [TIFF Supplement 3](http://chriscox.org/TIFFTN3d1.pdf) |
||||
|
- [TIFF-F/FX Extension (RFC2301)](http://www.ietf.org/rfc/rfc2301.txt) |
||||
|
- [TIFF/EP Extension (Wikipedia)](https://en.wikipedia.org/wiki/TIFF/EP) |
||||
|
- [Adobe TIFF Pages](http://partners.adobe.com/public/developer/tiff/index.html) |
||||
|
- [Unofficial TIFF FAQ](http://www.awaresystems.be/imaging/tiff/faq.html) |
||||
|
- [CCITT T.4 Compression](https://www.itu.int/rec/T-REC-T.4-198811-S/_page.print) |
||||
|
- [CCITT T.6 Compression](https://www.itu.int/rec/T-REC-T.6/en) |
||||
|
|
||||
|
- DNG |
||||
|
- [Adobe DNG Pages](https://helpx.adobe.com/photoshop/digital-negative.html) |
||||
|
|
||||
|
- Metadata (EXIF) |
||||
|
- [EXIF 2.3 Specification](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf) |
||||
|
|
||||
|
- Metadata (XMP) |
||||
|
- [Adobe XMP Pages](http://www.adobe.com/products/xmp.html) |
||||
|
- [Adobe XMP Developer Center](http://www.adobe.com/devnet/xmp.html) |
||||
|
|
||||
|
## Implementation Status |
||||
|
|
||||
|
- The Decoder and Encoder currently only supports a single frame per image. |
||||
|
- Some compression formats are not yet supported. See the list below. |
||||
|
|
||||
|
### Deviations from the TIFF spec (to be fixed) |
||||
|
|
||||
|
- Decoder |
||||
|
- A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels) |
||||
|
- NB: Need to handle this for both planar and chunky data |
||||
|
- If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this |
||||
|
- Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows) |
||||
|
- Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB? |
||||
|
|
||||
|
### Compression Formats |
||||
|
|
||||
|
| |Encoder|Decoder|Comments | |
||||
|
|---------------------------|:-----:|:-----:|--------------------------| |
||||
|
|None | Y | Y | | |
||||
|
|Ccitt1D | Y | Y | | |
||||
|
|PackBits | Y | Y | | |
||||
|
|CcittGroup3Fax | Y | Y | | |
||||
|
|CcittGroup4Fax | | | | |
||||
|
|Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case | |
||||
|
|Old Jpeg | | | We should not even try to support this | |
||||
|
|Jpeg (Technote 2) | | | | |
||||
|
|Deflate (Technote 2) | Y | Y | Based on PNG Deflate. | |
||||
|
|Old Deflate (Technote 2) | | Y | | |
||||
|
|
||||
|
### Photometric Interpretation Formats |
||||
|
|
||||
|
| |Encoder|Decoder|Comments | |
||||
|
|---------------------------|:-----:|:-----:|--------------------------| |
||||
|
|WhiteIsZero | Y | Y | General + 1/4/8-bit optimised implementations | |
||||
|
|BlackIsZero | Y | Y | General + 1/4/8-bit optimised implementations | |
||||
|
|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation | |
||||
|
|Rgb (Planar) | | Y | General implementation only | |
||||
|
|PaletteColor | Y | Y | General implementation only | |
||||
|
|TransparencyMask | | | | |
||||
|
|Separated (TIFF Extension) | | | | |
||||
|
|YCbCr (TIFF Extension) | | | | |
||||
|
|CieLab (TIFF Extension) | | | | |
||||
|
|IccLab (TechNote 1) | | | | |
||||
|
|
||||
|
### Baseline TIFF Tags |
||||
|
|
||||
|
| |Encoder|Decoder|Comments | |
||||
|
|---------------------------|:-----:|:-----:|--------------------------| |
||||
|
|NewSubfileType | | | | |
||||
|
|SubfileType | | | | |
||||
|
|ImageWidth | Y | Y | | |
||||
|
|ImageLength | Y | Y | | |
||||
|
|BitsPerSample | Y | Y | | |
||||
|
|Compression | Y | Y | | |
||||
|
|PhotometricInterpretation | Y | Y | | |
||||
|
|Thresholding | | | | |
||||
|
|CellWidth | | | | |
||||
|
|CellLength | | | | |
||||
|
|FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. | |
||||
|
|ImageDescription | Y | Y | | |
||||
|
|Make | Y | Y | | |
||||
|
|Model | Y | Y | | |
||||
|
|StripOffsets | Y | Y | | |
||||
|
|Orientation | | - | Ignore. Many readers ignore this tag. | |
||||
|
|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample | |
||||
|
|RowsPerStrip | Y | Y | | |
||||
|
|StripByteCounts | Y | Y | | |
||||
|
|MinSampleValue | | | | |
||||
|
|MaxSampleValue | | | | |
||||
|
|XResolution | Y | Y | | |
||||
|
|YResolution | Y | Y | | |
||||
|
|PlanarConfiguration | | Y | Encoding support only chunky. | |
||||
|
|FreeOffsets | | | | |
||||
|
|FreeByteCounts | | | | |
||||
|
|GrayResponseUnit | | | | |
||||
|
|GrayResponseCurve | | | | |
||||
|
|ResolutionUnit | Y | Y | | |
||||
|
|Software | Y | Y | | |
||||
|
|DateTime | Y | Y | | |
||||
|
|Artist | Y | Y | | |
||||
|
|HostComputer | Y | Y | | |
||||
|
|ColorMap | Y | Y | | |
||||
|
|ExtraSamples | | - | | |
||||
|
|Copyright | Y | Y | | |
||||
|
|
||||
|
### Extension TIFF Tags |
||||
|
|
||||
|
| |Encoder|Decoder|Comments | |
||||
|
|---------------------------|:-----:|:-----:|--------------------------| |
||||
|
|NewSubfileType | | | | |
||||
|
|DocumentName | Y | Y | | |
||||
|
|PageName | | | | |
||||
|
|XPosition | | | | |
||||
|
|YPosition | | | | |
||||
|
|T4Options | | Y | | |
||||
|
|T6Options | | | | |
||||
|
|PageNumber | | | | |
||||
|
|TransferFunction | | | | |
||||
|
|Predictor | Y | Y | only Horizontal | |
||||
|
|WhitePoint | | | | |
||||
|
|PrimaryChromaticities | | | | |
||||
|
|HalftoneHints | | | | |
||||
|
|TileWidth | | - | | |
||||
|
|TileLength | | - | | |
||||
|
|TileOffsets | | - | | |
||||
|
|TileByteCounts | | - | | |
||||
|
|BadFaxLines | | | | |
||||
|
|CleanFaxData | | | | |
||||
|
|ConsecutiveBadFaxLines | | | | |
||||
|
|SubIFDs | | - | | |
||||
|
|InkSet | | | | |
||||
|
|InkNames | | | | |
||||
|
|NumberOfInks | | | | |
||||
|
|DotRange | | | | |
||||
|
|TargetPrinter | | | | |
||||
|
|SampleFormat | | - | | |
||||
|
|SMinSampleValue | | | | |
||||
|
|SMaxSampleValue | | | | |
||||
|
|TransferRange | | | | |
||||
|
|ClipPath | | | | |
||||
|
|XClipPathUnits | | | | |
||||
|
|YClipPathUnits | | | | |
||||
|
|Indexed | | | | |
||||
|
|JPEGTables | | | | |
||||
|
|OPIProxy | | | | |
||||
|
|GlobalParametersIFD | | | | |
||||
|
|ProfileType | | | | |
||||
|
|FaxProfile | | | | |
||||
|
|CodingMethods | | | | |
||||
|
|VersionYear | | | | |
||||
|
|ModeNumber | | | | |
||||
|
|Decode | | | | |
||||
|
|DefaultImageColor | | | | |
||||
|
|JPEGProc | | | | |
||||
|
|JPEGInterchangeFormat | | | | |
||||
|
|JPEGInterchangeFormatLength| | | | |
||||
|
|JPEGRestartInterval | | | | |
||||
|
|JPEGLosslessPredictors | | | | |
||||
|
|JPEGPointTransforms | | | | |
||||
|
|JPEGQTables | | | | |
||||
|
|JPEGDCTables | | | | |
||||
|
|JPEGACTables | | | | |
||||
|
|YCbCrCoefficients | | | | |
||||
|
|YCbCrSubSampling | | | | |
||||
|
|YCbCrPositioning | | | | |
||||
|
|ReferenceBlackWhite | | | | |
||||
|
|StripRowCounts | - | - | See RFC 2301 (File Format for Internet Fax). | |
||||
|
|XMP | Y | Y | | |
||||
|
|ImageID | | | | |
||||
|
|ImageLayer | | | | |
||||
|
|
||||
|
### Private TIFF Tags |
||||
|
|
||||
|
| |Encoder|Decoder|Comments | |
||||
|
|---------------------------|:-----:|:-----:|--------------------------| |
||||
|
|Wang Annotation | | | | |
||||
|
|MD FileTag | | | | |
||||
|
|MD ScalePixel | | | | |
||||
|
|MD ColorTable | | | | |
||||
|
|MD LabName | | | | |
||||
|
|MD SampleInfo | | | | |
||||
|
|MD PrepDate | | | | |
||||
|
|MD PrepTime | | | | |
||||
|
|MD FileUnits | | | | |
||||
|
|ModelPixelScaleTag | | | | |
||||
|
|IPTC | Y | Y | | |
||||
|
|INGR Packet Data Tag | | | | |
||||
|
|INGR Flag Registers | | | | |
||||
|
|IrasB Transformation Matrix| | | | |
||||
|
|ModelTiepointTag | | | | |
||||
|
|ModelTransformationTag | | | | |
||||
|
|Photoshop | | | | |
||||
|
|Exif IFD | | - | 0x8769 SubExif | |
||||
|
|ICC Profile | Y | Y | | |
||||
|
|GeoKeyDirectoryTag | | | | |
||||
|
|GeoDoubleParamsTag | | | | |
||||
|
|GeoAsciiParamsTag | | | | |
||||
|
|GPS IFD | | | | |
||||
|
|HylaFAX FaxRecvParams | | | | |
||||
|
|HylaFAX FaxSubAddress | | | | |
||||
|
|HylaFAX FaxRecvTime | | | | |
||||
|
|ImageSourceData | | | | |
||||
|
|Interoperability IFD | | | | |
||||
|
|GDAL_METADATA | | | | |
||||
|
|GDAL_NODATA | | | | |
||||
|
|Oce Scanjob Description | | | | |
||||
|
|Oce Application Selector | | | | |
||||
|
|Oce Identification Number | | | | |
||||
|
|Oce ImageLogic Characteristics| | | | |
||||
|
|DNGVersion | | | | |
||||
|
|DNGBackwardVersion | | | | |
||||
|
|UniqueCameraModel | | | | |
||||
|
|LocalizedCameraModel | | | | |
||||
|
|CFAPlaneColor | | | | |
||||
|
|CFALayout | | | | |
||||
|
|LinearizationTable | | | | |
||||
|
|BlackLevelRepeatDim | | | | |
||||
|
|BlackLevel | | | | |
||||
|
|BlackLevelDeltaH | | | | |
||||
|
|BlackLevelDeltaV | | | | |
||||
|
|WhiteLevel | | | | |
||||
|
|DefaultScale | | | | |
||||
|
|DefaultCropOrigin | | | | |
||||
|
|DefaultCropSize | | | | |
||||
|
|ColorMatrix1 | | | | |
||||
|
|ColorMatrix2 | | | | |
||||
|
|CameraCalibration1 | | | | |
||||
|
|CameraCalibration2 | | | | |
||||
|
|ReductionMatrix1 | | | | |
||||
|
|ReductionMatrix2 | | | | |
||||
|
|AnalogBalance | | | | |
||||
|
|AsShotNeutral | | | | |
||||
|
|AsShotWhiteXY | | | | |
||||
|
|BaselineExposure | | | | |
||||
|
|BaselineNoise | | | | |
||||
|
|BaselineSharpness | | | | |
||||
|
|BayerGreenSplit | | | | |
||||
|
|LinearResponseLimit | | | | |
||||
|
|CameraSerialNumber | | | | |
||||
|
|LensInfo | | | | |
||||
|
|ChromaBlurRadius | | | | |
||||
|
|AntiAliasStrength | | | | |
||||
|
|DNGPrivateData | | | | |
||||
|
|MakerNoteSafety | | | | |
||||
|
|CalibrationIlluminant1 | | | | |
||||
|
|CalibrationIlluminant2 | | | | |
||||
|
|BestQualityScale | | | | |
||||
|
|Alias Layer Metadata | | | | |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enumerates the available bits per pixel for the tiff format.
|
||||
|
/// </summary>
|
||||
|
public enum TiffBitsPerPixel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 1 bit per pixel, for bi-color image.
|
||||
|
/// </summary>
|
||||
|
Bit1 = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 4 bits per pixel, for images with a color palette.
|
||||
|
/// </summary>
|
||||
|
Bit4 = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 8 bits per pixel, grayscale or color palette images.
|
||||
|
/// </summary>
|
||||
|
Bit8 = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 24 bits per pixel. One byte for each color channel.
|
||||
|
/// </summary>
|
||||
|
Bit24 = 24, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The number of bits per component.
|
||||
|
/// </summary>
|
||||
|
public enum TiffBitsPerSample |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Bits per samples is not known.
|
||||
|
/// </summary>
|
||||
|
Unknown = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// One bit per sample for bicolor images.
|
||||
|
/// </summary>
|
||||
|
Bit1 = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors.
|
||||
|
/// </summary>
|
||||
|
Bit4 = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors.
|
||||
|
/// </summary>
|
||||
|
Bit8 = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 24 bits per sample, each color channel has 8 Bits.
|
||||
|
/// </summary>
|
||||
|
Bit24 = 24, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
internal static class TiffBitsPerSampleExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8]
|
||||
|
/// </summary>
|
||||
|
/// <param name="tiffBitsPerSample">The tiff bits per sample.</param>
|
||||
|
/// <returns>Bits per sample array.</returns>
|
||||
|
public static ushort[] Bits(this TiffBitsPerSample tiffBitsPerSample) |
||||
|
{ |
||||
|
switch (tiffBitsPerSample) |
||||
|
{ |
||||
|
case TiffBitsPerSample.Bit1: |
||||
|
return TiffConstants.BitsPerSample1Bit; |
||||
|
case TiffBitsPerSample.Bit4: |
||||
|
return TiffConstants.BitsPerSample4Bit; |
||||
|
case TiffBitsPerSample.Bit8: |
||||
|
return TiffConstants.BitsPerSample8Bit; |
||||
|
case TiffBitsPerSample.Bit24: |
||||
|
return TiffConstants.BitsPerSampleRgb8Bit; |
||||
|
|
||||
|
default: |
||||
|
return Array.Empty<ushort>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maps an array of bits per sample to a concrete enum value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="bitsPerSample">The bits per sample array.</param>
|
||||
|
/// <returns>TiffBitsPerSample enum value.</returns>
|
||||
|
public static TiffBitsPerSample GetBitsPerSample(this ushort[] bitsPerSample) |
||||
|
{ |
||||
|
switch (bitsPerSample.Length) |
||||
|
{ |
||||
|
case 3: |
||||
|
if (bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0] && |
||||
|
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] && |
||||
|
bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2]) |
||||
|
{ |
||||
|
return TiffBitsPerSample.Bit24; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case 1: |
||||
|
if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0]) |
||||
|
{ |
||||
|
return TiffBitsPerSample.Bit1; |
||||
|
} |
||||
|
|
||||
|
if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0]) |
||||
|
{ |
||||
|
return TiffBitsPerSample.Bit4; |
||||
|
} |
||||
|
|
||||
|
if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0]) |
||||
|
{ |
||||
|
return TiffBitsPerSample.Bit8; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return TiffBitsPerSample.Unknown; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Registers the image encoders, decoders and mime type detectors for the TIFF format.
|
||||
|
/// </summary>
|
||||
|
public sealed class TiffConfigurationModule : IConfigurationModule |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public void Configure(Configuration configuration) |
||||
|
{ |
||||
|
configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); |
||||
|
configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); |
||||
|
configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Image decoder for generating an image out of a TIFF stream.
|
||||
|
/// </summary>
|
||||
|
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions, IImageInfoDetector |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
|
||||
|
/// </summary>
|
||||
|
public bool IgnoreMetadata { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.NotNull(stream, "stream"); |
||||
|
|
||||
|
var decoder = new TiffDecoderCore(configuration, this); |
||||
|
return decoder.Decode<TPixel>(configuration, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
var decoder = new TiffDecoderCore(configuration, this); |
||||
|
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
||||
|
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken) |
||||
|
.ConfigureAwait(false); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IImageInfo Identify(Configuration configuration, Stream stream) |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
var decoder = new TiffDecoderCore(configuration, this); |
||||
|
return decoder.Identify(configuration, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
var decoder = new TiffDecoderCore(configuration, this); |
||||
|
return decoder.IdentifyAsync(configuration, stream, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,339 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Compression; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; |
||||
|
using SixLabors.ImageSharp.IO; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Performs the tiff decoding operation.
|
||||
|
/// </summary>
|
||||
|
internal class TiffDecoderCore : IImageDecoderInternals |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Used for allocating memory during processing operations.
|
||||
|
/// </summary>
|
||||
|
private readonly MemoryAllocator memoryAllocator; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
|
||||
|
/// </summary>
|
||||
|
private readonly bool ignoreMetadata; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The stream to decode from.
|
||||
|
/// </summary>
|
||||
|
private BufferedReadStream inputStream; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration.</param>
|
||||
|
/// <param name="options">The decoder options.</param>
|
||||
|
public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) |
||||
|
{ |
||||
|
options ??= new TiffDecoder(); |
||||
|
|
||||
|
this.Configuration = configuration ?? Configuration.Default; |
||||
|
this.ignoreMetadata = options.IgnoreMetadata; |
||||
|
this.memoryAllocator = this.Configuration.MemoryAllocator; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the number of bits per component of the pixel format used to decode the image.
|
||||
|
/// </summary>
|
||||
|
public TiffBitsPerSample BitsPerSample { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the bits per pixel.
|
||||
|
/// </summary>
|
||||
|
public int BitsPerPixel { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the lookup table for RGB palette colored images.
|
||||
|
/// </summary>
|
||||
|
public ushort[] ColorMap { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the photometric interpretation implementation to use when decoding the image.
|
||||
|
/// </summary>
|
||||
|
public TiffColorType ColorType { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the compression used, when the image was encoded.
|
||||
|
/// </summary>
|
||||
|
public TiffDecoderCompressionType CompressionType { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the Fax specific compression options.
|
||||
|
/// </summary>
|
||||
|
public FaxCompressionOptions FaxCompressionOptions { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the planar configuration type to use when decoding the image.
|
||||
|
/// </summary>
|
||||
|
public TiffPlanarConfiguration PlanarConfiguration { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the photometric interpretation.
|
||||
|
/// </summary>
|
||||
|
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the horizontal predictor.
|
||||
|
/// </summary>
|
||||
|
public TiffPredictor Predictor { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Configuration Configuration { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Size Dimensions { get; private set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
this.inputStream = stream; |
||||
|
var reader = new DirectoryReader(stream); |
||||
|
|
||||
|
IEnumerable<ExifProfile> directories = reader.Read(); |
||||
|
|
||||
|
var frames = new List<ImageFrame<TPixel>>(); |
||||
|
foreach (ExifProfile ifd in directories) |
||||
|
{ |
||||
|
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd); |
||||
|
frames.Add(frame); |
||||
|
} |
||||
|
|
||||
|
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder); |
||||
|
|
||||
|
// TODO: Tiff frames can have different sizes
|
||||
|
ImageFrame<TPixel> root = frames[0]; |
||||
|
this.Dimensions = root.Size(); |
||||
|
foreach (ImageFrame<TPixel> frame in frames) |
||||
|
{ |
||||
|
if (frame.Size() != root.Size()) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new Image<TPixel>(this.Configuration, metadata, frames); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
this.inputStream = stream; |
||||
|
var reader = new DirectoryReader(stream); |
||||
|
IEnumerable<ExifProfile> directories = reader.Read(); |
||||
|
|
||||
|
ExifProfile rootFrameExifProfile = directories.First(); |
||||
|
var rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); |
||||
|
|
||||
|
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, rootFrameExifProfile); |
||||
|
int width = GetImageWidth(rootFrameExifProfile); |
||||
|
int height = GetImageHeight(rootFrameExifProfile); |
||||
|
|
||||
|
return new ImageInfo(new PixelTypeInfo((int)rootMetadata.BitsPerPixel), width, height, metadata); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes the image data from a specified IFD.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="tags">The IFD tags.</param>
|
||||
|
/// <returns>
|
||||
|
/// The tiff frame.
|
||||
|
/// </returns>
|
||||
|
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
ImageFrameMetadata imageFrameMetaData = this.ignoreMetadata ? |
||||
|
new ImageFrameMetadata() : |
||||
|
new ImageFrameMetadata { ExifProfile = tags, XmpProfile = tags.GetValue(ExifTag.XMP)?.Value }; |
||||
|
|
||||
|
TiffFrameMetadata tiffFrameMetaData = imageFrameMetaData.GetTiffMetadata(); |
||||
|
TiffFrameMetadata.Parse(tiffFrameMetaData, tags); |
||||
|
|
||||
|
this.VerifyAndParse(tags, tiffFrameMetaData); |
||||
|
|
||||
|
int width = GetImageWidth(tags); |
||||
|
int height = GetImageHeight(tags); |
||||
|
var frame = new ImageFrame<TPixel>(this.Configuration, width, height, imageFrameMetaData); |
||||
|
|
||||
|
int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; |
||||
|
Number[] stripOffsets = tags.GetValue(ExifTag.StripOffsets)?.Value; |
||||
|
Number[] stripByteCounts = tags.GetValue(ExifTag.StripByteCounts)?.Value; |
||||
|
|
||||
|
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) |
||||
|
{ |
||||
|
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts); |
||||
|
} |
||||
|
|
||||
|
return frame; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates the size (in bytes) for a pixel buffer using the determined color format.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">The width for the desired pixel buffer.</param>
|
||||
|
/// <param name="height">The height for the desired pixel buffer.</param>
|
||||
|
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
|
||||
|
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
|
||||
|
private int CalculateStripBufferSize(int width, int height, int plane = -1) |
||||
|
{ |
||||
|
int bitsPerPixel; |
||||
|
|
||||
|
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) |
||||
|
{ |
||||
|
DebugGuard.IsTrue(plane == -1, "Expected Chunky planar."); |
||||
|
bitsPerPixel = this.BitsPerPixel; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
bitsPerPixel = this.BitsPerSample.Bits()[plane]; |
||||
|
} |
||||
|
|
||||
|
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; |
||||
|
return bytesPerRow * height; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes the image data for strip encoded data.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="frame">The image frame to decode data into.</param>
|
||||
|
/// <param name="rowsPerStrip">The number of rows per strip of data.</param>
|
||||
|
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
|
||||
|
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
|
||||
|
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
int stripsPerPixel = this.BitsPerSample.Bits().Length; |
||||
|
int stripsPerPlane = stripOffsets.Length / stripsPerPixel; |
||||
|
int bitsPerPixel = this.BitsPerPixel; |
||||
|
|
||||
|
Buffer2D<TPixel> pixels = frame.PixelBuffer; |
||||
|
|
||||
|
var stripBuffers = new IManagedByteBuffer[stripsPerPixel]; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) |
||||
|
{ |
||||
|
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); |
||||
|
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); |
||||
|
} |
||||
|
|
||||
|
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); |
||||
|
|
||||
|
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); |
||||
|
|
||||
|
for (int i = 0; i < stripsPerPlane; i++) |
||||
|
{ |
||||
|
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; |
||||
|
|
||||
|
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) |
||||
|
{ |
||||
|
int stripIndex = (i * stripsPerPixel) + planeIndex; |
||||
|
|
||||
|
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); |
||||
|
} |
||||
|
|
||||
|
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
foreach (IManagedByteBuffer buf in stripBuffers) |
||||
|
{ |
||||
|
buf?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
// If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip.
|
||||
|
if (rowsPerStrip == TiffConstants.RowsPerStripInfinity) |
||||
|
{ |
||||
|
rowsPerStrip = frame.Height; |
||||
|
} |
||||
|
|
||||
|
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); |
||||
|
int bitsPerPixel = this.BitsPerPixel; |
||||
|
|
||||
|
using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize, AllocationOptions.Clean); |
||||
|
|
||||
|
Buffer2D<TPixel> pixels = frame.PixelBuffer; |
||||
|
|
||||
|
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); |
||||
|
|
||||
|
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); |
||||
|
|
||||
|
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) |
||||
|
{ |
||||
|
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; |
||||
|
int top = rowsPerStrip * stripIndex; |
||||
|
if (top + stripHeight > frame.Height) |
||||
|
{ |
||||
|
// Make sure we ignore any strips that are not needed for the image (if too many are present)
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffer.GetSpan()); |
||||
|
|
||||
|
colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, top, frame.Width, stripHeight); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the width of the image frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="exifProfile">The image frame exif profile.</param>
|
||||
|
/// <returns>The image width.</returns>
|
||||
|
private static int GetImageWidth(ExifProfile exifProfile) |
||||
|
{ |
||||
|
IExifValue<Number> width = exifProfile.GetValue(ExifTag.ImageWidth); |
||||
|
if (width == null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); |
||||
|
} |
||||
|
|
||||
|
return (int)width.Value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the height of the image frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="exifProfile">The image frame exif profile.</param>
|
||||
|
/// <returns>The image height.</returns>
|
||||
|
private static int GetImageHeight(ExifProfile exifProfile) |
||||
|
{ |
||||
|
IExifValue<Number> height = exifProfile.GetValue(ExifTag.ImageLength); |
||||
|
if (height == null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); |
||||
|
} |
||||
|
|
||||
|
return (int)height.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Iptc; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The decoder metadata creator.
|
||||
|
/// </summary>
|
||||
|
internal static class TiffDecoderMetadataCreator |
||||
|
{ |
||||
|
public static ImageMetadata Create<TPixel>(List<ImageFrame<TPixel>> frames, bool ignoreMetadata, ByteOrder byteOrder) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
if (frames.Count < 1) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); |
||||
|
} |
||||
|
|
||||
|
var imageMetaData = new ImageMetadata(); |
||||
|
ExifProfile exifProfileRootFrame = frames[0].Metadata.ExifProfile; |
||||
|
|
||||
|
SetResolution(imageMetaData, exifProfileRootFrame); |
||||
|
|
||||
|
TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); |
||||
|
tiffMetadata.ByteOrder = byteOrder; |
||||
|
|
||||
|
if (!ignoreMetadata) |
||||
|
{ |
||||
|
for (int i = 0; i < frames.Count; i++) |
||||
|
{ |
||||
|
ImageFrame<TPixel> frame = frames[i]; |
||||
|
ImageFrameMetadata frameMetaData = frame.Metadata; |
||||
|
if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes)) |
||||
|
{ |
||||
|
frameMetaData.IptcProfile = new IptcProfile(iptcBytes); |
||||
|
} |
||||
|
|
||||
|
IExifValue<byte[]> iccProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.IccProfile); |
||||
|
if (iccProfileBytes != null) |
||||
|
{ |
||||
|
frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return imageMetaData; |
||||
|
} |
||||
|
|
||||
|
public static ImageMetadata Create(ByteOrder byteOrder, ExifProfile exifProfile) |
||||
|
{ |
||||
|
var imageMetaData = new ImageMetadata(); |
||||
|
SetResolution(imageMetaData, exifProfile); |
||||
|
|
||||
|
TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); |
||||
|
tiffMetadata.ByteOrder = byteOrder; |
||||
|
|
||||
|
return imageMetaData; |
||||
|
} |
||||
|
|
||||
|
private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) |
||||
|
{ |
||||
|
imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; |
||||
|
double? horizontalResolution = exifProfile?.GetValue(ExifTag.XResolution)?.Value.ToDouble(); |
||||
|
if (horizontalResolution != null) |
||||
|
{ |
||||
|
imageMetaData.HorizontalResolution = horizontalResolution.Value; |
||||
|
} |
||||
|
|
||||
|
double? verticalResolution = exifProfile?.GetValue(ExifTag.YResolution)?.Value.ToDouble(); |
||||
|
if (verticalResolution != null) |
||||
|
{ |
||||
|
imageMetaData.VerticalResolution = verticalResolution.Value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool TryGetIptc(IReadOnlyList<IExifValue> exifValues, out byte[] iptcBytes) |
||||
|
{ |
||||
|
iptcBytes = null; |
||||
|
IExifValue iptc = exifValues.FirstOrDefault(f => f.Tag == ExifTag.IPTC); |
||||
|
|
||||
|
if (iptc != null) |
||||
|
{ |
||||
|
if (iptc.DataType == ExifDataType.Byte || iptc.DataType == ExifDataType.Undefined) |
||||
|
{ |
||||
|
iptcBytes = (byte[])iptc.GetValue(); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// Some Encoders write the data type of IPTC as long.
|
||||
|
if (iptc.DataType == ExifDataType.Long) |
||||
|
{ |
||||
|
uint[] iptcValues = (uint[])iptc.GetValue(); |
||||
|
iptcBytes = new byte[iptcValues.Length * 4]; |
||||
|
Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4); |
||||
|
if (iptcBytes[0] == 0x1c) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
else if (iptcBytes[3] != 0x1c) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Probably wrong endianess, swap byte order.
|
||||
|
Span<byte> iptcBytesSpan = iptcBytes.AsSpan(); |
||||
|
Span<byte> buffer = stackalloc byte[4]; |
||||
|
for (int i = 0; i < iptcBytes.Length; i += 4) |
||||
|
{ |
||||
|
iptcBytesSpan.Slice(i, 4).CopyTo(buffer); |
||||
|
iptcBytes[i] = buffer[3]; |
||||
|
iptcBytes[i + 1] = buffer[2]; |
||||
|
iptcBytes[i + 2] = buffer[1]; |
||||
|
iptcBytes[i + 3] = buffer[0]; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,281 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Compression; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The decoder options parser.
|
||||
|
/// </summary>
|
||||
|
internal static class TiffDecoderOptionsParser |
||||
|
{ |
||||
|
private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines the TIFF compression and color types, and reads any associated parameters.
|
||||
|
/// </summary>
|
||||
|
/// <param name="options">The options.</param>
|
||||
|
/// <param name="exifProfile">The exif profile of the frame to decode.</param>
|
||||
|
/// <param name="frameMetadata">The IFD entries container to read the image format information for current frame.</param>
|
||||
|
public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) |
||||
|
{ |
||||
|
if (exifProfile.GetValue(ExifTag.TileOffsets)?.Value != null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); |
||||
|
} |
||||
|
|
||||
|
if (exifProfile.GetValue(ExifTag.ExtraSamples)?.Value != null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); |
||||
|
} |
||||
|
|
||||
|
TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; |
||||
|
if (fillOrder != TiffFillOrder.MostSignificantBitFirst) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported."); |
||||
|
} |
||||
|
|
||||
|
if (frameMetadata.Predictor == TiffPredictor.FloatingPoint) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); |
||||
|
} |
||||
|
|
||||
|
TiffSampleFormat[] sampleFormat = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); |
||||
|
if (sampleFormat != null) |
||||
|
{ |
||||
|
foreach (TiffSampleFormat format in sampleFormat) |
||||
|
{ |
||||
|
if (format != TiffSampleFormat.UnsignedInteger) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger SampleFormat."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); |
||||
|
} |
||||
|
|
||||
|
VerifyRequiredFieldsArePresent(exifProfile, frameMetadata); |
||||
|
|
||||
|
options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; |
||||
|
options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; |
||||
|
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; |
||||
|
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; |
||||
|
options.BitsPerSample = GetBitsPerSample(frameMetadata.BitsPerPixel); |
||||
|
|
||||
|
options.ParseColorType(exifProfile); |
||||
|
options.ParseCompression(frameMetadata.Compression, exifProfile); |
||||
|
} |
||||
|
|
||||
|
private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata) |
||||
|
{ |
||||
|
if (exifProfile.GetValue(ExifTag.StripOffsets) == null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); |
||||
|
} |
||||
|
|
||||
|
if (exifProfile.GetValue(ExifTag.StripByteCounts) == null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); |
||||
|
} |
||||
|
|
||||
|
if (frameMetadata.BitsPerPixel == null) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ParseColorType(this TiffDecoderCore options, ExifProfile exifProfile) |
||||
|
{ |
||||
|
switch (options.PhotometricInterpretation) |
||||
|
{ |
||||
|
case TiffPhotometricInterpretation.WhiteIsZero: |
||||
|
{ |
||||
|
if (options.BitsPerSample.Bits().Length != 1) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); |
||||
|
} |
||||
|
|
||||
|
switch (options.BitsPerSample) |
||||
|
{ |
||||
|
case TiffBitsPerSample.Bit8: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.WhiteIsZero8; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffBitsPerSample.Bit4: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.WhiteIsZero4; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffBitsPerSample.Bit1: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.WhiteIsZero1; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
default: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.WhiteIsZero; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffPhotometricInterpretation.BlackIsZero: |
||||
|
{ |
||||
|
if (options.BitsPerSample.Bits().Length != 1) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); |
||||
|
} |
||||
|
|
||||
|
switch (options.BitsPerSample) |
||||
|
{ |
||||
|
case TiffBitsPerSample.Bit8: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.BlackIsZero8; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffBitsPerSample.Bit4: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.BlackIsZero4; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffBitsPerSample.Bit1: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.BlackIsZero1; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
default: |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.BlackIsZero; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffPhotometricInterpretation.Rgb: |
||||
|
{ |
||||
|
if (options.BitsPerSample.Bits().Length != 3) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); |
||||
|
} |
||||
|
|
||||
|
if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) |
||||
|
{ |
||||
|
options.ColorType = options.BitsPerSample == TiffBitsPerSample.Bit24 ? TiffColorType.Rgb888 : TiffColorType.Rgb; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
options.ColorType = TiffColorType.RgbPlanar; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffPhotometricInterpretation.PaletteColor: |
||||
|
{ |
||||
|
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; |
||||
|
if (options.ColorMap != null) |
||||
|
{ |
||||
|
if (options.BitsPerSample.Bits().Length != 1) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); |
||||
|
} |
||||
|
|
||||
|
options.ColorType = TiffColorType.PaletteColor; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image."); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
default: |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}"); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile) |
||||
|
{ |
||||
|
switch (compression) |
||||
|
{ |
||||
|
case TiffCompression.None: |
||||
|
{ |
||||
|
options.CompressionType = TiffDecoderCompressionType.None; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffCompression.PackBits: |
||||
|
{ |
||||
|
options.CompressionType = TiffDecoderCompressionType.PackBits; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffCompression.Deflate: |
||||
|
case TiffCompression.OldDeflate: |
||||
|
{ |
||||
|
options.CompressionType = TiffDecoderCompressionType.Deflate; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffCompression.Lzw: |
||||
|
{ |
||||
|
options.CompressionType = TiffDecoderCompressionType.Lzw; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffCompression.CcittGroup3Fax: |
||||
|
{ |
||||
|
options.CompressionType = TiffDecoderCompressionType.T4; |
||||
|
options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case TiffCompression.Ccitt1D: |
||||
|
{ |
||||
|
options.CompressionType = TiffDecoderCompressionType.HuffmanRle; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
default: |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported"); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static TiffBitsPerSample GetBitsPerSample(TiffBitsPerPixel? bitsPerPixel) => bitsPerPixel switch |
||||
|
{ |
||||
|
TiffBitsPerPixel.Bit1 => TiffBitsPerSample.Bit1, |
||||
|
TiffBitsPerPixel.Bit4 => TiffBitsPerSample.Bit4, |
||||
|
TiffBitsPerPixel.Bit8 => TiffBitsPerSample.Bit8, |
||||
|
TiffBitsPerPixel.Bit24 => TiffBitsPerSample.Bit24, |
||||
|
_ => TiffBitsPerSample.Bit24, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Compression.Zlib; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Encoder for writing the data image to a stream in TIFF format.
|
||||
|
/// </summary>
|
||||
|
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public TiffBitsPerPixel? BitsPerPixel { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public TiffCompression? Compression { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public DeflateCompressionLevel? CompressionLevel { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public TiffPredictor? HorizontalPredictor { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IQuantizer Quantizer { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
var encode = new TiffEncoderCore(this, image.GetMemoryAllocator()); |
||||
|
encode.Encode(image, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
var encoder = new TiffEncoderCore(this, image.GetMemoryAllocator()); |
||||
|
return encoder.EncodeAsync(image, stream, cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,381 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Threading; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Compression.Zlib; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Compression; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Writers; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Performs the TIFF encoding operation.
|
||||
|
/// </summary>
|
||||
|
internal sealed class TiffEncoderCore : IImageEncoderInternals |
||||
|
{ |
||||
|
private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian |
||||
|
? TiffConstants.ByteOrderLittleEndianShort |
||||
|
: TiffConstants.ByteOrderBigEndianShort; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Used for allocating memory during processing operations.
|
||||
|
/// </summary>
|
||||
|
private readonly MemoryAllocator memoryAllocator; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A scratch buffer to reduce allocations.
|
||||
|
/// </summary>
|
||||
|
private readonly byte[] buffer = new byte[4]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The global configuration.
|
||||
|
/// </summary>
|
||||
|
private Configuration configuration; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The quantizer for creating color palette image.
|
||||
|
/// </summary>
|
||||
|
private readonly IQuantizer quantizer; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the deflate compression level.
|
||||
|
/// </summary>
|
||||
|
private readonly DeflateCompressionLevel compressionLevel; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default predictor is None.
|
||||
|
/// </summary>
|
||||
|
private const TiffPredictor DefaultPredictor = TiffPredictor.None; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default bits per pixel is Bit24.
|
||||
|
/// </summary>
|
||||
|
private const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default compression is None.
|
||||
|
/// </summary>
|
||||
|
private const TiffCompression DefaultCompression = TiffCompression.None; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The default photometric interpretation is Rgb.
|
||||
|
/// </summary>
|
||||
|
private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="options">The options for the encoder.</param>
|
||||
|
/// <param name="memoryAllocator">The memory allocator.</param>
|
||||
|
public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator) |
||||
|
{ |
||||
|
this.memoryAllocator = memoryAllocator; |
||||
|
this.PhotometricInterpretation = options.PhotometricInterpretation; |
||||
|
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; |
||||
|
this.BitsPerPixel = options.BitsPerPixel; |
||||
|
this.HorizontalPredictor = options.HorizontalPredictor; |
||||
|
this.CompressionType = options.Compression; |
||||
|
this.compressionLevel = options.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the photometric interpretation implementation to use when encoding the image.
|
||||
|
/// </summary>
|
||||
|
internal TiffPhotometricInterpretation? PhotometricInterpretation { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the compression implementation to use when encoding the image.
|
||||
|
/// </summary>
|
||||
|
internal TiffCompression? CompressionType { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression.
|
||||
|
/// </summary>
|
||||
|
internal TiffPredictor? HorizontalPredictor { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bits per pixel.
|
||||
|
/// </summary>
|
||||
|
internal TiffBitsPerPixel? BitsPerPixel { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
|
||||
|
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
||||
|
/// <param name="cancellationToken">The token to request cancellation.</param>
|
||||
|
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.NotNull(image, nameof(image)); |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
this.configuration = image.GetConfiguration(); |
||||
|
|
||||
|
ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; |
||||
|
TiffFrameMetadata rootFrameTiffMetaData = rootFrameMetaData.GetTiffMetadata(); |
||||
|
|
||||
|
// Determine the correct values to encode with.
|
||||
|
// EncoderOptions > Metadata > Default.
|
||||
|
TiffBitsPerPixel? bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel; |
||||
|
|
||||
|
TiffPhotometricInterpretation? photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation; |
||||
|
|
||||
|
TiffPredictor predictor = |
||||
|
this.HorizontalPredictor |
||||
|
?? rootFrameTiffMetaData.Predictor |
||||
|
?? DefaultPredictor; |
||||
|
|
||||
|
TiffCompression compression = |
||||
|
this.CompressionType |
||||
|
?? rootFrameTiffMetaData.Compression |
||||
|
?? DefaultCompression; |
||||
|
|
||||
|
// Make sure, the Encoder options makes sense in combination with each other.
|
||||
|
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor); |
||||
|
|
||||
|
using (var writer = new TiffStreamWriter(stream)) |
||||
|
{ |
||||
|
long firstIfdMarker = this.WriteHeader(writer); |
||||
|
|
||||
|
// TODO: multiframing is not supported
|
||||
|
this.WriteImage(writer, image, firstIfdMarker); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes the TIFF file header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The <see cref="TiffStreamWriter" /> to write data to.</param>
|
||||
|
/// <returns>
|
||||
|
/// The marker to write the first IFD offset.
|
||||
|
/// </returns>
|
||||
|
public long WriteHeader(TiffStreamWriter writer) |
||||
|
{ |
||||
|
writer.Write(ByteOrderMarker); |
||||
|
writer.Write(TiffConstants.HeaderMagicNumber); |
||||
|
return writer.PlaceMarker(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes all data required to define an image.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
|
||||
|
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
|
||||
|
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
|
||||
|
private void WriteImage<TPixel>(TiffStreamWriter writer, Image<TPixel> image, long ifdOffset) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
var entriesCollector = new TiffEncoderEntriesCollector(); |
||||
|
|
||||
|
using TiffBaseCompressor compressor = TiffCompressorFactory.Create( |
||||
|
this.CompressionType ?? TiffCompression.None, |
||||
|
writer.BaseStream, |
||||
|
this.memoryAllocator, |
||||
|
image.Width, |
||||
|
(int)this.BitsPerPixel, |
||||
|
this.compressionLevel, |
||||
|
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); |
||||
|
|
||||
|
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create( |
||||
|
this.PhotometricInterpretation, |
||||
|
image.Frames.RootFrame, |
||||
|
this.quantizer, |
||||
|
this.memoryAllocator, |
||||
|
this.configuration, |
||||
|
entriesCollector, |
||||
|
(int)this.BitsPerPixel); |
||||
|
|
||||
|
int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow); |
||||
|
|
||||
|
colorWriter.Write(compressor, rowsPerStrip); |
||||
|
|
||||
|
entriesCollector.ProcessImageFormat(this); |
||||
|
entriesCollector.ProcessGeneral(image); |
||||
|
|
||||
|
writer.WriteMarker(ifdOffset, (uint)writer.Position); |
||||
|
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates the number of rows written per strip.
|
||||
|
/// </summary>
|
||||
|
/// <param name="height">The height of the image.</param>
|
||||
|
/// <param name="bytesPerRow">The number of bytes per row.</param>
|
||||
|
/// <returns>Number of rows per strip.</returns>
|
||||
|
private int CalcRowsPerStrip(int height, int bytesPerRow) |
||||
|
{ |
||||
|
DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); |
||||
|
DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow)); |
||||
|
|
||||
|
int rowsPerStrip = TiffConstants.DefaultStripSize / bytesPerRow; |
||||
|
|
||||
|
if (rowsPerStrip > 0) |
||||
|
{ |
||||
|
if (rowsPerStrip < height) |
||||
|
{ |
||||
|
return rowsPerStrip; |
||||
|
} |
||||
|
|
||||
|
return height; |
||||
|
} |
||||
|
|
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes a TIFF IFD block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
|
||||
|
/// <param name="entries">The IFD entries to write to the file.</param>
|
||||
|
/// <returns>The marker to write the next IFD offset (if present).</returns>
|
||||
|
private long WriteIfd(TiffStreamWriter writer, List<IExifValue> entries) |
||||
|
{ |
||||
|
if (entries.Count == 0) |
||||
|
{ |
||||
|
TiffThrowHelper.ThrowArgumentException("There must be at least one entry per IFD."); |
||||
|
} |
||||
|
|
||||
|
uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); |
||||
|
var largeDataBlocks = new List<byte[]>(); |
||||
|
|
||||
|
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag); |
||||
|
|
||||
|
writer.Write((ushort)entries.Count); |
||||
|
|
||||
|
foreach (IExifValue entry in entries) |
||||
|
{ |
||||
|
writer.Write((ushort)entry.Tag); |
||||
|
writer.Write((ushort)entry.DataType); |
||||
|
writer.Write(ExifWriter.GetNumberOfComponents(entry)); |
||||
|
|
||||
|
uint length = ExifWriter.GetLength(entry); |
||||
|
if (length <= 4) |
||||
|
{ |
||||
|
int sz = ExifWriter.WriteValue(entry, this.buffer, 0); |
||||
|
DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); |
||||
|
writer.WritePadded(this.buffer.AsSpan(0, sz)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var raw = new byte[length]; |
||||
|
int sz = ExifWriter.WriteValue(entry, raw, 0); |
||||
|
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written"); |
||||
|
largeDataBlocks.Add(raw); |
||||
|
writer.Write(dataOffset); |
||||
|
dataOffset += (uint)(raw.Length + (raw.Length % 2)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
long nextIfdMarker = writer.PlaceMarker(); |
||||
|
|
||||
|
foreach (byte[] dataBlock in largeDataBlocks) |
||||
|
{ |
||||
|
writer.Write(dataBlock); |
||||
|
|
||||
|
if (dataBlock.Length % 2 == 1) |
||||
|
{ |
||||
|
writer.Write(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nextIfdMarker; |
||||
|
} |
||||
|
|
||||
|
private void SanitizeAndSetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) |
||||
|
{ |
||||
|
// BitsPerPixel should be the primary source of truth for the encoder options.
|
||||
|
if (bitsPerPixel.HasValue) |
||||
|
{ |
||||
|
switch (bitsPerPixel) |
||||
|
{ |
||||
|
case TiffBitsPerPixel.Bit1: |
||||
|
if (compression == TiffCompression.Ccitt1D || compression == TiffCompression.CcittGroup3Fax || compression == TiffCompression.CcittGroup4Fax) |
||||
|
{ |
||||
|
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
|
||||
|
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None); |
||||
|
break; |
||||
|
case TiffBitsPerPixel.Bit4: |
||||
|
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None); |
||||
|
break; |
||||
|
case TiffBitsPerPixel.Bit8: |
||||
|
this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor); |
||||
|
break; |
||||
|
default: |
||||
|
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// If no photometric interpretation was chosen, the input image bit per pixel should be preserved.
|
||||
|
if (!photometricInterpretation.HasValue) |
||||
|
{ |
||||
|
// At the moment only 8 and 32 bits per pixel can be preserved by the tiff encoder.
|
||||
|
if (inputBitsPerPixel == 8) |
||||
|
{ |
||||
|
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, compression, predictor); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
switch (photometricInterpretation) |
||||
|
{ |
||||
|
case TiffPhotometricInterpretation.BlackIsZero: |
||||
|
case TiffPhotometricInterpretation.WhiteIsZero: |
||||
|
if (this.CompressionType == TiffCompression.Ccitt1D || |
||||
|
this.CompressionType == TiffCompression.CcittGroup3Fax || |
||||
|
this.CompressionType == TiffCompression.CcittGroup4Fax) |
||||
|
{ |
||||
|
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None); |
||||
|
return; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
case TiffPhotometricInterpretation.PaletteColor: |
||||
|
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); |
||||
|
return; |
||||
|
|
||||
|
case TiffPhotometricInterpretation.Rgb: |
||||
|
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.SetEncoderOptions(DefaultBitsPerPixel, DefaultPhotometricInterpretation, compression, predictor); |
||||
|
} |
||||
|
|
||||
|
private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) |
||||
|
{ |
||||
|
this.BitsPerPixel = bitsPerPixel; |
||||
|
this.PhotometricInterpretation = photometricInterpretation; |
||||
|
this.CompressionType = compression; |
||||
|
this.HorizontalPredictor = predictor; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,378 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Metadata; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
internal class TiffEncoderEntriesCollector |
||||
|
{ |
||||
|
private const string SoftwareValue = "ImageSharp"; |
||||
|
|
||||
|
public List<IExifValue> Entries { get; } = new List<IExifValue>(); |
||||
|
|
||||
|
public void ProcessGeneral<TPixel>(Image<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
=> new GeneralProcessor(this).Process(image); |
||||
|
|
||||
|
public void ProcessImageFormat(TiffEncoderCore encoder) |
||||
|
=> new ImageFormatProcessor(this).Process(encoder); |
||||
|
|
||||
|
public void AddOrReplace(IExifValue entry) |
||||
|
{ |
||||
|
int index = this.Entries.FindIndex(t => t.Tag == entry.Tag); |
||||
|
if (index >= 0) |
||||
|
{ |
||||
|
this.Entries[index] = entry; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.Entries.Add(entry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void Add(IExifValue entry) => this.Entries.Add(entry); |
||||
|
|
||||
|
private class GeneralProcessor |
||||
|
{ |
||||
|
private readonly TiffEncoderEntriesCollector collector; |
||||
|
|
||||
|
public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; |
||||
|
|
||||
|
public void Process<TPixel>(Image<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
ImageFrame<TPixel> rootFrame = image.Frames.RootFrame; |
||||
|
ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile(); |
||||
|
byte[] rootFrameXmpBytes = rootFrame.Metadata.XmpProfile; |
||||
|
|
||||
|
var width = new ExifLong(ExifTagValue.ImageWidth) |
||||
|
{ |
||||
|
Value = (uint)image.Width |
||||
|
}; |
||||
|
|
||||
|
var height = new ExifLong(ExifTagValue.ImageLength) |
||||
|
{ |
||||
|
Value = (uint)image.Height |
||||
|
}; |
||||
|
|
||||
|
var software = new ExifString(ExifTagValue.Software) |
||||
|
{ |
||||
|
Value = SoftwareValue |
||||
|
}; |
||||
|
|
||||
|
this.collector.Add(width); |
||||
|
this.collector.Add(height); |
||||
|
|
||||
|
this.ProcessResolution(image.Metadata, rootFrameExifProfile); |
||||
|
this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpBytes); |
||||
|
this.ProcessMetadata(rootFrameExifProfile); |
||||
|
|
||||
|
if (!this.collector.Entries.Exists(t => t.Tag == ExifTag.Software)) |
||||
|
{ |
||||
|
this.collector.Add(software); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool IsPureMetadata(ExifTag tag) |
||||
|
{ |
||||
|
switch ((ExifTagValue)(ushort)tag) |
||||
|
{ |
||||
|
case ExifTagValue.DocumentName: |
||||
|
case ExifTagValue.ImageDescription: |
||||
|
case ExifTagValue.Make: |
||||
|
case ExifTagValue.Model: |
||||
|
case ExifTagValue.Software: |
||||
|
case ExifTagValue.DateTime: |
||||
|
case ExifTagValue.Artist: |
||||
|
case ExifTagValue.HostComputer: |
||||
|
case ExifTagValue.TargetPrinter: |
||||
|
case ExifTagValue.XMP: |
||||
|
case ExifTagValue.Rating: |
||||
|
case ExifTagValue.RatingPercent: |
||||
|
case ExifTagValue.ImageID: |
||||
|
case ExifTagValue.Copyright: |
||||
|
case ExifTagValue.MDLabName: |
||||
|
case ExifTagValue.MDSampleInfo: |
||||
|
case ExifTagValue.MDPrepDate: |
||||
|
case ExifTagValue.MDPrepTime: |
||||
|
case ExifTagValue.MDFileUnits: |
||||
|
case ExifTagValue.SEMInfo: |
||||
|
case ExifTagValue.XPTitle: |
||||
|
case ExifTagValue.XPComment: |
||||
|
case ExifTagValue.XPAuthor: |
||||
|
case ExifTagValue.XPKeywords: |
||||
|
case ExifTagValue.XPSubject: |
||||
|
return true; |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ProcessResolution(ImageMetadata imageMetadata, ExifProfile exifProfile) |
||||
|
{ |
||||
|
UnitConverter.SetResolutionValues( |
||||
|
exifProfile, |
||||
|
imageMetadata.ResolutionUnits, |
||||
|
imageMetadata.HorizontalResolution, |
||||
|
imageMetadata.VerticalResolution); |
||||
|
|
||||
|
this.collector.Add(exifProfile.GetValue(ExifTag.ResolutionUnit).DeepClone()); |
||||
|
|
||||
|
IExifValue xResolution = exifProfile.GetValue(ExifTag.XResolution)?.DeepClone(); |
||||
|
IExifValue yResolution = exifProfile.GetValue(ExifTag.YResolution)?.DeepClone(); |
||||
|
|
||||
|
if (xResolution != null && yResolution != null) |
||||
|
{ |
||||
|
this.collector.Add(xResolution); |
||||
|
this.collector.Add(yResolution); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ProcessMetadata(ExifProfile exifProfile) |
||||
|
{ |
||||
|
foreach (IExifValue entry in exifProfile.Values) |
||||
|
{ |
||||
|
// todo: skip subIfd
|
||||
|
if (entry.DataType == ExifDataType.Ifd) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
switch ((ExifTagValue)(ushort)entry.Tag) |
||||
|
{ |
||||
|
case ExifTagValue.SubIFDOffset: |
||||
|
case ExifTagValue.GPSIFDOffset: |
||||
|
case ExifTagValue.SubIFDs: |
||||
|
case ExifTagValue.XMP: |
||||
|
case ExifTagValue.IPTC: |
||||
|
case ExifTagValue.IccProfile: |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
switch (ExifTags.GetPart(entry.Tag)) |
||||
|
{ |
||||
|
case ExifParts.ExifTags: |
||||
|
case ExifParts.GpsTags: |
||||
|
break; |
||||
|
|
||||
|
case ExifParts.IfdTags: |
||||
|
if (!IsPureMetadata(entry.Tag)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag)) |
||||
|
{ |
||||
|
this.collector.AddOrReplace(entry.DeepClone()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfile, byte[] xmpProfile) |
||||
|
{ |
||||
|
if (exifProfile != null && exifProfile.Parts != ExifParts.None) |
||||
|
{ |
||||
|
foreach (IExifValue entry in exifProfile.Values) |
||||
|
{ |
||||
|
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null) |
||||
|
{ |
||||
|
ExifParts entryPart = ExifTags.GetPart(entry.Tag); |
||||
|
if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart)) |
||||
|
{ |
||||
|
this.collector.AddOrReplace(entry.DeepClone()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
exifProfile.RemoveValue(ExifTag.SubIFDOffset); |
||||
|
} |
||||
|
|
||||
|
if (imageMetadata.IptcProfile != null) |
||||
|
{ |
||||
|
imageMetadata.IptcProfile.UpdateData(); |
||||
|
var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte) |
||||
|
{ |
||||
|
Value = imageMetadata.IptcProfile.Data |
||||
|
}; |
||||
|
|
||||
|
this.collector.Add(iptc); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
exifProfile.RemoveValue(ExifTag.IPTC); |
||||
|
} |
||||
|
|
||||
|
if (imageMetadata.IccProfile != null) |
||||
|
{ |
||||
|
var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined) |
||||
|
{ |
||||
|
Value = imageMetadata.IccProfile.ToByteArray() |
||||
|
}; |
||||
|
|
||||
|
this.collector.Add(icc); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
exifProfile.RemoveValue(ExifTag.IccProfile); |
||||
|
} |
||||
|
|
||||
|
TiffMetadata tiffMetadata = imageMetadata.GetTiffMetadata(); |
||||
|
if (xmpProfile != null) |
||||
|
{ |
||||
|
var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) |
||||
|
{ |
||||
|
Value = xmpProfile |
||||
|
}; |
||||
|
|
||||
|
this.collector.Add(xmp); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
exifProfile.RemoveValue(ExifTag.XMP); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class ImageFormatProcessor |
||||
|
{ |
||||
|
private readonly TiffEncoderEntriesCollector collector; |
||||
|
|
||||
|
public ImageFormatProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; |
||||
|
|
||||
|
public void Process(TiffEncoderCore encoder) |
||||
|
{ |
||||
|
var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel) |
||||
|
{ |
||||
|
Value = GetSamplesPerPixel(encoder) |
||||
|
}; |
||||
|
|
||||
|
ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder); |
||||
|
var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample) |
||||
|
{ |
||||
|
Value = bitsPerSampleValue |
||||
|
}; |
||||
|
|
||||
|
ushort compressionType = GetCompressionType(encoder); |
||||
|
var compression = new ExifShort(ExifTagValue.Compression) |
||||
|
{ |
||||
|
Value = compressionType |
||||
|
}; |
||||
|
|
||||
|
var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation) |
||||
|
{ |
||||
|
Value = (ushort)encoder.PhotometricInterpretation |
||||
|
}; |
||||
|
|
||||
|
this.collector.AddOrReplace(samplesPerPixel); |
||||
|
this.collector.AddOrReplace(bitPerSample); |
||||
|
this.collector.AddOrReplace(compression); |
||||
|
this.collector.AddOrReplace(photometricInterpretation); |
||||
|
|
||||
|
if (encoder.HorizontalPredictor == TiffPredictor.Horizontal) |
||||
|
{ |
||||
|
if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || |
||||
|
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || |
||||
|
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) |
||||
|
{ |
||||
|
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; |
||||
|
|
||||
|
this.collector.AddOrReplace(predictor); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static uint GetSamplesPerPixel(TiffEncoderCore encoder) |
||||
|
{ |
||||
|
switch (encoder.PhotometricInterpretation) |
||||
|
{ |
||||
|
case TiffPhotometricInterpretation.PaletteColor: |
||||
|
case TiffPhotometricInterpretation.BlackIsZero: |
||||
|
case TiffPhotometricInterpretation.WhiteIsZero: |
||||
|
return 1; |
||||
|
case TiffPhotometricInterpretation.Rgb: |
||||
|
default: |
||||
|
return 3; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder) |
||||
|
{ |
||||
|
switch (encoder.PhotometricInterpretation) |
||||
|
{ |
||||
|
case TiffPhotometricInterpretation.PaletteColor: |
||||
|
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4) |
||||
|
{ |
||||
|
return TiffConstants.BitsPerSample4Bit; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return TiffConstants.BitsPerSample8Bit; |
||||
|
} |
||||
|
|
||||
|
case TiffPhotometricInterpretation.Rgb: |
||||
|
return TiffConstants.BitsPerSampleRgb8Bit; |
||||
|
|
||||
|
case TiffPhotometricInterpretation.WhiteIsZero: |
||||
|
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) |
||||
|
{ |
||||
|
return TiffConstants.BitsPerSample1Bit; |
||||
|
} |
||||
|
|
||||
|
return TiffConstants.BitsPerSample8Bit; |
||||
|
|
||||
|
case TiffPhotometricInterpretation.BlackIsZero: |
||||
|
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) |
||||
|
{ |
||||
|
return TiffConstants.BitsPerSample1Bit; |
||||
|
} |
||||
|
|
||||
|
return TiffConstants.BitsPerSample8Bit; |
||||
|
|
||||
|
default: |
||||
|
return TiffConstants.BitsPerSampleRgb8Bit; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static ushort GetCompressionType(TiffEncoderCore encoder) |
||||
|
{ |
||||
|
switch (encoder.CompressionType) |
||||
|
{ |
||||
|
case TiffCompression.Deflate: |
||||
|
// Deflate is allowed for all modes.
|
||||
|
return (ushort)TiffCompression.Deflate; |
||||
|
case TiffCompression.PackBits: |
||||
|
// PackBits is allowed for all modes.
|
||||
|
return (ushort)TiffCompression.PackBits; |
||||
|
case TiffCompression.Lzw: |
||||
|
if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || |
||||
|
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || |
||||
|
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) |
||||
|
{ |
||||
|
return (ushort)TiffCompression.Lzw; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case TiffCompression.CcittGroup3Fax: |
||||
|
return (ushort)TiffCompression.CcittGroup3Fax; |
||||
|
|
||||
|
case TiffCompression.Ccitt1D: |
||||
|
return (ushort)TiffCompression.Ccitt1D; |
||||
|
} |
||||
|
|
||||
|
return (ushort)TiffCompression.None; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Encapsulates the means to encode and decode Tiff images.
|
||||
|
/// </summary>
|
||||
|
public sealed class TiffFormat : IImageFormat<TiffMetadata, TiffFrameMetadata> |
||||
|
{ |
||||
|
private TiffFormat() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current instance.
|
||||
|
/// </summary>
|
||||
|
public static TiffFormat Instance { get; } = new TiffFormat(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string Name => "TIFF"; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string DefaultMimeType => "image/tiff"; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IEnumerable<string> MimeTypes => TiffConstants.MimeTypes; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IEnumerable<string> FileExtensions => TiffConstants.FileExtensions; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public TiffMetadata CreateDefaultFormatMetadata() => new TiffMetadata(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new TiffFrameMetadata(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,113 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides Tiff specific metadata information for the frame.
|
||||
|
/// </summary>
|
||||
|
public class TiffFrameMetadata : IDeepCloneable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
|
||||
|
/// </summary>
|
||||
|
public TiffFrameMetadata() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="other">The other tiff frame metadata.</param>
|
||||
|
private TiffFrameMetadata(TiffFrameMetadata other) |
||||
|
{ |
||||
|
this.BitsPerPixel = other.BitsPerPixel; |
||||
|
this.Compression = other.Compression; |
||||
|
this.PhotometricInterpretation = other.PhotometricInterpretation; |
||||
|
this.Predictor = other.Predictor; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the bits per pixel.
|
||||
|
/// </summary>
|
||||
|
public TiffBitsPerPixel? BitsPerPixel { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the compression scheme used on the image data.
|
||||
|
/// </summary>
|
||||
|
public TiffCompression? Compression { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the color space of the image data.
|
||||
|
/// </summary>
|
||||
|
public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
|
||||
|
/// </summary>
|
||||
|
public TiffPredictor? Predictor { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new <see cref="TiffFrameMetadata"/> instance parsed from the given Exif profile.
|
||||
|
/// </summary>
|
||||
|
/// <param name="profile">The Exif profile containing tiff frame directory tags to parse.
|
||||
|
/// If null, a new instance is created and parsed instead.</param>
|
||||
|
/// <returns>The <see cref="TiffFrameMetadata"/>.</returns>
|
||||
|
internal static TiffFrameMetadata Parse(ExifProfile profile) |
||||
|
{ |
||||
|
var meta = new TiffFrameMetadata(); |
||||
|
Parse(meta, profile); |
||||
|
return meta; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Parses the given Exif profile to populate the properties of the tiff frame meta data..
|
||||
|
/// </summary>
|
||||
|
/// <param name="meta">The tiff frame meta data.</param>
|
||||
|
/// <param name="profile">The Exif profile containing tiff frame directory tags.</param>
|
||||
|
internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) |
||||
|
{ |
||||
|
if (profile != null) |
||||
|
{ |
||||
|
ushort[] bitsPerSample = profile.GetValue(ExifTag.BitsPerSample)?.Value; |
||||
|
meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(bitsPerSample); |
||||
|
meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; |
||||
|
meta.PhotometricInterpretation = |
||||
|
(TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; |
||||
|
meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; |
||||
|
|
||||
|
profile.RemoveValue(ExifTag.BitsPerSample); |
||||
|
profile.RemoveValue(ExifTag.Compression); |
||||
|
profile.RemoveValue(ExifTag.PhotometricInterpretation); |
||||
|
profile.RemoveValue(ExifTag.Predictor); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bits per pixel for the given bits per sample.
|
||||
|
/// </summary>
|
||||
|
/// <param name="bitsPerSample">The tiff bits per sample.</param>
|
||||
|
/// <returns>Bits per pixel.</returns>
|
||||
|
private static TiffBitsPerPixel? BitsPerPixelFromBitsPerSample(ushort[] bitsPerSample) |
||||
|
{ |
||||
|
if (bitsPerSample == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
int bitsPerPixel = 0; |
||||
|
foreach (ushort bits in bitsPerSample) |
||||
|
{ |
||||
|
bitsPerPixel += bits; |
||||
|
} |
||||
|
|
||||
|
return (TiffBitsPerPixel)bitsPerPixel; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IDeepCloneable DeepClone() => new TiffFrameMetadata(this); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue