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

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

相关推荐
昵称为空C7 分钟前
SpringBoot 实现DataSource接口实现多租户数据源切换方案
后端·mybatis
hqwest21 分钟前
C#WPF实战出真汁05--左侧导航
开发语言·c#·wpf·主界面·窗体设计·视图viewmodel
hrrrrb1 小时前
【Java Web 快速入门】九、事务管理
java·spring boot·后端
AirMan2 小时前
深入解析 Spring Caffeine:揭秘 W-TinyLFU 缓存淘汰策略的高命中率秘密
后端
YF云飞2 小时前
.NET 在鸿蒙系统(HarmonyOS Next)上的适配探索与实践
华为·.net·harmonyos
布朗克1682 小时前
Spring Boot项目通过RestTemplate调用三方接口详细教程
java·spring boot·后端·resttemplate
uhakadotcom4 小时前
使用postgresql时有哪些简单有用的最佳实践
后端·面试·github
IT毕设实战小研4 小时前
基于Spring Boot校园二手交易平台系统设计与实现 二手交易系统 交易平台小程序
java·数据库·vue.js·spring boot·后端·小程序·课程设计
bobz9654 小时前
QT 字体
后端