Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using WIC to Quickly Create Scaled Down Bitmap that honors EXIF Orientation Tag

I'm looking for the fastest way to create scaled down bitmap that honors EXIF orientation tag

Ref :https://weblogs.asp.net/bleroy/the-fastest-way-to-resize-images-from-asp-net-and-it-s-more-supported-ish

Currently i use the following code to create a Bitmap that honors EXIF Orientation tag

  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;
            }
        }

I'm first creating a orientation fixed image ,then resizing it (preserving aspect ratio) for fast processing.

  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;
        }

Now WIC allows much faster image manipulation.Ref:https://stackoverflow.com/a/57987315/848968

How can i create a Scaled Down BitmapImage that honours the EXIF tag

Update:

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;

                    }

                }
            }
like image 922
techno Avatar asked Sep 23 '19 11:09

techno


1 Answers

Here is some sample code that saves a thumbnail while preserving the image orientation, based on WPF classes (and a small WIC interop fun to determine the proper encoder for a given file extension, but this is optional):

  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
    }
}
like image 197
Simon Mourier Avatar answered Nov 04 '22 15:11

Simon Mourier