Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ private long WriteFrame<TPixel>(
entriesCollector.ProcessFrameInfo(frame, imageMetadata);
entriesCollector.ProcessImageFormat(this);

if (writer.Position % 2 != 0)
{
// Write padding byte, because the tiff spec requires ifd offset to begin on a word boundary.
writer.Write(0);
}

this.frameMarkers.Add((ifdOffset, (uint)writer.Position));

return this.WriteIfd(writer, entriesCollector.Entries);
Expand Down
10 changes: 5 additions & 5 deletions src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata, Exi
Value = imageMetadata.IptcProfile.Data
};

this.Collector.Add(iptc);
this.Collector.AddOrReplace(iptc);
}
else
{
Expand All @@ -192,7 +192,7 @@ private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata, Exi
Value = imageMetadata.IccProfile.ToByteArray()
};

this.Collector.Add(icc);
this.Collector.AddOrReplace(icc);
}
else
{
Expand All @@ -206,7 +206,7 @@ private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata, Exi
Value = xmpProfile.Data
};

this.Collector.Add(xmp);
this.Collector.AddOrReplace(xmp);
}
else
{
Expand Down Expand Up @@ -278,7 +278,7 @@ public void Process(TiffEncoderCore encoder)
Value = (ushort)TiffPlanarConfiguration.Chunky
};

ExifLong samplesPerPixel = new(ExifTagValue.SamplesPerPixel)
ExifShort samplesPerPixel = new(ExifTagValue.SamplesPerPixel)
{
Value = GetSamplesPerPixel(encoder)
};
Expand Down Expand Up @@ -317,7 +317,7 @@ TiffPhotometricInterpretation.PaletteColor or
}
}

private static uint GetSamplesPerPixel(TiffEncoderCore encoder)
private static ushort GetSamplesPerPixel(TiffEncoderCore encoder)
=> encoder.PhotometricInterpretation switch
{
TiffPhotometricInterpretation.PaletteColor or
Expand Down
19 changes: 19 additions & 0 deletions tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,25 @@ public void TiffEncoder_PreservesPredictor<TPixel>(TestImageProvider<TPixel> pro
Assert.Equal(expectedPredictor, frameMetadata.Predictor);
}

// https://github.com/SixLabors/ImageSharp/issues/2297
[Fact]
public void TiffEncoder_WritesIfdOffsetAtWordBoundary()
{
// arrange
var tiffEncoder = new TiffEncoder();
using var memStream = new MemoryStream();
using Image<Rgba32> image = new(1, 1);
byte[] expectedIfdOffsetBytes = { 12, 0 };

// act
image.Save(memStream, tiffEncoder);

// assert
byte[] imageBytes = memStream.ToArray();
Assert.Equal(imageBytes[4], expectedIfdOffsetBytes[0]);
Assert.Equal(imageBytes[5], expectedIfdOffsetBytes[1]);
}

[Theory]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)]
Expand Down
44 changes: 18 additions & 26 deletions tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata f
Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel);
Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation);
Assert.Equal(TiffPredictor.None, frameMetaData.Predictor);
}

[Theory]
Expand Down Expand Up @@ -129,7 +128,7 @@ public void MetadataProfiles<TPixel>(TestImageProvider<TPixel> provider, bool ig
Assert.NotNull(rootFrameMetaData.XmpProfile);
Assert.NotNull(rootFrameMetaData.ExifProfile);
Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length);
Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count);
Assert.Equal(25, rootFrameMetaData.ExifProfile.Values.Count);
}
}

Expand Down Expand Up @@ -163,38 +162,33 @@ public void BaselineTags<TPixel>(TestImageProvider<TPixel> provider)
TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata();
Assert.NotNull(exifProfile);

// The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData
// and removed from the profile on decode.
Assert.Equal(26, exifProfile.Values.Count);
Assert.Equal(25, exifProfile.Values.Count);
Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel);
Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression);
Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value);
Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value);
Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value);
Assert.Equal("ImageDescription", exifProfile.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal("Make", exifProfile.GetValue(ExifTag.Make).Value);
Assert.Equal("Model", exifProfile.GetValue(ExifTag.Model).Value);
Assert.Equal("ImageSharp", exifProfile.GetValue(ExifTag.Software).Value);
Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value);
Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value);
Assert.Equal("Artist", exifProfile.GetValue(ExifTag.Artist).Value);
Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value);
Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value);
Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value);
var expectedResolution = new Rational(10000, 1000, simplify: false);
var expectedResolution = new Rational(10, 1, simplify: false);
Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value);
Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value);
Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer());
Assert.Equal(new Number[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer());
Assert.Equal(new Number[] { 285u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer());
Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value);
Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value);
Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat));
Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile));
ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
Assert.NotNull(colorMap);
Assert.Equal(48, colorMap.Length);
Assert.Equal(10537, colorMap[0]);
Assert.Equal(14392, colorMap[1]);
Assert.Equal(58596, colorMap[46]);
Assert.Equal(3855, colorMap[47]);
Assert.Equal(4369, colorMap[0]);
Assert.Equal(8738, colorMap[1]);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation);
Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value);

Expand Down Expand Up @@ -238,7 +232,7 @@ public void Encode_PreservesMetadata<TPixel>(TestImageProvider<TPixel> provider)
{
// Load Tiff image
DecoderOptions options = new() { SkipMetadata = false };
using Image<TPixel> image = provider.GetImage(new TiffDecoder(), options);
using Image<TPixel> image = provider.GetImage(TiffDecoder, options);

ImageMetadata inputMetaData = image.Metadata;
ImageFrame<TPixel> rootFrameInput = image.Frames.RootFrame;
Expand Down Expand Up @@ -284,17 +278,15 @@ public void Encode_PreservesMetadata<TPixel>(TestImageProvider<TPixel> provider)
Assert.NotNull(encodedImageXmpProfile);
Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data);

Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value);
Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal("This is Изготовитель камеры", exifProfileInput.GetValue(ExifTag.Make).Value);
Assert.Equal("This is Авторские права", exifProfileInput.GetValue(ExifTag.Copyright).Value);

Assert.Equal(exifProfileInput.GetValue(ExifTag.Software).Value, encodedImageExifProfile.GetValue(ExifTag.Software).Value);
Assert.Equal(exifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value);
Assert.Equal(exifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value);
Assert.Equal(exifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal(exifProfileInput.GetValue(ExifTag.Artist).Value, encodedImageExifProfile.GetValue(ExifTag.Artist).Value);
Assert.Equal(exifProfileInput.GetValue(ExifTag.Orientation).Value, encodedImageExifProfile.GetValue(ExifTag.Orientation).Value);
Assert.Equal(exifProfileInput.GetValue(ExifTag.Model).Value, encodedImageExifProfile.GetValue(ExifTag.Model).Value);

// Note that the encoded profile has PlanarConfiguration explicitly set, which is missing in the original image profile.
Assert.Equal((ushort)TiffPlanarConfiguration.Chunky, encodedImageExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value);
Assert.Equal(exifProfileInput.Values.Count + 1, encodedImageExifProfile.Values.Count);
Assert.Equal(exifProfileInput.Values.Count, encodedImageExifProfile.Values.Count);
}
}
4 changes: 2 additions & 2 deletions tests/Images/Input/Tiff/metadata_sample.tiff
Git LFS file not shown