給圖片添加文字水印

功能需求

  1. 在圖片的給定位置上添加文字水印
  2. 水印可以旋轉(zhuǎn)和設(shè)置透明度

先說(shuō)說(shuō)自己的實(shí)現(xiàn)思路:

  1. 先創(chuàng)建具有透明背景色的文字水印圖像
  2. 將水印圖像添加到原圖像中

實(shí)現(xiàn)

首先創(chuàng)建一個(gè)接口,用于約束水印的創(chuàng)建方式:

public interface IWatermark
{
    Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle);
}

具體實(shí)現(xiàn):

public class Watermark : IWatermark
{
    //水印畫(huà)布
    protected virtual Rectangle WatermarkCanvas { set; get; }

    protected Watermark(){}

    public Watermark(string markText, Font font)
    {
        int width = (int)((markText.Length + 1) * font.Size);
        int height = font.Height;
        WatermarkCanvas = new Rectangle(0, 0, width, height);
    }

    /// <summary>
    /// 給圖片添加水印土榴,文字大小以像素(Pixel)為計(jì)量單位
    /// </summary>
    /// <param name="filename">圖片文件全名</param>
    public Bitmap Mark(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency)
    {
        return CreateMarkCore(filename, markText, font, brush, positionX, positionY, angle, transparency);
    }

    /// <summary>
    /// 繪制文字水印坟冲,文字大小以像素(Pixel)為計(jì)量單位
    /// </summary>
    public virtual Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle)
    {
        Bitmap watermark = new Bitmap(rectangle.Width, rectangle.Height);
        Graphics graphics = Graphics.FromImage(watermark);
        //消除鋸齒
        graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
        graphics.DrawString(markText, font, brush, rectangle);
        graphics.Dispose();
        return watermark;
    }

    /// <summary>
    /// 給圖片添加水印奖唯,文字大小以像素(Pixel)為計(jì)量單位
    /// </summary>
    /// <param name="filename">圖片文件全名</param>
    protected virtual Bitmap CreateMarkCore(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency)
    {
        if (!File.Exists(filename))
        {
            throw new FileNotFoundException("文件不存在门坷!");
        }
        Bitmap resultImg;
        using (Bitmap rawImg = new Bitmap(filename))
        {
            using (Bitmap watermarkImg = CreateWatermark(markText, font, brush, WatermarkCanvas))
            using (Bitmap rotateImg = Rotate(watermarkImg, angle))
            {
                using (Bitmap temp = SetAlpha(rotateImg, transparency))
                {
                    resultImg = new Bitmap(rawImg.Width, rawImg.Height);
                    using (Graphics newGraphics = Graphics.FromImage(resultImg))
                    {
                        newGraphics.DrawImage(rawImg, 0, 0);
                        newGraphics.DrawImage(temp, positionX, positionY);
                    }
                }
            }
        }
        return resultImg;
    }
}

水印圖片透明度設(shè)置和旋轉(zhuǎn)(下面這段代碼和上面一段代碼都位于Watermark類(lèi)中鲁僚,因?yàn)榇a量較大盔沫,所以分開(kāi)來(lái)展示):

public class Watermark : IWatermark
{
        protected Bitmap Rotate(Bitmap rawImg, int angle)
        {
            angle = angle % 360;
            //弧度轉(zhuǎn)換
            double radian = TranslateAngleToRadian(angle);
            //原圖的寬和高
            int width = rawImg.Width;
            int height = rawImg.Height;
            //旋轉(zhuǎn)之后圖像的寬和高
            Rectangle rotateRec = RecalculateRectangleSize(width, height, angle);
            int rotateWidth = rotateRec.Width;
            int rotateHeight = rotateRec.Height;
            //目標(biāo)位圖
            Bitmap targetImg = new Bitmap(rotateWidth, rotateHeight);
            Graphics targetGraphics = Graphics.FromImage(targetImg);
            //計(jì)算偏
            Point Offset = new Point((rotateWidth - width) / 2, (rotateHeight - height) / 2);
            //構(gòu)造圖像顯示區(qū)域:讓圖像的中心與窗口的中心點(diǎn)一致
            Rectangle rect = new Rectangle(Offset.X, Offset.Y, width, height);
            Point centerPoint = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
            targetGraphics.TranslateTransform(centerPoint.X, centerPoint.Y);
            targetGraphics.RotateTransform(angle);
            //恢復(fù)圖像在水平和垂直方向的平移
            targetGraphics.TranslateTransform(-centerPoint.X, -centerPoint.Y);
            targetGraphics.DrawImage(rawImg, rect);
            //重至繪圖的所有變換
            targetGraphics.ResetTransform();
            targetGraphics.Save();
            targetGraphics.Dispose();
            return targetImg;
        }

        /// <summary>
        /// 設(shè)置圖像透明度四康,0:全透明铭若,255:不透明
        /// </summary>
        protected Bitmap SetAlpha(Bitmap rawImg, int alpha)
        {
            if (!(0 <= alpha) && alpha <= 255)
            {
                throw new ArgumentOutOfRangeException("alpha ranges from 0 to 255.");
            }
            //顏色矩陣
            float[][] matrixItems =
            {
                new float[]{1,0,0,0,0},
                new float[]{0,1,0,0,0},
                new float[]{0,0,1,0,0},
                new float[]{0,0,0,alpha/255f,0},
                new float[]{0,0,0,0,1}
            };
            ColorMatrix colorMatrix = new ColorMatrix(matrixItems);
            ImageAttributes imageAtt = new ImageAttributes();
            imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
            Bitmap resultImg = new Bitmap(rawImg.Width, rawImg.Height);
            Graphics g = Graphics.FromImage(resultImg);
            g.DrawImage(rawImg, new Rectangle(0, 0, rawImg.Width, rawImg.Height),
                    0, 0, rawImg.Width, rawImg.Height, GraphicsUnit.Pixel, imageAtt);
            g.Dispose();

            return resultImg;
        }

        protected double TranslateAngleToRadian(float angle)
        {
            return angle * Math.PI / 180;
        }

        protected Rectangle RecalculateRectangleSize(int width, int height, float angle)
        {
            double radian = TranslateAngleToRadian(angle);
            double cos = Math.Cos(radian);
            double sin = Math.Sin(radian);
            double newWidth = (int)(Math.Max(Math.Abs(width * cos - height * sin), Math.Abs(width * cos + height * sin)));
            double newHeight = (int)(Math.Max(Math.Abs(width * sin - height * cos), Math.Abs(width * sin + height * cos)));
            return new Rectangle(0, 0, (int)newWidth, (int)newHeight);
        }

    }

Watermark類(lèi)對(duì)外暴露了API:Bitmap Mark(string filename, string markText, Font font, Brush brush, float positionX, float positionY, int angle, int transparency)洪碳,向圖片中添加水印只需創(chuàng)建Watermark實(shí)例,然后調(diào)用該方法即可叼屠。具體實(shí)現(xiàn)代碼如下:

//.NET中瞳腌,F(xiàn)ont尺寸的默認(rèn)單位是Point,這里統(tǒng)一使用Pixel作為計(jì)量單位
string path = @"C:\Users\chiwenjun\Desktop\1.PNG";
string markText = "字體:微軟雅黑";
Font font = new Font("微軟雅黑", 40, FontStyle.Bold, GraphicsUnit.Pixel);
Watermark watermark = new Watermark(markText, font);
Bitmap img = watermark.Mark(path, markText, font, new SolidBrush(Color.FromArgb(0, 0, 0)), 160, 535, 0, 180);
img.Save(path, ImageFormat.Png);
原圖
添加水印效果圖
水印順時(shí)針旋轉(zhuǎn)55<sup>0</sup>效果

旋轉(zhuǎn)前后镜雨,水印圖像的寬和高會(huì)發(fā)生變化嫂侍,如下圖所示:

水印圖片旋轉(zhuǎn)前后寬高變化

擴(kuò)展

上面的代碼很好的實(shí)現(xiàn)了在圖片上添加單行水印的效果,若要實(shí)現(xiàn)多行水印可以通過(guò)對(duì)Watermark類(lèi)的擴(kuò)展來(lái)實(shí)現(xiàn)荚坞。
創(chuàng)建類(lèi)MultiLineWatermark繼承自Watermark挑宠,然后覆寫(xiě)屬性WatermarkCanvas來(lái)指定水印畫(huà)布的大小西剥;覆寫(xiě)方法CreateWatermark來(lái)實(shí)現(xiàn)多行水印效果痹栖。


    public class MultiLineWatermark : Watermark
    {
        protected int _canvasWidth = 0;
        protected int _canvasHeight = 0;
        //每行水印所允許的最大字?jǐn)?shù)
        protected int _lineMaxLength = 0;
        //水印所允許的最大字?jǐn)?shù)
        protected int _wordMaxLength = 0;

        protected override Rectangle WatermarkCanvas
        {
            get
            {
                return new Rectangle(0, 0, this._canvasWidth, this._canvasHeight);
            }
        }

        public MultiLineWatermark(int canvasWidth, int canvasHeight, int lineMaxLength, int wordMaxLength)
        {
            this._canvasWidth = canvasWidth;
            this._canvasHeight = canvasHeight;
            this._lineMaxLength = lineMaxLength;
            this._wordMaxLength = wordMaxLength;
        }

        public override Bitmap CreateWatermark(string markText, Font font, Brush brush, Rectangle rectangle)
        {
            Bitmap watermark = new Bitmap(rectangle.Width, rectangle.Height);
            Graphics graphics = Graphics.FromImage(watermark);
            //消除鋸齒
            graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            int lineHeight = _canvasHeight / (_wordMaxLength / _lineMaxLength);
            if (markText.Contains('#'))
            {
                string[] textList = markText.Split('#');
                int count = (int)Math.Min(textList.Length, Math.Ceiling(_wordMaxLength * 1.0 / _lineMaxLength));
                for (int i = 0; i < count; i++)
                {
                    if (textList[i].Length > _lineMaxLength)
                    {
                        textList[i] = textList[i].Substring(0, _lineMaxLength);
                    }
                    //文字居中
                    graphics.DrawString(textList[i], font, brush, (rectangle.Width - textList[i].Length * font.Size) / 2, i * lineHeight);
                }
            }
            else
            {
                //文字居中
                if (markText.Length <= _lineMaxLength)
                {
                    graphics.DrawString(markText, font, brush, (rectangle.Width - markText.Length * font.Size) / 2, 0);
                }
                else
                {
                    int count = (int)Math.Min(Math.Ceiling(_wordMaxLength * 1.0 / _lineMaxLength), Math.Ceiling(markText.Length * 1.0 / _lineMaxLength));
                    string[] temp = new string[count];
                    for (int i = 0; i < count; i++)
                    {
                        if (i * _lineMaxLength + _lineMaxLength <= markText.Length)
                        {
                            temp[i] = markText.Substring(i * _lineMaxLength, _lineMaxLength);
                        }
                        else
                        {
                            temp[i] = markText.Substring(i * _lineMaxLength, markText.Length - i * _lineMaxLength);
                        }
                        graphics.DrawString(temp[i], font, brush, (rectangle.Width - temp[i].Length * font.Size) / 2, i * lineHeight);
                    }
                }
            }
            graphics.Dispose();
            return watermark;
        }
    }

具體的使用方式和調(diào)用Watermark類(lèi)似,代碼如下:

string path = @"C:\Users\chiwenjun\Desktop\1.PNG";
//以#作為換行標(biāo)記
string markText = "字體:#微軟雅黑雅黑雅黑";
Font font = new Font("微軟雅黑", 40, FontStyle.Bold, GraphicsUnit.Pixel);
//若字?jǐn)?shù)超過(guò)每行所允許的最大值瞭空,超出部分被忽略
int lineMaxLength = 7;
//超出的字?jǐn)?shù)會(huì)被忽略
int wordMaxLength = 14;
//行高揪阿,用于計(jì)算水印圖像的高
int lineHeight = 55;
int width = (int)((lineMaxLength + 1) * font.Size);
int height = (int)(Math.Ceiling(wordMaxLength * 1.0 / lineMaxLength) * lineHeight);
Watermark watermark = new MultiLineWatermark(width, height, lineMaxLength, wordMaxLength);
Bitmap img = watermark.Mark(path, markText, font, new SolidBrush(Color.FromArgb(0, 0, 0)), 150, 535, 0, 180);
img.Save(path, ImageFormat.Png);

多行水印的文字是居中顯示的:

多行水印效果圖

若沒(méi)有使用#標(biāo)記換行,當(dāng)一行字?jǐn)?shù)超過(guò)指定的最大字?jǐn)?shù)時(shí)咆畏,會(huì)自動(dòng)換行南捂。

自動(dòng)換行效果

這篇文章是對(duì)自己項(xiàng)目中添加水印功能的記錄,通篇以代碼為主旧找,看起來(lái)可能會(huì)感覺(jué)比較枯燥溺健。
功能的實(shí)現(xiàn)沒(méi)有太多難點(diǎn),唯有一點(diǎn)感受較深钮蛛,就是水印圖像寬和高的計(jì)算鞭缭。.NET(.NET Framework 4.5)中字體大小的度量單位默認(rèn)是Point剖膳,而圖像的度量單位是Pixel,單位的不同導(dǎo)致水印圖像尺寸的計(jì)算出現(xiàn)偏差岭辣,這一點(diǎn)折磨我很久吱晒。
圖像旋轉(zhuǎn)和透明度設(shè)置的兩個(gè)方法RotateSetAlpha是在網(wǎng)友代碼基礎(chǔ)上修改得到,非本人原創(chuàng)沦童,代碼原文已在參考文章中列出仑濒,在此對(duì)兩位網(wǎng)友表示感謝。

參考文章:

C#圖像旋轉(zhuǎn)
設(shè)置圖片透明度的四種方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末偷遗,一起剝皮案震驚了整個(gè)濱河市墩瞳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌氏豌,老刑警劉巖喉酌,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異箩溃,居然都是意外死亡瞭吃,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)涣旨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)歪架,“玉大人,你說(shuō)我怎么就攤上這事霹陡『万剑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵烹棉,是天一觀的道長(zhǎng)攒霹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)浆洗,這世上最難降的妖魔是什么催束? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮伏社,結(jié)果婚禮上抠刺,老公的妹妹穿的比我還像新娘。我一直安慰自己摘昌,他們只是感情好速妖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著聪黎,像睡著了一般罕容。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天锦秒,我揣著相機(jī)與錄音露泊,去河邊找鬼。 笑死脂崔,一個(gè)胖子當(dāng)著我的面吹牛滤淳,可吹牛的內(nèi)容都是我干的梧喷。 我是一名探鬼主播砌左,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼铺敌!你這毒婦竟也來(lái)了汇歹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤偿凭,失蹤者是張志新(化名)和其女友劉穎产弹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體弯囊,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痰哨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匾嘱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斤斧。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖霎烙,靈堂內(nèi)的尸體忽然破棺而出撬讽,到底是詐尸還是另有隱情,我是刑警寧澤悬垃,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布游昼,位于F島的核電站,受9級(jí)特大地震影響尝蠕,放射性物質(zhì)發(fā)生泄漏烘豌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一看彼、第九天 我趴在偏房一處隱蔽的房頂上張望廊佩。 院中可真熱鬧,春花似錦闲昭、人聲如沸罐寨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸯绿。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓶蝴,已是汗流浹背毒返。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舷手,地道東北人拧簸。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像男窟,于是被迫代替她去往敵國(guó)和親盆赤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容