간단하게 구현해 보았습니다.
주요 부분을 설명하자면
// .NET Framework 에서 제공하는 Built-in JPEG 인코더를 구해서 var jpegEncoder = ImageCodecInfo.GetImageEncoders().Where(info => info.FormatID == ImageFormat.Jpeg.Guid).FirstOrDefault(); // 인코딩 파라미터로 화질을 결정하고 var encoderParams = new EncoderParameters(1); var encoderParam = new EncoderParameter(Encoder.Quality, 50L); encoderParams.Param[0] = encoderParam; // 원본을 읽어서 var srcImage = new Bitmap(@"C:\Photo\SourceFile.JPG"); // 리사이징 한 후에 var newImage = new Bitmap(srcImage, newSize); // 원본의 EXIF 정보를 그대로 옮기고 foreach (var propItem in srcImage.PropertyItems) newImage.SetPropertyItem(propItem); // 저장 합니다 newImage.Save(@"C:\Photo\Thumbnail.JPG", jpegEncoder, encoderParams);
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Threading.Tasks; namespace BSPFP { class BSException : Exception { public BSException() : base() { } public BSException(string msg) : base(msg) { } public BSException(string fmt, params object[] args) : base(string.Format(fmt, args)) { } } class Program { static readonly int longSideLength = 640; // 섬네일의 긴쪽 길이 static readonly long quality = 50L; // 섬네일 화질 (0 - 100) static readonly HashSet<string> srcFolders = new HashSet<string> { Path.GetFullPath(Path.Combine(@"C:\Photo\사진 원본 1", ".")), Path.GetFullPath(Path.Combine(@"C:\Photo\사진 원본 2", ".")), }; static readonly string destFolder = Path.GetFullPath(Path.Combine(@"C:\Photo\사진 섬네일", ".")); static readonly HashSet<string> targetExt = new HashSet<string> { ".jpeg", ".jpg" }; static readonly int concurrentCount = Environment.ProcessorCount; static void CheckReadOnlyVariables() { foreach (var srcFolder in srcFolders) { if (string.Compare(srcFolder, destFolder, true) == 0) throw new BSException("원본, 대상 폴더가 같음: {0}: {1}", srcFolder, destFolder); if (destFolder.ToLower().StartsWith(srcFolder.ToLower())) throw new BSException("대상이 원본 하위 폴더: {0}: {1}", srcFolder, destFolder); } } static ConcurrentQueue<Tuple<string, string>> CollectSourceFiles() { var ret = new ConcurrentQueue<Tuple<string, string>>(); foreach (var srcFolder in srcFolders) { var folderQueue = new Queue<string>(); folderQueue.Enqueue(srcFolder); while (folderQueue.Count > 0) { var f = folderQueue.Dequeue(); foreach (var e in Directory.EnumerateFileSystemEntries(f)) { var fi = new FileInfo(e); if ((fi.Attributes & FileAttributes.Directory) != 0) { folderQueue.Enqueue(e); continue; } if (!targetExt.Contains(fi.Extension.ToLower())) continue; ret.Enqueue(new Tuple<string, string>(srcFolder, fi.FullName)); } } } return ret; } static int Main() { try { CheckReadOnlyVariables(); var srcFiles = CollectSourceFiles(); var tasks = new Task[concurrentCount]; for (var i = 0; i < concurrentCount; i++) { tasks[i] = Task.Factory.StartNew(() => { var jpegEncoder = ImageCodecInfo.GetImageEncoders().Where(info => info.FormatID == ImageFormat.Jpeg.Guid).FirstOrDefault(); using (var encoderParams = new EncoderParameters(1)) using (var encoderParam = new EncoderParameter(Encoder.Quality, quality)) { encoderParams.Param[0] = encoderParam; while (srcFiles.TryDequeue(out Tuple<string, string> src)) { using (var srcImage = new Bitmap(src.Item2)) { Size newSize; if (srcImage.Size.Width >= srcImage.Size.Height) newSize = new Size(longSideLength, (int)((double)srcImage.Size.Height * longSideLength / srcImage.Size.Width)); else newSize = new Size((int)((double)srcImage.Size.Width * longSideLength / srcImage.Size.Height), longSideLength); using (var newImage = new Bitmap(srcImage, newSize)) { foreach (var propItem in srcImage.PropertyItems) newImage.SetPropertyItem(propItem); var subPath = src.Item2.Substring(src.Item1.Length + 1); var folder = Path.GetDirectoryName(Path.GetFullPath(Path.Combine(destFolder, subPath))); var filename = Path.GetFileNameWithoutExtension(src.Item2); var destFile = Path.Combine(folder, filename + ".jpg"); if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); Console.WriteLine(subPath); newImage.Save(destFile, jpegEncoder, encoderParams); } } } } }); } Task.WaitAll(tasks); for (var i = 0; i < concurrentCount; i++) tasks[i].Dispose(); } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); return 1; } return 0; } } }