C#中基于GDI+(Graphics)图像处理系列之前言
Posted lhtzbj12
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#中基于GDI+(Graphics)图像处理系列之前言相关的知识,希望对你有一定的参考价值。
前言
图像处理是开发工程师们学习某种语言入门时就会遇到的问题,笔者刚开始接触C#使用GDI+进行图像处理,觉得太简单了,就没有深入研究,随着工作经验的积累,踏遍若干坑以后突然觉得还是有必要将这块的知识好好总结一下,毕竟还是有一些比较冷门的知识在实际应用中给我们的程序带来更多的灵活性,比如将图片保存成jpeg时进一步控制图片的质量、怎样获取任意角度旋转后的图像、怎样获取透明图像等等。
本文后面将直接放出图像处理工具类的全部源码和示例程序源码,供有一定开发能力的同学直接获取并使用。
需要深入了解代码的同学可以根据以下章节详细了解功能点的实现,会详细说明获取高质量缩略图的要领、图像旋转的实现步骤等重要内容。
- C#中基于GDI+(Graphics)图像处理系列之高质量缩略图
- C#中基于GDI+(Graphics)图像处理系列之图片压缩优化
- C#中基于GDI+(Graphics)图像处理系列之任意角度旋转图像
- C#中基于GDI+(Graphics)图像处理系列之文字或者图片水印(透明、任意角度旋转)
- C#中基于GDI+(Graphics)图像处理系列之UEditor上传图片自动压缩优化(添加水印)
图像处理工具类的全部源码
/************************************************************
* Copyright (C) Corporation. All rights reserved.
*
* Author : lihaitao
* Email : lhtzbj12@126.com
* Create Date : 2016-8-15
* Description : 图片处理工具,包括固定宽高、限制宽高、限制长边、文字水印(透明)、图片水印(透明、任意角度旋转)
*
*
* Revision History:
* Date Author Description
* 2016-8-15 lihaitao create
*
*************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace SDLight.Util
{
public partial class ImageTool
{
#region 变量
//优化良好的图片每个像素平均占用文件大小,经验值,可根据需要修改
private static readonly double sizePerPx = 0.18;
#endregion
#region 生成高质量缩略图、优化图片
/// <summary>
/// 生成高质量缩略图(固定宽高),不一定保持原宽高比
/// </summary>
/// <param name="destPath">目标保存路径</param>
/// <param name="srcPath">源文件路径</param>
/// <param name="width">生成缩略图的宽度,设置为0,则与源图比处理</param>
/// <param name="height">生成缩略图的高度,设置为0,则与源图等比例处理</param>
/// <param name="quality">1~100整数,无效值则取默认值95</param>
/// <param name="mimeType">如 image/jpeg</param>
public bool GetThumbnailImage(string destPath, string srcPath, int destWidth, int destHeight, int quality, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
//宽高不能小于0
if (destWidth < 0 || destHeight < 0)
{
error = "目标宽高不能小于0";
return retVal;
}
//宽高不能同时为0
if (destWidth == 0 && destHeight == 0)
{
error = "目标宽高不能同时为0";
return retVal;
}
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//获取源图像
srcImage = Image.FromFile(srcPath, false);
//计算高宽比例
float d = (float)srcImage.Height / srcImage.Width;
//如果输入的宽为0,则按高度等比缩放
if (destWidth == 0)
{
destWidth = Convert.ToInt32(destHeight / d);
}
//如果输入的高为0,则按宽度等比缩放
if (destHeight == 0)
{
destHeight = Convert.ToInt32(destWidth * d);
}
//定义画布
destImage = new Bitmap(destWidth, destHeight);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图像画到画布上,注意最后一个参数GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(0, 0, destWidth, destHeight), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//如果是覆盖则先释放源资源
if (destPath == srcPath)
{
srcImage.Dispose();
}
//保存到文件,同时进一步控制质量
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
/// <summary>
/// 对图片进行压缩优化(限制宽高),始终保持原宽高比
/// </summary>
/// <param name="destPath">目标保存路径</param>
/// <param name="srcPath">源文件路径</param>
/// <param name="max_Width">压缩后的图片宽度不大于这值,如果为0,表示不限制宽度</param>
/// <param name="max_Height">压缩后的图片高度不大于这值,如果为0,表示不限制高度</param>
/// <param name="quality">1~100整数,无效值则取默认值95</param>
/// <param name="mimeType">如 image/jpeg</param>
public bool GetCompressImage(string destPath, string srcPath, int maxWidth, int maxHeight, int quality, out string error,string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
//宽高不能小于0
if (maxWidth < 0 || maxHeight < 0)
{
error = "目标宽高不能小于0";
return retVal;
}
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//获取源图像
srcImage = Image.FromFile(srcPath, false);
FileInfo fileInfo = new FileInfo(srcPath);
//目标宽度
var destWidth = srcImage.Width;
//目标高度
var destHeight = srcImage.Height;
//如果输入的最大宽为0,则不限制宽度
//如果不为0,且原图宽度大于该值,则附值为最大宽度
if (maxWidth != 0 && destWidth > maxWidth)
{
destWidth = maxWidth;
}
//如果输入的最大宽为0,则不限制宽度
//如果不为0,且原图高度大于该值,则附值为最大高度
if (maxHeight != 0 && destHeight > maxHeight)
{
destHeight = maxHeight;
}
float srcD = (float)srcImage.Height / srcImage.Width;
float destD = (float)destHeight / destWidth;
//目的高宽比 大于 原高宽比 即目的高偏大,因此按照原比例计算目的高度
if (destD > srcD)
{
destHeight = Convert.ToInt32(destWidth * srcD);
}
else if (destD < srcD) //目的高宽比 小于 原高宽比 即目的宽偏大,因此按照原比例计算目的宽度
{
destWidth = Convert.ToInt32(destHeight / srcD);
}
//如果维持原宽高,则判断是否需要优化
if (destWidth == srcImage.Width && destHeight == srcImage.Height &&
fileInfo.Length < destWidth * destHeight * sizePerPx)
{
error = "图片不需要压缩优化";
return retVal;
}
//定义画布
destImage = new Bitmap(destWidth, destHeight);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图像画到画布上,注意最后一个参数GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(0, 0, destWidth, destHeight), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//如果是覆盖则先释放源资源
if (destPath == srcPath)
{
srcImage.Dispose();
}
//保存到文件,同时进一步控制质量
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
/// <summary>
/// 对图片进行压缩优化,始终保持原宽高比,限制长边长度,常用场景:相片
/// </summary>
/// <param name="destPath">目标保存路径</param>
/// <param name="srcPath">源文件路径</param>
/// <param name="max_Length">压缩后的图片边(宽或者高)长变不大于这值,为0表示不限制</param>
/// <param name="quality">1~100整数,无效值,则取默认值95</param>
/// <param name="mimeType">如 image/jpeg</param>
public bool GetCompressImage(string destPath, string srcPath, int maxLength, int quality, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
//最大边长不能小于0
if (maxLength < 0)
{
error = "最大边长不能小于0";
return retVal;
}
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//获取源图像
srcImage = Image.FromFile(srcPath, false);
FileInfo fileInfo = new FileInfo(srcPath);
//目标宽度
var destWidth = srcImage.Width;
//目标高度
var destHeight = srcImage.Height;
//如果限制
if (maxLength > 0)
{
//原高宽比
float srcD = (float)srcImage.Height / srcImage.Width;
//如果宽>高,且大于 限制
if (destWidth > destHeight && destWidth > maxLength)
{
destWidth = maxLength;
destHeight = Convert.ToInt32(destWidth * srcD);
}
else
{
if (destHeight > maxLength)
{
destHeight = maxLength;
destWidth = Convert.ToInt32(destHeight / srcD);
}
}
}
//如果维持原宽高,则判断是否需要优化
if (destWidth == srcImage.Width && destHeight == srcImage.Height &&
fileInfo.Length < destWidth * destHeight * sizePerPx)
{
error = "图片不需要压缩优化";
return retVal;
}
//定义画布
destImage = new Bitmap(destWidth, destHeight);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图像画到画布上,注意最后一个参数GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(0, 0, destWidth, destHeight), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//如果是覆盖则先释放源资源
if (destPath == srcPath)
{
srcImage.Dispose();
}
//保存到文件,同时进一步控制质量
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
#endregion
#region 从图片中挖取一块区域
/// <summary>
/// 从源图片中挖取一块区域保存为新图片
/// </summary>
/// <param name="destPath">保存路径</param>
/// <param name="srcPath">源路径,两个路径可以相同,相同则会覆盖源图片</param>
/// <param name="x">挖取区域左上角x值</param>
/// <param name="y">挖取区域左上角y值</param>
/// <param name="width">挖取区域的宽度</param>
/// <param name="height">挖取区域的高度</param>
/// <param name="quality">1~100整数,无效值,则取默认值95</param>
/// <param name="error"></param>
/// <param name="mimeType"></param>
/// <returns></returns>
public bool GetDigImage(string destPath, string srcPath, int x, int y, int width, int height, int quality, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//获取源图像
srcImage = Image.FromFile(srcPath, false);
//定义画布
destImage = new Bitmap(width, height);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图像的某区域画到新画布上,注意最后一个参数GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(0, 0, width, height), new Rectangle(x, y, width, height), GraphicsUnit.Pixel);
//如果是覆盖则先释放源资源
if (destPath == srcPath)
{
srcImage.Dispose();
}
//保存到文件,同时进一步控制质量
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
#endregion
#region 在图片加入文字水印或者图片水印,并控制水印透明度,图片水印还可以控制旋转角度
/// <summary>
/// 给图片加入文字水印,且设置水印透明度
/// </summary>
/// <param name="destPath">保存地址</param>
/// <param name="srcPath">源文件地址,如果想覆盖源图片,两个地址参数一定要一样</param>
/// <param name="text">文字</param>
/// <param name="font">字体,为空则使用默认,注意,在创建字体时 GraphicsUnit.Pixel </param>
/// <param name="brush">刷子,为空则使用默认</param>
/// <param name="pos">设置水印位置,1左上,2中上,3右上
/// 4左中,5中, 6右中
/// 7左下,8中下,9右下</param>
/// <param name="padding">跟css里的padding一个意思</param>
/// <param name="quality">1~100整数,无效值,则取默认值95</param>
/// <param name="opcity">不透明度 100 为完全不透明,0为完全透明</param>
/// <param name="error"></param>
/// <param name="mimeType"></param>
/// <returns></returns>
public bool DrawWaterText(string destPath, string srcPath, string text, Font font, Brush brush, int pos, int padding, int quality, int opcity, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
Image srcImage = null;
Image destImage = null;
Graphics graphics = null;
if (font == null)
{
font = new Font("微软雅黑", 20, FontStyle.Bold, GraphicsUnit.Pixel);//统一尺寸
}
if (brush == null)
{
brush = new SolidBrush(Color.White);
}
try
{
//获取源图像
srcImage = Image.FromFile(srcPath, false);
//定义画布,大小与源图像一样
destImage = new Bitmap(srcImage);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图像画到画布上,注意最后一个参数GraphicsUnit.Pixel
graphics.DrawImage(srcImage, new Rectangle(0, 0, destImage.Width, destImage.Height), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//如果水印字不为空,且不透明度大于0,则画水印
if (!string.IsNullOrEmpty(text) && opcity > 0)
{
//获取可以用来绘制水印图片的有效区域
Rectangle validRect = new Rectangle(padding, padding, srcImage.Width - padding * 2, srcImage.Height - padding * 2);
//获取绘画水印文字的格式,即文字对齐方式
StringFormat format = GetStringFormat(pos);
//如果不透明度==100,那么直接将字画到当前画布上.
if (opcity == 100)
{
graphics.DrawString(text, font, brush, validRect, format);
}
else
{
//如果不透明度在0到100之间,就要实现透明效果,文字没法透明,图片才能透明
//则先将字画到一块临时画布,临时画布与destImage一样大,先将字写到这块画布上,再将临时画布画到主画布上,同时设置透明参数
Bitmap transImg = null;
Graphics gForTransImg = null;
try
{
//定义临时画布
transImg = new Bitmap(destImage);
//获取高清Graphics
gForTransImg = GetGraphics(transImg);
//绘制文字
gForTransImg.DrawString(text, font, brush, validRect, format);
//**获取带有透明度的ImageAttributes
ImageAttributes imageAtt = GetAlphaImgAttr(opcity);
//将这块临时画布画在主画布上
graphics.DrawImage(transImg, new Rectangle(0, 0, destImage.Width, destImage.Height), 0, 0, transImg.Width, transImg.Height, GraphicsUnit.Pixel, imageAtt);
}
catch (Exception ex)
{
error = ex.Message;
return retVal;
}
finally
{
if (transImg != null)
transImg.Dispose();
if (gForTransImg != null)
gForTransImg.Dispose();
}
}
}
//如果两个地址相同即覆盖,则提前Dispose源资源
if (destPath.ToLower() == srcPath.ToLower())
{
srcImage.Dispose();
}
//保存到文件,同时进一步控制质量
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
}
return retVal;
}
/// <summary>
/// 给图片加入图片水印,且设置水印透明度,旋转角度
/// </summary>
/// <param name="destPath">保存地址</param>
/// <param name="srcPath">源文件地址,如果想覆盖源图片,两个地址参数一定要一样</param>
/// <param name="waterPath">水印图片地址</param>
/// <param name="pos">设置水印位置,1左上,2中上,3右上
/// 4左中,5中, 6右中
/// 7左下,8中下,9右下</param>
/// <param name="padding">跟css里的padding一个意思</param>
/// <param name="quality">1~100整数,无效值,则取默认值95</param>
/// <param name="opcity">不透明度 100 为完全不透明,0为完全透明</param>
/// <param name="angle">顺时针旋转角度</param>
/// <param name="error"></param>
/// <param name="mimeType"></param>
/// <returns></returns>
public bool DrawWaterImage(string destPath, string srcPath, string waterPath, int pos, int padding, int quality, int opcity, int angle, out string error, string mimeType = "image/jpeg")
{
bool retVal = false;
error = string.Empty;
Image srcImage = null;
Image waterImage = null;
Image destImage = null;
Graphics graphics = null;
try
{
//获取原图
srcImage = Image.FromFile(srcPath, false);
//获取水印图片
waterImage = Image.FromFile(waterPath, false);
var waterRect = new Rectangle(0, 0, waterImage.Width, waterImage.Height);
//定义画布
destImage = new Bitmap(srcImage);
//获取高清Graphics
graphics = GetGraphics(destImage);
//将源图画到画布上
graphics.DrawImage(srcImage, new Rectangle(0, 0, destImage.Width, destImage.Height), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
//不透明度大于0,则画水印
if (opcity > 0)
{
//获取可以用来绘制水印图片的有效区域
Rectangle validRect = new Rectangle(padding, padding, srcImage.Width - padding * 2, srcImage.Height - padding * 2);
//如果要进行旋转
if (angle != 0)
{
Image rotateImage = null;
try
{
//获取水印图像旋转后的图像
rotateImage = GetRotateImage(waterImage, angle);
if (rotateImage != null)
{
//旋转后图像的矩形区域
var rotateRect = new Rectangle(0, 0, rotateImage.Width, rotateImage.Height);
//计算水印图片的绘制位置
var destRect = GetRectangleByPostion(validRect, rotateRect, pos);
//如果不透明度>=100,那么直接将水印画到当前画布上.
if (opcity == 100)
{
graphics.DrawImage(rotateImage, destRect, rotateRect, GraphicsUnit.Pixel);
}
else
{
//如果不透明度在0到100之间,设置透明参数
ImageAttributes imageAtt = GetAlphaImgAttr(opcity);
//将旋转后的图片画到画布上
graphics.DrawImage(rotateImage, destRect, 0, 0, rotateRect.Width, rotateRect.Height, GraphicsUnit.Pixel, imageAtt);
}
}
}
catch (Exception ex)
{
error = ex.Message;
return retVal;
}
finally
{
if (rotateImage != null)
rotateImage.Dispose();
}
}
else
{
//计算水印图片的绘制位置
var destRect = GetRectangleByPostion(validRect, waterRect, pos);
//如果不透明度=100,那么直接将水印画到当前画布上.
if (opcity == 100)
{
graphics.DrawImage(waterImage, destRect, waterRect, GraphicsUnit.Pixel);
}
else
{
//如果不透明度在0到100之间,设置透明参数
ImageAttributes imageAtt = GetAlphaImgAttr(opcity);
//将水印图片画到画布上
graphics.DrawImage(waterImage, destRect, 0, 0, waterRect.Width, waterRect.Height, GraphicsUnit.Pixel, imageAtt);
}
}
}
//如果两个地址相同即覆盖,则提前Dispose源资源
if (destPath.ToLower() == srcPath.ToLower())
{
srcImage.Dispose();
}
SaveImage2File(destPath, destImage, quality, mimeType);
retVal = true;
}
catch (Exception ex)
{
error = ex.Message;
}
finally
{
if (srcImage != null)
srcImage.Dispose();
if (destImage != null)
destImage.Dispose();
if (graphics != null)
graphics.Dispose();
if (waterImage != null)
waterImage.Dispose();
}
return retVal;
}
#endregion
#region 共用方法
/// <summary>
/// 将Image实例保存到文件,注意此方法不执行 img.Dispose()
/// 图片保存时本可以直接使用destImage.Save(path, ImageFormat.Jpeg),但是这种方法无法进行进一步控制图片质量
/// </summary>
/// <param name="path"></param>
/// <param name="img"></param>
/// <param name="quality">1~100整数,无效值,则取默认值95</param>
/// <param name="mimeType"></param>
public void SaveImage2File(string path, Image destImage, int quality, string mimeType = "image/jpeg")
{
if (quality <= 0 || quality > 100) quality = 95;
//创建保存的文件夹
FileInfo fileInfo = new FileInfo(path);
if (!Directory.Exists(fileInfo.DirectoryName))
{
Directory.CreateDirectory(fileInfo.DirectoryName);
}
//设置保存参数,保存参数里进一步控制质量
EncoderParameters encoderParams = new EncoderParameters();
long[] qua = new long[] { quality };
EncoderParameter encoderParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
encoderParams.Param[0] = encoderParam;
//获取指定mimeType的mimeType的ImageCodecInfo
var codecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(ici => ici.MimeType == mimeType);
destImage.Save(path, codecInfo, encoderParams);
}
/// <summary>
/// 获取高清的Graphics
/// </summary>
/// <param name="img"></param>
/// <returns></returns>
public Graphics GetGraphics(Image img)
{
var g = Graphics.FromImage(img);
//设置质量
g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
//InterpolationMode不能使用High或者HighQualityBicubic,如果是灰色或者部分浅色的图像是会在边缘处出一白色透明的线
//用HighQualityBilinear却会使图片比其他两种模式模糊(需要肉眼仔细对比才可以看出)
g.InterpolationMode = InterpolationMode.Default;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
return g;
}
/// <summary>
/// 获取文字水印位置
/// </summary>
/// <param name="pos">
/// 1左上,2中上,3右上
/// 4左中,5中, 6右中
/// 7左下,8中下,9右下
/// </param>
/// <returns></returns>
public StringFormat GetStringFormat(int pos)
{
StringFormat format = new StringFormat();
switch (pos)
{
case 1: format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Near; break;
case 2: format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Near; break;
case 3: format.Alignment = StringAlignment.Far; format.LineAlignment = StringAlignment.Near; break;
case 4: format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Center; break;
case 6: format.Alignment = StringAlignment.Far; format.LineAlignment = StringAlignment.Center; break;
case 7: format.Alignment = StringAlignment.Near; format.LineAlignment = St以上是关于C#中基于GDI+(Graphics)图像处理系列之前言的主要内容,如果未能解决你的问题,请参考以下文章
C# 获取指定 “QQ头像“ 绘制 “圆形头像框“GDI(Graphics)
C# 获取指定 “QQ头像“ 绘制 “圆形头像框“GDI(Graphics)
C# 获取指定 “QQ头像“ 绘制 “圆形头像框“GDI(Graphics)