给图片添加水印
首选为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