|
|
@ -147,9 +147,10 @@ void main() { |
|
|
|
|
|
|
|
// FIXME: this should probably be IDisposable?
|
|
|
|
public class Photo { |
|
|
|
public string File; |
|
|
|
public string Filename; |
|
|
|
public bool Loaded = false; |
|
|
|
public Vector2i Size; |
|
|
|
public DateTime DateTimeOriginal; |
|
|
|
public string CameraModel = ""; |
|
|
|
public string LensModel = ""; |
|
|
|
public string FocalLength = "<unk>"; |
|
|
@ -163,12 +164,14 @@ public class Photo { |
|
|
|
private Texture placeholder; |
|
|
|
private Image<Rgba32>? image = null; |
|
|
|
|
|
|
|
public Photo(string file, Texture placeholder) { |
|
|
|
File = file; |
|
|
|
public Photo(string filename, Texture placeholder) { |
|
|
|
Filename = filename; |
|
|
|
this.placeholder = placeholder; |
|
|
|
texture = placeholder; |
|
|
|
|
|
|
|
ImageInfo info = Image.Identify(file); |
|
|
|
DateTime creationTime = File.GetCreationTime(filename); // Local time.
|
|
|
|
DateTimeOriginal = creationTime; |
|
|
|
ImageInfo info = Image.Identify(filename); |
|
|
|
Size = new(info.Size.Width, info.Size.Height); |
|
|
|
ParseExif(info.Metadata.ExifProfile); |
|
|
|
TryParseRating(info.Metadata.XmpProfile, out Rating); |
|
|
@ -178,7 +181,7 @@ public class Photo { |
|
|
|
// We don't assign to this.image until Load() is done, because we might
|
|
|
|
// edit the image due to rotation (etc) and don't want to try generating
|
|
|
|
// a texture for it until that's already happened.
|
|
|
|
Image<Rgba32> tmp = await Image.LoadAsync<Rgba32>(File); |
|
|
|
Image<Rgba32> tmp = await Image.LoadAsync<Rgba32>(Filename); |
|
|
|
Util.RotateImageFromExif(tmp, Orientation); |
|
|
|
image = tmp; |
|
|
|
} |
|
|
@ -206,14 +209,34 @@ public class Photo { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Exif (and other image metadata) reference, from the now-defunct Metadata Working Group:
|
|
|
|
// https://web.archive.org/web/20180919181934/http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf
|
|
|
|
//
|
|
|
|
// Specifically:
|
|
|
|
//
|
|
|
|
// In general, date/time metadata is being used to describe the following scenarios:
|
|
|
|
// * Date/time original specifies when a photo was taken
|
|
|
|
// * Date/time digitized specifies when an image was digitized
|
|
|
|
// * Date/time modified specifies when a file was modified by the user
|
|
|
|
//
|
|
|
|
// Original Date/Time – Creation date of the intellectual content (e.g. the photograph), rather than the creation date of the content being shown
|
|
|
|
// Exif DateTimeOriginal (36867, 0x9003) and SubSecTimeOriginal (37521, 0x9291)
|
|
|
|
// IPTC DateCreated (IIM 2:55, 0x0237) and TimeCreated (IIM 2:60, 0x023C)
|
|
|
|
// XMP (photoshop:DateCreated)
|
|
|
|
//
|
|
|
|
// Digitized Date/Time – Creation date of the digital representation
|
|
|
|
// Exif DateTimeDigitized (36868, 0x9004) and SubSecTimeDigitized (37522, 0x9292)
|
|
|
|
// IPTC DigitalCreationDate (IIM 2:62, 0x023E) and DigitalCreationTime (IIM 2:63, 0x023F)
|
|
|
|
// XMP (xmp:CreateDate)
|
|
|
|
//
|
|
|
|
// Modification Date/Time – Modification date of the digital image file
|
|
|
|
// Exif DateTime (306, 0x132) and SubSecTime (37520, 0x9290)
|
|
|
|
// XMP (xmp:ModifyDate)
|
|
|
|
private void ParseExif(ExifProfile? exifs) { |
|
|
|
if (exifs == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// FIXME: when we write out images, we'll want to correct the Exif Orientation to 1.
|
|
|
|
// FIXME: handle date shot / edited (and sort by shot date?)
|
|
|
|
|
|
|
|
IExifValue<ushort>? orientation; |
|
|
|
if (exifs.TryGetValue(ExifTag.Orientation, out orientation)) { |
|
|
|
Orientation = orientation.Value; |
|
|
@ -281,6 +304,23 @@ public class Photo { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// FIXME: I think the iPhone stores time in UTC but other cameras report it in local time.
|
|
|
|
IExifValue<string>? dateTimeOriginal; |
|
|
|
if (exifs.TryGetValue(ExifTag.DateTimeOriginal, out dateTimeOriginal)) { |
|
|
|
DateTime date; |
|
|
|
if (DateTime.TryParseExact( |
|
|
|
dateTimeOriginal.Value ?? "", |
|
|
|
"yyyy:MM:dd HH:mm:ss", |
|
|
|
System.Globalization.CultureInfo.InvariantCulture, |
|
|
|
System.Globalization.DateTimeStyles.AssumeLocal, |
|
|
|
out date)) { |
|
|
|
DateTimeOriginal = date; |
|
|
|
} else { |
|
|
|
Console.WriteLine($"*** WARNING: unexpected DateTimeOriginal value: {dateTimeOriginal.Value}"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// foreach (IExifValue exif in exifs.Values) {
|
|
|
|
// Console.WriteLine(exif.Tag.ToString() + " " + exif.GetValue().ToString());
|
|
|
|
// }
|
|
|
@ -301,7 +341,7 @@ public class Photo { |
|
|
|
|
|
|
|
public string Description() { |
|
|
|
string shootingInfo = $"{FocalLength}, {FNumber} at {ExposureTime}, {IsoSpeed}"; |
|
|
|
return String.Format("{0,-40} {1,-50} {2}", shootingInfo, $"{CameraModel} {LensModel}", File); |
|
|
|
return String.Format("{0,-40} {1,-50} {2}", shootingInfo, $"{CameraModel} {LensModel}", Filename); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -603,8 +643,8 @@ public class Game : GameWindow { |
|
|
|
|
|
|
|
// Load textures from JPEGs.
|
|
|
|
// string[] files = Directory.GetFiles(@"c:\users\colin\desktop\photos-test\");
|
|
|
|
// string[] files = Directory.GetFiles(@"c:\users\colin\pictures\photos\2023\07\14\");
|
|
|
|
string[] files = Directory.GetFiles(@"G:\DCIM\100EOSR6\"); |
|
|
|
string[] files = Directory.GetFiles(@"c:\users\colin\pictures\photos\2023\07\14\"); |
|
|
|
// string[] files = Directory.GetFiles(@"G:\DCIM\100EOSR6\");
|
|
|
|
// string[] files = Directory.GetFiles(@"C:\Users\colin\Pictures\photos\2018\06\23");
|
|
|
|
// string[] files = Directory.GetFiles(@"C:\Users\colin\Desktop\Germany all\104D7000");
|
|
|
|
// string[] files = Directory.GetFiles(@"C:\Users\colin\Desktop\many-birds\");
|
|
|
@ -617,11 +657,23 @@ public class Game : GameWindow { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
photos.Sort(ComparePhotosByDate); |
|
|
|
|
|
|
|
for (int i = 0; i < 40 && i < photos.Count; i++) { |
|
|
|
await Task.Run( () => { photos[i].Load(); }); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static int ComparePhotosByDate(Photo x, Photo y) { |
|
|
|
int compare = x.DateTimeOriginal.CompareTo(y.DateTimeOriginal); |
|
|
|
if (compare != 0) { |
|
|
|
return compare; |
|
|
|
} |
|
|
|
// If the photos have the same seconds value, sort by filename
|
|
|
|
// (since cameras usually increment the filename for successive shots.)
|
|
|
|
return x.Filename.CompareTo(y.Filename); |
|
|
|
} |
|
|
|
|
|
|
|
protected override void OnUnload() { |
|
|
|
base.OnUnload(); |
|
|
|
} |
|
|
|