Added dithering, intensity controls
This commit is contained in:
parent
2dfb84b6cc
commit
d21f5f662f
115
app.py
115
app.py
@ -23,6 +23,17 @@ HEADER_SIZE_3 = 28
|
|||||||
IMAGE_WIDTH = 384
|
IMAGE_WIDTH = 384
|
||||||
BULLET_CHAR = "• "
|
BULLET_CHAR = "• "
|
||||||
|
|
||||||
|
DITHERING_MODES = {
|
||||||
|
"Sin dithering": "none",
|
||||||
|
"Floyd-Steinberg": "floyd",
|
||||||
|
"Bayer 2x2": "bayer2x2",
|
||||||
|
"Bayer 4x4": "bayer4x4",
|
||||||
|
"Bayer 8x8": "bayer8x8",
|
||||||
|
"Bayer 16x16": "bayer16x16",
|
||||||
|
"Atkinson": "atkinson",
|
||||||
|
"Jarvis-Judice-Ninke": "jjn"
|
||||||
|
}
|
||||||
|
|
||||||
HTML_FORM = '''
|
HTML_FORM = '''
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<title>CatNote</title>
|
<title>CatNote</title>
|
||||||
@ -175,7 +186,7 @@ HTML_FORM = '''
|
|||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
margin-left: 1em;
|
padding: 0 1em 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=file] {
|
input[type=file] {
|
||||||
@ -247,6 +258,51 @@ HTML_FORM = '''
|
|||||||
min-height: 12em;
|
min-height: 12em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: left;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background: #181c1f;
|
||||||
|
text-align: center;
|
||||||
|
text-align-last: center;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
color: #eee;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
select option {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
background: #181c1f;
|
||||||
|
text-align: center;
|
||||||
|
border: 1.5px solid #444;
|
||||||
|
color: #eee;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number]::-webkit-inner-spin-button,
|
||||||
|
input[type=number]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-dithering,
|
||||||
|
.options-intensity {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) {
|
||||||
body {
|
body {
|
||||||
font-size: 1.28em !important; /* Big bump */
|
font-size: 1.28em !important; /* Big bump */
|
||||||
@ -293,6 +349,14 @@ HTML_FORM = '''
|
|||||||
font-size: 1.16em !important;
|
font-size: 1.16em !important;
|
||||||
padding: inherit 2.2em
|
padding: inherit 2.2em
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
}
|
||||||
|
input[type=number] {
|
||||||
|
font-size: 1.16em !important;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -305,6 +369,20 @@ HTML_FORM = '''
|
|||||||
<textarea name=md placeholder="Ingrese Markdown aquí...">{{ default_md }}</textarea><br>
|
<textarea name=md placeholder="Ingrese Markdown aquí...">{{ default_md }}</textarea><br>
|
||||||
<label for="userimg" style="display: inline-block; margin-bottom: 0.5em;">Subir imagen (opcional):</label><br>
|
<label for="userimg" style="display: inline-block; margin-bottom: 0.5em;">Subir imagen (opcional):</label><br>
|
||||||
<input type="file" name="userimg" accept="image/png, image/jpeg"><br>
|
<input type="file" name="userimg" accept="image/png, image/jpeg"><br>
|
||||||
|
<div class="options">
|
||||||
|
<div class="options-dithering">
|
||||||
|
<label for="dithering">Dithering:</label>
|
||||||
|
<select name="dithering">
|
||||||
|
{% for label, mode in dithering_modes.items() %}
|
||||||
|
<option value="{{ mode }}" {% if mode == current_dithering %}selected="selected"{% endif %}> {{ label }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="options-intensity">
|
||||||
|
<label for="intensity">Intensidad:</label>
|
||||||
|
<input type="number" name="intensity" value="85" min="0" max="100" step="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type=submit name="generate">📷 Generar</button>
|
<button type=submit name="generate">📷 Generar</button>
|
||||||
<button type=submit name="print">🖨️ Imprimir</button>
|
<button type=submit name="print">🖨️ Imprimir</button>
|
||||||
@ -354,11 +432,11 @@ def remove_uploaded_img():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def bleh_image_from_url(url):
|
def bleh_image_from_url(url, dithering):
|
||||||
resp = requests.get(url, stream=True)
|
resp = requests.get(url, stream=True)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
bleh = subprocess.Popen(
|
bleh = subprocess.Popen(
|
||||||
["./bleh", "-o", "-", "-mode", "1bpp", "-d", "floyd", "-"],
|
["./bleh", "-o", "-", "-mode", "1bpp", "-d", f"{dithering}", "-"],
|
||||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
out, err = bleh.communicate(resp.content)
|
out, err = bleh.communicate(resp.content)
|
||||||
if bleh.returncode != 0:
|
if bleh.returncode != 0:
|
||||||
@ -369,9 +447,9 @@ def bleh_image_from_url(url):
|
|||||||
img = img.resize((IMAGE_WIDTH, img.height), Image.LANCZOS)
|
img = img.resize((IMAGE_WIDTH, img.height), Image.LANCZOS)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
def bleh_image_from_bytes(image_bytes):
|
def bleh_image_from_bytes(image_bytes, dithering):
|
||||||
bleh = subprocess.Popen(
|
bleh = subprocess.Popen(
|
||||||
["./bleh", "-o", "-", "-mode", "1bpp", "-d", "floyd", "-"],
|
["./bleh", "-o", "-", "-mode", "1bpp", "-d", f"{dithering}", "-"],
|
||||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
)
|
)
|
||||||
out, err = bleh.communicate(image_bytes)
|
out, err = bleh.communicate(image_bytes)
|
||||||
@ -481,7 +559,7 @@ def wrap_segments(segments, font, font_bold, font_italic, font_bolditalic, max_w
|
|||||||
if line:
|
if line:
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
def render(md, uploaded_img_bytes=None):
|
def render(md, dithering, uploaded_img_bytes=None):
|
||||||
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
||||||
font_bold = ImageFont.truetype(FONT_PATH_BOLD, FONT_SIZE)
|
font_bold = ImageFont.truetype(FONT_PATH_BOLD, FONT_SIZE)
|
||||||
font_italic = ImageFont.truetype(FONT_PATH_OBLIQUE, FONT_SIZE)
|
font_italic = ImageFont.truetype(FONT_PATH_OBLIQUE, FONT_SIZE)
|
||||||
@ -500,14 +578,14 @@ def render(md, uploaded_img_bytes=None):
|
|||||||
tag = parse_line(src_line)
|
tag = parse_line(src_line)
|
||||||
if tag[0] == 'image':
|
if tag[0] == 'image':
|
||||||
try:
|
try:
|
||||||
image = bleh_image_from_url(tag[1])
|
image = bleh_image_from_url(tag[1], dithering)
|
||||||
lines_out.append(('image', image))
|
lines_out.append(('image', image))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
lines_out.append(('text', [('text', f"[Imagen inválida: {e}]")], font, FONT_SIZE))
|
lines_out.append(('text', [('text', f"[Imagen inválida: {e}]")], font, FONT_SIZE))
|
||||||
elif tag[0] == 'userimage':
|
elif tag[0] == 'userimage':
|
||||||
if uploaded_img_bytes:
|
if uploaded_img_bytes:
|
||||||
try:
|
try:
|
||||||
image = bleh_image_from_bytes(uploaded_img_bytes)
|
image = bleh_image_from_bytes(uploaded_img_bytes, dithering)
|
||||||
lines_out.append(('image', image))
|
lines_out.append(('image', image))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
lines_out.append(('text', [('text', f"[Error al procesar imagen]")], font, FONT_SIZE))
|
lines_out.append(('text', [('text', f"[Error al procesar imagen]")], font, FONT_SIZE))
|
||||||
@ -657,11 +735,23 @@ def index():
|
|||||||
else:
|
else:
|
||||||
remove_uploaded_img()
|
remove_uploaded_img()
|
||||||
md = request.form["md"]
|
md = request.form["md"]
|
||||||
image = render(md, uploaded_img_bytes)
|
dithering = request.form.get("dithering", "floyd")
|
||||||
|
image = render(md, dithering, uploaded_img_bytes)
|
||||||
|
session['dithering'] = dithering
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
image.save(buf, format="PNG")
|
image.save(buf, format="PNG")
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
img_data = base64.b64encode(buf.getvalue()).decode()
|
img_data = base64.b64encode(buf.getvalue()).decode()
|
||||||
|
intensity = request.form.get("intensity", "85")
|
||||||
|
try:
|
||||||
|
intensity = int(intensity)
|
||||||
|
if intensity < 0 or intensity > 100:
|
||||||
|
raise ValueError("Intensity must be between 0 and 100")
|
||||||
|
except ValueError:
|
||||||
|
error = "Intensidad debe ser un número entre 0 y 100"
|
||||||
|
intensity = 85
|
||||||
|
# Store intensity in session for later use
|
||||||
|
session['intensity'] = intensity
|
||||||
# If print button pressed, send to driver
|
# If print button pressed, send to driver
|
||||||
if "print" in request.form:
|
if "print" in request.form:
|
||||||
try:
|
try:
|
||||||
@ -669,11 +759,10 @@ def index():
|
|||||||
image.save(tmpfile, format="PNG")
|
image.save(tmpfile, format="PNG")
|
||||||
tmpfile.flush()
|
tmpfile.flush()
|
||||||
# Run the bleh command
|
# Run the bleh command
|
||||||
# You can set dither method here if desired
|
|
||||||
result = subprocess.run([
|
result = subprocess.run([
|
||||||
"./bleh",
|
"./bleh",
|
||||||
"-mode", "1bpp",
|
"-mode", "1bpp",
|
||||||
"-intensity", "100",
|
"-intensity", f"{intensity}",
|
||||||
tmpfile.name
|
tmpfile.name
|
||||||
], capture_output=True, text=True, timeout=90)
|
], capture_output=True, text=True, timeout=90)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
@ -682,8 +771,8 @@ def index():
|
|||||||
printed = True
|
printed = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = f"Failed to print: {e}"
|
error = f"Failed to print: {e}"
|
||||||
return render_template_string(HTML_FORM, img=img_data, default_md=md,
|
return render_template_string(HTML_FORM, dithering_modes=DITHERING_MODES, img=img_data, default_md=md,
|
||||||
printed=printed, error=error)
|
printed=printed, error=error, current_dithering=session.get('dithering', 'floyd'))
|
||||||
|
|
||||||
@app.route('/manifest.json')
|
@app.route('/manifest.json')
|
||||||
def manifest():
|
def manifest():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user