v1.0.0 完成。软件功能不多,应该没什么 bug
8
.gitignore
vendored
@@ -113,3 +113,11 @@ dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
|
||||
.idea/
|
||||
*.mp4
|
||||
src/moduels/HandRight/
|
||||
src/build/
|
||||
src/dist/
|
||||
src/output/
|
||||
84
README.md
@@ -1,37 +1,73 @@
|
||||
# QuickHand
|
||||
#  Quick Hand
|
||||
|
||||
#### 介绍
|
||||
## 📝 介绍
|
||||
快速的仿手写文字的图片生成器。基于 https://github.com/Gsllchb/Handright/ 的 GUI。
|
||||
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
它是开源的,你可以免费使用它。下载请到 release 界面。
|
||||
|
||||
目前在两个仓库更新:
|
||||
|
||||
- https://github.com/HaujetZhao/QuickHand
|
||||
- https://gitee.com/haujet/QuickHand
|
||||
|
||||
关于软件参数的帮助,你可以参照:https://github.com/Gsllchb/Handright/blob/master/docs/tutorial.md
|
||||
|
||||
界面预览:
|
||||
|
||||

|
||||
|
||||
|
||||
#### 安装教程
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
## 🔮 使用说明
|
||||
|
||||
#### 使用说明
|
||||
原理:首先,在水平位置、竖直位置和字体大小三个自由度上,对每个字的整体做随机扰动。随后,在水平位置、竖直位置和旋转角度三个自由度上,对每个字的每个笔画做随机扰动。
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
Windows 64 位系统的用户:下载软件发行版压缩包,解压,双击运行里面的 `QuickHand.exe` 就可以运行了。
|
||||
|
||||
#### 参与贡献
|
||||
- 请将需要用到的字体文件(ttf 格式)放到软件根目录的 `fonts` 文件夹中
|
||||
- 请将需要用到的背景图片放到软件根目录的 `backgrounds` 文件夹中
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
排版关系参考:
|
||||
|
||||

|
||||
|
||||
## 🔨 参与贡献
|
||||
|
||||
作者只有 Win10 64 位系统。如果你用的是其它系统电脑,比如 windows 32 位、MacOS、Linux,你可以参与志愿打包。
|
||||
|
||||
只要安装上 requirements.txt 中的 python 依赖包,确保源码能跑起来,再用 pyinstaller 使用以下的命令将 QuickHand.py 打包:
|
||||
|
||||
```
|
||||
lss
|
||||
|
||||
```
|
||||
|
||||
再将:
|
||||
|
||||
- `backgrounds` 文件夹
|
||||
- `fonts` 文件夹
|
||||
- `assets` 文件夹
|
||||
- `README_zh.html` 文件
|
||||
- `database.db` 文件
|
||||
- `icon.ico` 文件
|
||||
- `sponsor.jpg` 文件
|
||||
- `style.css` 文件
|
||||
|
||||
都复制到打包出的 QuickHand 文件夹根目录,再打包成压缩包,即可。
|
||||
|
||||
Linux 和 MacOS 用户可能还需要将打包出的 QuickHand 文件夹根目录内的可执行文件加上执行权限才行,并且不能用 zip 等打包格式,因为这会使得可执行权限丢失。建议使用 tar.gz 格式压缩。
|
||||
|
||||
MacOS 用户不能使用 `icon.ico` 图标,请手动将其转换为 `icon.icns` 格式图片,放到打包出的 QuickHand 文件夹根目录内。
|
||||
|
||||
## ☕ 打赏
|
||||
|
||||
万水千山总是情,一块几块都是情。本软件完全开源,用爱发电,如果你愿意,可以以打赏的方式支持我一下:
|
||||
|
||||

