2

I'm having a hard time to understand how to work with canvas elements in JavaScript.

I'm implementing a resize feature where user can resize image inside the lightbox. The lightbox will launch after preview image is been clicked. Inside the lightbox element, in addition of the image itself, there are two input fields for width and height.

The objective is to generate a copy of the original image in base64 format and send it to the server along with given width and height as query parameters and let the server side do the resize operation (I'm using PHP for my back end) or even better, let JavaScript do the resize operation in the front end and return new, resized image ready to be sent to the server via ajax.

The problem is that I don't exactly know how to deal with dynamically created canvas elements and how can I use it to resize my image in the front end.

Underneath is what I've tried so far with poor results:

index.html (basic HTML elements and the lightbox effect are omitted)

<!-- input fields for width and height -->
<div class="container">
    <div class="form-inline">
        <div class="form-group">
            <input type="number" class="form-control" id="width" placeholder="px">
        </div>
        <div class="form-group">
            <input type="number" class="form-control" id="height" placeholder="px">
        </div>
        <button id="resize" type="button" class="btn btn-primary">Resize</button>
    </div>
</div>

<!-- preview image -->
<div class="container">
    <img src="img/img1.jpg" alt="" class="img-responsive" id="preview">
</div>

<script type="text/javascript">
    button = document.getElementById("resize");

    button.addEventListener("click", function() {
        // get image
        const image = document.getElementById('preview');

        // create a canvas element
        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext("2d");

        canvas.width = image.width; // destination canvas size
        canvas.height = canvas.width * image.height / image.width;

        ctx.drawImage(image, 0, 0, image.width, image.height);
        var canvasData = canvas.toDataURL("image/jpeg");


        // ajax call
        var xhr = new XMLHttpRequest();
        var params = "photo=" + encodeURIComponent(canvasData) + "&name=" + encodeURIComponent(name) + "&width="+ encodeURIComponent(width) + "&height=" + encodeURIComponent(height);
        // send request
        xhr.open("POST", "admin.php?" + params);
        xhr.send();
    });
</script>

admin.php (nothing fancy here, just decode the image and write it to a folder)

<?php

if(isset($_POST['photoUpload']) && isset($_POST['name'])) {
// decode base64 formatted image
$data = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $_POST['photoUpload']));

if(isset($_POST['width'] && $_POST['height'])) {
    // resize image here using imagemagick
}

// write file to "img" directory
file_put_contents(dataPath.'img/'.$_POST['name'], $data);

// done
exit('OK|'.dataPath.'img/'.$_POST['name']);
}

Any tips, tricks and advices are much appreciated !

B_CooperA
  • 659
  • 1
  • 9
  • 28
  • this question is answered here: https://stackoverflow.com/questions/19262141/resize-image-with-javascript-canvas-smoothly – saman Nov 27 '18 at 19:35

1 Answers1

5

You can resize an image also at the client-side. The example code below uses an image loaded from the user's local system, to run the example without need to worry about CORS issues. The snippet also stores the image as a Blob object, which can be posted to the server if needed.

// Creates a canvas containing a resized image
function resizeImage(img) {
  var canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d'),
    oWidth = img.naturalWidth,
    oHeight = img.naturalHeight,
    ratio = oWidth / oHeight,
    width = (ratio > 1) ? Math.min(200, oWidth) : Math.min(100, oWidth),
    height = Math.round(width / ratio);
  canvas.width = width;
  canvas.height = height;
  canvas.className = 'temp-cnv';
  document.body.appendChild(canvas);
  ctx.drawImage(img, 0, 0, width, height);
  return canvas;
}

// Define UI elements
var img = document.getElementById('img'),
  loadBut = document.getElementById('load'),
  resizeBut = document.getElementById('resize'),
  resizedImage; // This will be sent to the server

// Creates a blob and attaches it to an image element
resizeBut.addEventListener('click', function() {
  var canvas;
  if (img.src === 'https://stacksnippets.net/js') {
    return; // Quit, no image loaded
  }
  canvas = resizeImage(img);
  canvas.toBlob(function(blob) {
    img.src = URL.createObjectURL(blob);
    resizedImage = blob;
    canvas.parentElement.removeChild(canvas);
  }, 'image/jpeg', 0.99);
});

// Reads an image from the user's local system
loadBut.addEventListener('change', function(e) {
  var file = new FileReader();
  file.addEventListener('load', function() {
    img.src = file.result;
  });
  file.readAsDataURL(e.target.files[0]);
});
.temp-cnv {
  display: none;
}
<input type="file" id="load">
<button id="resize">Resize</button>
<br>
<img src="" id="img">

resizeImage function creates a temporary canvas element, and calculates the dimensions for that canvas. Here the image is always shrinked, but you can implement your own resizing algorithms. img.naturalWidth/Height properties contain the original size of the image.

When the size of the canvas has been correctly set, the image is drawn into the canvas, at this point the actual resizing happens. Then the canvas is returned to the caller, and assigned to the local canvas variable.

Then a Blob object is created from the newly-created canvas. toBlob function takes a callback function, mime-type and an optional quality parameter (for JPEGs only) as arguments. The callback function attaches the canvas into the image, and stores the created Blob object to resizedImage variable for the further use, and finally removes the temporary canvas element.

Good to read at MDN:

ctx.drawImage method
Blob object
Canvas.toBlob method
CORS enabled images

If you're going to send the resized image to the server, you can create a FormData object, and append the image to that object. Then post the object to the server with AJAX. Something like this:

var xhr = new XMLHttpRequest(),
    form = new FormData();
form.append('imageBlob', resizedImage); // resizedImage is the Blob object created in the first snippet
form.append('imageName', 'THE_NAME_OF_THE_IMAGE');
xhr.addEventListener('load', function (data) {
    // AJAX response handler code
});
xhr.open('POST', 'THE_URL_TO_POST_TO');
xhr.send(form);

Notice, that the POST parameters (the FormData object in this case) are attached as an argument of xhr.send call.

Teemu
  • 22,918
  • 7
  • 53
  • 106
  • 1
    Thank u. I was able to figure it out by myself but decided to accept this as an answer since it's well explained. Cheers! :) – B_CooperA Nov 29 '18 at 09:05