65.9K
CodeProject 正在变化。 阅读更多。
Home

Acme 照片调整器是一个 C# 程序, 用于批量调整图片大小

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (5投票s)

2016年1月20日

CPOL

4分钟阅读

viewsIcon

13190

downloadIcon

605

在本文中,我将展示简单的程序如何帮助改进您的摄影工作流程。Acme Photo Resizer 可以批量调整 JPG 图像的大小。它还可以为每张图像添加版权声明和照片的文件名。我写它是为了帮助我调整赛车照片的大小,以便我能够将它们发布到

引言

Acme Photo Resizer 是一款简单图像处理程序,旨在批量处理图像的调整大小。

背景

为了支持当地的跑步社区,我为没有官方摄影师的当地比赛拍摄照片。我将它们发布到比赛网站和 Facebook。当您需要发布数百张图像时,您需要将文件大小减小到适合网站的大小。Facebook 近期已经改进了很多,并且可以显示 2048px 宽的图像。我需要一种轻松一次性调整数百张图像大小的方法。虽然我不收取赛车照片的费用,但我喜欢在每张照片上放置版权声明和文件名。这就是我编写此程序的动机。

我非常感谢 Code Project 的贡献者 Lev Danielyan 提供的 `EXIF 元数据提取库`。我使用 EXIF 方向标签来旋转以纵向模式拍摄的图像。

照片调整为 600px 宽。

选择图像

Acme Photo Resizer 界面非常基础。您只需指定调整大小选项,输入可选的版权持有人姓名,选择一组 JPG 文件,然后单击处理按钮。程序会显示进度条,并在列表框中列出已处理的文件。它将调整大小后的图像放入包含所选 JPG 文件的文件夹中名为“Resized”的子文件夹中。

EXIF 标签中的“方向”标签用于确定照片的方向。旋转是为了确保照片以正确的方向进行调整大小。

性能和多线程

在我写这篇文章的时候,我突然想到可以使用多线程来提高性能。由于程序会遍历一组文件,因此将其转换为使用 `Parallel` 类的 `ForEach` 方法相对容易。

         Task t = Task.Factory.StartNew(() =>
         {
            if (Parallel.ForEach(_Files, f =>
            {
               if (f.EndsWith(".jpg")) {
                  ResizeImageFile(f, dir, perCent, pixelWidth, pixelHeight, copyrightHolder, flipHorizontal, flipVertical);
                  ReportProgress(f);
               }
            }).IsCompleted) {
               Complete();
            }
         });

棘手的部分是在多线程环境中更新控件。控件只能在其创建的线程上访问。尝试直接从线程内部访问它们会引发异常。我有一个更新进度条和列表框的方法,并且我希望保留这些功能。我偶然发现了 Pablo Grisafi 的这篇 Code Project 文章,它提供了一个优雅的解决方案,所以我采用了他的技术。我将以下扩展方法类附加到窗体代码文件中。

    static class FormExtensions
    {
        static public void UIThread(this Form form, MethodInvoker code)
        {
            if (form.InvokeRequired)
            {
                form.Invoke(code);
                return;
            }
            code.Invoke();
        }
    }

这使我能够非常简单地实现 `ReportProgress`。

      private void ReportProgress(string f)
      {
         this.UIThread(delegate
         {
            proBar.Increment(1);
            lstResized.Items.Add(f);
         });
      }

我还需要一种方法在所有线程完成后进行清理。我使用了相同的技术。

      private void Complete()
      {
         this.UIThread(delegate
         {
            //
            // Ensure progres bar is completed
            proBar.Value = proBar.Maximum;
            //
            // Reset cursor and re-enable form.
            Cursor.Current = Cursors.Default;
            this.Enabled = true;
         });
      }

调整图像大小

执行调整大小的方法使用标准的 .Net 库。

        using System.Drawing;
        using System.Drawing.Imaging;
        using System.Drawing.Drawing2D;
        using System.Drawing.Text;

