Files
limoka/SunnexGB/Heroku-Modules/Assets/ASCII/ascii-lib.py
2026-06-12 08:36:40 +00:00

111 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# requires: Pillow numpy
# Дикие оправдания по поводу именно этого ассета а точнее кода в нем,честно я не знаю что сказать была попытка переписать JS на Py и как бы особых проблем не было,
# до момента пост-обработки на помощь я позвал Claude и он не решил мою проблему от слова совсем,так как в целом я своего рода призираю пилоу,а модуль мне хотелось
# написать я примерно вайб-кодил около 50 минут и я уверен из за этого будет возможно много проблем,в итоге благодаря немного копанию в коде,я нашел проблему и уже
# начал ее решать,НО я опять же вообще не понимал как сделать то что мне нужно,в интернете были сюрсы но будто бы тот или иной мне не подходили? Я не знаю почему я
# дропнул эту идею. Потом я стал искать в JS-е что там вообще можно сделать,в итоге я там импортировал модель какую то блядскую не нужную и опять впустую время
# потратил,думал что тут определено есть решение и снова пошел к ии,вывод опятьь 0 помощи,я не знаю почему я так вцепился лишь в 1 идею.Как бы я мог упростить все,
# даже наверное просто попросив какую то флагмен ии написать модуль и переписать его,но я уже на тот момент по моему мнению сделал много и не хотел ни каким образом
# оставлять это,поэтому через время я нашел сайты которые в целом давали возможность настраивать фильтр,была переделана логика(в целом ее переделал на 60 процентов
# клод,я просто убирал мусор который он испражнял.И вот дальше точно бред я убил более дня на решение проблем которые были решены мной,но результат мне не нравился
# И ОПЯТЬ я пошел просить помощи у гугла,потом понял что возможно даже будет легко(по факту легко,но я ленивый) пока искал,мне перехотелось и я уже потом пытался
# сделать режимы в модуле,что оказалось ужасом ведь они работали,но при возможности гармонично вписать их в код были конфликты И Я В ОЧЕРЕДНОЙ РАЗ ПОШЕЛ К ИИ,спойлер
# он не смог написать лучше чем я,в итоге я отбросил эту идею и думаю в целом никак больше не апдейтать модуль по крупному.
# Да это были оправдания,но зато какие!
import io
import numpy as np
from PIL import Image, ImageFilter, ImageEnhance, ImageOps
from .. import loader
BASE = 0x2800
INVERT_MAP = {chr(BASE + c): chr(BASE + (c ^ 0xFF)) for c in range(256)}
class AsciiLib(loader.Library):
developer = "@SunnexGB"
def resize(self, img):
if img.width > 768:
img = img.resize((768, int(img.height * 768 / img.width)), Image.LANCZOS)
w = img.width - img.width % 4
h = img.height - img.height % 4
if w != img.width or h != img.height:
img = img.resize((w, h), Image.LANCZOS)
return img
def mode(self, img, threshold, contrast):
gray = img.convert("L")
edges = ImageOps.invert(gray.filter(ImageFilter.FIND_EDGES))
contrast_img = ImageEnhance.Contrast(img).enhance(contrast).convert("L")
e = np.array(edges, dtype=np.float32) / 255.0
c = np.array(contrast_img, dtype=np.float32) / 255.0
blended = Image.fromarray((e * c * 255).astype(np.uint8), "L")
t = int(threshold * 255)
processed = blended.point(lambda p: 255 if p > t else 0, "L")
return processed, t
def braille(self, img, threshold, width):
cw = width * 2
o = -(-round(cw * img.height / img.width) // 4)
ch = 4 * o
px = np.array(img.resize((cw, ch), Image.LANCZOS).convert("L"))
order = [(0,0),(1,0),(2,0),(0,1),(1,1),(2,1),(3,0),(3,1)]
rows = []
for rs in range(0, ch, 4):
line = []
for cs in range(0, cw, 2):
grays = [
int(px[rs+dy, cs+dx]) if (rs+dy < ch and cs+dx < cw) else 255
for dy, dx in order
]
bits = list(reversed([1 if g < threshold else 0 for g in grays]))
code = int("".join(str(b) for b in bits), 2)
line.append(chr(BASE + code))
rows.append("".join(line))
return rows
def trim(self, lines):
blank = "\u2800"
while lines and all(c == blank for c in lines[0]):
lines = lines[1:]
while lines and all(c == blank for c in lines[-1]):
lines = lines[:-1]
if not lines:
return lines
left = min(next((i for i,c in enumerate(r) if c!=blank), len(r)) for r in lines)
right = min(next((i for i,c in enumerate(reversed(r)) if c!=blank), len(r)) for r in lines)
return [r[left: len(r)-right if right else len(r)] for r in lines]
def invert(self, lines):
return ["".join(INVERT_MAP.get(c,c) for c in l) for l in lines]
def fit(self, img, threshold, chars, width):
lo, hi = 5, 200
best = ""
for _ in range(14):
mid = (lo + hi)//2
lines = self.trim(self.braille(img, threshold, mid))
art = "\n".join(lines)
if len(art) <= chars:
best = art
lo = mid + 1
else:
hi = mid - 1
return best
def convert(self, data, width=50, threshold=0.65, contrast_boost=2.0, invert=False, target_chars=0):
buf = io.BytesIO(data)
img = Image.open(buf)
img.load()
buf.close()
img = img.convert("RGB")
img = self.resize(img)
processed, t = self.mode(img, threshold, contrast_boost)
if target_chars > 0:
art = self.fit(processed, t, target_chars, width)
else:
art = "\n".join(self.trim(self.braille(processed, t, width)))
if invert and art:
art = "\n".join(self.invert(art.split("\n")))
return art