当InPlaceBitmapMetadataWriter.TrySave()返回false时,如何向图像添加元数据?

时间:2016-02-03 21:56:34

标签: c# image c#-4.0 .net-4.0 metadata

我正在努力为图像添加一些IPTC元数据,但是在第二个TrySave上,该方法返回false:

string[] files = Directory.GetFiles(imagesFolderPath);

foreach(string filePath in files) 
{
    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
    {
        var decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.None);
        var metadata = decoder.Frames[0].CreateInPlaceBitmapMetadataWriter();
        Console.WriteLine(filePath);
        Console.WriteLine(metadata.TrySave());
        metadata.SetQuery("/Text/Description", "Testing");
        Console.WriteLine(metadata.TrySave());
    }
}

我认为这是因为元数据填充的大小适合图像的当前元数据量,并且添加了一些元数据填充超过了填充,但是如何将新元数据添加/写入图像?

2 个答案:

答案 0 :(得分:3)

这是帮我启动并运行的课程:

class ImageMetadata
{
    public static void SetImageMetadata(string imagesFolderPath)
    {
        string originalPath = @"C:\Users\test\Desktop\test.jpg";
        string outputPath = Environment.CurrentDirectory + @"\output.jpg";
        string finalPath = Environment.CurrentDirectory + @"\output_beforeInPlaceBitmapMetadataWriter.jpg";

        BitmapCreateOptions createOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile;

        uint paddingAmount = 2048; // 2Kb padding for this example, but really this can be any value. 
        // Our recommendation is to keep this between 1Kb and 5Kb as most metadata updates are not large.

        // High level overview:
        //   1. Perform a lossles transcode on the JPEG
        //   2. Add appropriate padding
        //   3. Optionally add whatever metadata we need to add initially
        //   4. Save the file
        //   5. For sanity, we verify that we really did a lossless transcode
        //   6. Open the new file and add metadata in-place

        using (Stream originalFile = File.Open(originalPath, FileMode.Open, FileAccess.Read))
        {
            // Notice the BitmapCreateOptions and BitmapCacheOption. Using these options in the manner here
            // will inform the JPEG decoder and encoder that we're doing a lossless transcode operation. If the
            // encoder is anything but a JPEG encoder, then this no longer is a lossless operation.
            // ( Details: Basically BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile 
            //   tell the decoder to use the original image bits and BitmapCacheOption.None tells the decoder to wait 
            //   with decoding. So, at the time of encoding the JPEG encoder understands that the input was a JPEG
            //   and just copies over the image bits without decompressing and recompressing them. Hence, this is a
            //   lossless operation. )
            BitmapDecoder original = BitmapDecoder.Create(originalFile, createOptions, BitmapCacheOption.None);

            if (!original.CodecInfo.FileExtensions.Contains("jpg"))
            {
                Console.WriteLine("The file you passed in is not a JPEG.");
                return;
            }

            JpegBitmapEncoder output = new JpegBitmapEncoder();

            // If you're just interested in doing a lossless transcode without adding metadata, just do this:
            //output.Frames = original.Frames;

            // If you want to add metadata to the image using the InPlaceBitmapMetadataWriter, first add padding:
            if (original.Frames[0] != null && original.Frames[0].Metadata != null)
            {
                // Your gut feel may want you to do something like:
                //     output.Frames = original.Frames;
                //     BitmapMetadata metadata = output.Frames[0].Metadata as BitmapMetadata;
                //     if (metadata != null)
                //     {
                //         metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", paddingAmount);
                //     }
                // However, the BitmapMetadata object is frozen. So, you need to clone the BitmapMetadata and then
                // set the padding on it. Lastly, you need to create a "new" frame with the updated metadata.

                BitmapMetadata metadata = original.Frames[0].Metadata.Clone() as BitmapMetadata;

                // Of the metadata handlers that we ship in WIC, padding can only exist in IFD, EXIF, and XMP.
                // Third parties implementing their own metadata handler may wish to support IWICFastMetadataEncoder
                // and hence support padding as well.
                metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", paddingAmount);
                metadata.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", paddingAmount);
                metadata.SetQuery("/xmp/PaddingSchema:Padding", paddingAmount);

                // Since you're already adding metadata now, you can go ahead and add metadata up front.
                metadata.SetQuery("/app1/ifd/{uint=897}", "hello there");
                metadata.SetQuery("/app1/ifd/{uint=898}", "this is a test");
                metadata.Title = "This is a title";

                // Create a new frame identical to the one from the original image, except the metadata will have padding.
                // Essentially we want to keep this as close as possible to:
                //     output.Frames = original.Frames;
                output.Frames.Add(BitmapFrame.Create(original.Frames[0], original.Frames[0].Thumbnail, metadata, original.Frames[0].ColorContexts));
            }

            using (Stream outputFile = File.Open(outputPath, FileMode.Create, FileAccess.ReadWrite))
            {
                output.Save(outputFile);
            }
        }

        // For sanity, let's verify that the original and the output contain image bits that are the same.
        using (Stream originalFile = File.Open(originalPath, FileMode.Open, FileAccess.Read))
        {
            BitmapDecoder original = BitmapDecoder.Create(originalFile, createOptions, BitmapCacheOption.None);

            using (Stream savedFile = File.Open(outputPath, FileMode.Open, FileAccess.Read))
            {
                BitmapDecoder output = BitmapDecoder.Create(savedFile, createOptions, BitmapCacheOption.None);

                Compare(original.Frames[0], output.Frames[0], 0, "foo", Console.Out);
            }
        }

        // Let's copy the file before we use the InPlaceBitmapMetadataWriter so that you can verify the metadata changes.
        File.Copy(outputPath, finalPath, true);
        Console.WriteLine();

        // Now let's use the InPlaceBitmapMetadataWriter.
        using (Stream savedFile = File.Open(outputPath, FileMode.Open, FileAccess.ReadWrite))
        {
            ConsoleColor originalColor = Console.ForegroundColor;

            BitmapDecoder output = BitmapDecoder.Create(savedFile, BitmapCreateOptions.None, BitmapCacheOption.Default);

            InPlaceBitmapMetadataWriter metadata = output.Frames[0].CreateInPlaceBitmapMetadataWriter();

            // Within the InPlaceBitmapMetadataWriter, you can add, update, or remove metadata.
            //metadata.SetQuery("/app1/ifd/{uint=899}", "this is a test of the InPlaceBitmapMetadataWriter");
            //metadata.RemoveQuery("/app1/ifd/{uint=898}");
            //metadata.SetQuery("/app1/ifd/{uint=897}", "Hello there!!");



            Console.WriteLine("#####################");

            Console.WriteLine(metadata.GetQuery(@"/app13/irb/8bimiptc/iptc/City"));

            Console.WriteLine("#####################");

            metadata.SetQuery(@"/app13/irb/8bimiptc/iptc/Headline", "TEst");
            metadata.SetQuery(@"/app13/irb/8bimiptc/iptc/City", "TEst");
            metadata.SetQuery(@"/app13/irb/8bimiptc/iptc/CountryPrimaryLocationName", "TEst");
            metadata.SetQuery(@"/app13/irb/8bimiptc/iptc/ByLine", "TEst");
            metadata.SetQuery(@"/app13/irb/8bimiptc/iptc/CaptionAbstract", "TEst");

            Console.WriteLine("#####################");

            Console.WriteLine(metadata.GetQuery(@"/app13/irb/8bimiptc/iptc/City"));

            Console.WriteLine("#####################");


            if (metadata.TrySave())
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("InPlaceMetadataWriter succeeded!");
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("InPlaceMetadataWriter failed!");
            }

            Console.ForegroundColor = originalColor;
        }

