C# WinForms 图片处理应用程序:完整教程与代码解析

概述

本文将详细介绍如何使用C# WinForms开发一个功能丰富的图片处理应用程序。该应用程序包含三大核心功能模块:调色、质感和氛围感,并支持撤销/重做操作。

功能架构

1. 调色模块

  • 构图调整:智能裁剪

  • 亮度调节:精确控制图片明暗程度

  • 饱和度调节:调整色彩鲜艳度

2. 质感模块

  • 背景虚化:创建专业级景深效果

  • 锐化处理:增强图片细节清晰度

3. 氛围感模块

  • 颗粒效果:添加胶片质感,营造复古氛围

4. 操作历史

  • 撤销/重做:完整的操作堆栈管理

  • 状态保存:实时保存处理状态

5. UI设计

  • UI效果:展示效果

一、核心功能模块设计

1. 调色模块参考代码展示

 // 更新亮度
 private void UpdateBrightness()
 {
     if (processedImage == null) return;

     brightness = trackBarBrightness.Value;
     ApplyImageProcessing();
 }

 // 更新饱和度
 private void UpdateSaturation()
 {
     if (processedImage == null) return;

     saturation = trackBarSaturation.Value;
     ApplyImageProcessing();
 }

  // 应用所有图像处理
  private void ApplyImageProcessing()
  {
      if (originalImage == null) return;

      // 保存当前状态到历史记录
      historyManager.AddState((Image)processedImage.Clone());

      // 从原始图像开始应用所有效果
      using (Bitmap tempBitmap = new Bitmap(originalImage))
      {
          Bitmap processedBitmap = (Bitmap)tempBitmap.Clone();

          // 应用亮度调整
          if (brightness != 0)
              processedBitmap = ImageProcessor.AdjustBrightness(processedBitmap, brightness);

          // 应用饱和度调整
          if (saturation != 0)
              processedBitmap = ImageProcessor.AdjustSaturation(processedBitmap, saturation);

          // 更新处理后的图像
          processedImage = (Image)processedBitmap.Clone();
          pictureBox.Image = (Image)processedImage.Clone();
      }
  }

  // 调整亮度
  public static Bitmap AdjustBrightness(Bitmap bitmap, int brightness)
  {
      float brightnessFactor = 1.0f + (brightness / 100.0f);

      // 创建颜色矩阵
      ColorMatrix colorMatrix = new ColorMatrix(new float[][]
      {
          new float[] {brightnessFactor, 0, 0, 0, 0},
          new float[] {0, brightnessFactor, 0, 0, 0},
          new float[] {0, 0, brightnessFactor, 0, 0},
          new float[] {0, 0, 0, 1, 0},
          new float[] {0, 0, 0, 0, 1}
      });

      return ApplyColorMatrix(bitmap, colorMatrix);
  }

  // 调整饱和度
  public static Bitmap AdjustSaturation(Bitmap bitmap, int saturation)
  {
      float saturationFactor = 1.0f + (saturation / 100.0f);

      // 计算灰度系数
      float gray = 0.3f;
      float gray2 = 0.59f;
      float gray3 = 0.11f;

      // 创建饱和度调整的颜色矩阵
      ColorMatrix colorMatrix = new ColorMatrix(new float[][]
      {
          new float[] {gray * (1 - saturationFactor) + saturationFactor, gray2 * (1 - saturationFactor), gray3 * (1 - saturationFactor), 0, 0},
          new float[] {gray * (1 - saturationFactor), gray2 * (1 - saturationFactor) + saturationFactor, gray3 * (1 - saturationFactor), 0, 0},
          new float[] {gray * (1 - saturationFactor), gray2 * (1 - saturationFactor), gray3 * (1 - saturationFactor) + saturationFactor, 0, 0},
          new float[] {0, 0, 0, 1, 0},
          new float[] {0, 0, 0, 0, 1}
      });

      return ApplyColorMatrix(bitmap, colorMatrix);
  }

