使用C#进行图片转换格式,缩放,自动旋转,保留exif

2010-06-02 20:37 by hackerzhou

这几天心血来潮做了一个批量图片缩放,转换格式,并且可以根据exif的信息旋转图片,校正exif信息后保存的小程序.根据配置文件指定需要的功能.

using System;
using System.IO;
using System.Drawing.Imaging;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Text;

namespace PhotoUtil
{
    class Program
    {
        [DllImport("kernel32")]
        private static extern long GetPrivateProfileString(string section, string key,
            string def, StringBuilder retVal, int size, string filePath);
        private const String configFile = "Config.ini";

        public static void Main(string[] args)
        {
            DirectoryInfo workingDir = new DirectoryInfo(ReadConfig("General", "WorkingDir",Environment.CurrentDirectory));
            if (!workingDir.Exists)
            {
                workingDir = new DirectoryInfo(Environment.CurrentDirectory);
            }
            int quality=int.Parse(ReadConfig("General", "Quality", "85"));
            bool needResize = Boolean.Parse(ReadConfig("ResizeImage", "Enable", "false"));
            int newWidth = int.Parse(ReadConfig("ResizeImage", "NewWidth", "800"));
            int newHeight = int.Parse(ReadConfig("ResizeImage", "NewHeight", "600"));
            bool padding = Boolean.Parse(ReadConfig("ResizeImage", "Padding", "false"));
            bool needRotate = Boolean.Parse(ReadConfig("RotateImage", "Enable", "true"));
            FileInfo[] files = workingDir.GetFiles();
            DirectoryInfo output = workingDir.CreateSubdirectory(DateTime.Now.ToString("yyyyMMdd") + "转换\r
            foreach (FileInfo i in files)
            {
                String type = i.Extension.ToLower();
                if (type.Contains("jpg") || type.Contains("jpeg") || (type.Contains("png")) || type.Contains("tif") || type.Contains("bmp"))
                {
                    Image img = Image.FromFile(i.FullName);
                    if (needResize)
                    {
                        Console.WriteLine("Resizing " + i.FullName);
                        ResizeImage(ref img, newWidth, newHeight, padding);
                    }
                    if (needRotate)
                    {
                        Console.WriteLine("Rotating " + i.FullName);
                        RotateImage(img);
                    }
                    SaveAs(img, output.FullName+"\\\\"+i.Name, quality);
                }
            }
            Console.ReadLine();
        }

        private static void SaveAs(Image img, string dest, long quality)
        {
            if (quality > 100 || quality < 1)
            {
                quality = 85;
            }
            EncoderParameters para = new EncoderParameters();
            para.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
            String extension = new FileInfo(dest).Extension;
            ImageCodecInfo info = GetImageCodecInfoByExtension(extension);
            if (info != null)
            {
                img.Save(dest, info, para);
            }
            else
            {
                throw new Exception("Unrecognized  format \\"" + extension + "\\"!\r
            }
        }

        private static void ResizeImage(ref Image image, int expectDestWidth, int expectDestHeight,bool padding)
        {
            PropertyItem[] exif = image.PropertyItems;
            int targetWidth = 0;
            int targetHeight = 0;
            double srcHWRate = (double)image.Width / (double)image.Height;
            double expectHWRate = (double)expectDestWidth / (double)expectDestHeight;
            if (srcHWRate > expectHWRate)
            {
                targetWidth = expectDestWidth;
                targetHeight = System.Convert.ToInt32(Math.Round(expectDestWidth / srcHWRate, 0));
            }
            else
            {
                targetHeight = expectDestHeight;
                targetWidth = System.Convert.ToInt32(Math.Round(expectDestHeight * srcHWRate, 0));
            }

            Image bitmap = null;
            if (!padding)
            {
                bitmap = new Bitmap(targetWidth, targetHeight);
            }
            else
            {
                bitmap = new Bitmap(expectDestWidth, expectDestHeight);
            }
            Graphics g = Graphics.FromImage(bitmap);
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.DrawImage(image, new Rectangle(0, 0, bitmap.Width, bitmap.Height), new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
            foreach (PropertyItem i in exif)
            {
                if (i.Id == 40962)
                {
                    i.Value = BitConverter.GetBytes(targetWidth);
                }
                else if (i.Id == 40963)
                {
                    i.Value = BitConverter.GetBytes(targetHeight);
                }
                bitmap.SetPropertyItem(i);
            }
            g.Dispose();
            image.Dispose();
            image = bitmap;
        }

        private static string ReadConfig(String Section, String Key, String defaultValue)
        {
            if (File.Exists(configFile))
            {
                StringBuilder temp = new StringBuilder(1024);
                GetPrivateProfileString(Section, Key, String.Empty, temp, 1024, new FileInfo(configFile).FullName);
                if (!String.IsNullOrEmpty(temp.ToString()))
                {
                    return temp.ToString();
                }
                else
                {
                    return defaultValue;
                }
            }
            else
            {
                return defaultValue;
            }
        }

        public static void RotateImage(Image img)
        {
            PropertyItem[] exif = img.PropertyItems;
            byte orientation = 0;
            foreach (PropertyItem i in exif)
            {
                if (i.Id == 274)
                {
                    orientation = i.Value[0];
                    i.Value[0] = 1;
                    img.SetPropertyItem(i);
                }
            }

            switch (orientation)
            {
                case 2:
                    img.RotateFlip(RotateFlipType.RotateNoneFlipX);
                    break;
                case 3:
                    img.RotateFlip(RotateFlipType.Rotate180FlipNone);
                    break;
                case 4:
                    img.RotateFlip(RotateFlipType.RotateNoneFlipY);
                    break;
                case 5:
                    img.RotateFlip(RotateFlipType.Rotate90FlipX);
                    break;
                case 6:
                    img.RotateFlip(RotateFlipType.Rotate90FlipNone);
                    break;
                case 7:
                    img.RotateFlip(RotateFlipType.Rotate270FlipX);
                    break;
                case 8:
                    img.RotateFlip(RotateFlipType.Rotate270FlipNone);
                    break;
                default:
                    break;
            }
            foreach (PropertyItem i in exif)
            {
                if (i.Id == 40962)
                {
                    i.Value = BitConverter.GetBytes(img.Width);
                }
                else if (i.Id == 40963)
                {
                    i.Value = BitConverter.GetBytes(img.Height);
                }
            }
        }

        private static ImageCodecInfo GetImageCodecInfoByExtension(String extension)
        {
            ImageCodecInfo[] list = ImageCodecInfo.GetImageEncoders();
            foreach(ImageCodecInfo i in list)
            {
                if (i.FilenameExtension.ToLower().Contains(extension.ToLower()))
                {
                    return i;
                }
            }
            return null;
        }
    }
}

配置文件名称:Config.ini,放在和程序同目录下,格式如下:
[General]
#工作目录
WorkingDir=.
#输出质量,1-100范围内的整数
Quality=80

[ResizeImage]
#是否启用
Enable=true
#新的宽值和高值
NewWidth=1024
NewHeight=768
#是否对超出原来比例的部分进行填充
Padding=false

#是否使用Exif中的旋转信息对图片进行旋转
[RotateImage]
Enable=true

1.保留exif信息的技巧是获取原始的Image.PropertyItems,对新图片添加即可.
2.关于Exif Orientation标志的定义 http://sylvana.net/jpegcrop/exif_orientation.html
3.变更jpeg输出质量的方法:
EncoderParameters para = new EncoderParameters();
para.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
//这里的quality是从1-100的long值,一开始想当然的以为1-100的值就用了byte,结果出错了.
最后调用Image.Save(String, ImageCodecInfo, EncoderParameters)来进行输出.
4.进行缩放/旋转后需要调整原始的exif信息,id查文档可知.列出一些
id=40962,width
id=40963,height
id=274,orientation type
对PropertyItem信息设置完了别忘了Image.SetPropertyItem(PropertyItem)添加到image中,我出一个很傻的bug,就是在修改了orientation type=1之后没有SetPropertyItem,于是导致图片实际被旋转至正确角度了,但是用ACDSee打开后可以看到exif的旋转信息依旧是原来的值.

本文基于 署名 2.5 中国大陆 许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 hackerzhou 并包含 原文链接
发表评论

本文有 2 条评论

  1. hackerzhou
    2010-10-13 21:02


    馬克:

    rotate中的
    orientation = i.Value[0];
    i.Value[0] = 1;
    似乎有問題!
    是不是直接傳入函數參數orientation,指定旋轉參數就好了?

    没有问题啊,旋转方向是储存在Exif结构体里的,有1-8几个可能的值,而我需要做的就是把2-8这些需要旋转的都转成1这样的方向,然后把旋转方向赋成1。你去看一下Exif的文档就明白了。

  2. 馬克
    2010-10-13 16:50

    rotate中的
    orientation = i.Value[0];
    i.Value[0] = 1;
    似乎有問題!
    是不是直接傳入函數參數orientation,指定旋轉參數就好了?

发表评论