From 4724aef61f55dd35dcb1870f834aa62f766308a0 Mon Sep 17 00:00:00 2001 From: Barry Rowe Date: Sun, 8 Nov 2020 22:23:00 -0800 Subject: [PATCH 1/2] Commit for v1.03 --- ztrans_client/config.py | 47 +- ztrans_client/diff_block_algorithm.py | 144 ++++ ztrans_client/moving_text_algorithm.py | 290 ++++++++ ztrans_client/notes.py | 5 +- ztrans_client/ocr_tools.py | 294 ++++++++ ztrans_client/package_loader.py | 18 +- ztrans_client/package_ocr.py | 17 +- ztrans_client/pipeline_general_service.py | 24 +- ztrans_client/pipeline_service.py | 791 ++++++++++++++++++++++ ztrans_client/pyocr_util.py | 115 ++++ ztrans_client/screen_grab.py | 36 +- ztrans_client/screen_translate.py | 1 - ztrans_client/start_up_win.sh | 5 + ztrans_client/startup.py | 105 ++- ztrans_client/startup_bat.bat | 4 + ztrans_client/text_tools.py | 155 +++++ ztrans_client/util.py | 655 ++++++++++++++++++ 17 files changed, 2623 insertions(+), 83 deletions(-) create mode 100644 ztrans_client/diff_block_algorithm.py create mode 100644 ztrans_client/moving_text_algorithm.py create mode 100644 ztrans_client/ocr_tools.py create mode 100644 ztrans_client/pipeline_service.py create mode 100644 ztrans_client/pyocr_util.py create mode 100644 ztrans_client/start_up_win.sh create mode 100644 ztrans_client/startup_bat.bat create mode 100644 ztrans_client/text_tools.py create mode 100644 ztrans_client/util.py diff --git a/ztrans_client/config.py b/ztrans_client/config.py index 2c232b0..46cf12d 100644 --- a/ztrans_client/config.py +++ b/ztrans_client/config.py @@ -1,5 +1,6 @@ import json +version_string = "1.03" server_host = "barry-mac"#"172.20.10.3" server_port = 8888 user_api_key = "" @@ -16,7 +17,13 @@ keycode_prev = 2 keycode_next = 3 -user_langs = ["Auto", "En", "De", "Fr", "Es", "Ja"] +ascii_capture = '`' +ascii_prev = '1' +ascii_next = '2' + +capture_mode = "fast" + +user_langs = ["En", "De", "Fr", "Es", "Ja"] user_font = "RobotoCondensed-Bold.ttf" ocr_output = ["image"] @@ -38,6 +45,13 @@ def load_init(): global keycode_prev global keycode_next + global capture_mode + + global ascii_capture# = 246 + global ascii_prev# = 49 + global ascii_next# = 50 + + global user_langs global user_font @@ -80,6 +94,13 @@ def load_init(): if "keycode_next" in config_file: keycode_next = config_file['keycode_next'] + if "ascii_capture" in config_file: + ascii_capture = config_file['ascii_capture'] + if "ascii_prev" in config_file: + ascii_prev = config_file['ascii_prev'] + if "ascii_next" in config_file: + ascii_next = config_file['ascii_next'] + if "user_langs" in config_file: user_langs = config_file['user_langs'] if "user_font" in config_file: @@ -88,11 +109,29 @@ def load_init(): if "ocr_output" in config_file: ocr_output = config_file['ocr_output'] + if "capture_mode" in config_file: + capture_mode = config_file['capture_mode'] + print "config loaded" print "====================" #print user_api_key return True +def set_config(key, value): + global ascii_capture + global ascii_prev + global ascii_next + global capture_mode + if key == "ascii_capture": + ascii_capture = value + elif key == "ascii_prev": + ascii_prev = value + elif key == "ascii_next": + ascii_next = value + elif key == "capture_mode": + capture_mode = value + write_init() + def write_init(): obj = {"server_host": server_host, "server_port": server_port, @@ -108,7 +147,11 @@ def write_init(): "keycode_prev": keycode_prev, "keycode_next": keycode_next, "user_langs": user_langs, - "user_font": user_font + "user_font": user_font, + "capture_mode": capture_mode, + "ascii_capture": ascii_capture, + "ascii_prev": ascii_prev, + "ascii_next": ascii_next } config_file = open("./config.json", "w") config_file.write(json.dumps(obj, indent=4)) diff --git a/ztrans_client/diff_block_algorithm.py b/ztrans_client/diff_block_algorithm.py new file mode 100644 index 0000000..32d7c9d --- /dev/null +++ b/ztrans_client/diff_block_algorithm.py @@ -0,0 +1,144 @@ +from PIL import Image, ImageEnhance, ImageChops, ImageDraw, ImageStat +from bson.objectid import ObjectId +import io +import base64 +import time +from util import load_image, get_color_counts, reduce_to_colors, color_hex_to_byte,\ + image_to_string, fix_bounding_box, general_index + +class DiffBlockAlgorithm: + @classmethod + def prep_image(cls, image_data, sharp=4.0):################### + img = load_image(image_data) + enhancer = ImageEnhance.Sharpness(img) + img = enhancer.enhance(sharp) + img = img.convert("P", palette=Image.ADAPTIVE, colors=256) + p = img.getpalette() + return {"image": img, "palette": p} + + @classmethod + def get_text_block(cls, img, p, block, text_colors=None):################ + bb = block['bounding_box'] + if 'x' in bb: + x = bb['x'] + y = bb['y'] + w = bb['w'] + h = bb['h'] + img = img.crop([x,y,x+w,y+h]) + else: + x1 = bb['x1'] + y1 = bb['y1'] + x2 = bb['x2'] + y2 = bb['y2'] + img = img.crop([x1,y1,x2,y2]) + + tcs = list() + + if not text_colors: + text_colors = block.get('text_colors') + if not text_colors: + text_colors = ["190506"] + + #img = reduce_to_colors(img, tcs, + # threshold=block.get("pixel_threshold", 16)) + return img + + + @classmethod + def get_image_diff(cls, test_image, index_image, tb, block, threshold):############ + if test_image.width <=0 or test_image.height<=0: + return 256 + + im = ImageChops.difference(test_image, index_image).convert("RGBA") + if block.get("text_colors"): + im_reduced = reduce_to_colors(im.convert("P", palette=Image.ADAPTIVE), + block.get('text_colors', ["FFFFFF"]), + 16.0).convert("RGB") + + pix_r = ImageStat.Stat(im_reduced).mean + threshold2 = 16.0 + #if pix_r[0]**2+pix_r[1]**2+pix_r[2]**2 > threshold2: + # return 256 + + if im.width <=0 or im.height<=0: + return 256 + pix = ImageStat.Stat(im).mean + + rr = pix[0]**2 + gg = pix[1]**2 + bb = pix[2]**2 + threshold = 16.0 + the_sum = (rr+gg+bb)/(threshold**2) + + if the_sum < 1.0 and pix[3] < 128: + return the_sum/4 + return 256 + + @classmethod + def find_matches(cls, image_data, package):##################### + if type(image_data) == Image.Image: + image_data_string = image_to_string(image_data) + else: + image_data_string = image_data + image_data = load_image(image_data) + + hsv = general_index(image_data_string) + threshold = 32 + + blocks = list() + original_image = image_data + draw = ImageDraw.Draw(original_image) + + prep = cls.prep_image(image_data_string, sharp=4.0) + + white_pixel_count = get_color_counts(image_data.convert("P", palette=Image.ADAPTIVE), + ["FFFFFF"], 16.0) + index = {"h": hsv[0], "s": hsv[1], "v": hsv[2], "pc": white_pixel_count} + + s_time = time.time() + + short_list = cls.find_blocks_by_index(index, package) + short_list.extend(cls.find_blocks_by_index({}, package)) + + for entry in short_list: + test_image = load_image(entry['index_image']) + tb = entry['bounding_box'] + + block = entry + crop = cls.get_text_block(prep['image'], prep['palette'], + block, text_colors=None).convert("RGBA") + + tb2 = {"x1": 0, "y1": 0, "x2": crop.width, "y2": crop.height} + threshold = 16 + + res = cls.get_image_diff(crop, test_image, tb2, entry, threshold) + + if res < 1.0: + #this is a match + blocks.append(entry) + draw.rectangle([tb['x1'], tb['y1'], tb['x2'], tb['y2']], + fill=(0,0,0,0)) + prep = cls.prep_image(image_to_string(original_image), + sharp=4.0) + return {"blocks": blocks, "image": image_to_string(original_image)} + + @classmethod + def find_blocks_by_index(cls, index, package): + pixel_factor = 0.75 + + hsv = list() + for c in 'hsv': + if c in index: + hsv.append(index[c]) + if not hsv: + hsv = None + else: + hsv = tuple(hsv) + + if 'pixel_count' in index: + pc = index['pixel_count'] + else: + pc = None + short_list = package.find_data_by_values("diff", hsv, pc) + return short_list + diff --git a/ztrans_client/moving_text_algorithm.py b/ztrans_client/moving_text_algorithm.py new file mode 100644 index 0000000..19fc86b --- /dev/null +++ b/ztrans_client/moving_text_algorithm.py @@ -0,0 +1,290 @@ +from util import general_index, load_image, fix_bounding_box, image_to_string, chop_to_box +from PIL import Image, ImageDraw, ImageChops +from bson.objectid import ObjectId + +class MovingTextAlgorithm: + @classmethod + def get_text_blocks(cls, image_data, text_colors=("FFFFFF",), + threshold=16, scale_factor=10): + output = list() + img_org = load_image(image_data) + img = img_org.convert("P", palette=Image.ADAPTIVE, colors=256) + p = img.getpalette() + + if not text_colors: + text_colors = ["FFFFFF"] + + tcs = list() + if text_colors: + for text_color in text_colors: + tc = (int(text_color[0:2], 16), + int(text_color[2:4], 16), + int(text_color[4:6], 16)) + tcs.append(tc) + + new_palette = list() + for i in range(256): + r = p[3*i] + g = p[3*i+1] + b = p[3*i+2] + vals = list() + for tc in tcs: + rr = r-tc[0] + gg = g-tc[1] + bb = b-tc[2] + vals.append(rr**2+gg**2+bb**2) + + if min(vals) <= threshold**2: + new_palette.extend([255,255,255]) + else: + new_palette.extend([0,0,0]) + img.putpalette(new_palette) + + width = img.width + height = img.height + bw = width/scale_factor + bh = height/scale_factor + t=img.convert("RGBA").resize((bw,bh), resample=Image.BILINEAR) + + checked = [[0 for y in range(bh)] for x in range(bw)] + for i in range(bw): + for j in range(bh): + if t.getpixel((i,j)) != (0,0,0,255) and checked[i][j] == 0: + s_pix = t.getpixel((i,j))[0] + min_x, min_y = i,j + max_x, max_y = i+1,j+1 + if j > 0 and i< bw-1 and t.getpixel((i+1, j-1)) != (0,0,0,255): + #in the case of rounded corner, the above loop could + #miss the first pixel line + min_y = j-1 + for k in range(i+1,bw): + if t.getpixel((k,j)) == (0,0,0,255) and\ + (k >= bw-1 or t.getpixel((k+1, j)) == (0,0,0,255)): + max_x = min(bw, k+1) + break + for l in range(j+1, bh): + if t.getpixel((k-1,l)) == (0,0,0,255) and\ + (l >= bh-1 or t.getpixel((k-1, l+1)) == (0,0,0,255)): + max_y = l+1 + break + if max_y>j+1: + for k2 in range(max_x, bw): + if t.getpixel((k2,l)) != (0,0,0,255) or \ + (l>0 and t.getpixel((k2, l-1))): + max_x = k2+1 + break + + #box to check: min_x, min_y, max_x, max_y + x1,y1,x2,y2= min_x*scale_factor, min_y*scale_factor,\ + max_x*scale_factor, max_y*scale_factor + #x1,y1,x2,y2 = [0,0,100,100] + f=img.crop((x1,y1,x2,y2)).convert("RGBA") + #crop again by min_x and min_y of white values, + # max_x, max_y of white values + f_width = f.width + f_height = f.height + min_width = 0 + min_height = 0 + max_width = 1 + max_height = 1 + for l in range(f_height): + for k in range(f_width): + if f.getpixel((k,l)) == (255,255,255,255): + min_height = l + break + else: + continue + break + for k in range(f_width): + for l in range(f_height): + if f.getpixel((k,l)) == (255,255,255,255): + min_width = k + break + else: + continue + break + for l in range(1,f_height): + for k in range(1,f_width): + if f.getpixel((f_width-k,f_height-l)) == (255,255,255,255): + max_height = f_height-l+1 + break + else: + continue + break + for k in range(1,f_width): + for l in range(1,f_height): + if f.getpixel((f_width-k,f_height-l)) == (255,255,255,255): + max_width = f_width-k+1 + break + else: + continue + break + + g = f.crop((min_width,min_height, max_width, max_height)) + + bounding_box = {"x1": min_width+x1, "y1": min_height+y1, + "x2": min_width+x1+g.width, "y2": min_height+y1+g.height} + pixel_count = 1 + for c in g.getcolors(): + if c[1] == (255,255,255,255): + pixel_count = c[0] + output.append([g, {"bounding_box": bounding_box, + "pixel_count": pixel_count}]) + + for k in range(min_x, max_x): + for l in range(min_y, max_y): + checked[k][l] = 1 + return output + + @classmethod + def find_blocks_by_index(cls, index, package): + pixel_factor = 0.75 + + hsv = list() + for c in 'hsv': + if c in index: + hsv.append(index[c]) + + if not hsv: + hsv = None + else: + hsv = tuple(hsv) + + if 'pixel_count' in index: + pc = index['pixel_count'] + else: + pc = None + short_list = package.find_data_by_values("mov", hsv, pc) + return short_list + + @classmethod + def get_center(cls, image, color): + n = 0 + avg_x = 0 + avg_y = 0 + threshold = 16.0 + for i, pixel in enumerate(image.getdata()): + x = i%image.width + y = int(i/image.width) + if (pixel[0]-color[0])**2+(pixel[1]-color[1])**2+\ + (pixel[2]-color[2])**2 < threshold**2: + avg_x += x + avg_y += y + n+=1 + if n == 0: + return [image.width/2, image.height/2] + return [avg_x/n, avg_y/n] + + @classmethod + def chop_difference(cls, tb, mi, color, threshold): + tb_c = cls.get_center(tb, color) + mi_c = cls.get_center(mi, color) + + #find the required size of the new embedding image: + tb_c_w = max(tb.width-tb_c[0], tb_c[0])*2 + tb_c_h = max(tb.height-tb_c[1], tb_c[1])*2 + + mi_c_w = max(mi.width-mi_c[0], mi_c[0])*2 + mi_c_h = max(mi.height-mi_c[1], mi_c[1])*2 + + new_w = max(tb_c_w, mi_c_w)+2 + new_h = max(tb_c_h, mi_c_h)+2 + + min_sum = 1000000000000000 + offs = [[0,0], [1,0], [-1, 0], [0, -1], [0, 1], + [-1, -1], [-1, 1], [1, -1], [1,1]] + #offs = [[0,0]] + for off in offs: + new_mi = Image.new("RGBA", (new_w, new_h), color=(0,0,0,255)) + new_tb = Image.new("RGBA", (new_w, new_h), color=(0,0,0,255)) + + #center the original images into this image: + mi_px = new_w/2 - mi_c[0] + mi_py = new_h/2 - mi_c[1] + new_mi.paste(mi, (mi_px+off[0], mi_py+off[1])) + + tb_px = new_w/2 - tb_c[0] + tb_py = new_h/2 - tb_c[1] + new_tb.paste(tb, (tb_px, tb_py)) + new_tb1 = new_tb.convert("RGB").resize((new_w/2, new_h/2), Image.BILINEAR) + new_mi1 = new_mi.convert("RGB").resize((new_w/2, new_h/2), Image.BILINEAR) + + im = ImageChops.difference(new_tb1, new_mi1).convert("RGBA") + + pix = im.convert("RGB").resize((1,1), Image.BILINEAR) + pix = pix.getpixel((0,0)) + #print pix + rr = pix[0]**2 + gg = pix[1]**2 + bb = pix[2]**2 + the_sum = (rr+gg+bb)/(threshold**2) + if the_sum < min_sum: + min_sum = the_sum + return min_sum + + + @classmethod + def test_image_text_via_index(cls, index, image_object): + mi = image_object + tb = load_image(index['index_image']) + threshold = 16 + + if mi.width <=0 or mi.height<=0: + return False + + the_sum = cls.chop_difference(tb, mi, (255,255,255,255), threshold) + if the_sum >= 1.0 and tb.height < mi.height: + the_sum = cls.chop_difference(tb, mi.crop((0,0, tb.width, tb.height)), + (255,255,255,255), threshold) + if the_sum >= 1.0 and tb.height < mi.height: + the_sum = cls.chop_difference(tb, mi.crop((0, mi.height-tb.height, tb.width, mi.height)), + (255,255,255,255), threshold) + + if the_sum < 1.0:# and pix[3] > 128: + return {"block_reference": index['_id'], "conf": 1.0-the_sum} + return False + + + @classmethod + def find_matches(cls, image_data, package): + hsv = general_index(image_data) + threshold = 32 + text_colors = cls.get_index_text_colors(package) + text_blocks = cls.get_text_blocks(image_data, text_colors, threshold) + blocks = list() + original_image = load_image(image_data) + draw = ImageDraw.Draw(original_image) + + found_block_ids = dict() + for text_image, text_data in text_blocks: + index = {"h": hsv[0], "s": hsv[1], "v": hsv[2]} + index['type'] = 'hsv_mov' + index['pixel_count'] = text_data['pixel_count'] + short_list = cls.find_blocks_by_index(index, package) + + for index_entry in short_list: + is_match = cls.test_image_text_via_index(index_entry, + text_image) + + if is_match: + index_entry['bounding_box'] = text_data['bounding_box'] + blocks.append(index_entry) + bb = text_data['bounding_box'] + draw.rectangle([bb['x1'], bb['y1'], bb['x2'], bb['y2']], + fill=(0,0,0,0)) + break + return {"blocks": blocks, "image": image_to_string(original_image)} + + @classmethod + def get_index_text_colors(cls, package): + doc = package.get_meta_data("moving_text_colors.json")[0] + return doc['text_colors'].keys() + + +def main(): + image_mi = Image.open("test_mi.png") + image_tb = Image.open("test_tb.png") + MovingTextAlgorithm.chop_difference(image_tb, image_mi, (255,255,255,255), 16) + +if __name__=="__main__": + main() diff --git a/ztrans_client/notes.py b/ztrans_client/notes.py index c60dd05..6e09185 100644 --- a/ztrans_client/notes.py +++ b/ztrans_client/notes.py @@ -1,10 +1,9 @@ """ -!DONE!?-add in more language support (eg: PTBR) +-add in more language support (eg: PTBR) -fix image clipping bug -increase speed of upload -add in warning about using auto-mode without a key. --merge in vgtranslate code. --test resize option to pipeline + """ diff --git a/ztrans_client/ocr_tools.py b/ztrans_client/ocr_tools.py new file mode 100644 index 0000000..ee2103d --- /dev/null +++ b/ztrans_client/ocr_tools.py @@ -0,0 +1,294 @@ +from PIL import Image +import base64 +import time +import io +import sys +import os +import shlex +import subprocess +from util import get_color_counts_simple + +if os.name == "nt": + import pyocr_util +else: + import pytesseract + + +lang_to_tesseract_lang = { + "deu": "deu", + "eng": "eng" +} + + +def setup_pytesseract(lang="eng"): + if lang is None: + lang = "eng" + + if os.name == "nt": + pyocr_util.load_tesseract_dll(lang=lang) + else: + #linux here + pytesseract.pytesseract.tesseract_cmd = r'tesseract' + + +def tess_helper(image, lang=None, mode=None,min_pixels=1): + if os.name == "nt": + return tess_helper_windows(image, lang, mode, min_pixels) + else: + return tess_helper_linux(image, lang, mode, min_pixels) + + +def tess_helper_windows(image, lang=None, mode=None,min_pixels=1): + setup_pytesseract(lang) + + if mode is None: + mode = 6 + + t_ = time.time() + pc = get_color_counts_simple(image, ["FFFFFF"], 2) + + if min_pixels and pc < min_pixels: + return list() + x = pyocr_util.image_to_boxes(image, lang=lang, mode=mode) + print ['ocr time', time.time()-t_] + + found_chars = list() + for word_box in x: + word = word_box.content + box = word_box.position + + l = len(word) + w = box[1][0] - box[0][0] + + for i, char in enumerate(word): + found_chars.append([char, [box[0][0]+((i)*w)/l, box[0][1], + box[0][0]+((i+1)*w)/l, box[1][1]]]) + + data = list() + curr_pos = None + curr_box = None + found_lines = list() + curr_line = "" + char_h = 0 + char_w = 0 + if found_chars: + for entry in found_chars: + char = entry[0] + coords = entry[1] + + this_char_h = coords[3]-coords[1] + this_char_w = coords[2]-coords[0] + char_h = max(char_h, this_char_h) + char_w = max(char_w, this_char_w) + + if curr_pos is None: + curr_pos = coords + curr_box = coords + curr_line = char + else: + if coords[0] > last_x-char_w and\ + curr_box[0]-char_w/2 < coords[0] < curr_box[2] + (char_w)*3 and\ + curr_box[0]-char_w/2 < coords[2] < curr_box[2] + (char_w)*4 and\ + curr_box[1]-char_h < coords[1] < curr_box[3] + (char_h) and\ + curr_box[1]-char_h < coords[3] < curr_box[3]+char_h: + #another character to add: + if curr_box[0] > coords[0]: + curr_box[0] = coords[0] + if curr_box[1] > coords[1]: + curr_box[1] = coords[1] + if curr_box[2] < coords[2]: + curr_box[2] = coords[2] + if curr_box[3] < coords[3]: + curr_box[3] = coords[3] + + curr_pos = coords + curr_line+=char + else: + found_lines.append([curr_line, curr_box]) + curr_pos = coords + curr_box = coords + curr_line = char + char_h = 0 + char_w = 0 + last_x = coords[0] + if curr_line: + found_lines.append([curr_line, curr_box]) + #for c in found_lines: + # print c + return found_lines + + +def tess_helper_linux(image, lang=None, mode=None, min_pixels=1): + setup_pytesseract() + + pc = get_color_counts_simple(image, ["FFFFFF"], 2) + + if min_pixels and pc < min_pixels: + return list() + + config_arg = "" + if mode is not None: + config_arg += " -psm "+str(mode) + + if not config_arg: + config_arg = "" + + for i in range(2): + try: + if lang: + lang = lang_to_tesseract_lang[lang] + x = pytesseract.image_to_boxes(image, lang=lang, config=config_arg) + else: + x = pytesseract.image_to_boxes(image, config=config_arg) + break + except Exception as e: + if type(e) == KeyboardInterrupt: + raise + + if i == 1: + raise + print 'tttttttttttttttttttttttt' + setup_pytesseract() + + + h = image.height + data = list() + curr_pos = None + curr_box = None + found_lines = list() + curr_line = "" + char_h = 0 + char_w = 0 + last_x = None + if x.strip(): + for line in x.split("\n"): + split = line.split(" ") + char = split[0] + coords = [int(i) for i in split[1:5]] + + coords = [coords[0], coords[3], coords[2], coords[1]] + coords[1] = h-coords[1] + coords[3] = h-coords[3] + + this_char_h = coords[3]-coords[1] + this_char_w = coords[2]-coords[0] + char_h = max(char_h, this_char_h) + char_w = max(char_w, this_char_w) + + if curr_pos is None: + curr_pos = coords + curr_box = coords + curr_line = char + else: + if coords[0] > last_x-char_w and\ + curr_box[0]-char_w/2 < coords[0] < curr_box[2] + (char_w)*3 and\ + curr_box[0]-char_w/2 < coords[2] < curr_box[2] + (char_w)*4 and\ + curr_box[1]-char_h < coords[1] < curr_box[3] + (char_h) and\ + curr_box[1]-char_h < coords[3] < curr_box[3]+char_h: + #another character to add: + if curr_box[0] > coords[0]: + curr_box[0] = coords[0] + if curr_box[1] > coords[1]: + curr_box[1] = coords[1] + if curr_box[2] < coords[2]: + curr_box[2] = coords[2] + if curr_box[3] < coords[3]: + curr_box[3] = coords[3] + + curr_pos = coords + curr_line+=char + else: + found_lines.append([curr_line, curr_box]) + curr_pos = coords + curr_box = coords + curr_line = char + char_h = 0 + char_w = 0 + last_x = coords[0] + + if curr_line: + found_lines.append([curr_line, curr_box]) + for l in found_lines: + print l + return found_lines + + +def tess_helper_server(image, lang=None, mode=None): + setup_pytesseract() + + config_arg = "" + if mode is not None: + config_arg += " -psm "+str(mode) + + if not config_arg: + config_arg = "" + + if lang: + lang = lang_to_tesseract_lang[lang] + x = pytesseract.image_to_boxes(image, lang=lang, config=config_arg) + else: + x = pytesseract.image_to_boxes(image, config=config_arg) + h = image.height + data = list() + curr_pos = None + curr_box = None + found_lines = list() + curr_line = "" + char_h = 0 + char_w = 0 + last_x = None + if x.strip(): + for line in x.split("\n"): + split = line.split(" ") + char = split[0] + coords = [int(i) for i in split[1:5]] + coords = [coords[0], coords[3], coords[2], coords[1]] + coords[1] = h-coords[1] + coords[3] = h-coords[3] + + this_char_h = coords[3]-coords[1] + this_char_w = coords[2]-coords[0] + char_h = max(char_h, this_char_h) + char_w = max(char_w, this_char_w) + if curr_pos is None: + curr_pos = coords + curr_box = coords + curr_line = char + else: + + if coords[0] > last_x-char_w and\ + curr_box[0]-char_w/2 < coords[0] < curr_box[2] + (char_w)*3 and\ + curr_box[0]-char_w/2 < coords[2] < curr_box[2] + (char_w)*4 and\ + curr_box[1]-char_h < coords[1] < curr_box[3] + (char_h) and\ + curr_box[1]-char_h < coords[3] < curr_box[3]+char_h: + #another character to add: + if curr_box[0] > coords[0]: + curr_box[0] = coords[0] + if curr_box[1] > coords[1]: + curr_box[1] = coords[1] + if curr_box[2] < coords[2]: + curr_box[2] = coords[2] + if curr_box[3] < coords[3]: + curr_box[3] = coords[3] + + curr_pos = coords + curr_line+=char + else: + found_lines.append([curr_line, curr_box]) + curr_pos = coords + curr_box = coords + curr_line = char + char_h = 0 + char_w = 0 + last_x = coords[0] + + if curr_line: + found_lines.append([curr_line, curr_box]) + return found_lines + +def main(): + image= Image.open("a.png") + tess_helper(image, lang="deu", mode=6, min_pixels=2) + +if __name__=='__main__': + main() diff --git a/ztrans_client/package_loader.py b/ztrans_client/package_loader.py index 920639a..ffa1189 100644 --- a/ztrans_client/package_loader.py +++ b/ztrans_client/package_loader.py @@ -5,16 +5,15 @@ import time import copy from bson import json_util -from ztrans_common.file_package import FilePackageDirectDAO -class PackageObject(FilePackageDirectDAO): +class PackageObject: def __init__(self, filename=None, file_or_string=None): - try: - super(PackageObject, self).__init__("", "", font="", filename=filename) - + try: #on windows, zipfile only seems to work for filename inputs and not file objects - return - """ + if filename: + the_package_file = filename + else: + pass self.the_package_zip = zipfile.ZipFile(the_package_file, "r") self.info = json.loads(self.the_package_zip.read("info.json")) self.zip_name_list = self.the_package_zip.namelist() @@ -43,13 +42,12 @@ def __init__(self, filename=None, file_or_string=None): ff.close() self.variables = dict() - """ except: import traceback traceback.print_exc() raise - """ + def is_index_disabled(self, name): return self.index_is_disabled.get(name, False) @@ -201,7 +199,7 @@ def recur_checker(i, index): else: results = list() return results - """ + #rewrite this function def find_data_by_values(self, algo, hsv, pc): output = list() diff --git a/ztrans_client/package_ocr.py b/ztrans_client/package_ocr.py index 7f08610..2da7527 100644 --- a/ztrans_client/package_ocr.py +++ b/ztrans_client/package_ocr.py @@ -1,13 +1,17 @@ import time import json from PIL import Image, ImageDraw + +from diff_block_algorithm import DiffBlockAlgorithm +from moving_text_algorithm import MovingTextAlgorithm +from util import load_image from pipeline_general_service import PipelineGeneralService -from ztrans_common import text_draw -from ztrans_common.image_util import load_image +import imaging running_textboxes = list() + class PackageOCR: @classmethod def call_ocr(cls, image_data, target_lang, package): @@ -42,7 +46,6 @@ def parse_commands(cls, special_json, block, package): if cls.check_requirement(req, package): for command in commands: cls.run_command(command, package) - @classmethod def check_requirement(cls, req, package): for cond in req: @@ -62,6 +65,7 @@ def check_requirement(cls, req, package): elif subkey == "equal": if len(running_textboxes) != cond[key][subkey]: return False + return True @classmethod @@ -125,10 +129,9 @@ def process_textboxes(cls, image, target_lang, package): if textbox['start'] <= c_time <= textbox['end']: bb = textbox['bounding_box'] #print textbox - text_draw.drawTextBox(draw, textbox['text'].get(target_lang), - bb[0],bb[1], bb[2], bb[3], - font=None, confid=1, - exact_font=textbox.get("font_size", 8)) + imaging.drawTextBox(draw, textbox['text'].get(target_lang), + bb[0],bb[1], bb[2], bb[3], + font=None, confid=1, exact_font=textbox.get("font_size", 8)) req = textbox.get("req", list()) if time.time() <= textbox['end'] and cls.check_requirement(req, package): new_textboxes.append(textbox) diff --git a/ztrans_client/pipeline_general_service.py b/ztrans_client/pipeline_general_service.py index 4e6bb8a..0ec52e5 100644 --- a/ztrans_client/pipeline_general_service.py +++ b/ztrans_client/pipeline_general_service.py @@ -1,7 +1,6 @@ import time from PIL import Image -from ztrans_common.pipeline_service import OCRPipeline -from ztrans_common.file_package import FilePackageDirectDAO +from pipeline_service import OCRPipeline class PipelineGeneralService: @classmethod @@ -10,6 +9,7 @@ def run_ocr_pipeline(cls, image, package, target_lang): indexes = package.get_game_indexes() the_image = image + out_data = list() t = time.time() for index in indexes: @@ -34,21 +34,13 @@ def run_ocr_pipeline(cls, image, package, target_lang): def main(): - user_id = "" - game_id = "" - font = "./fonts/Roboto-Bold.ttf" - - package = FilePackageDirectDAO(user_id, game_id, font, "/home/barry/Downloads/bbb.ztp") - image1 = Image.open("/home/barry/Downloads/silber_test1.png") - image2 = Image.open("/home/barry/Downloads/silber_test2.png") - - out, data = PipelineGeneralService.run_ocr_pipeline(image1, package, "en") - import pdb - pdb.set_trace() - out, data = PipelineGeneralService.run_ocr_pipeline(image2, package, "en") + import package_loader + package = package_loader.PackageObject("./packages/hw.1.01.ztp") + image = Image.open("d.png") + out,data = PipelineGeneralService.run_ocr_pipeline(image, package, "en") import pdb pdb.set_trace() - + if __name__=='__main__': - main() + main() \ No newline at end of file diff --git a/ztrans_client/pipeline_service.py b/ztrans_client/pipeline_service.py new file mode 100644 index 0000000..1c01723 --- /dev/null +++ b/ztrans_client/pipeline_service.py @@ -0,0 +1,791 @@ +import colorsys +import copy +import time +from PIL import Image, ImageEnhance, ImageChops, ImageDraw, ImageStat + +from bson.objectid import ObjectId +import imaging +from imaging import drawTextBox +import ocr_tools +from util import load_image, fix_bounding_box,\ + image_to_string, chop_to_box,\ + get_color_counts,\ + get_color_counts_simple,\ + intersect_area,\ + get_bounding_box_area,\ + convert_to_absolute_box, reduce_to_colors,\ + reduce_to_multi_color, segfill,\ + get_best_text_color, tint_image,\ + expand_horizontal, expand_vertical,\ + draw_solid_box +from moving_text_algorithm import MovingTextAlgorithm +from text_tools import levenshtein, levenshtein_shortcircuit,\ + substitute_text,levenshtein_shortcircuit_dist + + +HMAX = 16 + +def f_ord1(text): + return sum((ord(c)*12345701)%255 for c in text) + +def f_ord2(text): + return sum((ord(c)*12359911)%255 for c in text) + +def f_ord3(text): + vals = list() + for i,c in enumerate(text): + val = ord(c)*(i%16)+ord(c) + val = (val*123588163)%(255*16) + vals.append(val) + print vals + return sum(vals) + +def get_miss(miss_array, text): + l = len(text) + miss_var = miss_array[0] + #print miss_array, 'cccccc', len(text) + for i, val in enumerate(miss_array): + if val <= l: + miss_var = i+1 + return miss_var + + +class OCRPipeline: + @classmethod + def process_pipeline(cls, pipeline, image, metadata, short_list, + target_lang=None, user_id=None, game_id=None, + index_name=None, package=None): + org_image = image.copy() + data_in = [{"image": image, "meta": metadata, + "short_list": short_list}] + + t_time = time.time() + for stage in pipeline: + new_data_in = list() + for data in data_in: + new_data = cls.process_stage(stage, data, user_id=user_id, + game_id=game_id, index_name=index_name, + package=package) + if new_data: + new_data_in.extend(new_data) + data_in = new_data_in + #add in the textboxes over this now... + if target_lang != None: + return cls.do_block_post_processing(org_image, data_in, target_lang) + else: + #this is the same as the above, but here to emphasize the + #difference that target_lang=None doesa + return cls.do_block_post_processing(org_image, data_in, None) + + @classmethod + def do_block_post_processing(cls, org_image, data, target_lang): + #might have to do more resolution on what happens when there are + #multiple matches on a block of text + org_org_image = org_image.copy() + draw = ImageDraw.Draw(org_image) + new_data = list() + + box_non_overlap = list() + for entry in data: + #go through and select the best matches for boxes that are overlapping..." + if "l_dist" in entry and entry.get("match"): + bb = entry['block']['bounding_box'] + area = intersect_area(bb,bb) + for k in box_non_overlap: + if k.get('match'): + bb2 = k['block']['bounding_box'] + area2 = intersect_area(bb2,bb2) + if intersect_area(bb,bb2) > 0.75*min(area,area2): + #these boxes are overlapping, so get rid of one of them. + break + else: + box_non_overlap.append(entry) + continue + #so entry and k are overlapping... figure out which one to take + #based on their levenshtein distances + if entry['l_dist'] < k['l_dist']: + #k (old one) is worse, so make it a non-match and append the new one + #to box_non_overlap + k['match'] = False + box_non_overlap.append(entry) + else: + #the new one (entry) is worse, so mark it as a non-match and leave it + entry['match'] = False + + for entry in data: + if entry.get("match") == True: + if target_lang != None and entry['block']['source_text'] and\ + entry['block']['translation'][target_lang]: + tinge = None + if entry.get("meta", {}).get("text_colors"): + text_colors = entry['meta']['text_colors'] + tc_block = org_org_image.crop([entry['block']['bounding_box']['x1'], + entry['block']['bounding_box']['y1'], + entry['block']['bounding_box']['x2'], + entry['block']['bounding_box']['y2']]) + + tinge = get_best_text_color(tc_block, text_colors, threshold=32) + if tinge == "FFFFFF": + tinge = None + + if target_lang in entry['block'].get("drawn_text", {}): + offset = [entry['block']['bounding_box']['x1'], + entry['block']['bounding_box']['y1'], + ] + i_img = entry['block']['drawn_text'][target_lang] + i_img = load_image(i_img) + if tinge: + i_img = tint_image(i_img, tinge) + + org_image.paste(i_img, offset) + else: + tb = entry['block']['bounding_box'] + HMAX=16 + + font_name = "RobotoCondensed-Bold.ttf" + font_size = None + font_color = None + special_json = entry['block'].get('special_json', {}) + for i, atrb in enumerate(special_json.get("box_attributes", [])): + if i == int(entry['block']['block_id'].split("_")[1]): + font_size = atrb.get("font size") + font_color = atrb.get("font color") + + if tinge: + font_color = tinge + image = Image.new("RGBA", (tb['x2']-tb['x1'], + max(tb['y2']-tb['y1'], HMAX+1))) + draw = ImageDraw.Draw(image) + + translation = entry['block']['translation'][target_lang] + text_source = "" + for e in entry['meta']['index']: + for v in e['values']: + text_source+=v['text_source'] + + sub_chars = entry['meta']['subs']['sub_chars'] + sub_placeholder = entry['meta']['subs']['sub_placeholder'] + + output_text = substitute_text(translation, text_source, sub_placeholder, sub_chars) + draw = drawTextBox(draw, + output_text, + 2,0,image.width-5, + max(image.height-2, HMAX), + font_name, + font_size=font_size, + font_color=font_color) + org_image.paste(image, [tb['x1'], tb['y1']]) + + + + #org_image.show() + return org_image, data + + @classmethod + def process_stage(cls, stage, data, user_id=None, game_id=None, + index_name=None, package=None): + output = list() + options = stage.get('options', {}) + if stage['action'] == "resize": + out = PipelineResize.resize(data, options) + output.extend(out) + elif stage['action'] == "crop": + out = PipelineCrop.crop(data, options) + output.extend(out) + elif stage['action'] == "reduceToColors": + out = PipelineReduceToColors.reduce_colors(data, options) + output.extend(out) + elif stage['action'] == "reduceToMultiColor": + out = PipelineReduceToColors.reduce_to_multi_color(data, options) + output.extend(out) + elif stage['action'] == "segFill": + out = PipelineColorFill.seg_fill(data, options) + output.extend(out) + elif stage['action'] == "indexHSV": + out = PipelineIndexHSV.index_hsv(data, options) + output.extend(out) + elif stage['action'] == "indexPixelCount": + out = PipelineIndexPixelCount.index_pixel_count(data, options) + output.extend(out) + elif stage['action'] == "sharpen": + out = PipelineSharpen.sharpen(data, options) + output.extend(out) + elif stage['action'] == "findShortlistByIndex": + out = PipelineFindByIndex.find_by_index(data, options, user_id, + game_id, index_name, package=package) + output.extend(out) + elif stage['action'] == "expandShortlist": + short_list = data['short_list'] + out = list() + for short_block in short_list: + out.append({"image": data['image'].copy(), + "meta": copy.deepcopy(data['meta']), + "block": short_block + }) + output.extend(out) + elif stage['action'] == "diffImage": + out = PipelineDiffImage.diff_image(data, options) + output.extend(out) + elif stage['action'] == "fuzzyDiffImage": + out = PipelineFuzzyDiffImage.fuzzy_diff_image(data, options) + output.extend(out) + elif stage['action'] == "diffTextLines": + out = PipelineDiffTextLines.diff_text_lines(data, options) + output.extend(out) + elif stage['action'] == "getTextBlocks": + out = PipelineGetTextBlocks.get_text_blocks(data, options) + output.extend(out) + elif stage['action'] == "indexOCR": + out = PipelineIndexOCR.ocr_index(data, options) + output.extend(out) + elif stage['action'] == "expandColorHorizontal": + out = PipelineExpandColor.expand_horizontal(data, options) + ouput.extend(out) + elif stage['action'] == "expandColorVertical": + out = PipelineExpandColor.expand_vertical(data, options) + ouput.extend(out) + elif stage['action'] == "expandColor": + out = PipelineExpandColor.expand_color(data, options) + ouput.extend(out) + elif stage['action'] == "rectangle": + out = PipelineRectangle.rectangle(data, options) + ouput.extend(out) + elif stage['action'] == "createIndex": + out = PipelineCreateIndex.create_index(data, options, + user_id=user_id, + game_id=game_id, + index_name=index_name) + output.extend(out) + + return output + + +class PipelineAction: + @classmethod + def resolve_var(cls, name, data): + if isinstance(name, basestring) and name.startswith("$"): + names = name.partition("$")[2].split(".") + s_data = data + try: + for seg in names: + data = data[seg] + except: + data = s_data['meta'] + for seg in names: + data = data[seg] + return data + else: + return name + + +##### IMAGE MODIFIERS ################ + +class PipelineResize(PipelineAction): + @classmethod + def resize(cls, data, ops): + new_image = data['image'].resize([cls.resolve_var(ops['width'], data), + cls.resolve_var(ops['height'], data)]) + return [{"image": new_image, "meta": data['meta']}] + + +class PipelineCrop(PipelineAction): + @classmethod + def crop(cls, data, ops): + x1,y1,x2,y2 = [cls.resolve_var(ops[c],data) for c in\ + ['x1', 'y1', 'x2', 'y2']] + + new_image = data['image'].crop([x1, y1, x2, y2]) + if 'color' in ops: + pass#TODO + data['image'] = new_image + data['meta']['bounding_box'] = {"x1": x1, "x2": x2, "y1": y1, "y2": y2} + return [data] + +class PipelineSharpen(PipelineAction): + @classmethod + def sharpen(cls, data, ops): + enhancer = ImageEnhance.Sharpness(data['image']) + new_image = enhancer.enhance(cls.resolve_var(ops['sharpness'], data)) + return [{"image": new_image, "meta": data['meta']}] + +class PipelineReduceToColors(PipelineAction): + @classmethod + def reduce_colors(cls, data, ops): + threshold = cls.resolve_var(ops['threshold'], data) + colors = cls.resolve_var(ops['colors'], data) + img = reduce_to_colors(data['image'].convert("P", + palette=Image.ADAPTIVE), + colors, threshold) + return [{"image": img, "meta": data['meta']}] + + @classmethod + def reduce_to_multi_color(cls, data, ops): + image = data['image'] + image=image.convert("P", palette=Image.ADAPTIVE) + threshold = cls.resolve_var(ops['threshold'], data) + base_color = cls.resolve_var(ops['base'], data) + colors = cls.resolve_var(ops['colors'], data) + threshold = 32 + reduce_to_multi_color(image, base_color, + colors, + threshold=threshold) + return [{"image": image, "meta": data['meta']}] + +class PipelineColorFill(PipelineAction): + @classmethod + def seg_fill(cls, data, ops): + image = data['image'] + + base_color = cls.resolve_var(ops['base'], data) + colors = cls.resolve_var(ops['colors'], data) + image = segfill(image, "FF0000", "FFFFFF") + + return [{"image": image, "meta": data['meta']}] + + +######## INDEXERS ################# + +class PipelineIndexHSV(PipelineAction): + @classmethod + def index_hsv(cls, data, ops): + if "x1" in ops: + x1,y1,x2,y2 = [cls.resolve_var(ops[c], data) for c in\ + ['x1','y1', 'x2', 'y2']] + res = data['image'].crop([x1,y1,x2,x2]) + else: + res = data['image'] + + res = res.resize((1,1), Image.ANTIALIAS).convert("RGB") + r,g,b = res.getpixel((0,0)) + h,s,v = colorsys.rgb_to_hsv(float(r)/255, float(g)/255, float(b)/255) + if 'index' not in data['meta']: + data['meta']['index'] = list() + name = ops.get("name", "__"+str(len(data['meta']['index']))) + data['meta']['index'].append({"type": "hsv", + "values": {"h": h, "s":s, "v": v}, + "name": name, + "tolerance": ops['tolerance']}) + return [data] + +class PipelineIndexPixelCount(PipelineAction): + @classmethod + def index_pixel_count(cls, data, ops): + #######TODO + pixel_count = data['meta']['pixel_count'] + name = ops.get("name", "__"+str(len(data['meta']['index']))) + data['meta']['index'].append({"type": "pixel_count", + "values": {"pixel_count": pixel_count}, + "name": name, + "tolerance": ops['tolerance']}) + return [data] + +def ocr_fix_text(text, fixer): + for fix in fixer: + p = fix.partition("=") + text = text.replace(p[0], p[2]) + return text + +class PipelineIndexOCR(PipelineAction): + @classmethod + def ocr_fix_text(cls, text, fixer): + for fix in fixer: + p = fix.partition("=") + text = text.replace(p[0], p[2]) + return text + + @classmethod + def ocr_index(cls, data, ops): + t = time.time() + miss = cls.resolve_var(ops.get("miss", [1,2]), data) + min_pixels = cls.resolve_var(ops.get("min_pixels", 1), data) + common_errors = cls.resolve_var(ops.get("common_errors", []), data) + subs = cls.resolve_var(ops.get("subs", {}), data) + common_errors_subs = common_errors+[x+"=" for x in subs.get("sub_chars", [])] + + boxes = ocr_tools.tess_helper(data['image'], cls.resolve_var(ops['lang'], data), + cls.resolve_var(ops.get('mode'), data), + min_pixels) + print boxes + print ['--', miss] + text_colors = cls.resolve_var(ops.get("text_colors", []), data) + + if 'index' not in data['meta']: + data['meta']['index'] = list() + name = ops.get("name", "__"+str(len(data['meta']['index']))) + output = list() + + new_data = copy.deepcopy(data) + data = new_data + data['meta']['containing'] = True + data['meta']['miss'] = miss + data['meta']['index'].append({"type": "ocr", + "values": [{"text": ocr_fix_text(t[0],common_errors_subs), + "text_source": t[0], + "box": t[1]} for t in boxes], + "name": name, + }) + data['meta']['common_errors'] = common_errors + data['meta']['subs'] = subs + data['meta']['text_colors'] = text_colors + + return [data] + +########## INDEX FUNCTION ################ + +class PipelineFindByIndex(PipelineAction): + @classmethod + def find_by_index(cls, data, ops, user_id, game_id, index_name, package): + t_ = time.time() + if 'indexes' in ops: + indexes = list() + for entry in ops['indexes']: + for ind in data['meta']['index']: + if ind['name'] == entry: + indexes.append(ind) + break + else: + print "Index not found: "+entry + else: + indexes = data['meta'].get('index', []) + count = 0 + db_query = {"user_id": user_id, "game_id": game_id, "type": index_name} + for index in indexes: + if index['type'] == 'hsv': + tol = index['tolerance'] + for c in 'hsv': + ik = "index."+str(count)+".value" + db_query[ik] = {"$gte": index['values'][c]-tol, + "$lte": index['values'][c]+tol} + count+=1 + elif index['type'] == 'pixel_count': + tol = index['tolerance'] + ik = "index."+str(count)+".value" + db_query[ik] = {"$gte": index['values']['pixel_count']/tol, + "$lte": index['values']['pixel_count']*tol} + elif index['type'] == 'ocr': + l = list() + index_key1 = "index."+str(count)+".value" + #index_key2 = "index."+str(count)+".ord1" + #index_key3 = "index."+str(count)+".ord2" + if not '$and' in db_query: + db_query['$and'] = list() + db_query['$and'].append({"$or": []}) + + start_count = count + + for iv_entry in index['values']: + db_query['$and'][-1]['$or'].append(dict()) + + ord1 = f_ord1(iv_entry['text'].encode("utf-8")) + ord2 = f_ord2(iv_entry['text'].encode("utf-8")) + miss = get_miss(data['meta'].get("miss", [1,2]), iv_entry['text'].encode("utf-8")) + + index_key1 = "index."+str(start_count)+".value" + db_query['$and'][-1]['$or'][-1][index_key1] = { + "$gte": ord1-255*miss, + "$lte": ord1+255*miss} + + + index_key2 = "index."+str(start_count+1)+".value" + db_query['$and'][-1]['$or'][-1][index_key2] = { + "$gte": ord2-255*miss, + "$lte": ord2+255*miss} + + if not db_query['$and'][-1]['$or']: + db_query['$and'].pop() + if not db_query['$and']: + del db_query['$and'] + count+=2 + + short_list = [x for x in package.find_by_query(db_query)] + if not data.get("short_list"): + data['short_list'] = list() + data['short_list'].extend(short_list) + if db_query['type'] == 'deu_ocr': + print [time.time()-t_] + return [data] + + #indexes = [{"type": "hsv", "values": {"h": 0, "s": 0, "v": 0}, + # "name": "__0", "tolerance": 0.05}, + # {"type": "pixel_count", "values": {"count": 1150}, + # "name": "__1", "tolerance": 3}] + + +############ IMAGE TESTERS ################# + +class PipelineDiffImage(PipelineAction): + @classmethod + def diff_image(cls, data, ops): + test_image = data['image'].convert("RGBA") + #index_image = load_image(data['block']['index_image']) + index_image = load_image(cls.resolve_var(ops['image'], data)) + + if data['block'].get("text_colors"): + pass#figure this out in a bit.... + + im = ImageChops.difference(test_image, index_image).convert("RGBA") + pix = ImageStat.Stat(im).mean + threshold = ops.get("threshold", 16.0) + + the_sum = (pix[0]**2+pix[1]**2+pix[2]**2)/(threshold**2) + + output = data.copy() + if the_sum < 1.0 and pix[3] < 128: + output['match'] = True + output['l_dist'] = the_sum + else: + output['match'] = False + return [output] + +class PipelineGetTextBlocks(PipelineAction): + @classmethod + def get_text_blocks(cls, data, ops): + image_data = data['image'] + text_colors = cls.resolve_var(ops["colors"], data) + threshold = cls.resolve_var(ops["threshold"], data) + scale_factor = cls.resolve_var(ops.get("scale_factor"), data) + kwargs = dict() + if scale_factor: + kwargs['scale_factor'] = scale_factor + + boxes = MovingTextAlgorithm.get_text_blocks(image_data, text_colors, + threshold, **kwargs) + name = cls.resolve_var(ops.get("name", ""), data) + #boxes = [[image, {"bounding_box": bounding_box, + # "pixel_count": pixel_count}], ...] + + output = list() + for image, box in boxes: + if box['pixel_count'] > threshold: + d = copy.deepcopy(data) + d['image'] = image + + if not 'index' in d['meta']: + d['meta']['index'] = list() + + i_entry = { + 'bounding_box': box['bounding_box'], + 'pixel_count': box['pixel_count'], + 'text_colors': text_colors, + 'threshold': threshold, + 'type': "mov" + } + if name: + i_entry['name'] = name + else: + i_entry['name'] = "__"+str(len(d['meta']['index'])) + d['meta']['index'].append(i_entry) + d['meta']['bounding_box'] = box['bounding_box'] + d['meta']['text_colors'] = text_colors + d['meta']['threshold'] = threshold + d['meta']['pixel_count'] = box['pixel_count'] + d['meta']['dont_crop'] = True + output.append(d) + return output + +class PipelineFuzzyDiffImage(PipelineAction): + @classmethod + def fuzzy_diff_image(cls, data, ops): + test_image = data['image'] + index_image = load_image(cls.resolve_var(ops['image'], data)) + threshold = cls.resolve_var(ops.get("threshold",16), data) + the_sum = MovingTextAlgorithm.chop_difference(index_image, test_image, + (255,255,255,255), threshold) + if the_sum >= 1.0 and index_image.height < test_image.height: + the_sum = MovingTextAlgorithm.chop_difference(index_image, + test_image.crop((0,0, test_image.width, + index_image.height)), + (255,255,255,255), threshold) + + if the_sum >= 1.0 and index_image.height < test_image.height: + the_sum = MovingTextAlgorithm.chop_difference(index_image, + test_image.crop((0, test_image.height-index_image.height, + test_image.width, test_image.height)), + (255,255,255,255), threshold) + output = data + if the_sum < 1.0: + #is a match! + output['match'] = True + output['l_dist'] = the_sum + else: + output['match'] = False + return [output] + + +class PipelineDiffTextLines(PipelineAction): + @classmethod + def diff_text_lines(cls, data, ops): + #compare data['block'] (shortlist) to + # data['meta']['index'] + # data['block']['index_ocr_text'] = full ocr text to compare for the block + output = data.copy() + f_boxes = list() + common_errors = data['meta']['common_errors'] + sub_chars = [x+"=" for x in data['meta'].get("subs", {}).get("sub_chars", [])] + sub_placeholder = data['meta'].get("subs", {}).get("sub_placeholder") + + f_dists = list() + for entry in data['block']['index_ocr_text']: + text = entry['text'] + found = False + for check in data['meta']['index']: + for val in check['values']: + miss = data['meta'].get("miss", [1,2]) + miss = get_miss(miss, text) + + subs = data['meta'].get("subs", {}).get("sub_chars", []) + match, dist = is_string_similar(val['text'], + ocr_fix_text(text, common_errors+subs), + miss=miss) + if match: + f_boxes.append(val['box']) + f_dists.append(dist) + found = True + print ['t', dist, text] + break + if found: + break + + if found == False: + output['match'] = False + break + else: + #at this point we have a match, so modify the image and change + #the output and stuff. + #bb = data['block']['bounding_box'] + + #box = [min([b[0] for b in f_boxes]), min([b[1] for b in f_boxes])] + #box.append(box[0]+bb['x2']-bb['x1']) + #box.append(box[1]+bb['y2']-bb['y1']) + + box = [min([b[0] for b in f_boxes]), min([b[1] for b in f_boxes]), + max([b[2] for b in f_boxes]), max([b[3] for b in f_boxes])] + + data['block']['bounding_box'] = {"x1": box[0], "y1": box[1], + "x2": box[2], "y2": box[3]} + output['match'] = True + output['l_dist'] = sum(f_dists) + return [output] + + + +class PipelineExpandColor(PipelineAction): + @classmethod + def expand_horizontal(cls, data, ops): + image = data['image'] + base_color = cls.resolve_var(ops['base'], data) + target_color = cls.resolve_var(ops['target'], data) + if not target_color: + target_color = "FFFFFF" + #image.show() + image = expand_horizontal(image, base_color, target_color) + return [{"image": image, "meta": data['meta']}] + + @classmethod + def expand_vertical(cls, data, ops): + image = data['image'] + base_color = cls.resolve_var(ops['base'], data) + target_color = cls.resolve_var(ops['target'], data) + if not target_color: + target_color = "FFFFFF" + #image.show() + image = expand_vertical(image, base_color, target_color) + return [{"image": image, "meta": data['meta']}] + + @classmethod + def expand_color(cls, data, ops): + image = data['image'] + base_color = cls.resolve_var(ops['base'], data) + target_color = cls.resolve_var(ops['target'], data) + if not target_color: + target_color = "FFFFFF" + #image.show() + image = expand_horizontal(image, base_color, target_color) + image = expand_vertical(image, base_color, target_color) + return [{"image": image, "meta": data['meta']}] + + +class PipelineRectangle(PipelineAction): + @classmethod + def rectangle(cls, data, options): + boundary_box = cls.resolve_var(ops['bounding_box'], data) + color = cls.resolve_var(ops['color'], data) + image = draw_solid_box(image, color, boundary_box) + return [{"image": image, "meta": data['meta']}] + + + + +################################## + +############################### +def is_string_similar_inside(str1, str2): + #is string 2 inside str1? + vals1 = dict() + vals2 = dict() + for k in str1.lower().replace(" ", ""): + vals1[k] = vals1.get(k,0)+1 + for k in str2.lower().replace(" ", ""): + vals2[k] = vals2.get(k,0)+1 + + succ_count = 0 + fail_count = 0 + for key in vals2.keys(): + if vals2.get(key,0) <= vals1.get(key,0): + succ_count +=1 + else: + fail_count +=1 + if succ_count >= fail_count or (fail_count < 2 and len(str2) > 1): + return True + return False + +def is_string_similar_inside2(str1, str2): + str1 = str1.lower().replace(" ", "") + str2 = str2.lower().replace(" ", "") + if is_string_similar_inside(str1, str2): + for text in str2.split("\n"): + for stext in str1.split("\n"): + if levenshtein(text, stext, miss=miss): + break + else: + #no texts matched, so this part of str2 is not in str1 + #so these strings don't match + break + #this text did have a match, so continue for now. + else: + #all matches, so return True + return True + return False + +def is_string_similar(str1, str2, miss=2, subs=[]): + #return levenshtein_shortcircuit(str1, str2, miss) + str1 = str1.lower().replace(" ", "") + str2 = str2.lower().replace(" ", "") + for char in subs: + str1 = str1.replace(char, "") + str2 = str2.replace(char, "") + match, dist = levenshtein_shortcircuit_dist(str1, str2, miss) + return match, dist + +def is_string_simple_similar(str1,str2, miss): + if abs(len(str1)-len(str2)) > miss: + return False + return True + + s1 = dict() + s2 = dict() + for c in str1: + s1[c] = s1.get(c,0)+1 + for c in str2: + s2[c] = s2.get(c,0)+1 + + diss = 0 + for c in s1: + diss+=abs(s1[c]-s2.get(c,0)) + for c in s2: + diss+=abs(s2[c]-s1.get(c,0)) + if diss < miss*4: + return True + return False + + diff --git a/ztrans_client/pyocr_util.py b/ztrans_client/pyocr_util.py new file mode 100644 index 0000000..0ce4ad9 --- /dev/null +++ b/ztrans_client/pyocr_util.py @@ -0,0 +1,115 @@ +from PIL import Image + +import os +import os.path + +os.environ['TESSDATA_PREFIX'] = "bin" + +import pyocr.libtesseract +import pyocr +import ctypes +import time + + +################ hack to override pyocr ##################33 +handle = None + +def load_tesseract_dll(lang="eng"): + global handle + if handle is None: + handle = pyocr.libtesseract.tesseract_raw.init(lang=lang) + +def release_tesseract_dll(): + pyocr.libtesseract.tesseract_raw.cleanup(handle) + +def main(): + image = Image.open("bin\\tsg.tif") + + load_tesseract_dll() + s=image_to_boxes(image, 'eng') + + +def image_to_boxes(image, lang=None, builder=None, mode=6): + global handle + + if builder is None: + builder = pyocr.builders.WordBoxBuilder(mode) + if handle is None: + load_tesseract_dll(lang) + + lvl_line = pyocr.libtesseract.tesseract_raw.PageIteratorLevel.TEXTLINE + lvl_word = pyocr.libtesseract.tesseract_raw.PageIteratorLevel.WORD + + try: + clang = lang if lang else "eng" + for lang_item in clang.split("+"): + if lang_item not in pyocr.libtesseract.tesseract_raw.get_available_languages(handle): + raise pyocr.TesseractError( + "no lang", + "language {} is not available".format(lang_item) + ) + + pyocr.libtesseract.tesseract_raw.set_page_seg_mode( + handle, builder.tesseract_layout + ) + pyocr.libtesseract.tesseract_raw.set_debug_file(handle, os.devnull) + + pyocr.libtesseract.tesseract_raw.set_image(handle, image) + if "digits" in builder.tesseract_configs: + pyocr.libtesseract.tesseract_raw.set_is_numeric(handle, True) + + pyocr.libtesseract.tesseract_raw.recognize(handle) + res_iterator = pyocr.libtesseract.tesseract_raw.get_iterator(handle) + if res_iterator is None: + raise pyocr.TesseractError( + "no script", "no script detected" + ) + page_iterator = pyocr.libtesseract.tesseract_raw.result_iterator_get_page_iterator( + res_iterator + ) + + while True: + if pyocr.libtesseract.tesseract_raw.page_iterator_is_at_beginning_of( + page_iterator, lvl_line): + (r, box) = pyocr.libtesseract.tesseract_raw.page_iterator_bounding_box( + page_iterator, lvl_line + ) + assert(r) + box = pyocr.libtesseract._tess_box_to_pyocr_box(box) + builder.start_line(box) + + last_word_in_line = ( + pyocr.libtesseract.tesseract_raw.page_iterator_is_at_final_element( + page_iterator, lvl_line, lvl_word + ) + ) + + word = pyocr.libtesseract.tesseract_raw.result_iterator_get_utf8_text( + res_iterator, lvl_word + ) + + confidence = pyocr.libtesseract.tesseract_raw.result_iterator_get_confidence( + res_iterator, lvl_word + ) + + if word is not None and confidence is not None and word != "": + (r, box) = pyocr.libtesseract.tesseract_raw.page_iterator_bounding_box( + page_iterator, lvl_word + ) + assert(r) + box = pyocr.libtesseract._tess_box_to_pyocr_box(box) + builder.add_word(word, box, confidence) + + if last_word_in_line: + builder.end_line() + + if not pyocr.libtesseract.tesseract_raw.page_iterator_next(page_iterator, lvl_word): + break + + finally: + pass + + return builder.get_output() + +if __name__=='__main__': + main() diff --git a/ztrans_client/screen_grab.py b/ztrans_client/screen_grab.py index af17e2d..ab31060 100644 --- a/ztrans_client/screen_grab.py +++ b/ztrans_client/screen_grab.py @@ -2,6 +2,9 @@ from PIL import Image import time import datetime +import imaging +import pyautogui +import config if os.name == 'nt': try: @@ -28,11 +31,9 @@ ["default_images/napple_tale.jpg",'ja'] ] - class ImageGrabber: @classmethod def grab_image(cls,*args, **kwargs): - return Image.open("/home/barry/Downloads/silber_test2.png") if os.name == "nt": grabbed_image = cls.grab_image_windows(*args, **kwargs) else: @@ -59,11 +60,14 @@ def grab_image_mocked(cls): @classmethod def grab_image_windows(cls): - #print "grabbing images in windows..." - #hwnd = win32gui.FindWindow(None, 'Calculator') + if config.capture_mode == "fast": + return cls.grab_image_windows_fast() + else: + return cls.grab_image_windows_accurate() + + @classmethod + def grab_image_windows_fast(cls): hwnd = win32gui.GetForegroundWindow() - # Change the line below depending on whether you want the whole window - # or just the client area. try: left, top, right, bot = win32gui.GetClientRect(hwnd) #left, top, right, bot = win32gui.GetWindowRect(hwnd) @@ -96,17 +100,33 @@ def grab_image_windows(cls): saveDC.DeleteDC() mfcDC.DeleteDC() win32gui.ReleaseDC(hwnd, hwndDC) - + print ("aaaa", result) if result == 1: #PrintWindow Succeeded try: im.save("grab.bmp") except: - pass + import traceback + traceback.print_exc() return im + return im except: + import traceback + traceback.print_exc() return None + @classmethod + def grab_image_windows_accurate(cls): + hwnd = win32gui.GetForegroundWindow() + if hwnd: + win32gui.SetForegroundWindow(hwnd) + x, y, x1, y1 = win32gui.GetClientRect(hwnd) + x, y = win32gui.ClientToScreen(hwnd, (x, y)) + x1, y1 = win32gui.ClientToScreen(hwnd, (x1 - x, y1 - y)) + im = pyautogui.screenshot(region=(x, y, x1, y1)) + return im + else: + print('Window not found!') diff --git a/ztrans_client/screen_translate.py b/ztrans_client/screen_translate.py index 5f3da79..816002c 100644 --- a/ztrans_client/screen_translate.py +++ b/ztrans_client/screen_translate.py @@ -3,7 +3,6 @@ import ocr_api import server_client import config - mock_int = 0 class CallScreenshots: diff --git a/ztrans_client/start_up_win.sh b/ztrans_client/start_up_win.sh new file mode 100644 index 0000000..ff73c7e --- /dev/null +++ b/ztrans_client/start_up_win.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd ../ +../ENV2_Client/Scripts/python.exe setup.py install +cd ztrans_client +../../ENV2_Client/Scripts/python.exe startup.py diff --git a/ztrans_client/startup.py b/ztrans_client/startup.py index 2c138f5..42838bf 100644 --- a/ztrans_client/startup.py +++ b/ztrans_client/startup.py @@ -22,13 +22,13 @@ import api_service import key_input -from ztrans_common import text_draw - def donothing(): - filewin = Toplevel(root) - button = Button(filewin, text="Do nothing button") - button.pack() - + try: + filewin = Toplevel(root) + button = Button(filewin, text="Do nothing button") + button.pack() + except: + print("Do nothing error") DEFAULT_IMAGE_PATH = "default.png" @@ -75,6 +75,10 @@ def __init__(self): self.add_events() + def show_about(self): + about_string = "ZTranslate by Barry Rowe\n\nhttps://ztranslate.net\n\nVersion: "+config.version_string + tkMessageBox.showinfo("ZTranslate",about_string) + def set_window_basics(self): self.last_resize = time.time() self.should_resize = False @@ -108,16 +112,24 @@ def add_menu(self): self.filemenu.add_command(label="Exit", command=self.on_quit) self.menubar.add_cascade(label="File", menu=self.filemenu) ##################### - self.settingsmenu = Menu(self.menubar, tearoff=0) - self.settingsmenu.add_command(label="Key Binds", command=self.key_binds) - self.settingsmenu.add_command(label="API Key", command=self.api_key) + """ + self.editmenu = Menu(self.menubar, tearoff=0) + self.editmenu.add_command(label="Undo", command=donothing) - self.menubar.add_cascade(label="Settings", menu=self.settingsmenu) + self.editmenu.add_separator() + + self.editmenu.add_command(label="Cut", command=donothing) + self.editmenu.add_command(label="Copy", command=donothing) + self.editmenu.add_command(label="Paste", command=donothing) + self.editmenu.add_command(label="Delete", command=donothing) + self.editmenu.add_command(label="Select All", command=donothing) + + self.menubar.add_cascade(label="Edit", menu=self.editmenu) + """ ###################### self.helpmenu = Menu(self.menubar, tearoff=0) self.helpmenu.add_command(label="Help Index", command=donothing) - self.helpmenu.add_command(label="About...", command=donothing) - + self.helpmenu.add_command(label="About", command=self.show_about) self.menubar.add_cascade(label="Help", menu=self.helpmenu) self.root.config(menu=self.menubar) @@ -186,7 +198,7 @@ def add_top_ui(self): self.top_ui_source_lang_desc = ttk.Label(self.mainframe, text="Source Language:") self.top_ui_source_lang_desc.grid(row=2, column=0, sticky="W") - self.top_ui_source_lang = ttk.Combobox(self.mainframe, width=4) + self.top_ui_source_lang = ttk.Combobox(self.mainframe, width=5) self.top_ui_source_lang['values'] = ["Auto"]+langs_possible self.top_ui_source_lang['state'] = 'readonly' self.top_ui_source_lang.set("Auto") @@ -225,10 +237,26 @@ def add_top_ui(self): self.top_ui_screenkey_desc.grid(row=3, column=0, sticky="W") self.top_ui_screenkey_var = StringVar() self.top_ui_screenkey_entry = ttk.Entry(self.mainframe, width=3, textvariable=self.top_ui_screenkey_var) - self.top_ui_screenkey_var.set("~") + self.top_ui_screenkey_var.set(config.ascii_capture) + + self.top_ui_screenkey_entry.bind("", self.update_screencap_key) + + self.top_ui_screenkey_entry.grid(row=3, column=1, sticky='W') self.top_ui_screenkey_entry.grid_configure(padx=10, pady=5) + self.top_ui_capture_mode_desc = ttk.Label(self.mainframe, text="Capture mode:") + self.top_ui_capture_mode_desc.grid(row=3, column=2, sticky="W") + + self.top_ui_capture_mode = ttk.Combobox(self.mainframe, width=8) + self.top_ui_capture_mode['values'] = ["Fast", "Accurate"] + self.top_ui_capture_mode['state'] = 'readonly' + self.top_ui_capture_mode.bind('<>', self.set_capture_mode_callback) + self.top_ui_capture_mode.set(config.capture_mode.title()) + self.top_ui_capture_mode.grid(row=3, column=3, sticky='W') + self.top_ui_capture_mode.grid_configure(padx=10, pady=5) + + """ self.top_ui_screenkey_prev_var = StringVar() self.top_ui_screenkey_prev_entry = ttk.Entry(self.mainframe, width=3, textvariable=self.top_ui_screenkey_prev_var) self.top_ui_screenkey_prev_var.set("1") @@ -240,9 +268,17 @@ def add_top_ui(self): self.top_ui_screenkey_next_var.set("2") self.top_ui_screenkey_next_entry.grid(row=3, column=2, sticky='W') self.top_ui_screenkey_next_entry.grid_configure(padx=10, pady=5) - + """ #keep screenshots (moved to menu options) + def set_capture_mode_callback(self, event): + mode = event.widget.get().lower() + config.set_config("capture_mode", mode) + + def update_screencap_key(self, event): + config.set_config("ascii_capture", event.char) + self.top_ui_screenkey_var.set(event.char) + def add_image_section(self): if self.curr_image == None: self.load_image(DEFAULT_IMAGE_PATH) @@ -277,7 +313,7 @@ def load_image_object(self, image_object): self.img_org = image_object.copy() w = self.w - 5 - h = self.h - 145 + h = self.h - 140 if w < 640: w = 640 @@ -315,7 +351,7 @@ def add_events(self): def resizing_window(self, event): - if event.width >= 645 and event.height >= 545: + if event.width >= 645 and event.height >= 535: self.should_resize = True self.w = event.width self.h = event.height @@ -493,7 +529,7 @@ def load_package(self): try: self.package_object = package_loader.PackageObject(filename) except: - tkMessageBox.showinfo("Error", "Package '"+filename+"' could not be loaded. Is it being used by another program?") + tkMessageBox.showinfo("Error", "Package '"+filename+"' could not be loaded. Is it in use by another program?") self.top_ui_package_name_var = "(None)" self.top_ui_mode.set("Normal") return @@ -517,6 +553,7 @@ def update_auto_capture_checkbox(self): if self.top_ui_auto_package_var.get() == 1: self.root.after(25, self.call_screenshoter) + def package_info(self): try: self.package_info_window.destroy() @@ -546,7 +583,7 @@ def package_info(self): """ image_object = Image.open("/home/barry/Downloads/silber_test2.png") - self.grabbed_images.append(image_object) + self.grabbed_images.append(image_object) self.call_screenshoter() """ @@ -556,33 +593,31 @@ def package_info(self): game_name_l = ttk.Label(piw, text="Game Name:").grid(row=0, column=0, sticky="W") game_name = ttk.Label(piw, text=game_name_v).grid(row=0, column=1, columnspan=1, sticky="W") author_l = ttk.Label(piw, text="Author:").grid(row=1, column=0, sticky="W") - author = ttk.Label(piw, text=author_v).grid(row=1, column=1, columnspan=1,sticky="W") + author = ttk.Label(piw, text=author_v).grid(row=1, column=1, columnspan=1, sticky="W") version_l = ttk.Label(piw, text="Version:").grid(row=2, column=0, sticky="W") - version = ttk.Label(piw, text=version_v).grid(row=2, column=1, columnspan=1,sticky="W") - + version = ttk.Label(piw, text=version_v).grid(row=2, column=1, columnspan=1, sticky="W") api_version_l = ttk.Label(piw, text="API Version:").grid(row=3, column=0, sticky="W") - api_version = ttk.Label(piw, text=api_version_v).grid(row=3, column=1, columnspan=1,sticky="W") - - + api_version = ttk.Label(piw, text=api_version_v).grid(row=3, column=1, columnspan=1, sticky="W") + #source_l = ttk.Label(piw, text="Source Language:").grid(row=3, column=0, sticky="W") #source = ttk.Label(piw, text="(De)").grid(row=3, column=1, columnspan=1, sticky="W") #target_l = ttk.Label(piw, text="Target Language:").grid(row=4, column=0, sticky="W") - #target = ttk.Label(piw, text="(En)").grid(row=4, column=1, columnspan=1,sticky="W") - #spacer: + #target = ttk.Label(piw, text="(En)").grid(row=4, column=1, columnspan=1, sticky="W") + #spacer ttk.Label(piw, text="").grid(row=5) - desc_l = ttk.Label(piw, text="Description:").grid(row=6, column=0, sticky="W") desc = ttk.Label(piw, text=description_v, wraplength=320, justify="left").grid( row=7, column=0, columnspan=2, sticky="W") - + x, y = self.root.winfo_x(), self.root.winfo_y() w, h = self.root.winfo_width(), self.root.winfo_height() w2, h2 = 3*w/4, 3*h/4 x2, y2 = x+w2/8, y+h2/8 - + #w2, h2 = piw.winfo_width, piw.winfo_height() piw.geometry(str(w2)+"x"+str(h2)+"+"+str(x2)+"+"+str(y2)) + def close_package(self): self.top_ui_auto_package['state'] = 'disabled' self.top_ui_auto_package_desc['state'] = 'disabled' @@ -609,17 +644,17 @@ def on_pyhook(self, event): if not key_input.read_queue.empty(): scancode, ascii = key_input.read_queue.get() ##print [scancode, ascii] - if scancode == config.keycode_capture:#41 + if scancode == config.keycode_capture or ascii == config.ascii_capture:#41 print "Capture" if self.temp_call == "": self.temp_call = "active" if self.package_object and self.top_ui_auto_package_var.get() == 1: self.temp_call = "" self.call_screenshoter() - elif scancode == config.keycode_prev:#2 + elif scancode == config.keycode_prev or ascii == config.ascii_prev:#2 print "Prev" self.left_image() - elif scancode == config.keycode_next:#3 + elif scancode == config.keycode_next or ascii == config.ascii_next:#3 print "Next" self.right_image() @@ -640,8 +675,7 @@ def main(): except: pass return - text_draw.load_font(config.user_font) - + imaging.load_font(config.user_font) window = MainWindow() window.mainframe.pack(anchor=W) window.imageframe.pack(fill=BOTH, expand=YES) @@ -661,7 +695,6 @@ def main(): key_input.queue_executor.kill_queue() key_input.quit_pipe() api_service.kill_api_server() - if __name__=='__main__': diff --git a/ztrans_client/startup_bat.bat b/ztrans_client/startup_bat.bat new file mode 100644 index 0000000..247decc --- /dev/null +++ b/ztrans_client/startup_bat.bat @@ -0,0 +1,4 @@ +cd .. +..\ENV2_Client\Scripts\python.exe setup.py install +cd ztrans_client +..\..\ENV2_Client\Scripts\python.exe startup.py \ No newline at end of file diff --git a/ztrans_client/text_tools.py b/ztrans_client/text_tools.py new file mode 100644 index 0000000..4288c42 --- /dev/null +++ b/ztrans_client/text_tools.py @@ -0,0 +1,155 @@ +def substitute_text(translation, text_source, sub_placeholder, sub_chars): + #first get the proper order of text_source stuffs according to sub_chars + l = list() + last = False + for char in text_source: + if char in sub_chars: + if last is False: + last = True + l.append(char) + else: + l[-1] = l[-1]+char + else: + last = False + test_string = translation + out_string = "" + while sub_placeholder[0] in test_string and\ + sub_placeholder[1] in test_string.partition(sub_placeholder[0])[2]: + p = test_string.partition(sub_placeholder[0]) + out_string += p[0] + p2 = p[2].partition(sub_placeholder[1]) + try: + val = int(p2[0]) + out_string += l[val] + except: + pass + test_string = p2[2] + out_string+=test_string + return out_string + +def levenshtein(s, t, max_dist=3): + #as seen from https://www.python-course.eu/levenshtein_distance.php + """ + iterative_levenshtein(s, t) -> ldist + ldist is the Levenshtein distance between the strings + s and t. + For all i and j, dist[i,j] will contain the Levenshtein + distance between the first i characters of s and the + first j characters of t + """ + rows = len(s)+1 + cols = len(t)+1 + dist = [[0 for x in range(cols)] for x in range(rows)] + # source prefixes can be transformed into empty strings + # by deletions: + for i in range(1, rows): + dist[i][0] = i + # target prefixes can be created from an empty source string + # by inserting the characters + for i in range(1, cols): + dist[0][i] = i + + for col in range(1, cols): + for row in range(1, rows): + if s[row-1] == t[col-1]: + cost = 0 + else: + cost = 1 + dist[row][col] = min(dist[row-1][col] + 1, # deletion + dist[row][col-1] + 1, # insertion + dist[row-1][col-1] + cost) # substitution + #print dist[row][col] + if dist[row][col] < max_dist: + return True + return False + #return dist[row][col] + +def levenshtein_shortcircuit(s, t, max_dist=3): + #as seen from https://www.python-course.eu/levenshtein_distance.php + """ + iterative_levenshtein(s, t) -> ldist + ldist is the Levenshtein distance between the strings + s and t. + For all i and j, dist[i,j] will contain the Levenshtein + distance between the first i characters of s and the + first j characters of t + """ + rows = len(s)+1 + cols = len(t)+1 + if abs(rows-cols)> max_dist: + return False + + dist = [[0 for x in range(cols)] for x in range(rows)] + # source prefixes can be transformed into empty strings + # by deletions: + for i in range(1, rows): + dist[i][0] = i + # target prefixes can be created from an empty source string + # by inserting the characters + for i in range(1, cols): + dist[0][i] = i + + for col in range(1, cols): + max_num = 1000000 + for row in range(1, rows): + if s[row-1] == t[col-1]: + cost = 0 + else: + cost = 1 + dist[row][col] = min(dist[row-1][col] + 1, # deletion + dist[row][col-1] + 1, # insertion + dist[row-1][col-1] + cost) # substitution + if dist[row][col] < max_num: + max_num = dist[row][col] + if max_num >= max_dist: + return False + #print dist[row][col] + if dist[row][col] < max_dist: + return True + return False + #return dist[row][col] + +def levenshtein_shortcircuit_dist(s, t, max_dist=3): + #as seen from https://www.python-course.eu/levenshtein_distance.php + """ + iterative_levenshtein(s, t) -> ldist + ldist is the Levenshtein distance between the strings + s and t. + For all i and j, dist[i,j] will contain the Levenshtein + distance between the first i characters of s and the + first j characters of t + """ + rows = len(s)+1 + cols = len(t)+1 + if abs(rows-cols)> max_dist: + return False, 10000 + + dist = [[0 for x in range(cols)] for x in range(rows)] + # source prefixes can be transformed into empty strings + # by deletions: + for i in range(1, rows): + dist[i][0] = i + # target prefixes can be created from an empty source string + # by inserting the characters + for i in range(1, cols): + dist[0][i] = i + + for col in range(1, cols): + max_num = 1000000 + for row in range(1, rows): + if s[row-1] == t[col-1]: + cost = 0 + else: + cost = 1 + dist[row][col] = min(dist[row-1][col] + 1, # deletion + dist[row][col-1] + 1, # insertion + dist[row-1][col-1] + cost) # substitution + if dist[row][col] < max_num: + max_num = dist[row][col] + if max_num >= max_dist: + return False, 100000 + #print dist[row][col] + if dist[row][col] < max_dist: + return True, dist[row][col] + return False, 100000 + #return dist[row][col] diff --git a/ztrans_client/util.py b/ztrans_client/util.py new file mode 100644 index 0000000..4f42abb --- /dev/null +++ b/ztrans_client/util.py @@ -0,0 +1,655 @@ +import StringIO +import io +import base64 +import colorsys +from PIL import Image, ImageDraw, ImageChops + +def general_index(image_data): + byte_data = base64.b64decode(image_data) + image = Image.open(io.BytesIO(byte_data)) + res = image.resize((1,1), Image.ANTIALIAS).convert("RGB") + r, g, b = res.getpixel((0,0)) + h, s, v = colorsys.rgb_to_hsv(float(r)/255,float(g)/255,float(b)/255) + return h,s,v + +def load_image(image_data): + if type(image_data) == Image.Image: + return image_data + byte_data = base64.b64decode(image_data) + image = Image.open(io.BytesIO(byte_data)) + image = image.convert("RGBA") + return image + +def image_to_string(img): + output = StringIO.StringIO() + img.save(output, format="PNG") + string = output.getvalue() + return base64.b64encode(string) + +def image_to_string_bmp(img): + output = StringIO.StringIO() + img.convert("RGB").save(output, format="BMP") + string = output.getvalue() + return base64.b64encode(string) + + + +def color_hex_to_byte(text_color): + return (int(text_color[0:2], 16), + int(text_color[2:4], 16), + int(text_color[4:6], 16), + 255) + +def segfill(image, mark_color, target_color): + w = image.width + h = image.height + mark_color = tuple(color_hex_to_byte(mark_color)[0:3]) + target_color = tuple(color_hex_to_byte(target_color)[0:3]) + image = image.convert("RGB") + + last_red = False + h_range = list() + w_range = list() + + for j in range(h): + sec = image.crop((0,j, w, j+1)).getcolors() + for num, color in sec: + if color == target_color: + h_range.append(j) + break + for i in range(w): + sec = image.crop((i,0, i+1, h)).getcolors() + for num, color in sec: + if color == target_color: + w_range.append(i) + break + + new_w_range = dict() + new_h_range = dict() + white_count = 0 + for j in h_range: + last_red = False + white_count = 0 + for i in range(max(min(w_range)-1, 0), max(w_range)): + pix = image.getpixel((i,j)) + if last_red == False: + if pix == mark_color: + last_red = True + if white_count > 0: + for k in range(white_count): + image.putpixel((i-1-k,j), mark_color) + white_count = 0 + + elif pix == target_color: + white_count +=1 + new_w_range[i] = 1 + new_h_range[j] = 1 + else: + white_count = 0 + elif pix == target_color: + image.putpixel((i,j), mark_color) + if white_count > 0: + for k in range(white_count): + image.putpixel((i-1-k,j), mark_color) + white_count = 0 + elif pix == mark_color: + if white_count > 0: + for k in range(white_count): + image.putpixel((i-1-k,j), mark_color) + white_count = 0 + else: + white_count = 0 + last_red = False + + + for entry in new_h_range.keys(): + if entry > 0: + new_h_range[entry-1] = 1 + new_h_range = sorted(new_h_range.keys()) + new_w_range = sorted(new_w_range.keys()) + white_count = 0 + #vertical pass + for i in new_w_range: + last_red = False + white_count = 0 + for num_j, j in enumerate(new_h_range): + if num_j == 0 or new_h_range[num_j-1] != j-1: + white_count = 0 + + pix = image.getpixel((i,j)) + + if last_red == False: + if pix == mark_color: + last_red = True + if white_count > 0: + for k in range(white_count): + image.putpixel((i,j-1-k), mark_color) + white_count = 0 + elif white_count == target_color: + white_count += 1 + pass + else: + white_count = 0 + elif pix == target_color: + image.putpixel((i,j), mark_color) + if white_count > 0: + for k in range(white_count): + image.putpixel((i,j-1-k), mark_color) + white_count = 0 + elif pix == mark_color: + if white_count > 0: + for k in range(white_count): + image.putpixel((i,j-1-k), mark_color) + white_count = 0 + else: + white_count = 0 + last_red = False + return image.convert("RGBA") + + + +def floodfill2(image, center, bg_color): + def get_pix_near(bytes_array, xy, mark_color, threshold): + if (xy[0] >= w or xy[0] < 0 or xy[1]>= h or xy[1] < 0): + return None + s = w*xy[1]*4+xy[0]*4 + val = 0 + for i in range(4): + val += (mark_color[i]-bytes_array[s+i])**2 + if val < threshold**2: + return True + return False + + def get_pix_near2(bytes_array, xy, mark_color, threshold): + if (xy[0] >= w or xy[0] < 0 or xy[1]>= h or xy[1] < 0): + return None + s = w*xy[1]*4+xy[0]*4 + #print [mark_color, tuple(bytes_array[s:s+4])] + if mark_color == tuple(bytes_array[s:s+4]): + return True + return False + + def get_pix(bytes_array, xy): + s = w*xy[1]*4+xy[0]*4 + return bytes_array[s:s+4] + + def set_pix(bytes_array, xy, color): + s = w*xy[1]*4+xy[0]*4 + for i in range(4): + bytes_array[s+i] = color[i] + + t=time.time() + w = image.width + h = image.height + + bg_color = color_hex_to_byte(bg_color) + + mode = "RGBA" + bytes_array = [ord(x) for x in image.convert("RGBA").tobytes()] + mark_color = tuple(get_pix(bytes_array, (10,10))) + print mark_color + print bg_color + threshold = 16 + + last_marked = [[0,i] for i in range(h)]#center] + last_marked2 = list() + rounds = 0 + print time.time()-t + a = 0 + b = 0 + while last_marked: + rounds+=1 + for entry in last_marked: + for offx, offy in [[1,0]]:#, [0, 1], [-1,0], [0,-1]]: + a+=1 + if get_pix_near2(bytes_array, (entry[0]+offx, entry[1]+offy), + mark_color, threshold): + b+=1 + set_pix(bytes_array, (entry[0]+offx, entry[1]+offy), + bg_color) + last_marked2.append((entry[0]+offx, entry[1]+offy)) + last_marked = last_marked2 + last_marked2 = list() + + ########## + #print [chr(x) for x in bytes_array] + print time.time()-t + print [a,b] + image_out = Image.frombytes(mode, (w,h),"".join([chr(x) for x in bytes_array])) + print time.time()-t + image_out.show() + +def floodfill(image, bg_color, mid_color, end_color, + sample_points, threshold): + image = image.convert("RGBA") + center = [0,0] + bg_color = color_hex_to_byte(bg_color) + mid_color = color_hex_to_byte(mid_color) + end_color = color_hex_to_byte(end_color) + + for sample_point in sample_points: + center = tuple(sample_point) + + pixel = image.getpixel(center) + if color_dist(bg_color, pixel) <= threshold**2: + ImageDraw.floodfill(image, xy=center, value=mid_color) + + #revert to end color now + for sample_point in sample_points: + center = tuple(sample_point) + pixel = image.getpixel(center) + if color_dist(mid_color, pixel) <= threshold**2: + ImageDraw.floodfill(image, xy=center, value=end_color) + + return image + +def color_dist(color1, color2): + rr = color1[0] - color2[0] + gg = color1[1] - color2[1] + bb = color1[2] - color2[2] + return rr**2+gg**2+bb**2 + +def reduce_to_multi_color(img, bg, colors_map, threshold): + def vdot(a,b): + return a[0]*b[0]+a[1]*b[1]+a[2]*b[2] + + def vnorm(b): + return vdot(b,b)**0.5 + + def vscale(b, s): + return [b[0]*s, b[1]*s, b[2]*s] + + def vsub(a, b): + return [a[0]-b[0], a[1]-b[1], a[2]-b[2]] + + new_palette = list() + p = img.getpalette() + if bg is not None: + bg = color_hex_to_byte(bg) + + for i in range(256): + r = p[3*i] + g = p[3*i+1] + b = p[3*i+2] + closest = 1000000000 + close = color_hex_to_byte("000000") + + for entry in colors_map: + if type(entry) in (list, tuple): + tc, tc_map = entry + else: + tc, tc_map = entry, entry + + if isinstance(tc, basestring): + tc = color_hex_to_byte(tc) + + rr = r-tc[0] + gg = g-tc[1] + bb = b-tc[2] + d = rr**2+gg**2+bb**2 + if d < closest: + closest = d + close = color_hex_to_byte(tc_map) + else: + tc = [color_hex_to_byte(tc[0]), + color_hex_to_byte(tc[1])] + #color range vector + crv = [tc[1][0]-tc[0][0], + tc[1][1]-tc[0][1], + tc[1][2]-tc[0][2]] + #relative palette vector + rpv = [r-tc[0][0], + g-tc[0][1], + b-tc[0][2]] + + #formula to use vector dot product to get distance + #to line segment. + vb = crv + va = rpv + va1 = vdot(va, vscale(vb, 1/vnorm(vb))) + + if va1 < 0 or va1 > vnorm(vb): + continue + + va2 = vscale(vb, va1/vnorm(vb)) + va3 = vsub(va, va2) + #print va3 + d = vnorm(va3) + if d**2 < closest: + closest = d**2 + if type(tc_map) in [tuple, list]: + if va1/vnorm(vb) <0.5: + close = color_hex_to_byte(tc_map[0]) + else: + close = color_hex_to_byte(tc_map[1]) + else: + close = color_hex_to_byte(tc_map) + + if close is not None and closest <= threshold**2: + new_palette.extend(close[:3]) + elif bg is None: + new_palette.extend([r,g,b]) + else: + new_palette.extend(bg[:3]) + img.putpalette(new_palette) + return img + + + +def reduce_to_colors(img, colors, threshold): + new_palette = list() + p = img.getpalette() + for i in range(256): + r = p[3*i] + g = p[3*i+1] + b = p[3*i+2] + vals = list() + for tc in colors: + tc = color_hex_to_byte(tc) + rr = r-tc[0] + gg = g-tc[1] + bb = b-tc[2] + vals.append(rr**2+gg**2+bb**2) + + if vals and min(vals) <= threshold**2: + new_palette.extend([255,255,255]) + else: + new_palette.extend([0,0,0]) + img.putpalette(new_palette) + return img + +def get_color_counts(img, text_colors, threshold): + if img.mode != "P": + img = img.convert("P", palette=Image.ADAPTIVE) + tc = [color_hex_to_byte(x) for x in text_colors] + img = reduce_to_colors(img, text_colors, threshold) + pixel_count = 0 + for c in img.convert("RGBA").getcolors(): + if c[1] == (255,255,255,255): + pixel_count = c[0] + return pixel_count + +def get_color_counts_simple(img, text_colors, threshold): + test_image = img.convert("P", palette=Image.ADAPTIVE).convert("RGBA") + tc = [color_hex_to_byte(x) for x in text_colors] + total = 0 + for num, color in test_image.getcolors(): + for pix in tc: + if (pix[0]-color[0])**2+(pix[1]-color[1])**2+\ + (pix[2]-color[2])**2 < threshold**2: + total+=num + return total + +def convert_to_absolute_box(bb): + if "x" in bb: + return {"x1": int(bb['x']), 'y1': int(bb['y']), + 'x2': int(bb['x'])+int(bb['w']), + "y2": int(bb['y'])+int(bb['h'])} + else: + return bb +def fix_bounding_box(img, bounding_box): + w = img.width + h = img.height + for key in bounding_box: + if isinstance(bounding_box[key], basestring): + bounding_box[key] = int(bounding_box[key]) + if 'w' in bounding_box: + if bounding_box['x'] < 0: + bounding_box['x'] = 0 + elif bounding_box['x'] > w-1: + bounding_box['x'] = w-1 + if bounding_box['y'] < 0: + bounding_box['y'] = 0 + elif bounding_box['y'] > h-1: + bounding_box['y'] = h-1 + + if bounding_box['w'] < 0: + bounding_box['w'] = 0 + elif bounding_box['x']+bounding_box['w'] > w-1: + bounding_box['w'] = w-1-bounding_box['x'] + if bounding_box['h'] < 0: + bounding_box['h'] = 0 + elif bounding_box['y']+bounding_box['h'] > h-1: + bounding_box['h'] = h-1-bounding_box['y'] + + if bounding_box['w'] ==0: + bounding_box['w'] = 1 + if bounding_box['h'] == 0: + bounding_box['h'] = 1 + else: + if bounding_box['x1'] < 0: + bounding_box['x1'] = 0 + elif bounding_box['x1'] > w-1: + bounding_box['x1'] = w-1 + if bounding_box['y1'] < 0: + bounding_box['y1'] = 0 + elif bounding_box['y1'] > h-1: + bounding_box['y1'] = h-1 + + if bounding_box['x2'] < bounding_box['x1']: + bounding_box['x2'] = bounding_box['x1'] + elif bounding_box['x2'] > w-1: + bounding_box['x2'] = w-1 + if bounding_box['y2'] < bounding_box['y1']: + bounding_box['y2'] = bounding_box['y1'] + elif bounding_box['y2'] > h-1: + bounding_box['y2'] = h-1 + + if bounding_box['x1']==bounding_box['x2']: + bounding_box['x2']+=1 + if bounding_box['y1']==bounding_box['y2']: + bounding_box['y2']+=1 + return bounding_box + +def intersect_area(bb,tb): + dx = min(bb['x2'], tb['x2'])-max(bb['x1'], tb['x1']) + dy = min(bb['y2'], tb['y2'])-max(bb['y1'], tb['y1']) + if dx >= 0 and dy>=0: + return dx*dy + return 0 + + +def get_bounding_box_area(bb): + return intersect_area(bb,bb) + +def chop_to_box(image, tb, bb): + chop = [0,0,image.width,image.height] + if tb['x1'] < bb['x']: + chop[0] = bb['x']-tb['x1'] + if tb['y1'] < bb['y']: + chop[1] = bb['y']-tb['y1'] + if tb['x2'] > bb['x']+bb['w']: + chop[2] = image.width-tb['x2']+bb['x']+bb['w'] + if tb['y2'] > bb['y']+bb['h']: + chop[3] = image.height-tb['y2']+bb['y']+bb['h'] + image= image.crop(chop) + return image + +def get_best_text_color(image, text_colors, threshold): + test_image = image.convert("P", palette=Image.ADAPTIVE).convert("RGBA") + tc = [[x,color_hex_to_byte(x)] for x in text_colors] + totals = {} + + for num, color in test_image.getcolors(): + for c, pix in tc: + if (pix[0]-color[0])**2+(pix[1]-color[1])**2+\ + (pix[2]-color[2])**2 < threshold**2: + totals[c]=totals.get(c,0)+num + if totals: + for num, colors in test_image.getcolors(): + for c, pix in tc: + totals[c] = totals.get(c,0)+num + best = max(totals, key=totals.get) + return best + + +def tint_image(image, color, border=2): + byte_color = color_hex_to_byte(color) + image = image.convert("RGBA") + + tint_image = Image.new("RGBA", image.size, (255,255,255,255)) + draw = ImageDraw.Draw(tint_image) + draw.rectangle([1,1, image.width-1,image.height-1], + fill=byte_color, + outline=(255,255,255,255)) + new_image = ImageChops.multiply(image, tint_image) + return new_image + + +def black_expand(image, mark_color, target_colors): + w = image.width + h = image.height + if isinstance(target_colors, basestring): + target_colors = [target_colors] + + mark_color = tuple(color_hex_to_byte(mark_color)[0:3]) + target_colors = [tuple(color_hex_to_byte(x)[0:3]) for x in target_colors] + + image = image.convert("RGB") + t_time=time.time() + h_range = list() + w_range = list() + + #expand horizontally first: + for j in range(h): + sec = image.crop((0,j, w, j+1)).getcolors() + for num, color in sec: + if color == mark_color: + #there is a black pixel on this line + for i in range(w): + if image.getpixel((i,j)) == mark_color:#in target_colors: + if i > 0 and image.getpixel((i-1, j)) in target_colors: + image.putpixel((i-1,j), mark_color) + if i < w-1 and image.getpixel((i+1, j)) in target_colors: + image.putpixel((i+1,j), mark_color) + break + #expand vertical first: + for i in range(w): + sec = image.crop((i,0, i+1, h)).getcolors() + for num, color in sec: + if color == mark_color: + #there is a black pixel on this line + for j in range(h): + if image.getpixel((i,j)) == mark_color:#in target_colors: + if j > 0 and image.getpixel((i, j-1)) in target_colors: + image.putpixel((i,j-1), mark_color) + if j < h-1 and image.getpixel((i, j+1)) in target_colors: + image.putpixel((i,j+1), mark_color) + break + print "Black expand took: "+str(time.time()-t_time) + return image + +def expand_vertical(img, bg_color, target_color): + def cache_get(img, xy, cache): + if xy not in cache: + cache[xy] = img.getpixel(xy) + return cache[xy] + + t_time = time.time() + bg = color_hex_to_byte(bg_color)[:3] + target = color_hex_to_byte(target_color)[:3] + + w = img.width + h = img.height + image = img.convert("RGB") + + h_range = list() + for j in range(h): + sec = image.crop((0,j, w, j+1)).getcolors() + for num, color in sec: + if color == target: + if j > 0: + h_range.append(j-1) + if j < h-1: + h_range.append(j+1) + h_range.append(j) + break + h_range = list(set(h_range)) + h_range.sort() + + for i in range(w): + sec = image.crop((i,0, i+1, h)).getcolors() + for num, color in sec: + if color == target: + upset = dict() + cache = dict() + for j in h_range: + pix = cache_get(image, (i,j), cache)#.getpixel((i,j)) + if pix == bg: + if (j > 0 and cache_get(image,(i,j-1), cache) == target) or\ + (j < h-1 and cache_get(image, (i,j+1), cache) == target): + upset[j] = 1 + for key in upset: + image.putpixel((i, key), target) + print "vert expand ", time.time()-t_time + return image + + +def expand_horizontal(img, bg_color, target_color): + def cache_get(img, xy, cache): + if xy not in cache: + cache[xy] = img.getpixel(xy) + return cache[xy] + + t_time = time.time() + bg = color_hex_to_byte(bg_color)[:3] + target = color_hex_to_byte(target_color)[:3] + + w = img.width + h = img.height + image = img.convert("RGB") + + w_range = list() + for i in range(w): + sec = image.crop((i, 0, i+1, h)).getcolors() + for num, color in sec: + if color == target: + if i > 0: + w_range.append(i-1) + if i < w-1: + w_range.append(i+1) + w_range.append(i) + break + + w_range = list(set(w_range)) + w_range.sort() + + for j in range(h): + sec = image.crop((0,j, h, j+1)).getcolors() + + for num, color in sec: + if color == target: + upset = dict() + cache = dict() + for i in w_range: + + pix = cache_get(image, (i,j), cache)#.getpixel((i,j)) + if pix == bg: + if (i > 0 and cache_get(image,(i-1,j), cache) == target) or\ + (i < w-1 and cache_get(image, (i+1,j), cache) == target): + upset[i] = 1 + for key in upset: + image.putpixel((key, j), target) + print "horizontal expand ", time.time()-t_time + return image + +def draw_solid_box(image, color, bb): + draw = ImageDraw.Draw(image) + byte_color = color_hex_to_byte(color) + draw.rectangle([bb['x1'], bb['y1'], bb['x2'], bb['y2']], + fill=byte_color) + return image + +def fix_neg_width_height(bb): + if bb['w'] < 0: + new_x = bb['x']+bb['w'] + new_w = -1*bb['w'] + bb['x'] = new_x + bb['w'] = new_w + if bb['h'] < 0: + new_y = bb['y']+bb['h'] + new_h = -1*bb['h'] + bb['y'] = new_y + bb['h'] = new_h + return bb + From d243aad0199ea7bbb79c6eacba2d16499e9973a2 Mon Sep 17 00:00:00 2001 From: Barry Rowe Date: Sun, 8 Nov 2020 22:31:23 -0800 Subject: [PATCH 2/2] Added in v1.03 readme updates. --- ztrans_client/readme.txt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ztrans_client/readme.txt b/ztrans_client/readme.txt index 517fd11..a607c50 100644 --- a/ztrans_client/readme.txt +++ b/ztrans_client/readme.txt @@ -1,5 +1,5 @@ /*************************\ -* ZTranslate v1.01 * +* ZTranslate v1.03 * * * * By: Barry Rowe * * * @@ -17,9 +17,10 @@ At this point ztranslate will be grabing images from the window in focus. Depending on the game, different rendering options might have to be used to ensure that it can grab the image, or grab the image faster. For example, in the PPSSPP emulator, Direct 3D 9/11 will -work, but Open GL/Vulkan will not (they give a black screen). If -your game doesn't work, there may be other work arounds until -ZTranslate can be updated. +work when the capture mode is set to "Fast", but Open GL/Vulkan will +not (they give a black screen). In this case, setting the capture +mode to "Accurate" will correctly grab the screen, though it will +do so slower. ------------------------- How to Run Automatic Mode @@ -36,8 +37,8 @@ on your internet speed and the size of the game window, this may take 3-4 seconds or 10 seconds. Similiarily to package mode, if the client can't grab the game screen, -then it won't be able to translate the window. Modifing the video options -may make it work. +then it won't be able to translate the window. Changing the capture mode +or modifing the video options should make this work. ------------- @@ -76,6 +77,10 @@ Once installed, run ztranslate.sh to start. Change log ----------- +v1.03: + -Added in better custom key support for binds + -Added in capture mode for more screen grabbing options + v1.02: -Fixed window clipping bug (windows) -Added config options for key binds in windows