Krita Source Code Documentation
Loading...
Searching...
No Matches
comics_project_page_viewer.py
Go to the documentation of this file.
1"""
2SPDX-FileCopyrightText: 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3SPDX-FileCopyrightText: 2018 Ragnar Brynúlfsson <me@ragnarb.com>
4
5This file is part of the Comics Project Management Tools(CPMT).
6
7SPDX-License-Identifier: GPL-3.0-or-later
8"""
9
10"""
11This is a docker that shows your comic pages.
12"""
13
14import os
15import sys
16import json
17import zipfile
18try:
19 from PyQt6.QtGui import QImage, QPainter, QShortcut
20 from PyQt6.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QPushButton, QSizePolicy
21 from PyQt6.QtCore import QSize, Qt
22
23 # To run standalone
24 from PyQt6.QtWidgets import QApplication
25except:
26 from PyQt5.QtGui import QImage, QPainter
27 from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QPushButton, QSizePolicy, QShortcut
28 from PyQt5.QtCore import QSize, Qt
29
30 # To run standalone
31 from PyQt5.QtWidgets import QApplication
32
33
34class page_viewer(QPushButton):
35
36 def __init__(self, parent=None, flags=None):
37 super(page_viewer, self).__init__(parent)
38 self.alignment = 'left'
39 self.image = QImage()
40 self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
41
42 def align_left(self):
43 self.alignment = 'left'
44
45 def align_right(self):
46 self.alignment = 'right'
47
48 def align_center(self):
49 self.alignment = 'center'
50
51 def set_image(self, image=QImage()):
52 self.image = image
53 self.update()
54
55 def paintEvent(self, event):
56 painter = QPainter(self)
57 previewSize = self.size()*self.devicePixelRatioF()
58 image = ""
59
60 if self.image.width() <= previewSize.width() or self.image.height() <= previewSize.height():
61 # pixel art
62 image = self.image.scaled(previewSize, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation)
63 else:
64 image = self.image.scaled(previewSize, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
65 image.setDevicePixelRatio(self.devicePixelRatioF())
66 if self.alignment == 'right':
67 x_offset = int(self.width() - image.width()/self.devicePixelRatioF())
68 elif self.alignment == 'center':
69 x_offset = int((self.width() - image.width()/self.devicePixelRatioF()) / 2)
70 else:
71 x_offset = 0
72 painter.drawImage(x_offset, 0, image)
73
74 def sizeHint(self):
75 return QSize(256, 256)
76
77
79 pageIndex = 0
80 spread = True
81
82 def __init__(self):
83 super().__init__()
84
85 self.setModal(False)
86 self.setWindowTitle('Untitled')
87 self.setWindowFlags(
88 Qt.WindowType.WindowTitleHint |
89 Qt.WindowType.WindowMinimizeButtonHint |
90 Qt.WindowType.WindowMaximizeButtonHint |
91 Qt.WindowType.WindowCloseButtonHint
92 )
93 self.resize(1024, 768)
94 self.setLayout(QVBoxLayout())
95
97 self.left_viewer.align_right()
98 self.left_viewer.clicked.connect(self.prev_pageprev_page)
100 self.right_viewer.align_left()
101 self.right_viewer.clicked.connect(self.next_pagenext_page)
103 self.single_viewer.align_center()
104 self.single_viewer.clicked.connect(self.next_pagenext_page)
105 self.single_viewer.hide()
106
107 self.page_layout = QHBoxLayout()
108 self.layout().addLayout(self.page_layout)
109 self.page_layout.addWidget(self.left_viewer)
110 self.page_layout.addWidget(self.right_viewer)
111 self.page_layout.addWidget(self.single_viewer)
112
113 # Keyboard shortcuts
114 self.home_shortcut = QShortcut('Home', self)
115 self.home_shortcut.activated.connect(self.first_pagefirst_page)
116 self.left_shortcut = QShortcut('Left', self)
117 self.left_shortcut.activated.connect(self.prev_pageprev_page)
118 self.right_shortcut = QShortcut('Right', self)
119 self.right_shortcut.activated.connect(self.next_pagenext_page)
120 self.space_shortcut = QShortcut('Space', self)
121 self.space_shortcut.activated.connect(self.next_pagenext_page)
122 self.end_shortcut = QShortcut('End', self)
123 self.end_shortcut.activated.connect(self.last_pagelast_page)
124
125 self.first_btn = QPushButton('First')
126 self.first_btn.clicked.connect(self.first_pagefirst_page)
127 self.prev_btn = QPushButton('Previous')
128 self.prev_btn.clicked.connect(self.prev_pageprev_page)
129 self.spread_btn = QPushButton('Single Page')
130 self.spread_btn.clicked.connect(self.toggle_spreadtoggle_spread)
131 self.next_btn = QPushButton('Next')
132 self.next_btn.clicked.connect(self.next_pagenext_page)
133 self.last_btn = QPushButton('Last')
134 self.last_btn.clicked.connect(self.last_pagelast_page)
135
136 self.buttons_layout = QHBoxLayout()
137 self.layout().addLayout(self.buttons_layout)
138 self.buttons_layout.addWidget(self.first_btn)
139 self.buttons_layout.addWidget(self.prev_btn)
140 self.buttons_layout.addWidget(self.spread_btn)
141 self.buttons_layout.addWidget(self.next_btn)
142 self.buttons_layout.addWidget(self.last_btn)
143
144 def load_comic(self, path_to_config):
145 self.path_to_config = path_to_config
146 configFile = open(self.path_to_config, "r", newline="", encoding="utf-16")
147 self.setupDictionary = json.load(configFile)
148 self.projecturl = os.path.dirname(str(self.path_to_config))
149 if 'readingDirection' in self.setupDictionary:
150 if self.setupDictionary['readingDirection'] == "leftToRight":
151 self.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
152 self.left_shortcut.disconnect()
153 self.right_shortcut.disconnect()
154 self.left_shortcut.activated.connect(self.prev_pageprev_page)
155 self.right_shortcut.activated.connect(self.next_pagenext_page)
156 else:
157 self.left_shortcut.disconnect()
158 self.right_shortcut.disconnect()
159 self.setLayoutDirection(Qt.LayoutDirection.RightToLeft)
160 self.left_shortcut.activated.connect(self.next_pagenext_page)
161 self.right_shortcut.activated.connect(self.prev_pageprev_page)
162 else:
163 self.left_shortcut.disconnect()
164 self.right_shortcut.disconnect()
165 self.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
166 self.left_shortcut.activated.connect(self.prev_pageprev_page)
167 self.right_shortcut.activated.connect(self.next_pagenext_page)
168 configFile.close()
169
170 def go_to_page_index(self, index):
171 if index >= 0 and index < len(self.setupDictionary['pages']):
173 else:
174 self.pageIndexpageIndex = 0
175 self.flip_page()
176
177 def toggle_spread(self):
178 if self.spreadspread:
179 self.spreadspread = False
180 self.spread_btn.setText('Double Spread')
182 self.left_viewer.hide()
183 self.right_viewer.hide()
184 self.single_viewer.show()
185 self.flip_page()
186 else:
187 self.spreadspread = True
188 self.spread_btn.setText('Single Page')
189 self.left_viewer.show()
190 self.right_viewer.show()
191 self.single_viewer.hide()
192 self.flip_page()
193
194 def update_single_page(self, index):
195 image = self.get_mergedimage(index)
196 self.single_viewer.set_image(image)
197
198 def update_left_page(self, index):
199 image = self.get_mergedimage(index)
200 self.left_viewer.set_image(image)
201
202 def update_right_page(self, index):
203 image = self.get_mergedimage(index)
204 self.right_viewer.set_image(image)
205
206 def first_page(self):
207 self.pageIndexpageIndex = 0
208 self.flip_page()
209
210 def prev_page(self):
211 if self.pageIndexpageIndex <= 0:
212 self.pageIndexpageIndex = len(self.setupDictionary['pages']) - 1
213 else:
214 if self.spreadspread:
215 self.pageIndexpageIndex -= 2
216 else:
217 self.pageIndexpageIndex -= 1
218 self.flip_page()
219
220 def next_page(self):
221 if self.pageIndexpageIndex >= len(self.setupDictionary['pages']) - 1:
222 self.pageIndexpageIndex = 0
223 else:
224 if self.spreadspread:
225 self.pageIndexpageIndex += 2
226 else:
227 self.pageIndexpageIndex += 1
228 self.flip_page()
229
230 def last_page(self):
231 self.pageIndexpageIndex = len(self.setupDictionary['pages']) - 1
232 self.flip_page()
233
234 def flip_page(self):
235 if self.spreadspread:
236 if self.pageIndexpageIndex % 2 == 0: # Even/Left
237 left_page_number = self.pageIndexpageIndex - 1
238 right_page_number = self.pageIndexpageIndex
239 else: # Odd/Right
240 left_page_number = self.pageIndexpageIndex
241 right_page_number = self.pageIndexpageIndex + 1
242 self.update_left_page(left_page_number)
243 self.update_right_page(right_page_number)
244 if left_page_number < 0:
245 page_numbers = str(right_page_number + 1)
246 elif right_page_number >= len(self.setupDictionary['pages']):
247 page_numbers = str(left_page_number + 1)
248 else:
249 page_numbers = '{left} / {right}'.format(
250 left = str(left_page_number + 1),
251 right = str(right_page_number + 1))
252 self.setWindowTitle('{name} - {page_numbers}'.format(
253 name = self.setupDictionary['projectName'],
254 page_numbers = page_numbers))
255 else:
256 if self.pageIndexpageIndex >= len(self.setupDictionary['pages']):
257 self.pageIndexpageIndex = len(self.setupDictionary['pages']) - 1
258 if self.pageIndexpageIndex < 0:
259 self.pageIndexpageIndex = 0
261 self.setWindowTitle('{name} - {page_numbers}'.format(
262 name = self.setupDictionary['projectName'],
263 page_numbers = str(self.pageIndexpageIndex + 1)))
264
265
266 def get_mergedimage(self, index):
267 if index < len(self.setupDictionary['pages']) and index > -1:
268 image_url = os.path.join(self.projecturl, self.setupDictionary['pages'][index])
269 if os.path.exists(image_url):
270 page = zipfile.ZipFile(image_url, "r")
271 image = QImage.fromData(page.read("mergedimage.png"))
272 page.close()
273 return image
274 image = QImage(QSize(10, 10), QImage.Format.Format_ARGB32)
275 image.fill(Qt.GlobalColor.transparent)
276 return image
277
278if __name__ == '__main__':
279 ''' Run the page viewer outside Krita '''
280 app = QApplication(sys.argv)
281 if sys.argv:
282 print(sys.argv)
283 path_to_config = sys.argv[1]
284 if os.path.exists(path_to_config):
285 print('Run comics viewer')
286 page_viewer_dialog = comics_project_page_viewer()
287 page_viewer_dialog.load_comic(path_to_config)
288 page_viewer_dialog.go_to_page_index(0)
289 page_viewer_dialog.show()
290 else:
291 print('No comic found in {comic}'.format(comic=path_to_config))
292 else:
293 print('Pass the path to a Krita comicConfig.json file to this script, to view the comic.')
294
295 sys.exit(app.exec())
296
297
298