-
-
Notifications
You must be signed in to change notification settings - Fork 860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#12 Use exif reader for tiff tags reading #1494
#12 Use exif reader for tiff tags reading #1494
Conversation
d608688
to
f9b1a27
Compare
f9b1a27
to
acf5f3b
Compare
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
if (val == value) |
There was a problem hiding this comment.
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.
@@ -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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. 👍
|
||
private uint gpsOffset = 0; | ||
|
||
public ExifReader(bool isBigEndian, Stream stream) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about endianness?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
public override IExifValue DeepClone() => new ExifNumberArray(this); | ||
|
||
private bool SetSingle<T>(T value, Func<T, Number> converter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TrySetSingle
TrySetArray
There was a problem hiding this comment.
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); |
@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 ? |
strange, it doesn't work |
/// <summary> | ||
/// Gets the RowsPerStrip exif tag. | ||
/// </summary> | ||
public static ExifTag<Number> RowsPerStrip { get; } = new ExifTag<Number>(ExifTagValue.RowsPerStrip); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RowsPerStrip
dataType is short or long - https://www.awaresystems.be/imaging/tiff/tifftags/rowsperstrip.html
@@ -54,15 +54,6 @@ public static ImageMetadata Create(List<TiffFrameMetadata> frames, bool ignoreMe | |||
} | |||
} | |||
|
|||
if (coreMetadata.ExifProfile == null) | |||
{ | |||
byte[] buf = frame.GetArray<byte>(ExifTag.SubIFDOffset, true); |
There was a problem hiding this comment.
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>()); |
There was a problem hiding this comment.
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)
{ | ||
return false; | ||
// sometimes duplicates appear, |
There was a problem hiding this comment.
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:
-
Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); -
Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime)); -
Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932));
@@ -201,10 +203,14 @@ public void SetValue(TestImageWriteFormat imageFormat) | |||
IExifValue<Rational[]> latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); | |||
Assert.Equal(expectedLatitude, latitude.Value); | |||
|
|||
// todo: duplicate tags | |||
Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); |
There was a problem hiding this comment.
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
happened! |
Did you mean to close this? I thought it just needed a little work. |
sorry, I myself did not understand how it happened |
@JimBobSquarePants @brianpopow I pushed some small improvements, please review. Are you changes requested? |
{ | ||
if (this.ReadHeader()) | ||
var headerBytes = new byte[2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stackalloc span?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
@brianpopow if you’re happy, I’m happy. |
Thanks @IldarKhayrutdinov !! |
It was crazy PR 🙃 |
#12 Use exif reader for tiff tags reading
#12 Use exif reader for tiff tags reading
Prerequisites
Description
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.