1 Commits
Author SHA1 Message Date
Manatsawin Hanmongkolchai f40b130ad5 WIP: OCR 2021-01-23 20:23:24 +07:00
44 changed files with 804 additions and 78 deletions

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
build/
dist/
runekit/image/format.c
runekit/image/format.*.*
runekit/image/*.c
runekit/image/*.*.*
setup.py
*.egg-info
+3 -1
View File
@@ -2,7 +2,9 @@ from Cython.Build import cythonize
# use cythonize to build the extensions
modules = [
"runekit/image/format.pyx",
"runekit/image/_utils.pyx",
"runekit/image/_chat.pyx",
"runekit/image/_font.pyx",
]
extensions = cythonize(modules)
+16
View File
@@ -25,3 +25,19 @@ class RequestHandler(typing.TypedDict):
handlerName: str
handlerUrl: str
handlerScript: str
UnblendModes = typing.Literal["raw", "removebg", "blackbg"]
class FontMeta(typing.TypedDict):
basey: int
spacewidth: int
treshold: float
color: typing.Tuple[int, int, int]
unblendmode: UnblendModes
shadow: bool
chars: str
seconds: str
img: typing.Optional[str]
bonus: typing.Optional[typing.Dict[str, int]]
+1 -1
View File
@@ -100,7 +100,7 @@
},
get captureInterval() {
if(!syncChan) return 1000;
if(!syncChan) return 300;
return api.captureInterval;
},
+1 -1
View File
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
class GameInstance(QObject):
refresh_rate = 1000
refresh_rate = 300
manager: "GameManager"
_last_game_activity: float = 0
-27
View File
@@ -1,27 +0,0 @@
import logging
import struct
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from PIL import Image
try:
from .format import image_to_bgra
except ImportError:
logging.warning("Cannot import native image code, using pure python")
image_8bpp = struct.Struct("BBBB")
def image_to_bgra(image: "Image", x, y, w, h) -> bytes:
buf = bytearray(w * h * image_8bpp.size)
index = 0
for p in image.crop((x, y, x + w, y + h)).getdata():
r = p[0]
g = p[1]
b = p[2]
a = p[3] if len(p) == 4 else 255
image_8bpp.pack_into(buf, index, b, g, r, a)
index += image_8bpp.size
return buf
+55
View File
@@ -0,0 +1,55 @@
from ._utils cimport image_to_ptr, unblend, ImageRGBA, RGB
cdef struct Rectangle:
int x
int y
int width
int height
def get_chat_color(image, tuple box_, list colors):
cdef Rectangle box = Rectangle(box_[0], box_[1], box_[2], box_[3])
cdef float best_score = -1.0
cdef RGB best = RGB(0, 0, 0)
cdef ImageRGBA **data = image_to_ptr(image)
cdef double score
cdef ImageRGBA i1, i2
cdef (unsigned char, unsigned char) pixel1, pixel2
cdef int x, y
cdef RGB color
for color in colors:
score = 0.0
for y in range(box.y, box.y + box.height):
for x in range(box.x, box.x + box.width):
if x < 0 or x + 1 >= box.height:
continue
if y < 0 or y + 1 >= box.width:
continue
i1 = data[y][x]
i2 = data[y + 1][x + 1]
pixel1 = unblend(i1.r, i1.g, i1.b, color.r, color.g, color.b)
pixel2 = unblend(i2.r, i2.g, i2.b, color.r, color.g, color.b)
score += (pixel1[0] / 255 * pixel1[1] / 255) * (pixel2[0] / 255 * (255.0 - pixel2[1]) / 255)
if score > best_score:
best_score = score
best = color
return (best.r, best.g, best.b)
+9
View File
@@ -0,0 +1,9 @@
cdef struct CharInfo:
int width
int height
Py_UNICODE chr
int bonus
bint secondary
unsigned char *pixels
+77
View File
@@ -0,0 +1,77 @@
from cpython.mem cimport PyMem_Malloc, PyMem_Free
from ._font cimport CharInfo
from ._utils cimport image_to_ptr, ImageRGBA
cdef class FontDefinition:
cdef CharInfo *chars
cdef size_t chars_len
cdef readonly int width
cdef readonly int height
cdef readonly int space_width
cdef readonly bint shadow
cdef readonly int base_y
cdef readonly int min_rating
cdef readonly int max_spaces
def __dealloc__(self):
for i in range(self.chars_len):
PyMem_Free(self.chars[i].pixels)
PyMem_Free(self.chars)
cpdef FontDefinition generate_font(image, str chars, str seconds, bonuses, int base_y, int space_width, int threshold, bint shadow):
cdef int chars_len = len(chars)
cdef FontDefinition out = FontDefinition()
cdef CharInfo *cinfo = <CharInfo*> PyMem_Malloc(sizeof(CharInfo) * chars_len)
cdef ImageRGBA **raw = image_to_ptr(image)
cdef int width = image.width
cdef int height = image.height
cdef int min_y = image.height - 1
cdef int max_y = 0
cdef int ds = -1
cdef int i, dx, cur_char, letter_width, x, y
cdef Py_UNICODE letter
cdef CharInfo *chr
threshold *= 255
for dx in range(width):
if ds == -1:
i = height - 1
if raw[i][dx].r == 255 and raw[i][dx].a == 255:
ds = dx
continue
letter = chars[cur_char]
letter_width = ds - dx
cinfo[cur_char] = CharInfo(
width=letter_width,
chr=letter,
bonus=0,
secondary=letter in seconds,
)
if bonuses and letter in bonuses:
cinfo[cur_char].bonus = bonuses[letter]
cur_char += 1
out.width = max(out.width, letter_width)
for y in range(height - 1):
for x in range(letter_width):
if raw[y][x + ds].r >= threshold:
min_y = min(min_y, y)
max_y = max(max_y, y)
ds = -1
out.height = max_y + 1 - min_y
out.base_y = base_y - min_y
for a in range(chars_len):
chr = &cinfo[a]
# TODO
return out
+16
View File
@@ -0,0 +1,16 @@
cdef struct ImageRGBA:
unsigned char r
unsigned char g
unsigned char b
unsigned char a
cdef struct RGB:
unsigned char r
unsigned char g
unsigned char b
cpdef (unsigned char, unsigned char) unblend(int r, int g, int b, int R, int G, int B) nogil
cdef ImageRGBA** image_to_ptr(image)
cpdef int can_blend(int rm, int gm, int bm, int r1, int g1, int b1, int p) nogil
# cdef (double, double, double) decompose2col(unsigned char rp, unsigned char gp, unsigned char bp, unsigned char r1, unsigned char g1, unsigned char b1, unsigned char r2, unsigned char g2, unsigned char b2) nogil
# cdef (double, double, double) decompose3col(unsigned char rp, unsigned char gp, unsigned char bp, unsigned char r1, unsigned char g1, unsigned char b1, unsigned char r2, unsigned char g2, unsigned char b2, double r3, double g3, double b3) nogil
+116
View File
@@ -0,0 +1,116 @@
from PIL.Image import new as image_new
from libc.math cimport sqrt, fabs
from ._utils cimport ImageRGBA
cpdef (unsigned char, unsigned char) unblend(int r, int g, int b, int R, int G, int B) nogil:
cdef double m = sqrt(r * r + g * g + b * b)
cdef double n = sqrt(R * R + G * G + B * B)
cdef double x = (r * R + g * G + b * B) / n
cdef double y = sqrt(max(0.0, m * m - x * x))
cdef int r1 = int(max(0.0, (63.75 - y) * 4))
cdef int r2 = int(x / n * 255)
if r2 > 255:
r1 = max(0, r1 - r2 + 255)
r2 = 255
return r1, r2
cdef ImageRGBA** image_to_ptr(image):
ptr = dict(image.im.unsafe_ptrs)['image32']
cdef size_t ptr_int = <size_t> ptr
return <ImageRGBA**> ptr_int
def rgb(unsigned char r, unsigned char g, unsigned char b):
return RGB(r, g,b)
cpdef int can_blend(int rm, int gm, int bm, int r1, int g1, int b1, int p) nogil:
"""
Determine whether color [rgb]m can be a result of a blend with color [rgb]1 that is p (0-1) of the mix
:param rm: resulting color
:param r1: first color of the mix (the other color is unknown)
:param p: the portion of the [rgb]1 in the mix (0-1)
:return: the number that the second color has to lie outside of the possible color ranges
"""
cdef int m = min(50, p/(1-p))
cdef int r = rm + (rm - r1) * m
cdef int g = gm + (gm - g1) * m
cdef int b = bm + (bm - b1) * m
return max(-r, -g, -b, r - 255, g - 255, b - 255)
def unblend_known_background(img, bg_img, bint shadow, unsigned char r, unsigned char g, unsigned char b):
if bg_img:
assert img.width == bg_img.width and img.height == bg_img.height
out_img = image_new('RGBA', (img.width, img.height), None)
cdef ImageRGBA **outptr = image_to_ptr(out_img)
cdef ImageRGBA **imgptr = image_to_ptr(img)
cdef ImageRGBA **bgptr = image_to_ptr(bg_img)
cdef int width = img.width
cdef int height = img.height
cdef int x, y
cdef (double, double, double) col
cdef double m
cdef unsigned char red
with nogil:
for y in range(height):
for x in range(width):
pixel = imgptr[y][x]
bg_pixel = bgptr[y][x]
col = decompose2col(pixel.r, pixel.g, pixel.b, r, g, b, bg_pixel.r, bg_pixel.g, bg_pixel.b)
if shadow:
m = 1 - col[1] - fabs(col[2])
red = <unsigned char> (m * 255)
outptr[y][x].r = red
outptr[y][x].g = <unsigned char> (col[0] / m * 255.0)
outptr[y][x].b = red
else:
red = <unsigned char> (col[0] * 255)
outptr[y][x].r = red
outptr[y][x].g = red
outptr[y][x].b = red
outptr[y][x].a = 255
return out_img
cdef inline (double, double, double) decompose2col(double rp, double gp, double bp, double r1, double g1, double b1, double r2, double g2, double b2) nogil:
cdef double r3 = g1 * b2 - g2 * b1
cdef double g3 = b1 * r2 - b2 * r1
cdef double b3 = r1 * g2 - r2 * g1
cdef double norm = 255.0 / sqrt(r3 * r3 + g3 * g3 + b3 * b3)
r3 *= norm
g3 *= norm
b3 *= norm
return decompose3col(rp, gp, bp, r1, g1, b1, r2, g2, b2, r3, g3, b3)
cdef inline (double, double, double) decompose3col(double rp, double gp, double bp, double r1, double g1, double b1, double r2, double g2, double b2, double r3, double g3, double b3) nogil:
cdef double A = g2 * b3 - b2 * g3
cdef double B = g3 * b1 - b3 * g1
cdef double C = g1 * b2 - b1 * g2
cdef double D = b2 * r3 - r2 * b3
cdef double E = b3 * r1 - r3 * b1
cdef double F = b1 * r2 - r1 * b2
cdef double G = r2 * g3 - g2 * r3
cdef double H = r3 * g1 - g3 * r1
cdef double I = r1 * g2 - g1 * r2
cdef double det = r1 * A + g1 * D + b1 * G
cdef double x = (A * rp + D * gp + G * bp) / det
cdef double y = (B * rp + E * gp + H * bp) / det
cdef double z = (C * rp + F * gp + I * bp) / det
return x, y, z
+291
View File
@@ -0,0 +1,291 @@
from typing import List, Tuple, Optional, TypedDict, NamedTuple
from PIL import Image
from runekit.alt1.schema import FontMeta
from ._chat import get_chat_color
from ._utils import rgb, can_blend
Color = Tuple[int, int, int]
class Rectangle(NamedTuple):
x: int
y: int
width: int
height: int
class OCRLine(NamedTuple):
text: str
area: Rectangle
class ChatboxLineFragment(TypedDict):
color: int
text: str
index: int
class ReadCharInfo(NamedTuple):
chr: str
base_char: CharInfo
x: int
y: int
score: float
size_score: float
def read_chatbox_line(
image: Image,
x: int,
y: int,
colors: List[Color],
font: FontDefinition,
backwards=False,
allow_gap=False,
budget=15,
allow_multi_col=True,
):
colors = [rgb(*x) for x in colors]
fragment_count = 0
max_fragment_len = 0
scan_x = x
text = ""
fragments: List[ChatboxLineFragment] = []
total_space_size = 0
while True:
chat_color = get_chat_color(image, (scan_x, y - 8, 6, 8), colors)
ocr_line = read_chat_line(
image,
chat_color,
font,
scan_x,
y,
backward=backwards and fragment_count == 0,
forward=True,
scan_mode=True,
)
if not ocr_line or not ocr_line.text or not ocr_line.area[4]:
if not allow_multi_col and fragment_count != 0:
break
if total_space_size >= font["width"] and (
not allow_gap
or total_space_size >= 20
or scan_x - x >= 100
or budget < 0
):
break
space_size = font["space_width"]
if total_space_size != 0 or fragment_count == 0:
space_size = 1
scan_x += space_size
total_space_size += space_size
budget -= 1
continue
if total_space_size >= 2 and ocr_line.text:
text += " "
if len(fragments) > 0:
fragments[-1]["text"] += " "
coded_color = chat_color[0] * 256 * 256 + chat_color[1] * 256 + chat_color[2]
fragments.append(
{
"color": coded_color,
"text": ocr_line.text,
"index": len(text),
}
)
fragment_count += 1
text += ocr_line.text
max_fragment_len = max(max_fragment_len, len(ocr_line.text))
scan_x = max(
scan_x + font["space_width"], ocr_line.area.x + ocr_line.area.width
)
total_space_size = 0
if len(fragments) == 0 or max_fragment_len <= 2:
return None
return {
"fragments": fragments,
"text": text,
}
def read_chat_line(
image: Image,
ref_color: Color,
font: FontDefinition,
x: int,
y: int,
backward=False,
forward=True,
scan_mode=False,
) -> Optional[OCRLine]:
text = ""
w_fw = x
w_bw = x
if forward:
shift_x = 0
last_not_found = False
cur_text = ""
while True:
flag2 = scan_mode and not text # FIXME: What's my name?
char = read_chat_char(
image,
ref_color,
font,
x + shift_x,
y,
backward=False,
allow_secondary=not flag2,
)
if not char:
if flag2:
backward = False
break
if last_not_found:
break
cur_text += " "
shift_x += font["space_width"]
last_not_found = True
else:
last_not_found = False
shift_x += char.base_char["width"]
if char.base_char.secondary:
cur_text += char.chr
continue
text = text + cur_text + char.chr
cur_text = ""
w_fw = x + shift_x
if backward:
shift_x = 0
last_not_found = False
cur_text = ""
while True:
char = read_chat_char(image, ref_color, font, x + shift_x, y, backward=True)
if not char:
if last_not_found:
break
cur_text = " " + cur_text
shift_x -= font["space_width"]
last_not_found = True
continue
last_not_found = False
shift_x -= char.base_char.width
if char.base_char.secondary:
cur_text = char.chr + cur_text
continue
text = char.chr + cur_text + text
cur_text = ""
w_bw = x + shift_x
return OCRLine(text=text, area=Rectangle(w_bw, y - 9, w_fw - w_bw, 10))
class _chatScore(NamedTuple):
score: int
size_score: int
chr: CharInfo
def read_chat_char(
image: Image,
ref_color: Color,
font: FontDefinition,
x: int,
y: int,
backward=False,
allow_secondary=True,
) -> Optional[ReadCharInfo]:
y -= font["base_y"]
shadow = font["shadow"]
# Boundary check
if y < 0 or y + font["height"] >= image.height:
return None
if not backward:
if x < 0 or x + font["width"] > image.width:
return None
else:
if x - font["width"] < 0 or x > image.width:
return None
scores: List[_chatScore] = []
for chr in font["chars"]:
if chr.secondary and not allow_secondary:
continue
if backward:
chrx = x - chr.width
else:
chrx = x
a = 0
score = 0
while a < len(chr.pixels):
ix = chrx + chr.pixels[a]
iy = y + chr.pixels[a + 1]
pixel = image.getpixel((ix, iy))
if not shadow:
penalty = can_blend(
pixel[0],
pixel[1],
pixel[2],
ref_color[0],
ref_color[1],
ref_color[2],
chr.pixels[a + 2] / 255,
)
a += 3
else:
lum = chr.pixels[a + 3] / 255
penalty = can_blend(
pixel[0],
pixel[1],
pixel[2],
ref_color[0] * lum,
ref_color[1] * lum,
ref_color[2] * lum,
chr.pixels[a + 2] / 255,
)
a += 4
score += max(0, penalty)
size_score = score - chr.bonus
scores.append(_chatScore(score=score, size_score=size_score, chr=chr))
scores.sort(key=lambda item: item.size_score)
if not scores:
return None
winner = scores[0]
if winner.score > 400:
return None
shift_x = 0
shift_y = font["base_y"]
return ReadCharInfo(
chr=winner.chr.chr,
base_char=winner.chr,
x=x + shift_x,
y=y + shift_y,
score=winner.score,
size_score=winner.size_score,
)
+65
View File
@@ -0,0 +1,65 @@
import json
from pathlib import Path
from typing import TypedDict, NamedTuple, List, Optional
from PIL import Image
from runekit.alt1.schema import FontMeta
from ._utils import unblend_known_background
FONT_BASE = Path(__file__).parent / "fonts"
class CharInfo(NamedTuple):
width: int
chr: str
bonus: int
secondary: bool
pixels: List[int]
class FontDefinition(TypedDict):
chars: List[CharInfo]
width: int
height: int
space_width: int
shadow: bool
base_y: int
min_rating: Optional[int]
max_spaces: Optional[int]
def load_font(name: str) -> FontDefinition:
meta_file = FONT_BASE / (name + ".fontmeta.json")
with meta_file.open() as fp:
meta: FontMeta = json.load(fp)
return load_font_file(FONT_BASE / (name + ".data.png"), meta)
def load_font_file(image: Path, meta: FontMeta) -> FontDefinition:
img = Image.open(image)
return load_font_image(img, meta)
def load_font_image(image: Image, meta: FontMeta) -> FontDefinition:
if meta["unblendmode"] == "removebg":
bg_height = int((image.height - 1) / 2)
background = image.crop((0, bg_height + 1, image.width, image.height))
font_img = unblend_known_background(
image.crop((0, 0, image.width, bg_height)),
background,
meta["shadow"],
meta["color"][0],
meta["color"][1],
meta["color"][2],
)
font_img.show()
elif meta["unblendmode"] == "raw":
raise NotImplementedError
elif meta["unblendmode"] == "blackbg":
raise NotImplementedError
if __name__ == "__main__":
load_font("chat_8px")
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

@@ -0,0 +1,11 @@
{
"basey": 11,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": false,
"chars": "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~",
"seconds": ",.-:;\"'",
"spriteid": 17814
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

@@ -0,0 +1,10 @@
{
"basey": 17,
"spacewidth": 4,
"treshold": 0.6,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": false,
"chars": "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

@@ -0,0 +1,10 @@
{
"basey": 8,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": false,
"chars": "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

@@ -0,0 +1,10 @@
{
"basey": 10,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": false,
"chars": "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~",
"seconds": ",.-:;\"'|"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,10 @@
{
"basey": 10,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 127, 169, 255 ],
"unblendmode": "blackbg",
"shadow": false,
"chars": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%/+?!@#$^~&*()_-=[]{}:;\"'<>\\.,|",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1,10 @@
{
"basey": 8,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": true,
"chars": "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1,10 @@
{
"basey": 8,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": false,
"chars": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:()0123456789%,.'?",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -0,0 +1,10 @@
{
"basey": 10,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 127, 169, 255 ],
"unblendmode": "removebg",
"shadow": true,
"chars": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%/+?!@#$^~&*()_-=[]{}:;\"'<>\\.,|",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@@ -0,0 +1,10 @@
{
"basey": 13,
"spacewidth": 5,
"treshold": 0.6,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": false,
"chars": "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~",
"seconds": ",.-:;\"'|*"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

@@ -0,0 +1,10 @@
{
"basey": 12,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 127, 169, 255 ],
"unblendmode": "removebg",
"shadow": true,
"chars": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%/+?!@#$^~&*()_-=[]{}:;\"'<>\\.,|",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

@@ -0,0 +1,10 @@
{
"basey": 13,
"spacewidth": 4,
"treshold": 0.6,
"color": [ 127, 169, 255 ],
"unblendmode": "removebg",
"shadow": true,
"chars": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%/+?!@#$^~&*()_-=[]{}:;\"'<>\\.,|",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

@@ -0,0 +1,11 @@
{
"basey": 15,
"spacewidth": 6,
"treshold": 0.6,
"color": [ 127, 169, 255 ],
"unblendmode": "removebg",
"shadow": true,
"chars": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%/+?!@#$^~&*()_-=[]{}:;\"'<>\\.,|",
"seconds": ",.-:;\"'",
"//":"Seems to have a 2px letter spacing and 4px spacewidth, the letter spacing is included in the font definition after the characters"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -0,0 +1,10 @@
{
"basey": 10,
"spacewidth": 3,
"treshold": 0.6,
"color": [ 127, 169, 255 ],
"unblendmode": "removebg",
"shadow": true,
"chars": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%/+?!@#$^~&*()_-=[]{}:;\"'<>\\.,|",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

@@ -0,0 +1,10 @@
{
"basey": 8,
"spacewidth": 4,
"treshold": 0.9,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": false,
"chars": "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~",
"seconds": ",.-:;\"'"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

@@ -0,0 +1,10 @@
{
"basey": 7,
"spacewidth": 4,
"treshold": 0.9,
"color": [ 255, 255, 255 ],
"unblendmode": "raw",
"shadow": true,
"chars": "0123456789m()",
"seconds": ""
}
-46
View File
@@ -1,46 +0,0 @@
from cpython.mem cimport PyMem_Malloc, PyMem_Free
cdef struct ImageRGBA:
unsigned char r
unsigned char g
unsigned char b
unsigned char a
def image_to_bgra(image, long ix, long iy, unsigned long w, unsigned long h):
ptr = dict(image.im.unsafe_ptrs)['image32']
cdef size_t ptr_int = <size_t> ptr
cdef ImageRGBA **image32 = <ImageRGBA**> ptr_int
if ix < 0:
ix = 0
if iy < 0:
iy = 0
if ix + w > image.width:
w = image.width - ix
if iy + h > image.height:
h = image.height - iy
cdef size_t size = w * h * 4
cdef unsigned char *buf = <unsigned char*> PyMem_Malloc(size)
cdef size_t index, x, y
cdef unsigned char r, g, b, a
with nogil:
for y in range(iy, iy+h):
for x in range(ix, ix+w):
index = (((y-iy) * w) + (x-ix)) * 4
r = image32[y][x].r
g = image32[y][x].g
b = image32[y][x].b
a = image32[y][x].a
buf[index] = b
buf[index+1] = g
buf[index+2] = r
buf[index+3] = a
try:
return buf[:size]
finally:
PyMem_Free(buf)