Last active
December 18, 2024 10:26
-
-
Save kwinkunks/844a2e4eb3666ae0e2e41e2ba99c5cc2 to your computer and use it in GitHub Desktop.
Tiny image labeling app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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 /> | |
© 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