4

First of all - The problem


I am developing a web application that enables the user to take a picture with their smartphone, tablet or just browse for a picture on their pc. I need to upload this file to my database, which is working perfectly IF the picture is small enough.

After experimenting a bit with certain photos, I found out that a smartphone takes a picture with a size of at least 1MB and this is way to big too upload to my database.

The uploading goes as following:
1. Convert the image to a Base64 encoded string.
2. Send the string in an array (contains string in pieces) to WebAPI
3. Join the strings into one and convert this into byte array.

I noticed that if a file is around 70-90kb, this would be the maximum size of a file that can be inserted in the database. The moment I have a file that is bigger then 100kb, the inserting fails.

So I am searching for a good resizing library to resize the picture that was chosen. I don't know if this is even possible, but everything has to be done client-side.

Technology


I am using Javascript/jQuery and AngularJS for the client side and VB.NET for my WebAPI. This is because I'm doing an internship and don't have another choice to use VB.NET.

But this has nothing to do with the issue, I just need to find a way to compress/resize/minimize the chosen file, so this can be uploaded to the database.

The upload is happening via the $http.post() from AngularJS.

If someone could advice a library, care to help a fellow programmer out with some basic example code? I mostly have trouble figuring out how to use a plugin or library, because I'm quite new to all this. And I would appreciate it if you guys could provide me with at least some information to get me on track.

Thanks in advance!

Sorry that I couldn't provide any code or something other, it's more an informative question than a coding problem. This might popup when I have a library that I can use. Also, if there are any remarks, I'll consider them, because I have a deadline and don't have much time left to start fixing small problems. Most of it works by now, except for that image problem.

hunch_hunch
  • 2,283
  • 1
  • 21
  • 26
Jorrex
  • 1,503
  • 3
  • 13
  • 32
  • 1
    Isn't this a server side task? I believe that this also works with browsers that can handle canvases. And even though I really like the description of what you're trying to achieve - it's offtopic asking for a tool, library etc on SO. You should ask more code specific questions instead. – urbz Apr 24 '15 at 13:26
  • 1
    Yes, I know that this isn't really a coding question. But from experience, SO saved my ass a few times with code and I wouldn't really know another place to ask these sort of questions. Google didn't really help out, because most of the tools I found were online converters and that isn't helping at all. I need my own implementation of such sort tool and maybe people on SO could advice something? And a server side task, well .. I have a website and a web api project in VS12. The only way to communicate between both projects is via angular. The image is uploaded at client side and not the server – Jorrex Apr 24 '15 at 13:29
  • I figured out what you want for a few personal projects. Define a canvas, draw your image on it and save it as jpeg. The final image dimension will be the same as the canvas. Edit - Oh, that's exactly Nicholas' answer below :) – Jeremy Thille Apr 24 '15 at 13:31
  • Just because I appreciate you being descriptive.. [**link**](https://github.com/blueimp/JavaScript-Load-Image#image-scaling) – urbz Apr 24 '15 at 13:33

1 Answers1

6

You can use the HTML5 Canvas element to blit your image on it and then resize it appropriately

You create a canvas on-the-fly and perform pixel operations on it - so don't worry it's not visible anywhere on the DOM - think of it like a virtual canvas

Here is a little script I've written a while ago(with some help from another S.O question which I don't really remember) - that allows you to do this - whilst keeping the aspect ratios of the image:

Arguments

  • You provide an image directly into it
  • You provide a maxWidth/maxHeight and it will preserve at least one of the 2
  • It allows you to also rotate the image by defining the degrees parameter(which would be really useful on mobile devices since different devices provide arbitrarily rotated images - you will find out about this the hard way soon if you are dealing with mobile devices)

Returns

  • It returns a Base64 string of the resized image

function resizeImg(img, maxWidth, maxHeight, degrees) {
  var imgWidth = img.width,
    imgHeight = img.height;

  var ratio = 1,
    ratio1 = 1,
    ratio2 = 1;
  ratio1 = maxWidth / imgWidth;
  ratio2 = maxHeight / imgHeight;

  // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
  if (ratio1 < ratio2) {
    ratio = ratio1;
  } else {
    ratio = ratio2;
  }
  var canvas = document.createElement("canvas");
  var canvasContext = canvas.getContext("2d");
  var canvasCopy = document.createElement("canvas");
  var copyContext = canvasCopy.getContext("2d");
  var canvasCopy2 = document.createElement("canvas");
  var copyContext2 = canvasCopy2.getContext("2d");
  canvasCopy.width = imgWidth;
  canvasCopy.height = imgHeight;
  copyContext.drawImage(img, 0, 0);

  // init
  canvasCopy2.width = imgWidth;
  canvasCopy2.height = imgHeight;
  copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


  var rounds = 1;
  var roundRatio = ratio * rounds;
  for (var i = 1; i <= rounds; i++) {


    // tmp
    canvasCopy.width = imgWidth * roundRatio / i;
    canvasCopy.height = imgHeight * roundRatio / i;

    copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

    // copy back
    canvasCopy2.width = imgWidth * roundRatio / i;
    canvasCopy2.height = imgHeight * roundRatio / i;
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

  } // end for

  canvas.width = imgWidth * roundRatio / rounds;
  canvas.height = imgHeight * roundRatio / rounds;
  canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


  if (degrees == 90 || degrees == 270) {
    canvas.width = canvasCopy2.height;
    canvas.height = canvasCopy2.width;
  } else {
    canvas.width = canvasCopy2.width;
    canvas.height = canvasCopy2.height;
  }

  canvasContext.clearRect(0, 0, canvas.width, canvas.height);
  if (degrees == 90 || degrees == 270) {
    canvasContext.translate(canvasCopy2.height / 2, canvasCopy2.width / 2);
  } else {
    canvasContext.translate(canvasCopy2.width / 2, canvasCopy2.height / 2);
  }
  canvasContext.rotate(degrees * Math.PI / 180);
  canvasContext.drawImage(canvasCopy2, -canvasCopy2.width / 2, -canvasCopy2.height / 2);


  var dataURL = canvas.toDataURL();
  return dataURL;
}

And here's a working code snippet that allows you to upload from your filesystem and resize on the fly:

/*
-------------------------------
-------HANDLE FILE UPLOAD------
-------------------------------
*/

var input = document.getElementById('input');
input.addEventListener('change', handleFiles);

function handleFiles(e) {
  var img = new Image;
  img.src = URL.createObjectURL(e.target.files[0]);
  img.onload = function() {
    var base64String = resizeImg(img, 300, 300, 0); //HERE IS WHERE THE FUNCTION RESIZE IS CALLED!!!!
    alert(base64String);
    document.getElementById('previewImg').src = base64String;
  }
}



/*
-------------------------------
-------RESIZING FUNCTION-------
-------------------------------
*/


function resizeImg(img, maxWidth, maxHeight, degrees) {
  var imgWidth = img.width,
    imgHeight = img.height;

  var ratio = 1,
    ratio1 = 1,
    ratio2 = 1;
  ratio1 = maxWidth / imgWidth;
  ratio2 = maxHeight / imgHeight;

  // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
  if (ratio1 < ratio2) {
    ratio = ratio1;
  } else {
    ratio = ratio2;
  }
  var canvas = document.createElement("canvas");
  var canvasContext = canvas.getContext("2d");
  var canvasCopy = document.createElement("canvas");
  var copyContext = canvasCopy.getContext("2d");
  var canvasCopy2 = document.createElement("canvas");
  var copyContext2 = canvasCopy2.getContext("2d");
  canvasCopy.width = imgWidth;
  canvasCopy.height = imgHeight;
  copyContext.drawImage(img, 0, 0);

  // init
  canvasCopy2.width = imgWidth;
  canvasCopy2.height = imgHeight;
  copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


  var rounds = 1;
  var roundRatio = ratio * rounds;
  for (var i = 1; i <= rounds; i++) {


    // tmp
    canvasCopy.width = imgWidth * roundRatio / i;
    canvasCopy.height = imgHeight * roundRatio / i;

    copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

    // copy back
    canvasCopy2.width = imgWidth * roundRatio / i;
    canvasCopy2.height = imgHeight * roundRatio / i;
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

  } // end for

  canvas.width = imgWidth * roundRatio / rounds;
  canvas.height = imgHeight * roundRatio / rounds;
  canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


  if (degrees == 90 || degrees == 270) {
    canvas.width = canvasCopy2.height;
    canvas.height = canvasCopy2.width;
  } else {
    canvas.width = canvasCopy2.width;
    canvas.height = canvasCopy2.height;
  }

  canvasContext.clearRect(0, 0, canvas.width, canvas.height);
  if (degrees == 90 || degrees == 270) {
    canvasContext.translate(canvasCopy2.height / 2, canvasCopy2.width / 2);
  } else {
    canvasContext.translate(canvasCopy2.width / 2, canvasCopy2.height / 2);
  }
  canvasContext.rotate(degrees * Math.PI / 180);
  canvasContext.drawImage(canvasCopy2, -canvasCopy2.width / 2, -canvasCopy2.height / 2);


  var dataURL = canvas.toDataURL();
  return dataURL;
}
/*
-------------------------------
-------UNNECESSARY CSS---------
-------------------------------
*/

@import url(http://fonts.googleapis.com/css?family=Lato);
 .container {
  margin: 0 auto;
  width: 400px;
  height: 400px;
  box-shadow: 1px 1px 1px 1px gray;
}
h3,
h4,
h5,
h6 {
  margin: 4px !important;
}
.container,
.container * {
  display: block;
  margin: 12px auto;
  font-family: 'Lato';
}
.header {
  background-color: #2196F3;
  padding: 12px;
  color: #fff;
}
.container input {
  width: 128px;
  height: 32px;
  cursor: pointer;
}
.container img {
  display: block;
  margin: 12px auto;
}
<div class="container">
  <div class="header">
    <h3>Choose a file</h3>
    <h6>and I will alert back to you the base64 string of it's resized version</h6>
  </div>
  <input type="file" id="input" />
  <hr>
  <h5>Image Preview:</h5>
  <img id="previewImg" src="" />

</div>
nicholaswmin
  • 21,686
  • 15
  • 91
  • 167
  • Thanks a lot! I'll definitely check this out! I never knew that the canvas-element was able to preserve the ratios. I'll check it out right away! – Jorrex Apr 24 '15 at 13:32
  • 1
    The canvas element does not preserve the aspect ratio - it's the code itself that does this - the canvas element is simply an API you can use to perform pixel/drawing operations – nicholaswmin Apr 24 '15 at 13:34
  • Oh okay ;) +1'd for the info, because I never really worked with the graphical/visual side of HTML. This is the first time and it's not that easy tbh. – Jorrex Apr 24 '15 at 13:36
  • I did find a little bug in your code. When I want to access your function, at the very start it takes the width and height from the given image. These functions do work without the '()' at the end. I just wanted to point this out. – Jorrex Apr 24 '15 at 14:01
  • Hmm, I'm not sure I get what you're saying – nicholaswmin Apr 24 '15 at 14:05
  • Your function requires an image element, right? the first thing you do is get the width and height from that element. For me it didn't work if I didn't change img.width to img.width() and the same for the height. Unless I am mistaken and it's not an image element you're passing to resizeImg()? – Jorrex Apr 24 '15 at 14:09
  • Those are needed values in order to calculate the resizing ratios below - check the snippet I just embedded in my answer – nicholaswmin Apr 24 '15 at 14:11
  • Weird, when I ran your code, VS12 wouldn't let me continue because he didn't recognize the img.width and img.height. Oh well, sorry for the inconvenience ;) your code is just fine, VS12 is bugging once more :/ – Jorrex Apr 24 '15 at 14:13
  • 1
    Well, this is definitely what I want and need. Thanks for all the extra info about canvases. You really saved my ass mate! :) – Jorrex Apr 24 '15 at 14:16
  • 2
    It should be noted that *just* using canvas will not create a pixel perfect down scaled image. It will produce noise in the photo. For example is you reduce the image by 1/2 then 4 pixels become one. Canvas does not blend those pixels but picks one at random. For more details about how to do pixel perfect down scaling using canvas see this article: http://stackoverflow.com/questions/18922880/html5-canvas-resize-downscale-image-high-quality – Enzey Apr 24 '15 at 14:57
  • @Enzey nice, I didn't know that! – nicholaswmin Apr 24 '15 at 14:58