Skip to content

Instantly share code, notes, and snippets.

@soruly
Created June 10, 2026 02:49
Show Gist options
  • Select an option

  • Save soruly/11bcb33f96728a9a62c1feda8c244143 to your computer and use it in GitHub Desktop.

Select an option

Save soruly/11bcb33f96728a9a62c1feda8c244143 to your computer and use it in GitHub Desktop.
detect black borders using canvas with vanilla js
<style>
body {
font-family: monospace;
}
header {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
}
main {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
canvas {
max-width: 100%;
}
.item {
display: flex;
flex-direction: column;
}
</style>
<body>
<header>
<div>
<label for="colorTolerance">Black Tolerance (0-255): </label>
<input type="number" id="colorTolerance" value="5" min="0" max="255" />
<span>How black is considered black.</span>
</div>
<div>
<label for="blackPercentage">Black Percentage (0-100): </label>
<input type="number" id="blackPercentage" value="95" min="0" max="100" />
<span>% of pixels in a row/col needs to be "black" to be considered as a black row/col.</span>
<span>Scan outward from the center, but if center is dark, scan from the edge</span>
</div>
</header>
<main></main>
</body>
<script>
function getVideoFrameRect(imgElement, colorTolerance = 5, blackPercentage = 0.95) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const width = imgElement.naturalWidth;
const height = imgElement.naturalHeight;
canvas.width = width;
canvas.height = height;
ctx.drawImage(imgElement, 0, 0);
const data = ctx.getImageData(0, 0, width, height).data;
// Helper to check if a pixel is dark
function isDark(x, y) {
const i = (y * width + x) * 4;
return (
data[i] <= colorTolerance && data[i + 1] <= colorTolerance && data[i + 2] <= colorTolerance
);
}
// Helper: Check if a specific row is mostly dark
function isRowDark(y) {
let darkPixelCount = 0;
for (let x = 0; x < width; x++) {
if (isDark(x, y)) darkPixelCount++;
}
return darkPixelCount > width * blackPercentage;
}
// Helper: Check if a specific column is mostly dark
function isColDark(x) {
let darkPixelCount = 0;
for (let y = 0; y < height; y++) {
if (isDark(x, y)) darkPixelCount++;
}
return darkPixelCount > height * blackPercentage;
}
let top, bottom, left, right;
// Scan outward from the center, but if center is dark, scan from the edge
const centerY = Math.floor(height / 2);
const centerX = Math.floor(width / 2);
if (!isDark(centerX, centerY)) {
top = centerY;
bottom = centerY;
left = centerX;
right = centerX;
while (top > 0 && !isRowDark(top - 1)) top--;
while (bottom < height - 1 && !isRowDark(bottom + 1)) bottom++;
while (left > 0 && !isColDark(left - 1)) left--;
while (right < width - 1 && !isColDark(right + 1)) right++;
} else {
top = 0;
bottom = height - 1;
left = 0;
right = width - 1;
while (top < height && isRowDark(top)) top++;
while (bottom > top && isRowDark(bottom)) bottom--;
while (left < width && isColDark(left)) left++;
while (right > left && isColDark(right)) right--;
}
return {
x: left,
y: top,
width: right - left + 1,
height: bottom - top + 1,
};
}
const getNearestAspectRatio = (width, height, targetAspectRatios, threshold = 0.05) => {
const aspectRatio = width / height;
let bestRatio = null;
let minDiff = Infinity;
for (const targetRatio of targetAspectRatios) {
const diff = Math.abs(aspectRatio - targetRatio);
if (diff < minDiff) {
minDiff = diff;
bestRatio = targetRatio;
}
}
if (minDiff <= threshold) {
return bestRatio;
}
return null;
};
const input = [
"Gv46fTDa4AIdIGY.jpg",
"photo_2025-07-16_01-42-49.jpg",
"photo_2025-07-16_01-59-53.jpg",
"photo_2025-07-16_02-41-40.jpg",
"photo_2025-07-16_01-59-51.jpg",
"photo_2025-07-16_02-00-02.jpg",
"photo_2025-07-16_01-42-55.jpg",
"photo_2025-07-16_01-42-39.jpg",
"photo_2025-07-16_01-42-18.jpg",
"photo_2025-07-16_01-43-01.jpg",
"photo_2025-07-16_01-59-31.jpg",
"photo_2025-07-16_01-42-35.jpg",
"photo_2025-07-16_01-42-41.jpg",
"dark-bad.jpg",
"photo_2025-07-16_01-43-01 - Copy.jpg",
"distorted-good.jpg",
"cropped-good.jpg",
];
const images = [];
for (const image of input) {
const img = new Image();
const item = { name: image, img, loaded: false };
images.push(item);
img.onload = () => {
item.loaded = true;
runDetect();
};
img.src = "input/" + image;
}
const aspectRatioText = {};
aspectRatioText[4 / 3] = "4:3";
aspectRatioText[16 / 9] = "16:9";
aspectRatioText[21 / 9] = "21:9";
function runDetect() {
document.querySelector("main").innerHTML = "";
for (const item of images) {
if (!item.loaded) continue;
const img = item.img;
let x = 0;
let y = 0;
let width = img.naturalWidth;
let height = img.naturalHeight;
const aspectRatio = getNearestAspectRatio(
img.naturalWidth,
img.naturalHeight,
[4 / 3, 16 / 9, 21 / 9],
0.05,
);
const box = getVideoFrameRect(
img,
parseInt(document.getElementById("colorTolerance").value),
parseInt(document.getElementById("blackPercentage").value) / 100,
);
x = box.x;
y = box.y;
width = box.width;
height = box.height;
const detectedAspectRatio = getNearestAspectRatio(
width,
height,
[4 / 3, 16 / 9, 21 / 9],
0.05,
);
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
ctx.strokeStyle = "red";
ctx.lineWidth = 3;
ctx.strokeRect(x, y, width, height);
const div = document.createElement("div");
div.className = "item";
div.appendChild(canvas);
div.appendChild(
document.createTextNode(
`(${aspectRatio ? aspectRatioText[aspectRatio] : "n/a"}) ${
img.naturalWidth
}x${img.naturalHeight} => ${width}x${height} (${detectedAspectRatio ? aspectRatioText[detectedAspectRatio] : "n/a"})`,
),
);
document.querySelector("main").appendChild(div);
}
}
document.addEventListener("input", (e) => {
runDetect();
});
document.addEventListener("DOMContentLoaded", () => {
runDetect();
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment