get BitmapMetadata in WPF

Tagging is starting to infiltrate all of our media. It makes organizing and using it easier, and is a valuable feature to support.

 

Reading metadata from an image is fairly simple, once you find the pieces. Vista uses the property Keywords to store tags, so we’ll use that as well. The BitmapMetadata object holds quite a few properties for common metadata, such as Author, Rating, and Date Taken.

public string[] GetTags(string filename)
{
    // open a filestream for the file we wish to look at
    using (Stream fs = File.Open(filename, FileMode.Open, FileAccess.ReadWrite))
    {
        // create a decoder to parse the file
        BitmapDecoder decoder = BitmapDecoder.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.Default);
        // grab the bitmap frame, which contains the metadata
        BitmapFrame frame = decoder.Frames[0];
        // get the metadata as BitmapMetadata
        BitmapMetadata metadata = frame.Metadata as BitmapMetadata;
        // close the stream before returning
        fs.Close();
 
        // return a null array if keywords don't exist.  otherwise, return a string array
        if (metadata != null && metadata.Keywords != null)
            return metadata.Keywords.ToArray();
        else
            return null;
    }
}

If there is another metadata property you’d like to access, use the GetQuery method instead:

public string[] GetTags(string filename)
{
    // open a filestream for the file we wish to look at
    using (Stream fs = File.Open(filename, FileMode.Open, FileAccess.ReadWrite))
    {
        // create a decoder to parse the file
        BitmapDecoder decoder = BitmapDecoder.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.Default);
        // grab the bitmap frame, which contains the metadata
        BitmapFrame frame = decoder.Frames[0];
        // get the metadata as BitmapMetadata
        BitmapMetadata metadata = frame.Metadata as BitmapMetadata;
        // close the stream before returning
        fs.Close();
        // System.Keywords is the same as using the above method.  this particular metadata property
        // will return an array of strings, though we still have to cast it as such
        string[] tags = metadata.GetQuery("System.Keywords") as string[];
        return tags;
    }
}

There is plenty that can go wrong here – the file could not exist, it could be in use, or not be an image at all. Be sure to handle the various exceptions accordingly.

Writing tags can actually get a little hairy, because space for metadata isn’t necessarily alotted to every file. In actuality, it isn’t there unless an application (which, notably, will be yours after you implement this) makes some room for it. When writing tags, you still need to get to the associated BitmapMetadata object. Where the method departs from reading is instantiating an InPlaceBitmapMetadataWriter object. This is what we use to write the data (assuming there is space for it). If that fails, then we address the issue of adding space for the metadata.

public static void AddTags(string filename, string[] tags)
{
    // open a filestream for the file we wish to look at
    using (Stream fs = File.Open(filename, FileMode.Open, FileAccess.ReadWrite))
    {
        // create a decoder to parse the file
        BitmapDecoder decoder = BitmapDecoder.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.Default);
        // grab the bitmap frame, which contains the metadata
        BitmapFrame frame = decoder.Frames[0];
        // get the metadata as BitmapMetadata
        BitmapMetadata metadata = frame.Metadata as BitmapMetadata;
        // instantiate InPlaceBitmapMetadataWriter to write the metadata to the file
        InPlaceBitmapMetadataWriter writer = frame.CreateInPlaceBitmapMetadataWriter();
        string[] keys;
        if (metadata.Keywords != null)      // tags exist - include them when saving
        {
            // build the complete list of tags - new and old
            keys = new string[metadata.Keywords.Count + tags.Length];
            int i = 0;
            foreach (string keyword in metadata.Keywords)
            {
                keys[i] = keyword;
                i++;
            }
            foreach (string tag in tags)
            {
                keys[i] = tag;
                i++;
            }
 
            // associate the tags with the writer
            // the type of variable to pass (here, an array of strings) depends on
            // which metadata property you are using.  Since we are modifying the
            // Keywords property, we use the array.  If you use the author property,
            // it will simply be a string.
            writer.SetQuery("System.Keywords", keys);
        }
        else     // no old tags - just use the new ones
        {
            keys = tags;
            // associate the tags with the writer
            // the type of variable to pass (here, an array of strings) depends on
            // which metadata property you are using.  Since we are modifying the
            // Keywords property, we use the array.  If you use the author property,
            // it will simply be a string.
            writer.SetQuery("System.Keywords", tags);
        }
 
        // try to save the metadata to the file
        if (!writer.TrySave())
        {
            // if it fails, there is no room for the metadata to be written to.
            // we must add room to the file using SetUpMetadataOnImage (defined below)
            SetUpMetadataOnImage(filename, keys);
        }
    }
}

So, if we have space allotted for metadata in the file, this will work great. Again, be sure to add exception handling.

What if we need to add room for the metadata? Well, this requires creating a new file, which will overwrite the old one. Essentially, we open it up, copy the image data from the old file, add whatever metadata we want to add initially, and save the file. This is a lossless transcoding, so no quality is lost. This does mean that the file size grows, however.

private static void SetUpMetadataOnImage(string filename, string[] tags)
{
    // padding amount, using 2Kb.  don't need much here; metadata is rather small
    uint paddingAmount = 2048;
 
    // open image file to read
    using (Stream file = File.Open(filename, FileMode.Open, FileAccess.Read))
    {
        // create the decoder for the original file.  The BitmapCreateOptions and BitmapCacheOption denote
        // a lossless transocde.  We want to preserve the pixels and cache it on load.  Otherwise, we will lose
        // quality or even not have the file ready when we save, resulting in 0b of data written
        BitmapDecoder original = BitmapDecoder.Create(file, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
        // create an encoder for the output file
        JpegBitmapEncoder output = new JpegBitmapEncoder();
 
        // add padding and tags to the file, as well as clone the data to another object
        if (original.Frames[0] != null && original.Frames[0].Metadata != null)
        {
            // Because the file is in use, the BitmapMetadata object is frozen.
            // So, we clone the object and add in the padding.
            BitmapFrame frameCopy = (BitmapFrame)original.Frames[0].Clone();
            BitmapMetadata metadata = original.Frames[0].Metadata.Clone() as BitmapMetadata;
 
            // we use the same method described in AddTags() as saving tags to save an amount of padding
            metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", paddingAmount);
            metadata.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", paddingAmount);
            metadata.SetQuery("/xmp/PaddingSchema:Padding", paddingAmount);
            // we add the tags we want as well.  Again, using the same method described above
            metadata.SetQuery("System.Keywords", tags);
 
            // finally, we create a new frame that has all of this new metadata, along with the data that was in the original message
            output.Frames.Add(BitmapFrame.Create(frameCopy, frameCopy.Thumbnail, metadata, frameCopy.ColorContexts));original.Frames[0].ColorContexts));
            file.Close();  // close the file to ready for overwrite
        }
        // finally, save the new file over the old file
        using (Stream outputFile = File.Open(filename, FileMode.Create, FileAccess.Write))
        {
            output.Save(outputFile);
        }
    }
原文link:http://blog.andreweichacker.com/2009/02/reading-and-writing-tags-for-photos-in-wpf/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值