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.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png.Zlib |
|||
namespace SixLabors.ImageSharp.Compression.Zlib |
|||
{ |
|||
/// <content>
|
|||
/// 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: |
|||
|
|||
@ -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