C# Bitmap 类在工控实时图像处理中的高效应用与避坑

前言

在 C# 的图像处理世界里,Bitmap 类无疑是一个绕不开的核心角色。无论是开发图片编辑工具、处理摄像头采集的帧数据,还是生成动态二维码,都能看到它的身影。

它作为 .NET Framework 中 System.Drawing 命名空间的重要组成部分,为大家提供强大而灵活的图像处理能力。本文将带你全面了解 Bitmap 类,从基本概念到实战应用,再到避坑指南,帮助大家轻松驾驭这一图像处理利器。

一、Bitmap 到底是什么?

Bitmap是 System.Drawing 命名空间下的一个类,本质上是对 Windows GDI + 位图的封装,主要用于存储和处理图像数据。

核心作用

  • **图像的加载:**从文件、内存等多种来源读取图像数据

  • 图像创建:按需生成全新图像(空白图像、带初始内容图像)

  • **图像的编辑:**裁剪、缩放、颜色调整等常见图像处理操作

  • **图像保存:**可以保存为多种图像格式

特点鲜明

  • 功能丰富:具备大量的方法和属性

  • 集成 GDI +:借助 GDI + 强大绘图能力

  • 格式兼容广:支持 BMP、JPEG、PNG 等常见格式

二、使用场景

Bitmap 类虽然强大,但并非所有场景都适用。以下这些场景尤其适合它发挥优势:

  • 本地图片处理工具:如批量加水印、调整尺寸的小工具

  • 摄像头帧数据处理:从摄像头获取的帧数据可以转为 Bitmap 进行后续处理

  • 图像格式转换:PNG、JPG、BMP 等格式间转换时

  • 简单的图像编辑功能:裁剪头像、生成验证码图片等

  • 报表或文档中的图像生成:动态生成带数据的图表并嵌入文档

需要注意的是,在 Web 应用(如ASP.NET)中使用时要谨慎,因为它依赖 GDI+,可能存在性能或兼容性问题,此时更推荐使用专门的图像处理库。

三、实战

基础用法:加载、创建和保存

csharp 复制代码
using System;
using System.Drawing;
using System.Drawing.Imaging;

class BitmapBasicDemo
{   
    static void Main()   
    {       
        string sourcePath = @"C:\images\source.jpg";       
        string createdPath = @"C:\images\created.bmp";       
        try       
        {           
            // 1、加载已有图片(从文件加载)           
            // 使用using语句自动释放资源,避免内存泄漏           
            using (Bitmap loadedBmp = new Bitmap(sourcePath))           
            {               
                Console.WriteLine($"加载的图片尺寸:{loadedBmp.Width}x{loadedBmp.Height}");           
            }           
            // 2、创建新图片(在内存中创建一个200x200的位图)           
            // 参数:宽度、高度、像素格式(这里用32位ARGB,支持透明通道)           
            using (Bitmap createdBmp = new Bitmap(200, 200, PixelFormat.Format32bppArgb))           
            {               
                // 可以对创建的图片做些简单处理,比如填充背景色               
                using (Graphics g = Graphics.FromImage(createdBmp))               
                {                   
                    g.Clear(Color.White);  // 填充白色背景               
                }               
                // 3.保存图片               
                createdBmp.Save(createdPath);               
                Console.WriteLine("新图片创建并保存成功");           
            }       
        }       
        catch (Exception ex)       
        {           
            Console.WriteLine($"操作出错:{ex.Message}");       
        }   
    }
}
  • 所有 Bitmap 对象都用using语句包裹,确保即使发生异常也能释放非托管资源

  • 加载图片时直接通过文件路径构造 Bitmap 对象

  • 创建新图片需要指定宽度、高度和像素格式,PixelFormat 枚举有多种选项,根据需求选择

  • Save 方法支持指定保存格式(ImageFormat)

进阶用法

主要涉及图像的缩放、裁剪、颜色调整等操作

缩放图片

csharp 复制代码
/// <param name="sourcePath">源图片路径</param>
/// <param name="targetPath">目标图片路径</param>
/// <param name="newWidth">新宽度</param>
static void ResizeImage(string sourcePath, string targetPath, int newWidth)
{   
    using (Bitmap sourceBmp = new Bitmap(sourcePath))   
    {       
        // 计算等比例缩放的高度(避免拉伸变形)       
        float scale = (float)newWidth / sourceBmp.Width;       
        int newHeight = (int)(sourceBmp.Height * scale);       
        // 创建缩放后的位图       
        using (Bitmap resizedBmp = new Bitmap(newWidth, newHeight))       
        {           
            // 使用Graphics绘制缩放后的图像           
            using (Graphics g = Graphics.FromImage(resizedBmp))           
            {               
                // 设置插值模式为高质量,让缩放更清晰               
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;               
                // 绘制图像(目标位置、目标大小、源图像区域)               
                g.DrawImage(sourceBmp, 0, 0, newWidth, newHeight);           
            }           
            // 保存结果           
            resizedBmp.Save(targetPath, ImageFormat.Jpeg);       
        }   
    }
}

