Skip to content

Instantly share code, notes, and snippets.

@kwinkunks
Last active December 18, 2024 10:26
Show Gist options
  • Save kwinkunks/844a2e4eb3666ae0e2e41e2ba99c5cc2 to your computer and use it in GitHub Desktop.
Save kwinkunks/844a2e4eb3666ae0e2e41e2ba99c5cc2 to your computer and use it in GitHub Desktop.
Tiny image labeling app
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Label AI</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%220.9em%22 font-size=%2290%22>🔺</text></svg>">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
padding: 20px;
}
.footer {
color: #dedede;
}
hr {
border-color: #cdcdcd;
}
canvas {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.input-group {
margin-bottom: 20px;
}
#imageFrame {
position: relative;
height: 500px;
background-color: #f8f9fa;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
#imageFrame.dragover {
background-color: #eeeeff;
border-color: #0d6efd;
}
</style>
</head>
<body>
<div class="container">
<h1 class="mb-4 text-center">🔺 Label AI</h1>
<div id="imageFrame" class="border border-dashed border-3 rounded text-center p-5">
<p id="placeholderText" class="text-muted">
Click to upload or Ctrl-V to paste from the clipboard
</p>
<canvas id="canvas" class="d-none"></canvas>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="labelOption" id="generatedLabel" value="generated" checked>
<label class="form-check-label" for="generatedLabel">Generated by AI</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="labelOption" id="chooseModelLabel" value="model">
<label class="form-check-label" for="chooseModelLabel">Choose a model</label>
<select id="modelDropdown" class="form-select mt-2" disabled>
<option value="DALL-E">DALL-E</option>
<option value="Microsoft Copilot">Microsoft Copilot</option>
<option value="Midjourney">Midjourney</option>
<option value="Imagen">Imagen</option>
</select>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="labelOption" id="useAILabel" value="ai-label">
<label class="form-check-label" for="useAILabel">Use ai-label.org</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="labelOption" id="freeInputOption" value="free">
<label class="form-check-label" for="freeInputOption">Your own label</label>
<input class="form-checsk" type="text" id="freeInput" placeholder="Free text"/>
</div>
</div>
<div class="d-flex gap-2 mb-3">
<button id="labelButton" class="btn btn-primary flex-grow-1">Label it!</button>
<button id="clearButton" class="btn btn-danger flex-grow-2">Clear</button>
</div>
<div class="footer">
<hr />
&copy; 2024 Matt Hall, Equinor // <a href="https://gist.github.com/kwinkunks/844a2e4eb3666ae0e2e41e2ba99c5cc2">GitHub</a>
</div>
</div>
<script>
// Copyright 2024 Matt Hall, Equinor // MIT license
const labelButton = document.getElementById('labelButton');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const labelOptions = document.getElementsByName('labelOption');
const modelDropdown = document.getElementById('modelDropdown');
const freeInput = document.getElementById('freeInput');
const imageFrame = document.getElementById('imageFrame');
const placeholderText = document.getElementById('placeholderText');
let image = new Image();
let originalImageSrc = null; // Store the original image source
// Disable dropdown if not selected.
labelOptions.forEach(option => {
option.addEventListener('change', () => {
if (option.value === 'model') {
modelDropdown.disabled = false;
} else {
modelDropdown.disabled = true;
}
});
});
// Label It button
labelButton.addEventListener('click', () => {
const selectedOption = Array.from(labelOptions).find(option => option.checked).value;
const fontSize = canvas.width / 30;
if (selectedOption === 'generated') {
const label = "🔺 Generated by AI";
ctx.drawImage(image, 0, 0);
ctx.font = `${fontSize}px Arial`;
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.strokeText(label, fontSize, canvas.height - fontSize);
ctx.fillText(label, fontSize, canvas.height - fontSize);
} else if (selectedOption === 'model') {
const label = `🔺 Generated by ${modelDropdown.value}`;
ctx.drawImage(image, 0, 0);
ctx.font = `${fontSize}px Arial`;
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.strokeText(label, fontSize, canvas.height - fontSize);
ctx.fillText(label, fontSize, canvas.height - fontSize);
} else if (selectedOption === 'ai-label') {
const overlayImage = new Image();
const w = canvas.width / 8;
const h = w / 2.617;
overlayImage.crossOrigin = "anonymous";
overlayImage.src = "https://raw.githubusercontent.com/ecogex/ai-label/refs/heads/main/image-pack/ai-label_banner-made-with-ai.svg";
overlayImage.onload = () => {
ctx.drawImage(image, 0, 0);
ctx.drawImage(overlayImage, canvas.width - w - fontSize, canvas.height - h - fontSize, w, w/2.617);
};
} else if (selectedOption === 'free') {
const label = `${freeInput.value}`;
ctx.drawImage(image, 0, 0);
ctx.font = `${fontSize}px Arial`;
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.strokeText(label, fontSize, canvas.height - fontSize);
ctx.fillText(label, fontSize, canvas.height - fontSize);
}
});
// Clear button
document.getElementById('clearButton').addEventListener('click', () => {
if (originalImageSrc) {
loadImageFromURL(originalImageSrc);
} else {
placeholderText.classList.remove('d-none');
canvas.classList.add('d-none');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
});
// Image loading options.
// Click to upload...
imageFrame.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.onchange = (event) => {
loadImageFromFile(event.target.files[0]);
};
fileInput.click();
});
// Drag-and-drop
imageFrame.addEventListener('dragover', (event) => {
event.preventDefault();
imageFrame.classList.add('dragover');
});
imageFrame.addEventListener('dragleave', () => {
imageFrame.classList.remove('dragover');
});
imageFrame.addEventListener('drop', (event) => {
event.preventDefault();
imageFrame.classList.remove('dragover');
const file = event.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
loadImageFromFile(file);
}
});
// Ctrl-V
document.addEventListener('paste', (event) => {
const items = event.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) {
const blob = items[i].getAsFile();
loadImageFromBlob(blob);
break;
}
}
});
// Load image from a file
function loadImageFromFile(file) {
const reader = new FileReader();
reader.onload = (e) => {
loadImageFromURL(e.target.result);
};
reader.readAsDataURL(file);
}
// Load image from a blob
function loadImageFromBlob(blob) {
const reader = new FileReader();
reader.onload = (e) => {
loadImageFromURL(e.target.result);
};
reader.readAsDataURL(blob);
}
function loadImageFromURL(url) {
const image = new Image();
image.onload = () => {
placeholderText.classList.add('d-none');
canvas.classList.remove('d-none');
originalImageSrc = url; // Save original
canvas.width = image.width;
canvas.height = image.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
};
image.src = url;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment