Skip to content

#12 Use exif reader for tiff tags reading - #1494

Merged
JimBobSquarePants merged 8 commits into
SixLabors:tiff-formatfrom
IldarKhayrutdinov:tiff-format
Jan 29, 2021
Merged

JimBobSquarePants merged 8 commits into
SixLabors:tiff-formatfrom
IldarKhayrutdinov:tiff-format

Conversation

@IldarKhayrutdinov

@IldarKhayrutdinov IldarKhayrutdinov commented Jan 6, 2021 •

Copy link
Copy Markdown
Contributor

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

  • Remove duplicate code, use exif reader for tiff tags reading
  • Correct TrySetValue method for ExifNumberArray
  • Minor fixes for exif values and exif reader
  • Duplicate tags problem report

I was afraid to make a lot of changes to exif classes, so it turned out a little clumsy. Don't hesitate to write comments 😉
Tests are works correctly and successful.
The logic of Exif reading has not changed.
The difficulty was in supporting multi-page Tiff files, where IDF tags and their extended data (> 4 bytes) can be scattered throughout the file in no particular order, and file can be very large.

@IldarKhayrutdinov
IldarKhayrutdinov marked this pull request as ready for review January 7, 2021 11:39
@IldarKhayrutdinov IldarKhayrutdinov mentioned this pull request Jan 7, 2021
17 tasks done
@brianpopow brianpopow self-assigned this Jan 8, 2021

@brianpopow brianpopow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IldarKhayrutdinov thanks once again for your help.

return false;
// sometimes duplicates appear,
// can compare val.Tag == exif.Tag
if (val == exif)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is comparing by reference, this seems like a mistake.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this is suspicious, but it used to work like this:

Comment thread src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs

@JimBobSquarePants JimBobSquarePants left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Definitely a step in the right direction! I've picked up on a few things though to revisit.

Comment thread src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs Outdated
Comment thread src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs Outdated
@@ -31,7 +31,7 @@ public static T[] GetArray<T>(this TiffFrameMetadata meta, ExifTag tag, bool opt
public static bool TryGetArray<T>(this TiffFrameMetadata meta, ExifTag tag, out T[] result)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of all these extension methods. ExifProfile has strong type (Get/Set)Value methods already.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, now can remove or simplify these methods

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants I got rid of some methods, but most remained, they are useful as wrappers for ExifProfile methods - allow you to cast types for properties

@JimBobSquarePants JimBobSquarePants Jan 26, 2021 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow you to cast types for properties

I don't understand. The values returned by the existing method are already strong typed to the correct type without the need for internal calls and casting.

For example. This method is called upstream by the following in TiffDecoderMetadataCreator.

byte[] buf = frame.GetArray<byte>(ExifTag.XMP, true);
if (buf != null)
{
    tiffMetadata.XmpProfile = buf;
}

However, this could easily have been.

IExifValue<byte[]> xmp = frame.ExifProfile.GetValue(ExifTag.XMP);
if (xmp != null)
{
    tiffMetadata.XmpProfile = xmp.Value;
}

which uses the existing methods removing any code duplication and is better for someone reading the code since it indicates exactly where the metadata is coming from.

I do wish that ExifProfile.GetValue was ExifProfile.TryGetValue but we can't fix that without introducing a breaking change (I could be convinced to do that though).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same duplication is happening in the resolution getter/setter code.

We have existing methods in jpeg GetExifResolutionValue that demonstrate a much more simple approach using existing APIs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants yes, you are right, it can be simplified even more, I did it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ace... I'll give it another review tonight. 👍

Comment thread src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs Outdated

private uint gpsOffset = 0;

public ExifReader(bool isBigEndian, Stream stream)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these parameters should be reversed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it looks more logical

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

public ExifReader(byte[] exifData) =>
this.data = new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about endianness?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

endianness will readed later:

this.isBigEndian = this.ReadUInt16() != 0x4949;

the class turned out to be a little universal, it can make 2 inheritor for tiff and exif, what do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants it seems, worth to make 2 inheritor for tiff and exif, it will look better

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants please review again, I split the classes and they seems to be looking better now

Comment thread src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs Outdated
Comment thread src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs

public override IExifValue DeepClone() => new ExifNumberArray(this);

private bool SetSingle<T>(T value, Func<T, Number> converter)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TrySetSingle
TrySetArray

@IldarKhayrutdinov IldarKhayrutdinov Jan 25, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods return the value for simplicity case construction, they will always return true:

return this.SetSingle(val, v => (Number)v);

Comment thread tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs Outdated
@IldarKhayrutdinov

IldarKhayrutdinov commented Jan 12, 2021 •

Copy link
Copy Markdown
Contributor Author

@JimBobSquarePants @brianpopow I can't reply to your comments, maybe it's because of Assignees

@brianpopow brianpopow removed their assignment Jan 12, 2021
@brianpopow

brianpopow commented Jan 12, 2021 •

Copy link
Copy Markdown
Collaborator

@JimBobSquarePants @brianpopow I can't reply to your comments, maybe it's because of Assignees

i dont know why that is. I have removed myself from assignees. Can you try again @IldarKhayrutdinov ?

@IldarKhayrutdinov
IldarKhayrutdinov marked this pull request as draft January 12, 2021 19:28
@IldarKhayrutdinov
IldarKhayrutdinov marked this pull request as ready for review January 12, 2021 19:29
@IldarKhayrutdinov

IldarKhayrutdinov commented Jan 12, 2021 •

Copy link
Copy Markdown
Contributor Author

strange, it doesn't work

Comment thread src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
/// <summary>
/// Gets the RowsPerStrip exif tag.
/// </summary>
public static ExifTag<Number> RowsPerStrip { get; } = new ExifTag<Number>(ExifTagValue.RowsPerStrip);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs
Comment thread src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs

if (coreMetadata.ExifProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.SubIFDOffset, true);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now SubIFD and subGps are read to common TiffFrameMetaData.FrameTags list


// 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>());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DuplicateKeyComparer used for possiblity add a duplicate offsets (but tags don't duplicate)

foreach (IExifValue val in values)
{
return false;
// sometimes duplicates appear,

@IldarKhayrutdinov IldarKhayrutdinov Jan 7, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note, I added a reproduction of the problem with duplicates, these tests work successfully:

  1. Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));

  2. Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime));

  3. Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932));

