你还在为批量处理大量图片而头疼吗?设计师需要将几百张产品图片统一缩放,运营同学要批量压缩社交媒体素材,开发者要为移动端适配不同尺寸的图标...
今天,我将分享一个完整的C#批量图片处理解决方案,让你1分钟处理1000张图片,彻底告别重复劳动!
在实际开发中,我们经常遇到这些场景:
手动处理这些任务不仅效率低下,还容易出错。今天我们就用C#打造一个专业级的批量处理工具!
我们选择SkiaSharp作为图片处理库,原因如下:
让我们逐步构建这个批量图片处理工具:
C#private void BtnAddFiles_Click(object sender, EventArgs e)
{
using (var openDialog = new OpenFileDialog())
{
// 设置支持的图片格式过滤器
openDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff";
openDialog.Multiselect = true; // 允许多选
if (openDialog.ShowDialog() == DialogResult.OK)
{
foreach (string file in openDialog.FileNames)
{
// 避免重复添加同一文件
if (!fileListBox.Items.Contains(file))
{
fileListBox.Items.Add(file);
}
}
}
}
}
🎯 关键要点:
OpenFileDialog的Multiselect属性实现批量选择Filter属性限制文件类型,提升用户体验C#private void BtnOutputFolder_Click(object sender, EventArgs e)
{
using (var folderDialog = new FolderBrowserDialog())
{
if (folderDialog.ShowDialog() == DialogResult.OK)
{
outputFolder = folderDialog.SelectedPath;
// 实时更新状态栏,让用户知道当前设置
statusLabel.Text = $"输出文件夹: {outputFolder}";
}
}
}
C#private async void BtnStart_Click(object sender, EventArgs e)
{
// 参数验证 - 确保用户输入完整
if (fileListBox.Items.Count == 0)
{
MessageBox.Show("请先添加要处理的文件!", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrEmpty(outputFolder))
{
MessageBox.Show("请选择输出文件夹!", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 防止重复点击
btnStart.Enabled = false;
// 初始化进度条
progressBar.Maximum = fileListBox.Items.Count;
progressBar.Value = 0;
// 获取用户设置的缩放比例和输出格式
var scale = (float)scaleNumeric.Value / 100f;
var format = GetBatchFormat();
// 异步处理,避免UI卡顿
await Task.Run(() => ProcessBatch(scale, format));
btnStart.Enabled = true;
MessageBox.Show("批量处理完成!", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
🔥 性能优化要点:
async/await模式避免UI阻塞Task.Run将耗时操作移至后台线程C#private void ProcessBatch(float scale, SKEncodedImageFormat format)
{
for (int i = 0; i < fileListBox.Items.Count; i++)
{
string inputFile = fileListBox.Items[i].ToString();
// 跨线程更新UI - 重要!
this.Invoke(new Action(() => {
statusLabel.Text = $"正在处理: {Path.GetFileName(inputFile)}";
progressBar.Value = i;
}));
try
{
// 使用using确保资源正确释放
using (var bitmap = SKBitmap.Decode(inputFile))
{
if (bitmap != null)
{
// 计算新尺寸
int newWidth = (int)(bitmap.Width * scale);
int newHeight = (int)(bitmap.Height * scale);
using (var scaledBitmap = new SKBitmap(newWidth, newHeight))
using (var canvas = new SKCanvas(scaledBitmap))
using (var paint = new SKPaint {
IsAntialias = true, // 抗锯齿
FilterQuality = SKFilterQuality.High // 高质量缩放
})
{
// 执行缩放绘制
canvas.DrawBitmap(bitmap,
new SKRect(0, 0, newWidth, newHeight), paint);
// 生成输出文件名
string outputFile = Path.Combine(outputFolder,
Path.GetFileNameWithoutExtension(inputFile) +
"_scaled" + GetExtension(format));
// 保存文件
using (var fileStream = new FileStream(outputFile, FileMode.Create))
using (var skStream = new SKManagedWStream(fileStream))
{
scaledBitmap.Encode(skStream, format, 95); // 95%质量
}
}
}
}
}
catch (Exception ex)
{
// 错误处理 - 单个文件失败不影响整体处理
this.Invoke(new Action(() => {
MessageBox.Show($"处理文件 {Path.GetFileName(inputFile)} 时发生错误:{ex.Message}",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}));
}
}
// 更新最终状态
this.Invoke(new Action(() => {
progressBar.Value = fileListBox.Items.Count;
statusLabel.Text = "处理完成";
}));
}
💎 核心技术点:
using语句确保SKBitmap等资源及时释放SKFilterQuality.High提供最佳视觉效果IsAntialias = true让缩放后的图片更平滑Invoke确保后台线程安全更新UIC#private SKEncodedImageFormat GetBatchFormat()
{
switch (outputFormatCombo.SelectedIndex)
{
case 0: return SKEncodedImageFormat.Png; // 无损格式,适合图标
case 1: return SKEncodedImageFormat.Jpeg; // 有损格式,适合照片
case 2: return SKEncodedImageFormat.Bmp; // 位图格式
default: return SKEncodedImageFormat.Png;
}
}
private string GetExtension(SKEncodedImageFormat format)
{
switch (format)
{
case SKEncodedImageFormat.Png: return ".png";
case SKEncodedImageFormat.Jpeg: return ".jpg";
case SKEncodedImageFormat.Bmp: return ".bmp";
default: return ".png";
}
}
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SkiaSharp;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace AppGraphicsScaling
{
public partial class FrmBatchProcess : Form
{
private string outputFolder;
public FrmBatchProcess()
{
InitializeComponent();
}
private void BtnAddFiles_Click(object sender, EventArgs e)
{
using (var openDialog = new OpenFileDialog())
{
openDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff";
openDialog.Multiselect = true;
if (openDialog.ShowDialog() == DialogResult.OK)
{
foreach (string file in openDialog.FileNames)
{
if (!fileListBox.Items.Contains(file))
{
fileListBox.Items.Add(file);
}
}
}
}
}
private void BtnRemoveFile_Click(object sender, EventArgs e)
{
var selectedItems = fileListBox.SelectedItems.Cast<string>().ToList();
foreach (string item in selectedItems)
{
fileListBox.Items.Remove(item);
}
}
private void BtnClearAll_Click(object sender, EventArgs e)
{
fileListBox.Items.Clear();
}
private void BtnOutputFolder_Click(object sender, EventArgs e)
{
using (var folderDialog = new FolderBrowserDialog())
{
if (folderDialog.ShowDialog() == DialogResult.OK)
{
outputFolder = folderDialog.SelectedPath;
statusLabel.Text = $"输出文件夹: {outputFolder}";
}
}
}
private async void BtnStart_Click(object sender, EventArgs e)
{
if (fileListBox.Items.Count == 0)
{
MessageBox.Show("请先添加要处理的文件!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrEmpty(outputFolder))
{
MessageBox.Show("请选择输出文件夹!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
btnStart.Enabled = false;
progressBar.Maximum = fileListBox.Items.Count;
progressBar.Value = 0;
var scale = (float)scaleNumeric.Value / 100f;
var format = GetBatchFormat();
await Task.Run(() => ProcessBatch(scale, format));
btnStart.Enabled = true;
MessageBox.Show("批量处理完成!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void ProcessBatch(float scale, SKEncodedImageFormat format)
{
for (int i = 0; i < fileListBox.Items.Count; i++)
{
string inputFile = fileListBox.Items[i].ToString();
this.Invoke(new Action(() => {
statusLabel.Text = $"正在处理: {Path.GetFileName(inputFile)}";
progressBar.Value = i;
}));
try
{
using (var bitmap = SKBitmap.Decode(inputFile))
{
if (bitmap != null)
{
int newWidth = (int)(bitmap.Width * scale);
int newHeight = (int)(bitmap.Height * scale);
using (var scaledBitmap = new SKBitmap(newWidth, newHeight))
using (var canvas = new SKCanvas(scaledBitmap))
using (var paint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High })
{
canvas.DrawBitmap(bitmap, new SKRect(0, 0, newWidth, newHeight), paint);
string outputFile = Path.Combine(outputFolder,
Path.GetFileNameWithoutExtension(inputFile) + "_scaled" + GetExtension(format));
using (var fileStream = new FileStream(outputFile, FileMode.Create))
using (var skStream = new SKManagedWStream(fileStream))
{
scaledBitmap.Encode(skStream, format, 95);
}
}
}
}
}
catch (Exception ex)
{
this.Invoke(new Action(() => {
MessageBox.Show($"处理文件 {Path.GetFileName(inputFile)} 时发生错误:{ex.Message}",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}));
}
}
this.Invoke(new Action(() => {
progressBar.Value = fileListBox.Items.Count;
statusLabel.Text = "处理完成";
}));
}
private SKEncodedImageFormat GetBatchFormat()
{
switch (outputFormatCombo.SelectedIndex)
{
case 0: return SKEncodedImageFormat.Png;
case 1: return SKEncodedImageFormat.Jpeg;
case 2: return SKEncodedImageFormat.Bmp;
default: return SKEncodedImageFormat.Png;
}
}
private string GetExtension(SKEncodedImageFormat format)
{
switch (format)
{
case SKEncodedImageFormat.Png: return ".png";
case SKEncodedImageFormat.Jpeg: return ".jpg";
case SKEncodedImageFormat.Bmp: return ".bmp";
default: return ".png";
}
}
}
}

C#// ❌ 错误写法 - 可能导致内存泄漏
var bitmap = SKBitmap.Decode(inputFile);
// 处理逻辑...
// 忘记释放资源
// ✅ 正确写法 - 自动释放资源
using (var bitmap = SKBitmap.Decode(inputFile))
{
// 处理逻辑...
} // 自动调用Dispose()
C#// ❌ 错误写法 - 会抛出跨线程异常
Task.Run(() => {
progressBar.Value = i; // 跨线程访问UI控件
});
// ✅ 正确写法 - 使用Invoke
Task.Run(() => {
this.Invoke(new Action(() => {
progressBar.Value = i;
}));
});
对于超大图片文件,建议添加尺寸检查:
C#if (bitmap.Width > 10000 || bitmap.Height > 10000)
{
// 对超大图片进行分块处理或提示用户
MessageBox.Show("图片尺寸过大,建议先进行预处理");
continue;
}
通过这个批量图片处理工具的实现,我们掌握了:
这个工具不仅解决了批量图片处理的痛点,更重要的是展示了C#在桌面应用开发中的强大能力。无论是企业级应用还是个人工具,都可以基于这个框架进行扩展。
你在项目中遇到过哪些图片处理的挑战?欢迎在评论区分享你的经验,或者提出你想要的功能扩展!如果这篇文章对你有帮助,别忘了转发给更多需要的同行朋友!
相关信息
通过网盘分享的文件:AppGraphicsScaling.zip 链接: https://pan.baidu.com/s/1pmerX110FohRUarNbkUAfw?pwd=1mqf 提取码: 1mqf --来自百度网盘超级会员v9的分享:::
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!