c# - 如何使用元数据创建 png 文件

标签 c# wpf image metadata

我正在尝试创建一个 PNG 文件来保存我们捕获的图像数据,但在尝试设置公司、CameraManufacturer、亮度等元数据时我真的很痛苦。

Google PNGCS 库可以执行此操作,但我必须先编写文件,然后重新加载并重新保存。

我们使用的是 WPF,所以我认为我可以使用更通用的类,如 BitmapMetadataPngBitmapEncoderJpegBitmapEncoder

但是,我不断遇到 为我想使用的属性抛出类型为“System.NotSupportedException” 的异常。 您可以在我放在本文底部创建的元数据中的变量 watch 中看到这一点。

Image 类实际上是一个包含宽度高度、文件类型(如 png 或 gif 等)的数据结构。

从摄像头获取图像数据,添加我们想要的标签并保存到文件应该很简单。

与负载相同 - 我们应该能够获得它们。

代码:

/// <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;
    }
...
}

当我查看创建的元数据时,有许多不受支持的属性 - 事实上,我已经尝试了各种方法来解决这个问题,我确信这曾经有效,但我无法弄清楚发生了什么变化。

新元数据对象上的 watch 示例:Meta Data

最佳答案

这是我用来从 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();
                }
            }
        }
    }
}

希望这对某些人有所帮助 - 因为我发现它很难理解!

关于c# - 如何使用元数据创建 png 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47163090/

相关文章:

wpf - ListBox.SelectedItems 的双向手动绑定(bind)实现?

javascript - 带有缩放元素的响应式网格

image - 如何使一行三张图片响应

c# - NHibernate 中的 StatelessSession 和 Session 有什么区别?

c# - FlexLayout 可绑定(bind)源 -xamarin 表单 - 如何在不绑定(bind)的情况下获取数据

c# - 如何在 C# 中获取向量类型?

javascript - 如何在浏览器中通过 POST 请求加载外国图片?

c# - 在 Visual Studio 2013 中构建失败并显示 "Could not copy the file.. because it was not found"

c# - 如何从头开始创建一个快速的 WPF Datagrid 控件?

c# - 实现Finalize和Dispose的正确方法(当父类实现IDisposable时)