Skip to content

Commit 1a9d578

Browse files
Migrate TiffMetadata
1 parent fdcd602 commit 1a9d578

3 files changed

Lines changed: 172 additions & 19 deletions

File tree

src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public static ImageMetadata Create(List<ImageFrameMetadata> frames, bool ignoreM
2323
TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
2424
}
2525

26-
ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].ExifProfile);
26+
ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0]);
2727

2828
if (!ignoreMetadata)
2929
{
@@ -50,14 +50,22 @@ public static ImageMetadata Create(List<ImageFrameMetadata> frames, bool ignoreM
5050
return imageMetaData;
5151
}
5252

53-
private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile)
53+
private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ImageFrameMetadata rootFrameMetadata)
5454
{
5555
ImageMetadata imageMetaData = new();
56-
SetResolution(imageMetaData, exifProfile);
56+
SetResolution(imageMetaData, rootFrameMetadata.ExifProfile);
5757

5858
TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata();
5959
tiffMetadata.ByteOrder = byteOrder;
6060
tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default;
61+
62+
TiffFrameMetadata tiffFrameMetadata = rootFrameMetadata.GetTiffMetadata();
63+
tiffMetadata.BitsPerPixel = tiffFrameMetadata.BitsPerPixel;
64+
tiffMetadata.BitsPerSample = tiffFrameMetadata.BitsPerSample;
65+
tiffMetadata.Compression = tiffFrameMetadata.Compression;
66+
tiffMetadata.PhotometricInterpretation = tiffFrameMetadata.PhotometricInterpretation;
67+
tiffMetadata.Predictor = tiffFrameMetadata.Predictor;
68+
6169
return imageMetaData;
6270
}
6371

@@ -109,7 +117,7 @@ private static bool TryGetIptc(IReadOnlyList<IExifValue> exifValues, out byte[]
109117
return false;
110118
}
111119

112-
// Probably wrong endianess, swap byte order.
120+
// Probably wrong endianness, swap byte order.
113121
Span<byte> iptcBytesSpan = iptcBytes.AsSpan();
114122
Span<byte> buffer = stackalloc byte[4];
115123
for (int i = 0; i < iptcBytes.Length; i += 4)

src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -339,21 +339,35 @@ private void SanitizeAndSetEncoderOptions(
339339
TiffCompression compression,
340340
TiffPredictor predictor)
341341
{
342+
// Ensure 1 Bit compression is only used with 1 bit pixel type.
343+
// Choose a sensible default based on the bits per pixel.
344+
if (IsOneBitCompression(compression) && bitsPerPixel != TiffBitsPerPixel.Bit1)
345+
{
346+
compression = bitsPerPixel switch
347+
{
348+
< TiffBitsPerPixel.Bit8 => TiffCompression.None,
349+
_ => TiffCompression.Deflate,
350+
};
351+
}
352+
353+
// Ensure predictor is only used with compression that supports it.
354+
predictor = HasPredictor(compression) ? predictor : TiffPredictor.None;
355+
342356
// BitsPerPixel should be the primary source of truth for the encoder options.
343357
switch (bitsPerPixel)
344358
{
345359
case TiffBitsPerPixel.Bit1:
346360
if (IsOneBitCompression(compression))
347361
{
348362
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
349-
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
363+
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, predictor);
350364
break;
351365
}
352366

353-
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None);
367+
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
354368
break;
355369
case TiffBitsPerPixel.Bit4:
356-
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None);
370+
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor);
357371
break;
358372
case TiffBitsPerPixel.Bit8:
359373

@@ -381,23 +395,17 @@ or TiffPhotometricInterpretation.WhiteIsZero
381395
case TiffBitsPerPixel.Bit42:
382396
case TiffBitsPerPixel.Bit48:
383397
// Encoding not yet supported bits per pixel will default to 24 bits.
384-
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
398+
399+
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor);
385400
break;
386401
case TiffBitsPerPixel.Bit64:
387402
// Encoding not yet supported bits per pixel will default to 32 bits.
388-
this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
403+
this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, predictor);
389404
break;
390405
default:
391406
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
392407
break;
393408
}
394-
395-
// Make sure 1 Bit compression is only used with 1 bit pixel type.
396-
if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
397-
{
398-
// Invalid compression / bits per pixel combination, fallback to no compression.
399-
this.CompressionType = TiffCompression.None;
400-
}
401409
}
402410

