사진 섬네일 만들기

간단하게 구현해 보았습니다.

주요 부분을 설명하자면

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