Graphics.InterpolationMode:缩放时的插值模式,HighQualityBicubic 适合高质量需求,速度稍慢

Graphics.DrawImage:用于缩放、旋转等绘制操作

裁剪图片

csharp 复制代码
/// <param name="sourcePath">源图片路径</param>
/// <param name="targetPath">目标图片路径</param>
/// <param name="x">裁剪起点X坐标</param>
/// <param name="y">裁剪起点Y坐标</param>
/// <param name="width">裁剪宽度</param>
/// <param name="height">裁剪高度</param>
static void CropImage(string sourcePath, string targetPath, int x, int y, int width, int height)
{   
    using (Bitmap sourceBmp = new Bitmap(sourcePath))   
    {       
        // 定义裁剪区域(矩形:起点X、起点Y、宽度、高度)       
        Rectangle cropArea = new Rectangle(x, y, width, height);       
        // 使用Clone方法裁剪,注意第二个参数指定像素格式       
        using (Bitmap croppedBmp = sourceBmp.Clone(cropArea, sourceBmp.PixelFormat))       
        {           
            croppedBmp.Save(targetPath);       
        }   
    }
}

Bitmap.Clone:裁剪图像的高效方法,直接按指定矩形区域复制像素

颜色调整(反色处理)

csharp 复制代码
/// <param name="sourcePath">源图片路径</param>
/// <param name="targetPath">目标图片路径</param>
static void InvertColors(string sourcePath, string targetPath)
{
    using (Bitmap bmp = new Bitmap(sourcePath))
    {
        // 遍历每个像素
        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                // 获取当前像素颜色
                Color originalColor = bmp.GetPixel(x, y);
                // 计算反色(RGB值 = 255 - 原始值)
                Color invertedColor = Color.FromArgb(
                    originalColor.A, // 保持透明度不变
                    255 - originalColor.R,
                    255 - originalColor.G,
                    255 - originalColor.B
                );
                // 设置新颜色
                bmp.SetPixel(x, y, invertedColor);
            }
        }
        bmp.Save(targetPath);
    }
}

GetPixel/SetPixel:获取和设置单个像素的颜色,适合简单的颜色处理,但性能较低(大量像素处理推荐用 LockBits)

四、核心方法和属性

Bitmap 类提供了丰富的方法和属性,掌握这些核心成员能让你在开发中事半功倍。

常用函数

1、构造函数:创建Bitmap对象

  • Bitmap(string filename):从文件加载图像

  • Bitmap(int width, int height):创建指定尺寸的图像

  • Bitmap(int width, int height, PixelFormat format):创建指定尺寸和像素格式的图像

2、Clone: 创建 Bitmap 的副本

  • Bitmap Clone(Rectangle rect, PixelFormat format):指定区域和像素格式

  • Bitmap Clone() :创建一个与当前Bitmap对象具有相同像素数据的新Bitmap对象

常用于需要对图像进行复制操作,同时不影响原始图像的场景,如裁剪图像时可基于克隆的图像进行操作

csharp 复制代码
using (Bitmap source = new Bitmap("test.jpg"))
{   
    Rectangle rect = new Rectangle(0, 0, 100, 100);   
    var clone = source.Clone(rect, source.PixelFormat); // 裁剪左上角100x100的区域
}

3、GetHbitmap:获取 Windows GDI 位图的句柄

  • 主要用于与 Win32 API 进行交互,将Bitmap对象传递给需要 GDI 位图句柄的函数

  • 注意:需要手动调用 DeleteObject 释放,否则内存泄漏

4、GetPixel/SetPixel

  • 功能:获取 / 设置指定坐标的像素颜色

  • 语法:Color GetPixel(int x, int y)、void SetPixel(int x, int y, Color color)

  • 缺点:逐像素操作速度慢,适合简单场景

5、LockBits/UnlockBits

  • 功能:锁定 / 解锁图像的像素数据到内存,直接操作内存提高性能

  • 适合:批量处理大量像素(如滤镜效果)

注意:必须成对使用,解锁后才能进行其他操作

csharp 复制代码
using (Bitmap bitmap = new Bitmap("example.jpg"))
{   
    Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);   
    BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);   
    // 此处可进行像素数据的访问和修改操作   
    bitmap.UnlockBits(bitmapData);
}

6、MakeTransparent:将指定颜色设置为透明色,用于创建具有透明背景的图像

若不指定颜色参数,则默认将图像的左上角像素颜色设为透明

csharp 复制代码
using (Bitmap bmp = new Bitmap("test.png"))
{   
    // 将白色设为透明   
    bmp.MakeTransparent(Color.White);
}

7、GetPixelFormatSize:获取指定像素格式的每像素位数

用于了解像素格式的详细信息,以便进行相应的图像处理操作

csharp 复制代码
int size = Image.GetPixelFormatSize(PixelFormat.Format24bppRgb);

8、Dispose:释放 Bitmap 占用的非托管资源。使用 using 语句自动调用,无需手动调用

9、GetThumbnailImage: 获取图像的缩略图

  • 需要传入缩略图宽度、高度,以及两个回调函数(可设为null

注意:适合快速生成小尺寸缩略图,质量一般

csharp 复制代码
using (Bitmap bitmap = new Bitmap("example.jpg"))
{   
    using (Bitmap thumbnail = bitmap.GetThumbnailImage(100, 100, null, IntPtr.Zero))   
    {       
        thumbnail.Save("thumbnail.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);   
    }
}

10、Save:保存图像到文件

  • void Save(string filename):默认图像格式

  • void Save(string filename, ImageFormat format):指定图像格式

常用属性

1、Size:图像的尺寸(宽度和高度)

2、Width/Height:图像的宽度和高度(像素数),只读,修改尺寸需要重新创建 Bitmap

3、PixelFormat:图像的像素格式(如 Format32bppArgb),不同的像素格式决定了每个像素的颜色信息存储方式,影响图像质量和文件大小

4、RawFormat:图像的原始文件格式(如 ImageFormat.Jpeg)

csharp 复制代码
using (Bitmap bmp = new Bitmap("test.jpg"))
{   
    Console.WriteLine($"尺寸:{bmp.Width}x{bmp.Height}");   
    Console.WriteLine($"像素格式:{bmp.PixelFormat}");   
    Console.WriteLine($"文件格式:{bmp.RawFormat.Guid}"); // 不同格式有唯一Guid
}

五、避坑指南、注意事项

1、资源管理:Bitmap 属于非托管资源,using语句是最佳实践。如果手动创建,一定要在finally块中调用Dispose(),否则会导致内存泄漏

2、文件占用问题:加载图片后,会锁定该文件,直到 Bitmap 被释放。如果需要在不锁定文件的情况下加载,可以先将文件读入内存流再加载:

csharp 复制代码
byte[] data = File.ReadAllBytes("test.jpg");
using (MemoryStream ms = new MemoryStream(data))
using (Bitmap bmp = new Bitmap(ms))
{   
    // 此时源文件已解锁
}

3、性能问题:GetPixel/SetPixel逐像素操作性能极差,处理大图片时会非常慢。此时必须使用LockBits直接操作内存数据,速度能提升几十倍甚至上百倍

4、格式兼容性:不同图像格式有不同特性,保存图像时要确保目标格式支持所需特性。例如,JPEG 格式不支持透明度,保存带有透明度的图像到 JPEG 格式会丢失透明度信息

5、GDI + 依赖问题:Bitmap 依赖 GDI + 库,在某些环境(如服务器核心版 Windows)可能缺少相关组件,导致程序崩溃,部署时要注意环境依赖

6、跨线程操作:Bitmap 对象不是线程安全的,多线程同时操作同一个 Bitmap 会导致不可预知的错误,需要加锁保护

7、内存占用:处理大尺寸图像时,Bitmap对象可能占用大量内存。要注意系统内存限制,必要时进行分块处理或使用流加载等方式来减少内存压力

六、总结

Bitmap 作为 C# 中处理图像的核心类,提供从简单到复杂的全方位功能。无论是加载保存图片这种基础操作,还是裁剪缩放、像素处理等进阶需求,它都能满足。

  • 简单场景(加载、保存、格式转换):用基础的构造函数和 Save 方法即可

  • 中等需求(裁剪、缩放、简单颜色调整):结合 Graphics 类和 Clone 等方法

  • 高级需求(批量像素处理、滤镜效果):必须掌握 LockBits 的使用

任何场景都要牢记:用 using 语句管理资源,避免内存泄漏

关键词

Bitmap、C#、图像处理、System.Drawing、GetPixel、SetPixel、LockBits、UnlockBits、Clone、MakeTransparent、GetHbitmap、Dispose、Save、PixelFormat、RawFormat、Graphics、InterpolationMode、DrawImage、内存管理、性能优化、文件锁定、GDI+

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 DotNet技术匠 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

相关推荐
程序员海军32 分钟前
一个 AI 应用开发程序员的一天,都在屏幕前忙些什么?
前端·后端·程序员
不能放弃治疗40 分钟前
MCP
后端
江湖十年2 小时前
Go 语言中 YAML to JSON 踩坑笔记
后端·go·json
你驴我2 小时前
WhatsApp 消息撤回与编辑的幂等性设计实践
java·服务器·前端·后端·python
一缕清烟在人间3 小时前
HarmonyOS开发实战:小分享-TextEditPage文字编辑器——Header+TextArea+工具栏
后端·华为·harmonyos·鸿蒙
人间凡尔赛3 小时前
K8s 十年之变:从 Cloud Native 到 AI Native,2026 年后端架构的范式革命
后端·云原生·架构
吴可可1233 小时前
C#CAD点击计数器实现
c#
程序员cxuan3 小时前
Opus 5 深夜炸场,价格还挺香。。。
人工智能·后端·程序员
北冥you鱼3 小时前
Go 语言新手扫盲:指针 * 和 & 使用场景详解
开发语言·后端·golang
青山木3 小时前
Hot 100 ---腐烂的橘子
java·数据结构·后端·算法·leetcode·广度优先