403411
[MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
@@ -415,4 +423,7 @@ private void SetEncoderOptions(
415423

416424
public static bool IsOneBitCompression(TiffCompression? compression)
417425
=> compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax;
426+
427+
public static bool HasPredictor(TiffCompression? compression)
428+
=> compression is TiffCompression.Deflate or TiffCompression.Lzw;
418429
}

src/ImageSharp/Formats/Tiff/TiffMetadata.cs

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using SixLabors.ImageSharp.Formats.Tiff.Constants;
45
using SixLabors.ImageSharp.PixelFormats;
56

67
namespace SixLabors.ImageSharp.Formats.Tiff;
@@ -25,6 +26,11 @@ private TiffMetadata(TiffMetadata other)
2526
{
2627
this.ByteOrder = other.ByteOrder;
2728
this.FormatType = other.FormatType;
29+
this.BitsPerPixel = other.BitsPerPixel;
30+
this.BitsPerSample = other.BitsPerSample;
31+
this.Compression = other.Compression;
32+
this.PhotometricInterpretation = other.PhotometricInterpretation;
33+
this.Predictor = other.Predictor;
2834
}
2935

3036
/// <summary>
@@ -37,14 +43,142 @@ private TiffMetadata(TiffMetadata other)
3743
/// </summary>
3844
public TiffFormatType FormatType { get; set; }
3945

46+
/// <summary>
47+
/// Gets or sets the bits per pixel. Derived from the root frame.
48+
/// </summary>
49+
public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel;
50+
51+
/// <summary>
52+
/// Gets or sets number of bits per component. Derived from the root frame.
53+
/// </summary>
54+
public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample;
55+
56+
/// <summary>
57+
/// Gets or sets the compression scheme used on the image data. Derived from the root frame.
58+
/// </summary>
59+
public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression;
60+
61+
/// <summary>
62+
/// Gets or sets the color space of the image data. Derived from the root frame.
63+
/// </summary>
64+
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation;
65+
66+
/// <summary>
67+
/// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
68+
/// Derived from the root frame.
69+
/// </summary>
70+
public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor;
71+
4072
/// <inheritdoc/>
41-
public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException();
73+
public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
74+
{
75+
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
76+
return bpp switch
77+
{
78+
1 => new TiffMetadata
79+
{
80+
BitsPerPixel = TiffBitsPerPixel.Bit1,
81+
BitsPerSample = TiffConstants.BitsPerSample1Bit,
82+
PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero,
83+
Compression = TiffCompression.CcittGroup4Fax,
84+
Predictor = TiffPredictor.None
85+
},
86+
<= 4 => new TiffMetadata
87+
{
88+
BitsPerPixel = TiffBitsPerPixel.Bit4,
89+
BitsPerSample = TiffConstants.BitsPerSample4Bit,
90+
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
91+
Compression = TiffCompression.Deflate,
92+
Predictor = TiffPredictor.None // Best match for low bit depth
93+
},
94+
8 => new TiffMetadata
95+
{
96+
BitsPerPixel = TiffBitsPerPixel.Bit8,
97+
BitsPerSample = TiffConstants.BitsPerSample8Bit,
98+
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
99+
Compression = TiffCompression.Deflate,
100+
Predictor = TiffPredictor.Horizontal
101+
},
102+
16 => new TiffMetadata
103+
{
104+
BitsPerPixel = TiffBitsPerPixel.Bit16,
105+
BitsPerSample = TiffConstants.BitsPerSample16Bit,
106+
PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero,
107+
Compression = TiffCompression.Deflate,
108+
Predictor = TiffPredictor.Horizontal
109+
},
110+
32 or 64 => new TiffMetadata
111+
{
112+
BitsPerPixel = TiffBitsPerPixel.Bit32,
113+
BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
114+
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
115+
Compression = TiffCompression.Deflate,
116+
Predictor = TiffPredictor.Horizontal
117+
},
118+
_ => new TiffMetadata
119+
{
120+
BitsPerPixel = TiffBitsPerPixel.Bit24,
121+
BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
122+
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
123+
Compression = TiffCompression.Deflate,
124+
Predictor = TiffPredictor.Horizontal
125+
}
126+
};
127+
}
42128

43129
/// <inheritdoc/>
44-
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException();
130+
public PixelTypeInfo GetPixelTypeInfo()
131+
{
132+
int bpp = (int)this.BitsPerPixel;
133+
134+
TiffBitsPerSample samples = this.BitsPerSample;
135+
PixelComponentInfo info = samples.Channels switch
136+
{
137+
1 => PixelComponentInfo.Create(1, bpp, bpp),
138+
2 => PixelComponentInfo.Create(2, bpp, bpp, samples.Channel0, samples.Channel1),
139+
3 => PixelComponentInfo.Create(3, bpp, samples.Channel0, samples.Channel1, samples.Channel2),
140+
_ => PixelComponentInfo.Create(4, bpp, samples.Channel0, samples.Channel1, samples.Channel2, samples.Channel3)
141+
};
142+
143+
PixelColorType colorType;
144+
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
145+
switch (this.BitsPerPixel)
146+
{
147+
case TiffBitsPerPixel.Bit1:
148+
colorType = PixelColorType.Binary;
149+
break;
150+
case TiffBitsPerPixel.Bit4:
151+
case TiffBitsPerPixel.Bit6:
152+
case TiffBitsPerPixel.Bit8:
153+
colorType = PixelColorType.Indexed;
154+
break;
155+
case TiffBitsPerPixel.Bit16:
156+
colorType = PixelColorType.Luminance;
157+
break;
158+
case TiffBitsPerPixel.Bit32:
159+
case TiffBitsPerPixel.Bit64:
160+
colorType = PixelColorType.RGB | PixelColorType.Alpha;
161+
alpha = PixelAlphaRepresentation.Unassociated;
162+
break;
163+
default:
164+
colorType = PixelColorType.RGB;
165+
break;
166+
}
167+
168+
return new PixelTypeInfo(bpp)
169+
{
170+
ColorType = colorType,
171+
ComponentInfo = info,
172+
AlphaRepresentation = alpha
173+
};
174+
}
45175

46176
/// <inheritdoc/>
47-
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException();
177+
public FormatConnectingMetadata ToFormatConnectingMetadata()
178+
=> new()
179+
{
180+
PixelTypeInfo = this.GetPixelTypeInfo()
181+
};
48182

49183
/// <inheritdoc/>
50184
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

0 commit comments

Comments
 (0)