Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write metadata to both jpg and png

Tags:

c#

image

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".

like image 449
thomasb Avatar asked Mar 13 '15 16:03

thomasb


People also ask

Can you add metadata to PNG?

While PNG can embed metadata chunks, standard metadata formats such as EXIF, IPTC, or XMP are not supported for PNG.

Can metadata be stored in JPEG?

Metadata is stored in two main places: Internally – embedded in the image file, in formats such as JPEG, DNG, PNG, TIFF … Externally – outside the image file in a digital asset management system (DAM) or in a “sidecar” file (such as for XMP data) or an external news exchange format document as specified by the IPTC.


1 Answers

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


    }
}
like image 84
Asaf Pinhassi Avatar answered Oct 12 '22 21:10

Asaf Pinhassi