Tutorial :InPlaceBitmapMetadataWriter.TrySave() returns true but does nothing



Question:

On some .JPG files (EPS previews, generated by Adobe Illustrator) in Windows 7 InPlaceBitmapMetadataWriter.TrySave() returns true after some SetQuery() calls, but does nothing.

Code sample:

BitmapDecoder decoder;  BitmapFrame frame;  BitmapMetadata metadata;  InPlaceBitmapMetadataWriter writer;  decoder = BitmapDecoder.Create(s, BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default);  frame = decoder.Frames[0];  metadata = frame.Metadata as BitmapMetadata;  writer = frame.CreateInPlaceBitmapMetadataWriter();  try {      writer.SetQuery("System.Title", title);      writer.SetQuery(@"/app1/ifd/{ushort=" + exiftagids[0] + "} ", (title + '\0').ToCharArray());      writer.SetQuery(@"/app13/irb/8bimiptc/iptc/object name", title);      return writer.TrySave();  }  catch {      return false;  }  

Image sample

You can reproduce problem (if you have Windows 7) by downloading image sample and using this code sample to set title on this image. Image has enough room for metadata - and this code sample works fine on my WinXP. Same code works fine on Win7 with other .JPG files.

Any ideas are welcome :)


Solution:1

Two things:

  1. I don't think you will be able to write to your metadata variable just like that, as it will be Frozen. So, you will have to clone it:

      BitmapMetadata metadata = frame.Metadata.Clone() as BitmapMetadata;  
  2. Padding, you need padding. I found this out after about a day's worth of tinkering around trying to make some code (similar to yours) work. InPlaceBitmapMetadataWriter will not work if there is no metadata padding in your image file. So you need something like:

      JpegBitmapEncoder encoder = new JpegBitmapEncoder();  if(frame != null && metadata != null) {      metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", padding);      encoder.Frames.Add(BitmapFrame.Create(frame, frame.Thumbnail, metadata, frame.ColorContexts));      using (Stream outputFile = File.Open(_myoutputpath, FileMode.Create, FileAccess.ReadWrite)) {          encoder.Save(outputFile);      }  }  

Now you can use the file located at _myoutputpath which has added metadata padding for your InPlaceBitmapMetadataWriter operations.

This article and attached code should help you out.


Solution:2

Hi I found this article about InPlaceBitmapMetadataWriter where the guy said that TrySave() might corrupt the image and that's why he advised to do TrySave() on the copy of the original file and if this doesn't work, add padding to the copy of original file and than TrySave() again and if it works, delete the original and rename the copy.

I scratched my head and asked myself why I should bother with InPlaceBitmapMetadataWriter and writing 3x original file to the disk in case TrySave() doesn't work because there is not enough padding, if I can clone metadata, write whatever into them and assemble jpeg file right away.

Then I started to think that maybe thanks to InPlaceBitmapMetadataWriter I can edit metadata without losing quality, but it looks like it "just" helps you to write metadata more quickly if there is enough padding.

I wrote a small test where I compress one file many times to see the quality degradation and you can see it in the third-fourth compression, which is very bad.

But luckily, if you always use same QualityLevel with JpegBitmapEncoder there is no degradation.

In this example I rewrite keywords 100x in metadata and the quality seems not to change.

private void LosslessJpegTest() {    var original = "d:\\!test\\TestInTest\\20150205_123011.jpg";    var copy = original;    const BitmapCreateOptions createOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile;      for (int i = 0; i < 100; i++) {      using (Stream originalFileStream = File.Open(copy, FileMode.Open, FileAccess.Read)) {        BitmapDecoder decoder = BitmapDecoder.Create(originalFileStream, createOptions, BitmapCacheOption.None);          if (decoder.CodecInfo == null || !decoder.CodecInfo.FileExtensions.Contains("jpg") || decoder.Frames[0] == null)          continue;          BitmapMetadata metadata = decoder.Frames[0].Metadata == null          ? new BitmapMetadata("jpg")          : decoder.Frames[0].Metadata.Clone() as BitmapMetadata;          if (metadata == null) continue;          var keywords = metadata.Keywords == null ? new List<string>() : new List<string>(metadata.Keywords);        keywords.Add($"Keyword {i:000}");        metadata.Keywords = new ReadOnlyCollection<string>(keywords);          JpegBitmapEncoder encoder = new JpegBitmapEncoder {QualityLevel = 80};        encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0], decoder.Frames[0].Thumbnail, metadata,          decoder.Frames[0].ColorContexts));          copy = original.Replace(".", $"_{i:000}.");          using (Stream newFileStream = File.Open(copy, FileMode.Create, FileAccess.ReadWrite)) {          encoder.Save(newFileStream);        }      }    }  }  


Solution:3

I still didn't find the answer and has to write wrapper for exiftool instead of using WPF's way to work with metadata... May be som1 will find it useful.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »