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






4.83/5 (5投票s)
在本文中,我将展示简单的程序如何帮助改进您的摄影工作流程。Acme Photo Resizer 可以批量调整 JPG 图像的大小。它还可以为每张图像添加版权声明和照片的文件名。我写它是为了帮助我调整赛车照片的大小,以便我能够将它们发布到
引言
Acme Photo Resizer 是一款简单图像处理程序,旨在批量处理图像的调整大小。
背景
为了支持当地的跑步社区,我为没有官方摄影师的当地比赛拍摄照片。我将它们发布到比赛网站和 Facebook。当您需要发布数百张图像时,您需要将文件大小减小到适合网站的大小。Facebook 近期已经改进了很多,并且可以显示 2048px 宽的图像。我需要一种轻松一次性调整数百张图像大小的方法。虽然我不收取赛车照片的费用,但我喜欢在每张照片上放置版权声明和文件名。这就是我编写此程序的动机。
我非常感谢 Code Project 的贡献者 Lev Danielyan 提供的 `EXIF 元数据提取库`。我使用 EXIF 方向标签来旋转以纵向模式拍摄的图像。
选择图像
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 的第一个版本