0

I get this code that compress images from input element, this seems to works fine, the image is reduced at MAX_WIDTH, but my question is about the real size of the compressed image, i created a new File from the output (srcEncoded) and the size seems to be bigger than the original file. What's wrong with this? Thanks in advance.

function process() {

  const file = document.querySelector("#upload").files[0];
  console.log(file)
  console.log("file size:", file['size']);
  if (!file) return;

  const reader = new FileReader();
  reader.readAsDataURL(file);

  reader.onload = function(event){
    const imgElement = document.createElement("img");
    imgElement.src = event.target.result;
    document.querySelector("#input").src = event.target.result;

    imgElement.onload = function(e) {
      const canvas = document.createElement("canvas");
      const MAX_WIDTH = 350;

      const scaleSize = MAX_WIDTH / e.target.width;
      canvas.width = MAX_WIDTH;
      canvas.height = e.target.height * scaleSize;

      const ctx = canvas.getContext("2d");
      ctx.drawImage(e.target, 0, 0, canvas.width, canvas.height);
      console.log(e.target);
      const srcEncoded = ctx.canvas.toDataURL(e.target, "image/jpeg");
      console.log(srcEncoded)
      document.querySelector("#output").src = srcEncoded;
      let outputFile = new File([srcEncoded], {type: 'image/jpg'});
      console.log(outputFile)
    }
  }
}
body {
  padding: 20px;
  background: #e6f2ff;
}

input,
button {
  font-size: 60px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
    Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles.css" />
    <title>Resize Images Before Upload</title>
  </head>
  <body>
    <input type="file" id="upload" accept=".jpg, .jpeg, .png" />
    <button onclick="process()">Process</button>
    <div>
      <img id="input" />
    </div>
    <div>
      <img id="output" />
    </div>
    <script src="app.js"></script>
  </body>
</html>
iAmOren
  • 2,760
  • 2
  • 11
  • 23
sonEtLumiere
  • 4,461
  • 3
  • 8
  • 35
  • `if (!file) return` should be before the `console.log(.... file['size'])` - I'm sure it's for debugging only. – iAmOren Sep 28 '20 at 22:47
  • iAmOren yes you're right – sonEtLumiere Sep 28 '20 at 22:49
  • A data URL is actually `base64` encoding for the image, which in the case of JPEG actually encodes 4 bytes per pixel (well, base64 does, not JPEG), where it only needs 3. I don't think there are Web based API's that will give you a better result. You cannot write _actual_ JPEG files using Web API's, as there are no real 'file' APIs. Maybe if using a Buffer, but then you would need some NodeJS or something to save the actual file. The best results would be PNG in, PNG out, because PNG actually encodes the four bytes, so you would see the saving (because of the alpha channel, btw) – somethinghere Sep 28 '20 at 22:49
  • Actually i'm working on Angular, i can use a npm library for this but i was looking for a vanilla JS solution – sonEtLumiere Sep 28 '20 at 22:51
  • 1
    Yeah but I mean _web API_, not _node_. Node can write a `buffer` to the _file system_, which would only contain the necessary bits and nothing extra. But you can't do that on the web. You could send the data to a server, let node do its thing and let it spit out a file (that's what something like `ctx.getImageData` is for, you can feed that returned array buffer to a JPEG compressor). But the Web itself won't do that. Not in a browser. – somethinghere Sep 28 '20 at 22:53
  • 1
    `ctx.canvas.toDataURL(e.target, "image/jpeg");` should just be `ctx.canvas.toDataURL('image/jpeg');`, of course, since `.png` is the default and lossless compression, I would just use that. If you want to save a canvas file to the Server it's probably best to use `canvas.toBlob` like the answer [here](https://stackoverflow.com/questions/48195480/sending-canvas-todataurl-as-formdata). – StackSlave Sep 28 '20 at 23:02
  • Thanks, probably i'll use an Angular library for this. – sonEtLumiere Sep 28 '20 at 23:10
  • @somethinghere of course there is a File API in web APIs, the question itself is using a FileReader and reading a File from user's disk... – Kaiido Sep 29 '20 at 00:16
  • @sonEtLumiere you don't even need a FileReader here, use a blob:// URI pointing to your file (URL.createObjectURL), then use canvas.toBlob to generate a binary file, but beware you will loose a lot of quality doing this on front-end and may not even win in file size. You'd be better doing this server side with a proper image tool. – Kaiido Sep 29 '20 at 00:19
  • @Kaiido I never said there wasn't _a_ file API, just no direct access to _the_ file system. A file system, yeah, that temporary one. It's not like nodes `fs` which would allow you to do actual filesystem things like folders and files. I did forget about blobs, they could indeed come in handy for this. Better than `base64` anyhow. – somethinghere Sep 29 '20 at 00:45
  • 1
    @some You said "*You cannot write actual JPEG files using Web API's, as there are no real 'file' APIs.*". This is plain wrong. A file is a way to point at binary data, there are many such objects available in web APIs, ArrayBuffers,Blobs,Files,Streams. Yes web APIs can access the filesystem and directories, for reading with an and for writing there is a [Native File System API](https://wicg.github.io/file-system-access/). Finally, the pixel format is completely irrelevant in both jpg and png files, both binary formats use full bytes to store what is the compressed image data. – Kaiido Sep 29 '20 at 01:12
  • So what is the best practice to send image data to a server? idk why my answer is closed now, im just looking for the best way to compress an image on client side and optimize the upload (i live in argentina where internet connections are not the best). – sonEtLumiere Sep 29 '20 at 01:18
  • Read the answer there, it explains the flaws of doing it on front-end. If you still need to do it this way, it will still help you make your own judgement on what you want to trade off, we can't make this choice for you. But anyway, if you use a canvas, then use its `.toBlob()` method to generate a Blob that you'll send to your server (not wasting 34% of the file size for nothing), and if the original file is a JPEG, then use `image/jpeg` with a quality **you** need to choose, and keep using `image/png` if the original image was a PNG. – Kaiido Sep 29 '20 at 02:08
  • @Kaiido Okay, I meant file system. Technically you can turn anything into a file, it's just arbitrary. But yeah, you're right, that was probably worded badly. Either way, I agree, no solution that is acceptable here. This kind-of feels like the onus is on the person uploading the file: determine a file size limit and require the user to adhere to it. Otherwise, noe solution is really desirable for this use case I think. – somethinghere Sep 29 '20 at 02:24

0 Answers0