使用Windows图像组件(WIC)快速创建缩小位图,以遵守EXIF方向标记

7
我正在寻找一种最快的方法来创建缩小的位图,并遵守EXIF方向标签。
参考:https://weblogs.asp.net/bleroy/the-fastest-way-to-resize-images-from-asp-net-and-it-s-more-supported-ish 目前,我使用以下代码创建一个位图,以遵守EXIF方向标签。
  static Bitmap FixImageOrientation(Bitmap srce)
        {
            const int ExifOrientationId = 0x112;
            // Read orientation tag
            if (!srce.PropertyIdList.Contains(ExifOrientationId)) return srce;
            var prop = srce.GetPropertyItem(ExifOrientationId);
            var orient = BitConverter.ToInt16(prop.Value, 0);
            // Force value to 1
            prop.Value = BitConverter.GetBytes((short)1);
            srce.SetPropertyItem(prop);

            // Rotate/flip image according to <orient>
            switch (orient)
            {
                case 1:
                    srce.RotateFlip(RotateFlipType.RotateNoneFlipNone);
                    return srce;


                case 2:
                    srce.RotateFlip(RotateFlipType.RotateNoneFlipX);
                    return srce;

                case 3:
                    srce.RotateFlip(RotateFlipType.Rotate180FlipNone);
                    return srce;

                case 4:
                    srce.RotateFlip(RotateFlipType.Rotate180FlipX);
                    return srce;

                case 5:
                    srce.RotateFlip(RotateFlipType.Rotate90FlipX);
                    return srce;

                case 6:
                    srce.RotateFlip(RotateFlipType.Rotate90FlipNone);
                    return srce;

                case 7:
                    srce.RotateFlip(RotateFlipType.Rotate270FlipX);
                    return srce;

                case 8:
                    srce.RotateFlip(RotateFlipType.Rotate270FlipNone);
                    return srce;

                default:
                    srce.RotateFlip(RotateFlipType.RotateNoneFlipNone);
                    return srce;
            }
        }

我首先创建了一个方向固定的图像,然后按比例调整大小以进行快速处理。

  public static Bitmap UpdatedResizeImage(Bitmap source, Size size)
        {
            var scale = Math.Min(size.Width / (double)source.Width, size.Height / (double)source.Height);
            var bmp = new Bitmap((int)(source.Width * scale), (int)(source.Height * scale));

            using (var graph = Graphics.FromImage(bmp))
            {
                graph.InterpolationMode = InterpolationMode.High;
                graph.CompositingQuality = CompositingQuality.HighQuality;
                graph.SmoothingMode = SmoothingMode.AntiAlias;
                graph.DrawImage(source, 0, 0, bmp.Width, bmp.Height);
            }
            return bmp;
        }

现在,WIC 可以更快的进行图像处理。参考:https://dev59.com/Grbna4cB1Zd3GeqPXjxn#57987315

如何创建一个缩小的 BitmapImage 并保留 EXIF 标签?

更新:

if ((bitmapMetadata != null) && (bitmapMetadata.ContainsQuery("System.Photo.Orientation")))
            {
                object o = bitmapMetadata.GetQuery("System.Photo.Orientation");

                if (o != null)
                {
                    switch ((ushort)o)
                    {
                        case 3:
                            rotatedImage = new TransformedBitmap(resized, new RotateTransform(180));
                            break;
                        case 6:
                            rotatedImage = new TransformedBitmap(resized, new RotateTransform(90));
                            break;
                        case 8:
                            rotatedImage = new TransformedBitmap(resized, new RotateTransform(270));
                            break;

                    }

                }
            }

1
请参考以下链接:https://dev59.com/f0fRa4cB1Zd3GeqP5gV7 和 https://stackoverflow.com/questions/33914140/wpf-how-to-rotate-a-bitmapsource-by-any-angle。 - Simon Mourier
1
你面临的问题实际上有三个:在WPF中读取元数据、在WPF中旋转和在WPF中缩放。如果你只是独立地寻找这三个问题,你应该很容易地得到结果。 - Nyerguds
不,只需使用第一个链接中的元数据和第二个链接中的TransformedBitmap。您还可以保存元数据。请参见此处的另一个示例:https://www.markbetz.net/2011/05/31/creating-image-thumbs-with-transformedbitmap-and-scaletransform/ - Simon Mourier
@SimonMourier 我正在尝试将这些东西连接起来.. 请参见 https://www.paste.org/100655 。我需要一个单独的 BitmapDecoder 吗?因为你的代码示例中使用了它... 有没有一种方法可以从 BitmapFrame 创建 TransformedBitmap - techno
@SimonMourier 我尚未测试过这段代码。 - techno
显示剩余13条评论
1个回答

