Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

InPlaceBitmapMetadataWriter.TrySave() returns true but does nothing

Tags:

c#

metadata

wpf

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 :)

like image 800
mephisto123 Avatar asked May 02 '10 21:05

mephisto123


2 Answers

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.

like image 175
lazo Avatar answered Oct 13 '22 23:10

lazo


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);
      }
    }
  }
}
like image 34
MartinHoly Avatar answered Oct 14 '22 00:10

MartinHoly