C#图片处理相关方法整理
本文主要是代码整理,将项目中用到的图像数据处理相关的方法进行了整理,方便以后直接拿来使用。当然,有些方法在实际使用可能需要经过调整和扩展,方可满足各种不同的应用场景。
1. byte[] 转 System.Drawing.Bitmap
代码图像信息的Byte数据转换
/// <summary>
/// byte带有图像信息
/// </summary>
/// <param name="byteImage"></param>
/// <returns></returns>
public static System.Drawing.Bitmap ByteToBitmap(byte[] byteImage)
{
using (MemoryStream stream = new MemoryStream(byteImage))
{
return new System.Drawing.Bitmap(stream);
//return new Bitmap((Image)new Bitmap(stream));
}
}
原始ARGB格式的Byte数据
public static Bitmap ByteToBitmap(int width, int height, byte[] imgBytes)
{
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
//用Marshal的Copy方法,将刚才得到的内存字节数组复制到BitmapData中
System.Runtime.InteropServices.Marshal.Copy(imgBytes, 0, bmpData.Scan0, imgBytes.Length);
//解锁内存区域
bmp.UnlockBits(bmpData);
return bmp;
}
2. System.Drawing.Bitmap 转 byte[]
System.Drawing.Bitmap 转 byte[]
/// <summary>
/// 这里可自行决定生成什么格式的数据,Png,Jpg,gif等等
/// </summary>
/// <param name="bitmap"></param>
/// <returns></returns>
public static byte[] BitmapToByte(System.Drawing.Bitmap bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
//bitmap.Save(stream, bitmap.RawFormat);
byte[] byteImage = new Byte[stream.Length];
byteImage = stream.ToArray();
return byteImage;
}
}
3. 直接从图片文件读取数据
从文件读取数据
public static byte[] GetPictureData(string imagepath)
{
using (FileStream fs = new FileStream(imagepath, FileMode.Open, FileAccess.Read, FileShare.Read))//可以是其他重载方法
{
byte[] byData = new byte[fs.Length];
fs.Read(byData, 0, byData.Length);
fs.Close();
fs.Dispose();
return byData;
}
}
4.图像压缩
存在实际图片很大,传输时会耗时较长,可以适当压缩下在进行传输,只要保证显示效果即可。
压缩图像数据
/// <summary>
/// 压缩图像
/// </summary>
/// <param name="rawImgData"></param>
/// <param name="quality">图像的压缩质量 0-100</param>
/// <returns></returns>
public static byte[] CompressionImage(byte[] rawImgData ,long quality)
{
if (rawImgData == null || rawImgData.Count() == 0)
return rawImgData;
using (MemoryStream stream = new MemoryStream(rawImgData))
{
using (var bitmap = new System.Drawing.Bitmap(stream))
{
var codecInfo = GetEncoder(System.Drawing.Imaging.ImageFormat.Jpeg);
var myEncoder = System.Drawing.Imaging.Encoder.Quality;
var myEncoderParameters = new System.Drawing.Imaging.EncoderParameters(1);
var myEncoderParameter = new System.Drawing.Imaging.EncoderParameter(myEncoder, quality);
myEncoderParameters.Param[0] = myEn
using (var ms = new MemoryStream())
{
bitmap.Save(ms, codecInfo, myEncoderParameters);
myEncoderParameters.Dispose();
myEncoderParameter.Dispose();
return ms.ToArray();
}
}
}
}
private static System.Drawing.Imaging.ImageCodecInfo GetEncoder(System.Drawing.Imaging.ImageFormat format)
{
var codecs = System.Drawing.Imaging.ImageCodecInfo.GetImageDecoders();
return codecs.FirstOrDefault(codec => codec.FormatID == format.Guid);
}
5.System.Windows.Media.Imaging.BitmapSource 和 System.Drawing.Bitmap 相互转换
在WPF,WinForm等开发时,处理图像会大量应用到WriteableBitmap->BitmapSource->ImageSource这三个图像相关类,当然还有BitmapImage类,在需要对图像进行加工处理时自然也需要有转换成System.Drawing.Bitmap以便进一步处理。当然
BitmapSource 转 Bitmap
static public System.Drawing.Bitmap WriteableBitmapToBitmap(WriteableBitmap wbm)
{
System.Drawing.Bitmap bitmap = null;
using (MemoryStream stream = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(wbm));
encoder.Save(stream);
bitmap = new System.Drawing.Bitmap(stream);
}
return bitmap;
}
/// <summary>
/// WriteableBitmap 转 System.Drawing.Bitmap
/// </summary>
/// <param name="wbmp"></param>
/// <returns></returns>
public static System.Drawing.Bitmap BitmapSourceToBitmap(WriteableBitmap wbmp)
{
//using (var outBitmap = new System.Drawing.Bitmap(wbmp.PixelWidth, wbmp.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
//{
// System.Drawing.Imaging.BitmapData bitmapData = outBitmap.LockBits(new System.Drawing.Rectangle(System.Drawing.Point.Empty, outBitmap.Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, outBitmap.PixelFormat);
// wbmp.CopyPixels(Int32Rect.Empty, bitmapData.Scan0, bitmapData.Height * bitmapData.Stride, bitmapData.Stride);
// outBitmap.UnlockBits(bitmapData);
// return outBitmap;
//}
var outBitmap1 = BitmapSource2Bitmap(wbmp);
return outBitmap1;
}
public static System.Drawing.Bitmap BitmapSource2Bitmap(BitmapSource wbmp)
{
// 使用BitmapSource创建一个新的System.Drawing.Bitmap
using (MemoryStream stream = new MemoryStream())
{
BitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(wbmp));
encoder.Save(stream);
stream.Position = 0; // 重置流的位置到开始处
System.Drawing.Bitmap drawingBmp = new System.Drawing.Bitmap(stream);
return drawingBmp;
}
}
public static WriteableBitmap BitmapToImageSource(System.Drawing.Bitmap bitmap)
{
try
{
IntPtr intPtr = bitmap.GetHbitmap();
BitmapSource imageSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(intPtr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
return new WriteableBitmap(imageSource);
}
catch(Exception ex)
{
Console.WriteLine($"Bitmap convertTo WriteableBitmap error:{ex.ToString()}");
return null;
}
}
6.修改图片,给已有的图片添加新内容
存在一种情况,需要在已有的图片上添加一个新的内容,比如某个位置填充一个长方形,一条线条,添加文字等等。这里需要用到GDI绘图类System.Drawing.Graphics
Graphics绘制多边形曲线,长方形
/// <summary>
/// 绘制曲线
/// </summary>
/// <param name="bitmap">原有的图像</param>
/// <param name="curve">曲线需要的点集合</param>
/// <param name="drawColor">绘制的颜色</param>
/// <returns></returns>
static public System.Drawing.Bitmap DrawCurveOnBitmap(System.Drawing.Bitmap bitmap, List<System.Drawing.PointF> curve, System.Drawing.Color drawColor)
{
if (bitmap == null)
return null;
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap);
System.Drawing.Pen pen = new System.Drawing.Pen(drawColor, 1);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // 抗锯齿处理
// 创建一个GraphicsPath对象并添加曲线
using (System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath())
{
path.AddCurve(curve.ToArray());
g.DrawPath(pen, path); // 使用黑色画笔绘制路径
}
g.Dispose();
return bitmap;
}
/// <summary>
/// 绘制多边形
/// </summary>
/// <param name="bitmap"></param>
/// <param name="curve"></param>
/// <returns></returns>
static public System.Drawing.Bitmap DrawCurveOnBitmap(System.Drawing.Bitmap bitmap, List<System.Drawing.PointF> curve)
{
if (bitmap == null)
return null;
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap);
System.Drawing.Pen pen = new System.Drawing.Pen(System.Drawing.Color.Yellow, 1);
g.DrawPolygon(pen, curve.ToArray());
g.Dispose();
return bitmap;
}
Graphics类中提供了大量的方法,用来绘制不同的形状,点,直线,长方形,多边形,圆等等。
在图像上添加文字,合并文字到图像
/// <summary>
/// 在图像上添加文字,合并文字
/// </summary>
/// <param name="bytes"></param>
/// <param name="TextString">需要添加的文字列表</param>
/// <param name="PositionType">合并的位置,1=左上,2=右上,3=右下,4=左下,都是距离图像10个像素位置</param>
/// <returns></returns>
public static System.Drawing.Bitmap DrawTextOnBitmap(byte[] bytes, List<string> TextString,int PositionType, bool blackColor = false)
{
System.Drawing.Bitmap bitmap = ConvertByteToBitmap(bytes);
if (bitmap == null)
return null;
//System.Drawing.Bitmap
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap))
{
// 设置文字格式
System.Drawing.Font font = new System.Drawing.Font("Microsoft YaHei", 14);
System.Drawing.Brush brush = blackColor ? new System.Drawing.SolidBrush(System.Drawing.Color.Black) : new System.Drawing.SolidBrush(System.Drawing.Color.White);
//System.Drawing.PointF point = new System.Drawing.PointF((bitmap.Width-100), (bitmap.Height - 40)); // 文字开始的位置
if (PositionType == 3)
{
TextString.Reverse();
//绘制多个文字
for (int i = 0; i < TextString.Count; i++)
{
System.Drawing.PointF point = new System.Drawing.PointF((bitmap.Width - 100), (bitmap.Height - 40 - (20 * i))); // 文字开始的位置
g.DrawString(TextString[i], font, brush, point);// 绘制文字
}
}
else
{
for (int i = 0; i < TextString.Count; i++)
{
System.Drawing.PointF point = new System.Drawing.PointF(5, 5); // 文字开始的位置
g.DrawString(TextString[i], font, brush, point);// 绘制文字
}
}
}
return bitmap;
}
7.图片裁剪
有时候我们需要对图像进行裁剪,即原有图像指定的位置开始,按照指定的宽高裁剪出一张新的图像。
裁剪图片
/// <summary>
/// 切图
/// </summary>
/// <param name="bitmapSource">图源</param>
/// <param name="cut">切割区域</param>
/// <returns></returns>
public static BitmapSource CutImage(BitmapSource bitmapSource, Int32Rect cut)
{
//计算Stride
var stride = bitmapSource.Format.BitsPerPixel * cut.Width / 8;
//声明字节数组
byte[] data = new byte[cut.Height * stride];
//调用CopyPixels
bitmapSource.CopyPixels(cut, data, stride, 0);
return BitmapSource.Create(cut.Width, cut.Height, 0, 0, PixelFormats.Bgr32, null, data, stride);
}
使用示例:比如我们需要将一张已有的图片,从起始点0,0开始,按照指定的宽=100,高=50进行裁剪出新的图像,可以按照如下方式调用。
调用示例
BitmapImage bitmapImage = new BitmapImage(new Uri("pack://application:,,,/Controls;component/Icons/default/image.png替换成你的图片路径", UriKind.Absolute));
BitmapSource imageSource= ImageHelper.CutImage(bitmapImage, new Int32Rect(0, 0, 100, 50);
这里在引深一步,应道到实际场景,当我们在UI界面上有一个随着鼠标调整来实时显示图片的一部分的场景时,就可以根据鼠标位置实时剪切,当然另外一个方法就是在图片加上一个遮盖实时调整遮盖控件。我这里以WPF中使用场景为例。假若界面有长方形控件Rectangle,这个Rectangle控件需要使用一张图片进行填充,(图片很大),Rectangle可调整大小,Rectangle调整大小时图像要实时显示对应的位置不分。
前端XMAL代码,这里关键就是CurrentBrush这个绑定
使用示例代码
<Rectangle
x:Name="RectangleDemo"
Canvas.Left="{Binding LocationX}"
Canvas.Top="{Binding LocationY}"
Width="{Binding WidthX}"
Height="{Binding HeightY}"
Fill="{Binding CurrentBrush}"
Opacity="0.5"
StrokeThickness="2"
Visibility="{Binding IsVirtualStentChecked, Converter={StaticResource booleanToVisibilityConverter}}" />
//后端调用示例
CurrentBrush= new DrawingBrush();
BitmapImage bitmapImage = new BitmapImage(new Uri("pack://application:,,,/Controls;component/Icons/default/111.png替换成你的图片", UriKind.Absolute));
BitmapSource imageSource= ImageHelper.CutImage(bitmapImage, new Int32Rect(0, 0, (int)WidthX, (int)WidthY));
VirtualStentBrush.Drawing= new ImageDrawing(imageSource, new Rect(new Point(0, 0), new Vector(0.5, 0.5)));
8.最后,在附带几个图像数据格式转换的函数。
图像中经常使用的数据格式byte[], short[],ushort[]
short和byte转换
//输入byte[],返回IntPtr
public static IntPtr ByteArrToIntPtr(byte[] array)
{
return Marshal.UnsafeAddrOfPinnedArrayElement(array, 0);
}
public static IntPtr ShortArrToIntPtr(short[] shortArr)
{
return Marshal.UnsafeAddrOfPinnedArrayElement(ShortArrToByteArr(shortArr), 0);
}
public static byte[] ShortArrToByteArr(short[] shortArr)
{
int shortSize = sizeof(short) * shortArr.Length;
byte[] bytArr = new byte[shortSize];
//申请一块非托管内存
IntPtr ptr = Marshal.AllocHGlobal(shortSize);
//复制shortArr数组到该内存块
Marshal.Copy(shortArr, 0, ptr, shortArr.Length);
//复制回bytArr数组
Marshal.Copy(ptr, bytArr, 0, bytArr.Length);
//释放申请的非托管内存
Marshal.FreeHGlobal(ptr);
return bytArr;
}
public static short[] ByteArrToShortArr(byte[] byteArr)
{
int shortSize = byteArr.Length;
short[] shortArr = new short[shortSize / 2];
//申请一块非托管内存
IntPtr ptr = Marshal.AllocHGlobal(shortSize);
//复制byte数组到该内存块
Marshal.Copy(byteArr, 0, ptr, byteArr.Length);
//复制回shortArr数组
Marshal.Copy(ptr, shortArr, 0, shortArr.Length);
//释放申请的非托管内存
Marshal.FreeHGlobal(ptr);
return shortArr;
}
9.新增图片灰度化处理
此小点的内容主要来自博文https://www.cnblogs.com/huangxincheng/archive/2013/01/04/2845050.html
灰度化的RGB权重设置如下,差不多就是RGB的3:6:1的比例:
Gary(i,j)=0.299R(i,j)+0.587G(i,j)+0.114*B(i,j);
灰度化处理-原始版本每个像素都去单独处理
static void Main(string[] args)
{
Bitmap bitmap = new Bitmap(Environment.CurrentDirectory + "//1.jpg");
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
//取图片当前的像素点
var color = bitmap.GetPixel(i, j);
var gray = (int)(color.R * 0.299 + color.G * 0.587 + color.B * 0.114);
//重新设置当前的像素点
bitmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
}
}
bitmap.Save(Environment.CurrentDirectory + "//2.jpg");
}
第二版是基于字节的角度进行自考,减少循环次数,注意一个问题就是:比如图片的width=21px,一个像素点占用3个字节,但是21个像素点不一定就占用63个字节,这是因为系统基于性能考虑,在每一行中存放着一个“未用区域”,来确保图片每行的byte数是4的倍数,那么如何去读某一行的字节数呢?C#里面有一个Stride属性就可以用来获取。(这个Stride之前只知道这样用,但至于为什么终于是知道了。)
灰度化处理-第一版优化
static void Main(string[] args)
{
Bitmap bitmap = new Bitmap(Environment.CurrentDirectory + "//1.jpg");
//定义锁定bitmap的rect的指定范围区域
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
//加锁区域像素
var bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
//位图的首地址
var ptr = bitmapData.Scan0;
//stride:扫描行
int len = bitmapData.Stride * bitmap.Height;
var bytes = new byte[len];
//锁定区域的像素值copy到byte数组中
Marshal.Copy(ptr, bytes, 0, len);
for (int i = 0; i < bitmap.Height; i++)
{
for (int j = 0; j < bitmap.Width * 3; j = j + 3)
{
var color = bytes[i * bitmapData.Stride + j + 2] * 0.299
+ bytes[i * bitmapData.Stride + j + 1] * 0.597
+ bytes[i * bitmapData.Stride + j] * 0.114;
bytes[i * bitmapData.Stride + j]
= bytes[i * bitmapData.Stride + j + 1]
= bytes[i * bitmapData.Stride + j + 2] = (byte)color;
}
}
//copy回位图
Marshal.Copy(bytes, 0, ptr, len);
//解锁
bitmap.UnlockBits(bitmapData);
bitmap.Save(Environment.CurrentDirectory + "//3.jpg");
}
一般来说,做到上面的优化代码即可,当然,还可以用指针继续优化,指针是C#中对C++的沿用,自然就需要对C++中指针一定的了解,原理可能都知道,但很多C#程序员不见得会用以及能用好,我自己就是如此。这里的代码需要设置VS中项目属性里的 容许不安全代码选项。
灰度化处理-应用指针的计算,第二版优化
private unsafe void GrayScaleImg(Bitmap Img)
{
int X, Y , Stride, Width, Height;
byte* Pointer;
byte Value;
BitmapData BmpData = Img.LockBits(new Rectangle(0, 0, Img.Width, Img.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
Width = BmpData.Width; Height = BmpData.Height;
Stride = BmpData.Stride; // 这个值并不一定就等于width*height*色深/8
for (Y = 0; Y < Height; Y++)
{
Pointer = (byte*)BmpData.Scan0 + Y * Stride; // 必须在某个地方开启unsafe功能,其实C#中的unsafe很safe,搞的好吓人。
for (X = 0; X < Width; X++)
{
Value = (byte)((*Pointer * 299 + *(Pointer + 1) * 587 + *(Pointer + 2) * 114) / 1000); //优化算法是通过移位实现的。
*(Pointer) = Value;
*(Pointer + 1) = Value;
*(Pointer + 2) = Value;
Pointer += 3;
}
}
Img.UnlockBits(BmpData);
}

浙公网安备 33010602011771号