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技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

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

相关推荐
做运维的阿瑞39 分钟前
Python零基础入门:30分钟掌握核心语法与实战应用
开发语言·后端·python·算法·系统架构
猿究院-陆昱泽2 小时前
Redis 五大核心数据结构知识点梳理
redis·后端·中间件
yuriy.wang2 小时前
Spring IOC源码篇五 核心方法obtainFreshBeanFactory.doLoadBeanDefinitions
java·后端·spring
咖啡教室4 小时前
程序员应该掌握的网络命令telnet、ping和curl
运维·后端
迪丽热爱5 小时前
VS要求的.NET 9 SDK需求、安装注意事项及VS版本搭配
.net
时光追逐者5 小时前
一个基于 .NET 开源、简易、轻量级的进销存管理系统
开源·c#·.net·.net core·经销存管理系统
你的人类朋友5 小时前
Let‘s Encrypt 免费获取 SSL、TLS 证书的原理
后端
老葱头蒸鸡5 小时前
(14)ASP.NET Core2.2 中的日志记录
后端·asp.net
李昊哲小课5 小时前
Spring Boot 基础教程
java·大数据·spring boot·后端
码事漫谈6 小时前
C++内存越界的幽灵:为什么代码运行正常,free时却崩溃了?
后端