|
||||
|
||||
|
||||
#### 码云特技
|
||||
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
|
||||
5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
## 😀 交流
|
||||
|
||||
如果有软件方面的反馈可以提交 issues,或者加入 QQ 群:[1146626791](https://qm.qq.com/cgi-bin/qm/qr?k=DgiFh5cclAElnELH4mOxqWUBxReyEVpm&jump_from=webapi)
|
||||
|
||||
|
||||
BIN
assets/image-20200808220817456.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
assets/params_visualizing.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
assets/sponsor.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
8
requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
pillow == 6.2.1
|
||||
|
||||
twine >= 1.8.0
|
||||
setuptools >= 38.6.0
|
||||
wheel
|
||||
|
||||
pytest
|
||||
pyside2
|
||||
147
src/QuickHand.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import sqlite3
|
||||
|
||||
from PySide2.QtCore import *
|
||||
from PySide2.QtGui import *
|
||||
from PySide2.QtSql import *
|
||||
from PySide2.QtWidgets import *
|
||||
|
||||
from moduels.SystemTray import SystemTray # 引入托盘栏
|
||||
from moduels.HandRightTab import HandRightTab
|
||||
from moduels.ConfigTab import ConfigTab
|
||||
from moduels.HelpTab import HelpTab
|
||||
|
||||
|
||||
dbname = './database.db' # 存储预设的数据库名字
|
||||
presetTableName = 'configPreset' # 存储预设的表单名字
|
||||
preferenceTableName = 'preference'
|
||||
styleFile = './style.css'
|
||||
version = 'V1.0.0'
|
||||
|
||||
|
||||
|
||||
############# 主窗口和托盘 ################
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.initGui()
|
||||
self.loadStyleSheet()
|
||||
# self.status = self.statusBar() # 状态栏
|
||||
|
||||
|
||||
# self.setWindowState(Qt.WindowMaximized)
|
||||
# sys.stdout = Stream(newText=self.onUpdateText)
|
||||
|
||||
def initGui(self):
|
||||
# 定义中心控件为多 tab 页面
|
||||
self.tabs = QTabWidget()
|
||||
QApplication.quit()
|
||||
self.setCentralWidget(self.tabs)
|
||||
|
||||
# 定义多个不同功能的 tab
|
||||
self.handRightTab = HandRightTab(self, conn, presetTableName) # 主要功能的 tab
|
||||
self.ConfigTab = ConfigTab(self, conn, preferenceTableName) # 配置
|
||||
self.helpTab = HelpTab(version, platfm) # 帮助
|
||||
|
||||
self.tabs.addTab(self.handRightTab, self.tr('HandRight'))
|
||||
self.tabs.addTab(self.ConfigTab, self.tr('设置'))
|
||||
self.tabs.addTab(self.helpTab, self.tr('帮助'))
|
||||
self.adjustSize()
|
||||
|
||||
# 设置图标
|
||||
if platfm == 'Windows':
|
||||
self.setWindowIcon(QIcon('icon.ico'))
|
||||
else:
|
||||
self.setWindowIcon(QIcon('icon.icns'))
|
||||
self.setWindowTitle('Quick Hand')
|
||||
|
||||
# self.setWindowFlag(Qt.WindowStaysOnTopHint) # 始终在前台
|
||||
|
||||
self.show()
|
||||
|
||||
def loadStyleSheet(self):
|
||||
pass
|
||||
# global styleFile
|
||||
# try:
|
||||
# try:
|
||||
# with open(styleFile, 'r', encoding='utf-8') as style:
|
||||
# self.setStyleSheet(style.read())
|
||||
# except:
|
||||
# with open(styleFile, 'r', encoding='gbk') as style:
|
||||
# self.setStyleSheet(style.read())
|
||||
# except:
|
||||
# QMessageBox.warning(self, self.tr('主题载入错误'), self.tr('未能成功载入主题,请确保软件根目录有 "style.css" 文件存在。'))
|
||||
|
||||
def keyPressEvent(self, event) -> None:
|
||||
# 在按下 F5 的时候重载 style.css 主题
|
||||
if (event.key() == Qt.Key_F5):
|
||||
self.loadStyleSheet()
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Shuts down application on close."""
|
||||
# Return stdout to defaults.
|
||||
if main.ConfigTab.hideToSystemTraySwitch.isChecked():
|
||||
event.ignore()
|
||||
self.hide()
|
||||
else:
|
||||
sys.stdout = sys.__stdout__
|
||||
super().closeEvent(event)
|
||||
pass
|
||||
|
||||
|
||||
def createDB():
|
||||
cursor = conn.cursor()
|
||||
result = cursor.execute('''select * from sqlite_master where name = '%s' ''' % presetTableName)
|
||||
if result.fetchone() == None:
|
||||
cursor.execute('''create table %s (
|
||||
id integer primary key autoincrement,
|
||||
name text,
|
||||
useBackgroundImage integer,
|
||||
imageName text,
|
||||
backgroundSizeBoxX integer,
|
||||
backgroundSizeBoxY integer,
|
||||
fontName text,
|
||||
fontSize integer,
|
||||
fontColor text,
|
||||
lineSpacing integer,
|
||||
leftMargin integer,
|
||||
topMargin integer,
|
||||
rightMargin integer,
|
||||
bottomMargin integer,
|
||||
wordSpacing integer,
|
||||
lineSpacingSigma integer,
|
||||
fontSizeSigma integer,
|
||||
wordSpacingSigma integer,
|
||||
endChars text,
|
||||
perturbXSigma integer,
|
||||
perturbYSigma integer,
|
||||
perturbThetaSigma real)''' % presetTableName)
|
||||
conn.commit()
|
||||
result = cursor.execute('''select * from sqlite_master where name = '%s' ''' % preferenceTableName)
|
||||
if result.fetchone() == None:
|
||||
cursor.execute('''create table %s (
|
||||
id integer primary key autoincrement,
|
||||
item text,
|
||||
value text
|
||||
)''' % preferenceTableName)
|
||||
conn.commit()
|
||||
print(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.environ['PATH'] += os.pathsep + os.getcwd()
|
||||
app = QApplication(sys.argv)
|
||||
conn = sqlite3.connect(dbname)
|
||||
createDB()
|
||||
platfm = platform.system()
|
||||
main = MainWindow()
|
||||
if platfm == 'Windows':
|
||||
tray = SystemTray(QIcon('icon.ico'), main)
|
||||
else:
|
||||
tray = SystemTray(QIcon('icon.icns'), main)
|
||||
sys.exit(app.exec_())
|
||||
conn.close()
|
||||
BIN
src/QuickHand_Win64_pyinstaller.7z
Normal file
BIN
src/backgrounds/letter.png
Normal file
|
After Width: | Height: | Size: 309 KiB |
BIN
src/database.db
Normal file
BIN
src/fonts/品如手写体.ttf
Normal file
BIN
src/fonts/手书体.ttf
Normal file
BIN
src/fonts/新叶念体.otf
Normal file
BIN
src/fonts/杨任东竹石体-Regular.ttf
Normal file
BIN
src/fonts/沐瑶软笔手写体.ttf
Normal file
BIN
src/fonts/沐瑶随心手写体.ttf
Normal file
BIN
src/fonts/清松手写体1.ttf
Normal file
BIN
src/fonts/清松手写体2.ttf
Normal file
BIN
src/fonts/胡晓波骚包体.otf
Normal file
BIN
src/fonts/贤二体.ttf
Normal file
BIN
src/fonts/阿朱泡泡体.ttf
Normal file
BIN
src/fonts/鸿雷板书简体-Regular.ttf
Normal file
BIN
src/icon.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
582
src/misc/README_zh.html
Normal file
BIN
src/misc/icon.afphoto
Normal file
BIN
src/misc/icon.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/misc/排版关系参考图.afphoto
Normal file
BIN
src/misc/视频封面 - 视频教程.afphoto
Normal file
BIN
src/misc/视频封面 - 视频教程.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
23
src/moduels/ColorLabel.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from PySide2.QtWidgets import *
|
||||
from PySide2.QtGui import *
|
||||
class ColorLabel(QLabel):
|
||||
|
||||
palette = QPalette()
|
||||
color = QColor()
|
||||
color.setRgb(0,0,0)
|
||||
|
||||
|
||||
def __init__(self, text):
|
||||
super().__init__()
|
||||
self.setText(text)
|
||||
self.setAutoFillBackground(True)
|
||||
self.setColor()
|
||||
|
||||
def mousePressEvent(self, ev:QMouseEvent):
|
||||
self.color = QColorDialog.getColor()
|
||||
self.palette.setColor(QPalette.Window, self.color)
|
||||
self.setColor()
|
||||
|
||||
def setColor(self):
|
||||
self.palette.setColor(QPalette.Window, self.color)
|
||||
self.setPalette(self.palette)
|
||||
55
src/moduels/ConfigTab.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from PySide2.QtCore import *
|
||||
from PySide2.QtGui import *
|
||||
from PySide2.QtSql import *
|
||||
from PySide2.QtWidgets import *
|
||||
|
||||
|
||||
class ConfigTab(QWidget):
|
||||
def __init__(self, parent, conn, preferenceTableName):
|
||||
super(ConfigTab, self).__init__(parent)
|
||||
self.conn = conn
|
||||
self.preferenceTableName = preferenceTableName
|
||||
self.initGui()
|
||||
self.connectSlots()
|
||||
self.initValues()
|
||||
|
||||
def initGui(self):
|
||||
|
||||
self.hideToSystemTraySwitch = QCheckBox(self.tr('点击关闭按钮时隐藏到托盘'))
|
||||
# self.chooseLanguageHint = QLabel(self.tr('语言:'))
|
||||
|
||||
self.preferenceGroupLayout = QHBoxLayout()
|
||||
self.preferenceGroupLayout.addWidget(self.hideToSystemTraySwitch)
|
||||
self.preferenceGroup = QGroupBox(self.tr('偏好设置'))
|
||||
self.preferenceGroup.setLayout(self.preferenceGroupLayout)
|
||||
|
||||
self.masterLayout = QVBoxLayout()
|
||||
self.masterLayout.addWidget(self.preferenceGroup)
|
||||
self.masterLayout.addStretch(1)
|
||||
|
||||
self.setLayout(self.masterLayout)
|
||||
|
||||
def initValues(self):
|
||||
self.checkDB()
|
||||
|
||||
def connectSlots(self):
|
||||
self.hideToSystemTraySwitch.clicked.connect(self.hideToSystemTraySwitchClicked)
|
||||
|
||||
def checkDB(self):
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
hideToSystemTrayResult = cursor.execute('''select value from %s where item = '%s'; ''' % (self.preferenceTableName, 'hideToTrayWhenHitCloseButton') ).fetchone()
|
||||
if hideToSystemTrayResult == None: # 如果关闭窗口最小化到状态栏这个选项还没有在数据库创建,那就创建一个
|
||||
cursor.execute('''insert into %s (item, value) values ('hideToTrayWhenHitCloseButton', 'False') ''' % self.preferenceTableName)
|
||||
self.conn.commit()
|
||||
else:
|
||||
hideToSystemTrayValue = hideToSystemTrayResult[0]
|
||||
if hideToSystemTrayValue == 'True':
|
||||
self.hideToSystemTraySwitch.setChecked(True)
|
||||
else:
|
||||
self.hideToSystemTraySwitch.setChecked(False)
|
||||
|
||||
def hideToSystemTraySwitchClicked(self):
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute('''update %s set value='%s' where item = '%s';''' % (self.preferenceTableName, str(self.hideToSystemTraySwitch.isChecked()), 'hideToTrayWhenHitCloseButton'))
|
||||
self.conn.commit()
|
||||
19
src/moduels/ConsolePrintBox.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from PySide2.QtWidgets import *
|
||||
from PySide2.QtGui import *
|
||||
|
||||
|
||||
class ConsolePrintBox(QTextEdit):
|
||||
# 定义一个 QTextEdit 类,写入 print 方法。用于输出显示。
|
||||
def __init__(self, parent=None):
|
||||
super(ConsolePrintBox, self).__init__(parent)
|
||||
self.setReadOnly(True)
|
||||
|
||||
def print(self, text):
|
||||
try:
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.insertText(text)
|
||||
self.setTextCursor(cursor)
|
||||
self.ensureCursorVisible()
|
||||
except:
|
||||
pass
|
||||
31
src/moduels/ConsoleWindow.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from PySide2.QtWidgets import *
|
||||
from PySide2.QtGui import *
|
||||
from moduels.ConsolePrintBox import ConsolePrintBox
|
||||
|
||||
|
||||
class ConsoleWindow(QMainWindow):
|
||||
# 这个 console 是个子窗口,调用的时候要指定父窗口。例如:window = Console(main)
|
||||
# 里面包含一个 OutputBox, 可以将信号导到它的 print 方法。
|
||||
thread = None
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ConsoleWindow, self).__init__(parent)
|
||||
self.initGui()
|
||||
|
||||
def initGui(self):
|
||||
self.setWindowTitle(self.tr('运行信息输出窗口'))
|
||||
self.resize(1300, 700)
|
||||
self.consolePrintBox = ConsolePrintBox() # 他就用于输出用户定义的打印信息
|
||||
self.consolePrintBox.setParent(self)
|
||||
self.setCentralWidget(self.consolePrintBox)
|
||||
self.show()
|
||||
|
||||
def closeEvent(self, a0: QCloseEvent) -> None:
|
||||
try:
|
||||
self.thread.exit()
|
||||
print('Thread exited')
|
||||
self.thread.setTerminationEnabled(True)
|
||||
self.thread.terminate()
|
||||
print('Thread terminated')
|
||||
except:
|
||||
pass
|
||||
32
src/moduels/GenerateImagesThread.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from PySide2.QtCore import *
|
||||
from PIL import Image
|
||||
from handright import Template, handwrite
|
||||
|
||||
class GenerateImagesThread(QThread):
|
||||
text = None
|
||||
template = None
|
||||
outputPath = None
|
||||
signal = Signal(str)
|
||||
showImage = False
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GenerateImagesThread, self).__init__(parent)
|
||||
|
||||
def print(self, text):
|
||||
self.signal.emit(text)
|
||||
|
||||
def run(self):
|
||||
self.signal.emit('开始生成图片\n\n')
|
||||
try:
|
||||
images = handwrite(self.text, self.template)
|
||||
for i, im in enumerate(images):
|
||||
assert isinstance(im, Image.Image)
|
||||
self.signal.emit('第 %s 张图片生成\n\n' % str(int(i) + 1))
|
||||
if self.showImage == True:
|
||||
im.show()
|
||||
outputDir = self.outputPath.replace('\\', '/')
|
||||
im.save(outputDir + "/{}.webp".replace('//', '/').format(i))
|
||||
self.signal.emit('所有图片生成完毕,输出文件夹为:%s\n\n' % outputDir)
|
||||
except Exception as e:
|
||||
self.signal.emit('出错了,报错信息如下:\n\n')
|
||||
self.signal.emit(str(e) + '\n\n')
|
||||
663
src/moduels/HandRightTab.py
Normal file
@@ -0,0 +1,663 @@
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from PySide2.QtCore import *
|
||||
from PySide2.QtGui import *
|
||||
from PySide2.QtWidgets import *
|
||||
|
||||
from PIL import Image, ImageFont
|
||||
from handright import Template, handwrite
|
||||
|
||||
from moduels.GenerateImagesThread import GenerateImagesThread
|
||||
from moduels.ConsoleWindow import ConsoleWindow
|
||||
from moduels.ColorLabel import ColorLabel
|
||||
# 参考 https://github.com/Gsllchb/Handright/blob/master/docs/tutorial.md
|
||||
|
||||
class HandRightTab(QWidget):
|
||||
def __init__(self, parent, conn, presetTableName):
|
||||
super(HandRightTab, self).__init__(parent)
|
||||
self.conn = conn
|
||||
self.presetTableName = presetTableName
|
||||
self.initGui()
|
||||
self.refreshList()
|
||||
self.connectSlots()
|
||||
self.initValue()
|
||||
|
||||
def initGui(self):
|
||||
|
||||
self.inputBox = QPlainTextEdit()
|
||||
|
||||
self.outputPathHint = QLabel('输出路径')
|
||||
self.outputPathBox = QLineEdit()
|
||||
|
||||
self.runBtn = QPushButton('运行')
|
||||
|
||||
self.backgroundBlankRadioBtn = QRadioButton('使用空白背景')
|
||||
self.backgroundImageRadioBtn = QRadioButton('使用背景图片')
|
||||
|
||||
|
||||
self.backgroundHint = QLabel('背景图片')
|
||||
self.backgroundBox = QComboBox()
|
||||
|
||||
self.backgroundSizeHint = QLabel('背景图片大小')
|
||||
self.backgroundSizeBoxX = QLineEdit()
|
||||
self.backgroundSizeBoxMultipleHint = QLabel('×')
|
||||
self.backgroundSizeBoxY = QLineEdit()
|
||||
|
||||
|
||||
self.fontPathHint = QLabel('字体')
|
||||
self.fontPathBox = QComboBox()
|
||||
|
||||
self.fontSizeHint = QLabel('字体大小')
|
||||
self.fontSizeBox = QSpinBox()
|
||||
|
||||
self.fontColorHint = QLabel('字体颜色')
|
||||
self.fontColorBox = ColorLabel('')
|
||||
|
||||
self.lineSpacingHint = QLabel('行间距')
|
||||
self.lineSpacingBox = QSpinBox()
|
||||
|
||||
self.wordSpacingHint = QLabel('字符间距')
|
||||
self.wordSpacingBox = QSpinBox()
|
||||
|
||||
|
||||
|
||||
self.leftMarginHint = QLabel('页面左边距')
|
||||
self.leftMarginBox = QSpinBox()
|
||||
|
||||
self.topMarginHint = QLabel('页面上边距')
|
||||
self.topMarginBox = QSpinBox()
|
||||
|
||||
self.rightMarginHint = QLabel('页面右边距')
|
||||
self.rightMarginBox = QSpinBox()
|
||||
|
||||
self.bottomMarginHint = QLabel('页面下边距')
|
||||
self.bottomMarginBox = QSpinBox()
|
||||
|
||||
|
||||
|
||||
self.lindSpacingSigmaHint = QLabel('行间距扰动')
|
||||
self.lindSpacingSigmaBox = QSpinBox()
|
||||
|
||||
self.fontSizeSigmaHint = QLabel('字体大小扰动')
|
||||
self.fontSizeSigmaBox = QSpinBox()
|
||||
|
||||
self.wordSpacingSigmaHint = QLabel('字间距扰动')
|
||||
self.wordSpacingSigmaBox = QSpinBox()
|
||||
|
||||
self.perturbXSigmaHint = QLabel('笔画横向偏移扰动')
|
||||
self.perturbXSigmaBox = QSpinBox()
|
||||
|
||||
self.perturbYSigmaHint = QLabel('笔画纵向偏移扰动')
|
||||
self.perturbYSigmaBox = QSpinBox()
|
||||
|
||||
self.perturbThetaSigmaHint = QLabel('笔画旋转偏移扰动')
|
||||
self.perturbThetaSigmaBox = QDoubleSpinBox()
|
||||
|
||||
self.endCharsHint = QLabel('防止行首字符')
|
||||
self.endCharsBox = QLineEdit()
|
||||
|
||||
self.showImageBox = QCheckBox('每生成一张图片后自动打开')
|
||||
|
||||
self.presetHint = QLabel('预设列表')
|
||||
self.presetList = QListWidget()
|
||||
|
||||
self.upPresetBtn = QPushButton('↑')
|
||||
self.downPresetBtn = QPushButton('↓')
|
||||
self.addPresetBtn = QPushButton('+')
|
||||
self.delPresetBtn = QPushButton('-')
|
||||
|
||||
|
||||
self.outputLayout = QHBoxLayout()
|
||||
self.outputLayout.setContentsMargins(0,0,0,0)
|
||||
self.outputLayout.addWidget(self.outputPathHint)
|
||||
self.outputLayout.addWidget(self.outputPathBox)
|
||||
self.outputBox = QWidget()
|
||||
self.outputBox.setContentsMargins(0,0,0,0)
|
||||
self.outputBox.setLayout(self.outputLayout)
|
||||
self.inputAndRunLayout = QVBoxLayout()
|
||||
self.inputAndRunLayout.addWidget(self.inputBox)
|
||||
self.inputAndRunLayout.addWidget(self.outputBox)
|
||||
self.inputAndRunLayout.addWidget(self.runBtn)
|
||||
self.inputAndRunBox = QWidget()
|
||||
self.inputAndRunBox.setLayout(self.inputAndRunLayout)
|
||||
|
||||
|
||||
|
||||
|
||||
self.backgroundTypeLayout = QHBoxLayout() # 关于背景类型的两个按钮放置的布局
|
||||
self.backgroundTypeLayout.addWidget(self.backgroundBlankRadioBtn)
|
||||
self.backgroundTypeLayout.addWidget(self.backgroundImageRadioBtn)
|
||||
self.backgroundTypeBox = QWidget()
|
||||
self.backgroundTypeBox.setContentsMargins(0,0,0,0)
|
||||
self.backgroundTypeBox.setLayout(self.backgroundTypeLayout)
|
||||
|
||||
self.backgroundSizeLayout = QHBoxLayout() # 关于背景大小
|
||||
self.backgroundSizeLayout.setContentsMargins(0,0,0,0)
|
||||
self.backgroundSizeLayout.addWidget(self.backgroundSizeBoxX)
|
||||
self.backgroundSizeLayout.addWidget(self.backgroundSizeBoxMultipleHint)
|
||||
self.backgroundSizeLayout.addWidget(self.backgroundSizeBoxY)
|
||||
self.backgroundSizeBox = QWidget()
|
||||
self.backgroundSizeBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.backgroundSizeBox.setLayout(self.backgroundSizeLayout)
|
||||
|
||||
self.optionsLayout = QFormLayout()
|
||||
self.optionsLayout.setWidget(0, QFormLayout.SpanningRole, self.backgroundTypeBox) # 背景类型
|
||||
self.optionsLayout.addRow(self.backgroundHint, self.backgroundBox) # 背景图片
|
||||
self.optionsLayout.setWidget(2, QFormLayout.LabelRole, self.backgroundSizeHint) # 背景大小
|
||||
self.optionsLayout.setWidget(2, QFormLayout.FieldRole, self.backgroundSizeBox) # 背景大小
|
||||
self.optionsLayout.addRow(QLabel(''), QLabel('')) # 空白行
|
||||
self.optionsLayout.addRow(self.fontPathHint, self.fontPathBox) # 字体
|
||||
self.optionsLayout.addRow(self.fontSizeHint, self.fontSizeBox) # 字体大小
|
||||
self.optionsLayout.addRow(self.fontColorHint, self.fontColorBox) # 字体颜色
|
||||
self.optionsLayout.addRow(self.lineSpacingHint, self.lineSpacingBox) # 行间距
|
||||
self.optionsLayout.addRow(self.wordSpacingHint, self.wordSpacingBox) # 字符间距
|
||||
self.optionsLayout.addRow(QLabel(''), QLabel('')) # 空白行
|
||||
self.optionsLayout.addRow(self.leftMarginHint, self.leftMarginBox) # 左边距
|
||||
self.optionsLayout.addRow(self.topMarginHint, self.topMarginBox) # 上边距
|
||||
self.optionsLayout.addRow(self.rightMarginHint, self.rightMarginBox) # 右边距
|
||||
self.optionsLayout.addRow(self.bottomMarginHint, self.bottomMarginBox) # 下边距
|
||||
self.optionsLayout.addRow(QLabel(''), QLabel('')) # 空白行
|
||||
self.optionsLayout.addRow(self.lindSpacingSigmaHint, self.lindSpacingSigmaBox) # 行间距扰动
|
||||
self.optionsLayout.addRow(self.fontSizeSigmaHint, self.fontSizeSigmaBox) # 字体大小扰动
|
||||
self.optionsLayout.addRow(self.wordSpacingSigmaHint, self.wordSpacingSigmaBox) # 字间距扰动
|
||||
self.optionsLayout.addRow(self.perturbXSigmaHint, self.perturbXSigmaBox) # 笔画横向偏移扰动
|
||||
self.optionsLayout.addRow(self.perturbYSigmaHint, self.perturbYSigmaBox) # 行间距扰动
|
||||
self.optionsLayout.addRow(self.perturbThetaSigmaHint, self.perturbThetaSigmaBox) # 行间距扰动
|
||||
self.optionsLayout.addRow(self.endCharsHint, self.endCharsBox) # 防止行首字符
|
||||
self.optionsLayout.addRow(QLabel(''), QLabel('')) # 空白行
|
||||
self.optionsLayout.setWidget(23, QFormLayout.SpanningRole, self.showImageBox)
|
||||
|
||||
|
||||
self.optionsBox = QWidget()
|
||||
self.optionsBox.setLayout(self.optionsLayout)
|
||||
|
||||
|
||||
self.presetLayout = QGridLayout()
|
||||
self.presetLayout.addWidget(self.presetHint, 0,0,1,2)
|
||||
self.presetLayout.addWidget(self.presetList, 1,0,1,2)
|
||||
self.presetLayout.addWidget(self.upPresetBtn, 2,0,1,1)
|
||||
self.presetLayout.addWidget(self.downPresetBtn, 2,1,1,1)
|
||||
self.presetLayout.addWidget(self.addPresetBtn, 3,0,1,1)
|
||||
self.presetLayout.addWidget(self.delPresetBtn, 3,1,1,1)
|
||||
self.presetBox = QWidget()
|
||||
self.presetBox.setLayout(self.presetLayout)
|
||||
|
||||
|
||||
|
||||
self.splitBetweenOptionAndPesetTable = QSplitter()
|
||||
self.splitBetweenOptionAndPesetTable.addWidget(self.optionsBox)
|
||||
self.splitBetweenOptionAndPesetTable.addWidget(self.presetBox)
|
||||
|
||||
self.splitBetweenInputAndOption = QSplitter()
|
||||
self.splitBetweenInputAndOption.addWidget(self.inputAndRunBox)
|
||||
self.splitBetweenInputAndOption.addWidget(self.splitBetweenOptionAndPesetTable)
|
||||
|
||||
self.masterLayout = QHBoxLayout()
|
||||
self.masterLayout.addWidget(self.splitBetweenInputAndOption)
|
||||
|
||||
self.setLayout(self.masterLayout)
|
||||
|
||||
def initValue(self):
|
||||
font = QFont()
|
||||
font.setPointSize(12)
|
||||
self.inputBox.setFont(font)
|
||||
self.inputBox.setPlaceholderText('在这里输入要生成图片的文字')
|
||||
|
||||
self.outputPathBox.setText(os.getcwd().replace('\\', '/') + '/output')
|
||||
|
||||
self.backgroundBlankRadioBtn.click() # 启用或停用背景图片
|
||||
# self.switchUseable()
|
||||
|
||||
self.backgroundBox.clear()
|
||||
self.backgroundBox.addItems(os.listdir('./backgrounds')) # 添加背景图片列表
|
||||
|
||||
self.backgroundSizeBoxX.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter) # 初始化背景图片大小
|
||||
self.backgroundSizeBoxX.setValidator(QIntValidator(self))
|
||||
self.backgroundSizeBoxX.setText('2000')
|
||||
|
||||
self.backgroundSizeBoxY.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter) # 初始化背景图片大小
|
||||
self.backgroundSizeBoxY.setValidator(QIntValidator(self))
|
||||
self.backgroundSizeBoxY.setText('2000')
|
||||
|
||||
|
||||
self.fontPathBox.clear()
|
||||
self.fontPathBox.addItems(os.listdir('./fonts')) # 添加字体列表
|
||||
|
||||
self.fontSizeBox.setSingleStep(5)
|
||||
self.fontSizeBox.setMinimum(1)
|
||||
self.fontSizeBox.setMaximum(2000)
|
||||
self.fontSizeBox.setValue(100) # 设定字体初始大小
|
||||
self.fontSizeBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.lineSpacingBox.setSingleStep(10)
|
||||
self.lineSpacingBox.setMinimum(1)
|
||||
self.lineSpacingBox.setMaximum(2000)
|
||||
self.lineSpacingBox.setValue(120) # 初始化行间距
|
||||
self.lineSpacingBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.wordSpacingBox.setSingleStep(5)
|
||||
self.wordSpacingBox.setMinimum(-1000)
|
||||
self.wordSpacingBox.setMaximum(2000)
|
||||
self.wordSpacingBox.setValue(120) # 初始化字符间距
|
||||
self.wordSpacingBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.leftMarginBox.setSingleStep(5)
|
||||
self.leftMarginBox.setMinimum(1)
|
||||
self.leftMarginBox.setMaximum(2000)
|
||||
self.leftMarginBox.setValue(200) # 初始化页面左边距
|
||||
self.leftMarginBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.topMarginBox.setSingleStep(5)
|
||||
self.topMarginBox.setMinimum(1)
|
||||
self.topMarginBox.setMaximum(2000)
|
||||
self.topMarginBox.setValue(200) # 初始化页面上边距
|
||||
self.topMarginBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.rightMarginBox.setSingleStep(5)
|
||||
self.rightMarginBox.setMinimum(1)
|
||||
self.rightMarginBox.setMaximum(2000)
|
||||
self.rightMarginBox.setValue(200) # 初始化页面右边剧
|
||||
self.rightMarginBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.bottomMarginBox.setSingleStep(5)
|
||||
self.bottomMarginBox.setMinimum(1)
|
||||
self.bottomMarginBox.setMaximum(2000)
|
||||
self.bottomMarginBox.setValue(200) # 初始化页面下边距
|
||||
self.bottomMarginBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.lindSpacingSigmaBox.setSingleStep(1)
|
||||
self.lindSpacingSigmaBox.setMinimum(1)
|
||||
self.lindSpacingSigmaBox.setMaximum(1000)
|
||||
self.lindSpacingSigmaBox.setValue(6) # 初始化行间距扰动
|
||||
self.lindSpacingSigmaBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.fontSizeSigmaBox.setSingleStep(2)
|
||||
self.fontSizeSigmaBox.setMinimum(1)
|
||||
self.fontSizeSigmaBox.setMaximum(1000)
|
||||
self.fontSizeSigmaBox.setValue(20) # 初始化字体大小扰动
|
||||
self.fontSizeSigmaBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.wordSpacingSigmaBox.setSingleStep(1)
|
||||
self.wordSpacingSigmaBox.setMinimum(1)
|
||||
self.wordSpacingSigmaBox.setMaximum(1000)
|
||||
self.wordSpacingSigmaBox.setValue(3) # 初始化字间距扰动
|
||||
self.wordSpacingSigmaBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.perturbXSigmaBox.setSingleStep(1)
|
||||
self.perturbXSigmaBox.setMinimum(1)
|
||||
self.perturbXSigmaBox.setMaximum(1000)
|
||||
self.perturbXSigmaBox.setValue(4) # 笔画横向偏移扰动
|
||||
self.perturbXSigmaBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.perturbYSigmaBox.setSingleStep(1)
|
||||
self.perturbYSigmaBox.setMinimum(1)
|
||||
self.perturbYSigmaBox.setMaximum(1000)
|
||||
self.perturbYSigmaBox.setValue(4) # 笔画纵向偏移扰动
|
||||
self.perturbYSigmaBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.perturbThetaSigmaBox.setSingleStep(1)
|
||||
self.perturbThetaSigmaBox.setDecimals(2)
|
||||
self.perturbThetaSigmaBox.setMinimum(0.00)
|
||||
self.perturbThetaSigmaBox.setMaximum(1)
|
||||
self.perturbThetaSigmaBox.setValue(0.05) # 笔画旋转偏移扰动
|
||||
self.perturbThetaSigmaBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
self.endCharsBox.setText(',。!?') # 防止行首字符
|
||||
self.endCharsBox.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
|
||||
|
||||
|
||||
self.splitBetweenInputAndOption.setStretchFactor(0,5)
|
||||
self.splitBetweenInputAndOption.setStretchFactor(1,1)
|
||||
|
||||
|
||||
def connectSlots(self):
|
||||
self.runBtn.clicked.connect(self.run)
|
||||
self.backgroundBlankRadioBtn.clicked.connect(self.backgroundBlankRadioBtnClicked)
|
||||
self.backgroundImageRadioBtn.clicked.connect(self.backgroundImageRadioBtnClicked)
|
||||
self.upPresetBtn.clicked.connect(self.upMovePreset)
|
||||
self.downPresetBtn.clicked.connect(self.downMovePreset)
|
||||
self.addPresetBtn.clicked.connect(self.addPreset)
|
||||
self.delPresetBtn.clicked.connect(self.delPreset)
|
||||
self.presetList.itemClicked.connect(self.presetItemSelected)
|
||||
|
||||
|
||||
def backgroundBlankRadioBtnClicked(self):
|
||||
self.useBackgroundImage = False
|
||||
self.backgroundHint.setEnabled(False)
|
||||
self.backgroundBox.setEnabled(False)
|
||||
self.backgroundSizeHint.setText('背景图片大小')
|
||||
self.backgroundSizeBoxX.setText('2000')
|
||||
self.backgroundSizeBoxY.setText('4000')
|
||||
|
||||
def backgroundImageRadioBtnClicked(self):
|
||||
self.useBackgroundImage = True
|
||||
self.backgroundHint.setEnabled(True)
|
||||
self.backgroundBox.setEnabled(True)
|
||||
self.backgroundSizeHint.setText('背景图片缩放')
|
||||
self.backgroundSizeBoxX.setText('1')
|
||||
self.backgroundSizeBoxY.setText('1')
|
||||
|
||||
def refreshList(self):
|
||||
cursor = self.conn.cursor()
|
||||
try:
|
||||
result = cursor.execute('''select name from %s order by id asc''' % (self.presetTableName)).fetchall()
|
||||
except:
|
||||
print('数据库读取不正常') # 这里可能还需要加一个重新载入数据库的操作, 不过懒得写了
|
||||
self.presetList.clear()
|
||||
for item in result:
|
||||
self.presetList.addItem(item[0])
|
||||
|
||||
def presetItemSelected(self):
|
||||
currentRow = self.presetList.currentRow()
|
||||
currentItemName = self.presetList.item(currentRow).text()
|
||||
presetData = self.conn.cursor().execute('''select
|
||||
useBackgroundImage, imageName,
|
||||
backgroundSizeBoxX, backgroundSizeBoxY, fontName, fontSize,
|
||||
fontColor, lineSpacing, leftMargin, topMargin,
|
||||
rightMargin, bottomMargin, wordSpacing, lineSpacingSigma,
|
||||
fontSizeSigma, wordSpacingSigma, endChars, perturbXSigma,
|
||||
perturbYSigma, perturbThetaSigma
|
||||
from %s where name = '%s'; ''' % (
|
||||
self.presetTableName, currentItemName)).fetchone()
|
||||
|
||||
useBackgroundImage = presetData[0]
|
||||
imageName = presetData[1]
|
||||
backgroundSizeBoxX = presetData[2]
|
||||
backgroundSizeBoxY = presetData[3]
|
||||
fontName = presetData[4]
|
||||
fontSize = presetData[5]
|
||||
fontColor = presetData[6]
|
||||
lineSpacing = presetData[7]
|
||||
leftMargin = presetData[8]
|
||||
topMargin = presetData[9]
|
||||
rightMargin = presetData[10]
|
||||
bottomMargin = presetData[11]
|
||||
wordSpacing = presetData[12]
|
||||
lineSpacingSigma = presetData[13]
|
||||
fontSizeSigma = presetData[14]
|
||||
wordSpacingSigma = presetData[15]
|
||||
endChars = presetData[16]
|
||||
perturbXSigma = presetData[17]
|
||||
perturbYSigma = presetData[18]
|
||||
perturbThetaSigma = presetData[19]
|
||||
|
||||
if useBackgroundImage:
|
||||
self.backgroundImageRadioBtn.click()
|
||||
else:
|
||||
self.backgroundBlankRadioBtn.click()
|
||||
self.backgroundBox.setCurrentText(imageName)
|
||||
self.backgroundSizeBoxX.setText(str(backgroundSizeBoxX))
|
||||
self.backgroundSizeBoxY.setText(str(backgroundSizeBoxY))
|
||||
self.fontPathBox.setCurrentText(fontName)
|
||||
self.fontSizeBox.setValue(fontSize)
|
||||
colorTuple = tuple(eval(fontColor))
|
||||
print(colorTuple)
|
||||
self.fontColorBox.color = QColor()
|
||||
self.fontColorBox.color.setRgb(colorTuple[0], colorTuple[1], colorTuple[2])
|
||||
self.fontColorBox.setColor()
|
||||
self.lineSpacingBox.setValue(lineSpacing)
|
||||
self.leftMarginBox.setValue(leftMargin)
|
||||
self.topMarginBox.setValue(topMargin)
|
||||
self.rightMarginBox.setValue(rightMargin)
|
||||
self.bottomMarginBox.setValue(bottomMargin)
|
||||
self.wordSpacingBox.setValue(wordSpacing)
|
||||
self.lindSpacingSigmaBox.setValue(lineSpacingSigma)
|
||||
self.fontSizeSigmaBox.setValue(fontSizeSigma)
|
||||
self.wordSpacingSigmaBox.setValue(wordSpacingSigma)
|
||||
self.endCharsBox.setText(endChars)
|
||||
self.perturbXSigmaBox.setValue(perturbXSigma)
|
||||
self.perturbYSigmaBox.setValue(perturbYSigma)
|
||||
self.perturbThetaSigmaBox.setValue(perturbThetaSigma)
|
||||
|
||||
|
||||
def addPreset(self):
|
||||
presetName, _ = QInputDialog.getText(self,'添加或更新预设','请输入预设名称')
|
||||
|
||||
print(presetName)
|
||||
if _ == False:
|
||||
return False
|
||||
imageName = self.backgroundBox.currentText().replace("'", "''")
|
||||
imageSizeX = int(self.backgroundSizeBoxX.text())
|
||||
imageSizeY = int(self.backgroundSizeBoxY.text())
|
||||
fontName = self.fontPathBox.currentText().replace("'", "''")
|
||||
fontSize = self.fontSizeBox.value()
|
||||
fontColor = self.fontColorBox.color
|
||||
fontColor = (fontColor.red(), fontColor.green(), fontColor.blue())
|
||||
lineSpacing = self.lineSpacingBox.value()
|
||||
leftMargin = self.leftMarginBox.value()
|
||||
topMargin = self.topMarginBox.value()
|
||||
rightMargin = self.rightMarginBox.value()
|
||||
bottomMargin = self.bottomMarginBox.value()
|
||||
wordSpacing = self.wordSpacingBox.value()
|
||||
lineSpacingSigma = self.lindSpacingSigmaBox.value()
|
||||
fontSizeSigma = self.fontSizeSigmaBox.value()
|
||||
wordSpacingSigma = self.wordSpacingSigmaBox.value()
|
||||
endChars = self.endCharsBox.text().replace("'", "''")
|
||||
perturbXSigma = self.perturbXSigmaBox.value()
|
||||
perturbYSigma = self.perturbYSigmaBox.value()
|
||||
perturbThetaSigma = self.perturbThetaSigmaBox.value()
|
||||
|
||||
cursor = self.conn.cursor()
|
||||
result = cursor.execute('''select name from %s where name = '%s';''' % (self.presetTableName, presetName.replace("'", "''"))).fetchone()
|
||||
if result == None: # 如果没有重名的预设
|
||||
maxIdItem = cursor.execute('''select id from %s order by id desc;''' % (self.presetTableName)).fetchone()
|
||||
if maxIdItem == None:
|
||||
maxId = 0
|
||||
else:
|
||||
maxId = maxIdItem[0]
|
||||
print(maxId)
|
||||
cursor.execute('''insert into %s (id, name, useBackgroundImage, imageName,
|
||||
backgroundSizeBoxX, backgroundSizeBoxY, fontName, fontSize,
|
||||
fontColor, lineSpacing, leftMargin, topMargin,
|
||||
rightMargin, bottomMargin, wordSpacing, lineSpacingSigma,
|
||||
fontSizeSigma, wordSpacingSigma, endChars, perturbXSigma,
|
||||
perturbYSigma, perturbThetaSigma)
|
||||
values (%s, '%s', %s, '%s',
|
||||
%s, %s, '%s', %s,
|
||||
'%s', %s, %s, %s,
|
||||
%s, %s, %s, %s,
|
||||
%s, %s, '%s', %s,
|
||||
%s, %s);'''
|
||||
% (self.presetTableName, maxId+1, presetName, self.useBackgroundImage, imageName,
|
||||
imageSizeX, imageSizeY, fontName, fontSize,
|
||||
fontColor, lineSpacing, leftMargin, topMargin,
|
||||
rightMargin, bottomMargin, wordSpacing, lineSpacingSigma,
|
||||
fontSizeSigma, wordSpacingSigma, endChars, perturbXSigma,
|
||||
perturbYSigma, perturbThetaSigma))
|
||||
else: # 如果有重名的预设
|
||||
answer = QMessageBox.question(self, '更新预设', '已经存在相同名字的预设,是否更新?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
|
||||
if answer != QMessageBox.Yes:
|
||||
return False
|
||||
print(answer)
|
||||
cursor.execute('''update %s set useBackgroundImage=%s, imageName='%s',
|
||||
backgroundSizeBoxX=%s, backgroundSizeBoxY=%s, fontName='%s', fontSize=%s,
|
||||
fontColor='%s', lineSpacing=%s, leftMargin=%s, topMargin=%s,
|
||||
rightMargin=%s, bottomMargin=%s, wordSpacing=%s, lineSpacingSigma=%s,
|
||||
fontSizeSigma=%s, wordSpacingSigma=%s, endChars='%s', perturbXSigma=%s,
|
||||
perturbYSigma=%s, perturbThetaSigma=%s where name = '%s' '''
|
||||
% (self.presetTableName, self.useBackgroundImage, imageName,
|
||||
imageSizeX, imageSizeY, fontName, fontSize,
|
||||
fontColor, lineSpacing, leftMargin, topMargin,
|
||||
rightMargin, bottomMargin, wordSpacing, lineSpacingSigma,
|
||||
fontSizeSigma, wordSpacingSigma, endChars, perturbXSigma,
|
||||
perturbYSigma, perturbThetaSigma, presetName))
|
||||
QMessageBox.information(self, '成功', '预设更新成功')
|
||||
self.conn.commit()
|
||||
self.refreshList()
|
||||
|
||||
def delPreset(self):
|
||||
currentRow = self.presetList.currentRow()
|
||||
if currentRow < 0:
|
||||
return False
|
||||
currentItemName = self.presetList.item(currentRow).text()
|
||||
answer = QMessageBox.question(self, '删除预设', '确认要删除“%s”预设吗?' % currentItemName)
|
||||
if answer == QMessageBox.No:
|
||||
return False
|
||||
id = self.conn.cursor().execute('''select id from %s where name = '%s'; ''' % (self.presetTableName, currentItemName)).fetchone()[0]
|
||||
self.conn.cursor().execute("delete from %s where id = '%s'; " % (self.presetTableName, id))
|
||||
self.conn.cursor().execute("update %s set id=id-1 where id > %s" % (self.presetTableName, id))
|
||||
self.conn.commit()
|
||||
self.refreshList()
|
||||
|
||||
def upMovePreset(self):
|
||||
currentRow = self.presetList.currentRow()
|
||||
if currentRow < 1:
|
||||
return False
|
||||
currentItemName = self.presetList.currentItem().text().replace("'", "''")
|
||||
id = self.conn.cursor().execute(
|
||||
"select id from %s where name = '%s'" % (self.presetTableName, currentItemName)).fetchone()[0]
|
||||
self.conn.cursor().execute("update %s set id=10000 where id=%s-1 " % (self.presetTableName, id))
|
||||
self.conn.cursor().execute("update %s set id = id - 1 where name = '%s'" % (self.presetTableName, currentItemName))
|
||||
self.conn.cursor().execute("update %s set id=%s where id=10000 " % (self.presetTableName, id))
|
||||
self.conn.commit()
|
||||
self.refreshList()
|
||||
self.presetList.setCurrentRow(currentRow - 1)
|
||||
|
||||
def downMovePreset(self):
|
||||
currentRow = self.presetList.currentRow()
|
||||
totalRow = self.presetList.count()
|
||||
if currentRow < 0 or currentRow > totalRow - 2:
|
||||
return False
|
||||
currentItemName = self.presetList.currentItem().text().replace("'", "''")
|
||||
id = self.conn.cursor().execute(
|
||||
"select id from %s where name = '%s'" % (self.presetTableName, currentItemName)).fetchone()[0]
|
||||
self.conn.cursor().execute("update %s set id=10000 where id=%s+1 " % (self.presetTableName, id))
|
||||
self.conn.cursor().execute("update %s set id = id + 1 where name = '%s'" % (self.presetTableName, currentItemName))
|
||||
self.conn.cursor().execute("update %s set id=%s where id=10000 " % (self.presetTableName, id))
|
||||
self.conn.commit()
|
||||
self.refreshList()
|
||||
if currentRow < totalRow:
|
||||
self.presetList.setCurrentRow(currentRow + 1)
|
||||
else:
|
||||
self.presetList.setCurrentRow(currentRow)
|
||||
|
||||
def run(self):
|
||||
if not self.checkOutputPath(): # 如果输出检查发生了错误 就停止
|
||||
return False
|
||||
if self.fontPathBox.currentText() == '':
|
||||
QMessageBox.information(self, '字体问题', '选择的字体为空,运行停止。请将 ttf 格式的字体放到本软件根目录的 fonts 文件夹中,再重新启动软件。')
|
||||
return False
|
||||
if self.useBackgroundImage:
|
||||
imageName = self.backgroundBox.currentText() # 图片文件名
|
||||
if imageName == '':
|
||||
QMessageBox.warning(self, '背景问题', '你选择了使用图片背景,但背景图片文件为空,运行停止。请将 jpg、png 格式的图片放到本软件根目录的 backgrounds 文件夹中,再重新启动软件。')
|
||||
return False
|
||||
if (self.backgroundSizeBoxX.text() == '' or self.backgroundSizeBoxY.text() == '') or (int(self.backgroundSizeBoxX.text()) == 0 or int(self.backgroundSizeBoxY.text()) == 0):
|
||||
QMessageBox.warning(self, '背景问题', '背景图片大小错误,请检查')
|
||||
return False
|
||||
inputText = self.inputBox.toPlainText()
|
||||
if inputText == '':
|
||||
print('没有输入文字')
|
||||
return False
|
||||
|
||||
# 背景图片
|
||||
imageSizeX = int(self.backgroundSizeBoxX.text())
|
||||
imageSizeY = int(self.backgroundSizeBoxY.text())
|
||||
if not self.useBackgroundImage:
|
||||
background = Image.new(mode="RGB", size=(imageSizeX, imageSizeY),color=(255, 255, 255))
|
||||
else:
|
||||
imagePath = os.path.abspath('./backgrounds/' + imageName)
|
||||
try:
|
||||
background = Image.open(imagePath, 'r')
|
||||
except:
|
||||
QMessageBox.warning(self, '背景问题', '所选背景图片不是可打开的图片文件')
|
||||
return False
|
||||
width, height = background.size
|
||||
background = background.resize((width * imageSizeX, height * imageSizeY), resample=Image.LANCZOS)
|
||||
|
||||
# 字体文件
|
||||
fontName = self.fontPathBox.currentText()
|
||||
fontPath = os.path.abspath('./fonts/' + fontName)
|
||||
try:
|
||||
font = ImageFont.truetype(fontPath)
|
||||
except:
|
||||
QMessageBox.warning(self, '字体问题', '所选字体不是可打开的 ttf 字体文件')
|
||||
return False
|
||||
|
||||
fontSize = self.fontSizeBox.value()
|
||||
fontColor = self.fontColorBox.color
|
||||
fontColor = (fontColor.red(), fontColor.green(), fontColor.blue())
|
||||
lineSpacing = self.lineSpacingBox.value()
|
||||
leftMargin = self.leftMarginBox.value()
|
||||
topMargin = self.topMarginBox.value()
|
||||
rightMargin = self.rightMarginBox.value()
|
||||
bottomMargin = self.bottomMarginBox.value()
|
||||
wordSpacing = self.wordSpacingBox.value()
|
||||
lineSpacingSigma = self.lindSpacingSigmaBox.value()
|
||||
fontSizeSigma = self.fontSizeSigmaBox.value()
|
||||
wordSpacingSigma = self.wordSpacingSigmaBox.value()
|
||||
endChars = self.endCharsBox.text()
|
||||
perturbXSigma = self.perturbXSigmaBox.value()
|
||||
perturbYSigma = self.perturbYSigmaBox.value()
|
||||
perturbThetaSigma = self.perturbThetaSigmaBox.value()
|
||||
showImage = self.showImageBox.isChecked()
|
||||
|
||||
|
||||
template = Template(
|
||||
background=background,
|
||||
font_size=fontSize,
|
||||
font=font,
|
||||
line_spacing=lineSpacing,
|
||||
fill=fontColor, # 字体“颜色”
|
||||
left_margin=leftMargin,
|
||||
top_margin=topMargin,
|
||||
right_margin=rightMargin,
|
||||
bottom_margin=bottomMargin,
|
||||
word_spacing=wordSpacing,
|
||||
line_spacing_sigma=lineSpacingSigma, # 行间距随机扰动
|
||||
font_size_sigma=fontSizeSigma, # 字体大小随机扰动
|
||||
word_spacing_sigma=wordSpacingSigma, # 字间距随机扰动
|
||||
end_chars=endChars, # 防止特定字符因排版算法的自动换行而出现在行首
|
||||
perturb_x_sigma=perturbXSigma, # 笔画横向偏移随机扰动
|
||||
perturb_y_sigma=perturbYSigma, # 笔画纵向偏移随机扰动
|
||||
perturb_theta_sigma=perturbThetaSigma, # 笔画旋转偏移随机扰动
|
||||
)
|
||||
|
||||
window = ConsoleWindow(self.parent())
|
||||
thread = GenerateImagesThread()
|
||||
window.thread = thread
|
||||
thread.text = inputText
|
||||
thread.template = template
|
||||
thread.outputPath = self.outputPath
|
||||
thread.showImage = showImage
|
||||
thread.signal.connect(window.consolePrintBox.print)
|
||||
thread.start()
|
||||
|
||||
|
||||
|
||||
|
||||
def checkOutputPath(self):
|
||||
self.outputPath = self.outputPathBox.text()
|
||||
if not os.path.exists(self.outputPath): # 如果输出路径不存在
|
||||
respond = QMessageBox.question(self, '路径错误', '输出文件夹不存在,是否创建?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
|
||||
if respond == QMessageBox.Yes:
|
||||
try:
|
||||
os.mkdir(self.outputPath)
|
||||
return True
|
||||
except:
|
||||
QMessageBox.warning(self, '路径错误', '无法创建输出文件夹,请检查输出路径')
|
||||
return False
|
||||
else:
|
||||
return False # 任务取消
|
||||
else:
|
||||
return True # 路径存在, 继续任务
|
||||
|
||||
|
||||
|
||||
# 启用和停用某些选项
|
||||
def switchUseable(self):
|
||||
if self.backgroundSwitch.isChecked() == False:
|
||||
self.backgroundHint.setEnabled(False)
|
||||
self.backgroundBox.setEnabled(False)
|
||||
else:
|
||||
self.backgroundHint.setEnabled(True)
|
||||
self.backgroundBox.setEnabled(True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
56
src/moduels/HelpTab.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import webbrowser
|
||||
import os
|
||||
|
||||
from PySide2.QtCore import *
|
||||
from PySide2.QtGui import *
|
||||
from PySide2.QtSql import *
|
||||
from PySide2.QtWidgets import *
|
||||
|
||||
from moduels.SponsorDialog import SponsorDialog
|
||||
|
||||
|
||||
# 帮助页面
|
||||
class HelpTab(QWidget):
|
||||
def __init__(self, version, platfm):
|
||||
super().__init__()
|
||||
self.platfm = platfm
|
||||
self.openHelpFileButton = QPushButton(self.tr('打开帮助文档'))
|
||||
self.openVideoHelpButtone = QPushButton(self.tr('查看视频教程'))
|
||||
self.openGiteePage = QPushButton(self.tr('当前版本是 %s,到 Gitee 检查新版本') % version)
|
||||
self.openGithubPage = QPushButton(self.tr('当前版本是 %s,到 Github 检查新版本') % version)
|
||||
self.linkToDiscussPage = QPushButton(self.tr('加入 QQ 群'))
|
||||
self.tipButton = QPushButton(self.tr('打赏作者'))
|
||||
|
||||
self.openHelpFileButton.setMaximumHeight(100)
|
||||
self.openVideoHelpButtone.setMaximumHeight(100)
|
||||
self.openGiteePage.setMaximumHeight(100)
|
||||
self.openGithubPage.setMaximumHeight(100)
|
||||
self.linkToDiscussPage.setMaximumHeight(100)
|
||||
self.tipButton.setMaximumHeight(100)
|
||||
|
||||
self.openHelpFileButton.clicked.connect(self.openHelpDocument)
|
||||
self.openVideoHelpButtone.clicked.connect(lambda: webbrowser.open(self.tr(r'https://www.bilibili.com/video/BV14a4y1J7X8/')))
|
||||
self.openGiteePage.clicked.connect(lambda: webbrowser.open(self.tr(r'https://gitee.com/haujet/QuickHand/releases')))
|
||||
self.openGithubPage.clicked.connect(lambda: webbrowser.open(self.tr(r'https://github.com/HaujetZhao/QuickHand/releases')))
|
||||
self.linkToDiscussPage.clicked.connect(lambda: webbrowser.open(
|
||||
self.tr(r'https://qm.qq.com/cgi-bin/qm/qr?k=DgiFh5cclAElnELH4mOxqWUBxReyEVpm&jump_from=webapi')))
|
||||
self.tipButton.clicked.connect(lambda: SponsorDialog(platfm))
|
||||
|
||||
self.masterLayout = QVBoxLayout()
|
||||
self.setLayout(self.masterLayout)
|
||||
self.masterLayout.addWidget(self.openHelpFileButton)
|
||||
self.masterLayout.addWidget(self.openVideoHelpButtone)
|
||||
self.masterLayout.addWidget(self.openGiteePage)
|
||||
self.masterLayout.addWidget(self.openGithubPage)
|
||||
self.masterLayout.addWidget(self.linkToDiscussPage)
|
||||
self.masterLayout.addWidget(self.tipButton)
|
||||
|
||||
def openHelpDocument(self):
|
||||
try:
|
||||
if self.platfm == 'Darwin':
|
||||
import shlex
|
||||
os.system("open " + shlex.quote(self.tr("./misc/README_zh.html")))
|
||||
elif self.platfm == 'Windows':
|
||||
os.startfile(os.path.realpath(self.tr('./misc/README_zh.html')))
|
||||
except:
|
||||
pass
|
||||
18
src/moduels/SponsorDialog.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from PySide2.QtWidgets import *
|
||||
from PySide2.QtGui import *
|
||||
|
||||
class SponsorDialog(QDialog):
|
||||
def __init__(self, platfm, parent=None):
|
||||
super(SponsorDialog, self).__init__(parent)
|
||||
self.resize(784, 890)
|
||||
if platfm == 'Windows':
|
||||
self.setWindowIcon(QIcon('icon.ico'))
|
||||
else:
|
||||
self.setWindowIcon(QIcon('icon.icns'))
|
||||
self.setWindowTitle(self.tr('打赏作者'))
|
||||
self.exec()
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
pixmap = QPixmap('./sponsor.jpg')
|
||||
painter.drawPixmap(self.rect(), pixmap)
|
||||
45
src/moduels/SystemTray.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import sys
|
||||
|
||||
from PySide2.QtCore import *
|
||||
from PySide2.QtGui import *
|
||||
from PySide2.QtWidgets import *
|
||||
|
||||
|
||||
class SystemTray(QSystemTrayIcon):
|
||||
def __init__(self, icon, window):
|
||||
super(SystemTray, self).__init__()
|
||||
self.window = window
|
||||
self.setIcon(icon)
|
||||
self.setParent(window)
|
||||
self.activated.connect(self.trayEvent) # 设置托盘点击事件处理函数
|
||||
self.tray_menu = QMenu(QApplication.desktop()) # 创建菜单
|
||||
self.QuitAction = QAction(self.tr('退出'), self, triggered=self.quit) # 添加一级菜单动作选项(退出程序)
|
||||
self.tray_menu.addAction(self.QuitAction)
|
||||
self.setContextMenu(self.tray_menu) # 设置系统托盘菜单
|
||||
self.show()
|
||||
|
||||
def showWindow(self):
|
||||
self.window.showNormal()
|
||||
self.window.activateWindow()
|
||||
self.window.setWindowFlags(Qt.Window)
|
||||
self.window.show()
|
||||
|
||||
def quit(self):
|
||||
sys.stdout = sys.__stdout__
|
||||
self.hide()
|
||||
qApp.quit()
|
||||
|
||||
def trayEvent(self, reason):
|
||||
# 鼠标点击icon传递的信号会带有一个整形的值,1是表示单击右键,2是双击,3是单击左键,4是用鼠标中键点击
|
||||
if reason == 2 or reason == 3:
|
||||
if self.window.isMinimized() or not self.window.isVisible():
|
||||
# 若是最小化,则先正常显示窗口,再变为活动窗口(暂时显示在最前面)
|
||||
self.window.showNormal()
|
||||
self.window.activateWindow()
|
||||
self.window.setWindowFlags(Qt.Window)
|
||||
self.window.show()
|
||||
else:
|
||||
# 若不是最小化,则最小化
|
||||
self.window.showMinimized()
|
||||
# self.window.setWindowFlags(Qt.SplashScreen)
|
||||
self.window.show()
|
||||
9
src/pyinstaller打包.bat
Normal file
@@ -0,0 +1,9 @@
|
||||
pyinstaller --hidden-import pkg_resources.py2_warn --noconfirm -w -i icon.ico QuickHand.py
|
||||
|
||||
echo d | xcopy /y /s .\dist\rely .\dist\QuickHand
|
||||
|
||||
del /F /Q QuickHand_Win64_pyinstaller.7z
|
||||
|
||||
7z a -t7z QuickHand_Win64_pyinstaller.7z .\dist\QuickHand -mx=9 -ms=200m -mf -mhc -mhcf -mmt -r
|
||||
|
||||
pause
|
||||
13
src/requirements.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
srt
|
||||
keyboard
|
||||
numpy
|
||||
setuptools
|
||||
aliyun-python-sdk-core
|
||||
PyQt5
|
||||
audiotsm
|
||||
scipy
|
||||
cos-python-sdk-v5
|
||||
tencentcloud-sdk-python
|
||||
oss2
|
||||
pyaudio
|
||||
auditok @ git+https://github.com/amsehili/auditok@v0.1.8
|
||||
BIN
src/sponsor.jpg
Normal file
|
After Width: | Height: | Size: 283 KiB |