2. 质感模块参考代码展示

  // 应用所有图像处理
  private void ApplyImageProcessing()
  {
      if (originalImage == null) return;

      // 保存当前状态到历史记录
      historyManager.AddState((Image)processedImage.Clone());

      // 从原始图像开始应用所有效果
      using (Bitmap tempBitmap = new Bitmap(originalImage))
      {
          Bitmap processedBitmap = (Bitmap)tempBitmap.Clone();

          // 应用模糊
          if (blurAmount > 0)
              processedBitmap = ImageProcessor.ApplyBlur(processedBitmap, blurAmount);

          // 应用锐化
          if (sharpenAmount > 0)
              processedBitmap = ImageProcessor.ApplySharpen(processedBitmap, sharpenAmount);

          // 更新处理后的图像
          processedImage = (Image)processedBitmap.Clone();
          pictureBox.Image = (Image)processedImage.Clone();
      }
  }

        // 应用模糊效果
        public static Bitmap ApplyBlur(Bitmap bitmap, int blurAmount)
        {
            if (blurAmount < 1) return new Bitmap(bitmap);

            int radius = blurAmount;
            int size = radius * 2 + 1;
            float[] kernel = new float[size * size];
            float weight = 1.0f / (size * size);

            // 创建平均模糊内核
            for (int i = 0; i < kernel.Length; i++)
                kernel[i] = weight;

            return ApplyConvolutionFilter(bitmap, kernel, size, size);
        }

        // 应用锐化效果
        public static Bitmap ApplySharpen(Bitmap bitmap, int sharpenAmount)
        {
            if (sharpenAmount < 1) return new Bitmap(bitmap);

            float amount = 1.0f + (sharpenAmount * 0.2f);
            float[,] kernel = {
                {0, -1, 0},
                {-1, amount, -1},
                {0, -1, 0}
            };

            return ApplyConvolutionFilter(bitmap, kernel);
        }
   // 应用卷积滤镜(并行加速版 - 移除Math.Clamp)
   private static Bitmap ApplyConvolutionFilter(Bitmap bitmap, float[] kernel, int kernelWidth, int kernelHeight)
   {
       // 1. 基础初始化(避免循环内重复创建对象)
       int width = bitmap.Width;
       int height = bitmap.Height;
       int pixelSize = 4; // 固定32位ARGB(B-G-R-A)
       int kernelHalfWidth = kernelWidth / 2;
       int kernelHalfHeight = kernelHeight / 2;
       int maxX = width - 1;
       int maxY = height - 1;

       // 创建结果图(与原图尺寸/格式一致)
       Bitmap result = new Bitmap(width, height, PixelFormat.Format32bppArgb);

       // 2. 锁定内存(减少GC和内存碎片)
       Rectangle rect = new Rectangle(0, 0, width, height);
       BitmapData srcData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
       BitmapData dstData = result.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

       // 3. 复制像素数据到托管数组(连续内存访问更快)
       int stride = srcData.Stride;
       int byteCount = stride * height;
       byte[] srcBytes = new byte[byteCount];
       byte[] dstBytes = new byte[byteCount];
       Marshal.Copy(srcData.Scan0, srcBytes, 0, byteCount);

       // 4. 线程安全的Random(并行时避免随机数冲突,若卷积核无随机逻辑可删除)
       ThreadLocal<Random> threadRandom = new ThreadLocal<Random>(() =>
           new Random(Guid.NewGuid().GetHashCode()));

       // 5. 并行遍历行(核心优化:利用多核CPU)
       Parallel.For(0, height, y =>
       {
           // 无需随机数时可删除以下2行
           Random random = threadRandom.Value;
           int rowStart = y * stride; // 当前行起始字节索引(提前计算,减少循环内运算)

           // 遍历当前行每个像素
           for (int x = 0; x < width; x++)
           {
               float blue = 0, green = 0, red = 0;

               // 6. 卷积核遍历(只处理有效范围)
               for (int ky = -kernelHalfHeight; ky <= kernelHalfHeight; ky++)
               {
                   // 手动边界夹紧:替代Math.Clamp(y + ky, 0, maxY)
                   int pixelY = y + ky;
                   if (pixelY < 0) pixelY = 0;
                   else if (pixelY > maxY) pixelY = maxY;

                   int srcRow = pixelY * stride; // 目标行起始索引(提前计算)

                   for (int kx = -kernelHalfWidth; kx <= kernelHalfWidth; kx++)
                   {
                       // 手动边界夹紧:替代Math.Clamp(x + kx, 0, maxX)
                       int pixelX = x + kx;
                       if (pixelX < 0) pixelX = 0;
                       else if (pixelX > maxX) pixelX = maxX;

                       // 卷积核索引(避免循环内重复计算)
                       int kernelIndex = (ky + kernelHalfHeight) * kernelWidth + (kx + kernelHalfWidth);
                       float weight = kernel[kernelIndex];

                       // 7. 连续内存访问(提升缓存命中率)
                       int pixelIndex = srcRow + pixelX * pixelSize;
                       blue += srcBytes[pixelIndex] * weight;
                       green += srcBytes[++pixelIndex] * weight;
                       red += srcBytes[++pixelIndex] * weight;
                   }
               }

               // 8. 手动颜色值夹紧:替代Math.Clamp(blue/green/red, 0, 255)
               // 蓝色通道
               if (blue < 0) blue = 0;
               else if (blue > 255) blue = 255;
               // 绿色通道
               if (green < 0) green = 0;
               else if (green > 255) green = 255;
               // 红色通道
               if (red < 0) red = 0;
               else if (red > 255) red = 255;

               // 赋值到目标数组(连续索引递增,减少运算)
               int dstIndex = rowStart + x * pixelSize;
               dstBytes[dstIndex] = (byte)blue;    // B
               dstBytes[++dstIndex] = (byte)green; // G
               dstBytes[++dstIndex] = (byte)red;   // R
               dstBytes[++dstIndex] = srcBytes[dstIndex]; // A(保留原Alpha)
           }
       });

       // 9. 释放资源(避免内存泄漏)
       threadRandom.Dispose(); // 无需随机数时可删除
       Marshal.Copy(dstBytes, 0, dstData.Scan0, byteCount);
       bitmap.UnlockBits(srcData);
       result.UnlockBits(dstData);

       return result;
   }

   // 应用二维卷积滤镜
   private static Bitmap ApplyConvolutionFilter(Bitmap bitmap, float[,] kernel)
   {
       int kernelWidth = kernel.GetLength(1);
       int kernelHeight = kernel.GetLength(0);

       float[] kernelArray = new float[kernelWidth * kernelHeight];

       for (int y = 0; y < kernelHeight; y++)
       {
           for (int x = 0; x < kernelWidth; x++)
           {
               kernelArray[y * kernelWidth + x] = kernel[y, x];
           }
       }

       return ApplyConvolutionFilter(bitmap, kernelArray, kernelWidth, kernelHeight);
   }

