Created
August 2, 2018 16:14
-
-
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.
This file contains 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
<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