Working with image metadata in WPF

    I recently decided to become familiar with the .NET platform, the C # language, and the Windows Presentation Foundation.
    In the process of studying (and I always study languages ​​and technologies in the process of developing a pilot project), I met quite a few pitfalls and subtle points. Everyone would like to share it with the habrasociety (I believe that many beginning WPF developers would like to), but the volume of the resulting habratopik would be too large, so I decided to start with image metadata, information on this topic, even on the English-speaking Internet is not enough.

    In general, images of various formats may have metadata, however, I will talk about JPEG as an example, as worked with him. I think for other formats the difference will be small.

    Types of Metadata

    First, let's see what types of metadata can be in the image. Everyone probably knows this, but just in case, I’ll tell you:
    • EXIF (Exchangeable Image File Format) is a standard for storing metadata in an image, which is used by digital cameras to store information about shutter speed, aperture, and other shooting parameters. EXIF metadata can be stored in JPEG, TIFF, and RIFF WAV files. According to the standard, only description (description tag) and comment (user comment tag) can be stored in EXIF ​​from user descriptive metadata, but Windows Explorer also uses several additional tags (XPTitle, XPSubject, XPAuthor, XPComment, XPKeywords). Windows Explorer ignores the XPTitle tag with the standard Description tag.
    • IPTC (International Press Telecommunications Council) - rather the name of the organization that developed the standard. The metadata standard itself is called IIM (Information Interchange Model). The oldest of the standards described. In the initial version of the standard, metadata was stored so that software that was not aware of the existence of IPTC could not work with image files that contained such metadata. However, Adobe later expanded the standard by transferring metadata to the APP13 block of the JPEG file, which allowed software that did not know the standard to successfully read the JPEG file, ignoring unknown metadata. Descriptive fields such as ObjectName (title), Keywords (keywords), Caption (description, several tag variations) can be stored in IPTC metadata.
    • XMP (eXtensible Metadata Platform) is a standard developed by Adobe. Metadata is stored in the RDF model, presented in XML format, allowing you to include any necessary information in the image file. It is this format that prefers to use WIC (Windows Imaging Component) in Windows Vista / 7.

    WPF Metadata Principles

    To work with metadata in WPF, the classes BitmapEncoder, BitmapDecoder, BitmapSource, BitmapFrame, BitmapMetadata, InPlaceMetadataWriter are used.
    The BitmapEncoder and BitmapDecoder classes have descendants that allow you to work with specific image formats. In my case, JpegBitmapEncoder and JpegBitmapDecoder.
    The InPlaceMetadataWriter class is used to modify metadata directly in place, without transcoding the file.
    Data can be read and written in two ways - either using the GetQuery / SetQuery functions that operate on hierarchical metadata tag names, or using the fields of the BitmapMetadata class, which make it easy to access metadata.
    When accessing metadata through fields of the BitmapMetadata class, WIC tries to find the corresponding fields in the metadata of different standards in the following order: first XMP, then IPTC and EXIF. When writing tags through the fields of the BitmapMetadata class, WIC writes them in XMP format.

    Reading metadata

    Here is a ready-made example of a function with which you can read metadata from a file:

    1. FileStream f = File.Open("test.jpg", FileMode.Open);
    2. BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default);
    3. BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0].Metadata;
    4. // Получаем заголовок через поле класса
    5. string title = metadata.Title;
    6. // Получаем заголовок из XMP
    7. string xmptitle = (string)metadata.GetQuery(@"/xmp/dc:title");
    8. // Получаем заголовок из EXIF
    9. string exiftitle = (string)metadata.GetQuery(@"/app1/ifd/{ushort=40091}");
    10. // Получаем заголовок из IPTC
    11. string iptctitle = (string)metadata.GetQuery(@"/app13/irb/8bimiptc/iptc/object name");

    Everything here is quite simple and transparent, so immediately go to the recording.

    Recording Metadata

    1. BitmapMetadata md = new BitmapMetadata("jpg");
    2. md.SetQuery(@"/xmp/dc:title", xmptitle);
    3. md.SetQuery(@"/app1/ifd/{ushort=40091}", exiftitle);
    4. md.SetQuery(@"/app13/irb/8bimiptc/iptc/object name", iptctitle);
    5. BitmapFrame frame = BitmapFrame.Create(decoder.Frames[ 0], decoder.Frames[ 0].Thumbnail, md, decoder.Frames[ 0].ColorContexts);
    6. BitmapEncoder encoder = new JpegBitmapEncoder();
    7. encoder.Frames.Add(frame);
    8. FileStream of = File.Open("test2.jpg", FileMode.Create, FileAccess.Write);
    9. encoder.Save(of);
    10. of.Close();

    The code goes as a continuation of a fragment reading metadata. We create a copy of the original file by writing a title to its metadata in all three metadata formats.

    On-site metadata editing

    Until now, I have been telling in general quite well-documented and simple things, but here everything is already more complicated. The example in the official documentation (MSDN) is incorrect and generally opposite in meaning to the real state of things.
    To edit in-place metadata, you must create an object of the InPlaceBitmapMetadataWriter class:

    1. InPlaceBitmapMetadataWriter writer;
    2. writer = decoder.Frames[ 0].CreateInPlaceBitmapMetadataWriter();

    After that, you can work with it, as with the usual BitmapMetadata, calling SetQuery to set the necessary metadata.
    To save changes, you need to call the TrySave () method, trying to save the changes to the original stream. An attempt to record may or may not be successful. If successful, the method returns true, if not, false.
    The most common mistake that can prevent you from writing changes is that there is not enough free space in the metadata. As a rule, all freshly shot photos do not contain enough space in the metadata, therefore, in order to start using the editing of metadata in place, you should once make a copy of the file, adding metadata in it with special padding fields, leaving free space for subsequent changes. To do this, the file is opened, the desired frame and its metadata are cloned, and several requests are executed:

    1. BitmapFrame frame = (BitmapFrame)decoder.Frames[ 0].Clone();
    2. BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0].Metadata.Clone();
    3. metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", 2048);
    4. metadata.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", 2048);
    5. metadata.SetQuery("/xmp/PaddingSchema:Padding", 2048);
    6. BitmapFrame newframe = BitmapFrame.Create(frame, frame.Thumbnail, metadata, original.Frames[ 0].ColorContexts);

    After that, it is enough to encode the frame with the encoder and write it to the desired stream, as a result of which free space will appear in the image for editing metadata in place later.
    A padding value of 2048 bytes is usually sufficient. If you need more, you can specify a larger value.

    Query strings

    I think everyone who studies SetQuery / GetQuery methods has a reasonable question - where to get all these query strings that you can't call simple and intuitive?
    After extensive searches on MSDN, a list was found . There are probably all the necessary requests. Missing ones can, in principle, be compiled by analogy, there are plenty of examples :)

    Subtleties and pitfalls

    • WIC versions in Windows XP and Windows Vista may fail if the thread calling JpegBitmapEncoder.Save () does not have the STAThread attribute (by default, all threads created in the application receive the MTAThread attribute, unless otherwise specified).
    • The WIC version in Windows 7 stores the default EXIF ​​UserComment tag in Unicode, while in Windows XP and Windows Vista it is encoded in the current system language (CP1251 for Russian). The format for writing UTF-8 parameters is this: the tag value itself is saved not as a string, but as an array of bytes. The first 7 bytes is the ASCII string "UNICODE", after which a Unicode-encoded sequence of tag characters begins.
    • The BitmapCacheOptions parameter should be treated carefully. The OnLoad value caches all image data in uncompressed form in RAM, so if you open 20 large-format JPEGs with this option, the free memory will be eaten very quickly. This memory is not freed up when deleting the image classes themselves (BitmapFrame, BitmapDecoder, etc.) and processing them by the garbage collector. In addition, to use InPlaceBitmapMetadataWriter, you must open an image with BitmapCacheOptions = OnDemand or Default.
    • In the example, I open the image with the IgnoreColorProfile flag, because without it, BitmapDecoder throws an exception on some images.


    In general, working with metadata using WPF seemed rather complicated and confusing to me. Almost all of the pitfalls described cost me several hours of debugging and googling, there is no information about this anywhere, and the symptoms are sometimes very strange. Official documentation (MSDN) does not cover this question well, and in some places it is completely incorrect.
    I hope that this collected information will help those who need to work with metadata through WPF and save them some hours of time :)

    PS I will be glad to see comments (if I made a mistake somewhere) and descriptions of pitfalls that I have not met or forgot to mention.

    PPS Should I continue to write about WPF, or am I writing long-known things?

    Also popular now: