Python编写一个图片批量添加文字水印工具(附代码)

2025-12-13 0 899
目录
  • 项目目标
  • 技术栈
  • 运行效果
  • 环境搭建
  • 核心实现思路
    • 界面设计 (PyQt5)
    • 图像处理核心 (Pillow)
    • 多线程处理 (QThread)
  • 核心代码解析
  • 完整功能展示
  • 完整代码
  • 总结

在日常工作和自媒体运营中,我们经常需要给大量的图片添加水印以保护版权。市面上的工具要么收费,要么功能单一。今天,我们将使用 Python 强大的 GUI 库 PyQt5 和图像处理库 Pillow (PIL),亲手打造一个免费、开源且功能强大的批量水印工具。

项目目标

我们需要实现一个具备以下功能的桌面软件:

  • 批量处理:支持拖拽或选择多个文件/文件夹。
  • 可视化预览:在调整参数时实时预览水印效果。
  • 高度自定义:支持设置水印文字、大小、颜色、透明度、旋转角度。
  • 布局灵活:支持九宫格位置(如左上、右下)以及全图平铺模式。
  • 防卡顿:使用多线程处理图片,避免界面冻结。

技术栈

Python 3.x

PyQt5: 用于构建图形用户界面 (GUI)。

Pillow (PIL): 用于核心的图像处理(绘制文字、旋转、合成)。

运行效果

Python编写一个图片批量添加文字水印工具(附代码)

环境搭建

首先,我们需要安装必要的第三方库:

pipinstallPyQt5 Pillow

核心实现思路

界面设计 (PyQt5)

我们将界面分为左右两部分:

  • 左侧 (控制面板):包含文件列表、输出路径设置、以及所有的水印参数控件(输入框、滑块、下拉框等)。
  • 右侧 (预览区):显示当前选中图片的实时预览效果。

我们使用QHBoxLayout(水平布局) 来容纳左右面板,左侧面板内部使用QVBoxLayout(垂直布局) 来排列各个设置组 (QGroupBox)。

图像处理核心 (Pillow)

这是整个工具的灵魂。主要步骤如下:

1.打开图片:使用Image.open()并转换为RGBA模式以便处理透明度。

2.创建水印层:创建一个与原图等大的透明图层。

3.绘制文字

  • 使用ImageDraw.Draw绘制文本。
  • 计算文本大小 (draw.textbbox) 以便居中或定位。
  • 处理颜色和透明度。

4.旋转与平铺

  • 如果需要旋转,先在一个单独的小图层上绘制文字并旋转,然后粘贴到大水印层上。
  • 平铺模式:通过双重循环 (for x… for y…) 计算坐标,将水印重复粘贴到全图。

5.合成与保存:使用Image.alpha_composite将水印层叠加到原图,最后保存。

多线程处理 (QThread)

为了防止在处理几百张大图时界面卡死(“未响应”),我们将耗时的图片处理逻辑放入后台线程Worker中。

classWorker(QThread):

progress=pyqtSignal(int)# 进度信号

finished=pyqtSignal(str)# 完成信号

defrun(self):

# 遍历文件列表进行处理

fori, file_pathinenumerate(self.files):

self.process_image(file_path)

self.progress.emit(…)# 更新进度条

核心代码解析

水印绘制逻辑

这是实现平铺和定位的关键代码片段:

defprocess_image(self, file_path):

with Image.open(file_path).convert(\”RGBA\”) as img:

# 创建全透明水印层

watermark=Image.new(\’RGBA\’, img.size, (0,0,0,0))

# … (省略字体加载和颜色设置) …

# 创建单个水印小图用于旋转

txt_img=Image.new(\’RGBA\’, (max_dim, max_dim), (0,0,0,0))

txt_draw=ImageDraw.Draw(txt_img)

txt_draw.text((text_x, text_y), text, font=font, fill=fill_color)

# 旋转

ifrotation !=0:

txt_img=txt_img.rotate(rotation, resample=Image.BICUBIC)

# 核心布局逻辑

ifposition==\’平铺 (Tile)\’:

# 双重循环实现全图平铺

step_x=int(w_width+spacing)

step_y=int(w_height+spacing)

foryinrange(0, img.height, step_y):

forxinrange(0, img.width, step_x):

watermark.paste(txt_img, (x, y), txt_img)

else:

# 九宫格定位逻辑

# 根据 \’左\’, \’右\’, \’上\’, \’下\’ 关键字计算坐标

# …

watermark.paste(txt_img, (pos_x, pos_y), txt_img)

# 合成最终图片

out=Image.alpha_composite(img, watermark)

实时预览实现

预览功能的难点在于性能。我们不能每次调整参数都去处理原图(原图可能几千万像素)。

优化方案

  • 加载原图后,先生成一个较小的缩略图(例如最大边长 800px)。
  • 所有的预览计算都在这个缩略图上进行。
  • 注意:字体大小和间距需要根据缩略图的比例进行缩放,否则预览效果会和实际输出不一致。

# 缩放比例计算

scale_factor=preview_img.width/original_img_width

# 字体大小也要随之缩放

preview_font_size=int(user_set_font_size*scale_factor)

完整功能展示

运行main.py后,你将看到如下界面:

添加图片:点击“添加图片”或“添加文件夹”导入素材。

调整参数

  • 输入文字 “My Watermark”。
  • 拖动“旋转角度”滑块到 30 度。
  • 选择位置为“平铺”。
  • 调整透明度为 30% 使得水印不喧宾夺主。

预览:右侧会立即显示效果,所见即所得。

输出:选择输出目录,点击“开始处理”,进度条跑完即大功告成!

完整代码

importsys

importos

fromPyQt5.QtWidgetsimport(QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,

QLabel, QPushButton, QLineEdit, QFileDialog, QSlider, QSpinBox,

QComboBox, QColorDialog, QProgressBar, QMessageBox, QGroupBox,

QScrollArea, QListWidget)

fromPyQt5.QtCoreimportQt, QThread, pyqtSignal

fromPyQt5.QtGuiimportQPixmap, QImage, QColor, QFont

fromPILimportImage, ImageDraw, ImageFont, ImageEnhance

importmath

classWorker(QThread):

progress=pyqtSignal(int)

finished=pyqtSignal(str)

error=pyqtSignal(str)

def__init__(self, files, output_dir, config):

super().__init__()

self.files=files

self.output_dir=output_dir

self.config=config

self.is_running=True

defrun(self):

total=len(self.files)

success_count=0

ifnotos.path.exists(self.output_dir):

try:

os.makedirs(self.output_dir)

exceptException as e:

self.error.emit(f\”无法创建输出目录: {str(e)}\”)

return

fori, file_pathinenumerate(self.files):

ifnotself.is_running:

break

try:

self.process_image(file_path)

success_count+=1

exceptException as e:

print(f\”Error processing {file_path}: {e}\”)

self.progress.emit(int((i+1)/total*100))

self.finished.emit(f\”处理完成!成功: {success_count}/{total}\”)

defprocess_image(self, file_path):

try:

with Image.open(file_path).convert(\”RGBA\”) as img:

# 创建水印层

watermark=Image.new(\’RGBA\’, img.size, (0,0,0,0))

draw=ImageDraw.Draw(watermark)

text=self.config[\’text\’]

font_size=self.config[\’font_size\’]

opacity=self.config[\’opacity\’]

rotation=self.config[\’rotation\’]

color=self.config[\’color\’]# Tuple (r, g, b)

position=self.config[\’position\’]

spacing=self.config[\’spacing\’]# For tiling

# 加载字体 (使用默认字体,因为系统字体路径复杂,这里简化处理)

try:

# 尝试使用微软雅黑

font=ImageFont.truetype(\”msyh.ttc\”, font_size)

except:

font=ImageFont.load_default()

# default font doesn\’t scale well, but fallback is needed

# If we really want size, we might need a standard font file distributed with app

# Trying basic arial if msyh fails

try:

font=ImageFont.truetype(\”arial.ttf\”, font_size)

except:

pass# Fallback to default

# 计算文本大小

bbox=draw.textbbox((0,0), text, font=font)

text_width=bbox[2]-bbox[0]

text_height=bbox[3]-bbox[1]

# 创建单个水印图片用于旋转

# 留出足够空间以防旋转后被裁剪

max_dim=int(math.sqrt(text_width**2+text_height**2))

txt_img=Image.new(\’RGBA\’, (max_dim, max_dim), (0,0,0,0))

txt_draw=ImageDraw.Draw(txt_img)

# 居中绘制文本

text_x=(max_dim-text_width)//2

text_y=(max_dim-text_height)//2

# 设置颜色和透明度

fill_color=(color[0], color[1], color[2],int(255*opacity))

txt_draw.text((text_x, text_y), text, font=font, fill=fill_color)

# 旋转

ifrotation !=0:

txt_img=txt_img.rotate(rotation, resample=Image.BICUBIC)

# 获取旋转后的实际内容边界(可选,但为了精确布局最好做)

# 这里简单处理,直接使用txt_img

w_width, w_height=txt_img.size

ifposition==\’平铺 (Tile)\’:

# 平铺逻辑

# spacing 是间距倍数或像素

step_x=int(w_width+spacing)

step_y=int(w_height+spacing)

ifstep_x <=0: step_x=w_width+50

ifstep_y <=0: step_y=w_height+50

foryinrange(0, img.height, step_y):

forxinrange(0, img.width, step_x):

watermark.paste(txt_img, (x, y), txt_img)

else:

# 单个位置逻辑

pos_x=0

pos_y=0

margin=20

if\’左\’inposition:

pos_x=margin

elif\’右\’inposition:

pos_x=img.width-w_width-margin

else:# 中 (水平)

pos_x=(img.width-w_width)//2

if\’上\’inposition:

pos_y=margin

elif\’下\’inposition:

pos_y=img.height-w_height-margin

else:# 中 (垂直)

pos_y=(img.height-w_height)//2

watermark.paste(txt_img, (pos_x, pos_y), txt_img)

# 合成

out=Image.alpha_composite(img, watermark)

# 保存

filename=os.path.basename(file_path)

save_path=os.path.join(self.output_dir, filename)

# Convert back to RGB if saving as JPEG, otherwise keep RGBA for PNG

iffilename.lower().endswith((\’.jpg\’,\’.jpeg\’)):

out=out.convert(\’RGB\’)

out.save(save_path, quality=95)

else:

out.save(save_path)

exceptException as e:

print(f\”Processing failed for {file_path}: {e}\”)

raisee

classWatermarkApp(QMainWindow):

def__init__(self):

super().__init__()

self.setWindowTitle(\”批量图片水印工具\”)

self.resize(1000,700)

# Data

self.image_files=[]

self.current_preview_image=None

self.watermark_color=(255,255,255)# Default white

# UI Setup

central_widget=QWidget()

self.setCentralWidget(central_widget)

main_layout=QHBoxLayout(central_widget)

# Left Panel (Settings)

left_panel=QWidget()

left_layout=QVBoxLayout(left_panel)

left_panel.setFixedWidth(350)

main_layout.addWidget(left_panel)

# 1. File Selection

grp_files=QGroupBox(\”文件选择\”)

grp_files_layout=QVBoxLayout()

btn_layout=QHBoxLayout()

self.btn_add_files=QPushButton(\”添加图片\”)

self.btn_add_files.clicked.connect(self.add_files)

self.btn_add_folder=QPushButton(\”添加文件夹\”)

self.btn_add_folder.clicked.connect(self.add_folder)

self.btn_clear_files=QPushButton(\”清空列表\”)

self.btn_clear_files.clicked.connect(self.clear_files)

btn_layout.addWidget(self.btn_add_files)

btn_layout.addWidget(self.btn_add_folder)

btn_layout.addWidget(self.btn_clear_files)

self.list_files=QListWidget()

self.list_files.currentRowChanged.connect(self.update_preview)

grp_files_layout.addLayout(btn_layout)

grp_files_layout.addWidget(self.list_files)

grp_files.setLayout(grp_files_layout)

left_layout.addWidget(grp_files)

# 2. Output Directory

grp_output=QGroupBox(\”输出设置\”)

grp_output_layout=QVBoxLayout()

out_path_layout=QHBoxLayout()

self.edit_output=QLineEdit()

self.edit_output.setPlaceholderText(\”选择输出目录…\”)

self.btn_browse_output=QPushButton(\”浏览\”)

self.btn_browse_output.clicked.connect(self.browse_output)

out_path_layout.addWidget(self.edit_output)

out_path_layout.addWidget(self.btn_browse_output)

grp_output_layout.addLayout(out_path_layout)

grp_output.setLayout(grp_output_layout)

left_layout.addWidget(grp_output)

# 3. Watermark Settings

grp_settings=QGroupBox(\”水印设置\”)

grp_settings_layout=QVBoxLayout()

# Text

self.edit_text=QLineEdit(\”Sample Watermark\”)

self.edit_text.setPlaceholderText(\”输入水印文字\”)

self.edit_text.textChanged.connect(self.update_preview_delayed)

grp_settings_layout.addWidget(QLabel(\”水印文字:\”))

grp_settings_layout.addWidget(self.edit_text)

# Color

color_layout=QHBoxLayout()

self.btn_color=QPushButton(\”选择颜色\”)

self.btn_color.clicked.connect(self.choose_color)

self.lbl_color_preview=QLabel(\” \”)

self.lbl_color_preview.setStyleSheet(\”background-color: white; border: 1px solid black;\”)

self.lbl_color_preview.setFixedWidth(30)

color_layout.addWidget(QLabel(\”颜色:\”))

color_layout.addWidget(self.btn_color)

color_layout.addWidget(self.lbl_color_preview)

color_layout.addStretch()

grp_settings_layout.addLayout(color_layout)

# Font Size

size_layout=QHBoxLayout()

self.spin_size=QSpinBox()

self.spin_size.setRange(10,500)

self.spin_size.setValue(40)

self.spin_size.valueChanged.connect(self.update_preview_delayed)

size_layout.addWidget(QLabel(\”字体大小:\”))

size_layout.addWidget(self.spin_size)

grp_settings_layout.addLayout(size_layout)

# Opacity

opacity_layout=QHBoxLayout()

self.slider_opacity=QSlider(Qt.Horizontal)

self.slider_opacity.setRange(0,100)

self.slider_opacity.setValue(50)

self.slider_opacity.valueChanged.connect(self.update_preview_delayed)

opacity_layout.addWidget(QLabel(\”透明度:\”))

opacity_layout.addWidget(self.slider_opacity)

grp_settings_layout.addLayout(opacity_layout)

# Rotation

rotation_layout=QHBoxLayout()

self.slider_rotation=QSlider(Qt.Horizontal)

self.slider_rotation.setRange(0,360)

self.slider_rotation.setValue(0)

self.slider_rotation.valueChanged.connect(self.update_preview_delayed)

rotation_layout.addWidget(QLabel(\”旋转角度:\”))

rotation_layout.addWidget(self.slider_rotation)

grp_settings_layout.addLayout(rotation_layout)

# Position

pos_layout=QHBoxLayout()

self.combo_pos=QComboBox()

positions=[

\”左上\”,\”中上\”,\”右上\”,

\”左中\”,\”正中\”,\”右中\”,

\”左下\”,\”中下\”,\”右下\”,

\”平铺 (Tile)\”

]

self.combo_pos.addItems(positions)

self.combo_pos.setCurrentText(\”右下\”)

self.combo_pos.currentIndexChanged.connect(self.update_preview_delayed)

pos_layout.addWidget(QLabel(\”位置:\”))

pos_layout.addWidget(self.combo_pos)

grp_settings_layout.addLayout(pos_layout)

# Spacing (only for tile)

spacing_layout=QHBoxLayout()

self.spin_spacing=QSpinBox()

self.spin_spacing.setRange(0,500)

self.spin_spacing.setValue(100)

self.spin_spacing.valueChanged.connect(self.update_preview_delayed)

spacing_layout.addWidget(QLabel(\”间距 (平铺):\”))

spacing_layout.addWidget(self.spin_spacing)

grp_settings_layout.addLayout(spacing_layout)

grp_settings.setLayout(grp_settings_layout)

left_layout.addWidget(grp_settings)

left_layout.addStretch()

# Action Buttons

self.btn_start=QPushButton(\”开始处理\”)

self.btn_start.setMinimumHeight(40)

self.btn_start.setStyleSheet(\”font-weight: bold; font-size: 14px;\”)

self.btn_start.clicked.connect(self.start_processing)

left_layout.addWidget(self.btn_start)

self.progress_bar=QProgressBar()

left_layout.addWidget(self.progress_bar)

# Right Panel (Preview)

right_panel=QWidget()

right_layout=QVBoxLayout(right_panel)

main_layout.addWidget(right_panel)

right_layout.addWidget(QLabel(\”预览 (点击文件列表查看):\”))

self.scroll_area=QScrollArea()

self.scroll_area.setWidgetResizable(True)

self.lbl_preview=QLabel()

self.lbl_preview.setAlignment(Qt.AlignCenter)

self.scroll_area.setWidget(self.lbl_preview)

right_layout.addWidget(self.scroll_area)

# Debounce timer for preview update to avoid lag

self.preview_timer=None

defadd_files(self):

files, _=QFileDialog.getOpenFileNames(self,\”选择图片\”, \”\”, \”Images (*.png*.jpg*.jpeg*.bmp)\”)

iffiles:

self.image_files.extend(files)

self.update_file_list()

ifnotself.edit_output.text():

self.edit_output.setText(os.path.dirname(files[0])+\”/watermarked\”)

defadd_folder(self):

folder=QFileDialog.getExistingDirectory(self,\”选择文件夹\”)

iffolder:

forroot, dirs, filesinos.walk(folder):

forfileinfiles:

iffile.lower().endswith((\’.png\’,\’.jpg\’,\’.jpeg\’,\’.bmp\’)):

self.image_files.append(os.path.join(root,file))

self.update_file_list()

ifnotself.edit_output.text():

self.edit_output.setText(folder+\”/watermarked\”)

defclear_files(self):

self.image_files=[]

self.update_file_list()

self.lbl_preview.clear()

defupdate_file_list(self):

self.list_files.clear()

forfinself.image_files:

self.list_files.addItem(os.path.basename(f))

ifself.image_files:

self.list_files.setCurrentRow(0)

defbrowse_output(self):

folder=QFileDialog.getExistingDirectory(self,\”选择输出目录\”)

iffolder:

self.edit_output.setText(folder)

defchoose_color(self):

color=QColorDialog.getColor()

ifcolor.isValid():

self.watermark_color=(color.red(), color.green(), color.blue())

self.lbl_color_preview.setStyleSheet(f\”background-color: {color.name()}; border: 1px solid black;\”)

self.update_preview()

defupdate_preview_delayed(self):

# In a real app, use a QTimer to debounce.

# For simplicity here, just call update_preview directly,

# but keep method name to indicate intent if we add timer later.

self.update_preview()

defupdate_preview(self):

row=self.list_files.currentRow()

ifrow <0orrow >=len(self.image_files):

return

file_path=self.image_files[row]

# Generate preview

try:

config=self.get_config()

# Use PIL to generate preview

with Image.open(file_path).convert(\”RGBA\”) as img:

# Resize for preview if too large

preview_max_size=800

ifimg.width > preview_max_sizeorimg.height > preview_max_size:

img.thumbnail((preview_max_size, preview_max_size))

# Apply watermark (Reuse logic? For now duplicate simplified logic for preview speed)

watermark=Image.new(\’RGBA\’, img.size, (0,0,0,0))

draw=ImageDraw.Draw(watermark)

font_size=config[\’font_size\’]

# Scale font size relative to preview thumbnail

# Note: config[\’font_size\’] is for the original image?

# Ideally we should scale it down. But font size is usually absolute pixels.

# If we scaled down the image, the font will look HUGE if we don\’t scale it too.

# So we need to know the original image size vs preview size.

# Let\’s read original size first

with Image.open(file_path) as orig_img:

orig_w, orig_h=orig_img.size

scale_factor=img.width/orig_w

scaled_font_size=int(font_size*scale_factor)

ifscaled_font_size <1: scaled_font_size=1

try:

font=ImageFont.truetype(\”msyh.ttc\”, scaled_font_size)

except:

font=ImageFont.load_default()

try:

font=ImageFont.truetype(\”arial.ttf\”, scaled_font_size)

except:

pass

text=config[\’text\’]

bbox=draw.textbbox((0,0), text, font=font)

text_width=bbox[2]-bbox[0]

text_height=bbox[3]-bbox[1]

max_dim=int(math.sqrt(text_width**2+text_height**2))

txt_img=Image.new(\’RGBA\’, (max_dim, max_dim), (0,0,0,0))

txt_draw=ImageDraw.Draw(txt_img)

text_x=(max_dim-text_width)//2

text_y=(max_dim-text_height)//2

color=config[\’color\’]

opacity=config[\’opacity\’]

fill_color=(color[0], color[1], color[2],int(255*opacity))

txt_draw.text((text_x, text_y), text, font=font, fill=fill_color)

ifconfig[\’rotation\’] !=0:

txt_img=txt_img.rotate(config[\’rotation\’], resample=Image.BICUBIC)

w_width, w_height=txt_img.size

ifconfig[\’position\’]==\’平铺 (Tile)\’:

scaled_spacing=int(config[\’spacing\’]*scale_factor)

step_x=int(w_width+scaled_spacing)

step_y=int(w_height+scaled_spacing)

ifstep_x <=0: step_x=w_width+10

ifstep_y <=0: step_y=w_height+10

foryinrange(0, img.height, step_y):

forxinrange(0, img.width, step_x):

watermark.paste(txt_img, (x, y), txt_img)

else:

pos_x=0

pos_y=0

margin=int(20*scale_factor)

position=config[\’position\’]

if\’左\’inposition: pos_x=margin

elif\’右\’inposition: pos_x=img.width-w_width-margin

else: pos_x=(img.width-w_width)//2

if\’上\’inposition: pos_y=margin

elif\’下\’inposition: pos_y=img.height-w_height-margin

else: pos_y=(img.height-w_height)//2

watermark.paste(txt_img, (pos_x, pos_y), txt_img)

out=Image.alpha_composite(img, watermark)

# Convert to QPixmap

ifout.mode==\”RGBA\”:

r, g, b, a=out.split()

out=Image.merge(\”RGBA\”, (b, g, r, a))

elifout.mode==\”RGB\”:

r, g, b=out.split()

out=Image.merge(\”RGB\”, (b, g, r))

im2=out.convert(\”RGBA\”)

data=im2.tobytes(\”raw\”,\”RGBA\”)

qim=QImage(data, out.size[0], out.size[1], QImage.Format_ARGB32)

pixmap=QPixmap.fromImage(qim)

self.lbl_preview.setPixmap(pixmap)

exceptException as e:

print(f\”Preview error: {e}\”)

defget_config(self):

return{

\’text\’:self.edit_text.text(),

\’font_size\’:self.spin_size.value(),

\’opacity\’:self.slider_opacity.value()/100.0,

\’rotation\’:self.slider_rotation.value(),

\’color\’:self.watermark_color,

\’position\’:self.combo_pos.currentText(),

\’spacing\’:self.spin_spacing.value()

}

defstart_processing(self):

ifnotself.image_files:

QMessageBox.warning(self,\”提示\”,\”请先添加图片!\”)

return

output_dir=self.edit_output.text()

ifnotoutput_dir:

QMessageBox.warning(self,\”提示\”,\”请选择输出目录!\”)

return

self.btn_start.setEnabled(False)

self.progress_bar.setValue(0)

config=self.get_config()

self.worker=Worker(self.image_files, output_dir, config)

self.worker.progress.connect(self.progress_bar.setValue)

self.worker.finished.connect(self.processing_finished)

self.worker.error.connect(self.processing_error)

self.worker.start()

defprocessing_finished(self, msg):

self.btn_start.setEnabled(True)

QMessageBox.information(self,\”完成\”, msg)

defprocessing_error(self, msg):

self.btn_start.setEnabled(True)

QMessageBox.critical(self,\”错误\”, msg)

if__name__==\”__main__\”:

app=QApplication(sys.argv)

# 设置全局字体,看起来更现代一点

font=QFont(\”Microsoft YaHei\”,9)

app.setFont(font)

window=WatermarkApp()

window.show()

sys.exit(app.exec_())

总结

通过不到 400 行代码,我们结合了 PyQt5 的交互能力和 Pillow 的图像处理能力,开发出了一个实用的桌面工具。这个项目很好的展示了 Python 在自动化办公和工具开发领域的优势。

扩展思路

  • 支持图片水印(Logo)。
  • 保存/加载配置模板,方便下次直接使用。
  • 打包成 exe 文件(使用pyinstaller),方便分享给没有安装 Python 的同事使用。

以上就是Python编写一个图片批量添加文字水印工具(附代码)的详细内容,更多关于Python图片批量添加文字水印的资料请关注脚本之家其它相关文章!

来源 :https://www.jb51.net/python/35452557l.htm

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 Python编写一个图片批量添加文字水印工具(附代码) https://www.zuozi.net/36756.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务