Convert JPG images to PNG using HTML5 URL and Canvas

This article will show you how to convert JPG files to PNG using HTML5 Canvas API.

The assumption is that you are to some extend familiar with HTML and JS. If that is not the case, don’t worry! We will try to explain as much as possible.

So let’s get started!

Firstly, let’s create a very simple HTML markup with a image file field as below:

<!DOCTYPE html>
<html>
<head>
<title>JPG to PNG</title>
<style type="text/css">
body {
margin: 0;
padding: 0;
height: 100%;
text-align: center;
}
    .form-control {
margin-top: 20px;
}
</style>
</head>
<body>
<section class="convertor">
<div class="form-control">
<label>JPG Image to convert</label>
<input type="file" name="image" />
</div>
</section>
</body>
</html>

Save this file and open it in the browser. As you can see we have a very simple HTML file with the a file input called image.

Next we add a script tag before body to write the JS code. So it will look like this:

<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<script>
// code should go here
</script>
</body>
</html>

Create an empty JS file and dump the below code into it:

'use strict';

const JpgToPngConvertor = (() =>{
function convertor(imageFileBlob, options) {
options = options || {};

const defaults = {};
const settings = extend(defaults, options);

const canvas = document.createElement('canvas');
const ctx = canvas.getContext("2d");
const imageEl = createImage();
const downloadLink = settings.downloadEl || createDownloadLink();

function createImage(options) {
options = options || {};
const img = (Image) ? new Image() : document.createElement('img');

img.style.width = (options.width) ? options.width + 'px' : 'auto';
img.style.height = (options.height) ? options.height + 'px' : 'auto';

return img;
}

function extend(target, source) {
for (let propName in source) {
if (source.hasOwnProperty(propName)) {
target[propName] = source[propName];
}
}

return target;
}

function createDownloadLink() {
return document.createElement('a');
}

function download() {
if ('click' in downloadLink) {
downloadLink.click();
} else {
downloadLink.dispatchEvent(createClickEvent());
}
}

function updateDownloadLink(jpgFileName, pngBlob) {
const linkEl = downloadLink;
const pngFileName = jpgFileName.replace(/jpe?g/i, 'png');

linkEl.setAttribute('download', pngFileName);
linkEl.href = window.URL.createObjectURL(pngBlob);

// If there is custom download link we don't download automatically
if (settings.downloadEl) {
settings.downloadEl.style.display = 'block';
} else {
download();
}
}

function createClickEvent() {
if ('MouseEvent' in window) {
return new MouseEvent('click');
} else {
const evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window);
return evt;
}
}

function process() {
const imageUrl = window.URL.createObjectURL(imageFileBlob);

imageEl.onload = (e) => {
canvas.width = e.target.width;
canvas.height = e.target.height;
ctx.drawImage(e.target, 0, 0, e.target.width, e.target.height);
canvas.toBlob(updateDownloadLink.bind(window, imageFileBlob.name), 'image/png', 1);
};

imageEl.src = imageUrl;
if (settings.downloadEl) {
settings.downloadEl.style.display = 'none';
}
}

return {
process: process
};
}

return convertor;
})();

So what happens in the above script?

JpgToPngConvertor function has two parameters. The first is a File object which is inherited from Blob that is essentially binary data. So the process function creates a image element from the file blob using URL Api. Please note that we don’t even need to add this image to the DOM. In fact when we update the image source to the generated image URL, using the image onload event we can tell if the image is loaded.

When the image is loaded (again it is not added to the markup), we update our canvas element properties (canvas element also does not live in the DOM) based on the image dimensions. We then invoke canvas drawImage to add the image to the canvas.

Finally, we use canvas toBlob and set the mime to image/png to perform the conversion. The toBlob is async and therefore we use this callback function updateDownloadLink to get the PNG Blob data and do the download. Using an anchor element downloadLink (Once again not added to the HTML markup except for Firefox!) and the URL api we re-create a image url for the new PNG image and set the href of the anchor element to the url. To force download we add the download attribute to the anchor link and then dispatch a click event to force the download.

All good! Now it’s time to import this file to our HTML, we can name it convertor.js and add it to the markup:

<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<script src="convertor.js"></script>
<script>
// Todo: add code
</script>
</body>
</html>

Finally we add the file change event listener to be able to convert the image once it is selected:

<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<script src="convertor.js"></script>
<script>
// HTML image file field
const imageFileEl = document.querySelector('[name="image"]');

// Add file change event handler
imageFileEl.addEventListener('change', (event) => {
const jpgImageFileBlob = event.currentTarget.files[0];

// Validate
if (jpgImageFileBlob.type.match(/image\/jpe?g/i) !== null) {
JpgToPngConvertor(jpgImageFileBlob).process();
} else {
alert('Invalid JPG image file');
}
}, false);
</script>
</body>
</html>

And now we are ready to test. If you have saved the files, open the page on the browser and have a go. It should download the converted PNG version when you pick a JPG image. Alternatively, checkout the this demo: