将元数据写入jpg和png文件

6
我需要在上传的图片中添加元数据标签(描述)。
我已经找到了这个答案:https://dev59.com/fXI-5IYBdhLWcg3woJ9J#1764913,它对于JPG文件非常有效,但对于PNG文件则无法正常工作。
private string Tag = "test meta data";

private static Stream TagImage(Stream input, string type)
{
    bool isJpg = type.EndsWith("jpg", StringComparison.InvariantCultureIgnoreCase) || type.EndsWith("jpeg", StringComparison.InvariantCultureIgnoreCase);
    bool isPng = type.EndsWith("png", StringComparison.InvariantCultureIgnoreCase);

    BitmapDecoder decoder = null;

    if (isJpg)
    {
        decoder = new JpegBitmapDecoder(input, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
    }
    else if (isPng)
    {
        decoder = new PngBitmapDecoder(input, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
    }
    else
    {
        return input;
    }

    // modify the metadata
    BitmapFrame bitmapFrame = decoder.Frames[0];
    BitmapMetadata metaData = (BitmapMetadata)bitmapFrame.Metadata.Clone();
    metaData.Subject = Tag;
    metaData.Comment = Tag;
    metaData.Title = Tag;

    // get an encoder to create a new jpg file with the new metadata.      
    BitmapEncoder encoder = null;
    if (isJpg)
    {
        encoder = new JpegBitmapEncoder();
    }
    else if (isPng)
    {
        encoder = new PngBitmapEncoder();
    }

    encoder.Frames.Add(BitmapFrame.Create(bitmapFrame, bitmapFrame.Thumbnail, metaData, bitmapFrame.ColorContexts));

    // Save the new image 
    Stream output = new MemoryStream();
    encoder.Save(output);

    output.Seek(0, SeekOrigin.Begin);

    return output;
}

当我上传jpg时,它运行良好,但是对于png,在metaData.Subject = Tag 行,它会抛出一个System.NotSupportedException(此编解码器不支持指定的属性)。

更新

看来我必须根据图像格式使用不同的方法:

if (isJpg)
{
    metaData.SetQuery("/app1/ifd/exif:{uint=270}", Tag);
}
else
{
    metaData.SetQuery("/tEXt/{str=Description}", Tag);
}

根据可用格式的查询,第一种方法应该适用于两种格式。第二种方法实际上也不起作用(它会在图像中创建元数据,但不保存其值)。
如果我尝试在PNG中使用第一种方法(/app1/ifd/exif),在encoder.Save行处会出现不支持的异常,“没有适合的成像组件”。

与您的问题无关,但我认为您的isJpg =语句中有一个错误。我假设您想测试“.jpg”或“.jpeg”,但您测试了两次“.jpg”。 - RenniePet
是的,自那时起,代码已经被修复了,但问题中还没有。谢谢! - thomasb
3个回答

2
我使用了 pngcs 库来解决这个问题(你需要将下载的 dll 文件重命名为 "pngcs.dll")。
以下是我的实现方式:
    using Hjg.Pngcs;  // https://code.google.com/p/pngcs/
using Hjg.Pngcs.Chunks;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MarkerGenerator.Utils
{
    class PngUtils
    {

        public string getMetadata(string file, string key)
        {

            PngReader pngr = FileHelper.CreatePngReader(file);
            //pngr.MaxTotalBytesRead = 1024 * 1024 * 1024L * 3; // 3Gb!
            //pngr.ReadSkippingAllRows();
            string data = pngr.GetMetadata().GetTxtForKey(key);
            pngr.End();
            return data; ;
        }


        public static void addMetadata(String origFilename, Dictionary<string, string> data)
        {
            String destFilename = "tmp.png";
            PngReader pngr = FileHelper.CreatePngReader(origFilename); // or you can use the constructor
            PngWriter pngw = FileHelper.CreatePngWriter(destFilename, pngr.ImgInfo, true); // idem
            //Console.WriteLine(pngr.ToString()); // just information
            int chunkBehav = ChunkCopyBehaviour.COPY_ALL_SAFE; // tell to copy all 'safe' chunks
            pngw.CopyChunksFirst(pngr, chunkBehav);          // copy some metadata from reader 
            foreach (string key in data.Keys)
            {
                PngChunk chunk = pngw.GetMetadata().SetText(key, data[key]);
                chunk.Priority = true;
            }

            int channels = pngr.ImgInfo.Channels;
            if (channels < 3)
                throw new Exception("This example works only with RGB/RGBA images");
            for (int row = 0; row < pngr.ImgInfo.Rows; row++)
            {
                ImageLine l1 = pngr.ReadRowInt(row); // format: RGBRGB... or RGBARGBA...
                pngw.WriteRow(l1, row);
            }
            pngw.CopyChunksLast(pngr, chunkBehav); // metadata after the image pixels? can happen
            pngw.End(); // dont forget this
            pngr.End();
            File.Delete(origFilename);
            File.Move(destFilename, origFilename);

        }

        public static void addMetadata(String origFilename,string key,string value)
        {
            Dictionary<string, string> data = new Dictionary<string, string>();
            data.Add(key, value);
            addMetadata(origFilename, data);
        }


    }
}

当我们只有一个通道时,您如何处理? pngr.ImgInfo.Channels == 1? - Max Dove

1

-4

PNG格式不支持元数据 :(

但是XMP支持,这可能在JPEG和带有EXIF元数据的PNG之间转换时会有所帮助。


根据维基百科上的可移植网络图形主题,PNG可以存储元数据。所以我不确定你为什么认为它不能。 - Black Frog
1
我猜测问题出在 PngBitmapEncoder 的 Metadata 成员是不可设置的。特别地,encoder.Metadata = new BitmapMetadata("png"); 会产生一个异常:“指定的 BitmapEncoder 不支持全局元数据。” - Eponymous

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接