I had a situation where I had no backend server to handle the crop and resizing of an image. I know of a few cloud services which could do this easily however they come with a cost. So I thought to implement the solution within the browser itself. Before we proceed, let we warn you that this might not be an optimal solution for a page with a lot of images and which might require the use of web worker to offload the task to worker threads.
So let’s start with the process of resizing the image using JIMP - The “JavaScript Image Manipulation Program” :-)
To load JIMP in browser we are going to use unpkg CDN. If you are not familiar with unpkg or how to load any npm package in browser, please refer to tutorial, How to load any npm module in browser
To load JIMP using unkpg, we create a script tag
<script src="https://unpkg.com/jimp@0.14.0/browser/lib/jimp.js"></script>
Next, we create simple markup where original and cropped images will be loaded
<div id="container"> <div id="before"> <h3>Original</h3> <div class="image-container"> <img src="" /> </div> </div> <div id="afterCrop"> <h3>After Crop</h3> <div class="image-container"><img src="" /></div> </div> </div>
and little bit of styling :-)
body { font-family: sans-serif; } #container { display: flex; } #before, #afterCrop { padding: 10px; } #before { background: lightcyan; } #afterCrop { background: powderblue; } span { position: absolute; color: #fff; background: #000; opacity: 0.4; } .image-container { border: 1px dashed grey; padding: 0; margin: 0; }
Since we are going to use the same image source in HTML markup and JavaScript, I preferred to store the same in a JavaScript variable. In the future, if we want to change the image URL, it will be easy for us to swap this image with any other image by just changing the source at a single location.
const imageUrl = 'https://images.unsplash.com/photo-1595303526913-c7037797ebe7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=640&q=80' // handlers for before and after image elements const before = document.querySelector('#before img') const afterCrop = document.querySelector('#afterCrop img') // load image for original container element before.src = imageUrl
Now coming to the interesting stuff let’s play with JIMP to crop the image.
First, we need to load the image with Jimp.read()
method that returns a promise.
Jimp.read({ url: imageUrl, }) .then((image) => { // Crop logic goes here }) .catch((error) => { console.log(`Error loading image -> ${error}`) })
The Jimp.read()
return Jimp
object. If we log the image
variable on the console, returned by Jimp.read()
method we should see output something similar to below image
Jimp prototype chain contains a method crop
which we will use in the next step to crop the image.
Once we have Jimp object, we can pass the x
, y
, w
, h
as arguments to define crop region inside image.crop()
method which would return Promise
.
// rest of code removed for brevity ... .then((image) => { image.crop(80, 125, 100, 200) }) ...
Once the Promise to crop an image is resolved, we can use then()
method to get the base64
image using the method getBase64()
. The getBase64()
method accepts, mime
and callback
as first and second arguments respectively.
// rest of code removed for brevity ... .then((image) => { image.crop(80, 125, 100, 200) .then("image/png", (err, res) => { afterCrop.src = res; }) }) ...
In the callback arrow function, we are setting a source of second image element that is stored in variable afterCrop
.
This step is optional as I want to demonstrate how we can compare the size of the original with cropped one.
To display the dimension of the image, I used for loop
to create a span
element. The span
element will be displayed on top of the existing images on the top left corner with the respective image size as the text.
// print width and height of each image const images = document.querySelectorAll('img') images.forEach((i) => { let img = new Image() img.src = i.src img.onload = function () { let span = document.createElement('span') let spanText = document.createTextNode(`${img.width}X${img.height}`) span.appendChild(spanText) i.insertAdjacentElement('beforebegin', span) } })
const imageUrl = 'https://images.unsplash.com/photo-1595303526913-c7037797ebe7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=640&q=80' // handlers for before and after image elements const before = document.querySelector('#before img') const afterCrop = document.querySelector('#afterCrop img') // load image for original container element before.src = imageUrl Jimp.read({ url: imageUrl, }) .then((image) => { image.crop(80, 125, 100, 200).getBase64('image/png', (err, res) => { afterCrop.src = res // print width and height of each image const images = document.querySelectorAll('img') images.forEach((i) => { let img = new Image() img.src = i.src img.onload = function () { let span = document.createElement('span') let spanText = document.createTextNode(`${img.width}X${img.height}`) span.appendChild(spanText) i.insertAdjacentElement('beforebegin', span) } }) }) }) .catch((error) => { console.log(`Error loading image -> ${error}`) })
As we can see the size of original image was 640x400
whereas the size of new image is 100x200