Skip to content

Instantly share code, notes, and snippets.

@zuriby
Created January 11, 2025 05:40
Show Gist options
  • Save zuriby/4fc57e67c48624fe2d6217566ab15b50 to your computer and use it in GitHub Desktop.
Save zuriby/4fc57e67c48624fe2d6217566ab15b50 to your computer and use it in GitHub Desktop.
Sort image by its pixels colors
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pixel Sorter</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 20px;
}
.input-container {
margin-bottom: 20px;
}
.canvas-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 20px;
}
.canvas-wrapper {
border: 1px solid #ddd;
padding: 10px;
border-radius: 4px;
}
canvas {
max-width: 100%;
height: auto;
}
h2 {
color: #666;
font-size: 1.2em;
margin-bottom: 10px;
}
input[type="file"] {
display: block;
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
width: 100%;
box-sizing: border-box;
}
.sort-options {
margin: 10px 0;
}
select {
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
margin-right: 10px;
}
button {
padding: 8px 16px;
background: #4a90e2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #357abd;
}
</style>
</head>
<body>
<div class="container">
<h1>Pixel Sorter</h1>
<div class="input-container">
<input type="file" id="imageInput" accept="image/*">
<div class="sort-options">
<select id="sortMode">
<option value="luminance">Sort by Luminance</option>
<option value="hue">Sort by Hue</option>
<option value="both">Sort by Both</option>
</select>
<button id="sortButton">Sort Pixels</button>
</div>
</div>
<div class="canvas-container">
<div class="canvas-wrapper">
<h2>Original Image</h2>
<canvas id="originalCanvas"></canvas>
</div>
<div class="canvas-wrapper">
<h2>Sorted Pixels</h2>
<canvas id="sortedCanvas"></canvas>
</div>
</div>
</div>
<script>
const imageInput = document.getElementById('imageInput');
const sortMode = document.getElementById('sortMode');
const sortButton = document.getElementById('sortButton');
const originalCanvas = document.getElementById('originalCanvas');
const sortedCanvas = document.getElementById('sortedCanvas');
const originalCtx = originalCanvas.getContext('2d');
const sortedCtx = sortedCanvas.getContext('2d');
let currentImageData = null;
function rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h * 360, s * 100, l * 100];
}
function getLuminance(r, g, b) {
return 0.299 * r + 0.587 * g + 0.114 * b;
}
function sortPixels(imageData, mode) {
const pixels = [];
const { data, width, height } = imageData;
// Convert ImageData to array of pixels
for (let i = 0; i < data.length; i += 4) {
pixels.push({
r: data[i],
g: data[i + 1],
b: data[i + 2],
a: data[i + 3],
index: i
});
}
// Sort pixels based on selected mode
pixels.sort((a, b) => {
const [h1, s1, l1] = rgbToHsl(a.r, a.g, a.b);
const [h2, s2, l2] = rgbToHsl(b.r, b.g, b.b);
switch (mode) {
case 'luminance':
return getLuminance(b.r, b.g, b.b) - getLuminance(a.r, a.g, a.b);
case 'hue':
return h1 - h2;
case 'both':
const lumDiff = getLuminance(b.r, b.g, b.b) - getLuminance(a.r, a.g, a.b);
return Math.abs(lumDiff) > 1 ? lumDiff : h1 - h2;
default:
return 0;
}
});
// Create new ImageData with sorted pixels
const sortedData = new Uint8ClampedArray(data.length);
pixels.forEach((pixel, i) => {
const offset = i * 4;
sortedData[offset] = pixel.r;
sortedData[offset + 1] = pixel.g;
sortedData[offset + 2] = pixel.b;
sortedData[offset + 3] = pixel.a;
});
return new ImageData(sortedData, width, height);
}
imageInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
// Set canvas dimensions
originalCanvas.width = img.width;
originalCanvas.height = img.height;
sortedCanvas.width = img.width;
sortedCanvas.height = img.height;
// Draw original image
originalCtx.drawImage(img, 0, 0);
currentImageData = originalCtx.getImageData(0, 0, img.width, img.height);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
});
sortButton.addEventListener('click', () => {
if (currentImageData) {
const mode = sortMode.value;
const sortedImageData = sortPixels(currentImageData, mode);
sortedCtx.putImageData(sortedImageData, 0, 0);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment