Reduce an image before upload with Canvas

We upload images with a form using an input of type file.
What happens if the image you want to upload must not exceed a certain size?

In most cases, what you do is send the image through the input and once it is uploaded to the server, it checks the size of it and destroys it if the size is bigger thant the expected. How many times have you not seen “uploading” in a progress bar, and then and error caused by the size of the image.

Canvas can reduce the size of the image before the upload.
The idea is to set a fixed size of pixels for the width (Nx) and height (Ny) of the image. For simplicity, we could make Nx and Ny equal and define N = Nx = Ny. This would ensure that any image that has length and height below N, would take forever unless your image NxN pixel count.

Assuming that we have an input file that we use to upload images:

<input id="fbBrowseBtn" type="file" name="photo[]" multiple>

With the next JS code we can get the image, reduce it, send it and control the progress:

var files = document.getElementById('fbBrowseBtn').files;
resizeAndUpload(files[0], uploaded, progressBar);
 
function resizeAndUpload(file, callback, progress)
{
	var reader = new FileReader();
	reader.onloadend = function() {
		var tempImg = new Image();
		tempImg.onload = function() {
			// Check with aspect, how the reduction will be
			// MAX_IMAGE_SIZE_PROCESS is N
			var MAX_WIDTH = MAX_IMAGE_SIZE_PROCESS;
			var MAX_HEIGHT = MAX_IMAGE_SIZE_PROCESS;
			var tempW = tempImg.width;
			var tempH = tempImg.height;
			if (tempW > tempH) {
				if (tempW > MAX_WIDTH) {
					tempH *= MAX_WIDTH / tempW;
					tempW = MAX_WIDTH;
				}
			} else {
				if (tempH > MAX_HEIGHT) {
					tempW *= MAX_HEIGHT / tempH;
					tempH = MAX_HEIGHT;
				}
			}
			// We create a canvas for the reduced image and draw it in it
			var resizedCanvas = document.createElement('canvas');
			resizedCanvas.width = tempW;
			resizedCanvas.height = tempH;
			var ctx = resizedCanvas.getContext("2d");
			ctx.drawImage(this, 0, 0, tempW, tempH);
			var dataURL = resizedCanvas.toDataURL("image/jpeg");
 
			// Use the dataURL from Canvas to a Blob object
			// Send the Blob object with Ajax
			var file = dataURLtoBlob(dataURL);
			var fd = new FormData();
			fd.append("photo", file);
 
			$.ajax({
				url: <<url del endpoint que se encarga de la subida>>,
				type: "POST",
				data: fd,
				processData: false,
				contentType: false,
				dataType: 'json',
				xhr: function() {
					var xhr = new window.XMLHttpRequest();
					xhr.upload.addEventListener("progress", function(evt) {
						if (evt.lengthComputable) {
							// Percentage of the progress
							var percentComplete = evt.loaded / evt.total;
							progress(percentComplete * PERCENT_UPLOAD * 0.01);
						}
					}, false);
					return xhr;
				}
			}).done(function(respond) {
				// Upload finished
				callback(respond);
			});
		};
		tempImg.src = reader.result;
	}
	reader.readAsDataURL(file);
}
 
function dataURLtoBlob(dataURL)
{
	// Decode dataURL
	var binary = atob(dataURL.split(',')[1]);
	// Se transfiere a un array de 8-bit unsigned
	var array = [];
	var length = binary.length;
	for(var i = 0; i < length; i++) {
		array.push(binary.charCodeAt(i));
	}
	return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
 
function uploaded(response)
{
	// Called when done
}
 
function progressBar(percent)
{
	// Called in progress
}

The end-point would be something like this:

$data = array();
$data['success'] = false;
// Check if its an image
$size = getimagesize($_FILES['photo']['tmp_name']);
if($size) {
	if(	(
		$_FILES['photo']['type'] == 'image/gif' || 
		$_FILES['photo']['type'] == 'image/jpeg' || 
		$_FILES['photo']['type'] == 'image/jpg' || 
		$_FILES['photo']['type'] == 'image/pjpeg' || 
		$_FILES['photo']['type'] == 'image/png' || 
		$_FILES['photo']['type'] == 'image/x-png')) {
			// It is, save it and get the data from the key 'photo' of $_FILES
			$newFile = "path where the image is going to be saved";
			if (move_uploaded_file($_FILES['photo']['tmp_name'], $newFile)) {
				// Ok, return the location of the image
				$data['success'] = true;
				$data['image'] = $newFile;
				$data['msg'] = lang('upload_success');
			} else {
				// Error
				$data['msg'] = lang('upload_error');
			}
	} else {
		// It's not a common image
		$data['msg'] = lang('upload_error_extension');
	}
 
} else {
	// It's not an image
	$data['msg'] = lang('upload_error_noimage');
}
echo json_endode($data);

I used this method in a project of mine: Let’s Pixel.

This entry was tagged as

Leave a Reply

Your email address will not be published.