Browse Source

Migrate TiffMetadata

pull/2751/head
James Jackson-South 2 years ago
parent
commit
1a9d578a04
  1. 16
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  2. 35
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  3. 140
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs

16
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

@ -23,7 +23,7 @@ internal static class TiffDecoderMetadataCreator
TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
} }
ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].ExifProfile); ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0]);
if (!ignoreMetadata) if (!ignoreMetadata)
{ {
@ -50,14 +50,22 @@ internal static class TiffDecoderMetadataCreator
return imageMetaData; return imageMetaData;
} }
private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile) private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ImageFrameMetadata rootFrameMetadata)
{ {
ImageMetadata imageMetaData = new(); ImageMetadata imageMetaData = new();
SetResolution(imageMetaData, exifProfile); SetResolution(imageMetaData, rootFrameMetadata.ExifProfile);
TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata();
tiffMetadata.ByteOrder = byteOrder; tiffMetadata.ByteOrder = byteOrder;
tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default; tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default;
TiffFrameMetadata tiffFrameMetadata = rootFrameMetadata.GetTiffMetadata();
tiffMetadata.BitsPerPixel = tiffFrameMetadata.BitsPerPixel;
tiffMetadata.BitsPerSample = tiffFrameMetadata.BitsPerSample;
tiffMetadata.Compression = tiffFrameMetadata.Compression;
tiffMetadata.PhotometricInterpretation = tiffFrameMetadata.PhotometricInterpretation;
tiffMetadata.Predictor = tiffFrameMetadata.Predictor;
return imageMetaData; return imageMetaData;
} }
@ -109,7 +117,7 @@ internal static class TiffDecoderMetadataCreator
return false; return false;
} }
// Probably wrong endianess, swap byte order. // Probably wrong endianness, swap byte order.
Span<byte> iptcBytesSpan = iptcBytes.AsSpan(); Span<byte> iptcBytesSpan = iptcBytes.AsSpan();
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
for (int i = 0; i < iptcBytes.Length; i += 4) for (int i = 0; i < iptcBytes.Length; i += 4)

35
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -339,6 +339,20 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
TiffCompression compression, TiffCompression compression,
TiffPredictor predictor) TiffPredictor predictor)
{ {
// Ensure 1 Bit compression is only used with 1 bit pixel type.
// Choose a sensible default based on the bits per pixel.
if (IsOneBitCompression(compression) && bitsPerPixel != TiffBitsPerPixel.Bit1)
{
compression = bitsPerPixel switch
{
< TiffBitsPerPixel.Bit8 => TiffCompression.None,
_ => TiffCompression.Deflate,
};
}
// Ensure predictor is only used with compression that supports it.
predictor = HasPredictor(compression) ? predictor : TiffPredictor.None;
// BitsPerPixel should be the primary source of truth for the encoder options. // BitsPerPixel should be the primary source of truth for the encoder options.
switch (bitsPerPixel) switch (bitsPerPixel)
{ {
@ -346,14 +360,14 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
if (IsOneBitCompression(compression)) if (IsOneBitCompression(compression))
{ {
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None); this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, predictor);
break; break;
} }
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None); this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break; break;
case TiffBitsPerPixel.Bit4: case TiffBitsPerPixel.Bit4:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None); this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor);
break; break;
case TiffBitsPerPixel.Bit8: case TiffBitsPerPixel.Bit8:
@ -381,23 +395,17 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
case TiffBitsPerPixel.Bit42: case TiffBitsPerPixel.Bit42:
case TiffBitsPerPixel.Bit48: case TiffBitsPerPixel.Bit48:
// Encoding not yet supported bits per pixel will default to 24 bits. // Encoding not yet supported bits per pixel will default to 24 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor);
break; break;
case TiffBitsPerPixel.Bit64: case TiffBitsPerPixel.Bit64:
// Encoding not yet supported bits per pixel will default to 32 bits. // Encoding not yet supported bits per pixel will default to 32 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, predictor);
break; break;
default: default:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor); this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
break; break;
} }
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
this.CompressionType = TiffCompression.None;
}
} }
[MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))] [MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
@ -415,4 +423,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
public static bool IsOneBitCompression(TiffCompression? compression) public static bool IsOneBitCompression(TiffCompression? compression)
=> compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax; => compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax;
public static bool HasPredictor(TiffCompression? compression)
=> compression is TiffCompression.Deflate or TiffCompression.Lzw;
} }