3. 氛围模块参考代码展示

  // 应用所有图像处理
  private void ApplyImageProcessing()
  {
      if (originalImage == null) return;

      // 保存当前状态到历史记录
      historyManager.AddState((Image)processedImage.Clone());

      // 从原始图像开始应用所有效果
      using (Bitmap tempBitmap = new Bitmap(originalImage))
      {
          Bitmap processedBitmap = (Bitmap)tempBitmap.Clone();

          // 应用颗粒效果
          if (grainAmount > 0)
              processedBitmap = ImageProcessor.ApplyGrain(processedBitmap, grainAmount);

          // 更新处理后的图像
          processedImage = (Image)processedBitmap.Clone();
          pictureBox.Image = (Image)processedImage.Clone();
      }
  }
 // 应用颗粒效果(简化优化版)
 public static Bitmap ApplyGrain(Bitmap bitmap, int grainAmount)
 {
     if (grainAmount < 1)
         return new Bitmap(bitmap);

     Bitmap result = new Bitmap(bitmap.Width, bitmap.Height);
     Random random = new Random();
     Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);

     // 锁定图像数据
     var srcData = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
     var dstData = result.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

     // 计算字节数并分配缓冲区
     int byteCount = srcData.Stride * bitmap.Height;
     byte[] srcBytes = new byte[byteCount];
     byte[] dstBytes = new byte[byteCount];

     // 复制像素数据
     System.Runtime.InteropServices.Marshal.Copy(srcData.Scan0, srcBytes, 0, byteCount);

     // 处理每个像素
     for (int i = 0; i < byteCount; i += 4)
     {
         // 生成随机颗粒值
         int grain = random.Next(-grainAmount, grainAmount + 1);

         // 处理BGR通道(注意顺序是蓝、绿、红)
         dstBytes[i] = (byte)ClampColor(srcBytes[i] + grain);       // 蓝色
         dstBytes[i + 1] = (byte)ClampColor(srcBytes[i + 1] + grain); // 绿色
         dstBytes[i + 2] = (byte)ClampColor(srcBytes[i + 2] + grain); // 红色
         dstBytes[i + 3] = srcBytes[i + 3]; // 保留Alpha通道
     }

     // 复制回处理后的数据
     System.Runtime.InteropServices.Marshal.Copy(dstBytes, 0, dstData.Scan0, byteCount);

     // 解锁图像
     bitmap.UnlockBits(srcData);
     result.UnlockBits(dstData);

     return result;
 }

 // 颜色值范围限制
 private static int ClampColor(int value)
 {
     return value < 0 ? 0 : (value > 255 ? 255 : value);
 }

