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
|
||||
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 = '''
|
||||
<!doctype html>
|
||||
<title>CatNote</title>
|
||||
@ -175,7 +186,7 @@ HTML_FORM = '''
|
||||
|
||||
label {
|
||||
font-size: 1em;
|
||||
margin-left: 1em;
|
||||
padding: 0 1em 0 1em;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
@ -247,6 +258,51 @@ HTML_FORM = '''
|
||||
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) {
|
||||
body {
|
||||
font-size: 1.28em !important; /* Big bump */
|
||||
@ -293,6 +349,14 @@ HTML_FORM = '''
|
||||
font-size: 1.16em !important;
|
||||
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>
|
||||
@ -305,6 +369,20 @@ HTML_FORM = '''
|
||||
<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>
|
||||
<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">
|
||||
<button type=submit name="generate">📷 Generar</button>
|
||||
<button type=submit name="print">🖨️ Imprimir</button>
|
||||
@ -354,11 +432,11 @@ def remove_uploaded_img():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def bleh_image_from_url(url):
|
||||
def bleh_image_from_url(url, dithering):
|
||||
resp = requests.get(url, stream=True)
|
||||
resp.raise_for_status()
|
||||
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)
|
||||
out, err = bleh.communicate(resp.content)
|
||||
if bleh.returncode != 0:
|
||||
@ -369,9 +447,9 @@ def bleh_image_from_url(url):
|
||||
img = img.resize((IMAGE_WIDTH, img.height), Image.LANCZOS)
|
||||
return img
|
||||
|
||||
def bleh_image_from_bytes(image_bytes):
|
||||
def bleh_image_from_bytes(image_bytes, dithering):
|
||||
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
|
||||
)
|
||||
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:
|
||||
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_bold = ImageFont.truetype(FONT_PATH_BOLD, 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)
|
||||
if tag[0] == 'image':
|
||||
try:
|
||||
image = bleh_image_from_url(tag[1])
|
||||
image = bleh_image_from_url(tag[1], dithering)
|
||||
lines_out.append(('image', image))
|
||||
except Exception as e:
|
||||
lines_out.append(('text', [('text', f"[Imagen inválida: {e}]")], font, FONT_SIZE))
|
||||
elif tag[0] == 'userimage':
|
||||
if uploaded_img_bytes:
|
||||
try:
|
||||
image = bleh_image_from_bytes(uploaded_img_bytes)
|
||||
image = bleh_image_from_bytes(uploaded_img_bytes, dithering)
|
||||
lines_out.append(('image', image))
|
||||
except Exception as e:
|
||||
lines_out.append(('text', [('text', f"[Error al procesar imagen]")], font, FONT_SIZE))
|
||||
@ -657,11 +735,23 @@ def index():
|
||||
else:
|
||||
remove_uploaded_img()
|
||||
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()
|
||||
image.save(buf, format="PNG")
|
||||
buf.seek(0)
|
||||
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" in request.form:
|
||||
try:
|
||||
@ -669,11 +759,10 @@ def index():
|
||||
image.save(tmpfile, format="PNG")
|
||||
tmpfile.flush()
|
||||
# Run the bleh command
|
||||
# You can set dither method here if desired
|
||||
result = subprocess.run([
|
||||
"./bleh",
|
||||
"-mode", "1bpp",
|
||||
"-intensity", "100",
|
||||
"-intensity", f"{intensity}",
|
||||
tmpfile.name
|
||||
], capture_output=True, text=True, timeout=90)
|
||||
if result.returncode != 0:
|
||||
@ -682,8 +771,8 @@ def index():
|
||||
printed = True
|
||||
except Exception as e:
|
||||
error = f"Failed to print: {e}"
|
||||
return render_template_string(HTML_FORM, img=img_data, default_md=md,
|
||||
printed=printed, error=error)
|
||||
return render_template_string(HTML_FORM, dithering_modes=DITHERING_MODES, img=img_data, default_md=md,
|
||||
printed=printed, error=error, current_dithering=session.get('dithering', 'floyd'))
|
||||
|
||||
@app.route('/manifest.json')
|
||||
def manifest():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user