loonghao / photoshop-python-api

Python API for Photoshop.
https://loonghao.github.io/photoshop-python-api/
MIT License
632 stars 71 forks source link

请教关于复制图层或组到另一个psd #164

Open Upfunny opened 2 years ago

Upfunny commented 2 years ago

Hi~ 发现你base 深圳 很高兴可以使用中文沟通 有个问题想请教一下~ 我几个psd 想从这几个psd中,分别复制 图层 或者 组 到一个新的psd内 想请教一下 怎么跨psd复制图层或组

loonghao commented 2 years ago

@Upfunny 这个之前没尝试过,我到时候帮你测试一下

Upfunny commented 2 years ago

@Upfunny 这个之前没尝试过,我到时候帮你测试一下

感谢~! 另外想到一个曲线救国的方式 把psd打开 需要的保留 不需要的全删 存储为psd 再新建一个psd 将这个删减后的psd导入 但会有一个问题 psd导入后,是smart object,如何转换为图层?

TsXor commented 2 years ago

单凭ps api可能有点想不到,但是我们可以借助另一个模块:psd-tools 这是psd-tools的Getting started使用例:(将图层保存为图片)

from psd_tools import PSDImage

psd = PSDImage.open('example.psd')
psd.composite().save('example.png')

for layer in psd:
    print(layer)
    layer_image = layer.composite()
    layer_image.save('%s.png' % layer.name)

然后,这是ps api的Import Image As Layer(将图片导入为图层)用例:

"""Import a image as a artLayer."""

# Import local modules
from photoshop import Session

with Session(action="new_document") as ps:
    desc = ps.ActionDescriptor
    desc.putPath(ps.app.charIDToTypeID("null"), "your/image/path")
    event_id = ps.app.charIDToTypeID("Plc ")  # `Plc` need one space in here.
    ps.app.executeAction(ps.app.charIDToTypeID("Plc "), desc)

我们只要拼接上面两段代码即可:将要复制的psd存为临时文件(用tempfiles模块),从这个psd中读出图层并存为临时文件,在ps中切换到目标psd,然后将前述临时图片导入。

TsXor commented 2 years ago

顺便捉个虫:https://loonghao.github.io/photoshop-python-api/examples/#operation-layer-set 标题应为Operate Layer Set 第一行应为"""An example to show you how to operate layerSet."""

Upfunny commented 2 years ago

单凭ps api可能有点想不到,但是我们可以借助另一个模块:psd-tools 这是psd-tools的Getting started使用例:(将图层保存为图片)

from psd_tools import PSDImage

psd = PSDImage.open('example.psd')
psd.composite().save('example.png')

for layer in psd:
    print(layer)
    layer_image = layer.composite()
    layer_image.save('%s.png' % layer.name)

然后,这是ps api的Import Image As Layer(将图片导入为图层)用例:

"""Import a image as a artLayer."""

# Import local modules
from photoshop import Session

with Session(action="new_document") as ps:
    desc = ps.ActionDescriptor
    desc.putPath(ps.app.charIDToTypeID("null"), "your/image/path")
    event_id = ps.app.charIDToTypeID("Plc ")  # `Plc` need one space in here.
    ps.app.executeAction(ps.app.charIDToTypeID("Plc "), desc)

我们只要拼接上面两段代码即可:将要复制的psd存为临时文件(用tempfiles模块),从这个psd中读出图层并存为临时文件,在ps中切换到目标psd,然后将前述临时图片导入。

实际上不使用psd-tools 使用photoshop-python-api 也是可以实现的 筛选下图层名 不需要的隐藏掉 输出png就可以 但是我的需求中还需要对合成的psd进行二次编辑,所以直接保存为png肯定是不行的,目前是保存为psd,再在新的psd中导入之前导出的元素psd,缺点是导入后为智能对象,需要点击一下右键转为图层。目前在考虑是否结合PS里的动作进行处理

TsXor commented 2 years ago

单凭ps api可能有点想不到,但是我们可以借助另一个模块:psd-tools 这是psd-tools的Getting started使用例:(将图层保存为图片)

