dlemstra / Magick.NET

The .NET library for ImageMagick
Apache License 2.0
3.33k stars 405 forks source link

Update EXIF metadata without re-encoding JPEG image #530

Open Webreaper opened 4 years ago

Webreaper commented 4 years ago

I'm building an image processing app using .Net Core 3.0 and blazor. I'm using Magick.Net (latest version, Q16-AnyCPU, running on OSX and also on Linux) to read from and write to the IPTC tag list. I have two questions:

  1. My code to add a tag looks something like this:

        string tagToAdd = "New Tag";
        // Read image from file
        using (MagickImage image = new MagickImage(input))
        {
            // Retrieve the exif information
            var profile = image.GetIptcProfile();
    
            if( profile == null )
                profile = new IptcProfile();
    
            var keywords = profile.Values.Where(x => x.Tag == IptcTag.Keyword)
                                  .Select( x => x.Value )
                                  .ToList();
    
            keywords.Add(tagToAdd);
            string allKeywords = string.Join(",", keywords);
            profile.SetValue(IptcTag.Keyword, allKeywords);
            image.AddProfile(profile);
    
            image.Write(output);
        }

    Is this the right approach? Is there a better way? There aren't really any good code samples for IPTC writing, so I wanted to check I've not missed an API to just add a single keyword to the existing set of keywords.

  2. Assuming the code above is correct, is this guaranteed to be a no-op on the actual image itself? i.e., what I'm looking for is to just rewrite the IPTC Exif block without re-writing the image. What I don't want this to do is re-encode the JPEG (or touch it at all) because obviously if it does that, after adding 10 keywords in separate operations I'll end up with blotchy artifacts in my images. I know that ExifTool does the tags addition/removal without touching the image data, but want to check if the same assumption for ImageMagick/Magick.Net.

The reason this question cropped up is because I had a 7.6MB image on disk, added a single keyword using the code above, and the resulting image was 6.8MB - which seems to me like it may have re-encoded the image and lost some data into the process. I could just spawn a process and run exiftool, but it's cleaner to do the IPTC tag changes in-process, particularly when the thing I'm writing is cross-platform.

Thanks for the help!

dlemstra commented 4 years ago

There is currently no support for editing the metadata without decoding and encoding the image. And I have not seen any other libraries that can do that without spawning a process to do this. It should be possible to add something like that this to this library because there is already a class that can be used to optimize JPEG files without encoding and decoding them. But it could take a long time before something like this will be added.

Webreaper commented 4 years ago

Thanks for confirming. Glad I checked, as I might have ended up spoiling my original-res photos with tag editing! I'll look at spawning exiftool for now though. Will modify the title of this issue to fit the 'enhancement' tag.

Byron1c commented 4 years ago

+1 here... Id very much like to be able to edit the metadata/exif without touching the image.

Please let me know if this should be a separate issue, but I thought it might be appropriate here: I would also like to be able to add/edit the thumbnail image stored in the exif tags 0x501b (ThumbnailData), and 0x5023 (ThumbnailCompression). Or, is this handled in existing code somewhere, and I have missed it? The Exif profile only refers to CreateThumbnail (which only returns an existing thumbnail?), and RemoveThumbnail.

The code I am currently trying to use is as follows, but saving the image results in changes to the jpg itsself when saved. Im unable to copy the properties to MagickImage because it will not accept those IDs.

                    MagickGeometry mg = new MagickGeometry(vSizeX, vSizeY);
                    mg.FillArea = true;
                    mg.IgnoreAspectRatio = false;

                    OpenCL.IsEnabled = false;
                    MagickImage newThumb = (MagickImage)image.Clone();
                    newThumb.Resize(160, 160);

                    newImage = image.ToBitmap();
                    image.Dispose();

                    PropertyItem itemThumb = null;
                    PropertyItem itemThumbCompress = null;

                    if (itemThumb == null) itemThumb = (PropertyItem)FormatterServices.GetUninitializedObject(typeof(PropertyItem));
                    if (itemThumbCompress == null) itemThumbCompress = (PropertyItem)FormatterServices.GetUninitializedObject(typeof(PropertyItem));

                    itemThumb.Id = 0x501b; // PropertyTag ThumbnailData
                    itemThumb.Type = 1;
                    itemThumb.Len = newThumb.ToByteArray().Length;
                    itemThumb.Value = newThumb.ToByteArray();
                    newImage.SetPropertyItem(itemThumb);

                    //#region Create a new Thumbnail Compression property.
                    itemThumbCompress.Id = 0x5023; // PropertyTagThumbnailCompression
                    itemThumbCompress.Type = (short)ExifPropertyDataTypes.UShortArray;
                    itemThumbCompress.Len = 2;
                    itemThumbCompress.Value = new byte[] { 6, 0 };
                    newImage.SetPropertyItem(itemThumbCompress);

                    newImage.Save(vPath);

I also think the ThumbnailDateTime = 0x5033 would be useful Oh, Im running Q16-AnyCPU too

Thanks heaps for a great extension and all your work Dirk! :)

GF-Huang commented 4 years ago

This one can update EXIF lossless, bu I don't know does it implement.

http://www.magicexif.com/

image

wjax commented 2 years ago

I think this repo do extract Exif without reading/decoding whole file. https://github.com/esskar/ExifLib.Net

I have tested both Magick and this repo and the difference is abismal in memory and time.

Not complaining here, just leting you know that there are some alternatives (I do not know how clean code or proper implementations they are)

A+