140
src/ImageSharp/Formats/Tiff/TiffMetadata.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff; namespace SixLabors.ImageSharp.Formats.Tiff;
@ -25,6 +26,11 @@ public class TiffMetadata : IFormatMetadata<TiffMetadata>
{ {
this.ByteOrder = other.ByteOrder; this.ByteOrder = other.ByteOrder;
this.FormatType = other.FormatType; this.FormatType = other.FormatType;
this.BitsPerPixel = other.BitsPerPixel;
this.BitsPerSample = other.BitsPerSample;
this.Compression = other.Compression;
this.PhotometricInterpretation = other.PhotometricInterpretation;
this.Predictor = other.Predictor;
} }
/// <summary> /// <summary>
@ -37,14 +43,142 @@ public class TiffMetadata : IFormatMetadata<TiffMetadata>
/// </summary> /// </summary>
public TiffFormatType FormatType { get; set; } public TiffFormatType FormatType { get; set; }
/// <summary>
/// Gets or sets the bits per pixel. Derived from the root frame.
/// </summary>
public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel;
/// <summary>
/// Gets or sets number of bits per component. Derived from the root frame.
/// </summary>
public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample;
/// <summary>
/// Gets or sets the compression scheme used on the image data. Derived from the root frame.
/// </summary>
public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression;
/// <summary>
/// Gets or sets the color space of the image data. Derived from the root frame.
/// </summary>
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation;
/// <summary>
/// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
/// Derived from the root frame.
/// </summary>
public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor;
/// <inheritdoc/> /// <inheritdoc/>
public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
return bpp switch
{
1 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit1,
BitsPerSample = TiffConstants.BitsPerSample1Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero,
Compression = TiffCompression.CcittGroup4Fax,
Predictor = TiffPredictor.None
},
<= 4 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit4,
BitsPerSample = TiffConstants.BitsPerSample4Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.None // Best match for low bit depth
},
8 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit8,
BitsPerSample = TiffConstants.BitsPerSample8Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
},
16 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit16,
BitsPerSample = TiffConstants.BitsPerSample16Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
},
32 or 64 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit32,
BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
},
_ => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit24,
BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
}
};
}
/// <inheritdoc/> /// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BitsPerPixel;
TiffBitsPerSample samples = this.BitsPerSample;
PixelComponentInfo info = samples.Channels switch
{
1 => PixelComponentInfo.Create(1, bpp, bpp),
2 => PixelComponentInfo.Create(2, bpp, bpp, samples.Channel0, samples.Channel1),
3 => PixelComponentInfo.Create(3, bpp, samples.Channel0, samples.Channel1, samples.Channel2),
_ => PixelComponentInfo.Create(4, bpp, samples.Channel0, samples.Channel1, samples.Channel2, samples.Channel3)
};
PixelColorType colorType;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
switch (this.BitsPerPixel)
{
case TiffBitsPerPixel.Bit1:
colorType = PixelColorType.Binary;
break;
case TiffBitsPerPixel.Bit4:
case TiffBitsPerPixel.Bit6:
case TiffBitsPerPixel.Bit8:
colorType = PixelColorType.Indexed;
break;
case TiffBitsPerPixel.Bit16:
colorType = PixelColorType.Luminance;
break;
case TiffBitsPerPixel.Bit32:
case TiffBitsPerPixel.Bit64:
colorType = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
default:
colorType = PixelColorType.RGB;
break;
}
return new PixelTypeInfo(bpp)
{
ColorType = colorType,
ComponentInfo = info,
AlphaRepresentation = alpha
};
}
/// <inheritdoc/> /// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/> /// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

Loading…
Cancel
Save