Skip to content

Commit 0076c78

Browse files
committed
Fix image rotation handling.
Save the rotated image when saving. Read the appropriate orientation IFD for raw images. Clear metadata more correctly.
1 parent 845ecd9 commit 0076c78

File tree

2 files changed

+82
-36
lines changed

2 files changed

+82
-36
lines changed

PhotoTagger.Imaging/Exif.cs

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Text;
77
using System.Threading.Tasks;
8+
using System.Windows.Media;
89
using System.Windows.Media.Imaging;
910

1011
namespace PhotoTagger.Imaging {
@@ -35,7 +36,8 @@ private static string fromUniversalNewline(string from) {
3536
const string XmpDescriptionQuery = "/xmp/dc:description";
3637
const string DateTakenQuery = "/app1/ifd/exif/{ushort=36867}";
3738
const string DateTakenSubsecQuery = "/app1/ifd/exif/{ushort=37521}";
38-
const string OrientationQuery = "/app1/ifd/{ushort=274}";
39+
const string JpegOrientationQuery = "/app1/ifd/{ushort=274}";
40+
const string RawOrientationQuery = "/ifd/{ushort=274}";
3941

4042
const string LatitudeRefQuery = "/app1/ifd/gps/subifd:{ulong=1}";
4143
const string LatitudeQuery = "/app1/ifd/gps/subifd:{ulong=2}";
@@ -45,6 +47,32 @@ private static string fromUniversalNewline(string from) {
4547
const string PaddingQuery = "/app1/ifd/PaddingSchema:Padding";
4648
const string ExifPaddingQuery = "/app1/ifd/exif/PaddingSchema:Padding";
4749
const string XmpPaddingQuery = "/xmp/PaddingSchema:Padding";
50+
const string ColorSpaceQuery = "/app1/{ushort=0}/{ushort=34665}/{ushort=40961}";
51+
52+
// From the System.Title Photo Metadata Policy
53+
readonly static string[] TitleReadQueries = {
54+
WinTitleQuery,
55+
"/xmp/<xmpalt>dc:title",
56+
XmpTitleQuery,
57+
"/app1/ifd/exif/{ushort=37510}",
58+
TitleQuery,
59+
"/app13/irb/8bimiptc/iptc/caption",
60+
"/xmp/<xmpalt>dc:description",
61+
XmpDescriptionQuery,
62+
"/app13/irb/8bimiptc/iptc/caption",
63+
"/xmp/<xmpalt>exif:UserComment",
64+
};
65+
66+
// From the System.Title Photo Metadata Policy
67+
readonly static string[] TitleRemoveQueries = {
68+
WinTitleQuery,
69+
XmpTitleQuery,
70+
"/app1/ifd/exif/{ushort=37510}",
71+
"/xmp/<xmpalt>exif:UserComment",
72+
TitleQuery,
73+
"/app13/irb/8bimiptc/iptc/caption",
74+
XmpDescriptionQuery,
75+
};
4876

4977
#endregion
5078

@@ -82,22 +110,14 @@ private static string readString(BitmapMetadata metadata, string key) {
82110
}
83111

84112
private static string readTitle(BitmapMetadata metadata) {
85-
var xmpTitle = readString(metadata, XmpDescriptionQuery) as string ??
86-
readString(metadata, XmpTitleQuery) as string;
87-
if (!string.IsNullOrWhiteSpace(xmpTitle)) {
88-
return fromUniversalNewline(xmpTitle).Trim();
89-
}
90-
var exifTitle = readString(metadata, TitleQuery);
91-
if (!string.IsNullOrWhiteSpace(exifTitle)) {
92-
return fromUniversalNewline(exifTitle).Trim();
93-
}
94-
xmpTitle = readString(metadata, WinTitleQuery);
95-
if (!string.IsNullOrWhiteSpace(xmpTitle)) {
96-
// Remove trailing null.
97-
return fromUniversalNewline(xmpTitle
98-
.Substring(0, xmpTitle.Length - 1)).Trim();
99-
}
100-
return xmpTitle;
113+
foreach (var query in TitleReadQueries) {
114+
var v = readString(metadata, query);
115+
if (!string.IsNullOrWhiteSpace(v)) {
116+
return fromUniversalNewline(v)
117+
.TrimEnd('\0').Trim();
118+
}
119+
}
120+
return string.Empty;
101121
}
102122

103123
private static string readAuthor(BitmapMetadata metadata) {
@@ -159,7 +179,14 @@ public static Rotation OrienationToRotation(short orienation) {
159179

160180
private static short getOrientation(BitmapMetadata metadata) {
161181
try {
162-
var orientationProp = metadata.GetQuery(OrientationQuery) as ushort?;
182+
ushort? orientationProp;
183+
if (metadata.ContainsQuery(JpegOrientationQuery)) {
184+
orientationProp = metadata.GetQuery(JpegOrientationQuery) as ushort?;
185+
} else if (metadata.ContainsQuery(RawOrientationQuery)) {
186+
orientationProp = metadata.GetQuery(RawOrientationQuery) as ushort?;
187+
} else {
188+
return 1;
189+
}
163190
if (orientationProp.HasValue) {
164191
return (short)orientationProp.Value;
165192
}
@@ -180,39 +207,41 @@ await photo.Dispatcher.InvokeAsync(() => {
180207
source.Author = photo.Photographer;
181208
source.DateTaken = photo.DateTaken;
182209
source.Location = photo.Location;
210+
source.Orientation = 1;
183211
});
184-
int pad = 0;
185-
if (source.Title != null) {
212+
if (!string.IsNullOrWhiteSpace(source.Title)) {
186213
var title = toUniversalNewline(source.Title.Trim());
187214
var bytes = Encoding.UTF8.GetBytes(title);
188215
dest.SetQuery(TitleQuery, Encoding.Default.GetString(bytes));
189216
var utf16bytes = Encoding.Unicode.GetBytes(title + '\0');
190217
dest.SetQuery(WinTitleQuery, utf16bytes);
191218
dest.Title = title;
192-
pad += bytes.Length * 4 + utf16bytes.Length + 16;
219+
} else {
220+
dest.Title = string.Empty;
221+
foreach (var query in TitleRemoveQueries) {
222+
dest.RemoveQuery(query);
223+
}
193224
}
194-
if (source.Author != null) {
225+
if (!string.IsNullOrWhiteSpace(source.Author)) {
195226
var bytes = Encoding.UTF8.GetBytes(source.Author);
196227
dest.Author = new ReadOnlyCollection<string>(new string[] {
197228
Encoding.Default.GetString(bytes)
198229
});
199-
pad += bytes.Length + 16;
230+
} else {
231+
dest.Author = null;
200232
}
201233
if (source.DateTaken.HasValue) {
202234
var bytes = Encoding.ASCII.GetBytes(
203235
source.DateTaken.Value.ToString(ExifDateFormat, CultureInfo.InvariantCulture));
204236
dest.SetQuery(DateTakenQuery, Encoding.Default.GetString(bytes));
205-
pad += bytes.Length + 16;
237+
} else {
238+
dest.RemoveQuery(DateTakenQuery);
239+
dest.RemoveQuery(DateTakenSubsecQuery);
206240
}
207241
if (source.Location != null) {
208242
setLocation(dest, source.Location);
209-
pad += 404;
210-
}
211-
if (pad != 0) {
212-
uint padding = (uint)pad + 256u;
213-
dest.SetQuery(PaddingQuery, padding);
214-
dest.SetQuery(ExifPaddingQuery, padding);
215-
dest.SetQuery(XmpPaddingQuery, padding);
243+
} else {
244+
clearLocation(dest);
216245
}
217246
return source;
218247
}
@@ -230,6 +259,21 @@ private static void setLocation(BitmapMetadata dest, GpsLocation loc) {
230259
dest.SetQuery(LongitudeQuery, bytesToLongs(loc.LonBytes).ToArray());
231260
}
232261

262+
private static void clearLocation(BitmapMetadata dest) {
263+
dest.RemoveQuery(LatitudeRefQuery);
264+
dest.RemoveQuery(LatitudeQuery);
265+
dest.RemoveQuery(LongitudeRefQuery);
266+
dest.RemoveQuery(LongitudeQuery);
267+
}
268+
269+
internal static Rotation SaveRotation(BitmapMetadata md) {
270+
var rotation = OrienationToRotation(getOrientation(md));
271+
if (rotation != Rotation.Rotate0) {
272+
md.SetQuery(JpegOrientationQuery, (short)1);
273+
}
274+
return rotation;
275+
}
276+
233277
#endregion
234278
}
235279
}

PhotoTagger.Imaging/ImageLoadManager.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
using System.Windows;
9+
using System.Windows.Media;
910
using System.Windows.Media.Imaging;
1011
using System.Windows.Threading;
1112

@@ -324,9 +325,7 @@ await photo.Dispatcher.InvokeAsync(() => {
324325
using (var stream = new UnsafeMemoryMapStream(
325326
mmap.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read),
326327
FileAccess.Read)) {
327-
BitmapFrame sourceFrame;
328-
Guid format;
329-
(sourceFrame, format) = loadFrame(stream.Stream);
328+
(var sourceFrame, var format) = loadFrame(stream.Stream);
330329
var md = sourceFrame.Metadata.Clone() as BitmapMetadata;
331330
Photo.Metadata newMetadata = await Exif.SetMetadata(photo, md);
332331
newMetadata.Width = sourceFrame.PixelWidth;
@@ -336,10 +335,13 @@ await photo.Dispatcher.InvokeAsync(() => {
336335
BitmapEncoder.Create(format);
337336
encoder.Frames.Add(
338337
BitmapFrame.Create(
339-
sourceFrame.Clone() as BitmapFrame,
340-
sourceFrame.Thumbnail,
338+
sourceFrame,
339+
null,
341340
md,
342341
sourceFrame.ColorContexts));
342+
if (encoder is JpegBitmapEncoder jpg) {
343+
jpg.Rotation = Exif.SaveRotation(md);
344+
}
343345
sourceFrame = null;
344346
using (var outFile = new FileStream(destFile, FileMode.CreateNew)) {
345347
encoder.Save(outFile);

0 commit comments

Comments
 (0)