        FileInfo originalInfo = new FileInfo(originalPath);
        FileInfo outputInfo = new FileInfo(outputPath);
        FileInfo finalInfo = new FileInfo(finalPath);

        Console.WriteLine(outputPath);

        Console.WriteLine();
        Console.WriteLine("Original File Size: \t\t\t{0}", originalInfo.Length);
        Console.WriteLine("After Padding File Size: \t\t{0}", outputInfo.Length);
        Console.WriteLine("After InPlaceBitmapWriter File Size: \t{0}", finalInfo.Length);
    }


    /// <summary>
    /// Compares 2 BitmapSources. Basically a poor man's image comparison routine.
    /// </summary>
    /// <param name="originalImage">Original BitmapSource</param>
    /// <param name="resultImage">Result BitmapSource</param>
    /// <param name="tolerance">Tolerance to use for determining a fail</param>
    /// <param name="errorFilename">Filename prefix for the output files if a fail is detected</param>
    /// <param name="log">Where to log the results to</param>
    /// <returns>true is BitmapSources are within tolerance; false otherwise</returns>
    private static bool Compare(BitmapSource originalImage, BitmapSource resultImage, byte tolerance, string errorFilename, TextWriter log)
    {
        bool result = true;

        if (originalImage.PixelWidth != resultImage.PixelWidth)
        {
            log.WriteLine("\t\t\tBitmap widths are not equal.");
            return false;
        }

        if (originalImage.PixelHeight != resultImage.PixelHeight)
        {
            log.WriteLine("\t\t\tBitmap heights are not equal.");
            return false;
        }

        if (originalImage.Format != resultImage.Format)
        {
            log.WriteLine("\t\t\tBitmap pixel formats are not equal.");
            return false;
        }

        System.Windows.Media.PixelFormat pf = PixelFormats.Bgra32;

        FormatConvertedBitmap src0 = new FormatConvertedBitmap(originalImage, pf, null, 0);
        FormatConvertedBitmap src1 = new FormatConvertedBitmap(resultImage, pf, null, 0);

        GC.AddMemoryPressure(((src0.Format.BitsPerPixel * src0.PixelWidth + 7) / 8) * src0.PixelHeight);
        GC.AddMemoryPressure(((src1.Format.BitsPerPixel * src1.PixelWidth + 7) / 8) * src1.PixelHeight);

        int width = src0.PixelWidth;
        int height = src0.PixelHeight;
        int bpp = pf.BitsPerPixel;
        int stride = (bpp * width + 7) / 8;

        byte[] scanline0 = new byte[stride];
        byte[] scanline1 = new byte[stride];
        Int32Rect lineRect = new Int32Rect(0, 0, width, 1);

        log.WriteLine("Comparison progress...");

        for (int y = 0; y < height; y++)
        {
            if (y % 15 == 0)
            {
                log.Write("{0}, ", y);
            }

            lineRect.Y = y;
            src0.CopyPixels(lineRect, scanline0, stride, 0);
            src1.CopyPixels(lineRect, scanline1, stride, 0);

            for (int b = 0; b < stride; b++)
            {
                if (Math.Abs(scanline0[b] - scanline1[b]) > tolerance)
                {
                    // log.WriteLine(string.Format("\t\t\tBitmap pixels are not equal (y = {0})", y));
                    log.WriteLine(string.Format("\t\t\tExpected {0}, Found {1}, Tolerance {2}", scanline0[b], scanline1[b], tolerance));
                    result = false;
                }
            }
        }

        log.WriteLine();

        src0 = null;
        src1 = null;
        scanline0 = null;
        scanline1 = null;

        if (!result)
        {
            using (Stream stm = File.Create(errorFilename + ".original.png"))
            {
                PngBitmapEncoder png = new PngBitmapEncoder();
                png.Frames.Add(BitmapFrame.Create(originalImage));
                png.Save(stm);
            }
            using (Stream stm = File.Create(errorFilename + ".result.png"))
            {
                PngBitmapEncoder png = new PngBitmapEncoder();
                png.Frames.Add(BitmapFrame.Create(resultImage));
                png.Save(stm);
            }
        }

        return result;
    }
}

答案 1 :(得分:2)

我建议您检查一下这个答案:https://stackoverflow.com/a/3984221/213550 本文Using WPF's InPlaceBitmapMetadataWriterlinked code

您必须在 之前添加填充的主要想法,您可以使用您的信息填写。这可以这样做(粘贴代码与注释,与您的变量):

uint paddingAmount = 2048; // 2Kb padding for this example, but really this can be any value.
 // However, the BitmapMetadata object is frozen. So, you need to clone the BitmapMetadata and then
 // set the padding on it. Lastly, you need to create a "new" frame with the updated metadata.
BitmapMetadata metadata = decoder.Frames[0].Metadata.Clone ( ) as BitmapMetadata;
// Of the metadata handlers that we ship in WIC, padding can only exist in IFD, EXIF, and XMP.
// Third parties implementing their own metadata handler may wish to support IWICFastMetadataEncoder
// and hence support padding as well.
metadata.SetQuery ( "/app1/ifd/PaddingSchema:Padding", paddingAmount );
metadata.SetQuery ( "/app1/ifd/exif/PaddingSchema:Padding", paddingAmount );
metadata.SetQuery ( "/xmp/PaddingSchema:Padding", paddingAmount );

另一篇带填充编辑的文章:Updating jpeg Metadata Wthout Loss of Quality