1
这里是一段示例代码,使用WPF类保存缩略图并保留图像方向(基于WIC互操作功能来确定给定文件扩展名的正确编码器,但这是可选的):
  static void Main()
  {
      SaveThumbnail("new.jpg", 64); // auto jpg
      SaveThumbnail("new.jpg", 64, "new.png"); // explicit png output
  }

  public static void SaveThumbnail(string inputFilePath, int thumbnailSize, string outputFilePath = null)
  {
      if (inputFilePath == null)
          throw new ArgumentNullException(inputFilePath);

      // decode frame
      var frame = BitmapDecoder.Create(new Uri(inputFilePath, UriKind.RelativeOrAbsolute), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None).Frames[0];

      // read input transformations
      var transformations = new Transformations(frame.Metadata as BitmapMetadata);

      int width;
      int height;
      if (frame.Width > frame.Height)
      {
          width = thumbnailSize;
          height = (int)(frame.Height * thumbnailSize / frame.Width);
      }
      else
      {
          width = (int)(frame.Width * thumbnailSize / frame.Height);
          height = thumbnailSize;
      }

      Guid containerFormat;
      if (outputFilePath == null)
      {
          // use input format same as decode.
          containerFormat = frame.Decoder.CodecInfo.ContainerFormat;
          outputFilePath = Path.ChangeExtension(inputFilePath, thumbnailSize + Path.GetExtension(inputFilePath));
      }
      else
      {
          // icing on the cake..., we determine the format from the output file extension, using some WIC voodoo (code below)
          // you could make it simpler and harcode things out but this way you can use other 3rd parties codecs
          containerFormat = WicUtilities.EnumerateDecoderFormatsForExtension(Path.GetExtension(outputFilePath)).FirstOrDefault();
          if (containerFormat == Guid.Empty) // this extension is not supported on this system
              throw new ArgumentNullException(outputFilePath);
      }

      var encoder = BitmapEncoder.Create(containerFormat);
      Transform transform = new ScaleTransform(width / frame.Width * 96 / frame.DpiX, height / frame.Height * 96 / frame.DpiY, 0, 0);

      // the jpeg encoder has a built-in flip & rotate system
      if (encoder is JpegBitmapEncoder jpeg)
      {
          // exif is counter clockwise
          switch (transformations.Rotation)
          {
              case Rotation.Rotate90:
                  jpeg.Rotation = Rotation.Rotate270;
                  break;

              case Rotation.Rotate180:
                  jpeg.Rotation = Rotation.Rotate180;
                  break;

              case Rotation.Rotate270:
                  jpeg.Rotation = Rotation.Rotate90;
                  break;
          }

          jpeg.FlipVertical = transformations.FlipVertical;
          jpeg.FlipHorizontal = transformations.FlipHorizontal;

          // option: change quality level here
          // jpeg.QualityLevel = xx
      }
      else
      {
          // other codecs need transform
          var group = new TransformGroup();

          // we must flip before rotate
          // https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/how-to-flip-a-uielement-horizontally-or-vertically
          if (transformations.FlipHorizontal)
          {
              group.Children.Add(new ScaleTransform(-1, 1, 0.5, 0.5));
          }

          if (transformations.FlipVertical)
          {
              group.Children.Add(new ScaleTransform(1, -1, 0.5, 0.5));
          }

          // exif is counter clockwise
          switch (transformations.Rotation)
          {
              case Rotation.Rotate90:
                  group.Children.Add(new RotateTransform(270));
                  break;

              case Rotation.Rotate180:
                  group.Children.Add(new RotateTransform(180));
                  break;

              case Rotation.Rotate270:
                  group.Children.Add(new RotateTransform(90));
                  break;
          }

          // I scale *after* rotate/flip, but it's up to you, not sure it changes anything in perf or quality...
          group.Children.Add(transform);
          transform = group;
      }

      var resized = BitmapFrame.Create(new TransformedBitmap(frame, transform));
      encoder.Frames.Add(resized);
      using (var stream = File.OpenWrite(outputFilePath))
      {
          encoder.Save(stream);
      }
  }

// helper class that exposes supported transformations (rotate/flip)
public class Transformations
{
    public Transformations(BitmapMetadata md)
    {
        // https://learn.microsoft.com/en-us/uwp/api/windows.storage.fileproperties.photoorientation
        // https://learn.microsoft.com/en-us/windows/win32/wic/-wic-photoprop-system-photo-orientation
        // https://learn.microsoft.com/en-us/windows/win32/properties/props-system-photo-orientation
        const string orientationProperty = "System.Photo.Orientation";
        if (md != null && md.ContainsQuery(orientationProperty))
        {
            var orientation = (Orientation)md.GetQuery(orientationProperty);
            switch (orientation)
            {
                case Orientation.FlipHorizontal:
                    FlipHorizontal = true;
                    break;

                case Orientation.FlipVertical:
                    FlipVertical = true;
                    break;

                case Orientation.Rotate90:
                    Rotation = Rotation.Rotate90;
                    break;

                case Orientation.Rotate180:
                    Rotation = Rotation.Rotate180;
                    break;

                case Orientation.Rotate270:
                    Rotation = Rotation.Rotate270;
                    break;

                case Orientation.Transpose:
                    Rotation = Rotation.Rotate90;
                    FlipHorizontal = true;
                    break;

                case Orientation.Transverse:
                    Rotation = Rotation.Rotate270;
                    FlipHorizontal = true;
                    break;
            }
        }
    }

    public Rotation Rotation { get; set; }
    public bool FlipHorizontal { get; set; }
    public bool FlipVertical { get; set; }
}

public enum Orientation : ushort
{
    Undefined,
    Normal,
    FlipHorizontal,
    Rotate180,
    FlipVertical,
    Transpose,
    Rotate270,
    Transverse,
    Rotate90
}

// some WIC tool, need System.Runtime.InteropServices namespace
public static class WicUtilities
{
    public static IEnumerable<Guid> EnumerateEncoderFormatsForExtension(string extension) => EnumerateFormatsForExtension(WICComponentType.WICEncoder, extension);
    public static IEnumerable<Guid> EnumerateDecoderFormatsForExtension(string extension) => EnumerateFormatsForExtension(WICComponentType.WICDecoder, extension);

    private static IEnumerable<Guid> EnumerateFormatsForExtension(WICComponentType type, string extension)
    {
        if (extension == null)
            throw new ArgumentNullException(nameof(extension));

        foreach (var info in EnumerateCodecs(type))
        {
            info.GetFileExtensions(0, null, out var len);
            if (len >= 0)
            {
                var sb = new StringBuilder(len);
                info.GetFileExtensions(len + 1, sb, out _);
                var supportedExtensions = sb.ToString().Split(',');
                if (supportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                {
                    if (info.GetContainerFormat(out var format) == 0)
                        yield return format;
                }
            }
        }
    }

    private static IEnumerable<IWICBitmapCodecInfo> EnumerateCodecs(WICComponentType type)
    {
        var wfac = (IWICImagingFactory)new WICImagingFactory();
        wfac.CreateComponentEnumerator(type, 0, out var unks);
        if (unks != null)
        {
            var array = new object[1];
            do
            {
                if (unks.Next(1, array, out var _) != 0)
                    break;

                yield return (IWICBitmapCodecInfo)array[0];
            }
            while (true);
        }
    }

    [Guid("CACAF262-9370-4615-A13B-9F5539DA4C0A"), ComImport]
    private class WICImagingFactory { }

    [Guid("00000100-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IEnumUnknown
    {
        [PreserveSig]
        int Next(int celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.IUnknown)] object[] rgelt, out int celtFetched);

        // we don't need the rest
    }

    [Flags]
    private enum WICComponentType
    {
        WICDecoder = 0x1,
        WICEncoder = 0x2,
        // we don't need the rest
    }

    [Guid("ec5ec8a9-c395-4314-9c77-54d7a935ff70"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICImagingFactory
    {
        void _VtblGap1_20(); // skip 20 methods we don't need

        [PreserveSig]
        int CreateComponentEnumerator(WICComponentType componentTypes, int options, out IEnumUnknown ppIEnumUnknown);

        // we don't need the rest
    }

    [Guid("E87A44C4-B76E-4c47-8B09-298EB12A2714"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IWICBitmapCodecInfo
    {
        void _VtblGap1_8(); // skip 8 methods we don't need

        [PreserveSig]
        int GetContainerFormat(out Guid pguidContainerFormat);

        void _VtblGap2_5(); // skip 5 methods we don't need

        [PreserveSig]
        int GetFileExtensions(int cchFileExtensions, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder wzFileExtensions, out int pcchActual);

        // we don't need the rest
    }
}

我已经实现了这个 .. https://www.paste.org/102872 但是在涉及翻转时它会生成单行图像。从你的答案中,我得知ScaleTransform需要4个值。请看一下..还有什么情况下需要垂直翻转? - techno
你的代码存在一些问题。对于非JPEG编码器,生成的方向不正确。我硬编码了PNG编码器。请下载这个zip文件,其中包含带有方向标签的示例图像、exiftool和用于使用exiftool生成这些图像的批处理脚本。请将Windows资源管理器预览与您的输出进行比较。https://drive.google.com/file/d/1aEMWmMgqBPgzHB5YsGA0dC93Q8FqzMb0/view?usp=sharing - techno
谢谢,但对于Newfile4和6问题仍然存在。请使用资源管理器预览图像进行交叉检查。 - techno
我没有更新 Transformations。现在它正常工作了。希望额外的计算不会影响性能。 - techno
仅仅是一个更新。这段代码没有遵循图像的嵌入式颜色方案。对于位图,我可以设置 useEmbeddedColorManagement=true ,如何让这段代码遵循图像的颜色方案?请给予建议。 - techno
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接