dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.07k stars 1.17k forks source link

Setting PNG image metadata doesn't work #7121

Open Evheni opened 2 years ago

Evheni commented 2 years ago

Code example doesn't work. TrySave method is called before the changes in metadata. If we add saving after the changes, the TrySave method returns false.

image

Document Details

Do not edit this section. It is required for learn.microsoft.com ➟ GitHub issue linking.

adegeo commented 2 years ago

TrySave here isn't the same as the other Try* methods where you get a result indicating success or failure. TrySave is actually testing to see if you can modify the metadata or not. If it returns true you should be able to write directly to the metadat with .SetQuery. When the stream is closed the metadata should be written.

What makes you think it's not working? And are you using .NET 6 or .NET Framework?

EDIT: My description of TrySave was based on the API documentation, which is misleading. Looking at the source code confirms it actually does try to save and returns the result of that save.

Evheni commented 2 years ago

I'm using .Net Framework 4.7.2 I assume, when we read (.GetQuery) the same query, the return value shouldn't be null.

image

I tried both saving before and after .SetQuery.

Do you need the image I used?

adegeo commented 2 years ago

@Evheni I've transferred this issue to the WPF team.

I can't figure this out. I've been trying to for 1.5 hours and it just doesn't work. Can someone please help this customer?

miloush commented 2 years ago

You most certainly need to call TrySave to save the changes done by SetQuery which is the only way to invoke IWICFastMeatadataEncoder::Commit, see an example here: https://learn.microsoft.com/en-us/windows/win32/api/wincodec/nn-wincodec-iwicfastmetadataencoder

If it returns false, the changes cannot be done in place and you need to re-encode the bitmap.

The example (which is on multiple pages) could use some improvements indeed.

adegeo commented 2 years ago

Thanks @miloush

@Evheni After a lot of trial and error, I got it working:

const string FILE = @"c:\path\to\your\file.png";

// Open the file for reading
using (Stream pngStream = new FileStream(FILE, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
    var pngDecoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
    BitmapFrame pngFrame = pngDecoder.Frames[0];
    InPlaceBitmapMetadataWriter pngInplace = pngFrame.CreateInPlaceBitmapMetadataWriter();

    // Set metadata here
    pngInplace.SetQuery("/Text/Description", "Have a nice day.");

    // Try to save in place, if failed, recreate the png
    if (!pngInplace.TrySave())
    {
        // Use a secondary stream for writing
        using (var pngSaveStream = new FileStream(FILE, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
        {
            var pngEncoder = new PngBitmapEncoder();

            // Duplicate each frame
            foreach (var frame in pngDecoder.Frames)
                pngEncoder.Frames.Add(BitmapFrame.Create(frame));

            // Set the required metadata
            ((BitmapMetadata)pngEncoder.Frames[0].Metadata).SetQuery("/Text/Description", "Have a nice day.");

            pngEncoder.Save(pngSaveStream);
        } // close\dispose pngSaveStream
    }
} // close\dispose pngStream
Evheni commented 2 years ago

Works for me. Thanks. Will you update the example section?

miloush commented 2 years ago

I agree the documentation should be fixed, but this is not a very nice example. It also does not make much sense, since the built-in PNG codec does not support in-place metadata at all, see the table here: https://learn.microsoft.com/en-us/windows/win32/wic/-wic-about-metadata#supported-metadata-formats

adegeo commented 2 years ago

Sure thing, I'll get things updated.

adegeo commented 2 years ago

@miloush I tried using a jpg but I get an error trying to save the file:

System.NotSupportedException: 'No imaging component suitable to complete this operation was found.'

using (var imageStream = new System.IO.FileStream("smiley.jpg", FileMode.Open,
                                                                FileAccess.ReadWrite,
                                                                FileShare.ReadWrite))
{
    var decoder = new JpegBitmapDecoder(imageStream,
                                            BitmapCreateOptions.PreservePixelFormat,
                                            BitmapCacheOption.Default);

    BitmapFrame frame = decoder.Frames[0];

    // Try to set the metadata directly on the image
    InPlaceBitmapMetadataWriter inplaceMetadata = frame.CreateInPlaceBitmapMetadataWriter();
    inplaceMetadata.SetQuery("/Text/Description", "Have a nice day.");

    if (!inplaceMetadata.TrySave())
    {
        // In place metadata save failed, must rencode image
        using (var saveStream = new FileStream("smiley.jpg", FileMode.Open,
                                                                FileAccess.Write,
                                                                FileShare.ReadWrite))
        {
            var encoder = new JpegBitmapEncoder();

            // Duplicate each frame
            foreach (var cloneFrame in decoder.Frames)
                encoder.Frames.Add(BitmapFrame.Create(cloneFrame));

            // Set metadata
            ((BitmapMetadata)encoder.Frames[0].Metadata).SetQuery("/Text/Description", "Have a nice day.");

            encoder.Save(saveStream); // <------- ERROR
        }
    }
}
miloush commented 2 years ago

How did you come up with /Text/Description? I believe that is supposed to be PNG tEXt chunk. Would e.g. System.Title work for you? See https://learn.microsoft.com/en-us/windows/win32/wic/-wic-native-image-format-metadata-queries for supported format-specific queries.

adegeo commented 2 years ago

I don't know, it was in the example. The error was vague so I didn't really know what to do. Thank you for that link, it clears up a lot of things.

miloush commented 2 years ago

Yeah just scratch that example. On one of the pages it even says it's an example for TIFF files...

I think it's important for users to know that WIC is providing the imaging features in WPF and they can reach out there for details. If you can sneak that somewhere somehow, that would be great!

adegeo commented 2 years ago

Yep, will do!

ghost commented 2 years ago

This submission has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 14 days.

It will be closed if no further activity occurs within 7 days of this comment.

adegeo commented 2 years ago

This is still in progress.. I've just not found the time to get back to the docs and update them.

ghost commented 1 year ago

This submission has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 14 days.

It will be closed if no further activity occurs within 7 days of this comment.

ThomasGoulet73 commented 1 year ago

@adegeo Is this still in progress ? If so, I think we can remove the waiting-author-feedback label.