├── .gitignore ├── LICENSE ├── README.md ├── bgs ├── #000000-1000x1000.png ├── #000000-200x200.png ├── #000000-500x500.png ├── #ff55ff-500x500.png ├── grid.jpeg ├── letter.png ├── my.jpg ├── 村庄信笺纸.jpg └── 树信笺纸.jpg ├── fonts ├── hand.ttf ├── 上首鸿志手写体.ttf ├── 品如手写体.ttf ├── 张维镜手写楷书.ttf ├── 手写大象体.ttf ├── 未知手写体_1.ttf ├── 李国夫董事长手写体.ttf ├── 薛文轩钢笔楷体.ttf └── 青叶手写体.ttf ├── img_1.png ├── img_1_.png ├── img_4.png ├── img_5.png ├── logo.png ├── main.py ├── main.spec └── ui.png /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | test 4 | bgs 5 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 CodeFly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **HandwritingGenerator** 2 | 3 | > HandwritingGenerator 是一个使用 PyQt6 制作的手写文本图片生成器。 4 | > 5 | > 该工具允许用户自定义多种效果,通过在左边配置效果参数,右边实时预览,并在调整好后输出图片。 6 | 7 | ## 效果预览 8 | 9 | 界面预览: 10 | 11 | ![](./ui.png) 12 | 13 | 一封情书: 14 | 15 | ![](./img_1.png) 16 | 17 | ## 特点 18 | 19 | - **实时预览:** 在左侧配置效果参数时,右侧实时显示生成的手写文本图片,方便用户调整和查看效果。 20 | 21 | - **自定义效果:** 用户可以根据需要调整多种效果参数,以生成符合个人喜好或特定场景需求的手写文本图片。 22 | 23 | - **方便易用:** 使用直观的界面设计,使用户能够轻松上手,只需通过拖动滑块即可快速调整生成手写文本图片的效果。 24 | 25 | - **多样化背景:** 提供常见图片的同时,用户可以选择生成各种尺寸纯色背景,以满足不同设计和应用场景的需求。 26 | 27 | - **个性化预设:** 支持将参数保存为预设,避免多次重复的无意义调整参数 28 | 29 | ## 使用方法 30 | 31 | 1. **配置效果参数:** 打开 HandwritingGenerator,使用左侧的配置面板调整手写文本图片的效果参数。 32 | 33 | 2. **实时预览:** 在左侧配置的同时,右侧将实时显示生成的手写文本图片,以便用户可视化调整效果。 34 | 35 | 3. **输出图片:** 调整好效果参数后,点击导出按钮即可将生成的手写文本图片保存到本地。 36 | 37 | ## 如何安装 38 | 39 | ### 下载安装包: 40 | 41 | 就是项目中的rar文件,解压即可使用。 42 | 43 | 下载链接:[https://github.com/w-x-x-w/HandwritingGenerator/releases](https://github.com/w-x-x-w/HandwritingGenerator/releases) 44 | 45 | ### 下载源代码: 46 | 47 | 使用以下步骤在本地运行 HandwritingGenerator: 48 | 49 | ```bash 50 | # 克隆仓库 51 | git clone https://github.com/w-x-x-w/HandwritingGenerator.git 52 | 53 | # 进入项目目录 54 | cd HandwritingGenerator 55 | 56 | # 安装依赖 57 | pip install PyQt6 58 | 59 | # 运行应用 60 | python main.py 61 | ``` 62 | 63 | ## 后续待增加功能 64 | 65 | - 创建渐变色或纹理背景,使用户能够更灵活地定制图片外观。 66 | 67 | - 用户更大自由度选择字体而不是只能选择列表项 68 | -------------------------------------------------------------------------------- /bgs/#000000-1000x1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/#000000-1000x1000.png -------------------------------------------------------------------------------- /bgs/#000000-200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/#000000-200x200.png -------------------------------------------------------------------------------- /bgs/#000000-500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/#000000-500x500.png -------------------------------------------------------------------------------- /bgs/#ff55ff-500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/#ff55ff-500x500.png -------------------------------------------------------------------------------- /bgs/grid.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/grid.jpeg -------------------------------------------------------------------------------- /bgs/letter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/letter.png -------------------------------------------------------------------------------- /bgs/my.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/my.jpg -------------------------------------------------------------------------------- /bgs/村庄信笺纸.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/村庄信笺纸.jpg -------------------------------------------------------------------------------- /bgs/树信笺纸.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/bgs/树信笺纸.jpg -------------------------------------------------------------------------------- /fonts/hand.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/hand.ttf -------------------------------------------------------------------------------- /fonts/上首鸿志手写体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/上首鸿志手写体.ttf -------------------------------------------------------------------------------- /fonts/品如手写体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/品如手写体.ttf -------------------------------------------------------------------------------- /fonts/张维镜手写楷书.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/张维镜手写楷书.ttf -------------------------------------------------------------------------------- /fonts/手写大象体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/手写大象体.ttf -------------------------------------------------------------------------------- /fonts/未知手写体_1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/未知手写体_1.ttf -------------------------------------------------------------------------------- /fonts/李国夫董事长手写体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/李国夫董事长手写体.ttf -------------------------------------------------------------------------------- /fonts/薛文轩钢笔楷体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/薛文轩钢笔楷体.ttf -------------------------------------------------------------------------------- /fonts/青叶手写体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/fonts/青叶手写体.ttf -------------------------------------------------------------------------------- /img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/img_1.png -------------------------------------------------------------------------------- /img_1_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/img_1_.png -------------------------------------------------------------------------------- /img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/img_4.png -------------------------------------------------------------------------------- /img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/img_5.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/logo.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt6.QtWidgets import (QApplication, QGraphicsView, QGraphicsScene,QWidget,QHBoxLayout,QVBoxLayout,QLabel, 3 | QGraphicsPixmapItem, QGraphicsRectItem, QMainWindow,QMessageBox, 4 | QGraphicsTextItem, QPushButton,QInputDialog,QComboBox, 5 | QSlider,QVBoxLayout,QFileDialog,QColorDialog,QTextEdit,QDialog,QLineEdit) 6 | from PyQt6.QtCore import Qt, QRectF 7 | from PyQt6.QtGui import (QGuiApplication,QPixmap, 8 | QPainter, QImage, QFont, 9 | QKeySequence,QShortcut,QFont, 10 | QFontDatabase,QColor,QIcon) 11 | 12 | class ImageCreator(QDialog): 13 | def __init__(self): 14 | super().__init__() 15 | 16 | self.initUI() 17 | self.initLayout() 18 | self.center() 19 | 20 | def initUI(self): 21 | self.setWindowTitle('输出一个纯色背景图片') 22 | self.setGeometry(100,100,300,300) 23 | self.setWindowIcon(QIcon('logo.png')) 24 | 25 | def center(self): 26 | qr=self.frameGeometry() 27 | cp=QGuiApplication.primaryScreen().availableGeometry().center() 28 | qr.moveCenter(cp) 29 | self.move(qr.topLeft()) 30 | 31 | def initLayout(self): 32 | 33 | self.color_label = QLabel('当前颜色:(255,255,255,255)', self) 34 | self.color_button = QPushButton('选择颜色', self) 35 | self.color=QColor('black') 36 | self.color_button.clicked.connect(self.showColorDialog) 37 | color_layout=QHBoxLayout() 38 | color_layout.addStretch(1) 39 | color_layout.addWidget(self.color_label) 40 | color_layout.addWidget(self.color_button) 41 | color_layout.addStretch(1) 42 | 43 | self.bg_a=QLabel('图片不透明度:255') 44 | self.bg_a_slider=QSlider(Qt.Orientation.Horizontal) 45 | self.bg_a_slider.setRange(0, 255) 46 | self.bg_a_slider.setSingleStep(1) 47 | self.bg_a_slider.setValue(255) 48 | bg_a_layout=QHBoxLayout() 49 | bg_a_layout.addStretch(1) 50 | bg_a_layout.addWidget(self.bg_a) 51 | bg_a_layout.addWidget(self.bg_a_slider) 52 | bg_a_layout.addStretch(1) 53 | self.bg_a_slider.valueChanged.connect(self.update_bg_a_label) 54 | 55 | self.color_pre=QLabel('颜色预览:') 56 | self.color_rect=QLabel() 57 | self.color_rect.setFixedSize(50,50) 58 | self.color_rect.setStyleSheet(f'background-color: {self.color.name()}') 59 | color_pre_layout=QHBoxLayout() 60 | color_pre_layout.addStretch(1) 61 | color_pre_layout.addWidget(self.color_pre) 62 | color_pre_layout.addWidget(self.color_rect) 63 | color_pre_layout.addStretch(1) 64 | 65 | self.width_label = QLabel('图片长度:', self) 66 | self.width_input_line = QLineEdit(self) 67 | self.width_input_line.setPlaceholderText('在这里输入整数') 68 | self.width_unit_label = QLabel('像素', self) 69 | width_input_layout = QHBoxLayout() 70 | width_input_layout.addStretch(1) 71 | width_input_layout.addWidget(self.width_label) 72 | width_input_layout.addWidget(self.width_input_line) 73 | width_input_layout.addWidget(self.width_unit_label) 74 | width_input_layout.addStretch(1) 75 | 76 | self.height_label = QLabel('图片宽度:', self) 77 | self.height_input_line = QLineEdit(self) 78 | self.height_input_line.setPlaceholderText('在这里输入整数') 79 | self.height_unit_label = QLabel('像素', self) 80 | height_input_layout = QHBoxLayout() 81 | height_input_layout.addStretch(1) 82 | height_input_layout.addWidget(self.height_label) 83 | height_input_layout.addWidget(self.height_input_line) 84 | height_input_layout.addWidget(self.height_unit_label) 85 | height_input_layout.addStretch(1) 86 | 87 | self.create_button = QPushButton('创建图片', self) 88 | self.create_button.clicked.connect(self.createImage) 89 | 90 | layout = QVBoxLayout() 91 | layout.addLayout(color_layout) 92 | layout.addLayout(bg_a_layout) 93 | layout.addLayout(color_pre_layout) 94 | 95 | layout.addLayout(width_input_layout) 96 | layout.addLayout(height_input_layout) 97 | 98 | layout.addWidget(self.create_button) 99 | 100 | self.setLayout(layout) 101 | 102 | def update_bg_a_label(self): 103 | self.bg_a.setText(f'图片不透明度:{self.bg_a_slider.value()}') 104 | self.color.setAlpha(self.bg_a_slider.value()) 105 | self.color_rect.setStyleSheet(f'background-color: {self.color.name()}') 106 | 107 | def showColorDialog(self): 108 | color = QColorDialog.getColor() 109 | if color.isValid(): 110 | self.color_label.setText(f'当前颜色:{self.color.getRgb()}') 111 | self.color_button.setStyleSheet(f'background-color: {color.name()}') 112 | self.color_rect.setStyleSheet(f'background-color: {color.name()}') 113 | self.color = color 114 | 115 | def createImage(self): 116 | width = int(self.width_input_line.text()) 117 | height = int(self.height_input_line.text()) 118 | if width <= 0 or height <= 0: 119 | return 120 | if not hasattr(self, 'color'): 121 | return 122 | image = QPixmap(width, height) 123 | image.fill(self.color) 124 | image.save(f'./bgs/{self.color.name()}-{width}x{height}.png') 125 | QMessageBox.information(self, '提示', '图片已保存在 bgs 目录下!', QMessageBox.StandardButton.Ok) 126 | 127 | # print(f'图片已创建: {self.color.name()}-{width}x{height}.png') 128 | 129 | class HandFontWindow(QMainWindow): 130 | def __init__(self): 131 | super().__init__() 132 | 133 | self.initUI() 134 | 135 | def initUI(self): 136 | self.setGeometry(100, 100, 1200, 600) 137 | self.setWindowTitle('手写字体图片生成器') 138 | self.setWindowIcon(QIcon('logo.png')) 139 | 140 | self.initView() 141 | self.config_widget=self.initConfigForm() 142 | 143 | # 将配置窗口添加到主窗口的布局中 144 | main_layout = QHBoxLayout() 145 | main_layout.addWidget(self.config_widget) 146 | main_layout.addWidget(self.view) 147 | main_widget = QWidget(self) 148 | main_widget.setLayout(main_layout) 149 | self.setCentralWidget(main_widget) 150 | 151 | # 添加导出图片的快捷键 Ctrl + S 152 | export_shortcut = QShortcut(QKeySequence(QKeySequence.StandardKey.Save), self) 153 | export_shortcut.activated.connect(self.view.exportSceneToImage) 154 | 155 | self.updateView() 156 | 157 | self.show() 158 | 159 | def initView(self): 160 | self.my_scene = QGraphicsScene(self) 161 | self.view = GraphicsView(self.my_scene, self) 162 | self.setCentralWidget(self.view) 163 | 164 | def initConfigForm(self): 165 | # 创建配置参数窗口 166 | config_widget = QWidget(self) 167 | config_layout = QVBoxLayout(config_widget) 168 | # 添加文字区域配置 169 | text_config_label = QLabel('效果配置:') 170 | config_layout.addWidget(text_config_label) 171 | # 选择背景图片 172 | self.label_bg=QLabel('letter.png') 173 | self.button_bg=QPushButton('选择背景图') 174 | self.button_bg.clicked.connect(self.show_file_dialog) 175 | self.bg_path='./bgs/letter.png' 176 | config_layout.addWidget(self.label_bg) 177 | config_layout.addWidget(self.button_bg) 178 | 179 | creat_bg_layout=QHBoxLayout() 180 | creat_bg_label=QLabel('没有喜欢的背景图片?') 181 | creat_bg_button=QPushButton('创建一个纯色背景') 182 | creat_bg_button.clicked.connect(self.create_bg_widget) 183 | creat_bg_layout.addWidget(creat_bg_label) 184 | creat_bg_layout.addWidget(creat_bg_button) 185 | config_layout.addLayout(creat_bg_layout) 186 | 187 | # 选择字体 188 | self.label_font = QLabel("选择字体:") 189 | self.combo_box = QComboBox(self) 190 | self.combo_box.addItem('hand.ttf') 191 | self.combo_box.addItem('李国夫董事长手写体.ttf') 192 | self.combo_box.addItem('品如手写体.ttf') 193 | self.combo_box.addItem('青叶手写体.ttf') 194 | self.combo_box.addItem('上首鸿志手写体.ttf') 195 | self.combo_box.addItem('手写大象体.ttf') 196 | self.combo_box.addItem('未知手写体_1.ttf') 197 | self.combo_box.addItem('薛文轩钢笔楷体.ttf') 198 | self.combo_box.addItem('张维镜手写楷书.ttf') 199 | config_layout.addWidget(self.label_font) 200 | config_layout.addWidget(self.combo_box) 201 | self.combo_box.currentIndexChanged.connect(self.updateView) 202 | # 选择字体颜色 203 | self.label_font_color = QLabel("当前字体颜色:black") 204 | self.button_font_color = QPushButton('选择字体颜色') 205 | self.font_color='black' 206 | config_layout.addWidget(self.label_font_color) 207 | config_layout.addWidget(self.button_font_color) 208 | self.button_font_color.clicked.connect(self.show_color_dialog) 209 | 210 | self.label_text=QLabel('要显示的文字:') 211 | self.text_edit = QTextEdit(self) 212 | self.text_edit.setPlaceholderText('要显示的文字') 213 | self.text_edit.setText('''花费了一天,跟gpt3.5对话进百句,修修改改,最终做成这样的效果。 214 | 我的字迹一直很差,我也很讨厌写字,我花费了很多时间在练字上但我的字迹不好反坏。所以当我看到生成手写字图片的可能时,我便开始了迫不及待地尝试。 215 | 做到一半的时候我突然想到网上有没有已经做出来的。确实有,有点小失望,但又细看了一下发现它的应该操作比我的复杂点,所以我又继续做完就是现在的效果。''') 216 | # text_edit.toPlainText() 217 | self.text_edit.textChanged.connect(self.updateView) 218 | config_layout.addWidget(self.label_text) 219 | config_layout.addWidget(self.text_edit) 220 | 221 | # 坐标信息 222 | # 创建滑块调节数值并为其设置标签显示当前数值 223 | self.slider_x = QSlider(Qt.Orientation.Horizontal) 224 | self.slider_x.setRange(0, 500) 225 | self.slider_x.setSingleStep(1) 226 | self.slider_x.valueChanged.connect(self.updateView) 227 | self.slider_x.setValue(0) 228 | self.label_x = QLabel("文字坐标x:0") 229 | config_layout.addWidget(self.label_x) 230 | config_layout.addWidget(self.slider_x) 231 | 232 | self.slider_y = QSlider(Qt.Orientation.Horizontal) 233 | self.slider_y.setRange(0, 800) 234 | self.slider_y.setSingleStep(1) 235 | self.slider_y.valueChanged.connect(self.updateView) 236 | self.slider_x.setValue(0) 237 | self.label_y = QLabel("文字坐标y:0") 238 | config_layout.addWidget(self.label_y) 239 | config_layout.addWidget(self.slider_y) 240 | 241 | # 文字区域宽度 242 | self.slider_width = QSlider(Qt.Orientation.Horizontal) 243 | self.slider_width.setRange(10, 2000) 244 | self.slider_width.setSingleStep(1) 245 | self.slider_width.setValue(100) 246 | self.slider_width.valueChanged.connect(self.updateView) 247 | self.label_width = QLabel("文字显示宽度:100") 248 | config_layout.addWidget(self.label_width) 249 | config_layout.addWidget(self.slider_width) 250 | 251 | self.slider_height = QSlider(Qt.Orientation.Horizontal) 252 | self.slider_height.setRange(1, 2000) 253 | self.slider_height.setSingleStep(1) 254 | self.slider_height.setValue(200) 255 | self.slider_height.valueChanged.connect(self.updateView) 256 | self.label_height = QLabel("文字显示高度:200") 257 | config_layout.addWidget(self.label_height) 258 | config_layout.addWidget(self.slider_height) 259 | 260 | # 字体大小 261 | self.slider_font_size = QSlider(Qt.Orientation.Horizontal) 262 | self.slider_font_size.setRange(1, 100) 263 | self.slider_font_size.setSingleStep(1) 264 | self.slider_font_size.setValue(16) 265 | self.slider_font_size.valueChanged.connect(self.updateView) 266 | self.label_font_size = QLabel("文字大小:16") 267 | config_layout.addWidget(self.label_font_size) 268 | config_layout.addWidget(self.slider_font_size) 269 | # 字体粗细 270 | self.slider_font_weight = QSlider(Qt.Orientation.Horizontal) 271 | self.slider_font_weight.setRange(100, 1000) 272 | self.slider_font_weight.setSingleStep(100) 273 | self.slider_font_weight.setValue(500) 274 | self.slider_font_weight.valueChanged.connect(self.updateView) 275 | self.label_font_weight = QLabel("文字粗细:500") 276 | config_layout.addWidget(self.label_font_weight) 277 | config_layout.addWidget(self.slider_font_weight) 278 | 279 | # 字体间距 280 | self.slider_font_spacing = QSlider(Qt.Orientation.Horizontal) 281 | self.slider_font_spacing.setRange(-20,20) 282 | self.slider_font_spacing.setSingleStep(1) 283 | self.slider_font_spacing.setValue(2) 284 | self.slider_font_spacing.valueChanged.connect(self.updateView) 285 | self.label_font_spacing = QLabel("字体间距:0.1") 286 | config_layout.addWidget(self.label_font_spacing) 287 | config_layout.addWidget(self.slider_font_spacing) 288 | 289 | # 行间距 290 | self.slider_line_spacing = QSlider(Qt.Orientation.Horizontal) 291 | self.slider_line_spacing.setRange(20,80) 292 | self.slider_line_spacing.setSingleStep(1) 293 | self.slider_line_spacing.setValue(40) 294 | self.slider_line_spacing.valueChanged.connect(self.updateView) 295 | self.label_line_spacing = QLabel("行间距:1") 296 | config_layout.addWidget(self.label_line_spacing) 297 | config_layout.addWidget(self.slider_line_spacing) 298 | 299 | # 添加导出按钮 300 | export_btn = QPushButton('导出图片:', self) 301 | export_btn.clicked.connect(self.view.exportSceneToImage) 302 | config_layout.addWidget(export_btn) 303 | config_widget.setLayout(config_layout) 304 | 305 | return config_widget 306 | 307 | def create_bg_widget(self): 308 | self.dialog=ImageCreator() 309 | self.dialog.show() 310 | 311 | def show_file_dialog(self): 312 | # 创建文件选择框 313 | file_dialog = QFileDialog(self) 314 | file_dialog.setWindowTitle('Open File') 315 | file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile) 316 | 317 | # 显示文件选择框并等待用户选择 318 | if file_dialog.exec() == QFileDialog.DialogCode.Accepted: 319 | # 获取用户选择的文件路径 320 | selected_file = file_dialog.selectedFiles()[0] 321 | self.label_bg.setText(selected_file) 322 | self.bg_path=selected_file 323 | self.updateView() 324 | def show_color_dialog(self): 325 | # 创建颜色选择框 326 | color_dialog = QColorDialog(self) 327 | color_dialog.setWindowTitle('Choose Color') 328 | 329 | # 设置初始颜色(可选) 330 | color_dialog.setCurrentColor(QColor(255, 0, 0)) 331 | 332 | # 显示颜色选择框并等待用户选择 333 | if color_dialog.exec() == QColorDialog.DialogCode.Accepted: 334 | # 获取用户选择的颜色 335 | self.font_color = color_dialog.currentColor().name() 336 | self.label_font_color.setText('当前字体颜色:'+self.font_color) 337 | self.updateView() 338 | 339 | def updateView(self): 340 | current_text = self.combo_box.currentText() 341 | font_path='./fonts/'+current_text 342 | self.label_x.setText(f"文字坐标x:{self.slider_x.value()}") 343 | self.label_y.setText(f"文字坐标y:{self.slider_y.value()}") 344 | self.label_font_size.setText(f"文字大小:{self.slider_font_size.value()}") 345 | self.label_font_weight.setText(f"文字粗细:{self.slider_font_weight.value()}") 346 | self.label_font_color.setText(f"当前字体颜色:{self.font_color}") 347 | self.label_font_spacing.setText(f"文字间隔:{round(self.slider_font_spacing.value()/20,2)}") 348 | self.label_line_spacing.setText(f"行间隔x:{round(self.slider_line_spacing.value()/20,2)}") 349 | self.label_width.setText(f"文字显示宽度:{self.slider_width.value()}") 350 | self.label_height.setText(f"文字显示高度:{self.slider_height.value()}") 351 | 352 | text_tmp=self.text_edit.toPlainText().replace(' ',' ').replace('\n','
') 353 | 354 | self.view.addTextItem(text=text_tmp, 355 | bg_path=self.bg_path, 356 | font_path=font_path, 357 | font_color=self.font_color, 358 | font_weight=self.slider_font_weight.value(), 359 | x=self.slider_x.value(),y=self.slider_y.value(), 360 | font_size=self.slider_font_size.value(), 361 | font_spacing=round(self.slider_font_spacing.value()/20,2), 362 | line_spacing=round(self.slider_line_spacing.value()/40,2), 363 | rect_width=self.slider_width.value(), 364 | rect_height=self.slider_height.value() 365 | ) 366 | 367 | class GraphicsView(QGraphicsView): 368 | def __init__(self, scene, parent): 369 | super().__init__(scene, parent) 370 | 371 | self.setStyleSheet('GraphicsView{background-color:#FDF6E3;}') 372 | self.setRenderHint(QPainter.RenderHint.Antialiasing, True) 373 | self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) # 启用拖拽模式 374 | 375 | self.drawing_rect = None 376 | self.origin = None 377 | self.is_drawing = False 378 | self.coordinate_text_items = {} # 存储坐标信息的字典,以矩形为键 379 | 380 | def wheelEvent(self, event): 381 | modifiers = event.modifiers() 382 | if modifiers == Qt.KeyboardModifier.ControlModifier: 383 | # 只有在按下 Ctrl 键时才缩放 384 | factor = 1.2 # 缩放因子 385 | if event.angleDelta().y() < 0: 386 | factor = 1.0 / factor # 对于负的滚轮事件进行缩小 387 | self.scale(factor, factor) 388 | 389 | def mouseDoubleClickEvent(self, event): 390 | super().mouseDoubleClickEvent(event) 391 | if event.button() == Qt.MouseButton.LeftButton: 392 | # 获取鼠标双击的位置 393 | double_click_position = event.pos() 394 | # 获取双击位置相对于原图的像素位置 395 | double_click_scene_position = self.mapToScene(double_click_position) 396 | # 获取点击位置相对于原图的像素位置 397 | item = self.scene().itemAt(double_click_scene_position, self.transform()) 398 | if isinstance(item, QGraphicsRectItem): 399 | # 删除被点击的矩形的坐标信息 400 | rect_key = id(item) 401 | if rect_key in self.coordinate_text_items: 402 | self.removeCoordinateTextItems(self.coordinate_text_items[rect_key]) 403 | # 删除被点击的矩形 404 | self.scene().removeItem(item) 405 | 406 | def mouseMoveEvent(self, event): 407 | super().mouseMoveEvent(event) 408 | 409 | if event.buttons() & Qt.MouseButton.RightButton: 410 | if self.is_drawing and self.origin is not None: 411 | # 获取鼠标当前位置 412 | current_position = event.pos() 413 | 414 | # 获取当前位置相对于原图的像素位置 415 | current_scene_position = self.mapToScene(current_position) 416 | 417 | # 更新矩形的大小 418 | self.drawing_rect.setRect(QRectF(self.origin, current_scene_position)) 419 | 420 | # 实时显示图像 421 | scene_rect = self.sceneRect() 422 | self.setSceneRect(scene_rect) 423 | 424 | def mouseReleaseEvent(self, event): 425 | super().mouseReleaseEvent(event) 426 | 427 | if event.button() == Qt.MouseButton.RightButton: 428 | if self.is_drawing and self.origin is not None: 429 | self.is_drawing = False 430 | 431 | # 获取鼠标释放的位置 432 | release_position = event.pos() 433 | 434 | # 获取释放位置相对于原图的像素位置 435 | release_scene_position = self.mapToScene(release_position) 436 | 437 | # 更新矩形的大小 438 | self.drawing_rect.setRect(QRectF(self.origin, release_scene_position)) 439 | 440 | # 显示矩形的坐标信息 441 | self.showRectCoordinates(self.drawing_rect) 442 | 443 | def mousePressEvent(self, event): 444 | super().mousePressEvent(event) 445 | 446 | if event.button() == Qt.MouseButton.RightButton: 447 | if not self.is_drawing: 448 | # 获取鼠标点击的位置 449 | click_position = event.pos() 450 | 451 | # 获取点击位置相对于原图的像素位置 452 | scene_position = self.mapToScene(click_position) 453 | self.origin = scene_position 454 | self.is_drawing = True 455 | 456 | # 创建一个新的矩形 457 | self.drawing_rect = QGraphicsRectItem(QRectF(self.origin, self.origin)) 458 | self.scene().addItem(self.drawing_rect) 459 | 460 | def showRectCoordinates(self, rect_item): 461 | # 获取矩形的左上角和右下角相对于图片的坐标 462 | rect_top_left = rect_item.rect().topLeft() 463 | rect_bottom_right = rect_item.rect().bottomRight() 464 | # 将坐标信息显示在矩形的左上角和右下角 465 | # 在矩形的左上角显示坐标信息 466 | text_item_top_left = QGraphicsTextItem(f"({int(rect_top_left.x())}, {int(rect_top_left.y())})") 467 | text_item_top_left.setFont(QFont('Arial', 10)) 468 | text_item_top_left.setDefaultTextColor(Qt.GlobalColor.red) 469 | text_item_top_left.setPos(rect_top_left.x() + 5, rect_top_left.y() - 20) 470 | self.scene().addItem(text_item_top_left) 471 | # 在矩形的右下角显示坐标信息 472 | text_item_bottom_right = QGraphicsTextItem(f"({int(rect_bottom_right.x())}, {int(rect_bottom_right.y())})") 473 | text_item_bottom_right.setFont(QFont('Arial', 10)) 474 | text_item_bottom_right.setDefaultTextColor(Qt.GlobalColor.red) 475 | text_item_bottom_right.setPos(rect_bottom_right.x() - 50, rect_bottom_right.y() + 5) 476 | self.scene().addItem(text_item_bottom_right) 477 | # 将矩形对象作为键,将坐标信息文本项存储到字典中 478 | rect_key = id(rect_item) 479 | self.coordinate_text_items[rect_key] = [text_item_top_left, text_item_bottom_right] 480 | 481 | def removeCoordinateTextItems(self, text_items): 482 | # 从场景中删除坐标信息文本项 483 | for text_item in text_items: 484 | self.scene().removeItem(text_item) 485 | 486 | # 清空字典中的坐标信息 487 | text_items.clear() 488 | def addTextItem(self, text, 489 | bg_path='./bgs/letter.png', 490 | font_path="./fonts/hand.ttf",font_size=12, 491 | font_color='black', 492 | font_weight=600, 493 | x=100,y=100, 494 | font_spacing=0, line_spacing=1.2, 495 | rect_width=500, rect_height=50): 496 | # 清除先前的文本项 497 | self.scene().clear() 498 | #加载背景图片 499 | pixmap = QPixmap(bg_path) 500 | item = QGraphicsPixmapItem(pixmap) 501 | self.scene().addItem(item) 502 | self.scene().setSceneRect(0, 0, 5000, 5000) 503 | # 创建一个QGraphicsTextItem 504 | text_item = QGraphicsTextItem() 505 | text_item.setPos(x, y) 506 | # 加载本地字体文件,设置字体大小 507 | font_id = QFontDatabase.addApplicationFont(font_path) 508 | font_family = QFontDatabase.applicationFontFamilies(font_id)[0] 509 | font = QFont(font_family,font_size) 510 | text_item.setFont(font) 511 | # 使用 HTML 样式设置文本,包括换行和调整行间距、文字间距 512 | html_text = f"
{text}
" 513 | text_item.setHtml(html_text) 514 | # 设置文本项的边界矩形 515 | text_item.setTextInteractionFlags(Qt.TextInteractionFlag.TextEditorInteraction) 516 | text_item.setTextWidth(rect_width) 517 | # 将文本项添加到场景中 518 | self.scene().addItem(text_item) 519 | 520 | def exportSceneToImage(self): 521 | # 获取场景的范围 522 | scene_rect = self.sceneRect() 523 | # 获取场景内容的大小 524 | content_rect = self.scene().itemsBoundingRect() 525 | # 使用场景内容的大小来确定导出图片的大小 526 | image_size = content_rect.size().toSize() 527 | # 弹出输入对话框获取用户输入的文件名,默认为"img_1.png" 528 | file_name, _ = QInputDialog.getText(self, '导出图片', '输入文件名', text='img_1.png') 529 | # 如果用户取消了输入,则返回 530 | if not file_name: 531 | return 532 | # 创建一个 QImage 对象,使用 ARGB32_Premultiplied 格式,支持透明度 533 | image = QImage(image_size, QImage.Format.Format_ARGB32_Premultiplied) 534 | image.fill(Qt.GlobalColor.transparent) # 使用透明背景 535 | # 创建一个QPainter对象 536 | painter = QPainter(image) 537 | # 将场景渲染到QImage中,以内容的大小为主 538 | self.scene().render(painter, QRectF(image.rect()), content_rect) 539 | # 结束绘制 540 | painter.end() 541 | # 保存QImage为图片文件 542 | image.save(file_name) 543 | QMessageBox.information(self, '提示', '图片已保存在当前目录下!', QMessageBox.StandardButton.Ok) 544 | 545 | # def exportSceneToImage(self): 546 | # # 获取场景的范围 547 | # scene_rect = self.sceneRect() 548 | # # 弹出输入对话框获取用户输入的文件名,默认为"img_1.png" 549 | # file_name, _ = QInputDialog.getText(self, '导出图片', '输入文件名', text='img_1.png') 550 | # # 如果用户取消了输入,则返回 551 | # if not file_name: 552 | # return 553 | # # 创建一个QImage对象 554 | # image = QImage(scene_rect.size().toSize(), QImage.Format.Format_ARGB32) 555 | # image.fill(Qt.GlobalColor.white) 556 | # # 创建一个QPainter对象 557 | # painter = QPainter(image) 558 | # # 将场景渲染到QImage中 559 | # self.scene().render(painter, QRectF(image.rect()), scene_rect) 560 | # # 结束绘制 561 | # painter.end() 562 | # # 保存QImage为图片文件 563 | # image.save(file_name) 564 | # QMessageBox.information(self, '提示', '图片已保存在当前目录下!', QMessageBox.StandardButton.Ok) 565 | 566 | 567 | if __name__ == '__main__': 568 | app = QApplication(sys.argv) 569 | viewer = HandFontWindow() 570 | sys.exit(app.exec()) -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | a = Analysis( 5 | ['main.py'], 6 | pathex=[], 7 | binaries=[], 8 | datas=[], 9 | hiddenimports=[], 10 | hookspath=[], 11 | hooksconfig={}, 12 | runtime_hooks=[], 13 | excludes=[], 14 | noarchive=False, 15 | ) 16 | pyz = PYZ(a.pure) 17 | 18 | exe = EXE( 19 | pyz, 20 | a.scripts, 21 | [], 22 | exclude_binaries=True, 23 | name='main', 24 | debug=False, 25 | bootloader_ignore_signals=False, 26 | strip=False, 27 | upx=True, 28 | console=False, 29 | disable_windowed_traceback=False, 30 | argv_emulation=False, 31 | target_arch=None, 32 | codesign_identity=None, 33 | entitlements_file=None, 34 | icon=['logo.png'], 35 | ) 36 | coll = COLLECT( 37 | exe, 38 | a.binaries, 39 | a.datas, 40 | strip=False, 41 | upx=True, 42 | upx_exclude=[], 43 | name='main', 44 | ) 45 | -------------------------------------------------------------------------------- /ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpython666/HandwritingGenerator/8b1c7c6601f2f742f2db02319936b87d4c0976e8/ui.png --------------------------------------------------------------------------------