Assert.Equal(expectedLatitude, latitude.Value);

// todo: duplicate tags
Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this PR doesn't affected, it was earlier

@IldarKhayrutdinov

Copy link
Copy Markdown
Contributor Author

strange, it doesn't work

happened!

@JimBobSquarePants

Copy link
Copy Markdown
Member

Did you mean to close this? I thought it just needed a little work.

@IldarKhayrutdinov

Copy link
Copy Markdown
Contributor Author

Did you mean to close this? I thought it just needed a little work.

sorry, I myself did not understand how it happened

@IldarKhayrutdinov

Copy link
Copy Markdown
Contributor Author

@JimBobSquarePants @brianpopow I pushed some small improvements, please review. Are you changes requested?

private static ByteOrder ReadByteOrder(Stream stream)
{
if (this.ReadHeader())
var headerBytes = new byte[2];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stackalloc span?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, do you mean doing new Span(stackalloc byte[2]) ?

return null;
}
}
protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotImplementedException();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this is a feature waiting to be implemented?

@IldarKhayrutdinov IldarKhayrutdinov Jan 26, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HeaderReader class use one time to read only header. I changed exception type to NotSupported.
I tried to make fewer changes within the PR.
It was possible to move the stream reading methods into separate classes (as was in tiff), this would remove HeaderReader class and slightly simplify the exif/tiff readers.

@brianpopow brianpopow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be merged now, but i leave the final verdict to @JimBobSquarePants

@JimBobSquarePants

Copy link
Copy Markdown
Member

@brianpopow if you’re happy, I’m happy.

@JimBobSquarePants
JimBobSquarePants merged commit 1498dfa into SixLabors:tiff-format Jan 29, 2021
@JimBobSquarePants

Copy link
Copy Markdown
Member

Thanks @IldarKhayrutdinov !!

@IldarKhayrutdinov
IldarKhayrutdinov deleted the tiff-format branch January 29, 2021 20:14
@IldarKhayrutdinov

Copy link
Copy Markdown
Contributor Author

It was crazy PR 🙃

JimBobSquarePants added a commit that referenced this pull request Feb 17, 2021
#12 Use exif reader for tiff tags reading
JimBobSquarePants added a commit that referenced this pull request Feb 17, 2021
#12 Use exif reader for tiff tags reading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants