简体   繁体   English

如何使用元数据创建PNG文件

[英]How to create a png file with metadata

I am trying to create a PNG file to persist image data we capture, but am have a real pain trying to set the metadata like company, CameraManufacturer, Brightness etc. etc. 我正在尝试创建一个PNG文件以保留我们捕获的图像数据,但是尝试设置公司,CameraManufacturer,Brightness等等元数据确实很痛苦。

The Google PNGCS library will do it but I have to write the file first then re load it and re save it. Google PNGCS库将执行此操作,但是我必须先编写文件,然后重新加载并重新保存它。

We are using WPF so I would have thought I could get the more generic classes like BitmapMetadata , PngBitmapEncoder , JpegBitmapEncoder to work. 我们正在使用WPF,所以我以为我可以让BitmapMetadataPngBitmapEncoderJpegBitmapEncoder等更通用的类正常工作。

However I keep coming up against the threw an exception of type "System.NotSupportedException" for the attributes I want to use. 但是,我一直想对我要使用的属性threw an exception of type "System.NotSupportedException" You can see this in the variable watch I put at the bottom of this post at the metadata created. 您可以在我在帖子底部创建的元数据中的变量watch看到这一点。

The Image class is really a data struct holding width height, file type like png or gif etc. Image类实际上是一个数据结构,包含宽度高度,文件类型(如png或gif等)。

It should be straight forward to get the image data from the camera, add the tags we want and save it to file. 应该可以直接从相机获取图像数据,添加我们想要的标签并将其保存到文件中。

Same with the load - we should be able to get them. 与负载相同-我们应该能够得到它们。

Code: 码:

/// <summary>
/// Handles the load, save, and export of images
/// </summary>
public interface IImageProvider
{
    string GetPath(string fileNameWithoutExtension, ImageVersion version);
    string GetPath(Plate plate);
    Image Load(string path);
    Image Load(string path, int width);
    Image Load(Plate plate);
    Image LoadThumb(Plate plate);
    Task<Image> LoadAsync(Plate plate);
    Task<Image> LoadAsync(Plate plate, ImageVersion version);
    void Save(Image image, string path, Resolution resolution);
    void Save(Image image, string name, ImageCategories category, Resolution resolution);
    void Save(Plate plate, Image image, Resolution resolution);
    Task SaveAsync(Image image, string path, Resolution resolution);
    void Export(Image image, string name, ExportFormats format, string directory, Resolution resolution);
    void ExportAsync(Image image, string path, ExportFormats format, string directory, double increment, Resolution resolution);
    Task<Image> Import(string fileName, Project project);
    void Delete(Plate plate);
    Image Load(Plate plate, IStage stage);
}

class ImageProvider
{
    private void Save(Image image, string name, ExportFormats format, string directory, Resolution resolution)
    {
        BitmapEncoder bitmapEncoder = null;

        //Determine the type of the export
        switch (format)
        {
            case ExportFormats.bmp:
                bitmapEncoder = new BmpBitmapEncoder();
                break;
            case ExportFormats.jpg:
                bitmapEncoder = new JpegBitmapEncoder();
                break;
            case ExportFormats.png:
                bitmapEncoder = new PngBitmapEncoder();
                break;
            case ExportFormats.tiff:
                bitmapEncoder = new TiffBitmapEncoder();
                break;
        }

        if (bitmapEncoder != null)
        {
            var source = ResizeToResolutionUniform(image, resolution);
           ReadOnlyCollection<ColorContext> colorContexts = null;
           BitmapMetadata metadata = new BitmapMetadata("png");
            BitmapSource thumbnail = null;
            BitmapFrame bitmapFrame = BitmapFrame.Create(source, thumbnail, metadata, colorContexts);

            bitmapEncoder.Frames.Add(bitmapFrame);
            //Get the folder and extension
            var extension = Enum.GetName(typeof(ExportFormats), format);
            var path = Path.Combine(directory, name + "." + extension);

            //Create the directory if needed
            if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
                Directory.CreateDirectory(directory);

            if (image.ProcessingSettings != null)
            {
                BitmapMetadata bmp = bitmapEncoder.Frames[0].Metadata as BitmapMetadata;
                BitmapMetadata bitmapMetadata = (BitmapMetadata)bitmapEncoder.Frames[0].Metadata;//bitmapEncoder.Metadata;

                if (!AddMetatDataTags(bitmapMetadata, image.ProcessingSettings))
                    LogW($"Could not to {path}");
            }

            using (FileStream fileStream = new FileStream(path, FileMode.Create))
            {
                //Save the image
                bitmapEncoder.Save(fileStream);
            }
        }
    }


