2

In C# am trying to set below property in a jpg file. But when I try to retrieve them back I get Property not found exception. Also the SetPropertyItem call has no way to report success\failure, so finding it difficult to understand what went wrong.

0x5111 PropertyTagPixelPerUnitX

0x5112 PropertyTagPixelPerUnitY

string file = "New.jpg";
double x = 24524.5555598;
double y = 234123.4123423;
Image img = Image.FromFile(file);
PropertyItem[] props = img.PropertyItems;
PropertyItem newProp1 = props.FirstOrDefault();
newProp1.Id = 0x5111;
newProp1.Type = 1;
newProp1.Value = BitConverter.GetBytes(x);
newProp1.Len = newProp1.Value.Length;

PropertyItem newProp2 = props.FirstOrDefault();
newProp2.Id = 0x5112;
newProp2.Type = 1;
newProp2.Value = BitConverter.GetBytes(y);
newProp2.Len = newProp1.Value.Length;

img.SetPropertyItem(newProp1);
img.SetPropertyItem(newProp2);
img.Save("New1.jpg", ImageFormat.Jpeg);

And code to retrieve them,

string file = "New1.jpg";
Image img = Image.FromFile(file);
PropertyItem prop = img.GetPropertyItem(0x5111);

The above line throws exception 'Property not found'

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
Rahul Sundar
  • 480
  • 8
  • 26
  • 2
    You overwrite newProp1 with newProp2 in your code. They both reference same first property. – Evk Sep 24 '15 at 12:22
  • Ah thanks for pointing out. But with since PropertyItem doesn't come with constructor, how do we add new properties (0x5112) without affecting existing properties? – Rahul Sundar Sep 24 '15 at 16:17

1 Answers1

4

I have no idea why they made PropertyItem constructor internal and didn't provide any other means of creating property item. However you can just use reflection to work around this strange issue, and it will work:

        string file = @"New1.jpg";
        double x = 24524.5555598;
        double y = 234123.4123423;
        var img = System.Drawing.Image.FromFile(file);
        // note how to force Activator.CreateInstance to call internal constructor, 
        // it's important to call this overload
        var newProp1 = (PropertyItem) Activator.CreateInstance(typeof(PropertyItem), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[0], CultureInfo.InvariantCulture);
        newProp1.Id = 0x5111;
        newProp1.Type = 1;
        newProp1.Value = BitConverter.GetBytes(x);
        newProp1.Len = newProp1.Value.Length;

        var newProp2 = (PropertyItem)Activator.CreateInstance(typeof(PropertyItem), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[0], CultureInfo.InvariantCulture);
        newProp2.Id = 0x5112;
        newProp2.Type = 1;
        newProp2.Value = BitConverter.GetBytes(y);
        newProp2.Len = newProp1.Value.Length;

        img.SetPropertyItem(newProp1);
        img.SetPropertyItem(newProp2);
        img.Save("New1.jpg", ImageFormat.Jpeg);
Evk
  • 98,527
  • 8
  • 141
  • 191
  • Note that you might want to check if property with such id already exists and if yes - update existing item. Who knows how duplicated properties will behave. – Evk Sep 24 '15 at 16:56
  • This seems to work, but when I read back the saved file, the new property isn't there. – Joel Coehoorn Oct 09 '17 at 14:38
  • @JoelCoehoorn I just tested this code with simple jpeg image and all new exif properties are there after save (in "PropertyItems" array), also confirmed with image magick `identify` tool. As far as I know - .NET Image class is not very good at working with exif tags and might skip some of them, but for this particular tags (PixelPerUnit) it works fine. If you are trying to set different tags - that might be the case. Ensure with identify or any other tools if tags were indeed added, because Image.PropertyItems is not a reliable source of that. – Evk Oct 09 '17 at 14:56
  • You are correct… this does work, but only for JPGs. I'm working on an app to let a student worker quickly annotate a portrait image with the face region, so further automated processes can then crop and resize the images to quickly upload to various systems at a small college (library circulation, student portal, SIS, LMS, e-mail, etc) no matter what size/aspect ratio the target needs, without cropping away any of the face. I usually have jpgs, but occasionally it will be a png, bmp, or gif. I want something generic to work for all of those types. Looks like I'll need separate code per type :( – Joel Coehoorn Oct 09 '17 at 17:15
  • @JoelCoehoorn it's not that simple I guess. I tried various png files and it was working fine with them, though officially png does not support exif tags. Various software can embed them in png and another software can read, but there is no official specification. (unlike jpeg). Same story with bmp (never heard of exif tags in bmp at all really, and even most sophisticated tools, like exiftool, cannot add exif tags to bmp). Here is a question about png and exif:https://stackoverflow.com/q/9542359/5311735 – Evk Oct 09 '17 at 19:49
  • I found that this works with the simplified CreateInstance method just `...CreateInstance(typeof(PropertyItem), nonPublic: true);` – TJ Rockefeller Sep 27 '18 at 19:03