最近在開發(fā)一個項目,需要對圖片進行處理,比如生成縮略圖、生成圖片驗證碼、圖片添加水印等功能,項目使用.netcore6.0開發(fā),開發(fā)系統(tǒng)使用的云桌面(win10系統(tǒng)),由于是云桌面系統(tǒng),無法在開發(fā)時使用docker進行調(diào)試,docker desktop無法啟動,原因是云桌面系統(tǒng)禁止了系統(tǒng)更新,導致安裝了docker desktop需要更新系統(tǒng)某些功能失敗,所以docker desktop無法啟動,不知道大家有沒有遇到過這個問題,有沒有解決辦法,可以告訴我一下。由于docker desktop無法啟動,所以也無法在本地模擬docker運行,本地開發(fā)一直都是使用的vs2022自帶的IISExpress進行的調(diào)試和開發(fā),此處對系統(tǒng)進行了詳細說明就是為了說明為什么在開發(fā)過程中為什么沒有遇到圖片處理的問題,因為使用的windows系統(tǒng)進行的調(diào)試,一直沒有出現(xiàn)問題,直到發(fā)布了測試版本到docker(liunx)上面,才發(fā)現(xiàn)了問題,圖片上傳不了,一直報錯。錯誤信息如下:
System.TypeInitializationException: The type initializer for ‘Gdip’ threw an exception.
—> System.DllNotFoundException: Unable to load shared library ‘libgdiplus’ or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibgdiplus: cannot open shared object file: No such file or directory
錯誤大致意思就是加載libgdiplus組件失敗,確實我剛部署上去時沒有安裝這個組件,Liunx系統(tǒng)是需要單獨安裝這個組件,于是我按百度搜索到的方法安裝了組件,但是安裝完成后,問題仍然還在,報的錯誤還是一樣。于是繼續(xù)查詢解決辦法,但是各種方法都試了,問題仍然沒有解決。最后我找到一篇文章,內(nèi)容如下 :
.NET 6之前
在Linux服務器上安裝 libgdiplus 即可解決,安裝方法可參考原文或者在百度搜索,由于本人使用的是.net6,沒有成功,具體方法不再說明
NET 6及以后
由于官方不再支持在非Windows環(huán)境使用libgdiplus,需要單獨開啟運行時環(huán)境支持
處理步驟
- 按照.NET 6之前的方案安裝 libgdiplus
- runtimeconfig.json配置文件中新增“System.Drawing.EnableUnixSupport": true,這句代碼加在runtimeOptions結點下面的,configProperties結點下面
- 構建項目時,會在輸出目錄中生成[appname].runtimeconfig.json文件,只需要修改該配置文件即可
按這個方法處理了之后,項目運行仍然還是報錯,報的錯誤和之前一樣,所以問題仍然沒有解決
原文地址:http://www.rzrgm.cn/yswenli/archive/2022/02/15/15895485.html
最后我也沒有辦法了,只好尋找新的解決方案,采用ImageSharp組件重寫,還有其他兩個組件可以選擇SkiaSharp和Microsoft.Maui.Graphics,這三種方法都是跨平臺支持的,不需要另外安裝單獨的組件。
需要安裝這三個包:SixLabors.Fonts,SixLabors.ImageSharp,SixLabors.ImageSharp.Drawing
下面簡單的貼幾個常用用圖片處理方法的代碼,注意:以下幾個方法,除了保存圖片,生成縮略圖以及生成驗證碼的方法驗證過,其他方法暫時還沒有用到也沒有去驗證,如果遇到問題歡迎指正
1.保存圖片并按要求壓縮圖片,并且按要求生成縮略圖
/// <summary>
/// 保存圖片
/// </summary>
/// <param name="stream">文件流</param>
/// <param name="path">保存路徑,相對路徑 </param>
/// <param name="isCompress">是否壓縮,如果圖片超過限制大小,是否壓縮圖片</param>
/// <param name="size">限制大小</param>
/// <param name="width">圖片寬度</param>
/// <param name="height">圖片高度</param>
/// <param name="flag">質(zhì)量</param>
public static bool CreateImage(Stream stream, string path, bool isCompress, decimal size, int width, int height, int flag, List<int> thumSizes, out string msg)
{
msg = "";
byte[] filebytes = stream.ToByteArray();
string savepath = FileHelper.GetMapPath(path);
FileHelper.CreatePath(savepath);
stream.Position = 0;
//如果是第一次調(diào)用,原始圖像的大小小于要壓縮的大小,則直接復制文件,并且返回true
bool needCompress = false;
using(Image iSource = Image.Load(stream))
{
try
{
iSource.Save(savepath);
if(isCompress && ((iSource.Width > width && iSource.Height > height) || stream.Length > size * 1024 * 1024))
needCompress = true;
}
catch(Exception ex)
{
msg = ex.Message;
return false;
}
finally
{
iSource.Dispose();
}
}
if(needCompress)
{
CompressImage(filebytes, savepath, width, height, flag, (size * 1024).ToInt());
}
if(thumSizes != null && thumSizes.Count > 0)
{
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
foreach(int thumsize in thumSizes)
{
CreateThumbnail(filebytes, thumsize, thumsize, savepath);
}
});
}
return true;
}
2.生成縮略圖,其中參數(shù)bytes是由stream生成
/// <summary>
/// 生成縮略圖
/// </summary>
/// <param name="filebytes">圖片文件保存的字節(jié)數(shù)組</param>
/// <param name="thumbnailPath">縮略圖保存的絕對路徑及文件名</param>
/// <param name="width">縮略圖長度</param>
/// <param name="height">縮略圖寬度</param>
/// <param name="path">保存路徑</param>
public static void CreateThumbnail(byte[] filebytes, int width, int height, string path)
{
using(MemoryStream ms = new MemoryStream(filebytes))
{
Image imageFrom = Image.Load(ms);
//var original = bytes.ToStream();
var fileExt = FileHelper.GetFileExt(path);
var newpath = path.Replace(fileExt, "_" + width + fileExt);
if(imageFrom.Width <= width && imageFrom.Height <= height)
{
// 如果源圖的大小沒有超過縮略圖指定的大小,則直接把源圖復制到目標文件
imageFrom.Save(newpath);
imageFrom.Dispose();
return;
}
// 源圖寬度及高度
int imageFromWidth = imageFrom.Width;
int imageFromHeight = imageFrom.Height;
float scale = height / (float)imageFromHeight;
if((width / (float)imageFromWidth) < scale)
scale = width / (float)imageFromWidth;
width = (int)(imageFromWidth * scale);
height = (int)(imageFromHeight * scale);
imageFrom.Mutate(x => x.Resize(width, height));
imageFrom.Save(newpath);
imageFrom.Dispose();
}
}
3. 壓縮圖片 暫時不支持通過壓縮質(zhì)量的方法來壓縮圖片
/// <summary>
/// 無損壓縮圖片
/// </summary>
/// <param name="sFile">原圖片地址</param>
/// <param name="dFile">壓縮后保存圖片地址</param>
/// <param name="flag">壓縮質(zhì)量(數(shù)字越小壓縮率越高)1-100</param>
/// <param name="size">壓縮后圖片的最大大小</param>
/// <param name="isCreateSmallImage">是否生成有損的質(zhì)量較小的圖</param>
/// <param name="sfsc">是否是第一次調(diào)用</param>
/// <returns></returns>
public static bool CompressImage(byte[] bytes, string dFile, int width, int height, int flag = 90, int size = 300, bool isCreateSmallImage = false, bool sfsc = true)
{
Stream stream = bytes.ToStream();
//如果是第一次調(diào)用,原始圖像的大小小于要壓縮的大小,則直接復制文件,并且返回true
using(Image image = Image.Load(stream))
{
if(sfsc == true && ((image.Width < width && image.Height < height && isCreateSmallImage) || stream.Length < size * 1024))
{
// 如果源圖的大小沒有超過縮略圖指定的大小,直接返回
return true;
}
var imageFromWidth = image.Width;
var imageFromHeight = image.Height;
if(isCreateSmallImage)
{
var scale = height / (float)imageFromHeight;
if((width / (float)imageFromWidth) < scale)
scale = width / (float)imageFromWidth;
width = (int)(imageFromWidth * scale);
height = (int)(imageFromHeight * scale);
}
else
{
height = image.Height;
width = image.Width;
}
// Resize the image in place and return it for chaining.
// 'x' signifies the current image processing context.
image.Mutate(x => x.Resize(width, height));
// The library automatically picks an encoder based on the file extensions then encodes and write the data to disk.
image.Save(dFile);
return true;
}
}
4.添加文字水印
/// <summary>
/// 生成文字水印
/// </summary>
/// <param name="originalPath">源圖路徑</param>
/// <param name="targetPath">保存路徑</param>
/// <param name="text">水印文字</param>
/// <param name="textSize">文字大小</param>
/// <param name="textFont">文字字體</param>
/// <param name="position">位置0 居中 1 左上 2 右上 3 左下 4右下</param>
public static void GenerateTextWatermark(string originalPath, string targetPath, string text, int textSize, EnumPosition position, string textFont = "", float px = 0, float py = 0)
{
var image = Image.Load(originalPath);
// Clone會返回一個經(jīng)過處理的深度拷貝的image對象.
//直接處理用Mutate=>Action
var newImage = image.Clone(x =>
{
// 獲取系統(tǒng)默認字體
Font font = null;
FontCollection fonts = new FontCollection();
if(!textFont.IsEmptyString())
{
//裝載字體(ttf)
FontFamily fontfamily = fonts.Add(textFont);
if(fontfamily != null)
{
font = new Font(fontfamily, textSize, FontStyle.Bold);
}
}//沒有指定字體則加載系統(tǒng)默認字體
else if(SystemFonts.Families != null && SystemFonts.Families.Count() > 0)
{
font = SystemFonts.CreateFont(SystemFonts.Families.First().Name, textSize, FontStyle.Bold);
}
else //如果系統(tǒng)默認字體加載失敗,則指定字體,請注意要將simhei.ttf字體放在根目錄,字體文件可以更換
{
FontFamily fontfamily = fonts.Add("simhei.ttf");//字體的路徑,也就是可以使用配置文件來指定字體
font = new Font(fontfamily, textSize, FontStyle.Bold);
}
//獲取該文件繪制所需的大小
var size = TextMeasurer.Measure(text, new TextOptions(font));
//繪制.這里是右下角, 也可以加入?yún)?shù)動態(tài)處理左上/右下/居中等...
//繪制圖片等也是類似
//計算文字位置,默認居中
var pointX = (image.Width - size.Width) / 2;
var pointY = (image.Height - size.Height) / 2;
(pointX, pointY) = GetPosition(image, size.Width, size.Height, position, px, py);
x.DrawText(text, font, Color.WhiteSmoke,
new PointF(pointX, pointY));
});
newImage.Save(targetPath);
}
/// <summary>
/// 獲取圖片位置點
/// </summary>
/// <param name="image"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="position"></param>
/// <returns></returns>
private static (float, float) GetPosition(Image image, float width, float height, EnumPosition position, float x = 0, float y = 0)
{
var pointX = (image.Width - width) / 2;
var pointY = (image.Height - height) / 2;
switch(position)
{
case EnumPosition.LeftTop:
pointX = 2;
pointY = 2;
break;
case EnumPosition.RightTop:
pointX = image.Width - width - 2;
pointY = 2;
break;
case EnumPosition.LeftMiddle:
pointX = 2;
pointY = (image.Height - height) / 2;
break;
case EnumPosition.LeftBottom:
pointX = 2;
pointY = image.Height - height - 2;
break;
case EnumPosition.RightMiddle:
pointX = image.Width - width - 2;
pointY = (image.Height - height) / 2;
break;
case EnumPosition.RightBottom:
pointX = image.Width - width - 2;
pointY = image.Height - height - 2;
break;
case EnumPosition.TopCenter:
pointX = (image.Width - width) / 2;
pointY = 2;
break;
case EnumPosition.BottomCenter:
pointX = (image.Width - width) / 2;
pointY = image.Height - height - 2;
break;
case EnumPosition.Custom:
pointX = x;
pointY = y;
break;
}
return (pointX, pointY);
}
5. 添加圖片水印以及合并圖片
/// <summary>
/// 添加圖片水印
/// </summary>
/// <param name="originalPath"></param>
/// <param name="watermarkPath"></param>
/// <param name="targetPath"></param>
/// <param name="position"></param>
/// <param name="width"></param>
/// <param name="heihgt"></param>
/// <param name="x"></param>
/// <param name="y"></param>
public static void GenerateImageWatermark(string originalPath, string watermarkPath, string targetPath, EnumPosition position, int width, int heihgt, float x = 0, float y = 0)
{
var image = Image.Load(originalPath);
var waterimage = Image.Load(watermarkPath);
if(width > image.Width) { width = image.Width; }
if(heihgt > image.Height) { heihgt = image.Height; }
float pointX = (image.Width - width) / 2;
float pointY = (image.Height - heihgt) / 2;
(pointX, pointY) = GetPosition(image, waterimage.Width, waterimage.Height, position, x, y);
var newImage = MergeImage(image, waterimage, pointX.ToInt(), pointY.ToInt(), width, heihgt);
newImage.Save(targetPath);
}
/// <summary>
/// 合并圖片
/// </summary>
/// <param name="templateImage"></param>
/// <param name="mergeImagePath">合并圖片</param>
/// <param name="x">X坐標</param>
/// <param name="y">y坐標</param>
/// <param name="width">寬度</param>
/// <param name="height">高度</param>
/// <returns></returns>
public static Image MergeImage(Image templateImage, Image mergeImage, int x, int y, int width, int height)
{
mergeImage.Mutate(m =>
{
m.Resize(new Size(width, height));
});
templateImage.Mutate(o =>
{
o.DrawImage(mergeImage, new Point(x, y), 1);
});
return templateImage;
}
6. 生成圖片驗證碼
/// <summary>
/// 獲取圖片驗證碼
/// </summary>
/// <param name="code"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="lineNum">干擾線條數(shù)</param>
/// <param name="pointNum">噪點個數(shù)</param>
/// <returns></returns>
public static MemoryStream GenerateCheckCode(string code, int fontsize = 25, int width = 140, int height = 50, int lineNum = 4, int pointNum = 60)
{
#region 顏色集合,去掉了一些和白色底色接近的顏色
List<Color> colors = new List<Color>();
colors.Add(Color.PaleGreen);
colors.Add(Color.PaleGoldenrod);
colors.Add(Color.Orchid);
colors.Add(Color.OrangeRed);
colors.Add(Color.Orange);
colors.Add(Color.OliveDrab);
colors.Add(Color.Olive);
colors.Add(Color.Navy);
colors.Add(Color.Moccasin);
colors.Add(Color.MidnightBlue);
colors.Add(Color.MediumVioletRed);
colors.Add(Color.MediumTurquoise);
colors.Add(Color.MediumSpringGreen);
colors.Add(Color.LightSteelBlue);
colors.Add(Color.Lime);
colors.Add(Color.PaleTurquoise);
colors.Add(Color.Magenta);
colors.Add(Color.MediumAquamarine);
colors.Add(Color.MediumBlue);
colors.Add(Color.MediumOrchid);
colors.Add(Color.MediumPurple);
colors.Add(Color.MediumSeaGreen);
colors.Add(Color.MediumSlateBlue);
colors.Add(Color.Maroon);
colors.Add(Color.PaleVioletRed);
colors.Add(Color.PeachPuff);
colors.Add(Color.SpringGreen);
colors.Add(Color.SteelBlue);
colors.Add(Color.Tan);
colors.Add(Color.Teal);
colors.Add(Color.Thistle);
colors.Add(Color.Tomato);
colors.Add(Color.Violet);
colors.Add(Color.Wheat);
colors.Add(Color.Yellow);
colors.Add(Color.YellowGreen);
colors.Add(Color.Turquoise);
colors.Add(Color.LightSkyBlue);
colors.Add(Color.SlateBlue);
colors.Add(Color.Silver);
colors.Add(Color.Peru);
colors.Add(Color.Pink);
colors.Add(Color.Plum);
colors.Add(Color.PowderBlue);
colors.Add(Color.Purple);
colors.Add(Color.Red);
colors.Add(Color.SkyBlue);
colors.Add(Color.RosyBrown);
colors.Add(Color.SaddleBrown);
colors.Add(Color.Salmon);
colors.Add(Color.SandyBrown);
colors.Add(Color.SeaGreen);
colors.Add(Color.Sienna);
colors.Add(Color.RoyalBlue);
colors.Add(Color.LightSeaGreen);
colors.Add(Color.LightSalmon);
colors.Add(Color.LightPink);
colors.Add(Color.Crimson);
colors.Add(Color.Cyan);
colors.Add(Color.DarkBlue);
colors.Add(Color.DarkCyan);
colors.Add(Color.DarkGoldenrod);
colors.Add(Color.DarkGray);
colors.Add(Color.Cornsilk);
colors.Add(Color.DarkGreen);
colors.Add(Color.DarkMagenta);
colors.Add(Color.DarkOliveGreen);
colors.Add(Color.DarkOrange);
colors.Add(Color.DarkOrchid);
colors.Add(Color.DarkRed);
colors.Add(Color.DarkSalmon);
colors.Add(Color.DarkKhaki);
colors.Add(Color.DarkSeaGreen);
colors.Add(Color.CornflowerBlue);
colors.Add(Color.Chocolate);
colors.Add(Color.AntiqueWhite);
colors.Add(Color.Aqua);
colors.Add(Color.Aquamarine);
colors.Add(Color.Bisque);
colors.Add(Color.Coral);
colors.Add(Color.Black);
colors.Add(Color.Blue);
colors.Add(Color.BlueViolet);
colors.Add(Color.Brown);
colors.Add(Color.BurlyWood);
colors.Add(Color.CadetBlue);
colors.Add(Color.Chartreuse);
colors.Add(Color.BlanchedAlmond);
colors.Add(Color.DarkSlateBlue);
colors.Add(Color.DarkTurquoise);
colors.Add(Color.IndianRed);
colors.Add(Color.Indigo);
colors.Add(Color.Khaki);
colors.Add(Color.HotPink);
colors.Add(Color.LawnGreen);
colors.Add(Color.LightBlue);
colors.Add(Color.LightCoral);
colors.Add(Color.LightCyan);
colors.Add(Color.LightGreen);
colors.Add(Color.Green);
colors.Add(Color.DarkViolet);
colors.Add(Color.DeepSkyBlue);
colors.Add(Color.DimGray);
colors.Add(Color.DodgerBlue);
colors.Add(Color.Firebrick);
colors.Add(Color.GreenYellow);
colors.Add(Color.Fuchsia);
colors.Add(Color.Gainsboro);
colors.Add(Color.Gold);
colors.Add(Color.Goldenrod);
colors.Add(Color.ForestGreen);
#endregion
using var image = new Image<Rgba32>(width, height);
// 字體
Font font = null;
FontCollection fonts = new FontCollection();
if(SystemFonts.Families != null && SystemFonts.Families.Count() > 0)
{
font = SystemFonts.CreateFont(SystemFonts.Families.First().Name, fontsize, FontStyle.Bold);
}
else
{
FontFamily fontfamily = fonts.Add("simhei.ttf");//字體的路徑和文件名,默認放在根目錄
font = new Font(fontfamily, fontsize, FontStyle.Bold);
}
var r = new Random();
image.Mutate(ctx =>
{
// 白底背景
ctx.Fill(Color.White);
// 畫驗證碼
for(int i = 0; i < code.Length; i++)
{
ctx.DrawText(code[i].ToString()
, font
, colors[r.Next(colors.Count)]
, new PointF(20 * i + 10, r.Next(2, 12)));
}
// 畫干擾線
for(int i = 0; i < lineNum; i++)
{
var pen = new Pen(colors[r.Next(colors.Count)], 1);
var p1 = new PointF(r.Next(width), r.Next(height));
var p2 = new PointF(r.Next(width), r.Next(height));
ctx.DrawLines(pen, p1, p2);
}
// 畫噪點
for(int i = 0; i < pointNum; i++)
{
var pen = new Pen(colors[r.Next(colors.Count)], 1);
var p1 = new PointF(r.Next(width), r.Next(height));
var p2 = new PointF(p1.X + 1f, p1.Y + 1f);
ctx.DrawLines(pen, p1, p2);
}
});
using var ms = new System.IO.MemoryStream();
// gif 格式
image.SaveAsGif(ms);
return ms;
}
/// <summary>
/// 生成驗證碼,并且返回驗證碼和驗證碼的圖片流
/// </summary>
/// <param name="checkCode">返回的驗證碼</param>
/// <param name="codelen">驗證碼長度,默認為4位</param>
/// <param name="fontsize">字體大小,默認為25</param>
/// <param name="width">圖片寬度,默認為0,根據(jù)字體大小和驗證碼長度計算</param>
/// <param name="height">圖片寬度,默認為0,根據(jù)字體大小來計算</param>
/// <param name="lineNum">干擾線的數(shù)量</param>
/// <param name="pointNum">噪點的數(shù)量</param>
/// <returns></returns>
public static MemoryStream GenerateCheckCode(out string checkCode, int codelen = 4, int fontsize = 25, int width = 0, int height = 0, int lineNum = 4, int pointNum = 60)
{
checkCode = string.Empty;
//驗證碼的字符集,去掉了一些容易混淆的字符 比如1和L,0和O
char[] character = { '2', '3', '4', '5', '6', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W', 'X', 'Y' };
var rnd = new Random();
//生成驗證碼字符串
for(var i = 0; i < codelen; i++)
{
checkCode += character[rnd.Next(character.Length)];
}
if(width == 0) { width = checkCode.Length * (fontsize + 2); }
if(height == 0) { height = fontsize + 4; }
return GenerateCheckCode(checkCode, fontsize, width, height, lineNum, pointNum);
}
7.擴展類 字節(jié)數(shù)組和Stream之間的相互轉(zhuǎn)換
public static class StreamExtensions
{
/// <summary>
/// 數(shù)據(jù)流轉(zhuǎn)換為字節(jié)流
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static byte[] ToByteArray(this Stream stream)
{
var data = new byte[stream.Length];
stream.Read(data, 0, data.Length);
return data;
}
/// <summary>
/// 字節(jié)流轉(zhuǎn)換為數(shù)據(jù)流
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static Stream ToStream(this byte[] bytes)
{
Stream stream = new MemoryStream(bytes);
return stream;
}
}
浙公網(wǎng)安備 33010602011771號