    public bool AddMetatDataTags(BitmapMetadata bitmapMetadata, IImageProcessingSettings settings)
    {
        CultureInfo culture = CultureInfo.InvariantCulture;
        var ret = false;

        if (bitmapMetadata != null)
        {
            // 1 off invariant information
            // Note: the PNG specification does not support many of these metadata properties (which are based on EXIF, which is NOT by the PNG spec)
            bitmapMetadata.SetQuery("/tEXt/Author", Environment.UserName); //Environment.UserName;
            object obj = bitmapMetadata.GetQuery("/tEXt/Author");
            string s = obj.ToString();
            AddKey(bitmapMetadata, 1, "CameraManufacturer", "Singer Instrument Company Limited");
            AddKey(bitmapMetadata, 2, "CameraModel", "Phenobooth");
            AddKey(bitmapMetadata, 0, "ApplicationName", "PhenoSuite");
            AddKey(bitmapMetadata, 3, "Brightness", Convert.ToString(settings.Brightness, culture)); // Capture specific data
            AddKey(bitmapMetadata, 4, "Exposure", Convert.ToString(settings.Exposure, culture));
            AddKey(bitmapMetadata, 5, "Gain", Convert.ToString(settings.Gain, culture));
            //...
           ret = true;
       }
  return ret;
}

/*
 * The tag dictionary in the bitmap properties has a strange implementation - based on a separated key value pair
 * if n = 0 entry like: /iTXt/Keyword    = key  /iTXt/TextEntry    = val
 * if n > 0 entry like: /[n]iTXt/Keyword = key  /[n]iTXt/TextEntry = val
 */
private void AddKey(BitmapMetadata metaData, int n, string key, string val)
{
    var _key = string.Format($"iTXt/{0}{1}{2}", (n > 0) ? "[" : "", (n > 0) ? n.ToString() : "", (n > 0) ? "]" : "");

    try
    {
        metaData.SetQuery(_key + "Keyword", key.ToCharArray()); // need to convert using ToCharArray as internal representation is based on the LPSTR C type
       metaData.SetQuery(_key + "TextEntry", val.ToCharArray());
    }
    catch (Exception e)
    {
        LogE($"Could not add metadata key:{key} index: {n} {e.Message}");
        throw;
    }
 }

public class Image
{
    #region Constructors

