Skip to main content

简介

官方地址官方文档

原理

利用Window提供的Api创建和使用COM对象来驱动PhotoShop 在python里可以用到win32com 或者 comtypes 来创建COM对象

基础使用

安装

pip install photoshop-python-api

引入

import photoshop.api as ps

基础类型

图层类型

来自 site-packages\photoshop\api\enumerations.py

class LayerKind(IntEnum):
BlackAndWhiteLayer = 22
BrightnessContrastLayer = 9
ChannelMixerLayer = 12
ColorBalanceLayer = 8
ColorLookup = 24
CurvesLayer = 7
ExposureLayer = 19
GradientFillLayer = 4
GradientMapLayer = 13
HueSaturationLayer = 10
InversionLayer = 14
Layer3D = 20
LevelsLayer = 6
NormalLayer = 1
PatternFillLayer = 5
PhotoFilterLayer = 18
PosterizeLayer = 16
SelectiveColorLayer = 11
SmartObjectLayer = 17 # 智能对象
SolidFillLayer = 3
TextLayer = 2 # 文字图层
ThresholdLayer = 15
Vibrance = 23
VideoLayer = 21


class LayerType(IntEnum):
ArtLayer = 1
LayerSet = 2

常用实例

文档对象

# 获取方式
doc = Application.activeDocument
doc = Application.documents[index]
doc = Application.documents.add()

# 所有类型的图层列表中
layer_list = doc.layers
# 所有非图层组的图层列表
artLayer_list = doc.artLayers
artLayer_count = len(list((i, x) for i, x in enumerate(app.documents.artLayers, 1)))
# 所有图层组
group_list = doc.layerSets
group_count = len(list((i, x) for i, x in enumerate(app.documents.layerSets, 1)))

# 可获取的属性
doc.resolution # 分辨率
doc.width
doc.height

# 常用方法
doc.save() # 原地保存
doc.activeLayer() # 选择指定的图层,如果在图层组中,可能会失效
doc.saveAs( # 另存为
file_path:str,
options,
asCopy:bool,
extensionType=ExtensionType.Lowercase)

doc.trim( # 裁剪
)

doc.backgroundLayer() # 获取背景图层
doc.resizeImage( # 缩放文件
width:int,
height:int,
dpi:int,
automatic:int=8)

常用的saveOptions


常用的ExtensionType


图层



# 新建图层
layer = docRef.artLayers.add()

# 选择图层
docRef.activeLayer = layer

# 获取指定图层
layer = docRef.layers[index] # index 从0开始
layer = docRef.layers.item(index) # index 从1开始

# 可以修改的图层熟悉感
layer.name
layer.visible


图层组

# 新建图层组
# 新建的图层组会替换下标[0],相当于在头部插入一个图层组
new_layer_set = docRef.layerSets.add()

new_layer_set.name
new_layer_set.opacity
new_layer_set.visible

# 复制一个图层组
copy_layer_set = new_layer_set.duplicate()

# 获取所有图层组
nlayerSets = docRef.layerSets

# 遍历图层组
for layerSet in nlayerSets:
print(layerSet.name)

# 获取可见性
ptrin(layerSet.visible)

# 设置可见性
layerSet.visible = False

# 通过下标获取图层组实例
layerSet = docRef.layerSets[0]
# 通过顺序?
layerSet = docRef.layerSets.item(1)

# 图层添加到图层组
layer = 图层实例
layer.move({图层组实例}, ps.ElementPlacement.PlaceInside)

# 图层组合并
merged_layer = docRef.layerSets[0].merge()

# 删除图层组
merged_layer.remove()

基础用例

import photoshop.api as ps

打开PS

def start_app():
app = ps.Application()
retuen app

打开智能对象图层

if layer.kind == LayerKind.SmartObjectLayer

psb =

获取 dom

docRef = app.open(file_path)

缩小psd分辨率

  • 一些边框效果会出现问题
docRef.resizeImage(
width, # 宽
height, # 高
resolution, # DPI
automatic, # 默认0
)
  • 另一个案例,可以保证效果也一起缩小
def resize_psd(size: int = 0, witch_side: str = "w", resolution: int = 72):
"""
@Description {description}

- param width=0 :{int} {description}
- param witch_side=w :{str} "`w`"|"`h`"
- param resolution=72 :{int} {description}

@returns `{}` {description}

"""
try:
app = get_app()

idImgS = app.charIDToTypeID("ImgS")
desc165 = ps.ActionDescriptor()
idPxl = app.charIDToTypeID("#Pxl")

if witch_side in ["w", "width"]:
idWdth = app.charIDToTypeID("Wdth")
else:
idWdth = app.charIDToTypeID("Hght")

desc165.putUnitDouble(idWdth, idPxl, size)
idscaleStyles = app.stringIDToTypeID("scaleStyles")
desc165.putBoolean(idscaleStyles, True)
idCnsP = app.charIDToTypeID("CnsP")
desc165.putBoolean(idCnsP, True)
idIntr = app.charIDToTypeID("Intr")
idIntp = app.charIDToTypeID("Intp")
idautomaticInterpolation = app.stringIDToTypeID("automaticInterpolation")
desc165.putEnumerated(idIntr, idIntp, idautomaticInterpolation)
app.executeAction(idImgS, desc165, ps.DialogModes.DisplayNoDialogs)

except Exception as e:
print("resize_psd faile", e)

修改分辨率(DPI)

def change_dpi(new_dpi: int, resize: bool = False) -> bool:
app = get_app()

try:
idImgS = app.charIDToTypeID("ImgS")
desc231 = ps.ActionDescriptor()

idRslt = app.charIDToTypeID("Rslt")
idRsl = app.charIDToTypeID("#Rsl")

desc231.putUnitDouble(idRslt, idRsl, new_dpi)
app.executeAction(idImgS, desc231, ps.DialogModes.DisplayNoDialogs)
except Exception as e:
print("change_dpi faile", e)

图层组操作

# 获取所有图层组
nlayerSets = docRef.layerSets
print(f'当前有{nlayerSets.length}个图层组')

# 通过下标获取一个图层组实例(如果只有一个)
nArtLayers = docRef.layerSets.item(nlayerSets.length)
print(f"获取下标为: {nlayerSets.length}")
print('图层组名称: ', nArtLayers.name)

# 软件内在选择(激活)指定图层组中的指定图层
# nArtLayers.artLayers.length 图层组的子图层数量
docRef.activeLayer = nArtLayers.artLayers.item(nArtLayers.artLayers.length)
print(f'当前图层组有{nArtLayers.artLayers.length}个子图层')

导出图片

# 导出png

# 导出jpg

# 导出pdf

# 导出psb

# 导出psd
doc = ps.active_document
options = ps.PhotoshopSaveOptions()
doc.saveAs(psd_file, options, True)

执行动作

"""Do a photoshop action."""
# Import local modules
from photoshop import Session


with Session() as api:
api.app.doAction(action="Frame Channel - 50 pixel")

列出当前打开的文件

for doc in app.documents:
print(doc.name)
doc.close()

关闭所有文件

# 基于ps2022的版本
def cloas_all_doc():
if len(list((i, x) for i, x in enumerate(app.documents, 1))) > 0:
for doc in app.documents:
doc.close()

更新psb链接内容

# 基于ps2022的版本
def update_psb():
try:
app = ps.Application()
idnull = app.charIDToTypeID("null")
action = app.stringIDToTypeID("placedLayerUpdateAllModified")
app.executeAction(action, None, ps.DialogModes.DisplayNoDialogs)
except Exception as e:
print('update_psb faile', e)
return False

锁定/解锁图层

# 基于ps2022的版本
def lock_layer(lock: bool = True) -> bool:
try:
app = ps.Application()
idapplyLocking = app.stringIDToTypeID("applyLocking")
desc439 = ps.ActionDescriptor()
idnull = app.charIDToTypeID("null")
ref46 = ps.ActionReference()

idLyr = app.charIDToTypeID("Lyr ")
idOrdn = app.charIDToTypeID("Ordn")
idTrgt = app.charIDToTypeID("Trgt")

ref46.putEnumerated(idLyr, idOrdn, idTrgt)
desc439.putReference(idnull, ref46)

idlayerLocking = app.stringIDToTypeID("layerLocking")
desc440 = ps.ActionDescriptor()

if lock:
idprotectNone = app.stringIDToTypeID("protectAll")
else:
idprotectNone = app.stringIDToTypeID("protectNone")
desc440.putBoolean(idprotectNone, True)
idlayerLocking = app.stringIDToTypeID("layerLocking")

desc439.putObject(idlayerLocking, idlayerLocking, desc440)
app.executeAction(idapplyLocking, desc439, ps.DialogModes.DisplayNoDialogs)

return True
except Exception as e:
print('lock_layer fail', e)
return False

替换图片

replace_contents = ps.app.stringIDToTypeID("placedLayerReplaceContents")
desc = ps.ActionDescriptor
idnull = ps.app.charIDToTypeID("null")
desc.putPath(idnull, "your/image/path.jpg")
ps.app.executeAction(replace_contents, desc)

打开智能对象图层

def open_smart_layer_in_psd(layer)->bool:
app = ps.Application()

if layer.kind != LayerKind.SmartObjectLayer:
return False

app.activeDocument.activeLayer = layer
old_doc = app.activeDocument.name
old_layer_name = layer.name

try:
app.doJavaScript('''
var idplacedLayerEditContents = stringIDToTypeID( "placedLayerEditContents" );
var desc923 = new ActionDescriptor();
executeAction( idplacedLayerEditContents, desc923, DialogModes.NO );
var idnewDocument = stringIDToTypeID( "newDocument" );
var desc924 = new ActionDescriptor();
var idDocI = charIDToTypeID( "DocI" );
desc924.putInteger( idDocI, 720 );
executeAction( idnewDocument, desc924, DialogModes.NO );
''')
except Exception as e:
# 如果两次的文档名称相同,则表示打开失败
if old_doc == app.activeDocument.name:
return False
else:
return True

插入图片

def create_img(img_path):
app = ps.Application()
_img_path = img_path.replace("\\", "/")
old_count = len(app.activeDocument.layers)
try:
JSX = '''
var idPlc = charIDToTypeID( "Plc " );
var desc1182 = new ActionDescriptor();
var idIdnt = charIDToTypeID( "Idnt" );
desc1182.putInteger( idIdnt, 4 );
var idnull = charIDToTypeID( "null" );
desc1182.putPath( idnull, new File( "{img_path}" ) );
var idFTcs = charIDToTypeID( "FTcs" );
var idQCSt = charIDToTypeID( "QCSt" );
var idQcsa = charIDToTypeID( "Qcsa" );
desc1182.putEnumerated( idFTcs, idQCSt, idQcsa );
var idOfst = charIDToTypeID( "Ofst" );
var desc1183 = new ActionDescriptor();
var idHrzn = charIDToTypeID( "Hrzn" );
var idPxl = charIDToTypeID( "#Pxl" );
desc1183.putUnitDouble( idHrzn, idPxl, 0 );
var idVrtc = charIDToTypeID( "Vrtc" );
var idPxl = charIDToTypeID( "#Pxl" );
desc1183.putUnitDouble( idVrtc, idPxl, 0 );
var idOfst = charIDToTypeID( "Ofst" );
desc1182.putObject( idOfst, idOfst, desc1183 );
executeAction( idPlc, desc1182, DialogModes.NO );'''
app.doJavaScript(JSX.format(img_path = _img_path))
new_count = len(app.activeDocument.layers)

if new_count - old_count == 1:
return True

return False
except Exception as e:
print('change_img fail', e)
return False

字体大小无法正确获取

字体大小与高度的关系

字体高度字体大小相除
4436230.711075441
4816800.707352941
991400.707142857
2773980.695979899
1201700.705882353
4355600.776785714
5016560.763719512
4045240.770992366
2994280.698598131
1431890.756613757
3394600.736956522
3925400.725925926
3354860.689300412
3865400.714814815
0.72579576
# 最终得出一个能获取字体的阈值
def get_text_size_fix(layer, scale: float = 0.75) -> int:
app = get_app()

try:
startX, startY, endX, endY = layer.bounds
width = endX - startX
height = endY - startY

fix_size = int(height / scale)
return fix_size
except:
return 0

设置激活图层时会被显示

如果将一个本来是隐藏的图层设置为激活图层,那么这个图层会被取消隐藏,如果对图层进行一些操作失败,记得将该图层还原为隐藏的图层,或者不保存文件退出

remove()

通过非layers遍历出来的图层,可能无法正确的调用remove()来删除自身

kind not found

某些特殊图层获取kind会抛出这个错误,然后导致整个ps终止运行。

目前已知会触发的图层:

  • 图层组