首选为PS
操作步骤:
第一步,快捷键Ctrl+Shift+N新建空白图层,输入要添加的水印文本,比如“部落窝教育”,然后按CTRL+T,将水印文本旋转角度,旋转完毕角度之后,按下回车键确认变换。效果如下:
第二步,隐藏水印文字下面的背景图层,使其不显示出来。注意观察图层面板。
第三步,使用矩形选区工具,绘制一个矩形选区,将文字包含在选区里面。
第四步,找到编辑菜单——定义图案,确定。
第五步,按下CTRL+D,取消选区。然后将“背景”图层显示出来。
第六步,按DEL键,将上面的水印文字图层删除。此时,就只有背景图层了。
第七步,单击图层面板的“创建新图层”,创建一个新图层,为:图层1。
第八步,按下SHIFT+F5键,打开“填充”对话框。选择我们前面操作步骤定义的水印文字。截图如下:
python也能够装逼
from PIL import Image, ImageDraw, ImageFont def add_text_to_image(image, text): font = ImageFont.truetype (\'C:\Windows\Fonts\STXINGKA.TTF\', 36) # 添加背景 new_img = Image.new (\'RGBA\', (image.size[0] * 3, image.size[1] * 3), (0, 0, 0, 0)) new_img.paste (image, image.size) # 添加水印 font_len = len (text) rgba_image = new_img.convert (\'RGBA\') text_overlay = Image.new (\'RGBA\', rgba_image.size, (255, 255, 255, 0)) image_draw = ImageDraw.Draw (text_overlay) for i in range (0, rgba_image.size[0], font_len * 40 + 100): for j in range (0, rgba_image.size[1], 200): image_draw.text ((i, j), text, font=font, fill=(0, 0, 0, 50)) text_overlay = text_overlay.rotate (-45) image_with_text = Image.alpha_composite (rgba_image, text_overlay) # 裁切图片 image_with_text = image_with_text.crop ((image.size[0], image.size[1], image.size[0] * 2, image.size[1] * 2)) return image_with_text if __name__ == \'__main__\': img = Image.open ("scr.jpg") im_after = add_text_to_image (img, u\'测试使用\') im_after.save (u\'测试使用.png\')
效果图
HTML页面添加水印
网页水印生成解决方案
通过canvas生成水印
这里我们用canvas来生成base64图片,通过CanIUse网站查询兼容性,如果在移动端以及一些管理系统使用,兼容性问题可以完全忽略。
HTMLCanvasElement.toDataURL 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。
如果画布的高度或宽度是0,那么会返回字符串“data:,”。 如果传入的类型非“image/png”,但是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。 Chrome支持“image/webp”类型。具体参考HTMLCanvasElement.toDataURL
具体代码实现如下:
(function () { // canvas 实现 watermark function __canvasWM({ // 使用 ES6 的函数默认值方式设置参数的默认取值 // 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters container = document.body, width = \'200px\', height = \'150px\', textAlign = \'center\', textBaseline = \'middle\', font = "20px microsoft yahei", fillStyle = \'rgba(184, 184, 184, 0.8)\', content = \'请勿外传\', rotate = \'30\', zIndex = 1000 } = {}) { var args = arguments[0]; var canvas = document.createElement(\'canvas\'); canvas.setAttribute(\'width\', width); canvas.setAttribute(\'height\', height); var ctx = canvas.getContext("2d"); ctx.textAlign = textAlign; ctx.textBaseline = textBaseline; ctx.font = font; ctx.fillStyle = fillStyle; ctx.rotate(Math.PI / 180 * rotate); ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2); var base64Url = canvas.toDataURL(); const watermarkDiv = document.createElement("div"); watermarkDiv.setAttribute(\'style\', ` position:absolute; top:0; left:0; width:100%; height:100%; z-index:${zIndex}; pointer-events:none; background-repeat:repeat; background-image:url(\'${base64Url}\')`); container.style.position = \'relative\'; container.insertBefore(watermarkDiv, container.firstChild); }); window.__canvasWM = __canvasWM; })(); // 调用 __canvasWM({ content: \'QQMusicFE\' })
效果如下:
为了使这个方法更通用,兼容不同的引用方式,我们还可以加上这段代码:
// 为了兼容不同的环境 if (typeof module != \'undefined\' && module.exports) { //CMD module.exports = __canvasWM; } else if (typeof define == \'function\' && define.amd) { // AMD define(function () { return __canvasWM; }); } else { window.__canvasWM = __canvasWM; }
这样似乎能满足我们的需求了,但是还有一个问题,稍微懂一点浏览器的使用或者网页知识的用户,可以用浏览器的开发者工具来动态更改DOM的属性或者结构就可以去掉了。这个时候有两个解决办法:
- 监测水印div的变化,记录刚生成的div的innerHTML,每隔几秒就取一次新的值,一旦发生变化,则重新生成水印。但是这种方式可能影响性能;
- 使用MutationObserver
MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力。
通过兼容性表可以看出高级浏览器以及移动浏览器支持非常不错。 Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。 使用MutationObserver构造函数,新建一个观察器实例,实例的有一个回调函数,该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例。MutationObserver 的实例的observe方法用来启动监听,它接受两个参数。 第一个参数:所要观察的 DOM 节点,第二个参数:一个配置对象,指定所要观察的特定变动,有以下几种:
|
属性 |
描述 |
|---|---|
|
childList |
如果需要观察目标节点的子节点(新增了某个子节点,或者移除了某个子节点),则设置为true. |
|
attributes |
如果需要观察目标节点的属性节点(新增或删除了某个属性,以及某个属性的属性值发生了变化),则设置为true. |
|
characterData |
如果目标节点为characterData节点(一种抽象接口,具体可以为文本节点,注释节点,以及处理指令节点)时,也要观察该节点的文本内容是否发生变化,则设置为true. |
|
subtree |
除了目标节点,如果还需要观察目标节点的所有后代节点(观察目标节点所包含的整棵DOM树上的上述三种节点变化),则设置为true. |
|
attributeOldValue |
在attributes属性已经设为true的前提下,如果需要将发生变化的属性节点之前的属性值记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true. |
|
characterDataOldValue |
在characterData属性已经设为true的前提下,如果需要将发生变化的characterData节点之前的文本内容记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true. |
|
attributeFilter |
一个属性名数组(不需要指定命名空间),只有该数组中包含的属性名发生变化时才会被观察到,其他名称的属性发生变化后会被忽略. |
MutationObserver只能监测到诸如属性改变、增删子结点等,对于自己本身被删除,是没有办法的可以通过监测父结点来达到要求。因此最终改造之后代码为:
(function () { // canvas 实现 watermark function __canvasWM({ // 使用 ES6 的函数默认值方式设置参数的默认取值 // 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters container = document.body, width = \'300px\', height = \'200px\', textAlign = \'center\', textBaseline = \'middle\', font = "20px Microsoft Yahei", fillStyle = \'rgba(184, 184, 184, 0.6)\', content = \'请勿外传\', rotate = \'30\', zIndex = 1000 } = {}) { const args = arguments[0]; const canvas = document.createElement(\'canvas\'); canvas.setAttribute(\'width\', width); canvas.setAttribute(\'height\', height); const ctx = canvas.getContext("2d"); ctx.textAlign = textAlign; ctx.textBaseline = textBaseline; ctx.font = font; ctx.fillStyle = fillStyle; ctx.rotate(Math.PI / 180 * rotate); ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2); const base64Url = canvas.toDataURL(); const __wm = document.querySelector(\'.__wm\'); const watermarkDiv = __wm || document.createElement("div"); const styleStr = ` position:absolute; top:0; left:0; width:100%; height:100%; z-index:${zIndex}; pointer-events:none; background-repeat:repeat; background-image:url(\'${base64Url}\')`; watermarkDiv.setAttribute(\'style\', styleStr); watermarkDiv.classList.add(\'__wm\'); if (!__wm) { container.style.position = \'relative\'; container.insertBefore(watermarkDiv, container.firstChild); } const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; if (MutationObserver) { let mo = new MutationObserver(function () { const __wm = document.querySelector(\'.__wm\'); // 只在__wm元素变动才重新调用 __canvasWM if ((__wm && __wm.getAttribute(\'style\') !== styleStr) || !__wm) { // 避免一直触发 mo.disconnect(); mo = null; __canvasWM(JSON.parse(JSON.stringify(args))); } }); mo.observe(container, { attributes: true, subtree: true, childList: true }) } } if (typeof module != \'undefined\' && module.exports) { //CMD module.exports = __canvasWM; } else if (typeof define == \'function\' && define.amd) { // AMD define(function () { return __canvasWM; }); } else { window.__canvasWM = __canvasWM; } })(); // 调用 __canvasWM({ content: \'QQMusicFE\' });
通过SVG生成水印
SVG:可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。 SVG由W3C制定,是一个开放标准。 -- 维基百科
相比Canvas,SVG有更好的浏览器兼容性,使用SVG生成水印的方式与Canvas的方式类似,只是base64Url的生成方式换成了SVG。具体如下:
(function () { // svg 实现 watermark function __svgWM({ container = document.body, content = \'请勿外传\', width = \'300px\', height = \'200px\', opacity = \'0.2\', fontSize = \'20px\', zIndex = 1000 } = {}) { const args = arguments[0]; const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${width}"> <text x="50%" y="50%" dy="12px" text-anchor="middle" stroke="#000000" stroke-width="1" stroke-opacity="${opacity}" fill="none" transform="rotate(-45, 120 120)" style="font-size: ${fontSize};"> ${content} </text></svg>`; const base64Url = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`; const __wm = document.querySelector(\'.__wm\'); const watermarkDiv = __wm || document.createElement("div"); // ... // 与 canvas 的一致 // ... })(); __svgWM({ content: \'QQMusicFE\' })
身为现代前端开发者,Node.JS也是需要掌握的。我们同样可以通过NodeJS来生成网页水印(出于性能考虑更好的方式是利用用户客户端来生成)。前端发一个请求,参数带上水印内容,后台返回图片内容。 具体实现(Koa2环境):
- 安装gm以及相关环境,详情看gm文档
-
ctx.type=\'image/png\';设置响应为图片类型 - 生成图片过程是异步的,所以需要包装一层Promise,这样才能为通过 async/await 方式为 ctx.body 赋值
const fs = require (\'fs\' )const gm = require (\'gm\' ); const imageMagick = gm 。子类({ imageMagick : true } ); const router = require (\'koa-router\' )(); 路由器。get (\'/ wm\' , 异步 ( ctx , next ) => { const { text } = ctx 。查询; ctx 。类型= \'image / png\' ; ctx 。状态= 200 ; ctx 。体= AWAIT ((() => { 返回 新 无极((决心,拒绝) => { ImageMagick的(200 , 100 , “RGBA(255,255,255,0)” ) 。fontSize (40 ) 。的drawText (10 , 50 ,文本) 。写(要求(\'路径\' )。加入( __dirname , `./ $ {文本} .png`), 功能 ( ERR ) { 如果 ( ERR ) { 拒绝( ERR ); } 其他 { 决心( FS 。readFileSync (要求(\'路径\' )。加入( __dirname , `./ $ {文本} .png`))) } } ); } ) } )()); } );
如果只是简单的水印展示,建议在浏览器生成,性能更好
图片水印生成解决方案
除了给网页加上水印之外,有时候我们需要给图片也加上水印,这样在用户保存图片后,带上了水印来源信息,既可以保护版权,水印的其他信息也可以防止泄密。
通过canvas给图片加水印
实现如下:
(function() { function __picWM({ url = \'\', textAlign = \'center\', textBaseline = \'middle\', font = "20px Microsoft Yahei", fillStyle = \'rgba(184, 184, 184, 0.8)\', content = \'请勿外传\', cb = null, textX = 100, textY = 30 } = {}) { const img = new Image(); img.src = url; img.crossOrigin = \'anonymous\'; img.onload = function() { const canvas = document.createElement(\'canvas\'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext(\'2d\'); ctx.drawImage(img, 0, 0); ctx.textAlign = textAlign; ctx.textBaseline = textBaseline; ctx.font = font; ctx.fillStyle = fillStyle; ctx.fillText(content, img.width - textX, img.height - textY); const base64Url = canvas.toDataURL(); cb && cb(base64Url); } } if (typeof module != \'undefined\' && module.exports) { //CMD module.exports = __picWM; } else if (typeof define == \'function\' && define.amd) { // AMD define(function () { return __picWM; }); } else { window.__picWM = __picWM; } })(); // 调用 __picWM({ url: \'http://localhost:3000/imgs/google.png\', content: \'QQMusicFE\', cb: (base64Url) => { document.querySelector(\'img\').src = base64Url }, });
效果如下:
通过NodeJS批量为图片加水印
我们同样可以通过gm这个库来给图片加上水印
function picWM(path, text) { imageMagick(path) .drawText(10, 50, text) .write(require(\'path\').join(__dirname, `./${text}.png`), function (err) { if (err) { console.log(err); } });}
如果需要批处理图片,只需要遍历相关文件即可
1 <style type="text/css" media="screen"> 2 .cover { 3 position:absolute; 4 left:0; 5 top:0; 6 z-index:999999999999999; 7 margin-right:0px; 8 margin-left:0px; 9 margin-top:5px; 10 margin-bottom:140px; 11 color:#fff; 12 color:#ccc\0; 13 display:block; 14 padding:2px 1px; 15 font-family:\'宋体\'; 16 font-size:16px; 17 font-weight:bold; 18 white-space:nowrap; 19 text-shadow: 1px 0 0 #eee; 20 transform:rotate(45deg); 21 -ms-transform:rotate(45deg); 22 -moz-transform:rotate(45deg); 23 -webkit-transform:rotate(45deg); 24 -o-transform:rotate(45deg); 25 -moz-opacity:0.3; opacity:0.3; 26 -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.7071067811865474, M12=-0.7071067811865477, M21=0.7071067811865477, M22=0.7071067811865474, SizingMethod=\'auto expand\')"; 27 } 28 .cover-Blink-area{ position:absolute;text-align: center;left:0;top:0;width:100%;height:100%;display:block;z-index:999999999999999; pointer-events: none;overflow: hidden;} 29 .cover-Blink{ 30 display:inline-block; 31 margin-right:50px; 32 margin-left:50px; 33 margin-top:140px; 34 margin-bottom:140px; 35 color:red; 36 padding:2px 1px; 37 font-family:\'宋体\'; 38 font-size:16px; 39 font-weight:bold; 40 color: pink; 41 white-space:nowrap; 42 text-shadow: 1px 0 0 rgba(0,0,0,.2); 43 transform:rotate(45deg); 44 -ms-transform:rotate(45deg); 45 -moz-transform:rotate(45deg); 46 -webkit-transform:rotate(45deg); 47 -o-transform:rotate(45deg); 48 49 } 50 </style> 51 52 <script type="text/javascript"> 53 $(function(){ 54 waterMark$(); 55 }); 56 function waterMark$(){ 57 if(navigator.appName == "Microsoft Internet Explorer"&& navigator.appVersion.match(/11./i)!="11."){ 58 $("p[name=\'p1$\']").remove(); 59 var winwidth$ = document.body.scrollWidth-17; 60 var winheight$ = document.body.scrollHeight; 61 $("body").append("<p id=\'waterSum_11\' name=\'p1$\' class=\'cover_through cover js-click-to-alert\'>测试张璐</p>"); 62 var fleft = Number($(\'#waterSum_11\').css("margin-left").substring(0,$(\'#waterSum_11\').css("margin-left").indexOf(\'p\'))); 63 var ftop = Number($(\'#waterSum_11\').css("margin-top").substring(0,$(\'#waterSum_11\').css("margin-top").indexOf(\'p\'))); 64 var perWidth = $("#waterSum_11").width(); 65 var perHeight = Number(\'140px\'.substring(0,\'140px\'.indexOf(\'p\')))+100; 66 var lines = parseInt(winwidth$/(perWidth+fleft)); 67 var rows = Math.round(winheight$/(perHeight+ftop)); 68 var totalPWidth = perWidth*lines; 69 var totalSpace = winwidth$-totalPWidth; 70 var perSpace = parseInt(totalSpace/(lines+1)); 71 $(\'#waterSum_11\').css("margin-left",perSpace); 72 for(var i=1;i<=rows;i++) { 73 for(var j=1;j<=lines;j++){ 74 if(i==1){ 75 if(j<=lines-1){ 76 var p = "<p id=\'waterSum_"+i+""+(j+1)+"\' name=\'p1$\' class=\'cover_through cover js-click-to-alert\'>SK测试</p>"; 77 var ileft = $(\'#waterSum_\'+i+\'\'+j).css("margin-left").substring(0,$(\'#waterSum_\'+i+\'\'+j).css("margin-left").indexOf(\'p\')); 78 var itop = $(\'#waterSum_11\').css("margin-top").substring(0,$(\'#waterSum_11\').css("margin-top").indexOf(\'p\')); 79 $("body").append(p); 80 $(\'#waterSum_\'+i+\'\'+(j+1)).css("margin-left",Number(ileft)+Number(perWidth)+perSpace); 81 $(\'#waterSum_\'+i+\'\'+(j+1)).css(\'margin-top\',itop); 82 } 83 }else{ 84 var p = "<p id=\'waterSum_"+i+""+j+"\' name=\'p1$\' class=\'cover_through cover js-click-to-alert\'>${TmpContent}</p>"; 85 var ileft = $(\'#waterSum_\'+(i-1)+\'\'+j).css("margin-left").substring(0,$(\'#waterSum_\'+(i-1)+\'\'+j).css("margin-left").indexOf(\'p\')); 86 var itop = $(\'#waterSum_\'+(i-1)+\'\'+j).css("margin-top").substring(0,$(\'#waterSum_\'+(i-1)+\'\'+j).css("margin-top").indexOf(\'p\')); 87 $("body").append(p); 88 $(\'#waterSum_\'+i+\'\'+j).css("margin-left",Number(ileft)); 89 $(\'#waterSum_\'+i+\'\'+j).css(\'margin-top\',Number(itop)+Number(perHeight)); 90 } 91 } 92 } 93 passThrough(); 94 }else{ 95 waterMarkNotIe$(); 96 } 97 } 98 function waterMarkNotIe$(){ 99 var winwidth$ = document.body.clientWidth; 100 var winheight$ = document.body.scrollHeight; 101 var waterSum$ = 100; 102 var oldleft$=0; 103 var maxI$=0; 104 var k$=0; 105 $("body").append("<div class=\'cover-Blink-area\'> </div>"); 106 $(\'.cover-Blink-area\').css(\'height\',\'500px\'); 107 for( var i=1;i<=waterSum$;i++) { 108 $(".cover-Blink-area").append("<p id=\'waterSum_" +i+"\' class=\'cover_through cover-Blink js-click-to-alert\'>SK测试</p>"); 109 var left = Number(document.getElementById("waterSum_" +i).offsetLeft); 110 if(left>oldleft$) { 111 oldleft$ = left; 112 maxI$ = i; 113 } 114 if (left<oldleft$&&k$==0){ 115 var top = $("#waterSum_1").css("margin-top").substring(0,$("#waterSum_1").css("margin-top").indexOf(\'p\')); 116 var bottom = $("#waterSum_1").css("margin-bottom").substring(0,$("#waterSum_1").css("margin-bottom").indexOf(\'p\')); 117 var pHeight = $("#waterSum_1").height(); 118 var totalHeight = Number(top)+Number(pHeight)+Number(bottom); 119 var Hnum = Math.round(500/(totalHeight/1.3)); 120 waterSum$ = Hnum*maxI$; 121 k$++; 122 } 123 } 124 } 125 window.onresize = function(){ 126 waterMark$(); 127 } 128 function passThrough() { 129 $(".cover").mouseenter(function(){ 130 $(this).stop(true).fadeOut().delay(1500).fadeIn(50); 131 }); 132 } 133 </script>
图片添加水印:http://www.16xx8.com/photoshop/jiaocheng/2019/148476.html
HTML页面添加水印:https://blog.csdn.net/zhanglu1236789/article/details/79105442
前端水印生成方案:https://cloud.tencent.com/developer/article/1158636