    /// <summary>
    /// Creates an image optionally copying the metadata
    /// </summary>
    /// <param name="width"></param>
    /// <param name="height"></param>
    /// <param name="format"></param>
    /// <param name="_imageMetadata"></param>
    public Image(int width, int height, PixelFormat format, ImageMetadata _imageMetadata) : this()
    {
        Width = width;
        Height = height;
        Format = format;
        ImageMetadata = _imageMetadata;
    }
...
}

When I look at the created metadata there are many not supported properties- indeed I have tried every which way to get around this problem, I am sure this used to work, but I cannot work out what has changed. 当我查看创建的元数据时,有许多不受支持的属性-实际上,我已经尝试了各种方法来解决此问题,我确信这曾经起作用,但是我无法弄清发生了什么变化。

Example of a watch on the newly metadata object: Meta Data 新元数据对象的watch示例: 元数据

Here is the code I use to get the key value pairs from the iTXt png metadata 这是我用来从iTXt png元数据中获取键值对的代码

public static class ImageUtils
{
    /// <summary>
    /// Captures all or part of the raw png metadata.
    /// Can use this to capture PhonoBooth metadata by setting the filter to "iTXt"
    /// 
    /// throws: ArgumentException if not a png file
    /// </summary>
    /// <param name="imageFilePath"></param>
    /// <param name="itemMap"></param>
    /// <param name="filter"> optional filter on the key (contains)</param>
    /// <returns>true if successful, false otherwise.</returns>
    public static bool GetMetaDataItems(string imageFilePath, ref Dictionary<string, string> itemMap, string filter=null)
    {
        Assertion<ArgumentException>(imageFilePath.ToLower().EndsWith(".png"), "Expected png file");

        var ret = false;
        var query = string.Empty;
        itemMap.Clear();

        try
        {
            using (Stream fileStream = File.Open(imageFilePath, FileMode.Open))
            {
                var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
                GetMetaDataItems(decoder.Frames[0].Metadata as BitmapMetadata, ref itemMap, filter);
            }

            ret = true;
        }
        catch (Exception e)
        {
            ret = false;
            LogE(e.Message);
        }

        return ret;
    }

    /// <summary>
    /// Used to get the meta data from png file metadata
    /// Can use this to capture PhonoBooth metadata by setting the filter to "iTXt"
    /// </summary>
    /// <param name="bitmapMetadata"></param>
    /// <param name="itemMap"></param>
    /// <param name="filter">set this to iTXt for Phenosuite image data</param>
    /// <param name="query">initally null, used in recursive calls to get the child data</param>
    public static void GetMetaDataItems(BitmapMetadata bitmapMetadata , ref Dictionary<string, string> itemMap, string filter= null, string query = null )
    {
        if (query == null)
            query = string.Empty;

        if (bitmapMetadata != null)
        {
            var key = string.Empty;

            foreach (string relativeQuery in bitmapMetadata)
            {
                var fullQuery = query + relativeQuery;
                // GetQuery returns an object: either a string or child metadata
                // If a string then it is one of 4 values: ["Keyword", "Translated", "Compression", "Language Tag", "TextEntry"]
                // We want the Keyword and the subsequent TextEntry items, the tags are a sequence in the order specified above
                var metadata = bitmapMetadata.GetQuery(relativeQuery);
                var innerBitmapMetadata = metadata as BitmapMetadata;

                if (innerBitmapMetadata == null)
                    AddToMap(ref key, fullQuery, metadata?.ToString(), ref itemMap, filter);    // Not a metadata structure so it is data - therefore check and Add to map
                else
                    GetMetaDataItems(innerBitmapMetadata, ref itemMap, filter, fullQuery);      // Recursive call
            }
        }
    }

    /// <summary>
    /// Suitable for Png iTXt metadata
    /// This is used to buld the item map from the metadata
    /// </summary>
    /// <param name="key">key like "Application" or "Lighting Mode"</param>
    /// <param name="fullQuery">metadata query</param>
    /// <param name="metadata">image metadata</param>
    /// <param name="itemMap">map being populated from the metadata</param>
    /// <param name="filter">we dont want all the meta data - so this filters on the "sub folder" of the meta data -Phenosuite uses "iTXt"  </param>
    private static void AddToMap(ref string key, string fullQuery, string metadata, ref Dictionary<string, string> itemMap, string filter)
    {
        if (metadata != null)
        {
            if (!fullQuery.Contains("Translated"))
            {
                if ((filter == null) || ((fullQuery.Contains(filter))))
                {
                    if (fullQuery.Contains("Keyword"))
                        key = metadata;

                    if (fullQuery.Contains("TextEntry") && (key != null))
                        itemMap[key] = metadata?.ToString();
                }
            }
        }
    }
}

Hope this helps somebody - as I found it very hard to fathom! 希望这对某人有帮助-我发现很难理解!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM