需求:
1、读取相片中的拍摄日期信息,添加水印到相片上,要求模拟胶片水印
2、自动化处理
3、方便后期调整
实现:
查询教程
用Photoshop脚本批量为照片增加拍摄时间水印批量添加时间水印灌进肚里的大老虎的博客-CSDN博客
这种胶片风格的水印照片是用什么软件拍的? – 知乎 (zhihu.com)
技术路线
Photoshop + Javascript
原理:
photoshop具有javascript脚本api,在javascript脚本中可以调用action,也可以在action中记录调用的js脚本
在js脚本中可以获取图片的信息
实现过程:
1、编辑Javascript脚本
Js脚本可以一次运行,批量处理,也可以在一个打开的文件中运行。
2、在打开的文件中运行一次脚本,或者在photoshop启动页打开脚本
调用脚本路径:
File->Scripts->Browse…>打开js文件
脚本内容:
AddDateWatermark.jsx 对打开的文件一次处理:
/*
功能:photoshop脚本从exif获取日期,自动添加日期水印。如没有exif日期信息,则从xmp信息从读取日期。
作者:leongongye, https://github.com/leongongye
参考:laozeng, https://github.com/laozeng1024,感谢!
*/
//自定义字符串,如“@上海”,使用urlencode编码
var customStr = "%40%E4%B8%8A%E6%B5%B7";
//exif中“日期时间”字段名称,urlencode编码
var photoTimeStr = "%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4";
//exif中“日期戳”字段名称,urlencode编码
var photoTimeStr2 = "%E6%97%A5%E6%9C%9F%E6%88%B3";
//"原始日期时间"
var photoTimeStr3 = "%E5%8E%9F%E5%A7%8B%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4";
var actionName = "DateWatermark_film";
var batchMode = false;
if(batchMode){
batchProcessDoc();
}else{
var docRef = app.activeDocument;
addDateTimeWatermark(docRef);
}
function batchProcessDoc(){
var inputFolder = Folder.selectDialog("选择添加日期水印图片所在文件夹:");
var outFolder = Folder.selectDialog("选择图片保存输出的文件夹:");
//判断文件夹是否存在
if (inputFolder != null && inputFolder != null) {
//获得文件夹下的所有图片
var fileList = inputFolder.getFiles();
//遍历图片
for (var i = 0; i < fileList.length; i++){
//判断图片是否正常文件,并且处于非隐藏状态
if (fileList[i] instanceof File && fileList[i].hidden == false) {
//打开遍历到的图片
var docRef = open(fileList[i]);
//设置另存路径文件名,重命名为:new_原文件名
var fileout = new File(outFolder+'/new_'+ basename(fileList[i]))
// // 旋转照片
// if(docRef.width > docRef.height){
// docRef.rotateCanvas(90);
// }
// 添加水印
addDateTimeWatermark(docRef);
//另存照片
saveDocAsCopy(docRef, fileout);
}
}
alert("添加日期水印已处理完成!")
}
}
function saveDocAsCopy(docRef, fileout) {
//定义一个变量[asCopy],用来指定图片以副本的方式保存
var asCopy = true;
//定义一个变量[extensionType],用来指定图片名称的后缀为小写的.jpg
var extensionType = Extension.LOWERCASE;
//定义一个变量[options],用来指定图片保存的格式为JPG。PNG为PNGSaveOptions
var jpegSaveOptions = JPEGSaveOptions;
jpegSaveOptions.embedColorProfile = true;
jpegSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
jpegSaveOptions.matte = MatteType.NONE;
jpegSaveOptions.quality = 12;
docRef.saveAs(fileout, jpegSaveOptions, asCopy, extensionType);
//操作完成后,直接关闭文档
if(batchMode==true){
docRef.close(SaveOptions.DONOTSAVECHANGES);
}
}
function addDateTimeWatermark(docRef) {
//获得exif照片日期,可自行加自定义文字customStr
//photoTime = getExifData(docRef) + decodeURIComponent(customStr)
photoTime = getDocCreateTime(docRef)
//如果exif没有日期数据,从文件名读取
if (photoTime == 0){
photoTime = basename(fileList[i]);
photoTime = photoTime.toString().slice(0, -4);
}
//新建图层
var layerRef = docRef.artLayers.add();
//设置为文字图层
layerRef.kind = LayerKind.TEXT;
//设置图层文字
layerRef.textItem.contents = "X " + photoTime;
// gimmePostScriptFontName("asdfasdf");
//根据图片宽度比例,设置文字大小
var docMaxSide = Math.min(docRef.width,docRef.height);
var textSize = docMaxSide/42/(docRef.resolution/72); //默认分辨率72,根据分辨率修改pt
layerRef.textItem.size = textSize;
layerRef.textItem.font = "LcdD"; //设置字体
// 设置文本对齐方式
layerRef.textItem.justification = Justification.RIGHT;
var textWidthOffset = docRef.width - textSize * 4;
var textHeightOffset = docRef.height - textSize * 3;
layerRef.textItem.position = new Array(textWidthOffset,textHeightOffset);
//定义颜色
var color = new RGBColor();
//设置red属性
color.red = 200;
//设置green属性
color.green = 58;
//设置blue属性
color.blue = 20;
//定义水印文字的颜色
var sc = new SolidColor();
//设置[sc]对象的[rgb]属性的值为变量[color]
sc.rgb = color;
//将文本图层的字体颜色设置为变量[sc]
layerRef.textItem.color = sc;
//设置文本透明度
// layerRef.fillOpacity = 74;
// 设置混合模式
// layerRef.blendMode = BlendMode.SCREEN;
// do action
app.doAction(actionName,"Scripts");
// 对亮背景做调整
if(actionName=="DateWatermark_film"){
var endPixelBrightness = getPixelColorBrightness(docRef, [textWidthOffset, textHeightOffset]);
if(endPixelBrightness > 65){
// layerRef.blendMode = BlendMode.HARDLIGHT;
layerRef.fillOpacity = 100;
}
//合并文本图层至背景图层
layerRef.merge();
}
}
function getPixelColorBrightness(docRef, position) {
// position = [new UnitValue( 100,'px'),new UnitValue(100,'px' )]
var colorSampler = docRef.colorSamplers.add(position);
var Brightness = Math.round(colorSampler.color.hsb.brightness); //Brightness value
colorSampler.remove();
return Brightness;
}
function getCreateDateFromXmp(doc) {
var ns = "http://ns.adobe.com/xap/1.0/";
ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
xmpMeta = new XMPMeta(doc.xmpMetadata.rawData);
var theValue = xmpMeta.getProperty(ns, "CreateDate");
return theValue;
}
//获取exif中的日期
function getDocCreateTime(doc) {
var exifData = doc.info.exif;
var photoTime = 0
//1. 优先从 exif 里取
for(j = 0; j < exifData.length; j++ )
{
encodeStr = encodeURIComponent(exifData[j][0]);
switch(encodeStr)
{
//urlencode 中文再判断
//日期时间
case photoTimeStr: case photoTimeStr2: case photoTimeStr3:
photoTime = exifData[j][1];
p = photoTime.split(" ")
// 2020:10:11 12:08:33 替换为2020-10-11 12:08:33
// 格式 2023-01-13 14:23:58
// photoTime = p[0].replace(/:/g,"-")+" "+p[1]
photoTime = p[0].replace(/:/g,"-")
break;
}
}
if(photoTime==0){
// 2. 最后从 xmp 数据里取,原始格式 2023-01-14T10:11:07+08:00
var xmpCreateDateStr = getCreateDateFromXmp(doc)+"";
var sp = xmpCreateDateStr.split("T");
// photoTime = sp[0]+" "+sp[1].substring(0,8);
// photoTime = sp[0].replace(/-/g,"/")
photoTime = sp[0];
/*
if(doc.info.creationDate){
// 2. 从doc的creationDate 里取,格式 20230113
// photoTime = doc.info.creationDate+"-D";
photoTime = getCreateDateFromXmp(doc)+"-X";
}else{
// 3. 最后从 xmp 数据里取,原始格式 2023-01-14T10:11:07+08:00
photoTime = getCreateDateFromXmp(doc)+"-X";
}
*/
}
// alert(photoTime);
// 调整文字格式 2023.09.23
var parts = photoTime.split("-"); // 将日期字符串拆分成年、月和日
var modifiedDate = "'" + parts[0].substring(2) + " " + parts[1] + " " + parts[2]; // 重新构建日期字符串
return modifiedDate;
}
function ShowTheObject(obj){
var des = "";
for(var name in obj){
// des += name + ";";
des += name + ":" + obj[name] + ";";
}
return des;
}
//获取文件名
function basename(str) {
str = str.toString();
var idx = str.toString().lastIndexOf('/')
idx = idx > -1 ? idx : str.lastIndexOf('\\')
if (idx < 0) {
return str
}
return str.substring(idx + 1);
}
//获取字体
function gimmePostScriptFontName(f)
{
numOfFonts = app.fonts.length;
// var s = "";
for (var i = 0, numOfFonts; i < numOfFonts; i++)
{
fnt = app.fonts[i].name;
// s += app.fonts[i].name + "***" + app.fonts[i].postScriptName + ";\n";
if (f == fnt)
{
return app.fonts[i].postScriptName;
}
}
}
AddDateWatermark_batch.jsx批量处理:
/*
功能:photoshop脚本从exif获取日期,自动添加日期水印。如没有exif日期信息,则从xmp信息从读取日期。
作者:leongongye, https://github.com/leongongye
参考:laozeng, https://github.com/laozeng1024,感谢!
*/
//自定义字符串,如“@上海”,使用urlencode编码
var customStr = "%40%E4%B8%8A%E6%B5%B7";
//exif中“日期时间”字段名称,urlencode编码
var photoTimeStr = "%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4";
//exif中“日期戳”字段名称,urlencode编码
var photoTimeStr2 = "%E6%97%A5%E6%9C%9F%E6%88%B3";
//"原始日期时间"
var photoTimeStr3 = "%E5%8E%9F%E5%A7%8B%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4";
var actionName = "DateWatermark_film";
var batchMode = true;
if(batchMode){
batchProcessDoc();
}else{
var docRef = app.activeDocument;
addDateTimeWatermark(docRef);
}
function batchProcessDoc(){
var inputFolder = Folder.selectDialog("选择添加日期水印图片所在文件夹:");
var outFolder = Folder.selectDialog("选择图片保存输出的文件夹:");
//判断文件夹是否存在
if (inputFolder != null && inputFolder != null) {
//获得文件夹下的所有图片
var fileList = inputFolder.getFiles();
//遍历图片
for (var i = 0; i < fileList.length; i++){
//判断图片是否正常文件,并且处于非隐藏状态
if (fileList[i] instanceof File && fileList[i].hidden == false) {
//打开遍历到的图片
var docRef = open(fileList[i]);
//设置另存路径文件名,重命名为:new_原文件名
var fileout = new File(outFolder+'/new_'+ basename(fileList[i]))
// // 旋转照片
// if(docRef.width > docRef.height){
// docRef.rotateCanvas(90);
// }
// 添加水印
addDateTimeWatermark(docRef);
//另存照片
saveDocAsCopy(docRef, fileout);
}
}
alert("添加日期水印已处理完成!")
}
}
function saveDocAsCopy(docRef, fileout) {
//定义一个变量[asCopy],用来指定图片以副本的方式保存
var asCopy = true;
//定义一个变量[extensionType],用来指定图片名称的后缀为小写的.jpg
var extensionType = Extension.LOWERCASE;
//定义一个变量[options],用来指定图片保存的格式为JPG。PNG为PNGSaveOptions
var jpegSaveOptions = JPEGSaveOptions;
jpegSaveOptions.embedColorProfile = true;
jpegSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
jpegSaveOptions.matte = MatteType.NONE;
jpegSaveOptions.quality = 12;
docRef.saveAs(fileout, jpegSaveOptions, asCopy, extensionType);
//操作完成后,直接关闭文档
if(batchMode==true){
docRef.close(SaveOptions.DONOTSAVECHANGES);
}
}
function addDateTimeWatermark(docRef) {
//获得exif照片日期,可自行加自定义文字customStr
//photoTime = getExifData(docRef) + decodeURIComponent(customStr)
photoTime = getDocCreateTime(docRef)
//如果exif没有日期数据,从文件名读取
if (photoTime == 0){
photoTime = basename(fileList[i]);
photoTime = photoTime.toString().slice(0, -4);
}
//新建图层
var layerRef = docRef.artLayers.add();
//设置为文字图层
layerRef.kind = LayerKind.TEXT;
//设置图层文字
layerRef.textItem.contents = "X " + photoTime;
// gimmePostScriptFontName("asdfasdf");
//根据图片宽度比例,设置文字大小
var docMaxSide = Math.min(docRef.width,docRef.height);
var textSize = docMaxSide/42/(docRef.resolution/72); //默认分辨率72,根据分辨率修改pt
layerRef.textItem.size = textSize;
layerRef.textItem.font = "LcdD"; //设置字体
// 设置文本对齐方式
layerRef.textItem.justification = Justification.RIGHT;
var textWidthOffset = docRef.width - textSize * 4;
var textHeightOffset = docRef.height - textSize * 3;
layerRef.textItem.position = new Array(textWidthOffset,textHeightOffset);
//定义颜色
var color = new RGBColor();
//设置red属性
color.red = 200;
//设置green属性
color.green = 58;
//设置blue属性
color.blue = 20;
//定义水印文字的颜色
var sc = new SolidColor();
//设置[sc]对象的[rgb]属性的值为变量[color]
sc.rgb = color;
//将文本图层的字体颜色设置为变量[sc]
layerRef.textItem.color = sc;
//设置文本透明度
// layerRef.fillOpacity = 74;
// 设置混合模式
// layerRef.blendMode = BlendMode.SCREEN;
// do action
app.doAction(actionName,"Scripts");
// 对亮背景做调整
if(actionName=="DateWatermark_film"){
var endPixelBrightness = getPixelColorBrightness(docRef, [textWidthOffset, textHeightOffset]);
if(endPixelBrightness > 65){
// layerRef.blendMode = BlendMode.HARDLIGHT;
layerRef.fillOpacity = 100;
}
//合并文本图层至背景图层
layerRef.merge();
}
}
function getPixelColorBrightness(docRef, position) {
// position = [new UnitValue( 100,'px'),new UnitValue(100,'px' )]
var colorSampler = docRef.colorSamplers.add(position);
var Brightness = Math.round(colorSampler.color.hsb.brightness); //Brightness value
colorSampler.remove();
return Brightness;
}
function getCreateDateFromXmp(doc) {
var ns = "http://ns.adobe.com/xap/1.0/";
ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
xmpMeta = new XMPMeta(doc.xmpMetadata.rawData);
var theValue = xmpMeta.getProperty(ns, "CreateDate");
return theValue;
}
//获取exif中的日期
function getDocCreateTime(doc) {
var exifData = doc.info.exif;
var photoTime = 0
//1. 优先从 exif 里取
for(j = 0; j < exifData.length; j++ )
{
encodeStr = encodeURIComponent(exifData[j][0]);
switch(encodeStr)
{
//urlencode 中文再判断
//日期时间
case photoTimeStr: case photoTimeStr2: case photoTimeStr3:
photoTime = exifData[j][1];
p = photoTime.split(" ")
// 2020:10:11 12:08:33 替换为2020-10-11 12:08:33
// 格式 2023-01-13 14:23:58
// photoTime = p[0].replace(/:/g,"-")+" "+p[1]
photoTime = p[0].replace(/:/g,"-")
break;
}
}
if(photoTime==0){
// 2. 最后从 xmp 数据里取,原始格式 2023-01-14T10:11:07+08:00
var xmpCreateDateStr = getCreateDateFromXmp(doc)+"";
var sp = xmpCreateDateStr.split("T");
// photoTime = sp[0]+" "+sp[1].substring(0,8);
// photoTime = sp[0].replace(/-/g,"/")
photoTime = sp[0];
/*
if(doc.info.creationDate){
// 2. 从doc的creationDate 里取,格式 20230113
// photoTime = doc.info.creationDate+"-D";
photoTime = getCreateDateFromXmp(doc)+"-X";
}else{
// 3. 最后从 xmp 数据里取,原始格式 2023-01-14T10:11:07+08:00
photoTime = getCreateDateFromXmp(doc)+"-X";
}
*/
}
// alert(photoTime);
// 调整文字格式 2023.09.23
var parts = photoTime.split("-"); // 将日期字符串拆分成年、月和日
var modifiedDate = "'" + parts[0].substring(2) + " " + parts[1] + " " + parts[2]; // 重新构建日期字符串
return modifiedDate;
}
function ShowTheObject(obj){
var des = "";
for(var name in obj){
// des += name + ";";
des += name + ":" + obj[name] + ";";
}
return des;
}
//获取文件名
function basename(str) {
str = str.toString();
var idx = str.toString().lastIndexOf('/')
idx = idx > -1 ? idx : str.lastIndexOf('\\')
if (idx < 0) {
return str
}
return str.substring(idx + 1);
}
//获取字体
function gimmePostScriptFontName(f)
{
numOfFonts = app.fonts.length;
// var s = "";
for (var i = 0, numOfFonts; i < numOfFonts; i++)
{
fnt = app.fonts[i].name;
// s += app.fonts[i].name + "***" + app.fonts[i].postScriptName + ";\n";
if (f == fnt)
{
return app.fonts[i].postScriptName;
}
}
}
在脚本中调用了Action app.doAction("DateWatermark","Scripts");
,在Action set Scripts
下有action “DateWatermark”, 因此如要正常运行脚本,需要设置提前action,后期对水印样式修改也可以通过action修改进行。若要完全由脚本控制处理过程,则需要其他工具。
拓展:
Xtools,用于将action文件转为js脚本
Photoshop Scripting Listener,用于将操作过程记录为scripts log文件
Photoshop流程
- 字体:LcdD
- 图层效果:outer glow + drop shadow
- 混合模式:normal
- 透明度:74%
效果: