JAVA#中基于GDI+(Graphics)图像处理系列之文字或者图片水印(透明、任意角度旋转)

2019-05-08 17:59:10


参考地址  C#中基于GDI+(Graphics)图像处理系列之文字或者图片水印(透明、任意角度旋转)


由于在绘图过程中旋转有问题,我们可以换个角度去做这个事。我们完全可以先旋转好图片,用旋转好的图片,来打水印。这样反而简单了。思路来自下面的教程。


简介

给图片添加水印是比较常用的功能,通常是用于给图片添加版权的信息或者作者信息。 

本文将重点向大家介绍怎么使用GDI+(Graphics)给图像添加图片水印和文字水印。


图片水印

技术要点

控制图片水印位置

一般水印位置最多为9个,如下图所示,红色矩形区域代表有效绘图区域,9个位置就是根据红色矩形宽高和水印图片宽高(或者旋转后所占区域宽高)计算而来,切记,红色矩形左上角的坐标不是(0,0),这个坐标怎么得到后面的内容里会提到。 

 

代码如下:


/// <summary>

/// 获取图片水印位置,及small在big里的位置

/// 如果small的高度大于big的高度,返回big的高度

/// 如果small的宽度大于big的宽度,返回big的宽度

/// </summary>

/// <param name="pos">

///         1左上,2中上,3右上

///         4左中,5中,  6右中

///         7左下,8中下,9右下

/// </param>

/// <returns></returns>

public Rectangle GetRectangleByPostion(Rectangle big, Rectangle small, int pos)

{

     if (big.Width < small.Width)

     {

          small.Width = big.Width;

     }

     if (big.Height < small.Height)

     {

          small.Height = big.Height;

     }

     Rectangle retVal = small;

     switch (pos)

     {

          case 1: retVal.X = 0; retVal.Y = 0; break;

          case 2: retVal.X = (big.Width - small.Width) / 2; retVal.Y = 0; break;

          case 3: retVal.X = big.Width - small.Width; retVal.Y = 0; break;

          case 4: retVal.X = 0; retVal.Y = (big.Height - small.Height) / 2; break;

          case 6: retVal.X = big.Width - small.Width; retVal.Y = (big.Height - small.Height) / 2; break;

          case 7: retVal.X = 0; retVal.Y = big.Height - small.Height; break;

          case 8: retVal.X = (big.Width - small.Width) / 2; retVal.Y = big.Height - small.Height; break;

          case 9: retVal.X = big.Width - small.Width; retVal.Y = big.Height - small.Height; break;

          default: retVal.X = (big.Width - small.Width) / 2; retVal.Y = (big.Height - small.Height) / 2; break;

     }

     retVal.X += big.X;

     retVal.Y += big.Y;

     return retVal;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

控制图片水印透明度

Graphics.DrawImage方法中有个参数可以传入ImageAttributes,ImageAttributes可以设置颜色矩阵,从而实现DrawImage时透明效果,代码如下:


/// <summary>

/// 获取一个带有透明度的ImageAttributes

/// </summary>

/// <param name="opcity"></param>

/// <returns></returns>

public ImageAttributes GetAlphaImgAttr(int opcity)

{

     if (opcity < 0 || opcity > 100)

     {

          throw new ArgumentOutOfRangeException("opcity 值为 0~100");

     }

     //颜色矩阵

     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,(float)opcity / 100,0},

          new float[]{0,0,0,0,1}

     };

     ColorMatrix colorMatrix = new ColorMatrix(matrixItems);

     ImageAttributes imageAtt = new ImageAttributes();

     imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

     return imageAtt;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

控制图片水印旋转角度

这个问题要说清楚比较费劲,图像处理工具类里有个方法是直接获取图像旋转N度后图像,方法如下:


 /// <summary>

/// 获取原图像绕中心任意角度旋转后的图像

/// </summary>

/// <param name="rawImg"></param>

/// <param name="angle"></param>

/// <returns></returns>

public Image GetRotateImage(Image srcImage, int angle)

///代码略

1

2

3

4

5

6

7

8

获取旋转图像的原理详见本系列的另一篇文章《C#中基于GDI+(Graphics)图像处理系列之任意角度旋转图像》


图片水印方法的代码

注意,传入的参数中padding值决定有效绘图区域,假如,图片大小为200*120,padding值为10,那么有绘图区域就是(10,10,180,100),”控制图片水印位置”章节中,红色矩形左上角的坐标就是(10,10),如下图: 



/// <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;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

注意:如果发现有的方法没有具体实现,请移步《C#中基于GDI+(Graphics)图像处理系列之前言》获取完整的图像处理工具类源码


文字水印

文字水印与图片水印在实现上有所不同,主要原因是因为图片的宽高是固定的,而文字即使字数确定,显示出来的行数无法确定,另外Graphics.DrawString本身是不支持透明的,后面有方法解决这个问题。


技术要点

控制文字水印位置

与图片水印不同,文字的水印位置可以认为是对齐方式,1左上,2中上,3右上,4左中等等,同也有设置内边距的功能(padding)即控制有效绘图区域,如下图: 

 

 



 /// <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 = StringAlignment.Far; break;

         case 8: format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Far; break;

         case 9: format.Alignment = StringAlignment.Far; format.LineAlignment = StringAlignment.Far; break;

         default: format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; break;

     }

     return format;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

控制文字水印透明度

由于DrawString不支持透明度,水印文字的透明需要借助DrawImage,实现步骤如下: 

(1)定义一个与目标图片大小一样的临时画布,var transImg = new Bitmap(destImage); 

(2)在有效绘图区域内,用DrawString将字画到临时画布上(使用StringFormat控制对齐方式) 

(3)将临时画布绘画到目标图布上(控制透明)。


文字水印方法的代码

/// <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;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

注意:如果发现有的代码中没有具体实现,请移步《C#中基于GDI+(Graphics)图像处理系列之前言》获取完整的图像处理工具源码


完整示例程序源码下载

http://download.csdn.net/detail/lhtzbj12/9730116


示例程序截图

 

如果想查阅本系列其他文章,请移步《C#中基于GDI+(Graphics)图像处理系列之前言》



  • 2021-01-13 13:36:29

    shortid nodejs短id生成器

    短ID在实际运用中很广泛, 其中比较典型的运用就是短地址。 市面上肯定有不少开源的生成短ID库, 基于node.js的估计也不少。 鉴于本人已然是node.js的脑残粉(本职java开发), 很多业余项目从前端到后端都基于javascript开发, 加上npm和bower的包管理以及grunt的打包工具, 在项目开发过程中体验特别酸爽。 由于当时项目前后端都会用到短ID, 但没找到合适的库同时支持npm和bower的(可能孤陋寡闻). 因此自己乐此不疲地又造了个轮子js-shortid(夷,为什么会说又呢?!). 下面主要介绍它的实现方案, 自认为比较优雅简洁。

  • 2021-01-13 17:23:21

    CREATE TABLE 表名 AS SELECT 语句 快速复制表但是锁表

    注意Table2的主键约束,如果Table2有主键而且不为空,则 field1, field2…中必须包括主键 在执行语句的时候,MySQL是逐行加锁的(扫描一个锁一个),直至锁住所有符合条件的数据,执行完毕才释放锁。所以当业务在进行的时候,切忌使用这种方法。 在RR隔离级别下,还会加行锁和间隙锁

  • 2021-01-13 17:27:04

    Navicat配置mysql数据库用户权限

    用数据库的时候就会遇到有多个用户,分配用户权限的情况,有些用户只读,有些用户可以读写,有些用户只能操作一个或者多个数据库,如何给mysql的用户设置权限,我这里描述一下如何用navicat图形操作分配用户权限

  • 2021-01-14 06:15:04

    通过glide获取图片显示后的真正宽高

    有时候需要获取网络图片的宽高来设置图片显示的大小,很多人会直接利用Glide的加载监听去拿图片的宽高,但是这样拿到的不是图片真正的宽高,而是图片显示在ImageView后的宽高。如下:

  • 2021-01-14 09:38:57

    Chrome插件详细教程

    严格来讲,我们正在说的东西应该叫Chrome扩展(Chrome Extension),真正意义上的Chrome插件是更底层的浏览器功能扩展,可能需要对浏览器源码有一定掌握才有能力去开发。鉴于Chrome插件的叫法已经习惯,本文也全部采用这种叫法,但读者需深知本文所描述的Chrome插件实际上指的是Chrome扩展。

  • 2021-01-14 17:07:51

    chrome.contextMenus.create不出现菜单

    主要原因是,我每次刷新玩,都复制一下右键,然而并没有出现菜单,一度颓废啊,因为demo,还有其他人的文章都是这样的。 哎,后来发现是这样的,我缺少了contexts选项。其实我是成功了,我现在只要不选择文字,直接点右键,菜单已经出现了哦。