I'm building a WPF desktop app to help me organize photos to post to Facebook. Here's my code for creating a copy of a photo at a new location with a caption (EXIF + IPTC + XMP) added:
private void SaveImageAs(string currPath, string newPath, bool setCaption = false, string captionToSet = "")
{
System.IO.FileStream stream = new System.IO.FileStream(currPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
JpegBitmapDecoder decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.None);
BitmapFrame bitmapFrame = decoder.Frames[0];
BitmapMetadata metadata = bitmapFrame.Metadata.Clone() as BitmapMetadata;
stream.Close();
if (setCaption)
{
// if we want to set the caption, do it in EXIF, IPTC, and XMP
metadata.SetQuery("/app1/ifd/{uint=270}", captionToSet);
metadata.SetQuery("/app13/irb/8bimiptc/iptc/Caption", captionToSet);
metadata.SetQuery("/xmp/dc:description/x-default", captionToSet);
}
MemoryStream memstream = new MemoryStream(); // create temp storage in memory
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapFrame, bitmapFrame.Thumbnail, metadata, bitmapFrame.ColorContexts));
encoder.Save(memstream); // save in memory
stream.Close();
stream = new FileStream(newPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
memstream.Seek(0, System.IO.SeekOrigin.Begin); // go to stream start
byte[] bytes = new byte[memstream.Length + 1];
memstream.Read(bytes, 0, (int)memstream.Length);
stream.Write(bytes, 0, bytes.Length);
stream.Close();
memstream.Close();
}
Running that, I get a "COMException was unhandled" exception highlighting this line:
encoder.Save(memstream);
An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in PresentationCore.dll
Additional information: The handle is invalid. (Exception from HRESULT: 0x80070006 (E_HANDLE))
I saw here that this could be due to a threading issue, so instead of directly calling SaveImageAs from the app, I added this, to no effect:
private void _SaveImageAs(string currPath, string newPath, bool setCaption = false, string captionToSet = "")
{
Thread saveThread = new Thread(() => SaveImageAs(currPath, newPath, setCaption, captionToSet));
saveThread.SetApartmentState(ApartmentState.STA);
saveThread.IsBackground = false;
saveThread.Start();
}
I also tried swapping out the MemoryStream for a FileStream creating a local temp file-- that didn't change anything:
FileStream memstream = new FileStream(System.IO.Path.GetDirectoryName(newPath) + @"\" + "temp.jpg", System.IO.FileMode.OpenOrCreate);
Any ideas?
There are a few things wrong in your code.
The source stream has to be kept open until the BitmapFrame is written to the target stream.
Image metadata of a BitmapFrame from a BitmapDecoder is read-only. You have to create a new BitmapFrame from the original one when you want to modify metadata.
The third query seems to be broken. The exception says "Property cannot be found".
This code works for me:
public static void SaveImageAs(string sourcePath, string targetPath, string caption)
{
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var decoder = new JpegBitmapDecoder(sourceStream, BitmapCreateOptions.None, BitmapCacheOption.None);
var frame = decoder.Frames[0];
if (!string.IsNullOrWhiteSpace(caption))
{
frame = BitmapFrame.Create(frame);
var metadata = (BitmapMetadata)frame.Metadata;
metadata.SetQuery("/app1/ifd/{uint=270}", caption);
metadata.SetQuery("/app13/irb/8bimiptc/iptc/Caption", caption);
//metadata.SetQuery("/xmp/dc:description/x-default", caption);
}
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(frame);
using (var targetStream = new FileStream(targetPath, FileMode.Create))
{
encoder.Save(targetStream);
}
}
}
You got the exception because you closed the stream used for loading the original jpeg. Comment the first stream.Close() (just above if(setCaption)) and it will work. Just like when working with a Image instance and you must keep the stream open for the lifetime of the Image.
User contributions licensed under CC BY-SA 3.0