6

I need to add a metadata tag (description) to uploaded images.

I have found out this answer: https://stackoverflow.com/a/1764913/6776 which works great for JPG files, but not for 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;
}

It works great when I upload a jpg, but with a png, at the metaData.Subject = Tag line, it throws a System.NotSupportedException (this codec does not support the specified property).

Update

It seems I have to use a different method based on the image format:

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

Based on the available formats' queries the first should work for both formats. The second doesn't really work either (it creates the metadata in the image but does not save its value).

If I try to use the first method (/app1/ifd/exif) for PNG, at the encoder.Save line I get a not supported exception, "no imaging component suitable".

Community
  • 1
  • 1
thomasb
  • 5,816
  • 10
  • 57
  • 92
  • Not relevant to your question, but I think there's a mistake in your isJpg = statement. I assume you want to test for ".jpg" or ".jpeg", but you test for ".jpg" twice. – RenniePet Mar 16 '15 at 11:28
  • Yes it's been fixed in the code since then, but not in the question. Thanks! – thomasb Mar 16 '15 at 11:43

3 Answers3

2

I solved it using pngcs library (you need to rename the downloaded dll to "pngcs.dll")

Here is how I implemented it:

    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);
        }


    }
}
Asaf Pinhassi
  • 15,177
  • 12
  • 106
  • 130
1

The library CompactExifLib can write EXIF tags in JPEG, TIFF and PNG files:

https://www.codeproject.com/Articles/5251929/CompactExifLib-Access-to-EXIF-Tags-in-JPEG-TIFF-an

It is written purely in C# and can be used for free.

Note: I am the author of this library.

Hans-Peter Kalb
  • 201
  • 2
  • 4
-4

The PNG-format does not support metadata :(

But XMP does, which might be of help when converting between JPEGs, with EXIF-metadata, and PNG.

flindeberg
  • 4,887
  • 1
  • 24
  • 37
oferb
  • 127
  • 1
  • 5
  • According the [Portable Network Graphics](https://en.wikipedia.org/wiki/Portable_Network_Graphics#Ancillary_chunks) topic on Wikipedia, PNG can store Metadata. So I am not sure why you think it cannot. – Black Frog Jul 06 '15 at 15:38
  • 1
    I suspect it is because `PngBitmapEncoder`'s Metadata member is unsettable. In particular `encoder.Metadata = new BitmapMetadata("png");` generates an exception "The designated BitmapEncoder does not support global metadata." – Eponymous Aug 20 '16 at 22:19