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> </tbody>
<tfoot> <tfoot>
<td> <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> </td>
</tfoot> </tfoot>
</table> </table>

View file

@ -3,7 +3,8 @@ const elementsTable = document.getElementById('elements');
const preview = document.getElementById('preview'); const preview = document.getElementById('preview');
const renderCtx = preview.getContext("2d"); const renderCtx = preview.getContext("2d");
const elementsTableBody = elementsTable.getElementsByTagName('tbody')[0]; 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 */ /** @type HTMLInputElement printButton */
const printButton = document.getElementById('print'); const printButton = document.getElementById('print');
const widthInput = document.getElementById('canvas-width'); const widthInput = document.getElementById('canvas-width');
@ -14,6 +15,8 @@ let defaultFontSize = 24;
let backgroundColor = "white"; let backgroundColor = "white";
let textColor = "white"; let textColor = "white";
let images = new Map();
const nextElementId = () => { const nextElementId = () => {
let rows = elementsTableBody.getElementsByTagName('tr'); let rows = elementsTableBody.getElementsByTagName('tr');
if (rows.length === 0) { if (rows.length === 0) {
@ -23,20 +26,7 @@ const nextElementId = () => {
} }
}; };
const addElement = () => { const addElementControls = (row) => {
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);
{ {
let sizeCol = document.createElement('td'); let sizeCol = document.createElement('td');
sizeCol.classList.add('size'); sizeCol.classList.add('size');
@ -44,7 +34,7 @@ const addElement = () => {
sizeInput.addEventListener('input', render); sizeInput.addEventListener('input', render);
sizeInput.type = "number"; sizeInput.type = "number";
sizeInput.value = defaultFontSize.toString(); sizeInput.value = defaultFontSize.toString();
sizeCol.appendChild(document.createTextNode("Front size")) sizeCol.appendChild(document.createTextNode("Size"))
sizeCol.appendChild(sizeInput); sizeCol.appendChild(sizeInput);
row.appendChild(sizeCol); row.appendChild(sizeCol);
} }
@ -55,12 +45,12 @@ const addElement = () => {
let posInputX = document.createElement('input'); let posInputX = document.createElement('input');
posInputX.addEventListener('input', render); posInputX.addEventListener('input', render);
posInputX.type = "number"; posInputX.type = "number";
posInputX.value = "2"; posInputX.value = "0";
posInputX.classList.add('pos_x'); posInputX.classList.add('pos_x');
let posInputY = document.createElement('input'); let posInputY = document.createElement('input');
posInputY.addEventListener('input', render); posInputY.addEventListener('input', render);
posInputY.type = "number"; posInputY.type = "number";
posInputY.value = "2"; posInputY.value = "0";
posInputY.classList.add('pos_y'); posInputY.classList.add('pos_y');
posCol.appendChild(document.createTextNode("Position")) posCol.appendChild(document.createTextNode("Position"))
posCol.appendChild(posInputX); posCol.appendChild(posInputX);
@ -78,15 +68,67 @@ const addElement = () => {
removeCol.appendChild(removeInput); removeCol.appendChild(removeInput);
row.appendChild(removeCol); 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); elementsTableBody.appendChild(row);
textInput.focus(); 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) => { const removeElement = (event) => {
let row = event.target.parentElement.parentElement; let row = event.target.parentElement.parentElement;
let id = row.dataset.elementId;
row.remove(); row.remove();
images.delete(id);
render(); render();
}; };
@ -94,12 +136,23 @@ const getElements = () => {
let rows = elementsTableBody.getElementsByTagName('tr'); let rows = elementsTableBody.getElementsByTagName('tr');
let elements = []; let elements = [];
for (let row of rows) { for (let row of rows) {
if (row.dataset.type === 'text') {
elements.push({ elements.push({
type: 'text',
text: row.querySelector('.text input').value, text: row.querySelector('.text input').value,
size: parseInt(row.querySelector('.size input').value, 10), size: parseInt(row.querySelector('.size input').value, 10),
x: parseInt(row.querySelector('.pos_x').value, 10), x: parseInt(row.querySelector('.pos_x').value, 10),
y: parseInt(row.querySelector('.pos_y').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; return elements;
} }
@ -108,13 +161,26 @@ const render = () => {
let elements = getElements(); let elements = getElements();
let width = preview.width; let width = preview.width;
let height = preview.height; let height = preview.height;
renderCtx.filter = '';
renderCtx.fillStyle = backgroundColor; renderCtx.fillStyle = backgroundColor;
renderCtx.fillRect(0, 0, width, height); renderCtx.fillRect(0, 0, width, height);
renderCtx.fillStyle = textColor; renderCtx.fillStyle = textColor;
for (let element of elements) { for (let element of elements) {
if (element.type === 'text') {
renderCtx.filter = '';
renderCtx.font = `${element.size}px serif`; 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'); }, 'image/png');
}; };
addButton.addEventListener('click', addElement); addTextButton.addEventListener('click', addTextElement);
addImageButton.addEventListener('click', addImageElement);
printButton.addEventListener('click', print); printButton.addEventListener('click', print);
widthInput.addEventListener('input', resize); widthInput.addEventListener('input', resize);
@ -168,5 +235,5 @@ fetch(apiRoot + '/status')
textColor = status.text_color; textColor = status.text_color;
tapeWidthMM = status.media_width; tapeWidthMM = status.media_width;
render(); render();
addElement(); addTextElement();
}) })

View file

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