image support

This commit is contained in:
Robin Appelman 2025-10-06 21:54:21 +02:00
commit 25f9e9b0a1
3 changed files with 103 additions and 29 deletions

View file

@ -14,7 +14,8 @@
</tbody>
<tfoot>
<td>
<input type="button" value="Add Text" id="add"/>
<input type="button" value="Add Text" id="add-text"/>
<input type="button" value="Add Image" id="add-image"/>
</td>
</tfoot>
</table>

View file

@ -3,7 +3,8 @@ const elementsTable = document.getElementById('elements');
const preview = document.getElementById('preview');
const renderCtx = preview.getContext("2d");
const elementsTableBody = elementsTable.getElementsByTagName('tbody')[0];
const addButton = document.getElementById('add');
const addTextButton = document.getElementById('add-text');
const addImageButton = document.getElementById('add-image');
/** @type HTMLInputElement printButton */
const printButton = document.getElementById('print');
const widthInput = document.getElementById('canvas-width');
@ -14,6 +15,8 @@ let defaultFontSize = 24;
let backgroundColor = "white";
let textColor = "white";
let images = new Map();
const nextElementId = () => {
let rows = elementsTableBody.getElementsByTagName('tr');
if (rows.length === 0) {
@ -23,20 +26,7 @@ const nextElementId = () => {
}
};
const addElement = () => {
let id = nextElementId();
let row = document.createElement('tr');
row.dataset.elementId = id;
let textCol = document.createElement('td');
textCol.classList.add('text');
let textInput = document.createElement('input');
textInput.type = "text";
textInput.addEventListener('input', render);
textCol.appendChild(textInput);
row.appendChild(textCol);
const addElementControls = (row) => {
{
let sizeCol = document.createElement('td');
sizeCol.classList.add('size');
@ -44,7 +34,7 @@ const addElement = () => {
sizeInput.addEventListener('input', render);
sizeInput.type = "number";
sizeInput.value = defaultFontSize.toString();
sizeCol.appendChild(document.createTextNode("Front size"))
sizeCol.appendChild(document.createTextNode("Size"))
sizeCol.appendChild(sizeInput);
row.appendChild(sizeCol);
}
@ -55,12 +45,12 @@ const addElement = () => {
let posInputX = document.createElement('input');
posInputX.addEventListener('input', render);
posInputX.type = "number";
posInputX.value = "2";
posInputX.value = "0";
posInputX.classList.add('pos_x');
let posInputY = document.createElement('input');
posInputY.addEventListener('input', render);
posInputY.type = "number";
posInputY.value = "2";
posInputY.value = "0";
posInputY.classList.add('pos_y');
posCol.appendChild(document.createTextNode("Position"))
posCol.appendChild(posInputX);
@ -78,15 +68,67 @@ const addElement = () => {
removeCol.appendChild(removeInput);
row.appendChild(removeCol);
}
}
const addTextElement = () => {
let id = nextElementId();
let row = document.createElement('tr');
row.dataset.elementId = id;
row.dataset.type = 'text';
let textCol = document.createElement('td');
textCol.classList.add('text');
let textInput = document.createElement('input');
textInput.type = "text";
textInput.addEventListener('input', render);
textCol.appendChild(textInput);
row.appendChild(textCol);
addElementControls(row);
elementsTableBody.appendChild(row);
textInput.focus();
};
const loadImage = (event) => {
/** @type HTMLInputElement input */
const input = event.target;
const row = event.target.parentElement.parentElement;
let file = input.files[0];
if (file.type.startsWith('image')) {
let image = row.getElementsByTagName('img')[0];
image.addEventListener('load', render);
image.src = URL.createObjectURL(file);
}
};
const addImageElement = () => {
let id = nextElementId();
let row = document.createElement('tr');
row.dataset.elementId = id;
row.dataset.type = 'image';
let imageCol = document.createElement('td');
imageCol.classList.add('image');
let imageInput = document.createElement('input');
imageInput.type = 'file';
imageInput.addEventListener('change', loadImage);
let imageThumb = document.createElement('img');
imageCol.appendChild(imageInput);
imageCol.appendChild(imageThumb);
row.appendChild(imageCol);
addElementControls(row);
elementsTableBody.appendChild(row);
};
const removeElement = (event) => {
let row = event.target.parentElement.parentElement;
let id = row.dataset.elementId;
row.remove();
images.delete(id);
render();
};
@ -94,12 +136,23 @@ const getElements = () => {
let rows = elementsTableBody.getElementsByTagName('tr');
let elements = [];
for (let row of rows) {
if (row.dataset.type === 'text') {
elements.push({
type: 'text',
text: row.querySelector('.text input').value,
size: parseInt(row.querySelector('.size input').value, 10),
x: parseInt(row.querySelector('.pos_x').value, 10),
y: parseInt(row.querySelector('.pos_y').value, 10),
});
} else if (row.dataset.type === 'image') {
elements.push({
type: 'image',
image: row.getElementsByTagName('img')[0],
size: parseInt(row.querySelector('.size input').value, 10),
x: parseInt(row.querySelector('.pos_x').value, 10),
y: parseInt(row.querySelector('.pos_y').value, 10),
});
}
}
return elements;
}
@ -108,13 +161,26 @@ const render = () => {
let elements = getElements();
let width = preview.width;
let height = preview.height;
renderCtx.filter = '';
renderCtx.fillStyle = backgroundColor;
renderCtx.fillRect(0, 0, width, height);
renderCtx.fillStyle = textColor;
for (let element of elements) {
if (element.type === 'text') {
renderCtx.filter = '';
renderCtx.font = `${element.size}px serif`;
renderCtx.fillText(element.text, element.x, height - element.y);
let textSize = renderCtx.measureText(element.text);
let textHeight = textSize.fontBoundingBoxAscent - textSize.emHeightDescent;
renderCtx.fillText(element.text, element.x, element.y + textHeight);
} else if (element.type === 'image' && element.image.naturalWidth > 0) {
renderCtx.filter = 'grayscale() saturate(0%) brightness(70%) contrast(1000%)';
if (textColor === 'white') {
renderCtx.filter = renderCtx.filter + ' invert()';
}
let ratio = element.image.naturalWidth / element.image.naturalHeight;
renderCtx.drawImage(element.image, element.x, element.y, element.size * ratio, element.size);
}
}
}
@ -143,7 +209,8 @@ const print = () => {
}, 'image/png');
};
addButton.addEventListener('click', addElement);
addTextButton.addEventListener('click', addTextElement);
addImageButton.addEventListener('click', addImageElement);
printButton.addEventListener('click', print);
widthInput.addEventListener('input', resize);
@ -168,5 +235,5 @@ fetch(apiRoot + '/status')
textColor = status.text_color;
tapeWidthMM = status.media_width;
render();
addElement();
addTextElement();
})

View file

@ -7,6 +7,7 @@
:root {
--background-color: #1e1e1e;
--font-color: #fff;
color-scheme: dark;
}
}
@ -19,4 +20,9 @@ body, input {
.pos input {
width: 60px;
}
.image img {
height: 32px;
width: auto;
}
}