4. 操作历史模块参考代码展示

  // 保存图片
  private void BtnSaveImage_Click(object sender, EventArgs e)
  {
      if (pictureBox.Image == null)
          return;

      using (SaveFileDialog saveFileDialog = new SaveFileDialog())
      {
          saveFileDialog.Filter = "PNG Image|*.png|JPEG Image|*.jpg|Bitmap Image|*.bmp";
          if (saveFileDialog.ShowDialog() == DialogResult.OK)
          {
              try
              {
                  string ext = System.IO.Path.GetExtension(saveFileDialog.FileName).ToLower();
                  ImageFormat format = ImageFormat.Png;

                  if (ext == ".jpg" || ext == ".jpeg")
                      format = ImageFormat.Jpeg;
                  else if (ext == ".bmp")
                      format = ImageFormat.Bmp;

                  pictureBox.Image.Save(saveFileDialog.FileName, format);
                  MessageBox.Show("图片保存成功!");
              }
              catch (Exception ex)
              {
                  MessageBox.Show($"保存图片失败: {ex.Message}");
              }
          }
      }
  }

  // 撤销操作
  private void BtnUndo_Click(object sender, EventArgs e)
  {
      Image previousState = historyManager.Undo();
      if (previousState != null)
      {
          processedImage = previousState;
          pictureBox.Image = (Image)processedImage.Clone();
          UpdateControlValues();
      }
  }

  // 重做操作
  private void BtnRedo_Click(object sender, EventArgs e)
  {
      Image nextState = historyManager.Redo();
      if (nextState != null)
      {
          processedImage = nextState;
          pictureBox.Image = (Image)processedImage.Clone();
          UpdateControlValues();
      }
  }
   // 更新控件值
   private void UpdateControlValues()
   {
       trackBarBrightness.Value = brightness;
       trackBarSaturation.Value = saturation;
       trackBarBlur.Value = blurAmount;
       trackBarSharpen.Value = sharpenAmount;
       trackBarGrain.Value = grainAmount;
   }

部分效果展示

使用说明

  1. 点击"打开"按钮选择要处理的图片

  2. 使用选项卡切换不同的功能模块

  3. 通过滑动条实时调整各种效果

  4. 使用"撤销"和"重做"按钮管理操作历史

  5. 点击"保存"按钮保存处理后的图片

这个应用程序提供了完整的图片处理功能,界面友好,操作简单,适合各种图片处理需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Smoothly1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值