它相当大。我是根据网上找到的各种示例构建的。我总是说“Google 是我的手册”。

      private static Image ScaleImage(Image imgPhoto, int? perCent, int? pixelWidth, int? pixelHeight, int orientation, ref Rectangle dest)
      {
         int sourceWidth = imgPhoto.Width;
         int sourceHeight = imgPhoto.Height;
         int sourceX = 0;
         int sourceY = 0;
         int destWidth = 0;
         int destHeight = 0;
         int destX = 0;
         int destY = 0;
         //
         // Calculate the width and height of the resized photo
         // depending on which option was chosen.
         if (perCent != null) {
            if (perCent == 100) {
               return imgPhoto;
            }
            double nPercent = ((double)perCent / 100);
            destWidth = (int)(sourceWidth * nPercent);
            destHeight = (int)(sourceHeight * nPercent);
         }
         else if (pixelWidth != null) {
            if (sourceWidth == (int)(pixelWidth)) {
               return imgPhoto;
            }
            destWidth = (int)(pixelWidth);
            destHeight = (int)((destWidth * sourceHeight) / sourceWidth);
         }
         else {
            if (sourceHeight == (int)(pixelHeight)) {
               return imgPhoto;
            }
            destHeight = (int)pixelHeight;
            destWidth = (int)((destHeight * sourceWidth) / sourceHeight);
         }
         //
         // Create a bitmap for the resized photo
         Bitmap bmPhoto = new Bitmap(destWidth, destHeight, PixelFormat.Format24bppRgb);
         bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);
         //
         // Create a graphics object from the bitmap and set attributes for highest quality
         Graphics grPhoto = Graphics.FromImage(bmPhoto);
         grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
         grPhoto.SmoothingMode = SmoothingMode.HighQuality;
         grPhoto.CompositingQuality = CompositingQuality.HighQuality;
         grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;
         //
         // Draw the new image
         grPhoto.DrawImage(imgPhoto, 
                           new Rectangle(destX, destY, destWidth, destHeight), 
                           new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight), 
                           GraphicsUnit.Pixel);
         //
         // Reorient, if necessary
         if (orientation == 8) {
            bmPhoto.RotateFlip(RotateFlipType.Rotate270FlipNone);
         }
         else if (orientation == 6) {
            bmPhoto.RotateFlip(RotateFlipType.Rotate90FlipNone);
         }
         //
         // return the Bit map and save its dimensions
         dest.Width = bmPhoto.Width;
         dest.Height = bmPhoto.Height;
         grPhoto.Dispose();
         return bmPhoto;
      }

翻转图像

有时您会得到水平或垂直翻转的图像。在我的例子中,是一些旧的幻灯片和底片,我从错误的一面拍摄了它们。我常用的处理程序 DxO Optics Pro 没有翻转功能,所以我为 Acme Photo Resizer 添加了此功能。翻转只需要一行代码。 `x.RotateFlip(RotateFlipType.RotateNoneFlipX);`

保存图片

Microsoft 文档 说“GDI+ 使用图像编码器将存储在 Bitmap 对象中的图像转换为各种文件格式。GDI+ 内置了 BMP、JPEG、GIF、TIFF 和 PNG 格式的编码器。当调用 Image 对象的 Save 或 SaveAdd 方法时,会调用编码器。”我使用了以下方法使用 JPEG 编码器保存文件。

      private void SaveJpeg(string path, Bitmap img, long quality)
      {
         // Encoder parameter for image quality
         EncoderParameter qualityParam =
             new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
         // Jpeg image codec
         ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
         if (jpegCodec == null)
            return;
         EncoderParameters encoderParams = new EncoderParameters(1);
         encoderParams.Param[0] = qualityParam;
         img.Save(path, jpegCodec, encoderParams);
      }
      private ImageCodecInfo GetEncoderInfo(string mimeType)
      {
         // Get image codecs for all image formats
         ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
         // Find the correct image codec
         for (int i = 0; i < codecs.Length; i++) {
            if (codecs[i].MimeType == mimeType) {
               return codecs[i];
            }
         }
         return null;
      }

我将 `90L` 传递给质量参数。这似乎可以提供良好的质量和方便的文件大小。

写入版权声明

将文本写入图像涉及几个步骤。这是我使用的代码。

        //
        // Get graphics object from bitmap
        Graphics g = Graphics.FromImage(y);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        //
        // Figure out fontsize relative to image width
        int fontSize = (int)(_FontSize * 350.0 / x.HorizontalResolution);
        //
        // Specify font. Currently hard-coded.
        Font font = new Font("Lucida Handwriting", fontSize, FontStyle.Bold);
        int picNameWidth = (int)g.MeasureString(picName, font).Width;
        //
        // Write out notice and picture name in black, then white offset by 2 pixels
        g.DrawString(copyrightNotice, font, Brushes.Black, new Point(10, dest.Height - 50));
        g.DrawString(copyrightNotice, font, Brushes.White, new Point(12, dest.Height - 48));
        g.DrawString(picName, font, Brushes.Black, new Point(dest.Width - 10 - picNameWidth, dest.Height - 50));
        g.DrawString(picName, font, Brushes.White, new Point(dest.Width - 8 - picNameWidth, dest.Height - 48));
        g.Dispose();

我使用黑色和白色写入文本,以产生轻微的阴影效果。这意味着即使背景非常浅或非常深,文本仍然可读。

帮助

我创建了一个 HTML 格式的帮助文件。单击 `Help` 按钮时会显示帮助文件。

使用代码

这段代码使用了中级的 C# 编码技术。它可以作为需要调整图像大小的应用程序的起点。它还说明了如何获取 EXIF 数据以及如何将照片旋转到正确的方向。

它是多线程的,并且能够更新进度条和列表框而不会出现跨线程问题,这要归功于 Pablo Grisafi

作为一名摄影师,我发现这个程序大大简化了将大型图像库上传到 Facebook 等网站的工作。

关注点

Acme Photo Resizer 展示了如何调整、旋转、翻转和保存图像。它使用 `Parallel` 类来提高多核处理器计算机的性能。

历史

Code-Project 的第一个版本

© . All rights reserved.