通过C#给图片添加任意文字的水印
近日有图片通过C#加水印的需求,在网上搜到了这个:https://www.jb51.net/program/319824nmi.htm
但是提供的代码有不少问题。经过分析和修改,整理为了这样的代码:
oldpath为原图片路径,newpath为保存到的图片路径。text为水印文字内容。rotate是以°为单位的角度,repeatD是垂直或水平相邻两次水印的间隔像素(设置为0时只显示一次文字),Trans是水印的透明度。
因为用不到90度的文字,所以只对Math.tan(π/2)报了异常。
源代码中的textWidth 、textHeight几乎是干扰项,实际上制作水印是根据MeasureString(text, font)来计算水印文字的长宽的,xcount和ycount也应该根据此长宽调整数量,而不是固定的+3。
此代码已经在项目中实际使用。

解释一下旋转功能:原图是黑色矩形(宽w高h),旋转是根据点(0,0)进行的,如果我们将其旋转一定角度就会得到灰色矩形,可以看到灰色矩形无法完全覆盖黑色矩形的区域。所以需要对黑色矩形外接一个红色矩形,从红色矩形的左上角开始绘制水印。
对于程序来说,原点还是(0,0),红色矩形左上边的x都是0,故x的取值范围是[0,wcos+hsin],y的取值范围是[-wsin,hcos]。
PS:在画图里测试了一下旋转前后文字左上角距离(0,0)的长度一致时,高兴了好一阵子=_= 不过至少是明白了旋转的原理了。
`
public void AddWaterText(string oldpath, string text, string newpath, System.Drawing.Font font, Color color, float rotate = 15.0f, int repeatD = 50, int Trans = 7)
{
FileStream fs = new FileStream(oldpath, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] bytes = br.ReadBytes((int)fs.Length);
br.Close();
fs.Close();
MemoryStream ms = new MemoryStream(bytes);
Image imgPhoto = Image.FromStream(ms);
int imgPhotoWidth = imgPhoto.Width;
int imgPhotoHeight = imgPhoto.Height;
Bitmap bmPhoto = new Bitmap(imgPhotoWidth, imgPhotoHeight, PixelFormat.Format24bppRgb);
bmPhoto.SetResolution(72, 72);
Graphics gbmPhoto = Graphics.FromImage(bmPhoto);
gbmPhoto.Clear(Color.White);
gbmPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
gbmPhoto.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
gbmPhoto.DrawImage(imgPhoto, new Rectangle(0, 0, imgPhotoWidth, imgPhotoHeight), 0, 0, imgPhotoWidth, imgPhotoHeight, GraphicsUnit.Pixel);
SizeF crSize = gbmPhoto.MeasureString(text, font);
float y = 0;
float x = 0;
StringFormat StrFormat = new StringFormat();
StrFormat.Alignment = StringAlignment.Center;
SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(Trans, color.R, color.G, color.B));
gbmPhoto.RotateTransform(rotate);
//如果原本要在(100,50)点一个点,顺时针旋转了30度后,显示在图片中这个点就到(50,100)了。经过测试我发现了文字旋转前后相对左上角点的距离不变。所以如果还按图片尺寸平铺绘制水印,右上就会缺一块儿三角形区域。所以程序需要在外接矩形中绘制文字。原图宽w,高h,则以特定角度外接矩形,相对x取值范围是【0,wcos+hsin】,y的取值范围是【-wsin,hcos】
if (repeatD == 0)
{
gbmPhoto.DrawString(text, font, semiTransBrush, x, y);
}
else
{
double arc = rotate / 180 * Math.PI;//弧度
if (double.IsInfinity(Math.Tan(arc))) throw new Exception("不允许90度设置水印");
y = -(int)(imgPhotoWidth * Math.Sin(arc));//外接矩形的x取值范围是【0,wcos+hsin】,y的取值范围是【-wsin,hcos】
int ycount = (int)Math.Ceiling((imgPhotoWidth * Math.Sin(arc) + imgPhotoHeight * Math.Cos(arc)) / (crSize.Height + repeatD));//向上取整,避免最后一行空白。
x = 0;
int xcount = (int)Math.Ceiling((imgPhotoWidth * Math.Cos(arc) + imgPhotoHeight * Math.Sin(arc)) / (crSize.Width + repeatD));//向上取整,避免最后一列空白。
for (int k = 0; k < ycount; k++)
{
for (int i = 0; i < xcount; i++)
{
gbmPhoto.DrawString(text, font, semiTransBrush, x, y);//想要再优化的话,就在这里提前判断一次是否绘制的矩形在画布上,如果不在就跳过。
x += crSize.Width + repeatD;
}
x = 0;//一行写完,换到下一行开头。
y += crSize.Height + repeatD;
}
}
bmPhoto.Save(newpath);
gbmPhoto.Dispose();
imgPhoto.Dispose();
bmPhoto.Dispose();
}//https://www.jb51.net/program/319824nmi.htm
`
希望越来越多的开发者在面向百度编程,借用各种博客代码后,能想起来分享一些自己的代码~
2024/07/15 当代码实际使用起来,发现效率很低。经过检查发现原本程序里有一个毫无意义的循环,之前程序是用tan计算原始绘制点的,导致程序会把大部分的绘制都放在画布之外,浪费了很多性能。

浙公网安备 33010602011771号