30 thoughts on “Reading and Writing Tags for Photos in WPF

  1. Pingback: TagFlo » Updates: » C#, WPF and Bitmap Metadata

  2. Hi,

    I just made some tests with your function SetUpMetadataOnImage() and I found out that it is NOT lossless, i. e. the jpeg seems to be encoded new.
    I compared the jpegs “before” and “after” with Beyond Compare 3 (there is a picture and also a hex compare mode, you can test it for 30 days).

    So why do you think this operation is lossless and (if you agree to me) do you have an idea how to add tags really lossless?

    Thanks and regards
    Thomas

  3. Hey Thomas,

    You’re right, it does incur some loss. After taking a look, I was able to verify that changing the BitmapCacheOption to BitmapCacheOption,None when creating the BitmapDecoder appears to fix this problem. Let me know if you find any other problems with this approach.

    Thanks!
    Andrew

  4. I’ve been struggling with your SetUpMetadataOnImage() function for several days now. In every experiment I tried, the last statement of the function (the output.Save) causes an exception, “The image data generated and overflow during processing.” I have no idea what this means and Googling didn’t come up with anything. The same exception occurs if you simplify the BitmapFrame.Create to just the first parameter. This is very unfortunate for me, because your post is the only one I’ve been able to find that claims to have solved the problem of adding metadata to a image file in WPF, and it simply does not work.

  5. I did some more experimenting and when I changed the BitmapCacheOption to OnLoad, the exception went away and I was able to create the new file with the added metadata. This makes me very happy, but I’m concerned about WikkaWikka’s discovery. My bitmap got 20% smaller using OnLoad. But I get that inexplicable exception when I set the option to None. I don’t understand why you don’t get the exception.

  6. I found the solution where BitmapCacheOption.None can still be used and the “overflow” exception does not happen. You have to Clone() the original.Frames[0] before you add them to the encoder Frames. Also I don’t get the point of specifying the IgnoreColorProfile BitmapCreationOptions and I have dropped it in my code. By specifying IgnoreColorProfile, aren’t you throwing away the color profile in your final image?

  7. Hey Frank,

    That is interesting that you have been having exceptions when not cloning the original.Frames[0] – I haven’t had any problems with that. I’ll implement that in my solution to be sure there isn’t some corner case where that occurs that I haven’t witnessed yet. Thanks for updating me with your solution.

    You’re right about using IgnoreColorProfile. This post includes some bits I’ve picked up from others, and that’s one line that I didn’t give much scrutiny. I’ll remove that for the benefit of others happening upon this post.

    Thanks, Frank!

  8. Hi, Andrew.

    My problem is worse than original.Frames[0] not working. I’ve been struggling with this for weeks now and I finally got part way to the root of the problem, why it works sometimes and not others. It fails 100% of the time if the BitmapDecoder.Save() function is executed inside a worker thread and succeeds 100% of the time if it is executed inside the main application thread. But I have been unable to find out why. My problem is, I need to be in a worker thread so I can show a progress bar, since I may have hundreds to thousands of images whose metadata I want to change (e.g. the copyright notice).

  9. Hi Frank,

    Things are a bit busy right now for me, so I don’t have the time to look into the reason behind it not working off of the UI thread; however using the Dispatcher.BeginInvoke() or Dispatcher.Invoke() methods (as used in this post,though I never did start my more detailed threading post) may be able to help work around the issue. You can run your logic for updating the user in the separate thread, but call the UI thread with one of those methods to execute this code to ensure its success. So it would be something like this pseudocode:

    // run in separate thread
    methodforupdatinguserwhilewritingmetadata()
    {
    // do stuff in separate thread you need to do
    Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
    (ThreadStart)delegate ()
    {
    // run setup metadata
    });
    }

    Hope that helps.

    Andrew

  10. Thanks for this example! The MS docs on this are horrible beyond belief. I would never have gotten my metadata writing working without your example.

  11. hello,
    first – thank you very much

    this seem exactly like what I need and cannot find anywhere else

    unfortunately.. the method SetUpMetadataOnImage throws an System.OverflowException 🙁 it is caused by the output.Save(outputFile);
    any ideas what can be wrong?

  12. Hi urza,

    No problem – I had a lot of trouble finding information as well, which is why I was sure to write up a post regarding it.

    Frank was having a problem with OverflowException on that same line. Here’s something he discovered:

    I found the solution where BitmapCacheOption.None can still be used and the “overflow” exception does not happen. You have to Clone() the original.Frames[0] before you add them to the encoder Frames.

    Let me know if this doesn’t do it for you.

  13. It seems to work now with the cloning.

    Would you know what string do I need to pass to SetQuery if I would also like to change the image title and image description in metadata?

  14. I am here again :/

    I still have the owerflow exception. Even with the cloning. It only works with pictures that allready have some metadata. Thats why I tought it works – I tried it on pictures in windows vista MyPictures/Samples – all files there have ratings or other metadata.. but when I download photo or picture from internet. It has no metadata and the code throws up the exception. It also destroys the file. The file has size 0 B and is not readable.

    any ideas? why is it only working with files that allready have metadata?

  15. Hey urza,

    Are you able to write the new image to a separate file? That is, replace:

    // finally, save the new file over the old file
    using (Stream outputFile = File.Open(filename, FileMode.Create, FileAccess.Write))
    {
    output.Save(outputFile);
    }

    with:
    // finally, save the new file over the old file
    using (Stream outputFile = File.Open(filename + “.tmp”, FileMode.Create, FileAccess.Write))
    {
    output.Save(outputFile);
    }

    If so, a workaround might be to save it to a temporary file (as shown) and override the original file afterwords. This way, you can check the newly created file before overriding the original and causing data loss.

    Andrew

  16. I was having the same issues with the encoder.Save throwing an exception, but that went away when I marked the program to use STAThread.
    Also, I was having the issue of having the new file being smaller, and used the advice of marking the cache options to None. But I was wondering, I couldn’t see any differnce between the smaller and the larger file. Does someone know what I might have been missing?

    • Likely, the difference wasn’t great enough to be seen by the human eye (or, at the least, an untrained eye). If you do a bitwise comparison, you’ll find that the two files are not equal; however, the loss isn’t entirely great. While that is so, it is definitely better to avoid loss at all.

  17. // finally, save the new file over the old file
    using (Stream outputFile = File.Open(filename, FileMode.Create, FileAccess.Write))
    {
    output.Save(outputFile);
    }

    On the line output.Save(outputFile);
    I’m getting the error ‘Access is Denied’

    Any idea what this means?

  18. Sounds like the outputFile is currently in use, or the location is not accessible to the current user.

    The outputFile might still be in use from this application, as the stream you opened for it earlier might not quite be closed. If that’s the case, you could go the route that I suggested above for urza, where you save a temporary file and copy it over. Alternatively, you could create a loop that waits for the file to be accessible, but be sure to have a cap for that in case it never becomes accessible.

    If it is a permissions problem, you’ll have to require that your application be ran with administrator privileges, or notify the user that the file they are trying to modify is not accessible by them (I’d go with the latter).

  19. Pingback: Scott Hanselman - Dealing with Images with Bad Metadata - Corrupted Color Profiles in WPF

  20. Pingback: Dealing with Images with Bad Metadata – Corrupted Color Profiles in WPF | Programming

  21. Hi Andrew,

    Great article and really nice code…but does this work for .tiff format as well ? I tried the code on a 16 bit .tiff file and at the code where the output file is saved, I got an Overflow Exception.

    I wonder if this has something to do with the padding value or is it the tiff format.

    metadata.SetQuery(“/app1/ifd/PaddingSchema:Padding”, paddingAmount);
    metadata.SetQuery(“/app1/ifd/exif/PaddingSchema:Padding”, paddingAmount);
    metadata.SetQuery(“/xmp/PaddingSchema:Padding”, paddingAmount);

    Can I use the above code for .tiff format as well ?

  22. Wow !!! I finally got this thing to work….thanks to Andrew and all the previous commenters.

    To all the guys above who found that the file size got smaller, I found the solution….

    Just set the encoder compression to None…..for example, in my case I was using TiffBitmapEncoder.

    All I had to do was set TiffCompressOption.None as the setting. That fixed it !!

  23. To help future readers, I’d like to point out some intricacies to remember when dealing with the WPF metadata writer:

    1. Make sure that your method that deals with the metadata is invoked on an STA apartment thread. Otherwise, you get some nasty COM exceptions. You can check if your method is running on STA by using the Thread.CurrentThread.GetApartmentState() call.

    2. If you find that the file is getting smaller after writing metadata, just make sure that your Encoder compression setting is “None”. (See my previous comment).

    3. If you find it hard to read an image file and write metadata to it, do it the simple way. Create a temporary file that is a copy of the original image. Load the image decoder using the temporary file and after filling in the metadata, write the encoder output to the original file. Then, you can delete the temporary file.

    Hope this helps !

  24. In the SetUpMetadataOnImage sub you have the line:

    output.Frames.Add(BitmapFrame.Create(frameCopy, frameCopy.Thumbnail, metadata, frameCopy.ColorContexts));original.Frames[0].ColorContexts));

    which does not seem to be correct. Can you give me the correct way to write that line?

Comments are closed.