Skip to content

Commit 364d2b6

Browse files
committed
Add new site support dongmanmanhua.cn
1 parent 56fb039 commit 364d2b6

File tree

13 files changed

+349
-32
lines changed

13 files changed

+349
-32
lines changed

models/const.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
def TRSM(str_name):
1414
return TRS("MainWindow", str_name)
1515

16-
APP_VERSION = "0.9.0"
16+
APP_VERSION = "0.9.1"
1717
APP_LINK = "https://github.com/freedy82/Comic-Toolbox"
1818
APP_LICENSE = "GNU General Public License v3.0"
1919
APP_LICENSE_LINK = "https://github.com/freedy82/Comic-Toolbox/blob/main/LICENSE"

models/controllers/reader_window_controller.py

-21
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ def setup_control(self):
167167
self.ui.actionFitHeight.triggered.connect(lambda: self.change_page_fit(PageFit.HEIGHT))
168168
self.ui.actionFitWidth.triggered.connect(lambda: self.change_page_fit(PageFit.WIDTH))
169169
self.ui.actionFitBoth.triggered.connect(lambda: self.change_page_fit(PageFit.BOTH))
170-
#self.ui.actionFitWidth80.triggered.connect(lambda: self.change_page_fit(PageFit.WIDTH80))
171170
self.ui.actionPageModeSingle.triggered.connect(lambda: self.change_page_mode(PageMode.SINGLE))
172171
self.ui.actionPageModeDouble.triggered.connect(lambda: self.change_page_mode(PageMode.DOUBLE))
173172
self.ui.actionPageModeTriple.triggered.connect(lambda: self.change_page_mode(PageMode.TRIPLE))
@@ -425,16 +424,12 @@ def update_page_fit_button(self):
425424
self.ui.actionFitHeight.setChecked(False)
426425
self.ui.actionFitWidth.setChecked(False)
427426
self.ui.actionFitBoth.setChecked(False)
428-
#self.ui.actionFitWidth80.setChecked(False)
429427
if self.page_fit == PageFit.HEIGHT:
430428
self.ui.actionFitHeight.setChecked(True)
431429
elif self.page_fit == PageFit.WIDTH:
432430
self.ui.actionFitWidth.setChecked(True)
433431
elif self.page_fit == PageFit.BOTH:
434432
self.ui.actionFitBoth.setChecked(True)
435-
elif self.page_fit == PageFit.WIDTH_FREE:
436-
pass
437-
#self.ui.actionFitWidth80.setChecked(True)
438433

439434
def update_page_mode_button(self):
440435
self.ui.actionPageModeSingle.setChecked(False)
@@ -842,14 +837,6 @@ def handle_right_click_from_image(self,source,event:QEvent):
842837
menu_page_gap.setIcon(icon_gap)
843838
menu_page_gap.addActions(self.btn_page_gap.menu().actions())
844839

845-
#tmp_page_gap = int(MY_CONFIG.get("reader", "page_gap"))
846-
#self.create_submenu_action_of_page_gap(menu_page_gap,tmp_page_gap,icon_gap)
847-
#menu_page_gap.addSeparator()
848-
#for tmp_gap in self.PAGE_GAPS:
849-
# if tmp_gap == tmp_page_gap:
850-
# continue
851-
# self.create_submenu_action_of_page_gap(menu_page_gap,tmp_gap,icon_gap)
852-
853840
# background color
854841
bg_color = MY_CONFIG.get("reader","background")
855842
action_change_background = menu_popup.addAction(TRSM("Background color"))
@@ -883,8 +870,6 @@ def recreate_auto_play_menu(self,menu_parent):
883870
q_pause_icon = QtGui.QIcon()
884871
q_pause_icon.addPixmap(QtGui.QPixmap(":/icon/pause"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
885872

886-
#self.ui.actionAutoPlay.setText(TRSM("Start or Pause Auto Play") + " (" + str(self.reader_auto_play_interval/1000) + "s)")
887-
#parent_menu.addAction(self.ui.actionAutoPlay)
888873
tmp_auto_play_interval = float(MY_CONFIG.get("reader", "auto_play_interval"))
889874
if self.auto_play_timer.isActive():
890875
action_pause_tmp = menu_parent.addAction(TRSM("Stop autoplay"))
@@ -951,10 +936,6 @@ def rotate_dialog_finished(self,file,rotate:PageRotate,mode:PageRotateMode):
951936
self.cursor_busy()
952937
self.current_reader.rotate_file(file,rotate,mode)
953938
self.cursor_un_busy()
954-
#if file != "":
955-
# self.update_single_image(file)
956-
#else:
957-
#force re-layout
958939
old_current_image_file = self.current_image_file
959940
self.start_load_current_path_list()
960941
#self.update_images()
@@ -981,6 +962,4 @@ def get_file_from_page_index(self,page_index):
981962
return ""
982963

983964
def process_make_multi_pages_list(self,files):
984-
#read_image_process = ReaderImageProcess()
985-
#read_image_process.set_reader(self.current_reader)
986965
return ReaderImageProcess.process_make_multi_pages_list(files,self.pages_ratio_require,self.current_reader)

models/controllers/translator/Translator.py

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ def __init__(self):
99
def translate_text(self,text,to_lang,from_lang=""):
1010
return text
1111

12+
def translate_multi_text(self,text_list,to_lang,from_lang="auto"):
13+
return text_list
14+
1215
@staticmethod
1316
def find_all_sub_class():
1417
for file in os.listdir(os.path.join(os.path.dirname(__file__), "translator")):

models/controllers/translator/bubble_detect/simple.py

+181-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import cv2
22
import numpy as np
33
import itertools
4+
#import cvxpy
5+
#from shapely.geometry import Polygon
46

57
from models.controllers.translator.BubbleDetect import BubbleDetectEngine
68

@@ -27,7 +29,15 @@ def get_bubble_from_file(self,file_name):
2729
results = []
2830
for box_stat in box_stats:
2931
approx, (y0, y1, x0, x1) = box_stat
30-
results.append([x0,y0,x1-x0,y1-y0])
32+
#print("==========",flush=True)
33+
#print(approx[:,0])
34+
new_x0,new_y0,new_x1,new_y1 = self.get_maximal_rectangle(approx[:,0])
35+
#print([x0,y0,x1-x0,y1-y0])
36+
#print([new_x0,new_y0,new_x1-new_x0,new_y1-new_y0])
37+
#print(rectangle)
38+
39+
#results.append([x0,y0,x1-x0,y1-y0])
40+
results.append([new_x0,new_y0,new_x1-new_x0,new_y1-new_y0])
3141

3242
return results
3343

@@ -100,12 +110,12 @@ def _bubble_contours(image, box_candidates, iom_threshold=0.5, convexify=False):
100110
circle_area_ratio = int(3.14 * circle_radius ** 2 / (cnt_area + 1e-6))
101111
rect_area_ratio = int(w * h / cnt_area)
102112
# This is a speech "bubble" heuristic, it should also work for boxes
103-
# The basic idea is that a bubbles area should approximate that of an enclosing circle
113+
# The basic idea is that a bubble area should approximate that of an enclosing circle
104114
if ((circle_area_ratio <= 2) & (cnt_area > 4000)) or (rect_area_ratio == 1):
105115
if convexify:
106116
approx = cv2.convexHull(approx)
107117
box_stats.append((approx, (y, y + h, x, x + w)))
108-
cv2.fillPoly(draw_mask, [approx], (255, 255, 255))
118+
#cv2.fillPoly(draw_mask, [approx], (255, 255, 255))
109119

110120
# Remove overlapping boxes
111121
coordinates = [pts for _, pts in box_stats]
@@ -149,4 +159,171 @@ def _calculate_iom(bb1, bb2):
149159
iom = intersection_area / float(min(bb1_area, bb2_area) + 1e-6)
150160
bigger_area = max(bb1_area, bb2_area)
151161
bigger_ix = int(bigger_area == bb2_area)
152-
return iom, bigger_ix
162+
return iom, bigger_ix
163+
164+
165+
166+
# @staticmethod
167+
# def get_maximal_rectangle(coordinates):
168+
# """
169+
# Find the largest, inscribed, axis-aligned rectangle.
170+
# :param coordinates:
171+
# A list of of [x, y] pairs describing a closed, convex polygon.
172+
# """
173+
#
174+
# coordinates = np.array(coordinates)
175+
# x_range = np.max(coordinates, axis=0)[0] - np.min(coordinates, axis=0)[0]
176+
# y_range = np.max(coordinates, axis=0)[1] - np.min(coordinates, axis=0)[1]
177+
#
178+
# scale = np.array([x_range, y_range])
179+
# sc_coordinates = coordinates / scale
180+
#
181+
# poly = Polygon(sc_coordinates)
182+
# inside_pt = (poly.representative_point().x,
183+
# poly.representative_point().y)
184+
#
185+
# A1, A2, B = SimpleBubbleDetectEngine.pts_to_leq(sc_coordinates)
186+
#
187+
# bl = cvxpy.Variable(2)
188+
# tr = cvxpy.Variable(2)
189+
# br = cvxpy.Variable(2)
190+
# tl = cvxpy.Variable(2)
191+
# obj = cvxpy.Maximize(cvxpy.log(tr[0] - bl[0]) + cvxpy.log(tr[1] - bl[1]))
192+
# constraints = [bl[0] == tl[0],
193+
# br[0] == tr[0],
194+
# tl[1] == tr[1],
195+
# bl[1] == br[1],
196+
# ]
197+
#
198+
# for i in range(len(B)):
199+
# if inside_pt[0] * A1[i] + inside_pt[1] * A2[i] <= B[i]:
200+
# constraints.append(bl[0] * A1[i] + bl[1] * A2[i] <= B[i])
201+
# constraints.append(tr[0] * A1[i] + tr[1] * A2[i] <= B[i])
202+
# constraints.append(br[0] * A1[i] + br[1] * A2[i] <= B[i])
203+
# constraints.append(tl[0] * A1[i] + tl[1] * A2[i] <= B[i])
204+
#
205+
# else:
206+
# constraints.append(bl[0] * A1[i] + bl[1] * A2[i] >= B[i])
207+
# constraints.append(tr[0] * A1[i] + tr[1] * A2[i] >= B[i])
208+
# constraints.append(br[0] * A1[i] + br[1] * A2[i] >= B[i])
209+
# constraints.append(tl[0] * A1[i] + tl[1] * A2[i] >= B[i])
210+
#
211+
# prob = cvxpy.Problem(obj, constraints)
212+
# prob.solve(solver=cvxpy.CVXOPT, verbose=False, max_iters=1000, reltol=1e-9)
213+
#
214+
# bottom_left = np.array(bl.value).T * scale
215+
# top_right = np.array(tr.value).T * scale
216+
#
217+
# return list(bottom_left[0]), list(top_right[0])
218+
#
219+
# @staticmethod
220+
# def two_pts_to_line(pt1, pt2):
221+
# """
222+
# Create a line from two points in form of
223+
# a1(x) + a2(y) = b
224+
# """
225+
# pt1 = [float(p) for p in pt1]
226+
# pt2 = [float(p) for p in pt2]
227+
# try:
228+
# slp = (pt2[1] - pt1[1]) / (pt2[0] - pt1[0])
229+
# except ZeroDivisionError:
230+
# slp = 1e5 * (pt2[1] - pt1[1])
231+
# a1 = -slp
232+
# a2 = 1.
233+
# b = -slp * pt1[0] + pt1[1]
234+
#
235+
# return a1, a2, b
236+
#
237+
# @staticmethod
238+
# def pts_to_leq(coords):
239+
# """
240+
# Converts a set of points to form Ax = b, but since
241+
# x is of length 2 this is like A1(x1) + A2(x2) = B.
242+
# returns A1, A2, B
243+
# """
244+
#
245+
# A1 = []
246+
# A2 = []
247+
# B = []
248+
# for i in range(len(coords) - 1):
249+
# pt1 = coords[i]
250+
# pt2 = coords[i + 1]
251+
# a1, a2, b = SimpleBubbleDetectEngine.two_pts_to_line(pt1, pt2)
252+
# A1.append(a1)
253+
# A2.append(a2)
254+
# B.append(b)
255+
# return A1, A2, B
256+
257+
# ref from https://stackoverflow.com/questions/21410449/how-do-i-crop-to-largest-interior-bounding-box-in-opencv/21479072#21479072
258+
@staticmethod
259+
def get_maximal_rectangle(contour):
260+
rect = []
261+
262+
for i in range(len(contour)):
263+
x1, y1 = contour[i]
264+
for j in range(len(contour)):
265+
x2, y2 = contour[j]
266+
area = abs(y2 - y1) * abs(x2 - x1)
267+
rect.append(((x1, y1), (x2, y2), area))
268+
269+
# the first rect of all_rect has the biggest area, so it's the best solution if he fits in the picture
270+
all_rect = sorted(rect, key=lambda x: x[2], reverse=True)
271+
272+
# we take the largest rectangle we've got, based on the value of the rectangle area
273+
# only if the border of the rectangle is not in the black part
274+
275+
# if the list is not empty
276+
if all_rect:
277+
278+
best_rect_found = False
279+
index_rect = 0
280+
nb_rect = len(all_rect)
281+
282+
# we check if the rectangle is a good solution
283+
while not best_rect_found and index_rect < nb_rect:
284+
285+
rect = all_rect[index_rect]
286+
(x1, y1) = rect[0]
287+
(x2, y2) = rect[1]
288+
289+
valid_rect = True
290+
291+
# we search a black area in the perimeter of the rectangle (vertical borders)
292+
x = min(x1, x2)
293+
while x < max(x1, x2) + 1 and valid_rect:
294+
#if mask[y1, x] == 0 or mask[y2, x] == 0:
295+
# # if we find a black pixel, that means a part of the rectangle is black
296+
# # so we don't keep this rectangle
297+
# valid_rect = False
298+
x += 1
299+
300+
y = min(y1, y2)
301+
while y < max(y1, y2) + 1 and valid_rect:
302+
#if mask[y, x1] == 0 or mask[y, x2] == 0:
303+
# valid_rect = False
304+
y += 1
305+
306+
if valid_rect:
307+
best_rect_found = True
308+
309+
index_rect += 1
310+
311+
if best_rect_found:
312+
return min(x1,x2),min(y1,y2),max(x1,x2),max(y1,y2)
313+
#return x1, y1, x2, y2
314+
315+
# cv2.rectangle(gray, (x1, y1), (x2, y2), (255, 0, 0), 1)
316+
# cv2.imshow("Is that rectangle ok?", gray)
317+
# cv2.waitKey(0)
318+
#
319+
# # Finally, we crop the picture and store it
320+
# result = input_picture[min(y1, y2):max(y1, y2), min(x1, x2):max(x1, x2)]
321+
#
322+
# cv2.imwrite("Lena_cropped.png", result)
323+
else:
324+
print("No rectangle fitting into the area")
325+
return 0, 0, 0, 0
326+
327+
else:
328+
print("No rectangle found")
329+
return 0, 0, 0, 0

models/controllers/translator/ocr/tesseract.py

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def load_image(self,file_name):
1616
self.original_image = cv2.imdecode(np.fromfile(file_name, dtype=np.uint8), cv2.IMREAD_UNCHANGED)
1717

1818
def ocr_image_with_area_frame(self,area_frame:QRect,lang=""):
19+
#print(f"area_frame:{area_frame}")
1920
img = self.original_image[area_frame.y():area_frame.y()+area_frame.height(), area_frame.x():area_frame.x()+area_frame.width()]
2021
img = self._preprocess_for_ocr(img)
2122
if lang.endswith("_vert"):

models/controllers/translator/translator/google.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,12 @@ def translate_text(self,text,to_lang,from_lang="auto"):
1414
translated_text = translations.text
1515
return translated_text
1616

17-
#todo translate multi text in same time
17+
def translate_multi_text(self,text_list,to_lang,from_lang="auto"):
18+
print(text_list)
19+
translator = Translator()
20+
translations = translator.translate(text_list, dest=to_lang, src=from_lang)
21+
results = []
22+
for translation in translations:
23+
results.append(translation.text)
24+
return results
1825

models/controllers/translator_window_controller.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def on_file_bubble_detect(self):
165165
engine_name = self.ui.cbx_bubble_detect_engine.currentText()
166166
engine = BubbleDetectEngine.init_by_name(engine_name)
167167
results = engine.get_bubble_from_file(self.current_image_file)
168+
#print(results)
168169
for tmp_result in results:
169170
self.current_photo_viewer.add_label("",frame=QRectF(tmp_result[0],tmp_result[1],tmp_result[2],tmp_result[3]))
170171
#print(results)
@@ -205,12 +206,23 @@ def on_file_translate_all_bubble(self):
205206
from_lang = self.LANG_MAP_TO_TRANS[self.LANG_OCR_FROM[self.ui.cbx_language_from.currentIndex()]]
206207
to_lang = self.LANG_MAP_TO_TRANS[self.LANG_TO[self.ui.cbx_language_to.currentIndex()]]
207208

209+
#text_to_translate = []
208210
for tmp_label in self.current_photo_viewer.get_all_labels():
209211
if tmp_label.org_text != "":
212+
#text_to_translate.append(tmp_label.org_text)
210213
trans_text = engine.translate_text(tmp_label.org_text,to_lang,from_lang)
211214
tmp_label.trans_text = trans_text
212215
tmp_label.update_text()
213216

217+
# if len(text_to_translate) > 0:
218+
# translated_text = engine.translate_multi_text(text_to_translate,to_lang,from_lang)
219+
# current_idx = 0
220+
# for tmp_label in self.current_photo_viewer.get_all_labels():
221+
# if tmp_label.org_text != "":
222+
# tmp_label.trans_text = translated_text[current_idx]
223+
# tmp_label.update_text()
224+
# current_idx += 1
225+
214226
def on_file_translate_this_bubble(self):
215227
tmp_label = self.current_photo_viewer.get_current_selected_label()
216228
if tmp_label is not None and self.ui.txt_text.toPlainText() != "":
@@ -426,11 +438,6 @@ def update_item_info(self, item: BoxItem):
426438
self.ui.cbx_alignment.setCurrentIndex(item.align_index)
427439
self.ui.cbx_text_style.setCurrentIndex(item.text_style_index)
428440

429-
def p_image_to_q_pixmap(self,p_image):
430-
q_image = self.p_image_to_q_image(p_image)
431-
q_pixmap = QPixmap.fromImage(q_image)
432-
return q_pixmap
433-
434441
def get_result_image(self):
435442
tmp_writer = Writer()
436443
tmp_writer.load_image_from_file(self.current_image_file)
@@ -449,6 +456,12 @@ def get_result_image(self):
449456
)
450457
return tmp_writer.get_result_image()
451458

459+
@staticmethod
460+
def p_image_to_q_pixmap(p_image):
461+
q_image = TranslatorWindowController.p_image_to_q_image(p_image)
462+
q_pixmap = QPixmap.fromImage(q_image)
463+
return q_pixmap
464+
452465
@staticmethod
453466
def p_image_to_q_image(p_image):
454467
pimg_converted = p_image.convert("RGBA")

0 commit comments

Comments
 (0)