How to resize then crop an image with canvas
I took this tutorial as an example http://tympanus.net/codrops/2014/10/30/resizing-cropping-images-canvas/ and did the same thing in vanilla js. It probably needs some refacturing but it is working (at least on my windows labtop in chrome)
One html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* styles for resize container and image */
.resize-container {
position: relative;
display: inline-block;
cursor: move;
margin: 0 auto;
top: 0;
left: 0;
}
.resize-container img {
display: block
}
.resize-container:hover img,
.resize-container:active img {
outline: 2px dashed rgba(222,60,80,.9);
}
/* styles for resize handles */
.resize-handle-ne,
.resize-handle-se,
.resize-handle-nw,
.resize-handle-sw {
position: absolute;
display: block;
width: 10px;
height: 10px;
background: rgba(222, 60, 80, .9);
z-index: 999;
}
.resize-handle-nw {
top: -5px;
left: -5px;
cursor: nw-resize;
}
.resize-handle-sw {
bottom: -5px;
left: -5px;
cursor: sw-resize;
}
.resize-handle-ne {
top: -5px;
right: -5px;
cursor: ne-resize;
}
.resize-handle-se {
bottom: -5px;
right: -5px;
cursor: se-resize;
}
.overlay {
position: absolute;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
z-index: 999;
width: 200px;
height: 200px;
border: solid 2px rgba(222,60,80,.9);
box-sizing: content-box;
pointer-events: none;
}
.overlay:after,
.overlay:before {
content: '';
position: absolute;
display: block;
width: 204px;
height: 40px;
border-left: dashed 2px rgba(222,60,80,.9);
border-right: dashed 2px rgba(222,60,80,.9);
}
.overlay:before {
top: 0;
margin-left: -2px;
margin-top: -40px;
}
.overlay:after {
bottom: 0;
margin-left: -2px;
margin-bottom: -40px;
}
.overlay-inner:after,
.overlay-inner:before {
content: '';
position: absolute;
display: block;
width: 40px;
height: 204px;
border-top: dashed 2px rgba(222,60,80,.9);
border-bottom: dashed 2px rgba(222,60,80,.9);
}
.overlay-inner:before {
left: 0;
margin-left: -40px;
margin-top: -2px;
}
.overlay-inner:after {
right: 0;
margin-right: -40px;
margin-top: -2px;
}
.btn-crop {
position: absolute;
vertical-align: bottom;
right: 5px;
bottom: 5px;
padding: 6px 10px;
z-index: 999;
background-color: rgb(222,60,80);
border: none;
border-radius: 5px;
color: #FFF;
}
#result {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 150vh;
z-index: -1;
display: flex;
align-items: flex-end;
}
</style>
</head>
<body>
<div id="resize-container" class="resize-container">
<span class="resize-handle resize-handle-nw"></span>
<span class="resize-handle resize-handle-ne"></span>
<span class="resize-handle resize-handle-sw"></span>
<span class="resize-handle resize-handle-se"></span>
<img id="img" class="resize-image" src="image.png" alt="Image" />
</div>
<div id="overlay" class="overlay">
<div class="overlay-inner">
</div>
</div>
<button id="js-crop" class="btn-crop">Crop</button>
<div id="result"></div>
<script>
const resizeableImage = image_target => {
// defining the variables
let constrain = false
const overlay = document.getElementById('overlay')
const cropBtn = document.getElementById('js-crop')
const container = document.getElementById('resize-container')
const orig_src = new Image()
const event_state = {}
const min_width = 60
const min_height = 60
const max_width = 800
const max_height = 900
const resize_canvas = document.createElement('canvas')
const resizeImage = (width, height) => {
resize_canvas.width = width
resize_canvas.height = height
resize_canvas.getContext('2d').drawImage(orig_src, 0, 0, width, height)
image_target.src = resize_canvas.toDataURL("image/png")
}
// the resizing function is where most of the action happens. This function is contansly invoked
// while the user is dragging one of the resize handles. Every time this function is called we
// work out the new with and height by taking the current position of the mouse relative to the
// initial position of the corner we are dragging
const resizing = e => {
let width, height, left, top
const rec = container.getBoundingClientRect()
const offset = {
top: rec.top + window.scrollY,
left: rec.left + window.scrollX
}
const mouse = {
x: (e.clientX || e.pageX || e.originalEvent.touches[0].clientX) + window.screenLeft,
y: (e.clientY || e.pageY || e.originalEvent.touches[0].clientY) + window.screenTop
}
const eTargetClass = event_state.evnt.target.classList[1]
// if statements for being able to resize from each corner
if (eTargetClass === 'resize-handle-se') { // south-east (bottom-right)
width = mouse.x - event_state.container_left
height = mouse.y - event_state.container_top
left = event_state.container_left
top = event_state.container_top
} else if (eTargetClass === 'resize-handle-sw') { // south-west (bottom-left)
width = event_state.container_width - (mouse.x - event_state.container_left)
height = mouse.y - event_state.container_top
left = mouse.x
top = event_state.container_top
} else if (eTargetClass === 'resize-handle-ne') { // north-east (top-right)
width = mouse.x - event_state.container_left
height = event_state.container_height - (mouse.y - event_state.container_top)
left = event_state.container_left
top = mouse.y
if(constrain || e.shiftKey){
top = mouse.y - ((width / orig_src.width * orig_src.height) - height)
}
} else if (eTargetClass === 'resize-handle-nw') { // north-west (top-left)
width = event_state.container_width - (mouse.x - event_state.container_left)
height = event_state.container_height - (mouse.y - event_state.container_top)
left = mouse.x
top = mouse.y
if(constrain || e.shiftKey){
top = mouse.y - ((width / orig_src.width * orig_src.height) - height)
}
}
if (constrain || e.shiftKey) {
height = width / orig_src.width * orig_src.height
}
if (width > min_width && height > min_height && width < max_width && height < max_height) {
container.style.top = `${top}px`
container.style.left = `${left}px`
resizeImage(width, height)
}
}
// before we start tracking the mouse position we want to take
// a snapshot of the container dimensions and other key data
// points. We store these in a variable named event_state and
// use them later as a point of reference while resizing to
// work out the change in height and width
const saveEventState = e => {
event_state.container_width = container.getBoundingClientRect().width
event_state.container_height = container.getBoundingClientRect().height
event_state.container_left = container.offsetLeft
event_state.container_top = container.offsetTop
// mouse coordinates
event_state.mouse_x = (e.clientX || e.pageX || e.originalEvent.touches[0].clientX) + window.screenLeft,
event_state.mouse_y = (e.clientY || e.pageY || e.originalEvent.touches[0].clientY) + window.screenTop,
event_state.evnt = e
}
// this two functions do very little other that tell the
// browser to start paying attention to where the mouse
// is moving and when to stop paying attention
const startResize = e => {
e.preventDefault()
e.stopPropagation()
saveEventState(e)
document.addEventListener('mousemove', resizing)
document.addEventListener('mouseup', endResize)
}
const endResize = e => {
e.preventDefault()
// The touchend event fires when one or more touch points are
// removed from the touch surface.
document.removeEventListener('mousemove', resizing)
document.removeEventListener('touchend', resizing)
document.removeEventListener('mouseup', endResize)
document.removeEventListener('touchmove', endResize)
}
// in this function we need to work out the new position of the top left edge of the
// container. This will be equal to the current position of the mouse, offset by the
// distance the mouse was from the top left corner when we started dragging the image
const moving = e => {
const mouse = {
x: (e.clientX || e.pageX) + window.screenLeft,
y: (e.clientY || e.pageY) + window.screenTop
}
container.style.left = `${mouse.x - (event_state.mouse_y - event_state.container_top)}px`
container.style.top = `${mouse.y - (event_state.mouse_y - event_state.container_top)}px`
}
// function for moving the image around
const startMoving = e => {
e.preventDefault()
e.stopPropagation()
saveEventState(e)
document.addEventListener('mousemove', moving)
document.addEventListener('mouseup', endMoving)
}
const endMoving = e => {
e.preventDefault()
document.removeEventListener('mouseup', endMoving)
document.removeEventListener('mousemove', moving)
}
const crop = e => {
const left = overlay.offsetLeft - container.offsetLeft
const top = overlay.offsetTop - container.offsetTop
const width = overlay.getBoundingClientRect().width
const height = overlay.getBoundingClientRect().height
const crop_canvas = document.createElement('canvas')
crop_canvas.width = width
crop_canvas.height = height
crop_canvas.getContext('2d').drawImage(image_target, left, top, width, height, 0, 0, width, height)
const croppedImage = document.createElement('img')
croppedImage.src = crop_canvas.toDataURL("image/png")
document.querySelector('#result').appendChild(croppedImage)
}
// this function is called immediately and makes a copy of the
// original image used for resizing
const init = () => {
// create a new image with a copy of the original src
// When resizing, we will always use this original copy
// as the base
orig_src.src = image_target.src
// add events
container.addEventListener('mousedown', startResize)
image_target.addEventListener('mousedown', startMoving)
cropBtn.addEventListener('click', crop)
}
init()
}
// initializing the canvas and the target image
const image = document.getElementById('img')
resizeableImage(image)
</script>
From the documentation, these are the parameters for drawImage
:
drawImage(image,
sx, sy, sw, sh,
dx, dy, dw, dh);
So, to crop the outer 10 pixels from the source image (Assuming it's 100 * 50
), and then to scale that up to 160*60
:
ctx.drawImage(image,
10, 10, // Start at 10 pixels from the left and the top of the image (crop),
80, 30, // "Get" a `80 * 30` (w * h) area from the source image (crop),
0, 0, // Place the result at 0, 0 in the canvas,
160, 60); // With as width / height: 160 * 60 (scale)
Example:
const image = new Image(),
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
image.src = 'https://i.stack.imgur.com/I4jXc.png';
image.addEventListener('load', () => {
ctx.drawImage(image,
70, 20, // Start at 70/20 pixels from the left and the top of the image (crop),
50, 50, // "Get" a `50 * 50` (w * h) area from the source image (crop),
0, 0, // Place the result at 0, 0 in the canvas,
100, 100); // With as width / height: 100 * 100 (scale)
});
Image: <br/>
<img src="https://i.stack.imgur.com/I4jXc.png" /><br/>
Canvas: <br/>
<canvas id="canvas" width="275px" height="95px"></canvas>
Before drawing onto the canvas, specify the final canvas size with canvas.width and canvas.height otherwise the final will always be 300x150