from psd_tools import PSDImage

psd = PSDImage.open('example.psd')
psd.composite().save('example.png')

for layer in psd:
    print(layer)
    layer_image = layer.composite()
    layer_image.save('%s.png' % layer.name)

然后,这是ps api的Import Image As Layer(将图片导入为图层)用例:

"""Import a image as a artLayer."""

# Import local modules
from photoshop import Session

with Session(action="new_document") as ps:
    desc = ps.ActionDescriptor
    desc.putPath(ps.app.charIDToTypeID("null"), "your/image/path")
    event_id = ps.app.charIDToTypeID("Plc ")  # `Plc` need one space in here.
    ps.app.executeAction(ps.app.charIDToTypeID("Plc "), desc)

我们只要拼接上面两段代码即可:将要复制的psd存为临时文件(用tempfiles模块),从这个psd中读出图层并存为临时文件,在ps中切换到目标psd,然后将前述临时图片导入。

实际上不使用psd-tools 使用photoshop-python-api 也是可以实现的 筛选下图层名 不需要的隐藏掉 输出png就可以 但是我的需求中还需要对合成的psd进行二次编辑,所以直接保存为png肯定是不行的,目前是保存为psd,再在新的psd中导入之前导出的元素psd,缺点是导入后为智能对象,需要点击一下右键转为图层。目前在考虑是否结合PS里的动作进行处理

请您仔细读,psd-tools可以分别存每一个图层。把一个psd中的一个图层存为png再导入另一个psd,和直接复制一个psd中的一个图层到另一个psd,这两个操作是等效的。

for layer in psd:
    print(layer)
    layer_image = layer.composite()
    layer_image.save('%s.png' % layer.name)

psd-tools示例中的这段代码已经明显的不能再明显了

补充一下,对于一个文字图层,直接读取它的所有属性然后在另一个psd中创建一个一样的即可。

Upfunny commented 2 years ago

单凭ps api可能有点想不到,但是我们可以借助另一个模块:psd-tools 这是psd-tools的Getting started使用例:(将图层保存为图片)

from psd_tools import PSDImage

psd = PSDImage.open('example.psd')
psd.composite().save('example.png')

for layer in psd:
    print(layer)
    layer_image = layer.composite()
    layer_image.save('%s.png' % layer.name)

然后,这是ps api的Import Image As Layer(将图片导入为图层)用例:

"""Import a image as a artLayer."""

# Import local modules
from photoshop import Session

with Session(action="new_document") as ps:
    desc = ps.ActionDescriptor
    desc.putPath(ps.app.charIDToTypeID("null"), "your/image/path")
    event_id = ps.app.charIDToTypeID("Plc ")  # `Plc` need one space in here.
    ps.app.executeAction(ps.app.charIDToTypeID("Plc "), desc)

我们只要拼接上面两段代码即可:将要复制的psd存为临时文件(用tempfiles模块),从这个psd中读出图层并存为临时文件,在ps中切换到目标psd,然后将前述临时图片导入。

实际上不使用psd-tools 使用photoshop-python-api 也是可以实现的 筛选下图层名 不需要的隐藏掉 输出png就可以 但是我的需求中还需要对合成的psd进行二次编辑,所以直接保存为png肯定是不行的,目前是保存为psd,再在新的psd中导入之前导出的元素psd,缺点是导入后为智能对象,需要点击一下右键转为图层。目前在考虑是否结合PS里的动作进行处理

请您仔细读,psd-tools可以分别存每一个图层。把一个psd中的一个图层存为png再导入另一个psd,和直接复制一个psd中的一个图层到另一个psd,这两个操作是等效的。

图层类型很多 文字图层 形状图层 智能对象等等,每个图层也可能包含各种效果,单纯的导入PNG和复制图层可不是等效

TsXor commented 2 years ago

图层类型很多 文字图层 形状图层 智能对象等等,每个图层也可能包含各种效果,单纯的导入PNG和复制图层可不是等效

兵来将挡,水来土掩呗。ps api是可以读取图层属性的,像文字图层,形状图层这样的图层只要读取所有信息并在目标psd中创建一个一样的即可。

Upfunny commented 2 years ago

那我请教一下 望不吝赐教 一个文字图层 多行文字 部分文字字号不同,颜色不同,可能加粗倾斜下划线 如何使用api读取信息并新建一个一模一样的

TsXor commented 2 years ago

你第一条回复的“目前在考虑是否结合PS里的动作进行处理”其实是个不错的思路。 我试了一下,录制了一个全选并复制的动作和一个粘贴的动作,尝试用这两个动作复制图层,可惜的是,复制的文字图层已经被自动转换成了图片,无法编辑。

TsXor commented 2 years ago

那我请教一下 望不吝赐教 一个文字图层 多行文字 部分文字字号不同,颜色不同,可能加粗倾斜下划线 如何使用api读取信息并新建一个一模一样的

读文档:https://loonghao.github.io/photoshop-python-api/reference/photoshop/api/text_item/ 我其实不太明白为啥作者不写一份中文文档,明明他写英语都有语法错误(见上方捉虫)

Upfunny commented 2 years ago

那我请教一下 望不吝赐教 一个文字图层 多行文字 部分文字字号不同,颜色不同,可能加粗倾斜下划线 如何使用api读取信息并新建一个一模一样的

读文档:https://loonghao.github.io/photoshop-python-api/reference/photoshop/api/text_item/ 我其实不太明白为啥作者不写一份中文文档,明明他写英语都有语法错误(见上方捉虫)

英文更普适 语法无所谓 看的大差不差就行了 不必吹毛求疵 PS的api 有一些信息是获取不到的比如修改我说的这种复杂一些的文字图层,我是没有找到方法能读出更细的信息 基于图层的调整字号 调整文本是没问题 但调整第5-10个字的字号 或者文本 这种就不行了

TsXor commented 2 years ago

那我请教一下 望不吝赐教 一个文字图层 多行文字 部分文字字号不同,颜色不同,可能加粗倾斜下划线 如何使用api读取信息并新建一个一模一样的

读文档:https://loonghao.github.io/photoshop-python-api/reference/photoshop/api/text_item/ 我其实不太明白为啥作者不写一份中文文档,明明他写英语都有语法错误(见上方捉虫)

英文更普适 语法无所谓 看的大差不差就行了 不必吹毛求疵 PS的api 有一些信息是获取不到的比如修改我说的这种复杂一些的文字图层,我是没有找到方法能读出更细的信息 基于图层的调整字号 调整文本是没问题 但调整第5-10个字的字号 或者文本 这种就不行了

这点你说的挺对,ps api确实没这种功能。 但是我发现ps的动作可以实现这种精确度的操作。 因此我有了这种思路:我可以先生成一个单一字体的文本,然后生成一个临时动作,再执行这个动作即可。 我试试能不能实现

loonghao commented 2 years ago

@Upfunny 这个之前没尝试过,我到时候帮你测试一下

感谢~! 另外想到一个曲线救国的方式 把psd打开 需要的保留 不需要的全删 存储为psd 再新建一个psd 将这个删减后的psd导入 但会有一个问题 psd导入后,是smart object,如何转换为图层?

下面代码会把当前活动层的智能对象转换成普通层

# example 1
with Session() as ps:
    js = """
var idplacedLayerConvertToLayers = stringIDToTypeID( "placedLayerConvertToLayers" );
executeAction( idplacedLayerConvertToLayers, undefined, DialogModes.NO );    
"""
    ps.app.doJavaScript(js)

# example 2
with Session() as ps:
    descriptor = ps.ActionDescriptor
    idplacedLayerConvertToLayers = ps.app.stringIDToTypeID("placedLayerConvertToLayers")
    ps.app.executeAction(idplacedLayerConvertToLayers, descriptor)
TsXor commented 2 years ago

结合动作做了一下解读,缺很多属性,有时间(指下周)再补

import photoshop.api as ps
from psaction import *

psapp = ps.Application()

xyt = psActionDescriptor(psapp, [ \ #作用未知
  ('putDouble', (psstr('xx'), 1.000000)), \
  ('putDouble', (psstr('xy'), 0.000000)), \
  ('putDouble', (psstr('yx'), 0.000000)), \
  ('putDouble', (psstr('yy'), 1.000000)), \
  ('putDouble', (psstr('tx'), 0.000000)), \
  ('putDouble', (psstr('ty'), 0.000000)), \
])
basexy = psActionDescriptor(psapp, [ \ #基底的xy坐标
  ('putDouble', (pschr('Hrzn'), 0.000000)), \
  ('putDouble', (pschr('Vrtc'), 0.000000)), \
])
blockstyle1 = psActionDescriptor(psapp, [ \
  ('putBoolean', (psstr('styleSheetHasParent'), True)), \
  ('putString', (psstr('fontPostScriptName'), """STKaiti""")), \ #字体PostScript名称
  ('putString', (pschr('FntN'), """STKaiti""")), \ #字体名称
  ('putString', (pschr('FntS'), """Regular""")), \ #字体样式
  ('putInteger', (pschr('Scrp'), 25)), \ #脚本,大部分字体的尺寸
  ('putInteger', (pschr('FntT'), 1)), \ #字体技术
  ('putUnitDouble', (pschr('Sz'), pschr('#Pnt'), 25.000000)), \ #本区域的字体尺寸
  ('putBoolean', (psstr('syntheticBold'), True)), \ #仿粗体
  ('putBoolean', (psstr('autoLeading'), False)), \ #自动行距
  ('putUnitDouble', (pschr('Ldng'), pschr('#Pnt'), 32.000000)), \ #行距
])
blockparastyle1 = psActionDescriptor(psapp, [ \
  ('putBoolean', (psstr('styleSheetHasParent'), True)), \
])

textShape = psActionDescriptor(psapp, [ \
  ('putEnumerated', (pschr('TEXT'), pschr('TEXT'), pschr('Pnt'))), \ #文本形状类型:点
  ('putEnumerated', (pschr('Ornt'), pschr('Ornt'), pschr('Hrzn'))), \ #文本方向:水平
  ('putObject', (pschr('Trnf'), pschr('Trnf'), xyt)), \ #调用上方xyt
  ('putInteger', (psstr('rowCount'), 1)), \ #列数
  ('putInteger', (psstr('columnCount'), 1)), \ #行数
  ('putBoolean', (psstr('rowMajorOrder'), True)), \ #是否带“使用行主顺序”
  ('putUnitDouble', (psstr('rowGutter'), pschr('#Pnt'), 0.000000)), \ #行装订线点数
  ('putUnitDouble', (psstr('columnGutter'), pschr('#Pnt'), 0.000000 )), \ #列装订线点数
  ('putUnitDouble', (pschr('Spcn'), pschr('#Pnt'), 0.000000 )), \ #列间距
  ('putEnumerated', (psstr('frameBaselineAlignment'), psstr('frameBaselineAlignment'), psstr('alignByAscent'))), \ #首行对齐:字母上缘
  ('putUnitDouble', (psstr('firstBaselineMinimum'), pschr('#Pnt'), 0.000000)), \ #首行最小高度点数
  ('putObject', (psstr('base'), pschr('Pnt'), basexy)), \ #关联基底配置
])
blockstyle1_range = psActionDescriptor(psapp, [ \
  ('putInteger', (pschr('From'), 0)), \ #从0起
  ('putInteger', (pschr('T'), 48)), \ #到48
  ('putObject', (pschr('TxtS'), pschr('TxtS'), blockstyle1)), \
])
blockparastyle1_range = psActionDescriptor(psapp, [ \
  ('putInteger', (pschr('From'), 0)), \
  ('putInteger', (pschr('T'), 48)), \
  ('putObject', (psstr('paragraphStyle'), psstr('paragraphStyle'), blockparastyle1)), \
])

warp = psActionDescriptor(psapp, [ \
  ('putEnumerated', (psstr('warpStyle'), psstr('warpStyle'), psstr('warpNone'))), \ #变形样式:无
  ('putDouble', (psstr('warpValue'), 0.000000)), \ #弯曲度
  ('putDouble', (psstr('warpPerspective'), 0.000000)), \ #垂直扭曲度
  ('putDouble', (psstr('warpPerspectiveOther'), 0.000000)), \ #水平扭曲度
  ('putEnumerated', (psstr('warpRotate'), pschr('Ornt'), pschr('Hrzn'))), \ #轴:水平
])
textShape_list = psActionList(psapp, [ \
  ('putObject', (psstr('textShape'), textShape)), \ #关联文本形状配置
])
blockstyle_list = psActionList(psapp, [ \
  ('putObject', (pschr('Txtt'), blockstyle1_range)), \ #关联样式范围配置
])
blockparastyle_list = psActionList(psapp, [ \
  ('putObject', (psstr('paragraphStyleRange'), blockparastyle1_range)), \ #关联段落样式范围配置
])
kerningrange_list = psActionList(psapp) #关联字距微调范围配置,遗憾的是本例无此配置

imgproperty = psActionDescriptor(psapp, [ \
  ('putString', (pschr('Txt'), """aaaaaaaaaaaaaaaakaaaa\raaaaaaaaaaaaaaaaaaasfgsdfg""")), \ #文本内容
  ('putObject', (psstr('warp'), psstr('warp'), warp)), \ #关联变形配置
  ('putEnumerated', (psstr('textGridding'), psstr('textGridding'), pschr('None'))), \ #文本网格:无
  ('putEnumerated', (pschr('Ornt'), pschr('Ornt'), pschr('Hrzn'))), \ #取向:水平
  ('putEnumerated', (pschr('AntA'), pschr('Annt'), pschr('AnSt'))), \ #消除锯齿:浑厚  
  ('putList', (psstr('textShape'), textShape_list)), \ #关联文本形状列表
  ('putList', (pschr('Txtt'), blockstyle_list)), \ #关联样式范围列表
  ('putList', (psstr('paragraphStyleRange'), blockparastyle_list)), \ #关联段落样式范围列表
  ('putList', (psstr('kerningRange'), kerningrange_list)), \ #关联字距微调范围列表
])

imgaction = psActionDescriptorTop(psapp, [ \
  ('putEnumerated',(pschr('TxLr'), pschr('Ordn'), pschr('Trgt'))), \
])
imgaction.add_commands([ \
  ('putObject', (pschr('T'), pschr('TxLr'), imgproperty)), \
])
imgaction.execute(pschr('setd'))

注:此段代码无法运行,原因是单行写多行似乎不能在中间注释,测试的话复制上面没注释的即可

Upfunny commented 2 years ago

另外,经测试,可以删去部分代码,只设置部分属性,如:

import photoshop.api as ps
from psaction import *

psapp = ps.Application()

blockstyle1 = psActionDescriptor(psapp, [ \
  ('putBoolean', (psstr('styleSheetHasParent'), True)), \
  ('putString', (psstr('fontPostScriptName'), """STKaiti""")), \
  ('putString', (pschr('FntN'), """STKaiti""")), \
  ('putString', (pschr('FntS'), """Regular""")), \
  ('putInteger', (pschr('Scrp'), 25)), \
  ('putInteger', (pschr('FntT'), 1)), \
  ('putUnitDouble', (pschr('Sz'), pschr('#Pnt'), 25.000000)), \
  ('putBoolean', (psstr('syntheticBold'), True)), \
  ('putBoolean', (psstr('autoLeading'), False)), \
  ('putUnitDouble', (pschr('Ldng'), pschr('#Pnt'), 32.000000)), \
])
blockparastyle1 = psActionDescriptor(psapp, [ \
  ('putBoolean', (psstr('styleSheetHasParent'), True)), \
])

blockstyle1_range = psActionDescriptor(psapp, [ \
  ('putInteger', (pschr('From'), 0)), \
  ('putInteger', (pschr('T'), 48)), \
  ('putObject', (pschr('TxtS'), pschr('TxtS'), blockstyle1)), \
])
blockparastyle1_range = psActionDescriptor(psapp, [ \
  ('putInteger', (pschr('From'), 0)), \
  ('putInteger', (pschr('T'), 48)), \
  ('putObject', (psstr('paragraphStyle'), psstr('paragraphStyle'), blockparastyle1)), \
])

blockstyle_list = psActionList(psapp, [ \
  ('putObject', (pschr('Txtt'), blockstyle1_range)), \
])
blockparastyle_list = psActionList(psapp, [ \
  ('putObject', (psstr('paragraphStyleRange'), blockparastyle1_range)), \
])
kerningrange_list = psActionList(psapp)

imgproperty = psActionDescriptor(psapp, [ \
  ('putString', (pschr('Txt'), """aaaaaaaaaaaaaaaakaaaa\raaaaaaaaaaaaaa\raaaaasfgsdfg""")), \
  ('putEnumerated', (pschr('Ornt'), pschr('Ornt'), pschr('Hrzn'))), \
  ('putList', (pschr('Txtt'), blockstyle_list)), \
  ('putList', (psstr('paragraphStyleRange'), blockparastyle_list)), \
  ('putList', (psstr('kerningRange'), kerningrange_list)), \
])

imgaction = psActionDescriptorTop(psapp, [ ('putEnumerated',(pschr('TxLr'), pschr('Ordn'), pschr('Trgt'))) ])
imgaction.add_commands([ ('putObject', (pschr('T'), pschr('TxLr'), imgproperty)) ])
imgaction.execute(pschr('setd'))

感觉胜利近在眼前了 上述动作只能设置文本的高级属性,并不能读取,但是看psd-tools的文档好像可以读取这些属性

话说怎么录制jsx? 我录制出来的动作是atn

TsXor commented 2 years ago

话说怎么录制jsx? 我录制出来的动作是atn

安装ScriptingListener插件:https://helpx.adobe.com/photoshop/kb/downloadable-plugins-and-content.html#id_68969 注意:不用时请将它移出插件目录,以免录制的操作挤爆硬盘

TsXor commented 2 years ago

通过这几天对ActionManager的研究,我发现了解决此问题的通用方案:

  1. 设计一个ActionReference选择目标图层
  2. 使用app.executeActionGet方法获取到包含目标图层数据的ActionDescriptor
  3. 切换文件
  4. 在新文件中创建一个同名同类型图层
  5. 使用executeAction将第2步的ActionDescriptor中的数据全部注入新文件中的图层
TsXor commented 2 years ago

此段代码可以实现一个文字图层的全复制,将当前文档选中的图层复制到下一个文档选中的图层上(需要都是文字图层)

#参考:https://loonghao.github.io/photoshop-python-api/examples/#copy-and-paste

#导入模块,设置app
import photoshop.api as ps
import photoshop.api.action_manager as am
app = ps.Application()

def switch_doc(n):  #ActionManager实现,向前/后切换n个文档,正数向后,负数向前
  next_desc = ps.ActionDescriptor.load({'null': ['!ref', ReferenceKey('document', am.Offset+n)])
  app.executeAction(am.str2id('select'), next_desc)

startRulerUnits = app.preferences.rulerUnits
app.preferences.rulerUnits = ps.Units.Inches

#这个reference会选择当前文字图层
curlyr_ref = ps.ActionReference.load(['!ref', ReferenceKey('textLayer', am.Enumerated('ordinal', 'targetEnum')))
curlyr_desc = app.executeActionGet(curlyr_ref)
curlyr_data = curlyr_desc.uget('textKey')
switch_doc(1)  #切换到后一个文档
desc = ps.ActionDescriptor()
desc.uput('target', curlyr_ref)
desc.uput('to', curlyr_data)
app.executeAction(am.str2id('set'), desc)

if startRulerUnits != app.preferences.rulerUnits:
    app.preferences.rulerUnits = startRulerUnits

完全复制,大小样式全部保留的那种

@Upfunny