使用 WIC 快速创建符合 EXIF 方向标记的缩小位图
Using WIC to Quickly Create Scaled Down Bitmap that honors EXIF Orientation Tag
我正在寻找创建支持 EXIF 方向标记的缩小位图的最快方法
目前我使用以下代码创建一个支持 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 允许更快的图像 manipulation.Ref:
我如何创建一个按比例缩小的位图图像,它支持 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;
}
}
}
下面是一些示例代码,它基于 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://docs.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://docs.microsoft.com/en-us/uwp/api/windows.storage.fileproperties.photoorientation
// https://docs.microsoft.com/en-us/windows/win32/wic/-wic-photoprop-system-photo-orientation
// https://docs.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
}
}
我正在寻找创建支持 EXIF 方向标记的缩小位图的最快方法
目前我使用以下代码创建一个支持 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 允许更快的图像 manipulation.Ref:
我如何创建一个按比例缩小的位图图像,它支持 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;
}
}
}
下面是一些示例代码,它基于 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://docs.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://docs.microsoft.com/en-us/uwp/api/windows.storage.fileproperties.photoorientation
// https://docs.microsoft.com/en-us/windows/win32/wic/-wic-photoprop-system-photo-orientation
// https://docs.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
}
}