Skip to content

Instantly share code, notes, and snippets.

@macleginn
Created August 2, 2018 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save macleginn/017af827794724e42407c7cd45b51ff8 to your computer and use it in GitHub Desktop.
Save macleginn/017af827794724e42407c7cd45b51ff8 to your computer and use it in GitHub Desktop.
A piece of JavaScript code for selecting parts of images and displaying them in the original context. Composite shapes consisting of rectangles can be cropped to their bounding box.
<html>
<head>
<meta charset="utf8">
<title>Crop’n’display demo</title>
<style>
.overlay {
padding: 30px;
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
background: white;
opacity: 0.95;
filter: alpha(opacity=95);
z-index: 1000;
display: none;
grid-template-columns: 15% 1fr;
}
</style>
</head>
<body>
<table>
<tr>
<td style="vertical-align: top;">
<canvas id='canvas' width="500" height="500"></canvas>
<p>Click and drag to select a rectangular region.</p>
<p>Hold Shift when clicking to add a rectangle to the selection.</p>
<p>Press Escape to clear the selection.</p>
</td>
<td style="vertical-align: top; padding-left: 10px;">
<h2>Cropped fragment:</h2>
<div><img id="crop" src="" alt="" onclick="showCrop();"></div>
<p id="caption" style="visibility: hidden;">(Click on the fragment to see it in context.)</p>
</td>
</tr>
</table>
<div id="overlay" class="overlay" onclick="hideOverlay();">
<div style="grid-column: 2/3;">
<p>(Click on a blank space to hide the overlay.)</p>
<canvas id="overlaycanvas"></canvas>
</div>
</div>
<script>
function hideOverlay() {
document
.getElementById('overlay')
.style
.display = 'none';
}
let mouseData = {
drag: false,
startX: [],
startY: [],
endX: [],
endY: [],
lastClickTime: 0
};
// Draw all the rectangles from mouseData
// except for the last one, which is being
// modified
function redrawPrevious() {
if (mouseData.startX.length < 2)
return;
for (let i = 0; i < mouseData.startX.length-1; i++) {
ctx.fillRect(
mouseData.startX[i],
mouseData.startY[i],
mouseData.endX[i]-mouseData.startX[i],
mouseData.endY[i]-mouseData.startY[i]
);
}
}
// Update the selection rectangle
// and redraw the previous ones.
function updateRect() {
ctx.fillStyle = "rgba(255,255,255,0.4)";
ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawPrevious();
let nrect = mouseData.startX.length;
ctx.fillRect(
mouseData.startX[nrect-1],
mouseData.startY[nrect-1],
mouseData.endX[nrect-1]-mouseData.startX[nrect-1],
mouseData.endY[nrect-1]-mouseData.startY[nrect-1]
);
}
// Get the bounding box for all
// selection rectangles
function getBoundingBox() {
let boundingBox = {
minX: Infinity,
minY: Infinity,
maxX: 0,
maxY: 0
};
let nrect = mouseData.startX.length;
for (let i = 0; i < nrect; i++) {
if (Math.min(mouseData.startX[i], mouseData.endX[i]) < boundingBox.minX)
boundingBox.minX = Math.min(mouseData.startX[i], mouseData.endX[i]);
if (Math.min(mouseData.startY[i], mouseData.endY[i]) < boundingBox.minY)
boundingBox.minY = Math.min(mouseData.startY[i], mouseData.endY[i]);
if (Math.max(mouseData.startX[i], mouseData.endX[i]) > boundingBox.maxX)
boundingBox.maxX = Math.max(mouseData.startX[i], mouseData.endX[i])
if (Math.max(mouseData.startY[i], mouseData.endY[i]) > boundingBox.maxY)
boundingBox.maxY = Math.max(mouseData.startY[i], mouseData.endY[i])
}
return boundingBox
}
// Get shifted coordinates for drawing cropped images
// on the small canvas.
function getShiftedCoords(boundingBox) {
let xOffset = boundingBox.minX,
yOffset = boundingBox.minY,
startX = mouseData.startX.map(x => x - xOffset),
endX = mouseData.endX.map(x => x - xOffset),
startY = mouseData.startY.map(y => y - yOffset),
endY = mouseData.endY.map(y => y - yOffset);
return {
startX: startX,
startY: startY,
endX: endX,
endY: endY
}
}
// Access the image from a CORS-enabled source.
// Get offset rect for mouse-pointer coordinates.
let image = new Image(),
canvas = document.getElementById('canvas'),
rect = canvas.getBoundingClientRect();
image.crossOrigin = 'Anonymous';
image.src = 'http://eurasianphonology.info/static/stele.jpg';
let ctx = canvas.getContext('2d');
// New rectangles do not increase opacity
// if they overlap with the old ones
ctx.globalCompositeOperation = 'source-out';
image.onload = function() {
canvas.width = image.width;
canvas.height = image.height;
// We do not manipulate the image so it's
// easier and quicker to not make it part
// of the canvas. Use the Image created
// above for copying.
canvas.style.backgroundImage = 'url("http://eurasianphonology.info/static/stele.jpg")';
// // Clear everything on escape
document.onkeyup = function(e) {
if (e.keyCode == 27) {
mouseData.startX = [];
mouseData.startY = [];
mouseData.endX = [];
mouseData.endY = [];
mouseData.drag = false;
document.body.style.cursor = 'default';
updateRect();
}
}
canvas.onmousedown = function(e) {
if (mouseData.drag) {
mouseUp();
return
}
mouseData.drag = true;
document.body.style.cursor = 'crosshair';
// Start from scratch if shift is not pressed
if (!e.shiftKey) {
mouseData.startX = [];
mouseData.startY = [];
mouseData.endX = [];
mouseData.endY = [];
}
mouseData.startX.push(e.pageX-rect.left);
mouseData.endX.push(e.pageX-rect.left);
mouseData.startY.push(e.pageY-rect.top);
mouseData.endY.push(e.pageY-rect.top);
};
canvas.onmousemove = function(e) {
if (!mouseData.drag)
return
let nrect = mouseData.endX.length;
mouseData.endX[nrect-1] = e.pageX-rect.left;
mouseData.endY[nrect-1] = e.pageY-rect.top;
window.requestAnimationFrame(updateRect);
};
let mouseUp = function(e) {
if (!mouseData.drag)
return;
// Create an invisible canvas of a needed size,
// project the parts of the copied image you need,
// and then covert to dataUrl.
let invCanv = document.createElement('canvas'),
invCtx = invCanv.getContext('2d'),
boundingBox = getBoundingBox(),
shiftedCoords = getShiftedCoords(boundingBox);
invCanv.width = boundingBox.maxX-boundingBox.minX;
invCanv.height = boundingBox.maxY-boundingBox.minY;
for (let i = 0; i < shiftedCoords.startX.length; i++) {
invCtx.drawImage(
image,
mouseData.startX[i],
mouseData.startY[i],
mouseData.endX[i]-mouseData.startX[i],
mouseData.endY[i]-mouseData.startY[i],
shiftedCoords.startX[i],
shiftedCoords.startY[i],
shiftedCoords.endX[i]-shiftedCoords.startX[i],
shiftedCoords.endY[i]-shiftedCoords.startY[i]
)
}
let result = invCanv.toDataURL();
document
.getElementById('crop')
.src = result;
mouseData.drag = false;
document.body.style.cursor = 'default';
document
.getElementById('caption')
.style
.visibility = 'visible';
};
document.onmouseup = mouseUp;
}
function showCrop() {
let overlayCanvas = document.getElementById('overlaycanvas');
overlayCanvas.width = image.width;
overlayCanvas.height = image.height;
let overlayCtx = overlayCanvas.getContext('2d');
overlayCtx.drawImage(image,0,0);
overlayCtx.fillStyle = "rgba(255,0,0,0.3)";
for (let i = 0; i < mouseData.startX.length; i++)
overlayCtx.fillRect(
mouseData.startX[i],
mouseData.startY[i],
mouseData.endX[i]-mouseData.startX[i],
mouseData.endY[i]-mouseData.startY[i]
);
document
.getElementById('overlay')
.style
.display = 'grid';
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment